mirror of
https://github.com/Sea-Haven-Industries/seahaven-org-baseline.git
synced 2026-09-30 20:53:17 +00:00
* feat(iam): lock app-owned HCP IAM and add bootstrap SCP (PLAT-143) * fix(iam): pin HCP boundary ARNs and bootstrap trust window (PLAT-143) Null on iam:PermissionsBoundary accepted any ceiling, including AdministratorAccess. Import apply cannot self-mutate hcptf-* while bootstrap trust is iam-bootstrap only; add a time-boxed exact StringEquals workspace grant instead of StringLike.
111 lines
4.3 KiB
Bash
Executable file
111 lines
4.3 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
#
|
|
# delete-terraform-substrate-prod-dev.sh — PLAT-147 live delete of
|
|
# seahaven-terraform-substrate in prod (011934824531) and/or dev (710827005802).
|
|
# Does not touch terraform-substrate-external-dev (396287094661).
|
|
#
|
|
# Gate: every prod/dev hcptf-* apply role MUST already have detached
|
|
# seahaven-hcptf-iam-management (consumer import PRs). OIDC is Retain and
|
|
# survives. The guardrail policy is deleted with the stack.
|
|
#
|
|
# Do not run until:
|
|
# 1. Each of the eight prod apply roles is imported in app Terraform.
|
|
# 2. A substrate update with DeletionPolicy: Retain has removed those roles
|
|
# from the template (CFN forgets them without deleting).
|
|
# 3. terraform-substrate-prod and terraform-substrate-dev are removed from
|
|
# bin/app.ts and .github/workflows/deploy.yaml so CD cannot recreate them.
|
|
#
|
|
# Usage:
|
|
# scripts/delete-terraform-substrate-prod-dev.sh --account prod|dev --inventory
|
|
# scripts/delete-terraform-substrate-prod-dev.sh --account prod|dev --yes
|
|
#
|
|
set -euo pipefail
|
|
|
|
ACCOUNT_KEY=""
|
|
INVENTORY=0
|
|
ASSUME_YES=0
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--account) ACCOUNT_KEY="$2"; shift 2 ;;
|
|
--inventory) INVENTORY=1; shift ;;
|
|
--yes|-y) ASSUME_YES=1; shift ;;
|
|
-h|--help) sed -n '2,24p' "$0"; exit 0 ;;
|
|
-*) echo "unknown flag: $1" >&2; exit 2 ;;
|
|
*) echo "unexpected argument: $1" >&2; exit 2 ;;
|
|
esac
|
|
done
|
|
|
|
case "$ACCOUNT_KEY" in
|
|
prod) ACCOUNT_ID="011934824531" ;;
|
|
dev) ACCOUNT_ID="710827005802" ;;
|
|
*)
|
|
echo "usage: $0 --account prod|dev [--inventory|--yes]" >&2
|
|
exit 2
|
|
;;
|
|
esac
|
|
|
|
if [[ "$ACCOUNT_ID" == "396287094661" ]]; then
|
|
echo "refusing: external-dev is out of scope (PLAT-148)" >&2
|
|
exit 2
|
|
fi
|
|
|
|
ORIG_AWS_ACCESS_KEY_ID="${AWS_ACCESS_KEY_ID-}"
|
|
ORIG_AWS_SECRET_ACCESS_KEY="${AWS_SECRET_ACCESS_KEY-}"
|
|
ORIG_AWS_SESSION_TOKEN="${AWS_SESSION_TOKEN-}"
|
|
restore_creds() {
|
|
if [[ -n "${ORIG_AWS_ACCESS_KEY_ID}" ]]; then
|
|
export AWS_ACCESS_KEY_ID="$ORIG_AWS_ACCESS_KEY_ID"
|
|
export AWS_SECRET_ACCESS_KEY="$ORIG_AWS_SECRET_ACCESS_KEY"
|
|
export AWS_SESSION_TOKEN="$ORIG_AWS_SESSION_TOKEN"
|
|
else
|
|
unset AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_SESSION_TOKEN
|
|
fi
|
|
}
|
|
trap restore_creds EXIT
|
|
|
|
CREDS="$(aws sts assume-role \
|
|
--role-arn "arn:aws:iam::${ACCOUNT_ID}:role/OrganizationAccountAccessRole" \
|
|
--role-session-name plat-147-tf-substrate \
|
|
--query Credentials --output json)"
|
|
export AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_SESSION_TOKEN
|
|
AWS_ACCESS_KEY_ID="$(python3 -c 'import json,sys; print(json.load(sys.stdin)["AccessKeyId"])' <<<"$CREDS")"
|
|
AWS_SECRET_ACCESS_KEY="$(python3 -c 'import json,sys; print(json.load(sys.stdin)["SecretAccessKey"])' <<<"$CREDS")"
|
|
AWS_SESSION_TOKEN="$(python3 -c 'import json,sys; print(json.load(sys.stdin)["SessionToken"])' <<<"$CREDS")"
|
|
|
|
echo "account: ${ACCOUNT_ID} (${ACCOUNT_KEY})"
|
|
echo "caller: $(aws sts get-caller-identity --query Arn --output text)"
|
|
|
|
POLICY_ARN="arn:aws:iam::${ACCOUNT_ID}:policy/seahaven-hcptf-iam-management"
|
|
ATTACHED="[]"
|
|
if aws iam get-policy --policy-arn "$POLICY_ARN" >/dev/null 2>&1; then
|
|
ATTACHED="$(aws iam list-entities-for-policy --policy-arn "$POLICY_ARN" --query 'PolicyRoles[].RoleName' --output json)"
|
|
fi
|
|
echo "seahaven-hcptf-iam-management attachments: $ATTACHED"
|
|
|
|
HCPS="$(aws iam list-roles --query 'Roles[?starts_with(RoleName, `hcptf-`)].RoleName' --output json)"
|
|
echo "hcptf-* roles still present: $HCPS"
|
|
|
|
OIDC="$(aws iam list-open-id-connect-providers --query 'OpenIDConnectProviderList[?contains(Arn, `app.terraform.io`)].Arn' --output json)"
|
|
echo "app.terraform.io OIDC: $OIDC"
|
|
|
|
if [[ "$INVENTORY" -eq 1 ]]; then
|
|
exit 0
|
|
fi
|
|
|
|
python3 - "$ATTACHED" <<'PY'
|
|
import json, sys
|
|
roles = json.loads(sys.argv[1])
|
|
if roles:
|
|
raise SystemExit(f"refusing delete: seahaven-hcptf-iam-management still attached to {roles}")
|
|
PY
|
|
|
|
if [[ "$ASSUME_YES" -eq 0 ]]; then
|
|
read -r -p "delete CloudFormation stack seahaven-terraform-substrate in ${ACCOUNT_ID}? [y/N] " ans
|
|
[[ "$ans" =~ ^[Yy]$ ]] || { echo "skipped"; exit 0; }
|
|
fi
|
|
|
|
echo "deleting seahaven-terraform-substrate (OIDC DeletionPolicy=Retain)"
|
|
aws cloudformation delete-stack --stack-name seahaven-terraform-substrate
|
|
aws cloudformation wait stack-delete-complete --stack-name seahaven-terraform-substrate
|
|
echo "stack gone. confirm OIDC still exists:"
|
|
aws iam list-open-id-connect-providers --query 'OpenIDConnectProviderList[?contains(Arn, `app.terraform.io`)].Arn' --output text
|