William OGOU Cybersecurity Blog

Published

- 16 min read

GCP Workload Identity Federation: GitHub Actions and GitLab Setup

img of GCP Workload Identity Federation: GitHub Actions and GitLab Setup

Most teams do not need another secret in their CI/CD system. They need a reliable way to prove which workload is running, where it came from, and what it is allowed to do.

That is the implementation intent behind Google Cloud Workload Identity Federation (WIF). Instead of storing a long-lived Google service-account key in GitHub, GitLab, AWS, or Azure, an external workload presents a short-lived identity assertion to Google Cloud Security Token Service (STS). Google validates the assertion against a workload identity provider, then issues short-lived credentials that can access selected resources or impersonate a service account.

This guide focuses on the decisions that make WIF secure in production. It uses GitHub Actions and GitLab as the primary CI/CD examples, then shows how the same design applies to AWS and Azure workloads.

The commands use placeholders deliberately. Replace them with your project number, pool, provider, service-account, organization, repository, role, and environment values. Test each trust policy with a non-production service account before granting production access.

Workload Identity Federation architecture

The target architecture

A secure deployment usually contains these components:

  1. External identity provider: GitHub Actions, GitLab, AWS, or Azure issues an identity assertion for the running workload.
  2. Workload identity pool: A Google Cloud boundary for external identities. Use separate pools for materially different environments or trust domains.
  3. Workload identity provider: Defines the issuer, accepted audience, attribute mappings, and CEL attribute condition.
  4. Federated principal: The external identity after Google maps its claims into google.subject, google.groups, or custom attributes.
  5. Resource or service account: The federated principal receives a narrow resource role directly, or receives permission to impersonate a dedicated service account.
  6. Short-lived credentials: STS and, when required, the IAM Service Account Credentials API issue temporary credentials for the job.

The important security boundary is not simply “OIDC enabled.” It is the combination of the provider condition, the principal in the IAM binding, the permissions on the target resource, and the permissions on any impersonated service account.

Workload Identity Federation versus service-account keys

PropertyService-account keyWorkload Identity Federation
Credential lifetimeLong-lived until manually revoked or rotatedShort-lived and issued for a specific exchange
Private key storageRequired in a secret manager, CI/CD secret, workstation, or fileNot required; credential configuration files contain no private key
Trust decisionPossession of the key is usually sufficientClaims, issuer, audience, mappings, and conditions are evaluated
Blast radiusOften the full service-account permission set for as long as the key survivesLimited by token lifetime, trust policy, principal binding, and service-account role design
RotationManual key creation, distribution, and revocationProvider and external-token lifecycle are managed by the identity systems
AttributionKey use is commonly recorded only as the service accountImpersonation audit logs can preserve both the external principal and target service account
Main failure modeKey leakage and secret sprawlOver-broad trust conditions or an over-privileged target identity

WIF is not automatically safe. A provider that trusts every token from a shared issuer, combined with roles/iam.workloadIdentityUser on a powerful service account, can create a different but still serious access path. Keyless authentication removes private-key exposure; it does not replace least privilege, pipeline isolation, or code-review controls.

For the broader consequences of over-permissioned identities, see GCP IAM privilege escalation. For CI/CD-specific pipeline threats, see The OWASP Top 10 CI/CD Security Risks.

One-time Google Cloud setup

The following setup creates a pool, an OIDC provider suitable for GitHub Actions or GitLab, and a deployment service account. The exact mappings and condition must match the provider you are configuring; do not reuse a GitHub condition for GitLab or vice versa.

Enable the required APIs

gcloud services enable \
  iam.googleapis.com \
  iamcredentials.googleapis.com \
  sts.googleapis.com \
  cloudresourcemanager.googleapis.com \
  --project="${PROJECT_ID}"

Use a dedicated project for pools and providers where practical. Record both the project ID and the project number. IAM principal resource names use the project number.

Create a pool

