diff --git a/lib/org-governance-stack.ts b/lib/org-governance-stack.ts index ce4dc9a..2c201cc 100644 --- a/lib/org-governance-stack.ts +++ b/lib/org-governance-stack.ts @@ -350,6 +350,8 @@ export class OrgGovernanceStack extends cdk.Stack { "iam:DeleteRolePolicy", "iam:DeleteRole", "iam:UpdateRole", + "iam:PutRolePermissionsBoundary", + "iam:DeleteRolePermissionsBoundary", "iam:TagRole", "iam:UntagRole", ], @@ -358,7 +360,7 @@ export class OrgGovernanceStack extends cdk.Stack { ArnNotLike: { "aws:PrincipalArn": [ "arn:aws:iam::*:role/OrganizationAccountAccessRole", - "arn:aws:iam::*:role/aws-reserved/sso.amazonaws.com/*/AWSReservedSSO_Platform_*", + "arn:aws:iam::*:role/aws-reserved/sso.amazonaws.com/*AWSReservedSSO_Platform_*", ], }, }, diff --git a/lib/scp/protect-privileged-roles.json b/lib/scp/protect-privileged-roles.json index 1b76198..1d8777b 100644 --- a/lib/scp/protect-privileged-roles.json +++ b/lib/scp/protect-privileged-roles.json @@ -44,6 +44,8 @@ "iam:DeleteRolePolicy", "iam:DeleteRole", "iam:UpdateRole", + "iam:PutRolePermissionsBoundary", + "iam:DeleteRolePermissionsBoundary", "iam:TagRole", "iam:UntagRole" ], @@ -52,7 +54,7 @@ "ArnNotLike": { "aws:PrincipalArn": [ "arn:aws:iam::*:role/OrganizationAccountAccessRole", - "arn:aws:iam::*:role/aws-reserved/sso.amazonaws.com/*/AWSReservedSSO_Platform_*" + "arn:aws:iam::*:role/aws-reserved/sso.amazonaws.com/*AWSReservedSSO_Platform_*" ] } } diff --git a/scripts/create-hcptf-bootstrap-roles.sh b/scripts/create-hcptf-bootstrap-roles.sh index 3197d70..75e143c 100755 --- a/scripts/create-hcptf-bootstrap-roles.sh +++ b/scripts/create-hcptf-bootstrap-roles.sh @@ -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- 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 \ @@ -281,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."