OIDC federation is a way for a CI/CD pipeline to obtain cloud credentials without storing any. Instead of holding a secret key, the pipeline receives a short-lived, signed token from its CI provider describing the job that is running — which repository, which workflow, which branch — and exchanges that token for a temporary cloud credential. The credential is issued for the run and expires with it. Nothing durable is kept anywhere.
The reason this matters is not that key storage is hard. It is that a long-lived cloud access key is a password with no expiry, living in a system whose entire purpose is to make things available to automation. It is copied into a local environment file by a developer who is debugging a pipeline. It ends up in a screenshot in a support ticket. It survives the contractor who created it. And unlike a password, nobody is ever prompted to change it.
What a stored key actually costs
The clean way to think about a static credential is to ask what an attacker gets from a single copy of it. The answer is: everything that credential can do, from anywhere on the internet, until somebody notices.
| Property | Stored access key | Federated short-lived credential |
|---|---|---|
| Lifetime | Until revoked, which usually means until an incident | Minutes to an hour |
| Where it can be used from | Anywhere | Only from a job that can prove the right claims |
| What it is scoped to | Whatever the identity can do | The role the trust policy allows for that specific job |
| Rotation | A manual process someone owns and postpones | Not applicable; there is nothing to rotate |
| Blast radius of a leak | Full, silent, indefinite | Bounded by the remaining minutes of a session |
| Evidence trail | A key was used | A named job in a named repository assumed a named role |
That last row is underrated. With federation, every action in the cloud traces back to a workflow run, which traces back to a commit and a reviewer. Attribution stops being a forensic exercise.
How the exchange works
There are four parties and one handshake.
- The job asks its CI provider for an identity token. The provider mints a JSON Web Token describing the run: the issuer, the audience, and a subject claim naming the repository and the branch, tag or environment.
- The job presents that token to the cloud. No secret is involved; the token is the evidence.
- The cloud verifies it. The signature is checked against the provider’s published keys, and the claims are checked against the trust policy attached to the role being requested.
- A temporary credential comes back, scoped to that role and expiring shortly.
The design is unremarkable. The failure modes are where the interesting work is.
The trust policy is the whole control
Everything that makes federation safe lives in one document, and it is easy to write a version that is worse than the key it replaced.
Pin the subject claim to the smallest thing that is true. A condition that matches repo:your-org/*:* trusts every repository in the organization, including one a contractor created this morning, including one an attacker can open a pull request against. Pin it to the repository, and then to the branch, tag or deployment environment that is allowed to deploy.
Prefer environments to branches for production. A branch condition is satisfied by anything that can push to that branch. An environment condition can additionally require a human approval before the token is issued at all — which is the difference between a pipeline that can reach production and one that reaches production only when someone says so.
Check the audience. It is one line and it prevents a token minted for one relying party from being replayed at another.
Use separate roles per environment. One role that can deploy anywhere is a convenience that removes the benefit you just bought. A role per environment, each with only the permissions that environment needs, keeps the blast radius aligned with the account boundaries described in the multi-account landing zone.
Deleting the keys is the point
The step teams skip is the only one that changes the risk. Running federation alongside the old access keys means you have added a mechanism and removed nothing. The keys are still there, still valid, still copied into whatever they were copied into two years ago.
A migration that finishes looks like this:
- Stand up federation and move one non-production pipeline to it.
- Move the rest, environment by environment, production last.
- Inventory every long-lived credential in the organization. Note who created each one and what it is used for. The list will contain surprises.
- Disable them, watch for a defined window, then delete them.
- Prevent new ones structurally.
Step five is what makes it stick. A policy at the organization level that denies the creation of long-lived credentials outright turns “we don’t use access keys” from a convention someone has to remember into a property of the environment. Conventions decay at the first deadline; structure does not.
What this looks like when it is finished
In the AWS Enterprise Baseline — the AWS edition of the BuiltForProd Baseline — there are no IAM users anywhere and no long-lived AWS access keys for anyone, human or machine. People sign in through IAM Identity Center against one of eleven permission sets, with multi-factor authentication required and sessions that expire — the human half of the same model, covered in zero trust access for engineers. Pipelines federate through GitHub OIDC into per-environment deployer roles in a dedicated automation account. A service control policy denies the creation of IAM users and access keys across the organization, which means the property is enforced above every account rather than agreed inside each one. Production is read-only for everyone except the platform and DevOps leads, and every apply to production passes a repository environment gate first.
Those specifics describe the AWS edition. The property they deliver — no standing credential exists for routine work — is part of the BuiltForProd Standard and applies to every edition.
A short checklist
- No long-lived cloud access key exists for a human or for a pipeline.
- Every trust policy pins issuer, audience and a subject claim narrowed to a repository and a branch, tag or environment.
- Production deployment requires an approval that gates token issuance, not only the merge.
- Roles are per environment and least privilege, not one role that can reach everything.
- Creating a long-lived credential is denied by policy, not discouraged by documentation.
- A break-glass path exists, is time-boxed, is alerted on, and has been exercised this year.
If more than two of those are open, the work is worth a sprint. If all six are open, it is worth a conversation — with us or with anyone, but soon.