Skip to content

About

Compose red team infrastructure and Active Directory ranges on a canvas, then compile to a complete Terraform and Ansible working directory you run yourself. Validate TTPs and tooling at production-ops level. GCP, AWS, and Azure.

Topics

Resources

Security policy

Stars

171 stars

Watchers

0 watching

Forks

Latest commit

Β 

History

369 Commits

Folders and files

Repository files navigation

redStackPRO: red team infrastructure and cyber ranges

MIT license version 0.10.0 providers GCP, AWS, and Azure status prerelease beta Terraform and Ansible

redStackPRO

A visual canvas for red team infrastructure and cyber ranges. Compose a topology, export a complete, runnable working directory of Terraform and Ansible, and deploy it from your own machine. redStackPRO never holds your cloud credentials.

redStackPRO is a validation range for TTPs and tooling at production-operations level, not a training or CTF product.

Important

redStackPRO is in prerelease (beta). Features are still moving before 1.0. GCP, AWS, and Azure are tested end to end; Proxmox and ESXi are on the roadmap. Expect rough edges, and pin to a released version if you need stability.

Hit a rough edge, or have feedback? Open an issue at Issues. If it was a deploy, attach the scrubbed logs/deploy-*.log the run wrote (it records versions, provider, and where it stopped, with secrets removed) so it can be analyzed fast. Your reports shape the release.

redStackPRO puts attack infrastructure and target ranges on the same canvas. The two canvas modes are Offense (attack infrastructure) and Defense (defensive AD ranges); the export names its handoff OFFENSE-BRIEFING.md or DEFENSE-BRIEFING.md to match.

Split horizon C2, attack infrastructure: two front doors that do not share a fate, Apache fronting Sliver and Nginx fronting Mythic and Adaptix, each redirector on its own peered network, with the teamservers, collector, and operators behind a jumpbox.

Split horizon C2 on the canvas: two redirector networks, Apache fronting Sliver and Nginx fronting Mythic and Adaptix, over a shared C2 subnet with the teamservers, an OpenSearch collector, operators and a jumpbox.

The two errors in the top bar are the canvas validating as you build: the redirectors have no domain yet, which you set to one you control before deploying. It is caught here, not at apply time.

Multiple operators, one stack. An attack-infrastructure jumpbox can name a list of operators (handle plus role), and each gets a Guacamole portal login on the shared lab password. Set the jumpbox's access_mode to wireguard or openvpn (default is a public portal) and every operator also gets a personal VPN credential, generated on the jumpbox during provisioning: the keys never enter the export, and each operator's client config, which holds their private key, waits on the jumpbox for you to fetch and hand over. The portal logins of the operators named in the topology share the one lab password, so per-operator isolation comes from each operator's own VPN credential, not the portal login; individual portal passwords for them are on the roadmap. On a VPN access mode the portal closes to the internet and moves behind the tunnel, while SSH stays open so the admin can keep deploying and managing the box, and sudo rsp-operator add <handle> (also remove and list) adds a teammate to the running jumpbox, printing their VPN credential and a portal password of their own. See the wiki Deploying a Range.

Harbor, a target range: a small corporate forest, a root domain and a child over a parent-child trust, with the ordinary path from a phished workstation to the forest.

The Harbor range on the canvas: the harbor and freight domains over an intra-forest trust, four Windows hosts and a jumpbox.

GOAD, the full lab: three domains across two forests, five machines and their trusts, the reference range the written solution follows.

The GOAD lab on the canvas: sevenkingdoms, north and essos across two forests, their intra and cross-forest trusts, five machines and a jumpbox.

Important

The export is the boundary. The canvas generates files; you run them under your own credentials. redStackPRO never deploys anything and never holds your cloud credentials or keys.

Caution

Authorized use only. redStackPRO builds offensive infrastructure and deliberately vulnerable ranges. Use it only in lab environments you own or are explicitly authorized to test, never against systems you do not have written permission for.


🧭 Status

