↓ Skip to main content

Federated subject recycling continued: AWS edition

·4 mins
Table of Contents

Federated credentials reduce the risks of token exposure and leaked secrets, but they can leave dangling trust relationships too. In my previous post, I covered subject recycling in Microsoft Entra ID: how to find GitHub Actions and GitLab trusts that rely on mutable subjects and how to switch to immutable identifiers.

This time, I’m looking at AWS IAM role trust policies. The question is the same: does the trust rely on an immutable identifier or a name that could be recycled?

As a quick recap, here’s how mutable and immutable subjects differ:

GitLab example:

-"sub": "project_path:nicolonsky/ITDR:ref_type:branch:ref:main"
+"sub": "project_id:715070358:ref_type:branch:ref:main"

GitHub example:

 -"sub": "repo:nicolonsky/ITDR:ref:refs/heads/main"
+"sub": "repo:nicolonsky@32899754/ITDR@715070358:ref:refs/heads/main"

AWS IAM role trust policies
#

If you’re coming from the Microsoft world, it’s worth distinguishing three core AWS IAM concepts:

  • Permissions policy: Defines which actions are allowed or denied on which resources. For example, s3:ListBucket allows listing objects in a bucket. The permissions are comparable to those in an Azure RBAC role. 1 2
  • Role trust policy: Defines who can assume a role and under which conditions. In the Microsoft world, principals always refer to Entra ID objects, such as users, groups and service principals (instances of app registrations). AWS can delegate trust directly to an external identity provider without local objects. 3
  • IAM role: Combines a trust policy with permissions policies. 4

Example configuration
#

Before using GitHub Actions or GitLab as a federated principal, add the IdP as an OIDC provider in IAM:

GitHub Actions and GitLab OIDC providers in IAM
GitHub Actions and GitLab listed as OIDC identity providers in AWS IAM

Then configure the role’s permissions and reference the provider in its trust policy.

This trust policy is too permissive: it uses mutable identifiers and StringLike with a wildcard that allows any GitHub Actions workflow under the specified GitHub handle.
IAM role trust policy for OIDC federation
Trusted entities and OIDC subject conditions for the IAM role

AWS CLI
#

We can scan role trust policies in bulk with the AWS CLI and jq. The scan flags subject conditions without the expected immutable identifiers.

AWS CLI scan results for mutable OIDC subjects
IAM role trust policies with subjects missing the checked immutable identifier markers
aws iam list-roles --output json --no-cli-pager |
jq -r '
  ["RoleName", "RoleArn", "Operator", "Claim", "Subject"],
  (
    .Roles[] | . as $role
    | .AssumeRolePolicyDocument.Statement
    | if type == "array" then .[] else . end
    | select(.Effect == "Allow")
    | (.Condition // {} | to_entries[]) as $condition
    | $condition.value | to_entries[]
    | select(.key | endswith(":sub"))
    | .key as $claim
    | .value | if type == "array" then .[] else . end
    | select(type == "string")
    | select(
        if $claim == "token.actions.githubusercontent.com:sub" then
          (test("repo:.+@\\d+/.+@\\d+:|repository_id"; "i") | not)
        elif test("project_id|project_path|ref_type"; "i") then
          (test("project_id"; "i") | not)
        else false end
      )
    | [$role.RoleName, $role.Arn, $condition.key, $claim, .]
  )
  | @tsv
'
Condition operators, wildcards and combinations of claims make exhaustive detection difficult. Treat this scan as a starting point.

For a broader review, list roles that allow sts:AssumeRoleWithWebIdentity, including through wildcard actions. This query excludes Cognito-only trust statements (cognito-identity.amazonaws.com) and returns the full trust policies for manual review:

Broader scan of IAM role trust policies
Roles allowing web identity federation through providers other than Cognito
aws iam list-roles --output json --no-cli-pager |
jq '
  [
    .Roles[]
    | select(any(
        .AssumeRolePolicyDocument.Statement
        | if type == "array" then .[] else . end;
        .Effect == "Allow" and any(
          (.Principal | if type == "object" then .Federated // [] else . // [] end) | if type == "array" then .[] else . end;
          . != "cognito-identity.amazonaws.com"
        ) and any(
          .Action // [] | if type == "array" then .[] else . end;
          . as $action
          | "sts:AssumeRoleWithWebIdentity"
          | test(
              "^" + ($action | gsub("\\*"; ".*") | gsub("\\?"; ".")) + "$";
              "i"
            )
        )
      ))
    | {RoleName, Arn, AssumeRolePolicyDocument}
  ]
'

Recap
#

OIDC and federation are vendor-agnostic, and so are the risks of weak or misconfigured workloads. Searching for mutable subjects can help identify privilege escalation paths during security assessments or red-team engagements.

Nicola Suter
Author
Nicola Suter
Security Consultant @baseVISION | Building cyber defense with Microsoft Security | Microsoft MVP