Home / AWS

AWSHow-toIntermediate

Transformation: remove AWS access keys from CI/CD with OIDC federation (GitHub Actions, GitLab)

Replace long-lived AWS access keys stored in CI secrets with short-lived role credentials via OpenID Connect. Create the identity provider, write a tight trust policy per repo and environment, update pipelines and delete the keys.

Deepak Bhardwaj2 min read

Applies toAWS IAM OIDC identity providers, GitHub Actions, GitLab CI, aws-actions/configure-aws-credentials

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 sub claim to a repository (and ideally an environment or branch). A trust policy with only the aud condition 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

  1. Run the pipeline successfully with OIDC.
  2. Remove AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY from CI secrets.
  3. 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