gcloud iam workload-identity-pools create "cicd-pool" \
  --project="${POOL_PROJECT_ID}" \
  --location="global" \
  --display-name="CI/CD external workloads"

POOL_NAME=$(gcloud iam workload-identity-pools describe "cicd-pool" \
  --project="${POOL_PROJECT_ID}" \
  --location="global" \
  --format="value(name)")

echo "${POOL_NAME}"

Consider separate pools for production and non-production when the environments have different administrators, repositories, or risk tolerances. A single pool is not a substitute for conditions that identify the exact workload.

Create a dedicated deployment service account

gcloud iam service-accounts create "cicd-deployer" \
  --project="${TARGET_PROJECT_ID}" \
  --display-name="Dedicated CI/CD deployer"

Grant this account only the permissions required by the deployment. Avoid broad roles such as Owner or Editor. If the deployment needs to write to one bucket, grant the narrowest suitable storage role on that bucket rather than a project-wide role.

Choose direct access or impersonation

Google recommends direct resource access when the target API supports federated principals. Direct access avoids adding another impersonation hop:

federated principal -> specific resource role

Use service-account impersonation when a product or client library requires a service account identity, or when your existing authorization model is deliberately built around dedicated service accounts:

federated principal -> roles/iam.workloadIdentityUser -> service account -> resource role

The impersonation binding must target the exact principal or principal set. Do not grant the role to every identity in a pool unless that is an intentional, reviewed design.

GitHub Actions: a production-oriented setup

GitHub Actions supplies an OIDC token for a workflow job when the workflow requests the id-token: write permission. The token contains claims such as the repository, organization, ref, event, environment, workflow, and actor. Your Google provider should map the claims needed for authorization and restrict the accepted organization and repository.

Create the GitHub OIDC provider

The following mapping is a starting point for GitHub-hosted Actions. Review the claims emitted by your workflow and adapt the mapping to your policy.

gcloud iam workload-identity-pools providers create-oidc "github" \
  --project="${POOL_PROJECT_ID}" \
  --location="global" \
  --workload-identity-pool="cicd-pool" \
  --display-name="GitHub Actions" \
  --issuer-uri="https://token.actions.githubusercontent.com" \
  --attribute-mapping="google.subject=assertion.sub,attribute.repository=assertion.repository,attribute.repository_owner=assertion.repository_owner,attribute.ref=assertion.ref,attribute.environment=assertion.environment,attribute.workflow=assertion.workflow" \
  --attribute-condition="assertion.repository_owner == '${GITHUB_ORG}' && assertion.repository == '${GITHUB_ORG}/${GITHUB_REPOSITORY}' && assertion.ref == 'refs/heads/main'"

This condition is intentionally restrictive. If production deployments use a GitHub environment, add an environment claim and require the expected environment. A branch check alone is not enough when workflows can be triggered by pull requests, tags, reusable workflows, or privileged environment rules.

Use the provider’s full audience as the audience requested by the workflow. A common audience is the provider resource:

//iam.googleapis.com/projects/POOL_PROJECT_NUMBER/locations/global/workloadIdentityPools/cicd-pool/providers/github

The accepted audience and the workflow’s requested audience must agree. If your organization uses a different audience convention, configure it explicitly on both sides.

Allow only the intended GitHub principal to impersonate

For a subject-based binding, use the exact mapped subject. For a repository or custom attribute binding, use a fully qualified principalSet and the project number:

gcloud iam service-accounts add-iam-policy-binding \
  "cicd-deployer@${TARGET_PROJECT_ID}.iam.gserviceaccount.com" \
  --project="${TARGET_PROJECT_ID}" \
  --role="roles/iam.workloadIdentityUser" \
  --member="principalSet://iam.googleapis.com/projects/${POOL_PROJECT_NUMBER}/locations/global/workloadIdentityPools/cicd-pool/attribute.repository/${GITHUB_ORG}%2F${GITHUB_REPOSITORY}"

