Workload Identity Federation — Azure · AWS · GCP — Side-by-Side Reference
Identity unit: Service Principal (App Registration)
Auth mechanism: Federated Credentials (OIDC)
Trust anchor: App Registration trusts GitHub issuer
Permissions: RBAC on resource/subscription
No secrets stored — token exchanged for Entra token
Identity unit: IAM Role
Auth mechanism: OIDC Identity Provider
Trust anchor: IAM OIDC Provider trusts GitHub issuer
Permissions: IAM Role + Trust Policy
No secrets stored — token exchanged for AWS creds
Identity unit: Service Account
Auth mechanism: Workload Identity Pools
Trust anchor: WIF Pool Provider trusts GitHub issuer
Permissions: IAM roles bound to Service Account
No secrets stored — token exchanged for GCP creds
Same flow across all three — only the names differ:
token.actions.githubusercontent.com| Azure | AWS | GCP | |
|---|---|---|---|
| Issuer URL | https://token.actions.githubusercontent.com |
https://token.actions.githubusercontent.com |
https://token.actions.githubusercontent.com |
| Audience | api://AzureADTokenExchange |
sts.amazonaws.com |
(default from issuer) |
| Azure | AWS | GCP |
|---|---|---|
|
Console: Entra ID → App Registrations → New Action: Create App Registration Note the Application (Client) ID |
Console: IAM → Identity Providers → Add Provider Action: Add OpenID Connect Provider Provider URL: token.actions.githubusercontent.comAudience: sts.amazonaws.com
|
Console: IAM & Admin → Workload Identity Pools Action: Create Pool (e.g. github-pool)→ Add OIDC Provider (e.g. github-provider)Issuer: https://token.actions.githubusercontent.com
|
| Azure | AWS | GCP |
|---|---|---|
|
Done. The App Registration IS your identity Service Principal = Managed Identity without a cert |
Console: IAM → Roles → Create Role Trusted entity: Web Identity Pick the OIDC provider from Step 1 Set audience to sts.amazonaws.com
|
Console: IAM & Admin → Service Accounts Action: + Create Service Account Name: github-actions-sa
|
| Azure | AWS | GCP |
|---|---|---|
|
Console: App Registration → Certificates & Secrets → Federated Credentials + Add Credential Entity type: Branch / PR / Environment GitHub org: inspireit-dtRepo: my-repo(Optional: add claim condition for specific ref) |
Console: IAM → Roles → pick role → Trust Relationships Edit Trust Policy (JSON): "Condition": {"StringLike": {"token.actions.githubusercontent.com:sub": "repo:inspireit-dt/*:*"}}
|
Console: WIF Pool → Grant Access Attribute name: attribute.repository_ownerAttribute value: inspireit-dtRole: Workload Identity User (See attribute mapping in WIF provider config) |
| Azure | AWS | GCP |
|---|---|---|
|
Console: Subscription → Access Control (IAM) → Add Role Assignment Assign to: Service Principal from Step 1 Role: Contributor / Storage Blob Data Contributor / etc. |
Console: IAM → Roles → pick role → Attach Policy Attach: Managed or custom policy Example: S3FullAccess, LambdaFullAccess, etc. |
Console: IAM & Admin → Service Accounts → pick SA + Grant Access Role examples: Storage Admin, GKE Developer, Cloud Run Admin |
All three require: permissions: { id-token: write, contents: read }
Action: azure/login@v2
Action: aws-actions/configure-aws-credentials@v4
Action: google-github-actions/auth@v2
# .github/workflows/deploy.yml name: Deploy to Azure on: push: branches: [main] jobs: deploy: runs-on: ubuntu-latest permissions: id-token: write contents: read steps: - uses: actions/checkout@v4 - uses: azure/login@v2 with: client-id: ${{ vars.AZURE_CLIENT_ID }} tenant-id: ${{ vars.AZURE_TENANT_ID }} subscription-id: ${{ vars.AZURE_SUBSCRIPTION_ID }} - run: az storage blob upload ...
# .github/workflows/deploy.yml name: Deploy to AWS on: push: branches: [main] jobs: deploy: runs-on: ubuntu-latest permissions: id-token: write contents: read steps: - uses: actions/checkout@v4 - uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: arn:aws:iam::123456789:role/github-actions-role aws-region: us-east-1 - run: aws s3 sync ./build s3://my-bucket/
# .github/workflows/deploy.yml name: Deploy to GCP on: push: branches: [main] jobs: deploy: runs-on: ubuntu-latest permissions: id-token: write contents: read steps: - uses: actions/checkout@v4 - uses: google-github-actions/auth@v2 with: workload_identity_provider: 'projects/PROJECT_NUMBER/locations/global/workloadIdentityPools/github-pool/providers/github-provider' service_account: 'github-actions-sa@PROJECT_ID.iam.gserviceaccount.com' - run: gcloud storage cp ./build gs://my-bucket/
| Provider | Required Variables | Type |
|---|---|---|
| Azure | AZURE_CLIENT_ID, AZURE_TENANT_ID, AZURE_SUBSCRIPTION_ID |
Variables (not secrets — these are public IDs) |
| AWS | role-to-assume hardcoded in workflow |
ARN is public — embed in YAML |
| GCP | workload_identity_provider, service_account hardcoded in workflow |
Public identifiers — embed in YAML |
| Scope | Azure | AWS | GCP |
|---|---|---|---|
| Entire Org | Subject: repo:inspireit-dt/*:* |
"StringLike": {"token.actions.githubusercontent.com:sub": "repo:inspireit-dt/*:*"} |
assertion.repository_owner == 'inspireit-dt' |
| Single Repo | Subject: repo:inspireit-dt/my-repo:* |
"StringEquals": {"token.actions.githubusercontent.com:sub": "repo:inspireit-dt/my-repo:ref:refs/heads/main"} |
assertion.repository == 'inspireit-dt/my-repo' |
| Specific Branch | Subject: repo:inspireit-dt/my-repo:ref:refs/heads/main |
"StringEquals": {..., "ref:refs/heads/main"} |
assertion.ref == 'refs/heads/main' |
| Environment | Subject: repo:inspireit-dt/my-repo:environment:prod |
Use OIDC sub prefix condition | assertion.sub.contains('environment:prod') |