This commit is contained in:
Adam Moussa 2026-09-27 21:20:13 -04:00 • committed by GitHub
commit 02f06b3abe
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 100 additions and 6 deletions

View file

@ -260,7 +260,7 @@ export class OrgGovernanceStack extends cdk.Stack {
name: "protect-privileged-roles",
type: "SERVICE_CONTROL_POLICY",
description:
"prod/nonprod: protect break-glass, CDK exec, githubdeploy, and hcptf-bootstrap roles",
"prod/nonprod: protect break-glass, CDK exec, githubdeploy, hcptf-bootstrap, and /platform/ roles",
targetIds: [prodOu.attrId, nonprodOu.attrId],
content: scpContent("protect-privileged-roles"),
});
@ -338,6 +338,33 @@ export class OrgGovernanceStack extends cdk.Stack {
},
},
},
{
Sid: "ProtectPlatformPath",
Effect: "Deny",
Action: [
"iam:CreateRole",
"iam:UpdateAssumeRolePolicy",
"iam:AttachRolePolicy",
"iam:DetachRolePolicy",
"iam:PutRolePolicy",
"iam:DeleteRolePolicy",
"iam:DeleteRole",
"iam:UpdateRole",
"iam:PutRolePermissionsBoundary",
"iam:DeleteRolePermissionsBoundary",
"iam:TagRole",
"iam:UntagRole",
],
Resource: "arn:aws:iam::*:role/platform/*",
Condition: {
ArnNotLike: {
"aws:PrincipalArn": [
"arn:aws:iam::*:role/OrganizationAccountAccessRole",
"arn:aws:iam::*:role/aws-reserved/sso.amazonaws.com/*AWSReservedSSO_Platform_*",
],
},
},
},
{
Sid: "ProtectDelegatedAdminMembership",
Effect: "Deny",

View file

@ -31,6 +31,33 @@
]
}
}
},
{
"Sid": "ProtectPlatformPath",
"Effect": "Deny",
"Action": [
"iam:CreateRole",
"iam:UpdateAssumeRolePolicy",
"iam:AttachRolePolicy",
"iam:DetachRolePolicy",
"iam:PutRolePolicy",
"iam:DeleteRolePolicy",
"iam:DeleteRole",
"iam:UpdateRole",
"iam:PutRolePermissionsBoundary",
"iam:DeleteRolePermissionsBoundary",
"iam:TagRole",
"iam:UntagRole"
],
"Resource": "arn:aws:iam::*:role/platform/*",
"Condition": {
"ArnNotLike": {
"aws:PrincipalArn": [
"arn:aws:iam::*:role/OrganizationAccountAccessRole",
"arn:aws:iam::*:role/aws-reserved/sso.amazonaws.com/*AWSReservedSSO_Platform_*"
]
}
}
}
]
}

View file

@ -16,7 +16,8 @@
# scripts/create-hcptf-bootstrap-roles.sh --account prod --allow-workspace STACK-prod
#
# --simulate runs iam:SimulatePrincipalPolicy against the apply role. Requires
# the role to already exist.
# the role to already exist. The policy source is the live Role.Arn from
# iam:GetRole, so an existing unpathed role and a /platform/ create both match.
#
# Default trust is exact StringEquals for workspace iam-bootstrap-<env> only.
# HCP workspace names are org-unique, so prod and dev cannot both be
@ -145,8 +146,41 @@ AWS_SESSION_TOKEN="$(python3 -c 'import json,sys; print(json.load(sys.stdin)["Se
echo "account: ${ACCOUNT_ID} (${ACCOUNT_KEY})"
echo "caller: $(aws sts get-caller-identity --query Arn --output text)"
# Live ARN, checked against the role path. IAM role names are unique per
# account, so GetRole finds either /hcptf-bootstrap or /platform/hcptf-bootstrap.
role_arn() {
local name="$1"
local line arn path
# Command substitution so set -e stops when GetRole fails. A missing role
# must not fall through to a printed ARN.
line="$(aws iam get-role --role-name "$name" \
--query 'Role.[Arn,Path]' --output text)"
arn="${line%%$'\t'*}"
path="${line#*$'\t'}"
path="${path%$'\r'}"
case "$path" in
/)
[[ "$arn" == "arn:aws:iam::${ACCOUNT_ID}:role/${name}" ]] || {
echo "${name}: ARN ${arn} does not match path ${path}" >&2
exit 1
}
;;
/platform/)
[[ "$arn" == "arn:aws:iam::${ACCOUNT_ID}:role/platform/${name}" ]] || {
echo "${name}: ARN ${arn} does not match path ${path}" >&2
exit 1
}
;;
*)
echo "${name}: unexpected path ${path} (ARN ${arn})" >&2
exit 1
;;
esac
printf '%s\n' "$arn"
}
if [[ "$SIMULATE" -eq 1 ]]; then
APPLY_ARN="arn:aws:iam::${ACCOUNT_ID}:role/hcptf-bootstrap"
APPLY_ARN="$(role_arn hcptf-bootstrap)"
echo "== simulate ${APPLY_ARN} =="
echo "-- CreateRole with tf-managed boundary (expect allowed) --"
aws iam simulate-principal-policy \
@ -239,10 +273,14 @@ create_or_update_role() {
aws iam update-assume-role-policy --role-name "$name" --policy-document "file://${trust_file}"
fi
else
echo " $name: create"
echo " $name: create on /platform/"
# Path is create-only. IAM cannot move an existing role onto /platform/.
# Prod and dev already have hcptf-bootstrap and hcptf-bootstrap-plan, so
# this branch does not run for them. Do not delete and recreate to set a path.
if [[ "$DRY_RUN" -eq 0 ]]; then
aws iam create-role \
--role-name "$name" \
--path /platform/ \
--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
@ -277,9 +315,11 @@ aws iam attach-role-policy \
2>/dev/null || true
echo " hcptf-bootstrap-plan: attached ViewOnlyAccess"
APPLY_ARN="$(role_arn hcptf-bootstrap)"
PLAN_ARN="$(role_arn hcptf-bootstrap-plan)"
echo "done. Next: HCP workspace ${BOOTSTRAP_WORKSPACE} 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 " TFC_AWS_APPLY_ROLE_ARN=${APPLY_ARN}"
echo " TFC_AWS_PLAN_ROLE_ARN=${PLAN_ARN}"
if [[ -n "$ALLOW_WORKSPACE" ]]; then
echo "First-apply/import window: point workspace ${ALLOW_WORKSPACE} TFC_AWS_* at the pair above,"
echo " apply, retarget scoped ARNs, then re-run this script with no --allow-workspace."