AWS Incident-Response Scenario Lab (Terraform)
A spin-up / lab / tear-down environment for practicing cloud IR. It's split so you can build the free identity foundation first and only pay for the billable services when you want to (those double as the AWS "free credit" activities).
00-bootstrap/ one-time: creates the S3 bucket that holds REMOTE state (~pennies)
01-iam-foundation/ FREE: prod + IR roles, a "service account", a budget ($0)
02-billable/ PAID: EC2, Lambda web app, RDS — earns the credit activities⚠️Throwaway sandbox account only. This builds intentionally risky identities (an over-privileged role, a long-lived static access key). Tear it down.
Step 0 — install prerequisites (macOS, one-time)
brew tap hashicorp/tap
brew install hashicorp/tap/terraform # Terraform (removed from homebrew-core)
brew install awscli jq direnv
brew install --cask session-manager-plugin
terraform version && aws --version && jq --versionStep 1 — authenticate the CLI (you only have root — fix that first)
Never use root for the CLIIn the Console, signed in as root, create an admin:
- Top-right Security credentials → enable MFA on the root user.
- IAM → Users → Create user → name
admin. - Attach policies directly →
AdministratorAccess→ Create user. - Open
admin→ Security credentials → Create access key → "Command Line Interface (CLI)" → acknowledge → copy the Access key ID + Secret. - (Recommended) add MFA to
admintoo.
Now put those keys in a named profile and confirm you're NOT root:
aws configure --profile ir-lab # Access key / Secret / region us-east-1 / json
aws sts get-caller-identity --profile ir-lab
# → arn:aws:iam::<acct>:user/admin (NOT :root)direnv so the profile auto-loads in this dir (no --profile flags; Terraform
picks it up automatically):
echo 'eval "$(direnv hook zsh)"' >> ~/.zshrc && source ~/.zshrc
cd cloud/aws/labs/ir-scenario
cp .envrc.example .envrc # AWS_PROFILE=ir-lab, AWS_REGION=us-east-1
direnv allowStep 2 — bootstrap the remote-state bucket (once)
cd 00-bootstrap
terraform init
terraform apply # creates ir-lab-tfstate-<account-id>
terraform output -raw state_bucket # copy this nameStep 3 — Section 1: the FREE identity foundation (remote state in S3)
cd ../01-iam-foundation
cp backend.hcl.example backend.hcl # paste the bucket name from Step 2 into it
terraform init -backend-config=backend.hcl
terraform apply # roles, service account, budget — all $0
terraform output # role ARNs, the service-account keys, etc.For a read-through writeup of a completed run — commands, real outputs, the concepts (EC2 identity, IMDSv1 vs v2, STS, SSRF, EBS storage), and the full teardown — see WALKTHROUGH.md.
See SCENARIO.md for what each identity is and the attacker/IR exercises (recon as the over-privileged workload, abuse the static-key service account, then respond with the IR roles). The state now lives in S3, not on your laptop — exactly as you wanted.
Step 4 — Section 2: the billable services (when ready; earns credits)
cd ../02-billable
cp backend.hcl.example backend.hcl # SAME bucket
terraform init -backend-config=backend.hcl
terraform apply
terraform output # ssh_command, lambda_web_url, rds_endpointCredit activities this covers
| Activity | How | Reward |
|---|---|---|
| Launch an EC2 instance | 02-billable builds the victim instance | $20 |
| Create a Lambda web app | 02-billable builds a Lambda + public Function URL — open lambda_web_url | $20 |
| Create an Aurora/RDS database | 02-billable builds a db.t3.micro PostgreSQL RDS | $20 |
| Use a model in the Bedrock playground | Manual (see below) | $20 |
| Set up a cost budget | 01-iam-foundation creates one (you've already done this) | ✅ |
Bedrock is manual (the activity is "use the playground", which Terraform can't do): Console → Bedrock → Model access → enable a model (e.g. Titan/Claude) → Playground → Chat → send one prompt. Done.
Tear down (reverse order — state bucket LAST)
cd 02-billable && terraform destroy && rm -f ir-lab-key.pem
cd ../01-iam-foundation && terraform destroy
cd ../00-bootstrap && terraform destroy # removes the state bucket lastRDS can take a few minutes to delete. Then check the EC2 / RDS / IAM consoles and your Budgets dashboard read clean.
Notes & gotchas
- Order matters:
00 → 01 → 02to build,02 → 01 → 00to destroy. Section 2 looks up Section 1's instance profile by name, so Section 1 must exist first. - State holds secrets (service-account key, RDS-managed password). It lives in
the S3 bucket (encrypted, versioned, private) — and
backend.hcl,*.pem,.envrcare all git-ignored. Never commit them. - Drift is expected when you manually swap the victim's instance profile to
quarantine; don't re-applySection 2 mid-experiment (it would revert it). - SSH, not Session Manager, for the victim shell — it's role-independent, so it survives swapping the role to deny-all.
- Free-tier:
t3.microanddb.t3.microare free for 12 months / 750h each; the state bucket and a couple of API calls are pennies. Destroy the same day to be safe.