The exact encoding and principal syntax should be generated or verified with the current Google Cloud documentation. If you need branch or environment-level authorization, enforce those properties in the provider condition rather than relying on a repository-only service-account binding.

Configure the workflow

The official google-github-actions/auth action can use Workload Identity Federation. The workflow still needs least-privilege permissions and should pin third-party actions according to your supply-chain policy.

name: Deploy

on:
  push:
    branches: [main]

permissions:
  contents: read
  id-token: write

jobs:
  deploy:
    runs-on: ubuntu-latest
    environment: production
    steps:
      - uses: actions/checkout@v4

      - id: auth
        uses: google-github-actions/auth@v2
        with:
          workload_identity_provider: projects/${{ vars.GCP_POOL_PROJECT_NUMBER }}/locations/global/workloadIdentityPools/cicd-pool/providers/github
          service_account: cicd-deployer@${{ vars.GCP_TARGET_PROJECT_ID }}.iam.gserviceaccount.com

      - name: Deploy
        run: gcloud run deploy example-service --source . --project "${{ vars.GCP_TARGET_PROJECT_ID }}"

The action exchanges the GitHub OIDC assertion for short-lived Google credentials. Do not add a JSON service-account key as a fallback. If the job fails, diagnose the issuer, audience, mapped claims, condition, principal binding, and target service-account permissions separately.

GitHub-specific security decisions

  • Require a specific organization, not just token.actions.githubusercontent.com.
  • Restrict the repository to the intended ORG/REPOSITORY.
  • Restrict production to a protected branch, tag, or environment.
  • Use GitHub environment protection rules for approvals and deployment branches.
  • Treat reusable workflows and pull-request workflows as separate trust decisions.
  • Do not let untrusted fork code run in a job that has id-token: write and production permissions.
  • Pin third-party actions to trusted commit SHAs when your supply-chain policy requires it.
  • Use a separate service account for each application or deployment boundary.

GitLab CI/CD: issuer, audience, and project claims

GitLab SaaS jobs can request OIDC ID tokens through the id_tokens keyword. GitLab tokens expose claims such as project_path, namespace_path, ref, ref_type, environment, and sub. Map the claims that express your authorization boundary and restrict the GitLab group or project in the provider condition.

Create the GitLab OIDC provider

gcloud iam workload-identity-pools providers create-oidc "gitlab" \
  --project="${POOL_PROJECT_ID}" \
  --location="global" \
  --workload-identity-pool="cicd-pool" \
  --display-name="GitLab SaaS" \
  --issuer-uri="https://gitlab.com" \
  --attribute-mapping="google.subject=assertion.sub,attribute.project_path=assertion.project_path,attribute.namespace_path=assertion.namespace_path,attribute.ref=assertion.ref,attribute.ref_type=assertion.ref_type,attribute.environment=assertion.environment" \
  --attribute-condition="assertion.namespace_path == '${GITLAB_GROUP}' && assertion.project_path == '${GITLAB_PROJECT_PATH}' && assertion.ref == 'main' && assertion.ref_type == 'branch'"

GitLab uses a shared issuer URL across organizations. Trusting only https://gitlab.com is therefore insufficient. The condition must identify your group and project, and production should normally restrict the branch and environment as well.

Bind the GitLab principal

gcloud iam service-accounts add-iam-policy-binding \
  "cicd-deployer@${TARGET_PROJECT_ID}.iam.gserviceaccount.com" \
  --project="${TARGET_PROJECT_ID}" \
  --role="roles/iam.workloadIdentityUser" \
  --member="principalSet://iam.googleapis.com/projects/${POOL_PROJECT_NUMBER}/locations/global/workloadIdentityPools/cicd-pool/attribute.project_path/${GITLAB_PROJECT_PATH}"

Keep the provider condition as the primary branch, project, and environment guard. The IAM binding determines who may impersonate the account; it should not be broader than the provider’s intended trust boundary.

Configure .gitlab-ci.yml