Pre-release, with more to come before 1.0. The pipeline itself works end to end: a topology compiles to Terraform and Ansible, and the export deploys.

Provider State
GCP, AWS Supported and tested end to end, for both target ranges and attack infrastructure.
Azure Supported and tested end to end, for both target ranges and attack infrastructure (new in 0.10.0).
Proxmox, ESXi On the roadmap, not yet supported.

🐳 Run with Docker

The whole canvas in one container, the API and the web app on one port:

docker compose up                    # http://127.0.0.1:8000
docker compose up --build            # build the image from this checkout instead

Compose runs the image it pins, ghcr.io/devzero-security/redstackpro:0.10.0, pulling it from GHCR and building it from this checkout only when the pull fails. Or run the published image without compose (:latest and :0.10.0 are the same build):

docker run -p 8000:8000 -v redstackpro-data:/data \
  ghcr.io/devzero-security/redstackpro:latest

The canvas listens on 8000 inside the container. To serve it on a different host port, change the left half of the mapping (-p 8787:8000), or set REDSTACKPRO_PORT for compose (REDSTACKPRO_PORT=8787 docker compose up in bash, $env:REDSTACKPRO_PORT=8787; docker compose up in PowerShell).

Compose also carries an optional Postgres backend for a shared deployment:

REDSTACKPRO_DATABASE_URL=postgresql+psycopg://redstackpro:redstackpro@db:5432/redstackpro \
  docker compose --profile postgres up

The image is the composition layer only. It does not carry Terraform or Ansible and never holds your cloud credentials: you run the export it produces from your own machine, exactly as in the from-source flow below.


βš™οΈ Run from source

Python 3.11 or newer, and Node 24 for the canvas.

git clone https://github.com/devZero-Security/redStackPRO.git
cd redStackPRO
python -m venv .venv
. .venv/bin/activate            # bash (Linux, macOS)
.venv\Scripts\Activate.ps1      # PowerShell (Windows)
pip install -e ".[dev]"

The canvas is two processes, the API and the web app. Start the API:

redstackpro serve                    # the API, http://127.0.0.1:8000

It keeps that terminal busy. In a second terminal, from the repo root:

cd frontend
npm install
npm run dev                          # the canvas, http://localhost:5173

redstackpro serve --port 8787 moves the API to a different port. Point the canvas dev server at it by starting it with the variable set:

REDSTACKPRO_API=http://127.0.0.1:8787 npm run dev               # bash
$env:REDSTACKPRO_API = 'http://127.0.0.1:8787'; npm run dev     # PowerShell

Open http://localhost:5173, use Load blueprint (the button, or the mode palette) to open a shipped starting point, choose your cloud in the toolbar provider selector (GCP, AWS, or Azure), open the Export tab (it compiles as you go), then Download. You get a zip of the working directory described below. Extract it to its own folder and run every step from there; the Export tab lists the steps for your cloud and for the machine you deploy from (Windows, or macOS and Linux).

Or skip the canvas entirely and compile a shipped blueprint from the command line, same compiler, same output:

redstackpro compile frontend/public/goad/goad-light.json -o export

The command line defaults to GCP. Pass --provider aws for AWS or --provider azure for Azure. -o replaces the folder it writes to, but refuses one that holds a deployed range's Terraform state (terraform/terraform.tfstate) or a key in keys/, since deleting either leaves a running range with nothing to tear it down from: pick a new folder, or pass --force to replace it anyway.

That writes about 200 files: Terraform for the cloud, Ansible for everything that happens on the boxes, a deploy.sh, a DEPLOYMENT-GUIDE.md with the steps for this export, and a DEFENSE-BRIEFING.md telling you the credentials and what is planted where. goad-light is a two-domain Active Directory range: sevenkingdoms and its child north, a parent-child trust in one forest, two domain controllers and a member server, plus a jumpbox.

Important

