mirror of
https://github.com/Sea-Haven-Industries/seahaven-org-baseline.git
synced 2026-10-07 16:08:55 +00:00
189 lines
6.8 KiB
Bash
Executable file
189 lines
6.8 KiB
Bash
Executable file
#!/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"
|