Skip to content
DevOps AI ToolKit
Newsletter
All guides
AI for DevOps Security & Hardening By James Joyner IV · · 8 min read

OIDC Keyless Cloud Auth in CI: Killing the Long-Lived Credentials in Your Pipeline

Static cloud keys in CI secrets are the breach waiting to happen. OIDC federation swaps them for short-lived tokens. Here's how to cut them over.

  • #security
  • #hardening
  • #oidc
  • #ci-cd
  • #iam
  • #cloud

Open your CI settings and look at the secrets list. If there’s an AWS_SECRET_ACCESS_KEY, a long-lived service-account JSON, or an Azure client secret sitting there, you’re holding a credential that doesn’t expire, can be exfiltrated by any malicious build step or compromised dependency, and grants standing access to your cloud. That’s the single most attacked surface in most pipelines, and it’s also one of the easiest to eliminate.

OIDC federation replaces those static keys with short-lived tokens minted per job. There’s nothing to leak because there’s nothing stored. I’ve cut this over for several teams now, and the pattern is the same everywhere.

How the handshake works

Your CI platform — GitHub Actions, GitLab, Buildkite — can act as an OIDC identity provider. For each job it can mint a signed JWT describing exactly who’s running: the repo, the branch, the workflow, the environment. The cloud provider is configured to trust that issuer and to exchange a valid token for short-lived credentials.

The flow:

  1. The job requests an OIDC token from its CI platform.
  2. The token contains claims: repo:acme/api:ref:refs/heads/main, environment, and so on.
  3. The job presents that token to AWS STS / GCP STS / Azure AD.
  4. The cloud validates the token against the configured trust and the claim conditions.
  5. It returns credentials that live ~15 minutes to an hour.

No secret was stored anywhere. The trust is in the shape of the identity, enforced by the cloud, not in a string you have to guard.

AWS: the role trust policy is the whole game

You register GitHub as an OIDC provider, then create a role whose trust policy pins the exact identity allowed to assume it:

{
  "Effect": "Allow",
  "Principal": {
    "Federated": "arn:aws:iam::1234:oidc-provider/token.actions.githubusercontent.com"
  },
  "Action": "sts:AssumeRoleWithWebIdentity",
  "Condition": {
    "StringEquals": {
      "token.actions.githubusercontent.com:aud": "sts.amazonaws.com"
    },
    "StringLike": {
      "token.actions.githubusercontent.com:sub": "repo:acme/api:ref:refs/heads/main"
    }
  }
}

That sub condition is the entire security boundary. Get it wrong and you’ve built a backdoor. The classic mistake is repo:acme/* or, worse, omitting the condition so any GitHub repo on the internet can assume your role. Pin it to the specific repo, and to a branch or environment where it matters. For deploy roles, scope to environment:production so only jobs in the protected environment can assume them.

The job side is clean — no secrets:

permissions:
  id-token: write
  contents: read

steps:
  - uses: aws-actions/configure-aws-credentials@v4
    with:
      role-to-assume: arn:aws:iam::1234:role/ci-deploy
      aws-region: us-east-1

GCP and Azure: same shape, different nouns

GCP uses Workload Identity Federation: create a workload identity pool, add the CI platform as a provider, and grant the pool’s principal permission to impersonate a service account — with an attribute condition matching the repo claim. Azure uses federated identity credentials on an app registration, matching subject and issuer. The mechanics differ; the principle is identical. Trust the issuer, constrain the claims, exchange for short-lived tokens.

In all three, scope the resulting permissions tightly too. OIDC removes the standing-secret risk, but a token good for an hour with *:* permissions is still a bad hour. Least privilege on the assumed role still applies.

GitLab and self-hosted

GitLab CI exposes ID tokens you request per job:

deploy:
  id_tokens:
    AWS_TOKEN:
      aud: https://gitlab.com
  script:
    - aws sts assume-role-with-web-identity
        --role-arn "$ROLE_ARN"
        --web-identity-token "$AWS_TOKEN"
        --role-session-name "ci-$CI_PIPELINE_ID"

Self-hosted runners work too, but your OIDC issuer must be reachable by the cloud provider’s STS endpoint — a private GitLab behind a firewall can’t be validated by AWS. That’s a real constraint for airgapped setups; plan for it before you commit.

The migration that doesn’t break everything

Cutting over a live pipeline takes some care:

  1. Stand up the OIDC trust alongside the existing keys. Don’t delete anything yet.
  2. Migrate one non-critical job to the OIDC role and confirm it works end to end.
  3. Watch CloudTrail / audit logs for the new role being assumed correctly with the right session names.
  4. Migrate the rest, job by job.
  5. Only then revoke the static keys — and actually delete them, don’t just rotate. A rotated-but-not-deleted key is still a standing secret.
  6. Add a guard that fails CI or alerts if a long-lived cloud key reappears in secrets. They creep back.

What you get

After cutover: no static cloud credentials in CI, every cloud action traceable to a specific repo, branch, and run via the session name, and an attacker who compromises a build step gets at most a short-lived, narrowly-scoped token instead of permanent keys. The blast radius of a poisoned dependency drops from “owns your cloud” to “had a 15-minute window scoped to one role.”

The work is mostly in writing the trust conditions correctly and migrating carefully. The payoff is deleting the worst secret in your pipeline.

For more pipeline and identity hardening, browse our security and hardening guides. When you write the role trust policy, paste it into the AI code review assistant — the over-broad sub condition is exactly the kind of subtle backdoor it’s tuned to catch.

IAM and trust-policy examples are illustrative. Validate claim conditions and least-privilege scoping against your own accounts before relying on them.

Free download · 368-page PDF

Download the Free 500-Prompt DevOps AI Toolkit

500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.

  • 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
  • Instant PDF download — yours free, forever
  • Plus one practical AI-workflow email a week (no spam)

Single opt-in · unsubscribe anytime · no spam.