Understanding OIDC for CI/CD
What Is OIDC for CI/CD in Simple Terms
OIDC for CI/CD eliminates the most dangerous secret in your pipeline: long-lived cloud credentials. Instead of storing an AWS access key and secret key as CI secrets (where they never expire, must be manually rotated, and can be exfiltrated), OIDC lets GitHub Actions prove its identity to AWS using a cryptographically signed short-lived token. AWS exchanges this token for temporary 15-minute credentials scoped to exactly the permissions you specify.
No secrets to store. No rotation schedules. No leaked credentials to revoke. If the token is somehow intercepted, it expires in minutes.
How It Works
+------------------------------------------+| GitHub Actions job starts |+------------------------------------------+ | v+------------------------------------------+| GitHub generates OIDC token || Token contains: || iss: token.actions.githubusercontent.com|| sub: repo:razorpay/payment-api:ref:main|| aud: sts.amazonaws.com || exp: now + 15 minutes |+------------------------------------------+ | sent to AWS STS | v+------------------------------------------+| AWS STS validates token || Checks: issuer, subject, audience || Matches IAM role trust policy |+------------------------------------------+ | v+------------------------------------------+| AWS returns temporary credentials || Access key, secret key, session token || Valid for 15 minutes || Scoped to the assumed IAM role |+------------------------------------------+ | v+------------------------------------------+| Job uses credentials for AWS operations || Push to ECR, deploy to EKS, etc. |+------------------------------------------+GitHub Actions OIDC with AWS setup:
## Step 1: Workflow permissions (required for OIDC)permissions: id-token: write ## allows requesting OIDC token contents: read jobs: deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 ## Step 2: Use the OIDC action -- no secrets needed - name: Configure AWS credentials via OIDC uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: arn:aws:iam::123456789:role/github-deploy-role aws-region: ap-south-1 ## role-session-name appears in CloudTrail logs role-session-name: github-actions-${{ github.run_id }} ## Now AWS CLI and SDKs work without explicit credentials - name: Push to ECR run: | aws ecr get-login-password --region ap-south-1 | \\ docker login --username AWS --password-stdin \\ 123456789.dkr.ecr.ap-south-1.amazonaws.com docker push 123456789.dkr.ecr.ap-south-1.amazonaws.com/payment-api:${{ github.sha }}AWS IAM trust policy for GitHub OIDC:
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Federated": "arn:aws:iam::123456789: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:razorpay/payment-api:ref:refs/heads/main" } } } ]}GitLab CI OIDC with AWS:
deploy: image: amazon/aws-cli:latest id_tokens: AWS_OIDC_TOKEN: aud: sts.amazonaws.com script: - | CREDS=$(aws sts assume-role-with-web-identity \\ --role-arn arn:aws:iam::123456789:role/gitlab-deploy-role \\ --role-session-name gitlab-ci-$CI_JOB_ID \\ --web-identity-token $AWS_OIDC_TOKEN \\ --query 'Credentials' \\ --output json) export AWS_ACCESS_KEY_ID=$(echo $CREDS | jq -r .AccessKeyId) export AWS_SECRET_ACCESS_KEY=$(echo $CREDS | jq -r .SecretAccessKey) export AWS_SESSION_TOKEN=$(echo $CREDS | jq -r .SessionToken) aws ecr get-login-password | docker login --username AWS --password-stdin $ECR_URLPractical Commands
## Create OIDC provider in AWS (one-time setup)aws iam create-open-id-connect-provider \ --url https://token.actions.githubusercontent.com \ --client-id-list sts.amazonaws.com \ --thumbprint-list 6938fd4d98bab03faadb97b34396831e3780aea1 ## Create IAM role with trust policyaws iam create-role \ --role-name github-deploy-role \ --assume-role-policy-document file://trust-policy.json ## Attach permissions to the roleaws iam attach-role-policy \ --role-name github-deploy-role \ --policy-arn arn:aws:iam::aws:policy/AmazonEKSClusterPolicy ## Test OIDC locally (debug)aws sts get-caller-identity ## shows which role is assumedTroubleshooting
| Symptom | Check | What to Look For |
|---|---|---|
| OIDC auth fails | Trust policy sub condition | Repo name, branch, ref format must match exactly |
| Credentials expire during job | Role session duration | Increase max_session_duration on IAM role |
| Wrong role assumed | role-to-assume ARN | Check ARN for typos, account ID |
| Works on main, fails on branch | Trust policy condition | StringLike with * for all branches |
PLACEMENT PRO TIP**Tip:** Use `StringLike` with a wildcard in the trust policy subject condition to allow all branches: `"repo:razorpay/payment-api:*"`. For production deployments, restrict to main only: `"repo:razorpay/payment-api:ref:refs/heads/main"`. This lets feature branches use OIDC for staging but prevents them from accessing production credentials.
REMEMBER THIS**Remember:** The `id-token: write` permission must be declared in the GitHub Actions workflow for OIDC to work. Without it, the workflow cannot request an OIDC token from GitHub and the `configure-aws-credentials` action will fail silently or with a cryptic error. Always declare this permission explicitly.
COMMON MISTAKE / WARNING**Security:** Scope IAM roles created for GitHub Actions to the minimum permissions needed. A role used for ECR push should have `ecr:GetAuthorizationToken` and `ecr:BatchCheckLayerAvailability`, `ecr:PutImage` -- not `AdministratorAccess`. The trust policy restricts WHICH pipelines can assume the role; the IAM policy restricts WHAT they can do after assuming it. Both must be correctly scoped.
COMMON MISTAKE / WARNING**Common Mistake:** Forgetting to add the GitHub OIDC provider to your AWS account before creating the IAM role. The trust policy references the OIDC provider ARN -- if the provider does not exist in your account, the `AssumeRoleWithWebIdentity` call will fail with an error about an invalid principal. Create the OIDC provider first, then create the IAM role.