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.
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.
GOAD, the full lab: three domains across two forests, five machines and their trusts, the reference range the written solution follows.
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.
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. |
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.
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.
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.
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.largeandt3.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.
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.
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.
- Wiki
- CHANGELOG.md
- docs/architecture.md
- docs/schema.md
- docs/validation.md
- docs/solutions/
- src/redstackpro/schema/topology/
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
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.