Two things before you deploy. Your cloud identity needs permission to create the resources the export builds (VPCs or networks, subnets, security groups or firewall rules, instances, elastic or static IPs), and your machine needs Terraform 1.5 or newer, ssh, tar, Python 3.8 or newer, and your cloud's CLI (gcloud, aws, or az): deploy.sh checks your login with it and manage.sh runs through it. redStackPRO generates the code and installs none of that for you; Ansible installs itself on the jumpbox. On Windows, Git for Windows supplies ssh, tar, and Git Bash, and winget installs the cloud CLI (open a new PowerShell window afterwards so PATH picks it up):

winget install -e --id Google.CloudSDK      # GCP
winget install -e --id Amazon.AWSCLI        # AWS
winget install -e --id Microsoft.AzureCLI   # Azure

On macOS and Linux, follow the CLI's own install steps, linked from the wiki Cloud Prerequisites.

New to the AWS CLI, gcloud, or the Azure CLI? Quick setup for whichever you target:

AWS

aws configure
aws sts get-caller-identity

Attach the AmazonEC2FullAccess managed policy to the IAM user or role you configure. Every shipped blueprint also turns on auto-stop, which on AWS creates an IAM role and an EventBridge Scheduler schedule, so the identity also needs iam:CreateRole, iam:PutRolePolicy, iam:PassRole, and scheduler:CreateSchedule, plus the read and delete actions listed in the wiki Cloud Prerequisites. Or turn auto-stop off in Topology settings before you export.

GCP

gcloud auth login
gcloud auth application-default login
gcloud config set project <project-id>
gcloud services enable compute.googleapis.com --project <project-id>

Grant your account roles/compute.admin on the project.

Azure

az login
az account set --subscription <subscription-id>

Your identity needs Contributor on the subscription. Set subscription_id in deploy.tfvars. A service principal in ARM_CLIENT_ID, ARM_CLIENT_SECRET, ARM_TENANT_ID, and ARM_SUBSCRIPTION_ID also works for deploy.sh, but manage.sh status, start, stop, and teardown --force need az installed and logged in. Before apply, deploy.sh runs a read-only preflight that checks VM sizes, vCPU and public IP quotas, and resource provider registration, and stops before anything is created if the subscription cannot take the range. Resource providers, quotas, and size restrictions are in the wiki Cloud Prerequisites.

Workstations on Azure (Windows 11 by default, or Windows 10, which is past end of support) boot Microsoft's client images, which Microsoft licenses only for customers with a qualifying license, for example Windows Enterprise E3 or E5 per user, a Microsoft 365 plan that includes it, or a Visual Studio subscription for dev and test. redStackPRO does not set license_type, so license eligibility is yours to confirm; see Microsoft's page.

Full permission tables, install links, and the GCP vCPU quota note (CPUS_ALL_REGIONS, default 32 per project) are in the wiki Cloud Prerequisites. See Getting Started for the full checklist.

To deploy it, work from the export folder: make an SSH key for the range hosts in keys/ (the deploy also finds ~/.ssh/id_ed25519), fill in deploy.tfvars (at the export root: at least operator_source_ranges, the IP you connect from as a /32, plus project on GCP or subscription_id on Azure), and run deploy.sh:

cd export
ssh-keygen -t ed25519 -f keys/id_ed25519 -N ""
bash deploy.sh

deploy.sh copies deploy.tfvars into terraform/terraform.tfvars at apply time and aborts if deploy.tfvars is missing, so edit the root file, not the one under terraform/.

On Windows, use PowerShell and run .\deploy.ps1 instead: bash deploy.sh at a PowerShell prompt launches WSL, a different filesystem with different credentials. Make the key without -N and press Enter twice when it asks for a passphrase: the deploy needs a key with no passphrase, and Windows PowerShell 5.1 drops an empty -N '', so ssh-keygen stops without writing a key.

ssh-keygen -t ed25519 -f keys\id_ed25519
.\deploy.ps1

deploy.ps1 and manage.ps1 are unsigned, so PowerShell's execution policy has to allow them.

