Skip to content

Deploying a Range

BaddKharma edited this page Nov 6, 2026 · 31 revisions

Deploying a Range

You deploy the export from your own machine, under your own credentials. redStackPRO is not in the loop once you press Download.

Caution

Deploying creates real, billed resources in your own cloud account: VMs, storage, a public IP or two, and the rest. Every shipped blueprint stops its instances 8 hours after the first apply, then daily at that time, but stopped disks and reserved addresses still bill, and a topology you start from an empty canvas has no auto-stop until you set one in Topology settings. Tear the range down when you are done (see Managing a range: status, start, stop, teardown below), and check your cloud console if a build ever fails partway through.

Check AWS quotas first (per region)

AWS only. On GCP, confirm a billing-enabled project and your CPUS_ALL_REGIONS quota first (see Providers), then continue at Make an SSH key. On Azure, the deploy checks VM sizes, vCPU and public IP quota, and resource providers itself before it applies, so register Microsoft.DevTestLab if you have not (see Cloud Prerequisites) and continue at Make an SSH key.

On AWS, scope your quotas for the target region before the first apply. A compile does not check them, and a shortfall surfaces mid-apply, after instances are up, leaving partial infrastructure to destroy. Each item is per region, so a new region means re-checking.

  • Elastic IPs: the default is 5 per region. A defense/AD range uses about 2, an offense range about 3, so the default holds only one or two concurrent ranges. Request an increase in advance.
  • vCPUs: a large AD range (for example full GOAD plus a SIEM) can exceed the default running On-Demand vCPU limit. Raise it before large deploys.
  • Kali: a Kali operator needs a one-time Marketplace subscription on the account. It covers every region. redStackPRO touches no account, so you subscribe once yourself.
  • Concurrent ranges: give each a distinct prefix so its host names stay distinguishable. The names that must be unique in the account or region (the key pair and the auto-stop IAM role and schedule) carry an automatic per-deployment suffix, so ranges in one account never collide, in the same region or across regions.

See Providers for the quota table and the increase commands.

Extract the export

Extract the download to its own folder, for example C:\redstackpro\my-range on Windows or ~/redstackpro/my-range on macOS and Linux, and open PowerShell or a terminal in it. Every step on this page runs from that folder: keys/ below is relative to it, and manage.sh runs from it after the deploy, so keep it. On Windows the path must not contain an apostrophe, because deploy.ps1 cannot run from one.

Make an SSH key

An SSH key pair is two matching files: a private half you keep and a public half you can hand out. The deploy authenticates to the hosts with the private half, and the public half is written into each host at creation, so make the pair before the first apply.

Run this from the export's root folder, the one holding deploy.sh and keys/ (the command below creates keys/id_ed25519 and keys/id_ed25519.pub there):

macOS, Linux, or Git Bash on Windows:

ssh-keygen -t ed25519 -f keys/id_ed25519 -N ""

Windows PowerShell (5.1 or 7). Leave -N off 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

keys/ sits beside DEPLOYMENT-GUIDE.md at the export root. Already have a key? Point at it with REDSTACKPRO_SSH_KEY=/path/to/key bash deploy.sh, or in PowerShell with $env:REDSTACKPRO_SSH_KEY = 'C:/path/to/key'; .\deploy.ps1.

On Windows, a key copied into keys\ takes that folder's permissions, and Windows ssh refuses a private key other accounts can read: it prints UNPROTECTED PRIVATE KEY FILE! and asks for a password. deploy.ps1 sets such a key to you, SYSTEM, and Administrators only and prints a note saying so. A key ssh-keygen made in place already has those permissions.

Fill in the variables

deploy.tfvars is a plain text file, one setting per line as name = "value" (or name = ["value"] for a list). It lives at the export root, next to deploy.sh. Open deploy.tfvars and set:

  • ssh_public_key: leave it empty and deploy.sh fills it from the key in keys/, or paste the single line from keys/id_ed25519.pub.

  • operator_source_ranges: the addresses you connect from, each as a /32. A CIDR like 203.0.113.7/32 means exactly that one address; find yours with curl https://checkip.amazonaws.com or by searching "what is my ip".

    [!CAUTION] This variable gates management access to the jumpbox (SSH, and the Guacamole portal or the VPN port) next to a deliberately vulnerable range. RDP is not opened to it: the portal reaches the Windows hosts from the jumpbox. The export ships it commented out, and deploy.sh stops until you set it, suggesting your current public IP when it can look it up. A literal 0.0.0.0/0, meaning anyone on the internet, is refused too, and the refusal names the override if you really mean it. Set it to your own /32 (or your team's known ranges).

  • region (AWS), project, region, and zone (GCP; the zone has to be inside the region), or subscription_id and location (Azure), plus any topology-specific inputs the briefing names. A region (Azure calls it a location) is the cloud's data center location (for example us-east-1 on AWS, us-east4 on GCP, eastus2 on Azure); pick one close to you or your target. A GCP project is the billing and resource container every GCP resource lives in; if you do not have one yet, see Providers for how to create one and enable billing.

  • Azure only: resource_group, the resource group the range is created in and that holds all of it (default {prefix}-{topology name}-rg). The deploy creates it and teardown deletes it, so it must not already exist; the preflight stops if it does. Optionally vm_sizes, the VM size per role. deploy.tfvars lists this topology's roles with their default sizes, commented out; uncomment it and keep only the roles you change. See Cloud Prerequisites.

deploy.tfvars explains each value and where to find it, in a comment above it. deploy.sh stops early, before anything is created, on an empty GCP project, an empty Azure subscription_id, or a zone outside its region.

You edit deploy.tfvars only. deploy.sh copies it into terraform/terraform.tfvars at apply time (and aborts if deploy.tfvars is missing), so terraform/terraform.tfvars is managed for you: do not edit it by hand.

Authenticate to your cloud first: aws configure, then aws sts get-caller-identity, or gcloud auth login then gcloud auth application-default login, or az login (or a service principal in ARM_CLIENT_ID, ARM_CLIENT_SECRET, ARM_TENANT_ID, and ARM_SUBSCRIPTION_ID). That needs your cloud's CLI installed; on Windows, winget installs it. For the install commands, the full auth steps, and the permissions your identity needs, see Cloud Prerequisites.

Operator access: portal or VPN (attack infrastructure only)

An attack-infrastructure jumpbox names its team directly. Two overlay fields control it, both optional:

  • operators: a list of {handle, role} entries, one per teammate. Each handle is unique and lowercase. Every operator gets a Guacamole portal login (the handle as the username, on the shared lab password), regardless of access mode.
  • access_mode: public (default), wireguard, or openvpn. vpn_port defaults to 51820 for WireGuard or 1194 for OpenVPN, and vpn_protocol is udp or tcp (WireGuard is udp only; OpenVPN can run tcp/443 to get through a restrictive network).

With a VPN access mode, provisioning on the jumpbox also creates a personal client config for each operator, a WireGuard .conf or an OpenVPN .ovpn. The keys are generated on the jumpbox and never enter the export. Each client config holds that operator's private key, so treat it as a credential: fetch it once provisioning has finished (RUN EXITED ok=1) and hand it only to that operator. Each lands at /opt/redstackpro/vpn/<handle>.conf (or .ovpn), owned by the platform account, so you fetch it yourself over your own ssh session:

scp -i keys/id_ed25519 redop@<jumpbox-ip>:/opt/redstackpro/vpn/<handle>.conf .

Firewall posture changes with the access mode:

  • public (default): both ssh (22) and the Guacamole portal (443) stay open to operator_source_ranges.
  • wireguard or openvpn: ssh (22) stays open to operator_source_ranges so the admin can keep deploying and managing the box, the public portal (443) closes to the internet and is reached instead over the tunnel at the jumpbox's private/tunnel address, and the VPN listen port opens to operator_source_ranges in its place. The jumpbox keeps the same public IP either way; it is the VPN endpoint too.

OFFENSE-BRIEFING.md in the export gets an "Operators & VPN access" section listing the endpoint (jumpbox public IP plus port and protocol), each operator's portal account, and the path to their credential file, with the scp line to fetch it.

Managing operators after deploy

With a wireguard or openvpn access mode, add or remove a teammate on a running jumpbox without a redeploy:

sudo rsp-operator add <handle> [role]
sudo rsp-operator remove <handle>
sudo rsp-operator list

rsp-operator generates or revokes the VPN credential and the portal account (an operator added this way gets a portal password of their own, which it prints), and keeps a durable roster at /opt/redstackpro/vpn/operators.json, so an operator added this way survives the next redeploy. A public jumpbox has no rsp-operator: change operators on the canvas and deploy again.

A defensive range ignores all of this: operators, access_mode, and the rest are attack-infrastructure-only fields, and a defense range keeps the plain public portal.

Terraform state

The generated terraform/versions.tf has a terraform block (required_version and required_providers) and the provider block, but no backend block, on GCP, AWS, and Azure. That means Terraform keeps local state: a terraform.tfstate file inside the export's own terraform/ folder, on your machine. There is no remote backend, so the state and its lock live only on this machine and nothing coordinates two machines; this is a single operator workflow. Do not run terraform apply against the same export from two machines at once, and never commit terraform.tfstate (it can hold values that should stay private).

If you deploy redStack (attack infrastructure) and a range as two separate exports, each gets its own terraform/ folder and its own state file: two independent deploys, and two independent teardowns. See Managing a range: status, start, stop, teardown below.

Run the deploy

Windows PowerShell, from the export folder:

.\deploy.ps1

macOS, Linux, or Git Bash:

bash deploy.sh

On Windows, do not run bash deploy.sh at a PowerShell prompt: that bash is the WSL launcher. WSL (Windows Subsystem for Linux) runs a separate Linux environment with its own filesystem, home directory, and credentials, so the deploy would run somewhere your SSH key and cloud login are not. deploy.ps1 finds Git Bash and calls it correctly. deploy.ps1 and manage.ps1 are unsigned, so PowerShell's execution policy has to allow them.

deploy.sh applies the Terraform, then provisions from the range's own jumpbox. The managed hosts sit on a private subnet nothing else can reach, so provisioning runs from inside the range rather than from your laptop. Your cloud credentials stay on your machine throughout.

On Azure, deploy.sh runs azure_preflight.py after terraform init and before terraform apply. If a VM size is not available to your subscription, a vCPU or public IP quota is short, or a resource provider is not registered, it stops before anything is created and prints what to change. See The Azure preflight in Cloud Prerequisites.

OFFENSE-BRIEFING.md (attack infrastructure) or DEFENSE-BRIEFING.md (a range) in the export lists the credentials and what is planted where.

What the deploy prints

The run opens on the redStackPRO wordmark and a title box naming the range, the provider and region, and the path of this run's log. Then it prints numbered phases, [1/7] Terraform apply to [7/7] Launch provisioning on the jumpbox on GCP and AWS, or eight on Azure with [1/8] Preflight: VM sizes, quota, providers first. Each phase line carries the time it started, and each phase ends on an indented ok line with a short result. A yellow note line reports something the deploy did for you, such as filling an empty ssh_public_key from the key in keys/, or, from deploy.ps1, setting a key in keys\ to you only (see Make an SSH key).

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.

Terraform's output is condensed on screen: the plan line, then one line per finished resource with its mark (+ created, ~ changed, - destroyed), how long it took, its name, and its kind; an error block in full; and a warning's title. A 41-resource Azure apply prints 42 lines on screen (the plan line and one per resource), where Terraform printed about 1120. Shortened:

[2/8] Terraform apply .............................. 09:51
      plan  41 to add, 0 to change, 0 to destroy
      +     0s  lab (password)
      ...
      +   1m3s  def-kingslanding (windows virtual machine)
      ...
      +    2m4s  def-kingslanding/redstackpro-winrm (virtual machine extension)
      ok  41 resources in 4m44s

Everything terraform apply printed is still in the deploy log (see Deploy logs, for troubleshooting below).

A run that gets through ends on a green DONE box:

==============================================================
 DONE  Infrastructure is up. Provisioning is running.
--------------------------------------------------------------
 Portal    https://<jumpbox>/guacamole

 Logins, run:
   terraform -chdir=terraform output guacamole

 Watch provisioning, run (it returns when the run ends):
   ssh -i keys/id_ed25519 blueop@<jumpbox> rsp-watch

 Finished  when run.log shows RUN EXITED ok=1 (45-75 min)
 Log       logs/deploy-<UTC>.log
==============================================================

The end of the same GOAD-Light deploy: steps 3 to 8 each finish ok, from filling the inventory to launching provisioning on the jumpbox, then a green DONE box with the portal URL, the terraform output command for the logins, the one-line ssh command that runs rsp-watch to follow provisioning, and that the run is finished when run.log shows RUN EXITED ok=1.

The box gives the portal address, the command that prints the portal logins, the ssh line that watches provisioning (see Following provisioning on the jumpbox below), how to tell provisioning has finished, and the deploy log's path. The example is a defense range. On attack infrastructure the account is redop and the expected time is 60 to 75 minutes. Every command the deploy prints, in the box and in the reasons it stops, sits alone on its own line under a label that says to run it, so a copied line is the whole command. Run from deploy.ps1, the box writes Windows paths (keys\id_ed25519) and the commands in PowerShell form, and the ssh line pastes into PowerShell as is. If a redirector's DNS record does not point at it yet, the box adds a DNS line naming the A record to set.

A run that stops ends on a red FAILED box instead:

==============================================================
 FAILED  [2/8] Terraform apply
--------------------------------------------------------------
 Reason    <why it stopped>
 Exit      <exit code>
 Log       logs/deploy-<UTC>.log (secrets scrubbed)
 Report    attach the log to a new issue:
           https://github.com/devZero-Security/redStackPRO/issues/new/choose
==============================================================

It names the phase, the reason, the exit code, and the log, with the link to open an issue. When Terraform fails, its error block prints in full above the box.

A real FAILED box on Azure: the deploy stopped at step 1 of 8, preflight, because the resource group def-goad-light-rg already exists. The preflight table shows the VM sizes, the vCPU quota, and the providers all ok, then a FAIL line explains the collision and that apply was not started and nothing was created, with the remedy to set another resource_group or tear down the range that owns it.

Here the Azure preflight caught a resource group that already exists and stopped before apply, so nothing was created. The reason line tells you how to fix it.

On a terminal the output is in colour: the red wordmark, red rules around the title box and the FAILED box, green rules around the DONE box, bold phase titles with a cyan [n/N], green ok and DONE, red FAILED and failure reasons, yellow notes and warnings, and cyan commands and portal address. Output to a file or a pipe gets none, and the deploy log is always plain text, with no colour codes and no wordmark. Set NO_COLOR to turn it off.

macOS, Linux, or Git Bash:

NO_COLOR=1 bash deploy.sh

Windows PowerShell:

$env:NO_COLOR = '1'; .\deploy.ps1

Following provisioning on the jumpbox

Provisioning runs on the jumpbox in a detached screen session and keeps going if you disconnect, so the deploy ends while it is still running. Watch it with the ssh line from the DONE box, which runs rsp-watch on the jumpbox. In this form it pastes into PowerShell or bash alike (the account is redop on attack infrastructure):

ssh -i keys/id_ed25519 blueop@<jumpbox> rsp-watch

rsp-watch shows ~/provision/run.log from the top and returns when the run ends: exit 0 for RUN EXITED ok=1, 1 otherwise. Ctrl-C stops the watching, not the build. If Windows ssh refuses the key with UNPROTECTED PRIVATE KEY FILE! and asks for a password, see Make an SSH key above.

~/provision/run.log is the condensed view. It gives each stage a heading (Jumpbox pass 1 of 3, Waiting for authenticated WinRM, then site.yml attempt 1 of 8 on a defense range; site.yml attempt 1 of 8 on attack infrastructure), leaves out hosts a task skipped, marks a failed task with !!, and ends on a RUN EXITED line followed by the last PLAY RECAP line for every host. RUN EXITED ok=1 means provisioning is done; ok=0 means the last attempt failed, and the lines marked !! name the failed tasks. Every line Ansible printed, skips included, is in ~/provision/run-full.log beside it.

Verify the deployment

The export ships verify.py next to deploy.sh. It checks a live range against the export that built it and exits 0 (all passed), 1 (a check failed), or 2 (the checks could not run). It needs PyYAML on your machine; without it Python stops on the import before any check runs.

Windows (PowerShell or Git Bash):

python -m pip install pyyaml

macOS or Linux:

python3 -m pip install pyyaml

If pip refuses with externally-managed-environment (Debian 12, Ubuntu 23.04 and later, Homebrew Python), install the distribution package instead (sudo apt install python3-yaml on Debian and Ubuntu) or install PyYAML in a venv.

Then, from the export folder. Windows:

python verify.py doors .     # offense only: redirector front doors, probed 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, probed from outside
python3 verify.py stack .    # the range over SSH from the jumpbox

doors exits 2 on a defense range, which has no redirector.

doors validates each redirector's TLS chain for its cert source, that every front door serves its cover page, that decoys differ between doors, that the gating header is enforced (an ungated request is turned away), and that one door's header value does not open another. stack logs in over the jumpbox and checks that provisioning finished, the Guacamole portal answers, the stock guacadmin account is gone, each teamserver holds 443, and each redirector carries its certificate tool. Add --plan to list the checks without running them.

On an offense range, sudo rsp-check on a redirector runs the same front-door checks from the box itself, and OFFENSE-BRIEFING.md carries the C2 payload recipe (see Redirectors and Cover Stories).

Deploy logs, for troubleshooting

Every run of deploy.sh (and deploy.ps1, which calls it) writes one timestamped log to logs/deploy-<UTC-timestamp>.log in the export. It holds every phase and everything terraform apply printed, in full, including what the screen condenses. It is plain text, with no colour codes, and opens with a header (redStackPRO version, provider, Terraform version, your system) that goes to the log only. Its last line is result: ok, or on a failure result: FAILED (exit N) in phase ... with the phase the run stopped in, so a log attached to an issue records how the run ended. When the run ends it is scrubbed of private keys and any password, secret, or token assignment, so it is safe to attach to a public issue.

Ansible runs on the jumpbox, detached, and keeps going after the deploy ends, so its output is not in this log. The provisioning run log is ~/provision/run.log on the jumpbox, with every line in ~/provision/run-full.log beside it, separate from the deploy log on your machine (see Following provisioning on the jumpbox above).

If a run fails, the FAILED box (see What the deploy prints above) prints the log's path and a link to open an issue. The repo ships GitHub issue templates (Deploy failure, Bug report) that ask for the log: if a deploy fails, or a range comes up wrong, attach the newest logs/deploy-*.log to the issue.

Redirectors need a real domain

If your topology has a redirector, set its domain before you compile: an export built without it must be recompiled, not patched after apply.

A redirector is the one host that answers to the internet by name. A domain has to be registered and pointed at the box by a human, and nothing can invent one. Any shipped example that carries a redirector arrives one field short on purpose, and the validator says so with RDR001.

Supply a domain you control, either in the canvas inspector before you compile, or on the command line:

redstackpro compile <blueprint> --hostname <your domain> -o export

Use a domain you registered. A hostname containing example, change-me, changeme, or your-domain is treated as a placeholder and still raises RDR001.

After apply, create the A record the briefing names, pointing your subdomain at the redirector's address. A DNS A record is the entry that maps a domain name to an IPv4 address; you create it in your domain registrar's or DNS provider's control panel (wherever you manage the domain), not anywhere in redStackPRO. See Redirectors and Cover Stories.

Managing a range: status, start, stop, teardown

Every jumpbox export ships a lifecycle script, manage.sh (and manage.ps1 for Windows PowerShell), with four subcommands. Run them from the export root, the folder holding deploy.sh.

Ranges are meant to be stood up, used, and torn down, so nothing keeps costing money after the work is done. Teardown runs the destroy for you: it copies deploy.tfvars into terraform/ and then destroys.

macOS, Linux, or Git Bash:

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

Windows PowerShell:

.\manage.ps1 status
.\manage.ps1 stop
.\manage.ps1 start
.\manage.ps1 teardown

Run it with no command (or help, -h, --help) for a help screen written for this export: the range name, provider, and region, the resource group (Azure) or GCP project read from deploy.tfvars, or on AWS the CLI profile from AWS_PROFILE (default when unset), each command and what it does on this provider (what still bills after stop, what teardown --force deletes first), the auto-stop schedule, and the CLI and login it needs. An unknown command or option prints the same screen and exits 1.

macOS, Linux, or Git Bash:

bash manage.sh

Windows PowerShell:

.\manage.ps1

The manage.ps1 help screen for a GOAD-Light range: a MANAGE GOAD-Light box with the provider, region, resource group, and auto-stop schedule, then the status, stop, start, and teardown commands with what each does on Azure, and two examples.

status, start, and stop run through your cloud's CLI (gcloud, aws, or az), which has to be installed and logged in; each checks for it first, and exits nonzero when the call fails on any instance. Each opens on a title box like the top of the help screen, without the wordmark: the command (STATUS, START, or STOP), the range name, the provider and region, and the resource group, project, or profile. They act on this range's own instances only, never the whole account or project, and on AWS they use the region from deploy.tfvars.

  • status: one table for the range, each instance's state and public address, with the portal address in the box above it.
  • stop and start: the cost lever between runs. stop halts the instances so they stop billing for compute (GCP, AWS, and Azure all support this live), and start brings them back without a redeploy; provisioning is not re-run. Disks and reserved public addresses still bill while stopped, and on AWS and Azure so does the NAT gateway; the help screen lists them for this export. On Azure, stop deallocates the VMs, which is what halts compute billing there, and the jumpbox keeps its public IP.
  • teardown: deletes the whole range with terraform destroy. It cannot be undone, so it asks first (see below).
  • teardown --force: recovery for an interrupted deploy (see below).

manage.ps1 status for a GOAD-Light range on Azure: a STATUS GOAD-Light box with the provider, resource group, and portal URL, then a table of the four VMs (def-castelblack, def-jumpbox, def-kingslanding, def-winterfell), each VM running, with the jumpbox's public address.

Proxmox and ESXi exports, which are on the roadmap, offer teardown only.

Teardown

teardown first shows a TEARDOWN box with what it is about to delete (the range, provider, region, and resource group, project, or profile) and a red line saying it deletes the whole range and cannot be undone, then asks you to type the range name, or yes. Any other answer deletes nothing. --yes skips the question, in either order with --force. manage.ps1 asks with Read-Host. manage.sh asks only on a terminal: without one and without --yes it stops and deletes nothing, so a script cannot hang on the question.

The destroy then prints as one step, terraform destroy, shaped like a deploy phase without a number: the plan line, one red - line per deleted resource, and ok with the count destroyed and how long it took, then a green DONE box (The range is deleted.) naming the teardown log, or a red FAILED box. Everything Terraform printed goes to logs/teardown-<UTC-timestamp>.log, and that log ends on a result: ok or result: FAILED (exit N) line.

manage.ps1 teardown --yes on a GOAD-Light range: terraform destroy with a plan of 0 to add, 0 to change, 41 to destroy, the def- resources deleted each with its time, ok 41 destroyed in 3m45s, and a green DONE box reading The range is deleted.

macOS, Linux, or Git Bash, without the question:

bash manage.sh teardown --yes

Windows PowerShell, without the question:

.\manage.ps1 teardown --yes

teardown --force is recovery for an interrupted deploy. If you stop deploy.sh partway (Ctrl-C, a closed terminal), the cloud can hold instances Terraform created but never recorded in its state. A plain teardown cannot see those and may fail trying to destroy a network they still sit in; its FAILED box then gives the --force command. --force clears the cloud side first, then runs the destroy:

  • GCP and AWS: it deletes (GCP) or terminates (AWS) this range's instances only. Every instance carries a redstackpro_deploy label (GCP) or tag (AWS), a random id Terraform keeps in this deployment's state. The sweep takes the instances in that state plus any carrying its id, which still finds one an interrupted apply created but never recorded. Other ranges in the same project or region are not touched, and with no state it deletes no instance. It needs gcloud or aws. The sweep covers instances only, so after a forced teardown on GCP or AWS check the console for a leftover static or Elastic IP or NAT gateway.
  • Azure: it deletes the range's whole resource group with az group delete, then runs the destroy. Without az it says so and runs the plain destroy.

macOS, Linux, or Git Bash:

bash manage.sh teardown --force

Windows PowerShell:

.\manage.ps1 teardown --force

After a clean teardown, re-running deploy.sh starts fresh.

If you deployed redStack (attack infrastructure) and a range as separate exports, each has its own terraform/ state in its own folder and needs its own teardown: run manage.sh teardown (or .\manage.ps1 teardown) once per export.

Managing several ranges

Each export is independent. Its Terraform state is local to that export's own terraform/ folder, and there is no central fleet view across exports: the canvas tracks nothing after Download, and one export knows nothing about another. The recommended pattern is one folder per range, each with its own deploy.tfvars and its own state, and you run manage.sh status (or .\manage.ps1 status) per range from inside its folder. Teardown is likewise per folder.

Give each range a distinct prefix (the field next to the topology name on the canvas) so its host names stay clear across your folders. On GCP two ranges in one project need it: every resource name is built from the prefix and a node id (the auto-stop policy is <prefix>-auto-stop), with no per-deployment suffix, and network and firewall rule names are unique across the whole project, so a second range with the same prefix stops at apply on a name already in use, in any region. The shipped blueprints all start on def (defense) or off (offense), so change the prefix before the second deploy. On AWS the names that must be unique in the account or region (the key pair and the auto-stop IAM role and schedule) carry an automatic per-deployment suffix, so ranges in one account never collide. On Azure the resource group is the range. Its default name comes from the prefix and the topology name, so two ranges with different names get different groups, and the preflight stops an export whose group already exists outside its own state.

Providers

GCP, AWS, and Azure are tested end to end. Proxmox and ESXi are on the roadmap, not yet supported. See Providers for the full matrix and the setup notes for each.

Clone this wiki locally