deploy:
  image: google/cloud-sdk:slim
  stage: deploy
  rules:
    - if: '$CI_COMMIT_BRANCH == "main" && $CI_ENVIRONMENT_NAME == "production"'
  environment: production
  id_tokens:
    GITLAB_OIDC_TOKEN:
      aud: 'https://iam.googleapis.com/projects/POOL_PROJECT_NUMBER/locations/global/workloadIdentityPools/cicd-pool/providers/gitlab'
  script:
    - printf '%s' "$GITLAB_OIDC_TOKEN" > "$CI_PROJECT_DIR/gitlab-oidc-token.jwt"
    - |
      cat > "$CI_PROJECT_DIR/google-wif.json" <<EOF
      {
        "type": "external_account",
        "audience": "//iam.googleapis.com/projects/POOL_PROJECT_NUMBER/locations/global/workloadIdentityPools/cicd-pool/providers/gitlab",
        "subject_token_type": "urn:ietf:params:oauth:token-type:jwt",
        "token_url": "https://sts.googleapis.com/v1/token",
        "credential_source": {
          "file": "$CI_PROJECT_DIR/gitlab-oidc-token.jwt"
        },
        "service_account_impersonation_url": "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/cicd-deployer@TARGET_PROJECT_ID.iam.gserviceaccount.com:generateAccessToken"
      }
      EOF
    - export GOOGLE_APPLICATION_CREDENTIALS="$CI_PROJECT_DIR/google-wif.json"
    - gcloud auth login --cred-file="$GOOGLE_APPLICATION_CREDENTIALS"
    - gcloud projects describe "$TARGET_PROJECT_ID"

The credential configuration file is not a service-account key: it contains the provider and token-exchange instructions, not a private key. Protect the temporary token file from logs and from later pipeline steps that do not need it, and delete temporary files when the job completes.

AWS: federation from EC2 roles

AWS federation is not identical to GitHub or GitLab OIDC. For common EC2 workloads, Google can validate AWS temporary credentials by using a signed AWS GetCallerIdentity request. The credentials normally come from an EC2 instance profile or another AWS IAM role; the AWS secret key is not sent to Google as a reusable Google credential.

A typical AWS provider uses AWS mappings such as:

google.subject=assertion.arn
attribute.account=assertion.account
attribute.aws_role=assertion.arn.extract('assumed-role/{role_name}/')

Restrict the provider to the expected AWS account and role:

assertion.arn.startsWith('arn:aws:sts::AWS_ACCOUNT_ID:assumed-role/EC2_ROLE_NAME/')

Create a credential configuration using the Google Cloud CLI rather than hand-writing the AWS subject-token format:

gcloud iam workload-identity-pools create-cred-config \
  "projects/${POOL_PROJECT_NUMBER}/locations/global/workloadIdentityPools/cicd-pool/providers/aws" \
  --service-account="cicd-deployer@${TARGET_PROJECT_ID}.iam.gserviceaccount.com" \
  --aws \
  --output-file="google-wif-aws.json"

export GOOGLE_APPLICATION_CREDENTIALS="$PWD/google-wif-aws.json"
gcloud auth application-default print-access-token

On AWS, protect the instance role and metadata service just as carefully as the Google-side provider. A compromised EC2 workload can still use its valid AWS role to obtain Google credentials if the provider trusts that role.

Azure: federation from managed identities

Azure VM workloads commonly use a system-assigned or user-assigned managed identity to obtain an Azure access token. Google Cloud exchanges that Azure identity assertion for short-lived Google credentials after the Azure tenant, issuer, audience, and mapped subject satisfy the provider configuration.

A provider-specific mapping and condition should identify the expected Microsoft Entra tenant and managed identity. For example, map a stable identity claim to google.subject and restrict the tenant or application identifier in the provider condition:

google.subject=assertion.sub
attribute.tenant=assertion.tid
attribute.application=assertion.appid

assertion.tid == 'AZURE_TENANT_ID' && assertion.appid == 'AZURE_MANAGED_IDENTITY_APP_ID'

