Workload Identity Federation Playbook (Secretsless CI/CD for GitHub Actions)
Date: 2026-03-01
Category: knowledge (software / security / platform)
Why this matters
Long-lived cloud keys in CI are a latent incident:
- they leak via logs/artifacts/mis-scoped secrets,
- they survive longer than intended,
- and revocation is often slow and incomplete.
Workload Identity Federation (WIF) replaces static keys with short-lived, claim-bound tokens. If you bind trust to repository/workflow/branch/environment claims, your deploy identity becomes:
- ephemeral,
- auditable,
- and policy-constrained by default.
Core model (mental picture)
- GitHub Actions job requests OIDC token (
id-token: write). - Cloud IAM validates JWT issuer/audience/subject (+ optional extra claims).
- Cloud exchanges JWT for short-lived credentials.
- Job uses temporary credentials; no static cloud secret stored in GitHub.
This is not “no auth”—it is better auth with tighter lifetime + stronger context binding.
Minimum trust contract (must-have)
For production, enforce all 5:
- Issuer pinned:
https://token.actions.githubusercontent.com - Audience pinned to provider expectation
- Subject constrained (repo + branch or environment, not wildcard
*) - Least privilege role for each workload identity
- Short session duration + full audit logging enabled
If any of these is weak, WIF degrades to “dynamic secret sprawl.”
GitHub OIDC claims that matter most
From GitHub OIDC docs, common high-signal claims are:
iss,aud,subrepository,repository_owner,ref,environmentworkflow_ref,job_workflow_ref(useful for reusable workflow pinning)
Common sub patterns:
- Branch scoped:
repo:ORG/REPO:ref:refs/heads/main - Environment scoped:
repo:ORG/REPO:environment:prod - PR scoped:
repo:ORG/REPO:pull_request
Practical rule: prefer environment-scoped subjects for production deploys, then protect environment with branch/tag approvals.
Cloud-specific notes (AWS / GCP / Azure)
AWS
- OIDC provider URL:
https://token.actions.githubusercontent.com - Typical audience for official action:
sts.amazonaws.com - Trust policy should constrain
token.actions.githubusercontent.com:sub
Example trust condition:
"Condition": {
"StringEquals": {
"token.actions.githubusercontent.com:aud": "sts.amazonaws.com",
"token.actions.githubusercontent.com:sub": "repo:my-org/my-repo:environment:prod"
}
}
Operational note: avoid broad wildcard subjects in production roles.
GCP
- Use Workload Identity Pool + Provider
- Map GitHub claims and add attribute conditions
- Bind provider principal to service account with
roles/iam.workloadIdentityUser
Typical guardrails:
- restrict
repositoryandrepository_owner - scope to branch/environment claims for deploy workloads
Azure
- Configure Entra app + federated credentials
- Recommended audience is commonly
api://AzureADTokenExchange - Create separate federated credentials per environment/repo boundary (avoid “one giant credential”)
GitHub workflow baseline
permissions:
id-token: write
contents: read
jobs:
deploy:
runs-on: ubuntu-latest
environment: prod
steps:
- uses: actions/checkout@v5
# cloud auth action here (aws/gcp/azure), pinned by commit SHA
Hardening checklist:
- pin third-party actions by commit SHA,
- require environment protection rules for
prod, - separate build/test identity from deploy identity,
- disallow deploy on untrusted events (fork PRs) unless explicitly sandboxed.
Reference architecture (recommended)
Use a two-lane identity design:
- Build lane identity
- read artifact registry, push build outputs, no production mutation permissions.
- Deploy lane identity
- minimal deploy-only permissions, environment-scoped subject, stricter approvals.
Benefits:
- smaller blast radius,
- easier audit reasoning,
- faster incident containment (disable one lane without freezing everything).
Rollout plan (safe migration from static secrets)
Phase 0 — Inventory
- list all CI cloud secrets (where used, scope, rotation age).
Phase 1 — Parallel auth (non-prod)
- add WIF in staging first,
- keep legacy secret path as fallback for one sprint.
Phase 2 — Production cutover
- enforce WIF for prod deploy jobs,
- revoke old static keys immediately after successful cutover.
Phase 3 — Post-cutover hardening
- reduce session TTL,
- split roles per environment/service,
- alert on denied token exchanges and unusual assume-role spikes.
Observability and governance
Track these signals weekly:
- WIF success rate / failure rate by repo and environment
- denied exchanges by reason (aud/sub mismatch, issuer mismatch, condition mismatch)
- temporary credential session duration distribution
- count of remaining static CI secrets (target: 0 for cloud auth)
Policy goal:
- 100% of production deploys use federated short-lived credentials
- no long-lived cloud keys in GitHub secrets for deployment paths
Common failure patterns
id-token: writemissing- job cannot request OIDC token.
Over-broad subject wildcard
- accidental privilege expansion across branches/environments.
Single role for all repos
- hard-to-audit cross-service blast radius.
No environment protection rules
- secure identity, insecure release process.
Forgot to delete legacy static keys
- new secure path + old insecure backdoor.
One-line operator rule
Treat federated deploy identity like production code: small scope, explicit conditions, short lifetime, continuous audit.
References
- GitHub OIDC reference: https://docs.github.com/en/actions/reference/security/oidc
- GitHub OIDC in AWS: https://docs.github.com/en/actions/how-tos/secure-your-work/security-harden-deployments/oidc-in-aws
- GitHub OIDC in GCP: https://docs.github.com/en/actions/how-tos/secure-your-work/security-harden-deployments/oidc-in-google-cloud-platform
- GitHub OIDC in Azure: https://docs.github.com/en/actions/how-tos/secure-your-work/security-harden-deployments/oidc-in-azure
- Google Cloud WIF for deployment pipelines: https://docs.cloud.google.com/iam/docs/workload-identity-federation-with-deployment-pipelines
- AWS IAM OIDC provider setup: https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_providers_create_oidc.html