GitHub Actions can authenticate to AWS without storing long-lived access keys. GitHub issues a short-lived OIDC token per workflow run, and AWS validates it against a trust policy you configure on an IAM role.
This only needs to happen once per AWS account.
aws iam create-open-id-connect-provider \
--url https://token.actions.githubusercontent.com \
--thumbprint-list 6938fd4d98bab03faadb97b34396831e3780aea1 \
--client-id-list sts.amazonaws.comIf you're using Terraform to manage IAM:
resource "aws_iam_openid_connect_provider" "github" {
url = "https://token.actions.githubusercontent.com"
client_id_list = ["sts.amazonaws.com"]
# GitHub's OIDC thumbprint. Verify this against GitHub's docs if pinning matters for you.
thumbprint_list = ["6938fd4d98bab03faadb97b34396831e3780aea1"]
}The trust policy controls which GitHub repositories and branches can assume the role.
{
"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"
},
"StringLike": {
"token.actions.githubusercontent.com:sub": "repo:your-org/your-repo:*"
}
}
}
]
}To scope to a specific branch only (e.g. main):
"token.actions.githubusercontent.com:sub": "repo:your-org/your-repo:ref:refs/heads/main"To scope to a specific GitHub environment (recommended for production roles):
"token.actions.githubusercontent.com:sub": "repo:your-org/your-repo:environment:production"Attach only the permissions the workflow needs. For Terraform managing S3 and EC2:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:PutObject",
"s3:DeleteObject",
"s3:ListBucket",
"dynamodb:GetItem",
"dynamodb:PutItem",
"dynamodb:DeleteItem"
],
"Resource": [
"arn:aws:s3:::my-terraform-state/*",
"arn:aws:s3:::my-terraform-state",
"arn:aws:dynamodb:us-east-1:123456789012:table/terraform-state-lock"
]
}
]
}permissions:
id-token: write
contents: readWithout id-token: write, GitHub won't issue an OIDC token and the assume-role step will fail with a credentials error.
- uses: kernelpanic09/github-actions-platform/actions/aws-oidc-assume@v1
with:
role_arn: arn:aws:iam::123456789012:role/github-actions-terraform
region: us-east-1Don't use one role for everything. Common pattern:
| Role | Trust condition | Permissions |
|---|---|---|
github-actions-plan |
Any branch | Read-only: S3 state read, describe resources |
github-actions-apply-staging |
main branch |
Write: apply to staging |
github-actions-apply-prod |
environment:production |
Write: apply to production |
github-actions-ecr-push |
Any branch in the app repo | ECR push to specific repo |
github-actions-eks-deploy |
main or environment:* |
EKS access to specific clusters |
The environment:production trust condition pairs with GitHub environment protection rules (require reviewers) to enforce manual approval before production applies.
If OIDC auth fails, check:
- The OIDC provider exists in the right AWS account
- The
subclaim in the trust policy matches the format GitHub sends (repo:<org>/<repo>:ref:refs/heads/<branch>orrepo:<org>/<repo>:environment:<name>) - The calling job has
id-token: writein itspermissions:block, not just the top-levelpermissions: - The region in the action input matches the region where the role lives
GitHub's OIDC token issuer: https://token.actions.githubusercontent.com
AWS docs: https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_providers_create_oidc.html