See the wiki Deploying a Range for detail.

deploy.sh applies the Terraform and then provisions from the range's own jumpbox, because the managed hosts sit on a private subnet nothing else can reach. Your cloud credentials stay on your machine throughout. It opens on the wordmark and a title box naming the topology, provider, and region, then prints numbered phases (seven, or eight on Azure with the preflight first), each ending on an ok line. Terraform's output is condensed on screen to the plan line and one line per finished resource, with any error in full. The run finishes on a DONE box (the portal address, the command that prints the logins, a one-line ssh command, rsp-watch, that follows ~/provision/run.log on the jumpbox and returns when the run ends, the expected time, and the log path), or on a FAILED box naming the phase, the reason, and the exit code. Colour is for the terminal only; set NO_COLOR to turn it off.

The opening of a GOAD-Light deploy on Azure in PowerShell: the redStackPRO wordmark, a DEPLOY GOAD-Light title box, the preflight reporting 4 VMs on Standard_D2as_v7 at 8 of 10 vCPU, and the Terraform apply plan of 41 resources with the first ones created.

Every run writes a plain-text, secret-scrubbed log to logs/deploy-<UTC timestamp>.log in the export, with everything Terraform printed. It ends on a result: line, result: ok or result: FAILED (exit N) in phase .... If a deploy fails or a range comes up wrong, attach the newest one to a GitHub issue; the FAILED box prints its path and the link.

Check it worked. The export ships verify.py next to deploy.sh. It needs PyYAML on your machine: python -m pip install pyyaml on Windows, python3 -m pip install pyyaml on macOS and Linux (on Debian and Ubuntu, sudo apt install python3-yaml or a venv if pip refuses). Run it against the deployed range to confirm the path end to end. Windows:

python verify.py doors .     # offense only: redirector front doors, from outside
python verify.py stack .     # the range over SSH from the jumpbox

macOS or Linux:

python3 verify.py doors .    # offense only: redirector front doors, from outside
python3 verify.py stack .    # the range over SSH from the jumpbox

Each exits 0 when every check passes, 1 when one fails, and 2 when it cannot run (for example doors on a defense range, which has no redirector). On an offense range, the generated OFFENSE-BRIEFING.md holds the C2 payload recipe (callback domain, URI prefix, and gating header per redirector), and sudo rsp-check on a redirector checks the same path from the box itself.

Manage and tear down. The export also ships lifecycle scripts, manage.sh (and manage.ps1 on Windows), for the running range. They take status, start, stop, and teardown: stop pauses compute billing without destroying the range, and teardown destroys it. teardown shows what it will delete and asks you to type the range name (or yes) first; --yes skips the question, for a script. It writes its own logs/teardown-<UTC timestamp>.log, named in its closing box. Run either script with no command for a help screen written for the export's provider: what each command does there, what still bills after stop, and the CLI login it needs.

bash manage.sh status
bash manage.sh start
bash manage.sh stop
bash manage.sh teardown

On Windows, the same commands are .\manage.ps1 status and so on.

Every shipped blueprint turns on auto-stop: the range stops itself 8 hours after the first apply, then daily at that time. Instances are stopped, not deleted, so start brings the range back; disks and reserved addresses still bill. Change it or turn it off in Topology settings (the Inspector tab with nothing selected; on a shipped defense blueprint, click Unlock to edit first) and export again. A topology you start from an empty canvas has no auto-stop until you set one.


☁️ Plan AWS quotas per region

AWS quotas are per region, and a compile does not check them. Scope them before a large or concurrent deploy, so an apply does not fail partway and leave infrastructure running and billing.

  • Elastic IPs. The default is 5 per region. A defense/AD range uses about 2 (jumpbox plus NAT gateway), an offense range about 3 (jumpbox, redirector, NAT gateway), so the default holds only one or two concurrent ranges. Request an increase per region in advance. An EIP shortfall surfaces at provisioning, after the instances are up, so a failed apply leaves them running to be destroyed.
  • vCPUs. The running On-Demand standard vCPU limit caps a region. Hosts are mostly t3.large and t3.medium (2 vCPU each), and a full AD range (GOAD plus a SIEM) can exceed the default. Check and raise it before large deploys.
  • Kali subscription. A Kali operator needs a one-time Marketplace subscription on the account. It is per account, not per region. redStackPRO generates code and touches no account, so you subscribe once before the first Kali deploy.

