Files
acdl/scripts/verify_phase08.sh
T
Jon Chery 1d5c4d2ae7 phase: 8, status: plan-as-execute, persona: platform-engineer+lead-developer, task: T-8.5..T-8.7
---ci---
project: acdl
phase: 8
milestone: v1.1
status: plan-as-execute
persona: platform-engineer+lead-developer
task: [T-8.5, T-8.6, T-8.7]
requirements.covered: [REQ-23]
---/ci---

Waves 3+4: rotation script + verify script + README + .gitignore.

- T-8.5 (platform): scripts/rotate_spike_key.sh - boto3 with bootstrap
  root key from env (ACDL_BOOTSTRAP_AWS_*); creates new key for
  acdl-spike-runner, deactivates+deletes old, writes new to gitignored
  .env.secrets (chmod 600); idempotent (re-run ends with exactly 1
  active key); optional Gitea secret upload if ACDL_GITEA_TOKEN set;
  does NOT rotate the root key (D-034 closure = manual user step).

- T-8.6 (lead): scripts/verify_phase08.sh - loads rotated key from
  .env.secrets, asserts caller identity is acdl-spike-runner (not root),
  S3 bucket + DynamoDB table + IAM user + scoped policy with
  DenyEverythingElse all present, .env.secrets + .bootstrap_state.json
  gitignored. Uses heredoc python to avoid bash quoting issues.

- T-8.7 (lead): terraform/bootstrap/README.md runbook (6 steps incl.
  manual D-034 root-key rotation) + .gitignore (.env.secrets +
  .bootstrap_state.json). Spike vs v1.2 boundary table.

bash -n + gitignore checks pass.
2026-07-21 19:00:08 +00:00

79 lines
3.5 KiB
Bash
Executable File

#!/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)"