Zero long-lived cloud credentials: OIDC in CI/CD

Static cloud keys in CI are a liability you chose to keep. OIDC federation replaces them with short-lived tokens your pipeline requests per job.

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.

Figure 1
The OIDC credential exchange

A pipeline job requests an identity token from its CI provider. It presents that token to the cloud security token service, which verifies the signature against the provider’s public keys and checks the token’s claims against the deploy role’s trust policy. The service returns a short-lived credential that the job uses and that expires when the job ends.

pipeline jobrepo · branchCI providersigns the claimtoken serviceverifies · checks policydeploy roleleast privilege1. request token2. present claim3. trust policy match4. short-lived credential, scoped to this job
The pipeline never holds a secret. It presents a signed claim about itself; the cloud verifies the signature against the provider's public keys, checks the claim against the role's trust policy, and issues a credential that expires with the job.
  1. 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.
  2. The job presents that token to the cloud. No secret is involved; the token is the evidence.
  3. 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.
  4. 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:

  1. Stand up federation and move one non-production pipeline to it.
  2. Move the rest, environment by environment, production last.
  3. Inventory every long-lived credential in the organization. Note who created each one and what it is used for. The list will contain surprises.
  4. Disable them, watch for a defined window, then delete them.
  5. 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.

Questions

What is OIDC federation in a CI/CD pipeline?

OIDC federation lets a pipeline exchange a signed identity token, issued by the CI provider and describing the job, for a short-lived cloud credential. Nothing is stored: the token is minted for the run, the cloud verifies its signature and claims, and the credential expires when the job ends.

How short should the credential be?

As short as the job. An hour is a common ceiling and most deployment jobs need far less. The value of federation comes from the credential being useless a short time after the job finishes, so a long session duration gives much of that value back.

What stops another repository from assuming our deploy role?

The trust policy, and only the trust policy. It must pin the issuer, the audience and the subject claim down to the repository and branch or environment. A subject condition using a broad wildcard trusts every repository under the organization, which is the most common and most serious misconfiguration in this pattern.

Do we still need any static credentials?

Usually for bootstrap and for a break-glass path, and both should be exceptional, time-boxed and audited. The goal is that no standing long-lived credential exists for routine work, not that the concept is banned in every corner of the organization.

How do we prove the keys are gone?

Two ways, and you want both. A credential report or equivalent inventory showing no active long-lived keys, and a preventive organization-level policy that denies creating them. The first is evidence; the second is the control that keeps the evidence true next quarter.