Skip to content

Stop Storing AWS Access Keys in Your CI/CD Pipeline: How to Connect GitHub Actions and Azure DevOps to AWS With OIDC

Long-lived AWS keys in CI secrets are one leaked log away from a breach. Connect GitHub Actions and Azure DevOps to AWS with OIDC, plus GitHub's 2026 change.

Cloud & DevOps · 1 Nov 2026 · 6 min read

Stop Storing AWS Access Keys in Your CI/CD Pipeline: How to Connect GitHub Actions and Azure DevOps to AWS With OIDC

By@ashtech52Lead Full-Stack Engineer

If your deployment pipeline stores AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY as secrets, you have a password to your AWS account that never expires, sitting in a system that runs code written by strangers every day.

You don't need it. GitHub Actions and Azure DevOps can both prove their identity to AWS with OpenID Connect (OIDC) and receive temporary credentials that expire on their own, usually within an hour. Nothing long-lived is left to leak.

Below: the switch on both platforms, the trust-policy mistakes that undo the benefit, and a GitHub change in July 2026 that can break deployments for teams who miss it. It's the approach we recommend when we build CI/CD pipelines that deploy to AWS.

Why stored keys are the weak point

In March 2025, attackers compromised tj-actions/changed-files, a GitHub Action used in thousands of repositories. They pointed its version tags at malicious code that printed the workflow's secrets into its build logs, so every version up to 45.0.7 was affected. The US Cybersecurity and Infrastructure Security Agency (CISA) warned that access keys, GitHub tokens, npm tokens and private keys could be exposed, and told everyone affected to rotate secrets immediately.

Teams with long-lived AWS keys in those workflows had to assume the keys were stolen. Teams using OIDC had far less to lose: at worst, a temporary credential that expired on its own, by default within an hour.

AWS's own IAM best practices make the same point: workloads should use temporary credentials through roles, not long-term access keys.

How OIDC works, in one paragraph

When a workflow runs, the CI platform issues a signed token saying exactly who is asking: which organisation, repository, branch or environment. The workflow hands that token to AWS. AWS checks the signature and compares the token with the trust policy on an IAM role you created. If everything matches, it returns short-lived credentials for that role. That trust policy decides who gets in, so that's where the care goes.

How to set up GitHub Actions OIDC with AWS

1. Add GitHub as an identity provider in AWS

In IAM, add an OpenID Connect provider with the URL https://token.actions.githubusercontent.com and the audience sts.amazonaws.com. With the AWS CLI:

`bash aws iam create-open-id-connect-provider \ --url https://token.actions.githubusercontent.com \ --client-id-list sts.amazonaws.com `

You no longer need a certificate thumbprint. Since 2023, AWS validates GitHub's endpoint against trusted certificate authorities, and in 2024 it made the thumbprint optional for any provider whose certificate chains to a trusted root certificate authority. Some older Terraform and CloudFormation templates still ask for one, but AWS ignores it for GitHub.

2. Create a role with a tight trust policy

This is the step that matters most. Restrict both the audience (aud) and the subject (sub), which identifies the repository and branch or environment:

`json { "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", "token.actions.githubusercontent.com:sub": "repo:your-org/your-repo:environment:production" } } } ] } `

Tie production roles to a GitHub environment (environment:production) rather than a branch. Environments can require approvals and limit which branches deploy, so the trust policy and the deployment rules work together.

3. Request the token in the workflow

`yaml permissions: id-token: write # lets the job request an OIDC token contents: read

jobs: deploy: runs-on: ubuntu-latest environment: production steps: - uses: actions/checkout@v7 - uses: aws-actions/configure-aws-credentials@v6 with: role-to-assume: arn:aws:iam::123456789012:role/deploy-production aws-region: eu-west-2 - run: aws s3 sync ./dist s3://your-bucket `

The id-token: write permission only lets the job request the token. It gives the workflow no other write access. Pin third-party actions to a full commit SHA rather than a tag, for the reason the tj-actions incident made obvious. Your package dependencies need the same care: see our guide to npm supply-chain attacks.

Immutable subject claims: the July 2026 change for new repositories