Generate the credential configuration with the Google Cloud CLI and use the Azure subject-token source supported by the current Google Cloud documentation:

gcloud iam workload-identity-pools create-cred-config \
  "projects/${POOL_PROJECT_NUMBER}/locations/global/workloadIdentityPools/cicd-pool/providers/azure" \
  --service-account="cicd-deployer@${TARGET_PROJECT_ID}.iam.gserviceaccount.com" \
  --azure \
  --app-uri="https://app.iamcredentials.googleapis.com/" \
  --output-file="google-wif-azure.json"

export GOOGLE_APPLICATION_CREDENTIALS="$PWD/google-wif-azure.json"
gcloud auth application-default print-access-token

Use separate managed identities for separate applications or environments. Do not treat “running on an Azure VM” as an authorization boundary: the Google provider must still distinguish the intended tenant and identity.

Common trust-condition mistakes

1. Trusting the issuer but not the tenant or organization

GitHub and GitLab use shared public issuer URLs. The issuer proves where a token was issued, not that it belongs to your organization. Require the organization, group, project, or repository claim in the provider condition.

2. Trusting an entire repository

A repository condition can still be too broad. Ask whether every branch, tag, workflow, event, and environment in that repository should impersonate the production service account. Usually the answer is no. Add protected branch, environment, workflow, or event restrictions where the provider exposes those claims.

3. Using a mutable or ambiguous subject

google.subject must identify one external identity uniquely and is limited to 127 characters. Avoid display names, branch names alone, or values that can be reused across providers. Prefer a stable provider subject combined with mapped attributes and conditions.

4. Referencing an attribute that was never mapped

Every custom attribute used by a CEL condition or IAM principal set must be present in the provider’s attribute mapping. A condition such as attribute.project_path == ... cannot work if attribute.project_path was not mapped from the assertion.

5. Getting the audience wrong

The external token’s audience, the provider’s accepted audience, and the credential configuration’s audience must agree. Audience errors are often reported as generic token-exchange failures, so verify the complete provider resource name and whether your configuration expects the https: prefix.

6. Mixing project ID and project number

Google resource names for workload identity principals use the numeric project number. The project ID is used in many gcloud flags and service-account email addresses. Record both values and label shell variables explicitly.

7. Granting access to every identity in a pool

A pool is a trust container, not an authorization role. Avoid bindings such as an unqualified pool-wide principalSet for sensitive resources. Bind the exact subject or a narrowly scoped custom attribute.

8. Confusing authentication with authorization

A valid token only proves that the provider accepted the external identity. The target IAM role still determines what the workload can do. Review both the resource role and any roles/iam.workloadIdentityUser binding.

9. Giving WIF access to an over-privileged service account

Replacing a key with WIF does not make roles/owner, broad IAM administration, or unrestricted service-account impersonation safe. Use a dedicated service account and review permissions such as iam.serviceAccounts.actAs, iam.serviceAccounts.getAccessToken, iam.serviceAccountKeys.create, and setIamPolicy.

10. Assuming WIF prevents a compromised pipeline

WIF prevents a stolen static key from being reused indefinitely, but a malicious step can request a valid token while the job is running. Protect workflow files, review pull requests, isolate production environments, pin actions and dependencies, and restrict the claims accepted by Google.

A practical debugging checklist

SymptomChecks
invalid audienceCompare the workflow aud, provider audience, credential config, and provider resource name.
attribute condition failedDecode a test token safely, confirm claim names and values, and verify every condition field is mapped.
principal not foundUse the pool project number, correct pool/provider IDs, URL-encode special characters, and verify the mapped subject or attribute.
STS succeeds but API calls failReview the direct resource role or the impersonated service account’s roles.
Impersonation is deniedCheck the roles/iam.workloadIdentityUser binding on the target service account and the exact external principal.
Unexpected production accessAudit branch, tag, environment, workflow, repository, group, tenant, and pool-wide bindings.
Logs show only the service accountConfirm impersonation and Data Access audit logging; direct key authentication does not preserve the same initiating identity chain.

