Short answer: GitHub Actions can assume an IAM role using OpenID Connect (OIDC). No long-lived access keys are stored in GitHub: each job gets short-lived credentials, and the role trust policy decides which repository and branch can use it.
- Time
- 30 min
- Cost
- Free
- Level
- Intermediate
- Services
- IAM · STS · GitHub Actions
Step 1: Add GitHub as an identity provider
In the IAM console, add an OpenID Connect provider with the URL https://token.actions.githubusercontent.com and the audience sts.amazonaws.com. You only need one per AWS account.
Step 2: Create the role and its trust policy
The sub condition is what limits the role to your repository and branch. Without it, any repository on GitHub could try to assume the role.
trust-policy.json
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": { "Federated": "arn:aws:iam::123456789012: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:my-org/my-repo:ref:refs/heads/main"
}
}
}]
}
Attach only the permissions the pipeline needs, for example deploy to one ECS service or write to one S3 bucket.
Step 3: Use it in the workflow
.github/workflows/deploy.yml
permissions:
id-token: write # required to request the 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::123456789012:role/github-deploy
aws-region: us-east-1
- run: aws sts get-caller-identity
If it fails
| Error | Usual cause |
|---|---|
Not authorized to perform sts:AssumeRoleWithWebIdentity | The sub in the trust policy does not match (branch, environment or pull request) |
Credentials could not be loaded | Missing id-token: write permission in the workflow |
| Works on main, fails on pull requests | PR tokens have a different sub; add a separate, more limited role for them |
Do not use StringLike with repo:my-org/* on a role that can deploy to production. Any repository in the organization could assume it.
Clean up
Delete the old access keys from GitHub secrets and deactivate them in IAM once the OIDC role works.