PATTERN Cited by 1 source
Sub-field scoping for role trust¶
Pattern¶
Structure your OIDC IdP's sub claim as a hierarchy of
identity components separated by a delimiter (e.g.
<org>:<app>:<machine>, or repo:<owner>/<name>:ref:<branch> in
GitHub Actions), so that cloud trust policies can scope Role
assumption along any prefix of the hierarchy using string-match
conditions like StringLike.
Canonical example, from Fly.io:
From the post:
Every OIDC token Fly.io generates is going to have a
subfield formattedorg:app:machine, so we can lock IAMRolesdown to organizations, or to specific Fly Apps, or even specific Fly Machine instances. (Source: sources/2024-06-19-flyio-aws-without-access-keys)
Why structure the sub¶
The OIDC spec allows sub to be any per-user-unique string. A
Fly.io machine could have sub = "ancient-snow-4824" and that
would satisfy the protocol. The design choice to make it
<org>:<app>:<machine> is what makes trust policies expressive.
With opaque sub values:
- Trust policies need
StringEqualson an exact value → one Role per Machine, or one Role trusted for every Machine. - To scope to "any Machine in the
weather-catapp," you'd need a second API (a name-to-sub-list registry) or you'd end up with a wildcard that's too permissive.
With hierarchical sub:
"example:*"→ any Machine in theexampleorg."example:weather-cat:*"→ any Machine in theweather-catapp."example:weather-cat:ancient-snow-4824"→ one specific Machine.
The scope is a property of the trust policy, driven by the identity shape of the IdP.
The prefix-hierarchy convention¶
Common sub shapes across platforms:
| Platform | sub shape |
|---|---|
| Fly.io | <org>:<app>:<machine> |
| GitHub Actions | repo:<owner>/<name>:ref:refs/heads/<branch> |
| GitLab CI | project_path:<group>/<project>:ref_type:branch:ref:<branch> |
| EKS IRSA | system:serviceaccount:<ns>:<sa-name> |
Each of these is a canonical
sub-field scoping
instance. The structure is chosen by the IdP; the scoping is
enforced by the cloud trust policy's StringLike condition.
Trust-policy idiom¶
On AWS, the two idiomatic conditions are:
"Condition": {
"StringEquals": {
"<idp-issuer>:aud": "sts.amazonaws.com"
},
"StringLike": {
"<idp-issuer>:sub": "<prefix-or-exact>"
}
}
Two load-bearing conditions:
aud StringEquals <expected-aud>— guarantees the token was minted specifically for this audience. Without this, a token minted for a different service could be cross-used to assume this Role.sub StringLike <scoping-prefix>— the scoping pivot.
Use StringEquals (not StringLike) on sub for the tightest
possible scope (one specific Machine / one specific
repo+branch). Use StringLike with * when you want to allow a
prefix class.
Anti-pattern: wildcard the whole sub¶
This trusts any token signed by the IdP — i.e., any workload in the Fly.io org can assume the Role. Effectively: "my org has Role X" rather than "this specific app has Role X." Useful occasionally for a well-audited platform-admin Role, but the default should be tight prefix-matching.
The "scope your subject" discipline¶
Like CIDR blocks in VPCs or resource ARNs in IAM policies, sub
prefixes give teams a structured way to express who within the
IdP's population gets access. The hierarchy is the policy author's
API surface — a well-chosen delimiter and component order makes
expressive policies easy; a poorly-chosen one makes them
impossible.
Fly.io's org:app:machine order goes from coarsest to finest,
which matches the usual scoping question ("everything in this
org? or this app? or just this one machine?").
Related counterparts on other clouds¶
- GCP Workload Identity Federation — uses attribute mapping from OIDC claims to Google identity, and conditions via CEL (Common Expression Language). Structured claims similarly drive scoping expressions.
- Azure workload-identity federation — uses federated-
credential records that match on
subjectstring. - HashiCorp Vault's OIDC auth — maps claims to roles via user/group_claim + bound_claims / bound_subject patterns.
Seen in¶
- sources/2024-06-19-flyio-aws-without-access-keys — canonical
wiki instance.
sub = <org>:<app>:<machine>;StringLikeon the sub prefix; "we can lock IAMRolesdown to organizations, or to specific Fly Apps, or even specific Fly Machine instances."
Related¶
- patterns/oidc-role-assumption-for-cross-cloud-auth — the outer pattern this is the scoping component of.
- concepts/oidc-federation-for-cloud-access, concepts/workload-identity, concepts/least-privileged-access.
- systems/oidc-fly-io, systems/aws-iam.