On 23 April 2026, GitHub announced immutable subject claims. For repositories created, renamed or transferred after 15 July 2026, the sub claim includes numeric owner and repository IDs:

` repo:your-org@123456/your-repo@456789:environment:production `

Existing repositories keep the old format unless their owner opts in. The reason is sound: if a repository is deleted and an attacker later creates one with the same name, the IDs won't match, so the old trust doesn't carry over.

The catch is practical. A trust policy written as repo:your-org/your-repo:... won't match a new repository's token, so the first deployment fails with an "access denied" error from AWS. For any repository set up after mid-July 2026, check the subject GitHub actually sends (the official documentation shows how to inspect the claims) and write your trust policy to match.

How to set up Azure DevOps OIDC with AWS

Azure DevOps supports the same pattern through the AWS Toolkit for Azure DevOps, version 1.15.0 or later:

  1. In IAM, add an OpenID Connect provider with the URL https://vstoken.dev.azure.com/{your-organisation-GUID} and the audience api://AzureADTokenExchange.
  2. Create a role whose trust policy checks that audience and a subject in the form sc://{organisation}/{project}/{service-connection-name}.
  3. Create an AWS service connection in Azure DevOps, enter the role ARN, enable Use OIDC and leave the access key fields empty.
  4. Point your pipeline's AWS tasks at that service connection.

The subject names one service connection, so restrict who can use that connection in Azure DevOps. Whoever can use it can deploy.

Five trust-policy mistakes to avoid

  1. No `sub` condition, or `repo:your-org/*`. Any workflow in the organisation, including one in a test repository, could then assume a production role.
  2. Forgetting the `aud` condition. Always pair it with sub.
  3. Using `StringLike` with wildcards where `StringEquals` would do. Every * widens who can get in.
  4. One role for everything. Use separate roles for build, staging and production, each with only the permissions that stage needs. Separate environments are also easier to tag and cost, one of the first steps in our checklist to reduce AWS costs.
  5. Letting pull requests from forks reach deployment roles. Keep deployment jobs on protected branches or environments, and never let a job that can assume a deployment role run code from outside contributors (watch pull_request_target workflows in particular).

Retire the old keys properly

The old keys still work until you delete them, so switching the workflow isn't the finish line.

  1. Generate the IAM credential report, or check "last used" on each access key, to find keys used by CI.
  2. Deploy once with OIDC and confirm it works.
  3. Deactivate the old key, wait a few days to catch anything you missed, then delete it.
  4. Remove the old secrets from GitHub and Azure DevOps so nobody reaches for them again.

Frequently asked questions

Is OIDC more secure than storing AWS access keys in GitHub secrets?

Yes. Stored keys don't expire and work from anywhere if they leak. OIDC credentials are issued per run, expire on their own and only work for the repository, branch or environment named in the role's trust policy.

Do I still need a thumbprint for the GitHub OIDC provider in AWS?

No. AWS validates GitHub's OIDC endpoint against trusted certificate authorities and ignores the thumbprint. Some older infrastructure-as-code templates still require a value, but it isn't used for validation.

Why does GitHub Actions get AccessDenied when assuming an AWS role?

Usually because the token doesn't match the role's trust policy. Check that the sub condition names the exact repository and branch or environment, and that the job has the id-token: write permission. For repositories created, renamed or transferred after 15 July 2026, GitHub's subject claim includes numeric owner and repository IDs, so a policy that matches only the repository name will fail until you update its sub condition to the new format.

The bottom line

A long-lived AWS key in a CI secret is a standing risk that one compromised action can turn into a breach. OIDC removes it, takes an afternoon to set up, and makes every deployment's access explicit in a trust policy you can read. Write those policies tightly, watch for GitHub's new subject format, and delete the old keys.

Still have AWS keys in your CI secrets, or not sure your trust policies are tight enough? Book a free consultation and we'll review your roles and trust policies with you.

Sources

Back to all insights

Keep reading.

Have a project in mind?

Tell us what you're building, what's slowing your team down or what you'd like to automate. We'll come back with honest next steps and a clear estimate.

No obligation and no sales script.

Popular searches

Change theme