Skip to content

sec(iac/aws): role-prefix ARN scopes are satisfiable via the IAM path (arn:aws:iam::*:role/cudly-*) #197

Description

@cristim

Surfaced during the review of LeanerCloud/cloud-commitments-cli#1818 (which closes LeanerCloud/cloud-commitments-cli#1723). Pre-existing, not introduced by that PR, which changed no Resource, Condition or ARN line. Filed separately rather than widening LeanerCloud/cloud-commitments-cli#1818's scope.

What

terraform/environments/aws/ci-cd-permissions/policy_data.tf scopes several statements with a trailing-wildcard role ARN:

  • :61 arn:aws:iam::*:role/cudly-* (with policy/cudly-* and instance-profile/cudly-*)
  • :77 arn:aws:iam::*:role/cudly-*, the iam:PassRole statement, conditioned on iam:PassedToService

In an IAM resource ARN, * matches any sequence of characters including /, and the IAM role path is part of the ARN. So arn:aws:iam::123456789012:role/cudly-x/EvilRole satisfies arn:aws:iam::*:role/cudly-*. The prefix that is supposed to identify our roles can be carried in the path, which is a field the role's creator controls, rather than in the role name.

The same shape applies to arn:aws:iam::*:role/CUDly* in CrossAccountAssumeRoleCeiling. That one additionally has drift protection from TestBoundaryMatchesCrossAccountRolePrefix, but the path-matching property is identical. Note this is a different concern from LeanerCloud/cloud-commitments-cli#1628 item 4 / LeanerCloud/cloud-commitments-cli#1636, which is about the missing per-tenant account condition on sts:AssumeRole, not about path-bearing ARNs.

Why it matters

This is the classic IAM path wildcard bypass: an attacker satisfies a name-prefix guard by putting the required prefix in a segment they choose.

It is currently contained, and the containment is worth stating precisely so nobody over-reacts or under-reacts:

So this is not a live escape today. It is a guard whose correctness depends entirely on a separate control staying in place, which is exactly the kind of coupling that breaks silently later.

Fix direction

Prefer a form that cannot be satisfied by a path segment. Options, roughly in order of preference:

  1. Constrain the path explicitly, e.g. require arn:aws:iam::*:role/cudly-* and add an iam:ResourceTag or a path condition that pins the expected path, so the name prefix is not the only thing being checked.
  2. Pin the path in the ARN itself (role/cudly/…) and match on the full, expected shape rather than a trailing wildcard.
  3. At minimum, add a guard test asserting that a path-bearing ARN such as arn:aws:iam::123456789012:role/cudly-x/EvilRole does not satisfy the intended scope, so the residual is pinned rather than remembered.

Whatever is chosen, note the house rule: a security condition must be compared to its expected value. "Starts with the right prefix" is a shape check, and a shape check admits every value of that shape.

Verification bar

Both directions. Assert that a path-bearing impostor ARN is refused and that the real deploy roles still match, or the fix will pass its test by refusing everything and break the deploy.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions