Problem
Storing AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY in GitHub secrets works but creates credential sprawl, rotation overhead, and a larger blast radius if a secret leaks.
Solution
GitHub Actions requests a JWT from GitHub's OIDC provider. AWS trusts that issuer via an IAM OIDC identity provider. A workflow assumes an IAM role using sts:AssumeRoleWithWebIdentity and receives temporary credentials scoped to what the role allows.
Setup steps
- Create an IAM OIDC provider for
token.actions.githubusercontent.com - Create an IAM role with a trust policy conditioned on repo, branch, or environment
- Attach least-privilege policies (e.g. S3 deploy, ECR push only)
- In the workflow, use
aws-actions/configure-aws-credentialswithrole-to-assume
Example trust condition
sub: repo:org/repo:ref:refs/heads/main
Tighten conditions so only expected workflows and branches can assume the role. Avoid wildcard * on production roles.
Outcomes
- No static AWS keys in GitHub
- Credentials expire automatically after the job
- Auditable via CloudTrail
AssumeRoleWithWebIdentityevents