Skip to main content
INS // Insights

Workload Identity Federation Migration on AWS

Updated September 2026 · 8 min read
Workload Identity Federation Migration on AWS

Workload identity federation migration is how a CI pipeline stops being a long-lived IAM user. GitHub Actions (or GitLab, or Terraform Cloud) presents an OIDC token. AWS STS issues short-lived credentials for a role. There is no access key to leak in a log, a fork, or a copied secret. AWS documents the pattern as IAM OIDC identity providers and AssumeRoleWithWebIdentity (IAM OIDC).

SaaS non-human-identity tools will happily inventory the old keys. They rarely write the trust policy that limits blast radius to one repo, one environment, and one set of actions. That policy is the migration.

Rutagon rolls this out on real pipelines — including a production SaaS platform with OIDC CI/CD and zero long-lived deploy keys as the target state. We also have a genuine cross-cloud pattern: a GCP service account federating into an AWS IAM role, still with no long-lived AWS key. We describe that generically. We do not name the system it came from.

What Federation Changes in the Threat Model

An IAM user access key is a bearer secret. Anyone who holds it is the user until you rotate. An OIDC trust is a conversation: the token must be signed by the provider, the audience must match, and the subject must match the workflow you intended. Compromise a single workflow file and you have a problem. You do not have a key that works from a laptop in another country with no other context.

Verizon's DBIR line on credentials is the reason CFOs fund this: stolen credentials remain a primary breach path (Verizon DBIR). Federation deletes a class of those credentials.

Workload Identity Federation Migration We Sequence

Wave 0 — Inventory. Every IAM user with Active access keys, every GitHub secret named AWS_, every deploy key. Rank by privilege.

Wave 1 — One production deploy role. Pin sub to repo:ORG/REPO:environment:production (or the branch ref if you do not use GitHub Environments). Least-privilege the role to the deploy actions only.

Wave 2 — The rest of CI. Plan, lint, and preview roles with even tighter permissions. No "shared CI admin."

Wave 3 — Delete. Keys disabled, then deleted. Secrets removed. A CloudWatch alarm on CreateAccessKey so they do not come back.

# GitHub Actions — no AWS keys in secrets
permissions:
  id-token: write
  contents: read

jobs:
  deploy:
    runs-on: ubuntu-latest
    environment: production
    steps:
      - uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::123456789012:role/ci-deploy
          aws-region: us-west-2

The dangerous version of this role is StringLike on repo:ORG/*. That is convenience. It is also every contractor fork path you forgot. We do not ship that unless the org is a single-purpose automation org with its own review.

Related delivery notes: GitHub Actions OIDC at scale, GitHub environment protection, replace AWS access keys with OIDC. Capability: security automation.

Blast-Radius Limits NHI Dashboards Skip

We add four conditions that scanners do not:

  1. Audience locked to sts.amazonaws.com.
  2. Subject locked to repo + environment, not the whole org.
  3. Permission boundary on the role so a sloppy inline policy cannot grow to AdministratorAccess.
  4. Deploy approvals on the GitHub Environment so the role cannot be assumed by a push to a feature branch.

SaaS NHI products can report that a role is broad. They do not sit in your Terraform PR and refuse the wildcard. We do.

Cross-cloud federation uses the same discipline: the foreign identity's subject and audience are explicit, the AWS role is scoped, and there is still no AWS access key on the other side.

Rollback and Dual-Run

For one or two pipelines we allow a dual-run: OIDC is primary, the old key is disabled but not deleted for a short window, with an alarm if the key is used. After the window the user object is gone. Teams that "keep the key just in case" for a month have not migrated.

What This Is Not

This is not a tutorial for building a credential-elimination product company. This is not a claim that Rutagon holds SOC 2 or FedRAMP. This is delivery on your AWS account and your org's OIDC provider.

Thumbprints on the OIDC provider are another quiet landmine. GitHub publishes the current CA thumbprint; it can change. We pin what AWS requires, subscribe to the provider's change notice, and treat a thumbprint update as a change ticket — not as an emergency grep at 2 a.m. after every pipeline fails AssumeRoleWithWebIdentity. If you copied a thumbprint from a random gist, we replace it from the official documentation before we delete the old keys.

Environment protection rules belong in the same PR as the trust policy. A role that is perfectly pinned to environment:production is useless if anyone can create that environment on a fork or if reviewers are optional. We set required reviewers on the GitHub Environment and we keep the AWS side boring. Two control planes, one blast-radius story.

Talk to us about your credential elimination pilot →

907-841-8407 · contact@rutagon.com

Frequently Asked Questions

Can we federate GitLab and GitHub into the same AWS account?

Yes. Separate OIDC providers, separate roles, separate subject conditions. Do not share one catch-all role.

What if we use self-hosted runners?

Self-hosted runners change the trust story. The token is still OIDC, but the runner's network and who can enqueue jobs become part of the control. We treat runner identity as in-scope, not as a footnote.

Does federation replace AWS IAM Identity Center for humans?

No. Humans should already be on Identity Center or similar. Federation is for workloads. Mixing them is how break-glass becomes a CI role.

How do we prove the old keys are gone for an auditor?

IAM credential reports, Access Analyzer unused-access findings, and a sealed inventory from the migration. Screenshots of an empty secrets list are optional garnish.

Is Terraform Cloud / other OIDC CI supported?

Yes, with that vendor's subject claims pinned the same way. The pattern is the provider, not the logo.