Why drop access keys
An access key stored as a GitHub secret works until someone copies it, a workflow leaks it in a log, or the person who created it leaves. It doesn’t expire unless someone rotates it, and it usually has more permissions than the pipeline needs.
With OIDC, each workflow run asks GitHub for a signed token that says which repository, branch or environment it is running from. AWS checks that token against rules you set, and hands back temporary credentials for one IAM role. Nothing long-lived is stored anywhere.
Step 1: add GitHub as an identity provider in AWS
In IAM, add an OpenID Connect identity provider with:
- Provider URL:
https://token.actions.githubusercontent.com - Audience:
sts.amazonaws.com
You do this once per AWS account.
Step 2: create a role that only your repository can use
The role’s trust policy is where the security lives. It checks the token’s audience and its subject, which names the repository and the branch or environment:
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": { "Federated": "arn:aws:iam::ACCOUNT_ID: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:your-org/your-repo:ref:refs/heads/main"
}
}
}]
}To tie the role to a GitHub environment instead of a branch, use a subject such as repo:your-org/your-repo:environment:production.
Don’t leave the subject open.
Without a sub condition, or with a wildcard like repo:your-org/*, any repository or branch that matches can assume the role, including a pull request branch.
Step 3: give the role only what the pipeline does
Attach a policy for exactly what the job needs: for a static site, writing to one bucket and invalidating one CloudFront distribution. Use separate roles for staging and production, so a staging workflow can never touch production.
Step 4: request the token in your workflow
The workflow needs permission to request an OIDC token, and then uses the official action to assume the role:
permissions: id-token: write # lets the job request an OIDC token contents: read jobs: deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: arn:aws:iam::ACCOUNT_ID:role/github-actions aws-region: ap-southeast-2 - run: aws s3 sync dist/ s3://your-bucket/
Once that works, delete the old access keys from GitHub and deactivate them in IAM.
A working example
This website deploys this way: staging and production workflows both request a token with id-token: write and assume a role with aws-actions/configure-aws-credentials. The workflows use no stored AWS keys. See how devopsplant.com.au is built and deployed.
Sources
Next step
Still have access keys in your pipelines?
On a 30-minute call we’ll look at where they are and what replacing them would take.