Replace Long‑Lived Secrets in GitHub Actions with Short‑Lived OIDC Tokens for AWS
Static AWS keys in GitHub secrets are hard to rotate and expose a large blast radius. This post explains how to replace them with short‑lived OIDC tokens, walks through an AWS setup, shows a real workflow, and discusses trade‑offs.
21 Jul 2026, 23:09 UTC

Why the Problem Matters
Most teams store AWS access keys in GITHUB_SECRETS and reference them in workflows. Those keys are long‑lived, must be rotated manually, and if a workflow or a fork accidentally leaks the secret, every service that trusts the key is exposed. The blast radius is huge and the operational overhead is high.
Thesis: Pin Credentials to Identity, Not to Copy
GitHub Actions can generate a short‑lived OpenID Connect (OIDC) token for each job. The token is signed by token.actions.githubusercontent.com and contains claims that uniquely identify the repository, ref, and actor. By configuring a cloud provider to trust this issuer, you can assume a role without ever storing static keys in the repository. The IAM role’s policy remains the single source of truth for permissions, and the token’s lifetime is automatically revoked when the job ends.
Setting Up Federation with AWS
1. Create an IAM Role that Trusts GitHub
In the AWS console or via CLI, create a role (e.g., GitHubActionsDeployRole) and attach a trust policy that accepts tokens from GitHub’s issuer. The policy must also match the sub claim to restrict which repository and ref 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:sub": "repo:myorg/myrepo:ref:refs/heads/main"
}
}
}
]
}
Replace 123456789012 with your AWS account ID, and adjust the repo and ref values to match the repository and branch you want to allow. For production environments, you can use a GitHub Environment name as the subject instead of a branch.
2. Grant the Role the Needed Permissions
Attach the minimal IAM policy required for the workflow’s tasks. For example, to deploy a CloudFormation stack:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"cloudformation:CreateStack",
"cloudformation:UpdateStack",
"cloudformation:DescribeStacks"
],
"Resource": "*"
}
]
}
Workflow Example
Below is a minimal GitHub Actions workflow that requests an OIDC token and uses the aws-actions/configure-aws-credentials action to assume the role. No AWS keys appear in secrets.
name: Deploy to AWS
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
permissions:
id-token: write # enable OIDC token generation
steps:
- uses: actions/checkout@v4
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v5
with:
role-to-assume: arn:aws:iam::123456789012:role/GitHubActionsDeployRole
aws-region: us-east-1
- name: Deploy CloudFormation
run: |
aws cloudformation deploy \
--stack-name my-stack \
--template-file template.yaml \
--capabilities CAPABILITY_NAMED_IAM
Key points:
- The job declares
permissions: id-token: writeto allow the OIDC token to be minted. - The
role-to-assumeargument points to the IAM role created earlier. - No
AWS_ACCESS_KEY_IDorAWS_SECRET_ACCESS_KEYsecrets are used.
Debugging a Failed Federation Attempt
When the role assumption fails, the error message usually references a mismatched claim. Common culprits:
- Incorrect
subformat (e.g., missingrepo:prefix or wrong ref). - Audience mismatch: by default the
audclaim issts.amazonaws.com, but some setups use a custom audience; set the audience explicitly on the workflow. - Typos in the trust policy string.
To inspect the raw token, add a debugging step:
- name: Print OIDC token
run: |
echo ${{ steps.configure-aws-credentials.outputs.id-token }} | base64 -d | jq .
Verify that the sub and aud fields match what the trust policy expects.
Trade‑Offs and Limitations
- Token Lifetime: The OIDC token is short‑lived (≈1 hour). Jobs that run longer than that or hand off credentials to downstream systems must re‑authenticate. AWS does not allow configuring the token TTL.
- Exact String Matching: Trust policies use
StringEqualson thesubclaim. A single typo can silently deny access, so careful version control and automated tests of the trust policy are advised. - Least‑Privilege Still Needed: Federation removes the need for static keys, but the IAM role must still be scoped tightly. A mis‑configured role can give the workflow more permissions than intended.
- Environment Mapping: To map to GitHub Environments, the
subclaim can berepo:myorg/myrepo:environment:prod. This requires the environment to be defined in GitHub and the trust policy updated accordingly.
When to Use Federation
If your CI pipeline:
- Needs to deploy to a cloud provider on a regular basis.
- Has multiple branches or environments that should be isolated.
- Wants to eliminate manual key rotation and reduce blast radius.
Then OIDC federation is a strong fit. For very short pipelines or where the cloud provider does not support OIDC, static keys may still be acceptable but should be rotated automatically.
Actionable Closing
Replace static AWS credentials in your GitHub Actions workflows with OIDC tokens by:
- Create an IAM role that trusts
token.actions.githubusercontent.comand matches thesubclaim to your repo/branch or environment. - Attach the minimal policy needed for the workflow.
- Update the workflow to enable
id-token: writeand useconfigure-aws-credentialswithrole-to-assume. - Verify the token’s claims before committing the workflow.
- Pair the role with a GitHub Environment for production deployments to add a manual approval gate.
By shifting the credential surface from the repository to an identity, you gain automatic revocation, tighter auditability, and a cleaner secrets store. The trade‑offs—short token lifetime and exact claim matching—are manageable with careful policy design and automated checks.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.