DEV Community

Peter Brau
Peter Brau

Posted on Originally published at psbrau-tech.github.io

CloudFormation Generated Role Names Can Break Least-Privilege IAM Twice

Problem

Least-privilege CloudFormation execution roles are often scoped to the IAM resources a stack is expected to create. That is a sound approach until the policy assumes a physical role-name pattern that CloudFormation does not actually use.

AWS currently documents that when RoleName is omitted from an AWS::IAM::Role, CloudFormation generates a unique physical ID and uses that value as the role name. The logical resource name is therefore not a reliable policy boundary by itself.

A mismatch can break the forward deployment and then break rollback against the same generated role family.

Failure Signature

The stack begins creating an IAM role, then an IAM operation fails because the execution policy's resource ARN pattern does not match the physical role name CloudFormation generated.

In the observed failure class, one generated role retained the expected naming shape while another generated role used a shorter physical name than the policy anticipated. The first visible denial occurred during tagging. When the stack rolled back, cleanup exposed the same resource-boundary mistake again.

That sequence is a useful signal: if both forward work and rollback are failing against the same IAM resource family, inspect the physical identity before adding more actions.

What We Expected

The execution policy was intended to authorize a bounded family of stack-created IAM roles, not every IAM role in the account.

The expectation was that the generated physical role names would continue to match a longer prefix inferred from the stack and logical-resource naming convention.

Root Cause

The policy was written from the intended naming convention rather than the observed physical resource identity.

A resource scope such as:

arn:aws:iam::<AWS_ACCOUNT_ID>:role/<EXPECTED_BOUNDED_PREFIX>*
Enter fullscreen mode Exit fullscreen mode

is only useful if <EXPECTED_BOUNDED_PREFIX> actually matches the role names CloudFormation creates.

The lesson is not that generated names are unsafe. The lesson is that generated physical IDs are an implementation boundary that least-privilege policy must account for. CloudFormation's current AWS::IAM::Role documentation explicitly says that it generates a unique physical ID for the role name when RoleName is not supplied.

Why the Obvious Fix Was Wrong

The tempting correction is:

arn:aws:iam::<AWS_ACCOUNT_ID>:role/*
Enter fullscreen mode Exit fullscreen mode

That makes a naming mismatch disappear by allowing the execution role to operate against every role in the account. It also destroys the resource boundary the policy was meant to preserve.

A second tempting mistake is to add only the action that failed during the forward path. That can get the deployment farther while leaving rollback unable to detach, untag, or delete the role if a later resource fails.

Least privilege must cover the intended lifecycle, not just the happy-path create call.

Minimal Correction

Use CloudFormation evidence to derive a bounded physical-name contract:

  1. Read the stack event containing the denied IAM action and resource ARN.
  2. Inspect the actual physical role name CloudFormation created or attempted to use.
  3. Compare that name with the resource ARN pattern in the execution policy.
  4. Replace the incorrect assumed prefix with the smallest verified prefix that covers only the intended generated role family.
  5. Review the IAM actions needed for both forward operations and rollback/cleanup.
  6. Keep iam:PassRole separately constrained to the intended role resources and destination service.
  7. Add a policy-contract test for the bounded physical-name pattern.

The exact lifecycle actions depend on the template. Depending on what the stack manages, the forward path may need operations such as role creation, tagging, policy attachment, and role passing; rollback may need the corresponding detach, untag, and delete operations.

For iam:PassRole, AWS documents the iam:PassedToService condition key as a way to restrict which service may receive the role. For an ECS task role, a generic condition shape is:

{
  "Condition": {
    "StringEquals": {
      "iam:PassedToService": "ecs-tasks.amazonaws.com"
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

That condition should complement, not replace, a bounded Resource ARN for the roles that may be passed.

Verification

The forward-path correction is proven when CloudFormation can perform the intended IAM operations against the generated role without broadening the policy to all roles.

The rollback contract should be verified separately. A deployment permission model is incomplete if a later stack failure can leave CloudFormation unable to detach policies, remove tags, or delete resources that the same execution role was allowed to create.

For protected environments, the safest proof is a synthetic or otherwise controlled lifecycle exercise that demonstrates both creation/update and complete rollback under the same bounded policy.

What Should Have Caught This Earlier

Three controls can catch this class before it consumes a long protected deployment:

  • Physical-name preflight: compare expected IAM ARN patterns with generated physical names observed in a safe change or prior synthetic stack.
  • Forward/rollback policy matrix: for each stack-managed IAM resource family, map create/update operations to their cleanup counterparts.
  • Static policy contract: test that the execution role permits only the verified bounded prefixes and that iam:PassRole retains both resource and service-principal constraints.

If a naming assumption is not backed by an observed physical resource or current CloudFormation behavior, treat it as an unverified dependency.

Reusable Rule

Least-privilege IAM for CloudFormation must be written against what AWS actually creates, not what a logical resource name makes you expect.

And deployment permission is not complete until rollback can undo what the forward path was allowed to create.

Sources

Top comments (0)