Skip to content

Deploying a Range

BaddKharma edited this page Sep 28, 2026 · 16 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. Nothing in this pipeline stops the clock for you. 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 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, per region. redStackPRO touches no account, so you subscribe once per region.
  • Concurrent ranges: give each a distinct prefix so its host names stay distinguishable. Account-global names (the key pair and the auto-stop IAM role) 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.

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 (the quoting differs):

ssh-keygen -t ed25519 -f keys\id_ed25519 -N ''

keys/ sits beside DEPLOYMENT-GUIDE.md at the export root. Already have a key? Point at it with REDSTACKPRO_SSH_KEY=/path/to/key.

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 export/deploy.tfvars and set:

  • ssh_public_key: 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 ifconfig.me or by searching "what is my ip".

    [!CAUTION] The shipped default is ["0.0.0.0/0"], meaning anyone on the internet. This variable gates management access (SSH, RDP, the Guacamole portal) next to a deliberately vulnerable range, so narrow it to your own /32 (or your team's known ranges) before you apply. Leaving it open is the single easiest way to hand your range to someone else.

  • region (AWS) or project and region (GCP), plus any topology-specific inputs the briefing names. A region is the cloud's data center location (for example us-east-1 on AWS, us-east4 on GCP); 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.

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. For 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, apply also creates a personal client config for each operator on the jumpbox, a WireGuard .conf or an OpenVPN .ovpn. The private keys stay on the jumpbox and never enter the export; only the client config file does. 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

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, and keeps a durable roster at /opt/redstackpro/vpn/operators.json, so an operator added this way survives the next redeploy.

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 declares only required_version and required_providers, no backend block, on both GCP and AWS. 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 and no state locking, so 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.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.

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

Deploy logs, for troubleshooting

Every run of deploy.sh (and deploy.ps1, which just calls it) writes one timestamped log to logs/deploy-<UTC-timestamp>.log in the export, capturing both the Terraform apply and the jumpbox-staged provisioning run. It opens with a header (redStackPRO version, provider, Terraform version, your system), and is scrubbed of secrets, private keys, and any password, secret, or token assignment, before it is written, so it is safe to attach to a public issue.

If a run fails, deploy.sh 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 it: 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.example -o export

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
  • status: reports the current state of the range's instances.
  • stop and start: the cost lever between runs. stop halts the instances so they stop billing for compute (GCP and AWS both support this live), and start brings them back without a redeploy.
  • teardown: destroys the range. Confirm the plan it shows before approving it.

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. The few account-global cloud names carry an automatic per-deployment suffix, so concurrent ranges in one account or project never collide, whether in one region or several.

Providers

GCP and AWS are tested end to end. Azure, 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