Skip to content
Cloud security · Identity

Workload Identity Migration Helper

A key that sits in a CI settings page, a Kubernetes Secret or a vendor's database can be stolen from any of them. Pick the workload and the cloud it calls, and see how to replace the key with a short-lived credential: which mechanism, which trust conditions to pin, what the config looks like before and after, how to roll it out and back, and how to retire the old key. Everything runs in your browser.

No download, no card

The long-lived credential being replaced is the IAM user access key (AKIA...). Placeholders in capitals (ORG, REPO, ACCOUNT_ID) are yours to fill in. Test every change in a non-production account first.

Replacement mechanism

GitHub Actions OIDC to an AWS IAM role (sts:AssumeRoleWithWebIdentity)

The workflow asks GitHub for a short-lived OIDC token and exchanges it with AWS STS for temporary credentials. No AWS secret is stored in GitHub.

Before you start
  • An IAM OIDC identity provider for https://token.actions.githubusercontent.com (audience sts.amazonaws.com).
  • An IAM role per job or environment, with a trust policy that pins the conditions below.
  • The workflow job needs permissions: id-token: write.

Pin these conditions

A federated or attached identity is only as safe as the conditions on it. A trust policy that accepts any repository, branch or namespace trades one stolen key for one stolen identity.

  • Audience (aud)
    sts.amazonaws.com

    Stops a token minted for another service being accepted here.

  • Subject (sub)
    repo:ORG/REPO:environment:production or repo:ORG/REPO:ref:refs/heads/main

    Pins the exact repository and either the environment or the branch. Avoid repo:ORG/*, which trusts every repository in the organization.

  • One role per purpose
    deploy role, read-only role, release role

    A pull-request build should never be able to assume the deploy role.

Before and after

Before (yaml)
# .github/workflows/deploy.yml (before)
jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: aws-actions/configure-aws-credentials@v4
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-region: us-east-1
After, part 1 (json)
{
  "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:production"
      }
    }
  }]
}
After, part 2 (yaml)
# .github/workflows/deploy.yml (after)
permissions:
  id-token: write
  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::ACCOUNT_ID:role/github-deploy
          aws-region: us-east-1
Verify in the vendor docs
  • The exact claim format of sub changes if you customize it at organization or repository level; check the token's claims in the GitHub OIDC documentation before pinning.

Rollout checklist

  1. Write down every place the old key is used (repositories, pipelines, hosts, scripts, vendor integrations) before changing anything.
  2. Create the cloud-side trust (provider or federated credential) and the role/identity with only the permissions the job needs. Start from what the job actually calls, not from the old key's permissions.
  3. Add the new authentication path to ONE non-production workload and leave the old key in place.
  4. Run the job and confirm in the cloud audit log that the calls came from the new identity (look for the assumed role, federated principal or service account in the log entry).
  5. Tighten the trust conditions (see "Pin these conditions") and confirm a token from a different repository, branch or namespace is refused.
  6. Move production workloads one at a time. Keep the old key until each has run through its normal schedule.
  7. Remove the old key as described in "What to do with the old key".

Rollback checklist

  • Keep the old key enabled (not deleted) until the new path has run through every normal schedule.
  • If the new path fails, revert the workflow or deployment change; the old key still works. Do not widen the trust condition to "make it work": read the token's claims and fix the condition to match the real value.
  • If you must re-enable a disabled key, do it for the one consumer, set a date to remove it, and record why.

What to do with the old key

In order: find usage, disable, watch, delete. Do not skip the watch step.

  1. Find who still uses it: in the IAM console open the user, Security credentials, and read "Last used" for the access key (or run: aws iam get-access-key-last-used --access-key-id AKIA_EXAMPLE).
  2. Disable, do not delete yet: aws iam update-access-key --user-name USER --access-key-id AKIA_EXAMPLE --status Inactive. A disabled key can be switched back on; a deleted one cannot.
  3. Watch: search CloudTrail for AccessDenied or InvalidClientTokenId errors that name the key, across one full cycle of your slowest job (monthly, quarterly or year-end jobs break last).
  4. Delete: aws iam delete-access-key --user-name USER --access-key-id AKIA_EXAMPLE. If the IAM user has no other purpose, delete the user too.
  5. Treat the old key as exposed if it ever sat in a CI log, image, backup or a vendor that has had an incident: rotate first, then migrate, and review its logged activity.

If you cannot migrate yet

  • AWS: add an aws:SourceIp condition (or aws:SourceVpc / aws:SourceVpce for traffic from inside a VPC) to the user or role policy so the key only works from your egress addresses.
  • Until the key is gone, restrict where it can be used from. magoo observed that most leaked credentials in the breaches he reviewed could have been network allow-listed and were used from unfamiliar remote networks.
  • Scope the key to the one role it needs and review its last-used date weekly.
Take it with you
TL;DR

What this tool is

5 points
  • Pick where a workload runs (GitHub Actions, GitLab CI, Kubernetes, a VM, serverless or on-premises), which cloud it calls and how its long-lived key is held today.
  • You get the replacement mechanism, the trust-policy conditions to pin, a before and after snippet you can copy, a rollout and rollback checklist, and what to do with the old key.
  • Mechanisms covered: GitHub and GitLab OIDC federation, Google Workload Identity Federation, Microsoft Entra workload identity federation, IRSA and EKS Pod Identity, GKE and AKS workload identity, instance profiles, attached service accounts, managed identities and IAM Roles Anywhere.
  • Anything that depends on a version or a licence is marked "verify in the vendor docs".
  • Runs in your browser. Nothing is uploaded and no cloud account is touched.

Why the key is the problem

In a retrospective on serial breaches of developer and infrastructure platforms (“Black October: Lessons From the Downstream Breaches”, Starting Up Security, October 2026), the author, magoo, describes a hub-and-spoke pattern: a breached hub exposed many customers' long-lived credentials, such as GitHub tokens, long-lived AWS keys and AI platform keys. His observations, which are one practitioner's view and not a measured rate, were that many of those keys did not need to exist and could have been role-based credentials or Workload Identity Federation, and that most could have been network allow-listed, since they were used from unfamiliar remote networks.

Short-lived credentials do not stop a determined attacker who is already inside a build, but they change what a leaked copy is worth: a token that expires, and that the cloud only accepts for one repository, branch or service account, is far less useful from a stolen database dump than a key that works from anywhere until someone rotates it.

Migrating is mostly careful bookkeeping: find every use, add the new path beside the old one, prove the audit log shows the new identity, then disable, watch and delete. The checklists above follow that order.

Frequently asked questions

What is workload identity federation?

A way for a workload to prove who it is with a short-lived token from a trusted identity provider (for example the one built into GitHub Actions, GitLab CI or a Kubernetes cluster) and exchange it for short-lived cloud credentials. Because no long-lived secret is stored, there is nothing to leak from a CI settings page, a repository or a vendor database.

How do I replace an AWS access key in GitHub Actions?

Create an IAM OIDC identity provider for token.actions.githubusercontent.com, create an IAM role whose trust policy allows sts:AssumeRoleWithWebIdentity and pins the audience and the subject (the repository plus a branch or environment), give the workflow permissions: id-token: write, and use the role-to-assume input of aws-actions/configure-aws-credentials. Then disable the old key, watch for failures, and delete it.

What conditions should a trust policy pin?

At least the audience and the subject. For GitHub that means the exact repository and a branch or environment, not a wildcard over the whole organization. For Kubernetes it means the namespace and service account. For Google Cloud it means an attribute condition on the provider, so tokens from other repositories are rejected before any IAM check.

Should I delete the old key straight away?

No. Disable it first where the provider allows it (AWS and Google Cloud do), watch for failures across your slowest scheduled job, and only then delete. Azure client secrets cannot be disabled, only removed, so record the secret ID first. If the key may have been exposed, rotate it first and review what it did.

Why does this matter after a downstream breach?

One responder, magoo, writing in Starting Up Security (Oct 2026) observed that in serial breaches of developer and infrastructure platforms, breached hubs exposed many customers' long-lived credentials such as GitHub tokens, AWS keys and AI platform keys, and that many of those keys did not need to exist: they could have been role-based or short-lived credentials. That is an observation by one practitioner, not a measured rate.

Is the output safe to paste into production?

Treat it as a starting point. Replace every placeholder, scope permissions to what the job calls, and test in a non-production account. Details such as flags and claim formats change between versions; the tool marks the ones to check in the vendor documentation.

The tools people use most on PlayCISO.

All tools
🖤 Black Friday: new customers get 3 months of Pro for $5, then $5/month. Ends 30 Nov. Claim 3 months for $5 →