Join our Newsletter — 33% off our NHI Course
Home› Guides› How to set up workload identity federation for…
Guide Non-Human Identity (NHI)

How to set up workload identity federation for GitHub Actions to AWS: Replace stored AWS keys with short-lived role credentials

PDF for newsletter subscribers · free to read online

← All guides
By NHI Mgmt Group, edited by Lalit Choda Updated 28 October 2026 10 min read
On this page

An AWS access key stored as a GitHub secret works from anywhere, for anyone who copies it, until somebody remembers to rotate it. GitHub Actions can instead request a signed OIDC token for each job, and AWS STS exchanges it for temporary role credentials only when the token's audience and subject match the role's trust policy. This page is the setup runbook: create the identity provider, write a trust policy that pins the exact repository and branch or environment, update the workflow, prove it works, roll back safely and delete the old keys. It is for platform and DevOps engineers who own AWS accounts and GitHub organisations. For threat modelling and the finer points of claim design across clouds, see the GitHub Actions OIDC Federation Hardening Guide; this page focuses on doing the migration.

Key takeaways

  • The trust policy is the security boundary. AWS warns that if token.actions.githubusercontent.com:sub is not limited to your organisation or repository, workflows outside your control can assume the role. Pin sub to one repository and one branch or environment.
  • Check which subject format your repository emits. Repositories created after 15 July 2026, renamed or transferred since, or opted in, use immutable subjects such as repo:octo-org@123456/octo-repo@456789:ref:refs/heads/main. A policy written for the old name-only format fails with "Not authorized to perform sts:AssumeRoleWithWebIdentity".
  • The workflow needs only id-token: write. That permission lets the job request an OIDC token; it does not grant write access to any repository resource.
  • Credentials last an hour by default. The aws-actions/configure-aws-credentials action requests 3,600 second sessions unless you set role-duration-seconds (900 to 43,200 seconds, within the role's maximum).
  • Finish by deleting keys. The migration is not done until the IAM user's access keys are deleted and the GitHub secrets removed.

When you need this

  • AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY sit in repository, environment or organisation secrets. These are long-lived IAM user keys, the pattern OWASP lists as NHI7 Long-Lived Secrets and NHI6 Insecure Cloud Deployment Configurations.
  • Keys have leaked or nearly leaked. In the CISA Private-CISA GitHub leak, a contractor's public repository holding GitHub Actions workflows also held administrative credentials for AWS GovCloud accounts for about six months, with push protection disabled.
  • You cannot answer which workflow made an AWS call. With a shared IAM user, every pipeline looks the same in CloudTrail. Federated sessions record the token subject.
  • Rotation is manual. Federation removes the rotation task for CI entirely, because nothing persistent is stored.

Before you start

ItemWhat you need
AWS permissionsRights to create an IAM OIDC identity provider and IAM roles in the target account (an administrator or a delegated IAM role)
GitHub permissionsWrite access to the workflow files; admin on the repository if you will create environments and protection rules
Issuer URLhttps://token.actions.githubusercontent.com on GitHub.com; https://HOSTNAME/_services/token on GitHub Enterprise Server
Audiencests.amazonaws.com, the default of the official action (China regions use sts.amazonaws.com.cn via the audience input)
Subject planWhich repository, and which branch, tag or environment may deploy. An environment-based subject (repo:ORG/REPO:environment:prod) is the usual choice for production
Permissions planThe exact AWS actions and resources the job uses, from CloudTrail history for the old IAM user

Decide the subject from the job's real context. GitHub builds sub from the workflow context: if the job references an environment, the subject is the environment form; otherwise a pull request produces repo:ORG/REPO:pull_request, and a push produces the branch or tag form. Colons inside values are encoded as %3A.

Subject formExampleUse it forWatch out for
Environmentrepo:ORG/REPO:environment:prodProduction deployments gated by reviewers and branch rulesAny branch allowed into the environment can assume the role, so the environment rules are part of the trust
Branchrepo:ORG/REPO:ref:refs/heads/mainSimple pipelines without environmentsAnyone who can push to the branch can deploy; protect the branch
Tagrepo:ORG/REPO:ref:refs/tags/v1.0Release jobs triggered by tagsExact tags only with StringEquals; protect tag creation
Pull requestrepo:ORG/REPO:pull_requestRead-only checks such as a planNever trust a deploy role from pull requests

Step-by-step

  1. Create the OIDC identity provider once per account.
    aws iam create-open-id-connect-provider \
      --url https://token.actions.githubusercontent.com \
      --client-id-list sts.amazonaws.com
    The action's documentation notes that a certificate thumbprint is no longer necessary and is ignored if specified. Check first with aws iam list-open-id-connect-providers; there is one provider per URL per account.
  2. Write the trust policy. Save as trust.json, replacing the account ID, organisation, repository and environment:
    {
      "Version": "2012-10-17",
      "Statement": [{
        "Effect": "Allow",
        "Principal": {"Federated": "arn:aws:iam::<ACCOUNT_ID>: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:<ORG>/<REPO>:environment:prod"
          }
        }
      }]
    }
    For an immutable-subject repository use repo:<ORG>@<ORG_ID>/<REPO>@<REPO_ID>:environment:prod. IAM rejects a GitHub trust policy at save time if the sub key is missing or is only * or ?, but it accepts repo:ORG/*, which trusts every repository in the organisation.
  3. Create the role and attach least-privilege permissions.
    aws iam create-role --role-name gha-payments-api-prod-deploy \
      --assume-role-policy-document file://trust.json --max-session-duration 3600
    aws iam put-role-policy --role-name gha-payments-api-prod-deploy \
      --policy-name deploy --policy-document file://deploy-permissions.json
    A role has no permissions by default. Build deploy-permissions.json from what the job actually calls. The action's documentation reports that a role named exactly "GitHubActions" has been reported not to work, so use a descriptive name.
  4. Protect the environment in GitHub. Create the prod environment and add deployment branch and tag rules and required reviewers. Both GitHub and AWS recommend protection rules whenever environments appear in OIDC policies.
  5. Update the workflow.
    permissions:
      id-token: write
      contents: read
    jobs:
      deploy:
        runs-on: ubuntu-latest
        environment: prod
        steps:
          - uses: actions/checkout@<pinned-sha>
          - uses: aws-actions/configure-aws-credentials@<pinned-sha>
            with:
              role-to-assume: arn:aws:iam::<ACCOUNT_ID>:role/gha-payments-api-prod-deploy
              role-session-name: payments-api-${{ github.run_id }}
              aws-region: eu-west-2
          - run: aws sts get-caller-identity
    Pin third-party actions to a full commit SHA, as GitHub's own example does. Leave output-credentials at its default of false so credentials are not exposed as step outputs.
  6. Run it on a pilot branch or environment first. Keep the old secrets in place but unused by this job until the federated run succeeds.

Verify it worked

  • Job output: aws sts get-caller-identity returns an assumed-role ARN (arn:aws:sts::<ACCOUNT_ID>:assumed-role/gha-payments-api-prod-deploy/payments-api-...), not an IAM user.
  • CloudTrail: find the AssumeRoleWithWebIdentity event (event source sts.amazonaws.com). Its userIdentity.type is WebIdentityUser, responseElements.subjectFromWebIdentityToken holds the GitHub subject, and responseElements.audience the audience. Requests to the global STS endpoint are logged in us-east-1; regional endpoint requests are logged in their own region.
  • Negative test: trigger the workflow from a branch or environment the policy does not name. It must fail with "Not authorized to perform sts:AssumeRoleWithWebIdentity".
  • Claim inspection: if you are unsure what subject the job emits, AWS's action documentation points to the actions-oidc-debugger action to print decoded claims, run in a private repository only.
CloudTrail fieldWhat it tells you
userIdentity.identityProviderThe IAM OIDC provider ARN that vouched for the caller
responseElements.subjectFromWebIdentityTokenThe repository and ref or environment that obtained credentials
requestParameters.roleSessionNameYour session name, ideally containing the run ID for correlation with GitHub logs
requestParameters.durationSecondsRequested session length
additionalEventData.identityProviderConnectionVerificationMethodIAMTrustStore or Thumbprint, how AWS verified the provider

Rollback and common errors

SymptomLikely causeFix
"Not authorized to perform sts:AssumeRoleWithWebIdentity"sub mismatch: wrong branch, job uses an environment, or immutable versus legacy formatInspect claims, then match the policy to the format the repository emits
The action cannot obtain an OIDC tokenMissing id-token: write at job or workflow levelAdd the permission; job-level permissions replace workflow-level ones
Audience errorProvider created with a different client ID, or a custom audience inputMake the provider client ID, policy aud and action input agree
Session duration rejectedrole-duration-seconds above the role's maximum session durationLower the input or raise the role maximum deliberately
Works for pushes, fails for pull requestsPull request subjects are repo:ORG/REPO:pull_requestGive pull request jobs a separate, read-only role, or none

Rollback: during the pilot, revert the workflow change to restore the secret-based login; nothing in AWS needs undoing. After cut-over, rollback means re-issuing keys, which you should avoid. Once the federated workflow has run cleanly through a release, deactivate the old IAM user's keys (aws iam update-access-key --status Inactive), wait a cycle, then delete them and the GitHub secrets. To withdraw federation from a repository, change or delete the role's trust policy; credentials already issued remain valid until they expire, which is why short session durations matter. See How to rotate an AWS IAM access key without downtime for the key retirement steps.

Hardening afterwards

  • Opt in to immutable subjects. For repositories created before 15 July 2026, opt in at organisation or repository level so a recycled organisation or repository name cannot match your trust policy.
  • Use StringEquals, not wildcards. List exact subjects. Avoid ForAllValues: operators in Allow statements, which the action's maintainers warn return true when a claim is absent or misspelled.
  • One role per purpose. Separate read-only plan roles from deploy roles, and production from non-production, so a compromised feature branch cannot deploy.
  • Customise the issuer for enterprises. GitHub Enterprise Cloud can issue tokens from https://token.actions.githubusercontent.com/<enterpriseSlug>, so AWS trusts only your enterprise's repositories. This requires a new IAM provider for that URL.
  • Watch untrusted triggers. The action's security notes call out pull_request_target and non-ephemeral runners. Dependabot jobs present an event_name of dynamic; GitHub advises allowing only expected event names where the provider supports conditions on it.
  • Alert on drift. Alert on UpdateAssumeRolePolicy for CI roles, on new OIDC providers, and on AssumeRoleWithWebIdentity with an unexpected subject.
MetricTarget
IAM users with access keys used only from CIZero
GitHub trust policies using StringLike on subZero for production roles
CI roles with a maximum session duration above one hourExceptions documented
Trust policy changes without a linked changeZero
FrameworkReferenceRelevance
MITRE ATT&CKT1552.001 Credentials In Files; T1078.004 Cloud AccountsKeys exposed in repositories and workflow files; abuse of valid cloud roles
NIST SP 800-53 Rev. 5IA-5, IA-5(7), AC-6, AU-6Authenticator management, no embedded static authenticators, least privilege, log review
NIST CSF 2.0PR.AA-01, PR.AA-05, DE.CM-03Credential management, least privilege, monitoring
OWASP NHI Top 10 2025NHI6 Insecure Cloud Deployment Configurations; NHI7 Long-Lived SecretsStatic CI credentials and weak OIDC claim conditions

Practitioner checklist

  • List every workflow that uses AWS keys from secrets, and the IAM users behind them.
  • Create the GitHub OIDC provider in each account, with audience sts.amazonaws.com.
  • Create one role per repository and purpose, with StringEquals on aud and an exact sub.
  • Confirm whether each repository emits immutable or legacy subjects, and opt older repositories in.
  • Use protected environments for production subjects.
  • Grant permissions from observed usage, with a one hour session maximum unless justified.
  • Run positive and negative tests and check subjectFromWebIdentityToken in CloudTrail.
  • Deactivate, then delete, the old IAM user keys and GitHub secrets.

Standards and references

Related NHI Mgmt Group resources: GitHub Actions OIDC Federation Hardening Guide · Multi-Cloud Workload Identity Troubleshooting Guide · How to find unused AWS IAM roles and access keys · How to set up workload identity federation for GitLab CI to Azure

PDF for subscribers

Take this guide with you

Every section, table and checklist in one PDF, with links to the sources and related guides. Subscribe to our weekly NHI & AI Identity Journal and we’ll email you a download link.

Free weekly newsletter

Subscribe to the NHI & AI Identity Journal

The latest on NHI and Agentic AI security – articles, research, breaches, news and events every week.

Bonus 33% off our NHI Course when you subscribe.

NHIMG Editorial Note
Researched and written by the NHI Mgmt Group editorial team using AI tools, under the editorial direction of Lalit Choda. Facts are drawn from the sources listed on this page and checked by our automated quality process. Last updated 28 October 2026. Spotted an error? Use our contact page.
NHI Mgmt Group, the independent authority on Non-Human Identity, IAM and Agentic AI security. nhimg.org

PDF Downloads for Newsletter Subscribers

Get the guide

Enter your details and we’ll email you a download link.

The full guide is free to read online. The PDF is a thank you for subscribing.

Fill in all fields marked * and tick the PDF box to continue.