OIDC Federation for CI: Enterprise Implementation Guide for Secretless Cloud Access
Prerequisites
- Working knowledge of CI/CD pipelines
- Basic familiarity with cloud IAM and OIDC claims
Steps
OIDC federation for CI lets build pipelines exchange short-lived identity tokens for cloud credentials instead of storing long-lived secrets. Enterprises adopt it to reduce credential exposure, improve auditability, and enforce workload-level trust with policy-based access.
Overview
OIDC federation for CI is a trust model where a CI platform such as GitHub Actions, GitLab CI, or Terraform Cloud issues an OpenID Connect token to a workload, and a cloud provider validates that token before granting temporary access. The core purpose is to eliminate static secrets in pipelines and replace them with short-lived, scoped credentials tied to repository, branch, workflow, or project claims.
Enterprises use OIDC federation because it reduces secret sprawl, limits blast radius, and aligns with Zero Trust principles. Instead of storing cloud keys in CI variables or secret managers for every pipeline, the platform requests credentials just in time, with policy controls based on issuer, audience, subject, and custom claims.
Architecture
Core components:
- CI issuer: GitHub Actions, GitLab, or another platform that mints OIDC tokens
- Identity provider trust: AWS IAM OIDC provider, Azure Entra federated credential, or Google Workload Identity Federation
- Target role or service principal: The cloud identity assumed by the pipeline
- Policy engine: Conditions on claims such as
sub,repository,ref,environment, oraud - Audit plane: CloudTrail, Entra sign-in logs, or GCP audit logs
Deployment models:
- Single-account trust for one engineering team
- Central identity account with cross-account role assumption
- Multi-cloud federation from one CI issuer to AWS, Azure, and GCP
View as text
[Developer] ---- 1. push/merge ----> [CI Platform] [CI Platform] ---- 2. request OIDC token ----> [OIDC Issuer] [OIDC Issuer] ---- 3. signed JWT ----> [CI Job] [CI Job] ---- 4. exchange JWT ----> [Cloud STS / Entra / WIF] [Cloud IAM] ---- 5. short-lived credentials ----> [CI Job] [CI Job] ---- 6. deploy/read state ----> [Cloud Resources] [Cloud IAM] ---- 7. audit events ----> [Logging/SIEM]
Data flow:
- A workflow starts from a trusted event.
- The CI runner requests an OIDC token from its issuer.
- The token includes claims describing repo, branch, workflow, project, and audience.
- The cloud IAM service validates issuer metadata, signing keys, and claim conditions.
- Temporary credentials are issued for minutes, not days.
Implementation Guide
Example target: GitHub Actions to AWS IAM.
- Create an AWS OIDC provider.
aws iam create-open-id-connect-provider --url https://token.actions.githubusercontent.com --client-id-list sts.amazonaws.com --thumbprint-list 6938fd4d98bab03faadb97b34396831e3780aea1
- Create a trust policy bound to a repository and branch.
{
"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/platform-infra:ref:refs/heads/main"
}
}
}
]
}
- Create the role and attach least-privilege permissions.
aws iam create-role --role-name gha-deploy-role --assume-role-policy-document file://trust-policy.json
aws iam attach-role-policy --role-name gha-deploy-role --policy-arn arn:aws:iam::aws:policy/ReadOnlyAccess
- Configure the GitHub workflow.
name: deploy
on:
push:
branches: [main]
permissions:
id-token: write
contents: read
jobs:
aws-auth:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789012:role/gha-deploy-role
aws-region: eu-central-1
- run: aws sts get-caller-identity
- Validate token claims during rollout.
aws sts get-caller-identity
aws cloudtrail lookup-events --lookup-attributes AttributeKey=EventName,AttributeValue=AssumeRoleWithWebIdentity --max-results 5
Code Examples
TOKEN=$(curl -sH "Authorization: bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" "$ACTIONS_ID_TOKEN_REQUEST_URL&audience=sts.amazonaws.com" | jq -r '.value')
python3 - <<'PY'
import os, jwt
print(jwt.get_unverified_header(os.environ['TOKEN']))
print(jwt.decode(os.environ['TOKEN'], options={"verify_signature": False}))
PY
permissions:
id-token: write
contents: read
steps:
- uses: azure/login@v2
with:
client-id: ${{ secrets.AZURE_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
- run: az account show
resource "google_iam_workload_identity_pool" "ci" {
workload_identity_pool_id = "github-pool"
display_name = "GitHub CI Pool"
}
resource "google_iam_workload_identity_pool_provider" "github" {
workload_identity_pool_id = google_iam_workload_identity_pool.ci.workload_identity_pool_id
workload_identity_pool_provider_id = "github-provider"
oidc { issuer_uri = "https://token.actions.githubusercontent.com" }
attribute_mapping = {
"google.subject" = "assertion.sub"
"attribute.repository" = "assertion.repository"
"attribute.ref" = "assertion.ref"
}
}
Security Hardening
- Restrict by issuer, audience, subject, and branch or environment claims.
- Use separate roles for
plan,apply, and production deployment. - Keep session duration short, typically 15 to 60 minutes.
- Encrypt logs and state stores with KMS-managed keys.
- Require protected branches, signed commits, and environment approvals before privileged jobs run.
- Deny wildcard trust such as
repo:acme/*unless combined with strong environment controls.
Comparison
| Capability | OIDC Federation for CI | HashiCorp Vault JWT/OIDC Auth | Azure DevOps Service Connections |
|---|---|---|---|
| Pricing | Usually included in CI and cloud IAM | Vault Enterprise often adds cost at scale | Included with Azure DevOps, cloud costs separate |
| Deployment | Native trust between CI and cloud | Requires Vault cluster and auth backend ops | Native in Azure DevOps, strongest for Azure |
| Scalability | High, no secret distribution | High but operationally heavier | High inside Azure DevOps ecosystems |
| Security | Short-lived creds, no static keys | Strong brokering and dynamic secrets | Good, but can still depend on service connection design |
Troubleshooting
- Invalid audience
An error occurred (InvalidIdentityToken) when calling the AssumeRoleWithWebIdentity operation: Incorrect token audience
Fix: Ensure the workflow requests aud=sts.amazonaws.com and the IAM trust policy matches exactly.
- Subject claim mismatch
AccessDenied: Not authorized to perform sts:AssumeRoleWithWebIdentity
Condition check failed: token.actions.githubusercontent.com:sub
Fix: Compare the real sub claim from the JWT to the trust policy. Branch refs must match refs/heads/main exactly.
- Missing workflow permission
Error: Error message: Insufficient permission to access OIDC token
Error: HttpError: Resource not accessible by integration
Fix: Add permissions: id-token: write at workflow or job level and verify the event type is allowed.
Best Practices
Do
- Bind trust to exact repositories and protected branches.
- Separate non-production and production roles.
- Send IAM and CI audit logs to a SIEM with correlation on run ID and commit SHA.
- Test claim conditions in a sandbox account before rollout.
Don't
- Do not keep fallback static cloud keys in repository secrets.
- Do not allow broad trust on all branches for deployment roles.
- Do not reuse one role across teams; create workload-specific identities with clear ownership.
Have a project in mind?
Get an instant AI price estimate for it, or talk directly to our team.
One email a month on what we learn building with AI