Compare commits

..

5 commits

Author SHA1 Message Date
f0a2b7191d
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 18:06:46 -04:00
Adam Moussa
0df5ee3955
Merge pull request #97 from Sea-Haven-Industries/fix/remove-slack-bot-role-step2
fix(iam): drop the decommissioned slack-bot deploy role from the stack (step 2/2)
2026-07-27 18:06:43 -04:00
e009d3c65f
fix(iam): drop the decommissioned slack-bot deploy role from the stack (step 2/2)
Removes the SeahavenSlackBotDeployRole resource block. Step 1 recorded
DeletionPolicy/UpdateReplacePolicy Retain in the deployed template, so
CloudFormation stops managing the resource without issuing DeleteRole
against a role that no longer exists -- confirmed from the change set,
which reports PolicyAction: Retain on a single Remove entry.

seahaven-slack-bot was decommissioned in favour of sh-mcp and the role
was deleted directly in IAM on 2026-07-23. The stack is now consistent
with reality again, and stack updates no longer fail on it.
2026-07-27 18:04:47 -04:00
Adam Moussa
2ec783052a
Merge pull request #96 from Sea-Haven-Industries/fix/remove-decommissioned-slack-bot-role
fix(iam): stop the decommissioned slack-bot role breaking every stack update (step 1/2)
2026-07-27 18:02:55 -04:00
2f232b6efd
fix(iam): stop the decommissioned slack-bot role breaking every stack update
seahaven-slack-bot was retired in favour of sh-mcp and its deploy role was
deleted directly in IAM on 2026-07-23, leaving the stack holding a
resource that no longer exists. The Outputs section resolved
!GetAtt SeahavenSlackBotDeployRole.Arn as a LIVE IAM read at the end of
every update, so the role's absence failed the whole thing:

  Unable to retrieve Arn attribute for AWS::IAM::Role, with error message
  The role with name githubdeploy-seahaven-slack-bot cannot be found. (404)

This is latent and invisible: the resource definition is unchanged, so it
produces no change-set entry, and change sets do not preview Outputs
resolution. A clean change set was not evidence the update would succeed.
It surfaced when the Phase A boundary-Deny change failed on it.

Step 1 of two. Removes the Output so updates stop resolving the ghost, and
records DeletionPolicy/UpdateReplacePolicy Retain so that step 2 can drop
the resource without CloudFormation issuing DeleteRole against a role that
is not there. Verified from the change set that this step touches only
DeletionPolicy and UpdateReplacePolicy -- metadata, requiresRecreation
Never -- so no IAM call is made against the missing role.

Nothing imported the Output: it had no ExportName, and no stack imports
any export from this stack.

Step 2 deletes the resource block itself.
2026-07-27 18:00:38 -04:00

View file

@ -1334,33 +1334,6 @@ 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:
@ -1516,8 +1489,10 @@ Outputs:
Value: !GetAtt AfiBackupMonitorDeployRole.Arn
PaymentsDashboardDeployRoleArn:
Value: !GetAtt PaymentsDashboardDeployRole.Arn
SeahavenSlackBotDeployRoleArn:
Value: !GetAtt SeahavenSlackBotDeployRole.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).
ExecAideDeployRoleArn:
Value: !GetAtt ExecAideDeployRole.Arn
SeahavenDoorUnlockApiDeployRoleArn: