OIDC for CI/CD: Replacing Long-Lived Cloud Credentials with Workload Identity

4 min readCloud Infrastructure

A practical migration guide for using OIDC in CI/CD so pipelines can assume cloud roles without storing long-lived secrets.

authenticationoidcci-cdawssecurity
Security and identity themed abstract infrastructure image
The important shift is not the JWT itself. It is the operating model: the pipeline proves who it is at runtime instead of carrying a static cloud secret forever.

Why this migration matters

The old pattern for CI/CD cloud access is simple and dangerous:

  1. create a long-lived cloud credential
  2. store it in the CI system
  3. hope rotation, scope, and audit discipline stay perfect forever

That model fails in boring ways. Secrets leak into logs, get copied into multiple projects, survive longer than intended, and become hard to trace back to a single workflow run.

OIDC changes the shape of the problem. Instead of storing a reusable secret, the pipeline requests a short-lived identity token from the CI platform, and the cloud provider validates that token before issuing temporary credentials.

OIDC in one sentence

ConceptIdentity Federation

OpenID Connect lets a workload present a signed identity token so another system can verify who issued it, what audience it targets, and which workload attributes should be trusted.

Prerequisites

  • JWT basics
  • IAM roles/policies
  • CI pipeline concepts

Key Points

  • The CI platform acts as the identity provider (IdP).
  • The cloud provider trusts that IdP through a federation configuration.
  • The JWT claims become the policy boundary: branch, repo, org, project, and audience all matter.
  • Temporary credentials reduce blast radius because they expire automatically.

The practical request flow

In a modern CI pipeline, the sequence usually looks like this:

  1. The CI platform issues a signed OIDC token for the current job.
  2. The token includes claims such as the issuer, audience, repository, branch, and project identity.
  3. The cloud provider validates the token against the IdP metadata and signing keys.
  4. IAM policy conditions decide whether this specific workload may assume a role.
  5. The cloud provider returns short-lived credentials to the job.

This is the piece many teams miss: OIDC alone does not create safety. The safety comes from writing strict trust conditions around the token claims.

Static CI secrets vs OIDC workload identity

Both approaches get a pipeline into the cloud. Only one scales well operationally.

Static secrets
  • Easy to bootstrap, but hard to govern over time
  • Credentials often get reused across multiple pipelines or environments
  • Rotation is manual and frequently deferred
  • Incident investigation is harder because identity is shared
OIDC federation
  • No long-lived cloud secret stored in the CI platform
  • Access can be narrowed by repo, branch, environment, and audience claims
  • Credentials are temporary by default
  • Audit trails are cleaner because issuance happens per workload run
Verdict

Use static secrets only as a temporary bootstrap measure. For production CI/CD, workload identity is the stronger default because it improves both security posture and operational clarity.

What CircleCI actually exposes

CircleCI publishes OIDC discovery metadata under your organization-specific issuer URL:

https://oidc.circleci.com/org/REPLACE_ORG_ID/.well-known/openid-configuration

A representative discovery document looks like this:

{
  "issuer": "https://oidc.circleci.com/org/ORG_ID",
  "jwks_uri": "https://oidc.circleci.com/org/ORG_ID/.well-known/jwks-pub.json",
  "response_types_supported": ["id_token"],
  "id_token_signing_alg_values_supported": ["RS256"],
  "claims_supported": [
    "aud",
    "sub",
    "iss",
    "iat",
    "exp",
    "oidc.circleci.com/project-id",
    "oidc.circleci.com/context-ids",
    "oidc.circleci.com/vcs-ref",
    "oidc.circleci.com/vcs-origin"
  ]
}

During a job, CircleCI exposes an identity token as an environment variable such as:

$CIRCLE_OIDC_TOKEN

And the decoded claims typically contain fields like:

{
  "aud": "ORG_ID",
  "exp": 1741209197,
  "iat": 1741205597,
  "iss": "https://oidc.circleci.com/org/ORG_ID",
  "oidc.circleci.com/context-ids": ["CONTEXT_ID"],
  "oidc.circleci.com/project-id": "PROJECT_ID",
  "oidc.circleci.com/vcs-origin": "github.com/ORG_NAME/REPO_NAME",
  "oidc.circleci.com/vcs-ref": "refs/heads/main",
  "sub": "org/ORG_ID/project/PROJECT_ID/user/USER_ID"
}

Those claims are not trivia. They are the raw material for your trust policy.

Designing the trust boundary well

The most common mistake is to validate only the issuer and audience, then allow a broad role assumption. That recreates the same blast-radius problem under a different mechanism.

A better pattern is:

  • trust a specific issuer
  • require the exact intended audience
  • restrict by repository or VCS origin
  • restrict by branch or ref for deployment roles
  • separate staging and production roles
  • keep the permissions on the assumed role small and explicit
💡A strong default mental model

Treat OIDC trust policy design exactly like public API design. If the allowed caller set is ambiguous, the integration will eventually be used in ways you did not intend.

Example: AWS role assumption with web identity

A common AWS flow is:

  1. create an IAM OIDC identity provider for the CI issuer
  2. create an IAM role with sts:AssumeRoleWithWebIdentity
  3. add conditions on iss, aud, and CI-specific claims
  4. let the job exchange the JWT for temporary AWS credentials

Illustrative trust-policy shape:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Federated": "arn:aws:iam::123456789012:oidc-provider/oidc.circleci.com/org/ORG_ID"
      },
      "Action": "sts:AssumeRoleWithWebIdentity",
      "Condition": {
        "StringEquals": {
          "oidc.circleci.com/org/ORG_ID:aud": "ORG_ID",
          "oidc.circleci.com/org/ORG_ID:oidc.circleci.com/vcs-ref": "refs/heads/main"
        },
        "StringLike": {
          "oidc.circleci.com/org/ORG_ID:oidc.circleci.com/vcs-origin": "github.com/ORG_NAME/REPO_NAME"
        }
      }
    }
  ]
}

The exact condition keys vary by provider integration, but the principle does not: bind identity to the workload context you actually trust.

Failure modes worth planning for

Failure mode: branch-based trust without environment separation

If both staging and production jobs run from the same branch and assume the same role, OIDC may remove long-lived secrets without actually improving your deployment boundary. Separate roles and permission scopes still matter.

Failure mode: over-trusting all repos in an org

Organization-wide federation is tempting because it is convenient. It is also an easy path to privilege sprawl. Prefer per-repo or per-workload constraints unless you have a strong governance reason not to.

📝Failure mode: assuming token expiry solves everything

Short-lived credentials are a major improvement, but a broadly scoped role can still do a lot of damage within its valid window. Expiration reduces persistence, not authorization mistakes.

Migration plan that usually works in real teams

  1. Inventory current secrets used by CI for cloud access.
  2. Group workflows by privilege level rather than by tool or repository alone.
  3. Introduce one federated role per environment boundary.
  4. Start with non-production to validate claim formats and trust conditions.
  5. Add audit logging and verify issued role sessions during real pipeline runs.
  6. Remove old static secrets only after successful parallel validation.

This phased approach is slower than a one-shot cutover, but much safer. Identity migrations are easy to make "mostly working" and surprisingly easy to mis-scope.

Which policy decision most improves OIDC safety in CI/CD?

medium

Assume the pipeline can already obtain a valid JWT from the CI provider.

  • ATrust any repository in the org as long as the issuer is correct
    Incorrect.That still leaves a very wide blast radius and weak environment isolation.
  • BConstrain role assumption by issuer, audience, repository, and deployment ref
    Correct!This is the core governance win of workload identity: policy can be bound to the exact workload context.
  • CIncrease token lifetime so deployments fail less often
    Incorrect.Longer-lived credentials usually make risk worse, not better.
  • DKeep the old cloud access keys as a fallback in every job
    Incorrect.Fallback secrets often become the real production path and undermine the migration.

Hint:The right answer narrows who may assume the role, not just how the token is delivered.

What I would standardize across a platform team

If I were defining the house standard, I would require:

  • workload identity by default for CI-to-cloud access
  • environment-specific roles
  • repository- and ref-level trust conditions
  • no shared admin roles for deployment pipelines
  • explicit logging for role assumptions
  • periodic review of trust policy sprawl

That is the difference between "we enabled OIDC" and "we actually improved our deployment security model."

Bottom line

OIDC is not just a safer token exchange. It is a chance to redesign CI/CD cloud access around ephemeral identity, narrower trust boundaries, and cleaner auditability.

Used well, it removes an entire class of secret-management pain. Used casually, it can become a more modern-looking version of the same over-permissioned pipeline model.

For most teams, the winning move is simple: replace stored cloud secrets with workload identity, then spend the real design energy on claim-level trust conditions and role scoping.