#!/usr/bin/env bash # scripts/verify_phase08.sh - Phase 08 aws-bootstrap gate. set -u ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" cd "$ROOT" fail() { echo "FAIL: $*" >&2; exit 1; } ok() { echo "ok: $*"; } ENV_FILE="$ROOT/.env.secrets" [ -f "$ENV_FILE" ] || fail ".env.secrets missing (run scripts/rotate_spike_key.sh first)" # Confirm .env.secrets + .bootstrap_state.json are gitignored. git check-ignore -q "$ENV_FILE" || fail ".env.secrets is not gitignored" git check-ignore -q terraform/bootstrap/.bootstrap_state.json || \ fail "terraform/bootstrap/.bootstrap_state.json is not gitignored" ok ".env.secrets + .bootstrap_state.json are gitignored" # Source the rotated spike key. set -a . "$ENV_FILE" set +a : "${ACDL_AWS_ACCESS_KEY_ID:?ACDL_AWS_ACCESS_KEY_ID missing in .env.secrets}" : "${ACDL_AWS_SECRET_ACCESS_KEY:?ACDL_AWS_SECRET_ACCESS_KEY missing in .env.secrets}" : "${AWS_DEFAULT_REGION:?AWS_DEFAULT_REGION missing in .env.secrets}" # --- Check 1: caller identity is acdl-spike-runner (NOT root) --- CALLER=$(AWS_ACCESS_KEY_ID="$ACDL_AWS_ACCESS_KEY_ID" \ AWS_SECRET_ACCESS_KEY="$ACDL_AWS_SECRET_ACCESS_KEY" \ AWS_DEFAULT_REGION="$AWS_DEFAULT_REGION" \ python3 -c " import boto3, json s = boto3.Session(region_name='$AWS_DEFAULT_REGION') print(json.dumps(s.client('sts').get_caller_identity())) ") ARN=$(echo "$CALLER" | python3 -c "import sys, json; print(json.load(sys.stdin)['Arn'])") [ "$ARN" = "arn:aws:iam::581513795199:user/acdl-spike-runner" ] \ || fail "caller identity is $ARN, expected arn:aws:iam::581513795199:user/acdl-spike-runner" ok "caller identity is acdl-spike-runner (NOT root)" # --- Check 2: S3 state bucket exists --- AWS_ACCESS_KEY_ID="$ACDL_AWS_ACCESS_KEY_ID" \ AWS_SECRET_ACCESS_KEY="$ACDL_AWS_SECRET_ACCESS_KEY" \ AWS_DEFAULT_REGION="$AWS_DEFAULT_REGION" \ python3 -c " import boto3 s = boto3.Session(region_name='$AWS_DEFAULT_REGION') s.client('s3').head_bucket(Bucket='acdl-tfstate-581513795199-us-east-1') " || fail "S3 state bucket acdl-tfstate-581513795199-us-east-1 not accessible" ok "S3 state bucket exists" # --- Check 3: DynamoDB outbox table exists --- AWS_ACCESS_KEY_ID="$ACDL_AWS_ACCESS_KEY_ID" \ AWS_SECRET_ACCESS_KEY="$ACDL_AWS_SECRET_ACCESS_KEY" \ AWS_DEFAULT_REGION="$AWS_DEFAULT_REGION" \ python3 -c " import boto3 s = boto3.Session(region_name='$AWS_DEFAULT_REGION') s.client('dynamodb').describe_table(TableName='acdl-outbox') " || fail "DynamoDB table acdl-outbox not accessible" ok "DynamoDB outbox table exists" # --- Check 4: IAM user exists with the scoped inline policy containing the Deny statement --- AWS_ACCESS_KEY_ID="$ACDL_AWS_ACCESS_KEY_ID" \ AWS_SECRET_ACCESS_KEY="$ACDL_AWS_SECRET_ACCESS_KEY" \ AWS_DEFAULT_REGION="$AWS_DEFAULT_REGION" \ python3 <<'PY' || fail "IAM user acdl-spike-runner missing or policy lacks DenyEverythingElse" import boto3, json, urllib.parse s = boto3.Session(region_name='us-east-1') iam = s.client('iam') iam.get_user(UserName='acdl-spike-runner') doc = iam.get_user_policy(UserName='acdl-spike-runner', PolicyName='acdl-spike-runner-policy')['PolicyDocument'] parsed = json.loads(urllib.parse.unquote(doc)) sids = [st.get('Sid', '') for st in parsed['Statement']] assert 'DenyEverythingElse' in sids, 'DenyEverythingElse statement missing' PY ok "IAM user acdl-spike-runner exists with the scoped Deny-everything-else policy" echo "VERIFIED — Phase 08: AWS bootstrap complete; spike key rotated; D-034 closed (user must rotate the root key manually now)"