DEV Community

Ray Ch
Ray Ch

Posted on

AWS OIDC Setup for CircleCI

This guide explains how to configure AWS IAM with OIDC (OpenID Connect) for secure CircleCI authentication without storing long-lived credentials.

Overview

Why OIDC?

Access Keys (Old) OIDC (Recommended)
Long-lived credentials Temporary tokens (auto-expire)
Stored in CircleCI No stored secrets
Can be leaked Nothing to leak
Manual rotation needed Automatic rotation

Prerequisites

  • AWS Account with Admin access
  • CircleCI Organization ID (found in CircleCI → Organization Settings → Overview)
  • AWS Account ID

Step 1: Create OIDC Identity Provider

AWS Console

  1. Go to IAM → Identity Providers → Add Provider
  2. Fill in:
Field Value
Provider type OpenID Connect
Provider URL https://oidc.circleci.com/org/<CIRCLECI_ORG_ID>
Audience <CIRCLECI_ORG_ID>
  1. Click Get thumbprint
  2. Click Add provider

AWS CLI

aws iam create-open-id-connect-provider \
  --url "https://oidc.circleci.com/org/<CIRCLECI_ORG_ID>" \
  --client-id-list "<CIRCLECI_ORG_ID>" \
  --thumbprint-list "9e99a48a9960b14926bb7f3b02e22da2b0ab7280"
Enter fullscreen mode Exit fullscreen mode

Step 2: Create IAM Policy

CircleCI-Deployment-Policy

Create this policy in IAM → Policies → Create Policy:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "ECRAuthToken",
            "Effect": "Allow",
            "Action": "ecr:GetAuthorizationToken",
            "Resource": "*"
        },
        {
            "Sid": "ECRPushImages",
            "Effect": "Allow",
            "Action": [
                "ecr:BatchCheckLayerAvailability",
                "ecr:GetDownloadUrlForLayer",
                "ecr:BatchGetImage",
                "ecr:PutImage",
                "ecr:InitiateLayerUpload",
                "ecr:UploadLayerPart",
                "ecr:CompleteLayerUpload"
            ],
            "Resource": "arn:aws:ecr:<REGION>:<ACCOUNT_ID>:repository/<ECR_REPO_NAME>"
        },
        {
            "Sid": "AppRunnerDeploy",
            "Effect": "Allow",
            "Action": [
                "apprunner:StartDeployment",
                "apprunner:DescribeService"
            ],
            "Resource": "arn:aws:apprunner:<REGION>:<ACCOUNT_ID>:service/<SERVICE_NAME>/*"
        },
        {
            "Sid": "AmplifyDeploy",
            "Effect": "Allow",
            "Action": [
                "amplify:StartJob",
                "amplify:GetJob"
            ],
            "Resource": "arn:aws:amplify:<REGION>:<ACCOUNT_ID>:apps/<AMPLIFY_APP_ID>/*"
        }
    ]
}
Enter fullscreen mode Exit fullscreen mode

Note: Replace <REGION>, <ACCOUNT_ID>, <ECR_REPO_NAME>, <SERVICE_NAME>, and <AMPLIFY_APP_ID> with your actual values. For quick setup, you can use "Resource": "*" but this is less secure.


Step 3: Create IAM Role

AWS Console

  1. Go to IAM → Roles → Create Role
  2. Select Web identity as trusted entity type
  3. Select your OIDC provider from the dropdown
  4. Set Audience to your CircleCI Org ID
  5. Click Next
  6. Attach CircleCI-Deployment-Policy
  7. Name the role: CircleCI-OIDC-Role
  8. Click Create role

Trust Policy

After creating the role, update the trust policy for additional security:

IAM → Roles → CircleCI-OIDC-Role → Trust relationships → Edit

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Federated": "arn:aws:iam::<ACCOUNT_ID>:oidc-provider/oidc.circleci.com/org/<CIRCLECI_ORG_ID>"
      },
      "Action": "sts:AssumeRoleWithWebIdentity",
      "Condition": {
        "StringEquals": {
          "oidc.circleci.com/org/<CIRCLECI_ORG_ID>:aud": "<CIRCLECI_ORG_ID>"
        }
      }
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

Optional: Restrict to Specific Project

Add this condition to limit access to a specific CircleCI project:

"StringLike": {
  "oidc.circleci.com/org/<CIRCLECI_ORG_ID>:sub": "org/<CIRCLECI_ORG_ID>/project/<PROJECT_ID>/user/*"
}
Enter fullscreen mode Exit fullscreen mode

Step 4: Update CircleCI Configuration

Update .circleci/config.yml to use OIDC authentication:

version: 2.1

orbs:
  node: circleci/node@5.2.0
  aws-cli: circleci/aws-cli@4.1.2
  aws-ecr: circleci/aws-ecr@9.0.2

executors:
  node-executor:
    docker:
      - image: cimg/node:20.11
    working_directory: ~/project

jobs:
  push-backend-to-ecr:
    executor: node-executor
    steps:
      - checkout
      - setup_remote_docker:
          version: 20.10.24
      - aws-cli/setup:
          role_arn: ${AWS_ROLE_ARN}
          region: ${AWS_REGION}
      - run:
          name: Build and Push Backend Docker Image
          command: |
            aws ecr get-login-password --region $AWS_REGION | docker login --username AWS --password-stdin $AWS_ECR_REGISTRY
            docker build -t $AWS_ECR_REGISTRY/demo-backend:$CIRCLE_SHA1 -t $AWS_ECR_REGISTRY/demo-backend:latest ./backend
            docker push $AWS_ECR_REGISTRY/demo-backend:$CIRCLE_SHA1
            docker push $AWS_ECR_REGISTRY/demo-backend:latest

  deploy-backend-to-apprunner:
    executor: node-executor
    steps:
      - aws-cli/setup:
          role_arn: ${AWS_ROLE_ARN}
          region: ${AWS_REGION}
      - run:
          name: Trigger App Runner Deployment
          command: |
            aws apprunner start-deployment --service-arn $AWS_APP_RUNNER_SERVICE_ARN

  deploy-frontend-to-amplify:
    executor: node-executor
    steps:
      - checkout
      - aws-cli/setup:
          role_arn: ${AWS_ROLE_ARN}
          region: ${AWS_REGION}
      - run:
          name: Trigger Amplify Deployment
          command: |
            aws amplify start-job --app-id $AWS_AMPLIFY_APP_ID --branch-name main --job-type RELEASE
Enter fullscreen mode Exit fullscreen mode

Step 5: Configure CircleCI Environment Variables

Add These Variables

In CircleCI → Project Settings → Environment Variables:

Variable Value Description
AWS_ROLE_ARN arn:aws:iam::<ACCOUNT_ID>:role/CircleCI-OIDC-Role IAM role ARN
AWS_REGION us-east-1 AWS region
AWS_ECR_REGISTRY <ACCOUNT_ID>.dkr.ecr.<REGION>.amazonaws.com ECR registry URL
AWS_APP_RUNNER_SERVICE_ARN arn:aws:apprunner:... App Runner service ARN
AWS_AMPLIFY_APP_ID <APP_ID> Amplify app ID

Remove These Variables (After Testing)

Variable Reason
AWS_ACCESS_KEY_ID No longer needed with OIDC
AWS_SECRET_ACCESS_KEY No longer needed with OIDC

Step 6: Test OIDC Authentication

Add a test job to verify the setup:

jobs:
  test-oidc-auth:
    docker:
      - image: cimg/base:current
    steps:
      - aws-cli/setup:
          role_arn: ${AWS_ROLE_ARN}
          region: ${AWS_REGION}
      - run:
          name: Verify AWS Identity
          command: aws sts get-caller-identity
Enter fullscreen mode Exit fullscreen mode

Expected Output

{
  "UserId": "AROA...:circleci-test-oidc-auth",
  "Account": "<ACCOUNT_ID>",
  "Arn": "arn:aws:sts::<ACCOUNT_ID>:assumed-role/CircleCI-OIDC-Role/circleci-test-oidc-auth"
}
Enter fullscreen mode Exit fullscreen mode

Troubleshooting

Error: "Not authorized to perform sts:AssumeRoleWithWebIdentity"

  • Verify the OIDC provider URL matches exactly
  • Check the audience claim matches your CircleCI Org ID
  • Ensure the trust policy has the correct Principal ARN

Error: "Token is expired"

  • OIDC tokens are short-lived; ensure the job runs promptly
  • Check for clock skew issues

Error: "Invalid identity token"

  • Verify CircleCI Org ID is correct
  • Check OIDC provider thumbprint

Security Best Practices

Practice Description
Restrict resources Use specific ARNs instead of * in policies
Limit by project Add project ID condition to trust policy
Enable CloudTrail Audit all role assumptions
Separate environments Use different roles for staging/production
Regular review Periodically audit IAM policies and roles

Developer Access (Separate from CI/CD)

Developers should have their own IAM users/roles with appropriate permissions. See AWS_DEVELOPER_ACCESS.md for developer IAM setup.


Cost

OIDC authentication is free. No additional AWS charges for:

  • OIDC Identity Provider
  • IAM Roles and Policies
  • STS AssumeRoleWithWebIdentity calls

References

Top comments (0)