Skip to main content
INS // Insights

GitHub Actions OIDC to AWS: Federation at Org Scale

Updated August 2026 · 8 min read

Federating a single repository's GitHub Actions workflow to AWS via OIDC is a well-documented, mostly mechanical task. Federating forty repositories across a real engineering org — each with different deploy targets, different AWS accounts, and different existing IAM permission sets built up over years — is a migration project with real sequencing decisions, and treating it like forty copies of the same tutorial produces either an outage or, more commonly, forty overly-permissive roles that recreate the standing-credential risk in a different form.

The Trust Policy Is Where Scale Gets Dangerous

A single-repo OIDC setup with a trust policy scoped to that exact repository and branch is safe by construction. The failure mode at scale is a shared, broadly-scoped trust policy created to "make it work for everyone faster" — a role whose trust condition matches an entire GitHub organization rather than specific repositories, which means any workflow in any repo in that org, including a compromised or malicious fork's PR workflow, can assume it.

# Correct: scoped to a specific repo and branch pattern
resource "aws_iam_role" "deploy_service_a" {
  name = "github-actions-deploy-service-a"

  assume_role_policy = jsonencode({
    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"
        }
        StringLike = {
          "token.actions.githubusercontent.com:sub" = "repo:acme-org/service-a:ref:refs/heads/main"
        }
      }
    }]
  })
}
# Dangerous at scale: org-wide trust with no repo scoping — do not do this
# StringLike = { "token.actions.githubusercontent.com:sub" = "repo:acme-org/*" }

The scoped version means a workflow in service-b cannot assume service-a's deploy role even though they're in the same org — each repository gets its own role, trust-scoped to itself. This is more IAM roles to manage than a shared role, and it's the only version that actually preserves least privilege at organizational scale.

A Migration Sequence That Doesn't Require a Big-Bang Cutover

Migrating forty repositories from static AWS keys to OIDC one workflow file change at a time, verified individually, is slower than a scripted mass cutover but categorically safer — a broken trust policy or missing permission surfaces as one repo's deploy failing, not an org-wide outage.

# migration-audit.sh — inventory every repo still using static AWS credentials
gh api graphql -f query='
  query($org: String!) {
    organization(login: $org) {
      repositories(first: 100) {
        nodes { name }
      }
    }
  }' -f org=acme-org --jq '.data.organization.repositories.nodes[].name' | \
while read -r repo; do
  if gh secret list --repo "acme-org/$repo" 2>/dev/null | grep -q "AWS_ACCESS_KEY_ID"; then
    echo "$repo: still using static AWS credentials"
  fi
done

Run this weekly during the migration and track the count trending to zero — it's the migration's own progress evidence and, later, a data point for a SOC 2 auditor asking how credential elimination was actually verified rather than just claimed.

Sequence by risk and blast radius, not by convenience: internal tooling and low-traffic services first to validate the pattern, then production-critical services once the pattern is proven, then the handful of legacy pipelines (often built against tooling that predates OIDC support) last, with those getting an explicit compensating control — a narrowly scoped, short-rotation static key — if federation genuinely isn't achievable for them.

Multi-Account Federation Needs a Role Per Account, Not a Cross-Account Hop

Organizations running multiple AWS accounts (dev, staging, production as separate accounts, which is the AWS-recommended pattern) sometimes federate once into a central account and then cross-account-assume into the target account. This works but adds a second trust hop that's harder to audit than direct federation — the cleaner pattern is registering the OIDC provider in each account that needs to be deployed to, with the trust policy scoped per-repo per-account.

# Direct federation into each target account — no cross-account hop to trace
resource "aws_iam_openid_connect_provider" "github_actions" {
  url             = "https://token.actions.githubusercontent.com"
  client_id_list  = ["sts.amazonaws.com"]
  thumbprint_list = ["6938fd4d98bab03faadb97b34396831e3780aea1"]
}

This costs more Terraform to manage (one OIDC provider registration per account instead of one centrally) but produces a trust chain an auditor can verify in a single hop per deploy: this exact repo, this exact branch, assumed this exact role, in this exact account.

Frequently Asked Questions

How long does an org-wide OIDC migration typically take?

For an organization with 30-50 repositories and a phased, verified-one-at-a-time approach, 4-8 weeks is a realistic range — the bottleneck is usually not the technical federation setup but auditing and rebuilding each repo's actual IAM permission requirements, which are often broader than the workflow actually needs after years of accumulated grants.

Can we federate GitHub Actions to multiple cloud providers at once?

Yes — OIDC federation is provider-agnostic on the GitHub side. The same GitHub Actions OIDC token can federate into AWS IAM roles, Azure AD app registrations, and GCP service accounts, each configured with their own trust policy referencing the same token issuer.

What happens if a repository is renamed or transferred to another org?

The trust policy's sub claim is tied to the exact repository name and org — renaming or transferring breaks the trust condition, which is a feature, not a bug. It forces an explicit re-authorization step rather than silently carrying access forward to a repository under different ownership.

Do self-hosted GitHub Actions runners change the OIDC federation setup?

No — the OIDC token is issued by GitHub's token service regardless of whether the runner is GitHub-hosted or self-hosted. The federation trust policy and role configuration are identical; only the runner infrastructure itself differs.

Is there a way to detect if a static AWS key is still being used after migration claims completion?

Yes — CloudTrail records the credential type used for every API call. Querying CloudTrail for IAMUser (long-lived key) versus AssumedRole (federated) principal types for your CI/CD-associated IAM identities gives a definitive, evidence-grade answer that's stronger than checking whether the secret still exists in GitHub, since an unused secret sitting in the repo settings doesn't prove it isn't still referenced somewhere.


Rutagon runs org-wide OIDC federation migrations — trust policy design, phased cutover, and CloudTrail-verified completion evidence.

Talk to us about your credential elimination pilot → rutagon.com/contact | 907-841-8407 | contact@rutagon.com

Related reading: Replacing AWS Access Keys With OIDC in CI/CD · Stop Rotating AWS Access Keys—Eliminate Them · Security Automation Capability

External reference: AWS IAM Documentation — Creating OpenID Connect (OIDC) Identity Providers