GitHub Actions needs OIDC audience constraints
44 points by yossarian
44 points by yossarian
GH Actions security is kinda rickety. If you want to prevent i.e. untrusted public users from being able to i.e. use a specific runner, there is no way to do so other than Actions files in the repo, which are attacker controlled anyways. It's pretty annoying for self-hosted public runners! One solution would be i.e. restrict allowed branches to run on at the runner level, but GH offers no such thing last I looked.
I've always wondered what OpenID is and why everyone only seems to talk about the Connect part, whatever that is.
Not wondered enough to research it, mind you.
I’ll force the knowledge onto you. OpenID is a consortium that, once upon a time, proposed a standard of the same name for a system of “federated Identity Providers” - effectively a combination of SSO and external sign in providers that you see when you have “Sign In with Google” or whatever.
OpenID Connect kind of fits this niche? But it’s unrelated to the legacy OpenID IdP stuff - it’s just sparkling OAuth2; largely they added standardised JWT based bearer tokens, and a key discovery mechanism for them, that allows you to attest that the identity provider controls the domain the identity is tied to. In practice OIDC is used as a more modern and slightly less annoying alternative to SAML. Relevant to this case, it’s seen particular adoption for attesting machine identity (AWS workloads on EKS, PyPI trusted publishing).
Essentially, it’s a poorly designed identity layer on top of OAuth 2.0 protocol based on JWTs. Easy to self-host a provider server, but in practice each one of them has its own quirks and inconveniences that the clients have to be aware of…
Seems pretty reasonable. I loosely (i.e. not a dedicated issue yet) suggested this to Forgejo (which added workload identity support in v15): https://codeberg.org/forgejo/forgejo/pulls/10481#issuecomment-20829260
If GHA's sub isn't sufficient, isn't the solution to push for sub enhancements (or additional metadata in custom claims) rather than trying to elevate aud?
Or the workflow name claim, or environments, or a combination of aud, sub, and workflow, etc. (Does anyone rely on aud only?)
OP does recognize this (“came to the conclusion that the machine identity […] must include the workflow name”) and hints at the real problem: “many integrations want to use just the org/repo slug as a sufficient identity”.
If relying parties want to trust org/repo but don’t want to track which workflows and refs are supposed to call, aud feels like a crutch. A case could be made that different actions could be setting different aud values but that’s an insufficient compartmentalization problem that declarative makes a bit more visible but IMHO doesn’t eliminate.
One of the unstated annoyances here is a few cloud providers don't support inspecting anything other than iss, sub, and aud - so you can't actually use the extra OIDC claims GitHub provides[0] and effectively have to rely on org/repo for identity.
Notably, AWS largely can't read anything other than the aforementioned three unless you use a proprietary claim format[1] that GHA still can't emit[2], thus https://github.com/Skyscanner/gha-aws-oidc-sample tricks at work (completely sub reliant).
At least AWS have recently been working on issuer-specific workarounds[3][4] (but this still leaves Enterprise Server/self-hosted orgs in an awkward spot 😅)
Nobody (including me, the OP) is saying you should rely solely on the audience claim! The audience is not an alternative to properly identifying the principal for the ID token.
The only point of the post is that GitHub’s ID token generation is more flexible than is ideal. Audiences should be fixed to make it harder to an attacker to pivot between services, not because they can replace the subject (or better claims).
I’d also like to see declarative aud, but I can kind of see why that one was left to Actions publishers to choose, instead of equipping the STS with the mapping and starting to reject token signing requests, breaking CI. Ultimately in those cases it seems to me that aud is fixed, it’s just fixed to an action’s version as opposed to the definition’s gitref, and that leaves some slack (mutable tags, supply chain attacks, etc.) Seems like the issuers prefer to focus on adding custom claims, but as someone else clarified earlier, there are relying parties that can only match on sub and aud (and iss) so there might be a broader compatibility motivation. (Edit: clarifying the case, formatting)
Not sure what you mean why elevating aud: the idea in the post is to protect it because it’s already critical, not to make it more critical.
But yes, the post calls out that RPs should do more than pattern matching on the subject claim. The problem is that many RPs don’t, so the audience is a valuable defense in depth.
What you could do is generate the id token in one job and then pass it as an input to another job.
It’s heavy-handed but it does get you security isolation.
Problem is that now the job-id claim in the id token doesn’t match the job it’s being used in; which is unfortunate.