Test the complete path with a harmless read-only permission before adding deployment permissions. Then verify a negative case: a different repository, branch, environment, AWS role, or Azure identity should fail the token exchange or authorization step.

WIF and the rest of the security program

WIF should be one control in a larger identity and software supply-chain design:

  • Use dedicated service accounts and narrow resource-level roles.
  • Disable service-account key creation where organization policy allows it.
  • Monitor STS, IAM Credentials, and target-service audit logs.
  • Review who can modify workflow files, pipeline definitions, providers, service accounts, and IAM policies.
  • Protect production environments with approvals and branch restrictions.
  • Use IAM Deny policies and organization policies as defense-in-depth controls.
  • Treat build runners, dependencies, actions, and container images as part of the deployment trust boundary.

For an example of what an over-permissioned deployment identity can expose, read GCP Storage Security: Guide to Preventing Silent Takeovers. For broader pipeline and dependency controls, see Defending the Software Supply Chain and The OWASP Top 10 CI/CD Security Risks.

Conclusion

The practical case for Workload Identity Federation is simple: external workloads should prove their identity when they run, receive only short-lived credentials, and access only the Google Cloud resources they need.

The hard part is designing the trust boundary. GitHub and GitLab require organization, repository, ref, workflow, and environment thinking. AWS requires careful control of the account and role behind signed GetCallerIdentity requests. Azure requires stable tenant and managed-identity claims. Every provider requires narrow IAM bindings and a service account that is no more powerful than the deployment requires.

If your current design is “store a service-account JSON key in CI/CD,” begin by inventorying what that key can do. Create a dedicated pool, provider, and service account. Test a restrictive condition. Migrate one workload. Revoke the old key only after logs confirm that the new path works.

Keyless authentication is the starting point. Correct claim design, least privilege, pipeline integrity, and continuous audit are what make it secure.

To further enhance your cloud security and implement Zero Trust, contact me on LinkedIn Profile or [email protected].

Frequently Asked Questions (FAQ)

Is Workload Identity Federation always safer than a service-account key?

WIF removes the long-lived private key and limits credentials to a short-lived exchange, but it is not automatically safe. Over-broad provider conditions, pool-wide IAM bindings, compromised workflows, and over-privileged service accounts can still create serious risk.

Should GitHub Actions use a branch condition or an environment condition?

Use the strongest boundary that matches your deployment model. A protected production environment can provide an additional approval and branch-control layer; the Google provider should still restrict the repository and the claims that identify the intended production workload.

What is the main difference between GitHub Actions and GitLab WIF setup?

Both use OIDC assertions, but their issuers, claim names, audiences, and subject formats differ. GitHub commonly uses repository, ref, workflow, and environment claims, while GitLab commonly uses project_path, namespace_path, ref, ref_type, and environment claims. Do not copy mappings or conditions between them without validating the token claims.

Do AWS and Azure workloads use the same OIDC flow as GitHub?

Not necessarily. Google Cloud supports AWS-specific federation using signed GetCallerIdentity requests and Azure federation using managed-identity tokens. Use the provider-specific Google Cloud credential configuration rather than assuming every external cloud produces a GitHub-style JWT.

Does WIF remove the need for service accounts?

No. WIF can grant a federated principal direct access to supported resources, or it can let that principal impersonate a service account. Service accounts remain useful as dedicated, auditable authorization identities, but they should not be backed by long-lived keys.

Can a compromised CI/CD job still abuse WIF?

Yes. A running job that satisfies the provider condition may obtain a valid short-lived token. Reduce the impact with protected environments, narrow claims and roles, separate deployment identities, action and dependency pinning, review controls, short token lifetimes, and audit monitoring.

Official resources


William OGOU

William OGOU

Need help implementing Zero Trust strategy or securing your cloud infrastructure? I help organizations build resilient, compliance-ready security architectures.