seahaven-org-baseline/scripts/create-hcptf-bootstrap-roles.sh

190 lines
6.8 KiB
Bash
Raw Normal View History

#!/usr/bin/env bash
#
# create-hcptf-bootstrap-roles.sh — create the out-of-band HCP general apply/plan
# pair in seahaven-prod and seahaven-dev (PLAT-145). Not a CDK stack. Not
# external-dev.
#
# Prerequisites:
# * org-governance has deployed protect-privileged-roles to prod/nonprod
# (hcptf-bootstrap* is SCP-protected). Do not create the roles first.
# * Caller can sts:AssumeRole OrganizationAccountAccessRole in the target.
# * The account already has oidc-provider/app.terraform.io (Retain).
#
# Usage:
# scripts/create-hcptf-bootstrap-roles.sh --account prod|dev [--dry-run]
# scripts/create-hcptf-bootstrap-roles.sh --account prod --simulate
#
# --simulate runs iam:SimulatePrincipalPolicy against the apply role. Requires
# the role to already exist.
#
# After create: HCP workspace iam-bootstrap in project seahaven-<env>, Manual
# apply, workspace-level TFC_AWS_*_ROLE_ARN only (never a project variable set).
#
set -euo pipefail
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
TMPL="$ROOT/lib/hcptf-bootstrap"
ACCOUNT_KEY=""
DRY_RUN=0
SIMULATE=0
while [[ $# -gt 0 ]]; do
case "$1" in
--account) ACCOUNT_KEY="$2"; shift 2 ;;
--dry-run) DRY_RUN=1; shift ;;
--simulate) SIMULATE=1; shift ;;
-h|--help) sed -n '2,28p' "$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"
HCP_PROJECT="seahaven-prod"
;;
dev)
ACCOUNT_ID="710827005802"
HCP_PROJECT="seahaven-dev"
;;
*)
echo "usage: $0 --account prod|dev [--dry-run] [--simulate]" >&2
exit 2
;;
esac
render_to() {
local src="$1"
local dest="$2"
sed -e "s/__ACCOUNT_ID__/${ACCOUNT_ID}/g" -e "s/__HCP_PROJECT__/${HCP_PROJECT}/g" "$src" > "$dest"
}
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-145-hcptf-bootstrap \
--query Credentials --output json)"
export AWS_ACCESS_KEY_ID
export AWS_SECRET_ACCESS_KEY
export 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)"
if [[ "$SIMULATE" -eq 1 ]]; then
APPLY_ARN="arn:aws:iam::${ACCOUNT_ID}:role/hcptf-bootstrap"
echo "== simulate ${APPLY_ARN} =="
aws iam simulate-principal-policy \
--policy-source-arn "$APPLY_ARN" \
--action-names iam:CreateRole \
--resource-arns "arn:aws:iam::${ACCOUNT_ID}:role/tf-managed/example" \
--context-entries 'ContextKeyName=iam:PermissionsBoundary,ContextKeyValues=arn:aws:iam::'"${ACCOUNT_ID}"':policy/tf-managed/example,ContextKeyType=string' \
--query 'EvaluationResults[].{Action:EvalActionName,Decision:EvalDecision}' \
--output table
aws iam simulate-principal-policy \
--policy-source-arn "$APPLY_ARN" \
--action-names iam:CreatePolicy \
--resource-arns "arn:aws:iam::${ACCOUNT_ID}:policy/tf-managed/example" \
--query 'EvaluationResults[].{Action:EvalActionName,Decision:EvalDecision}' \
--output table
aws iam simulate-principal-policy \
--policy-source-arn "$APPLY_ARN" \
--action-names iam:CreateRole iam:PutRolePolicy iam:DeleteRole \
--resource-arns \
"arn:aws:iam::${ACCOUNT_ID}:role/githubdeploy-example" \
"arn:aws:iam::${ACCOUNT_ID}:role/cdk-hnb659fds-example" \
"arn:aws:iam::${ACCOUNT_ID}:role/OrganizationAccountAccessRole" \
--query 'EvaluationResults[].{Action:EvalActionName,Resource:EvalResourceName,Decision:EvalDecision}' \
--output table
exit 0
fi
WORKDIR="$(mktemp -d)"
cleanup() {
rm -rf "${WORKDIR:-}"
restore_creds
}
trap cleanup EXIT
render_to "$TMPL/trust-apply.json.tmpl" "$WORKDIR/trust-apply.json"
render_to "$TMPL/trust-plan.json.tmpl" "$WORKDIR/trust-plan.json"
render_to "$TMPL/apply-policy.json.tmpl" "$WORKDIR/apply-policy.json"
render_to "$TMPL/plan-refresh-policy.json.tmpl" "$WORKDIR/plan-refresh.json"
python3 - "$WORKDIR" <<'PY'
import json, pathlib, sys
root = pathlib.Path(sys.argv[1])
for p in root.glob("*.json"):
json.loads(p.read_text())
PY
create_or_update_role() {
local name="$1"
local trust_file="$2"
if aws iam get-role --role-name "$name" >/dev/null 2>&1; then
echo " $name: exists, updating trust"
if [[ "$DRY_RUN" -eq 0 ]]; then
aws iam update-assume-role-policy --role-name "$name" --policy-document "file://${trust_file}"
fi
else
echo " $name: create"
if [[ "$DRY_RUN" -eq 0 ]]; then
aws iam create-role \
--role-name "$name" \
--assume-role-policy-document "file://${trust_file}" \
--description "HCP Terraform ${name} (PLAT-145). Console/CLI owned. Manual apply only." \
--tags Key=Project,Value=hcp-bootstrap Key=Owner,Value=adam@seahavenind.com Key=ManagedBy,Value=cli
fi
fi
}
echo "== roles =="
create_or_update_role hcptf-bootstrap "$WORKDIR/trust-apply.json"
create_or_update_role hcptf-bootstrap-plan "$WORKDIR/trust-plan.json"
if [[ "$DRY_RUN" -eq 1 ]]; then
echo "dry-run: skipping PutRolePolicy / AttachRolePolicy"
exit 0
fi
aws iam put-role-policy \
--role-name hcptf-bootstrap \
--policy-name hcptf-bootstrap-iam-factory \
--policy-document "file://${WORKDIR}/apply-policy.json"
echo " hcptf-bootstrap: put inline hcptf-bootstrap-iam-factory"
aws iam put-role-policy \
--role-name hcptf-bootstrap-plan \
--policy-name hcptf-bootstrap-plan-refresh \
--policy-document "file://${WORKDIR}/plan-refresh.json"
echo " hcptf-bootstrap-plan: put inline hcptf-bootstrap-plan-refresh"
aws iam attach-role-policy \
--role-name hcptf-bootstrap-plan \
--policy-arn arn:aws:iam::aws:policy/job-function/ViewOnlyAccess \
2>/dev/null || true
echo " hcptf-bootstrap-plan: attached ViewOnlyAccess"
echo "done. Next: HCP workspace iam-bootstrap in ${HCP_PROJECT}, Manual apply,"
echo " TFC_AWS_APPLY_ROLE_ARN=arn:aws:iam::${ACCOUNT_ID}:role/hcptf-bootstrap"
echo " TFC_AWS_PLAN_ROLE_ARN=arn:aws:iam::${ACCOUNT_ID}:role/hcptf-bootstrap-plan"
echo "Then: $0 --account ${ACCOUNT_KEY} --simulate"