Skip to main content
INS // Insights

CircleCI OIDC AWS Roles Replace Static Keys

Updated October 2026 · 7 min read

CircleCI OIDC AWS is the cutover that removes AWS_ACCESS_KEY_ID from a CircleCI project context. The job presents a short-lived token, AWS STS returns role credentials, and the static key is deleted from the project, from context restrictions, and from any laptop that was copying the same pair for local debugging. A security lead who still rotates that key every quarter is maintaining a secret that CI should never have stored.

This is the CircleCI path specifically. GitLab OIDC to AWS uses a different issuer and a different subject. Copying a GitLab trust policy onto CircleCI produces Not authorized to perform sts:AssumeRoleWithWebIdentity and someone pastes the key back "until the pipeline is green." Rutagon builds the trust policy, the project binding, and the deletion of the key. The record of the cutover is filed in the GRC you already run.

What the CircleCI token actually asserts

CircleCI issues the token from https://oidc.circleci.com/org/<organization-id>. The job reads it from CIRCLE_OIDC_TOKEN. The sub claim has the shape org/<organization-id>/project/<project-id>/user/<user-id>. The audience defaults to the organization id. Those three facts are the whole trust decision. A role that trusts the issuer and forgets the subject will accept a token from every project in the org.

Two custom claims decide whether production is safe. oidc.circleci.com/project-id pins the project. oidc.circleci.com/ssh-rerun is the string true when someone starts an SSH rerun to debug a job. An SSH rerun is an interactive shell with the job environment. A production role that accepts ssh-rerun=true lets a debug session inherit deploy rights. We require that claim to be false on any role that can change production.

{
  "Effect": "Allow",
  "Principal": {
    "Federated": "arn:aws:iam::111122223333: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/ssh-rerun": "false"
    },
    "StringLike": {
      "oidc.circleci.com/org/ORG_ID:sub": "org/ORG_ID/project/PROJECT_ID/user/*"
    }
  }
}

The IAM OIDC provider thumbprint and URL must match that issuer, including the organization id in the path. A provider registered at oidc.circleci.com without the org segment will not validate these tokens. AWS documents the provider setup in Creating OpenID Connect identity providers.

CircleCI OIDC AWS trust that blocks the wrong job

We split roles by what the job does. A plan or test role can read state and describe resources. An apply role is a second role, trusted by the same project subject, and CircleCI context or approval rules decide which workflow can request it. One role with AdministratorAccess trusted by every branch is a static key with extra steps.

The subject binds the project, not the branch name. CircleCI's sub does not include the git ref. If you need branch isolation, use a separate CircleCI project for production workflows, or pass the token only from a workflow that already passed a manual approval, and keep the apply role's identity policy narrow. We say that out loud during the design so nobody expects the subject string to encode main.

Session duration stays at one hour or less. The job assumes the role at the start of the deploy step, not at the start of a two-hour test suite. Long sessions widen the window if the runner is compromised. The role session name includes the workflow id so CloudTrail shows which run assumed it.

Forked pull requests are the other foot-gun. A project setting that exposes OIDC tokens to forked builds will mint a token whose subject still matches the project. We keep OIDC off forked pull requests. The trust policy cannot see "this commit came from a fork" unless you have arranged a claim that says so. The project setting is the control.

Removing the static key without a rollback to the context

The migration order is fixed. Create the OIDC provider and the role. Confirm a non-production job can assume it and call a harmless API such as sts get-caller-identity. Point the production workflow at the role. Run it once and read CloudTrail for AssumeRoleWithWebIdentity with the expected role ARN and subject. Only then delete AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY from the CircleCI project and from any context the workflow uses.

Deletion is part of the deliverable. A pipeline that works with OIDC while the old key remains in the context has not eliminated the credential. We also deactivate the IAM access key in AWS and remove it after the recovery window you choose. A key left Active in IAM is still a standing credential no matter what the pipeline does.

Local developer access does not move onto the CI role. Engineers who were using the CI key on laptops get their own Identity Center permission set, or they use the same OIDC pattern from a different issuer. Sharing the CircleCI role with a human principal recreates a standing path. Replacing AWS access keys with OIDC is the wider pattern. This article is the CircleCI issuer, the ssh-rerun condition, and the project subject.

If a second CI system also deploys, it gets its own provider and its own role. Jenkins OIDC AWS is that sibling. A single role trusted by both issuers means a token from either system satisfies production.

What we file after the cutover

The evidence packet has four pieces: the IAM trust policy, the project id it allows, a CloudTrail event id from a successful assume with ssh-rerun denied by policy, and a screenshot or API read showing the CircleCI context no longer contains AWS keys. We file that packet in the GRC you already run. The next access review of the AWS account should show the CI user gone. If a user still exists only to hold CI keys, we delete the user after the role path is stable.

Tradeoff we will name: OIDC moves the secret from a key you rotate to the availability of CircleCI's issuer and your runner network. A CircleCI outage stops deploys. That is the correct failure mode. A leaked key in a context stops being a silent, durable breach. We accept issuer dependency in exchange for a credential that expires when the job ends.

Organizations with many projects get one IAM OIDC provider per CircleCI organization and one role per project or per deploy boundary. We do not create a provider per project. The provider is the issuer. The role trust is where the project id lives.

Frequently Asked Questions

Why is the ssh-rerun claim on the production role?

An SSH rerun opens an interactive shell in the job environment. If the production role accepts a token with that claim set to true, the debug session receives deploy credentials. We require the claim to be false on roles that change production. Read-only diagnostic roles can be a separate trust if you truly need cloud access during a rerun.

Can one IAM role serve every CircleCI project?

A role whose subject allows every project in the organization accepts every project's token. We bind sub to the project id that deploys, and other projects get other roles. The OIDC provider itself is shared, and each role trust stays on one project.

Does the CircleCI subject restrict which git branch can deploy?

The subject identifies the organization, project, and user. It does not include the branch. Branch control stays in CircleCI workflow filters and approval jobs. If production must be a separate identity, give production its own project and its own role.

When do you delete the old AWS keys?

After one production workflow has assumed the role and CloudTrail shows that assume. We then remove the keys from the CircleCI project and context and deactivate them in IAM. Leaving the keys stored "as backup" keeps the standing credential the migration was supposed to end.

Where is the cutover recorded?

The trust policy, the allowed project, a CloudTrail event id, and proof the context has no AWS keys go into the GRC you already run. Rutagon builds that packet. We do not claim a compliance attestation for your organization.

Talk to us about your credential elimination pilot. Reach Rutagon at rutagon.com/contact, 907-841-8407, or contact@rutagon.com.