BlogCloud

GitHub Actions to AWS with OIDC: no more long-lived keys

Federate your workflows into AWS with short-lived tokens scoped to repo and branch. Delete the access keys after.

Jan 20, 2026·4 min readIntermediate·By the SecOpsLog team · command-tested

A long-lived AWS_ACCESS_KEY_ID in GitHub repository secrets never expires, works from anywhere on the internet, and leaks the first time someone echos the wrong variable in a debug step or a fork PR exfiltrates secrets. OIDC federation removes the static key entirely: GitHub mints a short-lived JWT per workflow run, AWS STS exchanges it via AssumeRoleWithWebIdentity, and the job receives temporary credentials scoped to an IAM role — typically one hour, tied to that repository and ref.

The security boundary is the IAM role trust policy — specifically the sub claim condition that pins which repository, ref, or GitHub Environment may assume the role. Get it wrong and any fork or feature branch in your org can deploy to production. The Software supply chain security track covers OIDC federation, secret hygiene, signing, and pipeline hardening end to end.

OIDC token exchange

Register the OIDC provider once per account. Every deploy role trust policy references the same provider ARN.

1OIDC providertoken.actions.githubusercontent…2IAM roletrust policy + sub condition3Workflowid-token: write permission4GitHub signs JWTrepo + ref claims5STS AssumeRoletemporary creds6Deployscoped to role policy7Delete keysremove old access keys

Register GitHub as an OIDC provider

Create an IAM OIDC identity provider with URL https://token.actions.githubusercontent.com and audience sts.amazonaws.com. AWS documents the thumbprint — verify it against current GitHub documentation when you set this up, as it can rotate. One provider per account; many roles can trust it with different sub conditions.

create-oidc-provider.sh
aws iam create-open-id-connect-provider \
--url https://token.actions.githubusercontent.com \
--client-id-list sts.amazonaws.com \
--thumbprint-list 6938fd4d98bab03fa2718477c8256450588bf45a

A role that only your repo can assume

The trust policy allows sts:AssumeRoleWithWebIdentity from the federated principal. Two conditions matter: aud must equal sts.amazonaws.com, and sub must match the exact repository and ref (or GitHub Environment) you intend. Use StringLike for ref patterns only when you understand the blast radius — refs/heads/main is safer than *.

trust-policy.json
{
"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:myorg/myapp:ref:refs/heads/main"
}
}
}
Never wildcard the sub claim
A sub of repo:myorg/*:* lets any branch of any repo in your org — including an attacker's fork PR branch if secrets are exposed — assume the role. Pin the repo and ref. For production, use GitHub Environments with required reviewers and trust `repo:org/app:environment:prod` instead of a bare main branch match.

The workflow side

Grant the job permissions.id-token: write — without it GitHub will not issue the OIDC token. Use aws-actions/configure-aws-credentials@v4 with role-to-assume; the action handles the STS exchange. Do not pass aws-access-key-id or aws-secret-access-key. Scope the role's permission policy to what the deploy actually needs — S3 put on one bucket, not AdministratorAccess.

.github/workflows/deploy.yml
permissions:
id-token: write # required to request the OIDC token
contents: read
jobs:
deploy:
runs-on: ubuntu-latest
environment: production
steps:
- uses: actions/checkout@v4
- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789012:role/gha-deploy-prod
aws-region: us-east-1
- run: aws s3 sync ./dist s3://my-artifacts-prod/
bash — caller identity inside the joblive
aws sts get-caller-identity
{
"Account": "123456789012",
"Arn": "arn:aws:sts::123456789012:assumed-role/gha-deploy-prod/GitHubActions"
}
session expires in ~1h — nothing stored in repo secrets

Delete the old access keys

Once OIDC works, rotate and delete long-lived IAM user access keys used by the old pipeline. A federated role sitting next to a live static key is the old attack surface with extra steps — attackers take the path of least resistance. Audit with aws iam get-access-key-last-used and remove keys with no recent legitimate use.

Where this goes next

The same federation pattern works for GCP Workload Identity Federation and Azure federated credentials — one OIDC token, many clouds. Layer GitHub Environment protection rules, Cosign keyless signing for artifacts, and OPA policy on Terraform plans so the identity hardening connects to verifiable deploys. The Software supply chain security path covers OIDC, signing, SBOMs, and provenance from commit to cluster.

Go deeper in a courseSoftware supply chain securityOIDC federation, secret hygiene, signing, and provenance across the pipeline.View course

Related posts