Security Notes
Hands-on Labs

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).

4 min read 7 sections
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)

bash
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 --version

Step 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:

  1. Top-right Security credentials → enable MFA on the root user.
  2. IAM → Users → Create user → name admin.
  3. Attach policies directly → AdministratorAccess → Create user.
  4. Open admin → Security credentials → Create access key → "Command Line Interface (CLI)" → acknowledge → copy the Access key ID + Secret.
  5. (Recommended) add MFA to admin too.

Now put those keys in a named profile and confirm you're NOT root:

bash
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):

bash
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 allow

Step 2 — bootstrap the remote-state bucket (once)

bash
cd 00-bootstrap
terraform init
terraform apply                     # creates ir-lab-tfstate-<account-id>
terraform output -raw state_bucket  # copy this name

Step 3 — Section 1: the FREE identity foundation (remote state in S3)

bash
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)

bash
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_endpoint

Credit activities this covers

ActivityHowReward
Launch an EC2 instance02-billable builds the victim instance$20
Create a Lambda web app02-billable builds a Lambda + public Function URL — open lambda_web_url$20
Create an Aurora/RDS database02-billable builds a db.t3.micro PostgreSQL RDS$20
Use a model in the Bedrock playgroundManual (see below)$20
Set up a cost budget01-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)

bash
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 last

RDS 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 → 02 to build, 02 → 01 → 00 to 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, .envrc are all git-ignored. Never commit them.
  • Drift is expected when you manually swap the victim's instance profile to quarantine; don't re-apply Section 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.micro and db.t3.micro are free for 12 months / 750h each; the state bucket and a couple of API calls are pennies. Destroy the same day to be safe.