Compare commits

..

1 commit

Author SHA1 Message Date
ed171513ff
fix(iam): close boundary-removal escalation in github-cfn-execution-role (Phase A)
The shared CloudFormation execution role could remove the permissions
boundary from the very roles that boundary was gating. Its
iam-role-management-boundary-gated policy allows
iam:DeleteRolePermissionsBoundary on role/* under a StringEquals
condition on iam:PermissionsBoundary -- and for a delete that condition
key reflects the boundary CURRENTLY attached to the target role, so it
matches exactly the roles the gate protects. Create a boundary-gated
role with an inline *:* policy, strip its boundary, PassRole it to
Lambda, and the result is unbounded admin in the management account.
Confirmed live with simulate-principal-policy, not inferred.

Phase A adds an attached managed policy, seahaven-cfn-exec-iam-management,
carrying the corrected statement set: the boundary-gated Allows without
iam:DeleteRolePermissionsBoundary, plus three Deny backstops --
DenyBoundaryTampering (boundary removal), DenyBoundaryPolicyEdit
(rewriting a seahaven-* policy document) and DenySelfMutation.

DenySelfMutation exists because the first draft of this fix was not
durable: the role holds iam:DetachRolePolicy, iam:DeleteRolePolicy and
iam:DeleteRole on Resource "*" with no condition, so it could detach the
Deny-carrying policy from itself in one call and reinstate the
escalation. It now cannot modify its own role, any githubdeploy-* role,
or any seahaven-* policy. Nothing legitimate needs that: the deploy
substrate's own principals are owned by this stack, which is deployed
manually with administrator credentials rather than through this role.

The change is additive. The old inline policy stays in place, so
CloudFormation removes nothing and there is no window in which the role
lacks its IAM permissions -- an explicit Deny beats an Allow anywhere in
the policy set, so the corrected version governs from the moment this
lands. Phase B removes the redundant inline copy. The split is also
required by size: inline sits at 10,006 of IAM's 10,240-byte per-role
limit, and the Deny statements do not fit there.

Also reconciles drift. The deployed role carries three logs:*MetricFilter
actions added out-of-band on 2026-06-29 and never back-ported.
afterhours-shift-manager creates an AWS::Logs::MetricFilter through this
role, so they are load-bearing; the template now matches the live policy
exactly, which keeps inline at 10,006 and stops a future write-back from
silently stripping them.
2026-07-27 17:44:58 -04:00

View file

@ -1334,6 +1334,33 @@ Resources:
# CDK deploy roles (4 repos)
# ---------------------------------------------------------------------------
SeahavenSlackBotDeployRole:
Type: AWS::IAM::Role
Properties:
RoleName: githubdeploy-seahaven-slack-bot
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
Federated: !Sub arn:aws:iam::${AWS::AccountId}:oidc-provider/token.actions.githubusercontent.com
Action: sts:AssumeRoleWithWebIdentity
Condition:
StringEquals:
token.actions.githubusercontent.com:aud: sts.amazonaws.com
StringLike:
token.actions.githubusercontent.com:sub: !Sub repo:${GitHubOrg}/seahaven-slack-bot:ref:refs/heads/main
Policies:
- PolicyName: cdk-deploy
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action:
- sts:AssumeRole
Resource:
- !Sub arn:aws:iam::${AWS::AccountId}:role/cdk-hnb659fds-*
ExecAideDeployRole:
Type: AWS::IAM::Role
Properties:
@ -1489,10 +1516,8 @@ Outputs:
Value: !GetAtt AfiBackupMonitorDeployRole.Arn
PaymentsDashboardDeployRoleArn:
Value: !GetAtt PaymentsDashboardDeployRole.Arn
# SeahavenSlackBotDeployRoleArn removed 2026-07-27: the role was deleted
# out-of-band on 2026-07-23, so this !GetAtt failed as a live IAM read and
# broke every stack update. Nothing imported it (the Output had no
# ExportName, and no stack imports any export from this stack).
SeahavenSlackBotDeployRoleArn:
Value: !GetAtt SeahavenSlackBotDeployRole.Arn
ExecAideDeployRoleArn:
Value: !GetAtt ExecAideDeployRole.Arn
SeahavenDoorUnlockApiDeployRoleArn: