Why
CI secrets are among the most frequently leaked credentials. With OIDC, the pipeline exchanges a signed token for temporary credentials for one role — nothing to steal, nothing to rotate.
1. Create the OIDC provider (once per account)
aws iam create-open-id-connect-provider \
--url https://token.actions.githubusercontent.com \
--client-id-list sts.amazonaws.com
AWS now validates GitHub's certificate chain itself; older guides tell you to add a thumbprint. If your CLI version requires --thumbprint-list, check the current AWS documentation for guidance.
2. Role with a tight trust policy
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": { "Federated": "arn:aws:iam::111122223333:oidc-provider/token.actions.githubusercontent.com" },
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"token.actions.githubusercontent.com:aud": "sts.amazonaws.com",
"token.actions.githubusercontent.com:sub": "repo:contoso/orders:environment:production"
}
}
}]
}
aws iam create-role --role-name gha-orders-prod-deploy --assume-role-policy-document file://trust.json --max-session-duration 3600
aws iam attach-role-policy --role-name gha-orders-prod-deploy --policy-arn arn:aws:iam::111122223333:policy/orders-deploy
Caution
Always pin the
subclaim to a repository (and ideally an environment or branch). A trust policy with only theaudcondition lets any GitHub repository assume your role.
Useful sub patterns: repo:org/repo:ref:refs/heads/main, repo:org/repo:environment:production, repo:org/repo:pull_request.
3. Update the workflow
permissions:
id-token: write
contents: read
jobs:
deploy:
runs-on: ubuntu-latest
environment: production
steps:
- uses: actions/checkout@v4
- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::111122223333:role/gha-orders-prod-deploy
aws-region: eu-west-1
- run: aws sts get-caller-identity
4. GitLab CI
Create an OIDC provider for https://gitlab.com (or your self-managed URL) with audience set to your choice, and use id_tokens in the job:
deploy:
id_tokens:
AWS_TOKEN:
aud: https://gitlab.com
script:
- >
export $(printf "AWS_ACCESS_KEY_ID=%s AWS_SECRET_ACCESS_KEY=%s AWS_SESSION_TOKEN=%s"
$(aws sts assume-role-with-web-identity --role-arn $ROLE_ARN --role-session-name gitlab-$CI_JOB_ID
--web-identity-token $AWS_TOKEN --query "Credentials.[AccessKeyId,SecretAccessKey,SessionToken]" --output text))
- aws sts get-caller-identity
Pin the trust policy's gitlab.com:sub to project_path:group/project:ref_type:branch:ref:main.
5. Delete the old keys
- Run the pipeline successfully with OIDC.
- Remove
AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEYfrom CI secrets. - Deactivate, then delete the IAM user's keys (and the user).
Checklist
- OIDC provider per account
- One role per repo/environment with pinned
sub - Least-privilege deploy policy
- Pipelines switched; secrets removed; IAM users deleted