The quotas are per region. Running several ranges at once? Give each a distinct prefix so its host names stay clear; the few names AWS requires to be unique in the account or region (the key pair and the auto-stop role and schedule) auto-suffix per deployment, so ranges never collide in one account. The full quota table, the increase commands, and the pre-deploy checklist are in the wiki: Providers and Deploying a Range.


πŸ€– API and agents

Everything the canvas does is available headless, so an agent can operate redStackPRO without a person clicking through it. A topology is plain JSON against a published schema (src/redstackpro/schema/topology/, also served at /api/v1/registry/schema), the registry endpoints report the vocabulary of node kinds, providers, and roles, and the validate and compile steps the canvas calls are the same REST endpoints. The API is documented at /docs and /openapi.json, which a model can read as tools.

redstackpro serve                                  # http://127.0.0.1:8000
curl 127.0.0.1:8000/api/v1/registry/schema         # the topology schema
curl -X POST 127.0.0.1:8000/api/v1/validate \
  -H 'content-type: application/json' \
  -d '{"document": {...topology...}, "provider": "gcp"}'
curl -X POST 127.0.0.1:8000/api/v1/compile \
  -H 'content-type: application/json' \
  -d '{"document": {...topology...}, "provider": "gcp"}'

Validation returns structured findings, each with a stable code, the target, prose and a remedy, and the compiler refuses a document with errors and hands back those same findings. The loop is read the schema, compose a topology, validate, fix what the findings name, compile. A model iterates on the outcome rather than scraping prose, and the validate call stores nothing along the way. The command line is the same pipeline for a shell agent, redstackpro validate and redstackpro compile.

The running instance trusts the local caller, which suits a single-user or self-hosted deployment. redStackPRO holds no cloud credentials on any path: the export is the handoff, and you run it yourself.


πŸ’‘ Why

redStack proved the pattern but the topology is fixed in code. redStackPRO makes it composable, adds multi-provider support, and puts attack infrastructure and target ranges on the same canvas.


πŸ“š Docs


πŸ—‚οΈ Repo layout

src/                     the Python package and everything it ships
  redstackpro/           the composition layer: validator, compiler, API
    schema/              the document schema, registry data, worked examples
    tools/               command line tools (compile, validate, dev tooling)
    assets/              Ansible roles and static files baked into the export
frontend/                the web canvas (Vite + React)
tests/                   the suite: compiler, validator, API, export
docs/                    architecture, schema, validation, solutions, images
pyproject.toml           package metadata, deps, and the console entry points
alembic.ini              migration config for the Postgres backend
Dockerfile               the one-container build (API plus built canvas)
docker-compose.yml       local run, with an optional Postgres profile
.dockerignore            build context trim
README.md                this file
CHANGELOG.md             release notes per version
LICENSE                  MIT (the GOAD files in frontend/public/goad/ carry their own GPLv3 LICENSE)
.github/                 the CI workflow and the issue templates

License

MIT. See LICENSE.

The GOAD range blueprints under frontend/public/goad/ are the exception: they are derived from GOAD (GPLv3) and are distributed under GPLv3, with the license and attribution in that directory. They are data, not code: the canvas loads them as native ranges that compile through redStackPRO's own terraform and ansible. The rest of the project is MIT.

A devZero Security Project.

About

Compose red team infrastructure and Active Directory ranges on a canvas, then compile to a complete Terraform and Ansible working directory you run yourself. Validate TTPs and tooling at production-ops level. GCP, AWS, and Azure.

Topics

Resources

Security policy

Stars

171 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages