Sultan's Cloud Shaping

Workload Identity Federation — Azure · AWS · GCP — Side-by-Side Reference

Concept Overview
Step-by-Step Setup
GitHub Actions YAML
Concept Mapping

1 Core Concept

Az Azure

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

AW AWS

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

GC GCP

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

2 Architecture Flow

Same flow across all three — only the names differ:

  1. GitHub Actions obtains an OIDC token from token.actions.githubusercontent.com
  2. GitHub sends the token to the cloud provider's token exchange endpoint
  3. Cloud verifies the token signature, issuer, audience, and any attribute conditions
  4. Cloud exchanges the GitHub OIDC token for a short-lived cloud credential
  5. Workflow uses that credential to call cloud APIs — no long-lived secrets anywhere

3 Issuer & Audience (All 3)

AzureAWSGCP
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)
Step 1Create the Identity Trust (Issuer)
AzureAWSGCP
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.com
Audience: 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
Step 2Create the Identity (Service Account Equivalent)
AzureAWSGCP
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
Step 3Configure Federation (Link GitHub to Cloud)
AzureAWSGCP
Console: App Registration → Certificates & Secrets → Federated Credentials
+ Add Credential
Entity type: Branch / PR / Environment
GitHub org: inspireit-dt
Repo: 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_owner
Attribute value: inspireit-dt
Role: Workload Identity User
(See attribute mapping in WIF provider config)
Step 4Grant Permissions (RBAC / IAM Roles)
AzureAWSGCP
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

GitHub Actions Workflow Snippets

All three require: permissions: { id-token: write, contents: read }

Az Azure

Action: azure/login@v2

AW AWS

Action: aws-actions/configure-aws-credentials@v4

GC GCP

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/

Variables / Secrets to Set in GitHub

ProviderRequired VariablesType
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

Concept Mapping (TL;DR)

Concept
Azure
AWS
GCP
What GitHub trusts
App Registration + Federated Credential
IAM OIDC Provider
WIF Pool + OIDC Provider
What gets permissions
Service Principal
IAM Role
Service Account
Where permissions assigned
RBAC on resource / subscription
IAM Policy attached to Role
IAM roles bound to Service Account
Where to restrict repo/org
Federated Credential
(claim-based UI / JSON)
Trust Policy Condition
(JSON)
Attribute Condition
(CEL expression)
Impersonation concept
Service Principal token exchange
IAM Role Assume
Service Account Impersonation

Restrict by Scope

ScopeAzureAWSGCP
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')