Skip to content

ttl-reaper: cross-account trust names a CFN-generated role ARN suffix — recreating the stack breaks EVERY account at once (#457 trap 1) #476

Description

@scttfrdmn

Every customer account's trust policy names the reaper's execution role by an ARN containing a CloudFormation-generated physical ID:

arn:aws:iam::966362334030:role/spawn-ttl-reaper-production-TTLReaperFunctionRole-ZJ84YZ2dCPei
                                                                                 ^^^^^^^^^^^^

Recreating the spawn-ttl-reaper-production stack regenerates that suffix. Every trust policy referencing the old ARN then denies, simultaneously, across every account.

Why it is worse than it sounds

This is #457 trap 1, and #469 documented the consequence precisely: from the reaper's side, "our role ARN changed" is indistinguishable from "the entire customer base uninstalled at once." #469's per-account aggregation means each broken account contributes 1 to AccountsDenied, and REAPER REACHED NO ACCOUNTS fires — which is the correct signal, but the operator sees a mass-uninstall shape and has to already know about this trap to look at the role ARN first. (The README's alarm table says "investigate our side first — the #457 role-ARN suffix" for exactly this reason.)

Recovery is O(number of accounts) and needs admin in each: every account must redeploy ttl-reaper-cross-account-role.yaml with the new ARN. For third-party accounts that means asking each owner. There is no way to fix it from the infra account alone.

Now affecting 2 accounts, not 1

  • 435415984226 (Mycelium Development)
  • 942542972736 — added 2026-07-31

Each account added multiplies the blast radius. Fixing this gets strictly more expensive over time and is cheapest now.

Proposed fix

Give the function role a stable, explicit name so its ARN is deterministic and stack-recreation-safe:

TTLReaperFunctionRole:
  Properties:
    RoleName: !Sub 'spawn-ttl-reaper-${Environment}-role'

Requires CAPABILITY_NAMED_IAM (the Makefile currently passes only CAPABILITY_IAM) and blocks a no-downtime rename, since the role cannot be renamed in place. Migration is the careful part and must not lock us out mid-flight:

  1. Add the new stable-named role alongside the existing one.
  2. Have each account's trust policy accept both principals.
  3. Cut the function over to the new role; confirm AccountsDenied: 0.
  4. Drop the old principal from each account, then the old role.

A Path-based ARN or a dedicated non-SAM role stack are alternatives; the two-principal migration is the part that matters regardless of which is chosen.

Acceptance

  • The reaper's role ARN survives a full stack delete/recreate.
  • deployment/cloudformation/ttl-reaper-cross-account-role.yaml documents that the ARN is now stable.
  • Migration does not require simultaneous redeploys across accounts.

Related: #457 (trap 1), #469 (why the failure presents as mass-uninstall).

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:infraIAM, buckets, CloudFormation, releasesarea:lifecycleTTL / idle / on-complete / hibernation / reaperbugSomething isn't workingcomponent:spawn

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions