The most surprising thing about authenticating CircleCI to AWS with OIDC is that you don’t actually store any AWS credentials on CircleCI, ever.

Let’s see this in action. Imagine a simple workflow that deploys a static website to an S3 bucket.

version: 2.1

jobs:
  deploy_to_s3:
    docker:
      - image: cimg/aws:2023.01
    steps:
      - checkout
      - aws-cli/install
      - aws-oidc/assume-role-with-web-identity:
          role-arn: "arn:aws:iam::123456789012:role/CircleCI-OIDC-Role"
          role-session-name: "circleci-deploy-session-${CIRCLE_WORKFLOW_ID}"
      - run:
          name: Deploy to S3
          command: |
            aws s3 sync --delete ./dist s3://my-static-website-bucket

In this snippet, the aws-oidc/assume-role-with-web-identity step is where the magic happens. It doesn’t ask for an AWS_ACCESS_KEY_ID or AWS_SECRET_ACCESS_KEY. Instead, it uses the role-arn to tell AWS which IAM role CircleCI should impersonate. The role-session-name is good practice for tracking specific builds in CloudTrail.

Here’s the mental model:

  1. The Problem: Storing static AWS access keys in CI/CD systems is a massive security risk. If your CI/CD provider is compromised, your AWS credentials are too. Rotating these keys is also a pain.
  2. The Solution: IAM Roles for Service Accounts (IRSA) and OpenID Connect (OIDC): AWS IAM allows you to create roles that can be assumed by external identities. OIDC is a standard protocol that allows identity providers (like CircleCI) to securely exchange identity information with service providers (like AWS).
  3. How it Works:
    • CircleCI Configuration: You configure CircleCI to trust AWS as an OIDC provider. This involves associating a CircleCI project with a specific AWS account.

    • AWS IAM Role: You create an IAM role in AWS specifically for CircleCI. This role has a trust policy that allows it to be assumed by the OIDC provider (CircleCI) only for specific CircleCI projects and branches. The trust policy looks something like this:

      {
        "Version": "2012-10-17",
        "Statement": [
          {
            "Effect": "Allow",
            "Principal": {
              "Federated": "arn:aws:iam::123456789012:oidc-provider/oidc.circleci.com/vs/<circleci-org-id>/<circleci-project-slug>"
            },
            "Action": "sts:AssumeRoleWithWebIdentity",
            "Condition": {
              "StringEquals": {
                "oidc.circleci.com/vs/<circleci-org-id>/<circleci-project-slug>:aud": "sts.amazonaws.com",
                "oidc.circleci.com/vs/<circleci-org-id>/<circleci-project-slug>:sub": "repo/<your-github-username>/<your-repo-name>"
              }
            }
          }
        ]
      }
      
      • arn:aws:iam::123456789012:oidc-provider/oidc.circleci.com/...: This is the OIDC provider ARN in AWS, pointing to CircleCI’s identity server. You’ll need to replace <circleci-org-id> and <circleci-project-slug> with your actual CircleCI organization ID and project slug (e.g., oidc.circleci.com/vs/f42c1b5a-3f1a-4d6b-8a1c-9c1a9a1b2c3d/gh/my-org/my-repo).
      • sts:AssumeRoleWithWebIdentity: This is the specific action allowed.
      • Condition: This is crucial for security. It restricts which CircleCI builds can assume this role. aud (audience) should always be sts.amazonaws.com. sub (subject) is a more granular check, often using the repository name (repo/<your-github-username>/<your-repo-name>) or even specific branch names.
    • CircleCI Job Execution: When a job runs, the aws-oidc/assume-role-with-web-identity orb (or similar mechanism) exchanges a short-lived OIDC token generated by CircleCI for temporary AWS credentials (an access_key_id, secret_access_key, and session_token). These temporary credentials are then used by the AWS CLI or SDK to interact with AWS services.

    • Temporary Credentials: These credentials are short-lived (usually 1 hour) and are only valid for the duration of the job. They are never stored persistently.

The most important, and often overlooked, part of the AWS IAM role’s trust policy is the Condition block. Without it, any CircleCI job in any project associated with that OIDC provider could potentially assume the role, which is a significant security hole. You’re not just saying "CircleCI can assume this role," you’re saying "CircleCI, specifically for this project on this branch, can assume this role for a limited time."

Once you have this set up, the next thing you’ll likely want to tackle is managing different AWS environments (dev, staging, prod) using separate IAM roles and conditional logic in your CircleCI config.

Want structured learning?

Take the full Circleci course →