GCP Workload Identity Federation
Configure Google Cloud Platform for secretless authentication from external systems like GitHub Actions, GitLab CI, and Buildkite using OIDC without storing service account keys.
Overview
Workload Identity Federation allows external identity providers to authenticate to GCP by exchanging OIDC tokens for short-lived GCP credentials. This eliminates the need to manage and rotate service account keys.
Authentication Flow
- External system generates OIDC token with identity claims
- Token sent to GCP STS (Security Token Service)
- STS validates token against Workload Identity Provider configuration
- STS returns federated token (valid for ~10 minutes)
- Federated token exchanges for service account token (valid for ~1 hour)
- Service account token used to access GCP resources
Prerequisites
- GCP project with billing enabled
roles/iam.workloadIdentityPoolAdminpermission- Project number (found in project settings)
- APIs enabled: IAM, Security Token Service (STS)
Create Workload Identity Pool
The Workload Identity Pool is a container for external identity providers.
Using gcloud CLI
gcloud iam workload-identity-pools create POOL_ID \
--location="global" \
--description="Identity pool for external CI/CD systems" \
--display-name="CI/CD Identity Pool"
Parameters:
POOL_ID: Immutable identifier (choose carefully)location: Use "global" for worldwide availability
Using Terraform
resource "google_iam_workload_identity_pool" "cicd_pool" {
workload_identity_pool_id = "cicd-pool"
display_name = "CI/CD Identity Pool"
description = "Identity pool for external CI/CD systems"
disabled = false
}
Configure OIDC Provider
GitHub Actions Provider
Using gcloud:
gcloud iam workload-identity-pools providers create-oidc github-provider \
--location="global" \
--workload-identity-pool="cicd-pool" \
--issuer-uri="https://token.actions.githubusercontent.com" \
--attribute-mapping="google.subject=assertion.sub,attribute.actor=assertion.actor,attribute.repository=assertion.repository,attribute.repository_owner=assertion.repository_owner" \
--attribute-condition="assertion.repository_owner=='YOUR_ORG_NAME'"
Using Terraform:
resource "google_iam_workload_identity_pool_provider" "github" {
workload_identity_pool_id = google_iam_workload_identity_pool.cicd_pool.workload_identity_pool_id
workload_identity_pool_provider_id = "github-provider"
display_name = "GitHub Actions Provider"
attribute_condition = "assertion.repository_owner=='YOUR_ORG'"
attribute_mapping = {
"google.subject" = "assertion.sub"
"attribute.actor" = "assertion.actor"
"attribute.repository" = "assertion.repository"
"attribute.repository_owner" = "assertion.repository_owner"
"attribute.repository_id" = "assertion.repository_id"
}
oidc {
issuer_uri = "https://token.actions.githubusercontent.com"
}
}
GitLab CI Provider
Using gcloud:
gcloud iam workload-identity-pools providers create-oidc gitlab-provider \
--location="global" \
--workload-identity-pool="cicd-pool" \
--issuer-uri="https://gitlab.com" \
--attribute-mapping="google.subject=assertion.sub,attribute.project_path=assertion.project_path,attribute.namespace_id=assertion.namespace_id,attribute.ref=assertion.ref" \
--attribute-condition="assertion.namespace_id=='YOUR_GROUP_ID'"
Using Terraform:
resource "google_iam_workload_identity_pool_provider" "gitlab" {
workload_identity_pool_id = google_iam_workload_identity_pool.cicd_pool.workload_identity_pool_id
workload_identity_pool_provider_id = "gitlab-provider"
display_name = "GitLab CI Provider"
attribute_condition = "assertion.namespace_id=='YOUR_GROUP_ID'"
attribute_mapping = {
"google.subject" = "assertion.sub"
"attribute.project_path" = "assertion.project_path"
"attribute.namespace_id" = "assertion.namespace_id"
"attribute.ref" = "assertion.ref"
}
oidc {
issuer_uri = "https://gitlab.com"
}
}
Attribute Mapping
Attribute mappings define how external OIDC token claims map to GCP attributes using Common Expression Language (CEL).
Required Mapping
google.subject- MUST be mapped (principal identifier, max 127 bytes)
Common Patterns
Basic mapping:
google.subject=assertion.sub
Custom subject with multiple claims:
google.subject=assertion.repository + ":" + assertion.ref
Extract specific values:
google.subject=assertion.sub.extract('/repos/{repo}')
Attribute Conditions
Attribute conditions are CEL expressions that restrict which tokens are accepted. Critical for security with multi-tenant providers.
GitHub Examples
Specific organization:
assertion.repository_owner=='myorg'
Specific repository and branch:
assertion.repository_owner=='myorg' &&
assertion.repository=='myorg/myrepo' &&
assertion.ref=='refs/heads/main'
Use immutable IDs (recommended):
assertion.repository_owner_id=='12345678'
GitLab Examples
Specific group:
assertion.namespace_id=='YOUR_GROUP_ID'
Production environment only:
assertion.namespace_id=='YOUR_GROUP_ID' &&
assertion.environment=='production'
Protected branches:
assertion.ref_protected=='true'
Service Account Configuration
Create Service Account
gcloud iam service-accounts create github-actions-sa \
--display-name="GitHub Actions Service Account" \
--description="Service account for GitHub Actions workflows"
Grant Impersonation Permission
For specific repository:
gcloud iam service-accounts add-iam-policy-binding SERVICE_ACCOUNT_EMAIL \
--role="roles/iam.workloadIdentityUser" \
--member="principalSet://iam.googleapis.com/projects/PROJECT_NUMBER/locations/global/workloadIdentityPools/POOL_ID/attribute.repository/ORG/REPO"
For all repositories in organization:
gcloud iam service-accounts add-iam-policy-binding SERVICE_ACCOUNT_EMAIL \
--role="roles/iam.workloadIdentityUser" \
--member="principalSet://iam.googleapis.com/projects/PROJECT_NUMBER/locations/global/workloadIdentityPools/POOL_ID/attribute.repository_owner/ORG"
Using Terraform:
resource "google_service_account" "github_actions" {
account_id = "github-actions-sa"
display_name = "GitHub Actions SA"
}
resource "google_service_account_iam_member" "workload_identity" {
service_account_id = google_service_account.github_actions.name
role = "roles/iam.workloadIdentityUser"
member = "principalSet://iam.googleapis.com/projects/${var.project_number}/locations/global/workloadIdentityPools/${google_iam_workload_identity_pool.cicd_pool.workload_identity_pool_id}/attribute.repository/${var.github_org}/${var.github_repo}"
}
Grant Resource Permissions
# Example: Grant storage permissions
gcloud projects add-iam-policy-binding PROJECT_ID \
--member="serviceAccount:SERVICE_ACCOUNT_EMAIL" \
--role="roles/storage.admin"
Complete Terraform Example
variable "project_id" {
description = "GCP Project ID"
}
variable "project_number" {
description = "GCP Project Number"
}
variable "github_org" {
description = "GitHub Organization"
}
variable "github_repo" {
description = "GitHub Repository"
}
# Workload Identity Pool
resource "google_iam_workload_identity_pool" "github_pool" {
workload_identity_pool_id = "github-pool"
display_name = "GitHub Actions Pool"
description = "Identity pool for GitHub Actions"
}
# OIDC Provider
resource "google_iam_workload_identity_pool_provider" "github_provider" {
workload_identity_pool_id = google_iam_workload_identity_pool.github_pool.workload_identity_pool_id
workload_identity_pool_provider_id = "github-provider"
display_name = "GitHub Provider"
attribute_condition = "assertion.repository_owner == '${var.github_org}'"
attribute_mapping = {
"google.subject" = "assertion.sub"
"attribute.actor" = "assertion.actor"
"attribute.repository" = "assertion.repository"
"attribute.repository_owner" = "assertion.repository_owner"
}
oidc {
issuer_uri = "https://token.actions.githubusercontent.com"
}
}
# Service Account
resource "google_service_account" "github_actions" {
account_id = "github-actions-sa"
display_name = "GitHub Actions SA"
}
# Allow impersonation
resource "google_service_account_iam_member" "workload_identity_user" {
service_account_id = google_service_account.github_actions.name
role = "roles/iam.workloadIdentityUser"
member = "principalSet://iam.googleapis.com/projects/${var.project_number}/locations/global/workloadIdentityPools/${google_iam_workload_identity_pool.github_pool.workload_identity_pool_id}/attribute.repository/${var.github_org}/${var.github_repo}"
}
# Grant permissions
resource "google_project_iam_member" "service_account_permissions" {
project = var.project_id
role = "roles/storage.objectAdmin"
member = "serviceAccount:${google_service_account.github_actions.email}"
}
# Outputs
output "workload_identity_provider" {
value = "projects/${var.project_number}/locations/global/workloadIdentityPools/${google_iam_workload_identity_pool.github_pool.workload_identity_pool_id}/providers/${google_iam_workload_identity_pool_provider.github_provider.workload_identity_pool_provider_id}"
}
output "service_account_email" {
value = google_service_account.github_actions.email
}
Security Best Practices
1. Use Specific Attribute Conditions
Never allow all identities:
# BAD - allows any GitHub repository
attribute_condition = ""
# GOOD - specific organization
attribute_condition = "assertion.repository_owner=='myorg'"
2. Use Immutable IDs
Prefer numeric IDs over names:
repository_idinstead ofrepositorynamespace_idinstead ofnamespace_path
3. Separate Roles by Environment
Create different service accounts for dev/staging/production.
4. Least Privilege Permissions
Grant only necessary permissions:
# Specific bucket access
gsutil iam ch serviceAccount:SA_EMAIL:roles/storage.objectAdmin gs://BUCKET
5. Enable Audit Logging
# Enable IAM audit logs
gcloud projects add-iam-policy-binding PROJECT_ID \
--member="serviceAccount:SA_EMAIL" \
--role="roles/logging.viewer"
Monitor:
- Token exchanges (STS API calls)
- Service account impersonation
- Failed authentication attempts
Troubleshooting
Error: "Failed to generate Google Cloud federated token"
Cause: Attribute condition not met
Solution:
- Verify attribute condition matches token claims
- Use OIDC debugger to inspect token
- Check attribute mappings exist for all conditions
Error: "Unable to acquire impersonated credentials"
Cause: Service account impersonation not configured
Solution:
- Verify IAM binding exists:
gcloud iam service-accounts get-iam-policy SERVICE_ACCOUNT_EMAIL
- Ensure using project NUMBER not ID
- Check principal format is correct
Error: "google.subject exceeds 127 bytes"
Cause: Subject claim too long
Solution: Use extract or shorten:
google.subject=assertion.repository_id + ":" + assertion.ref
Next Steps
- For GitHub Actions: See GitHub Actions Initiator Documentation
- For GitLab CI: See GitLab CI Initiator Documentation
- Integration Guides: Check Integration Guides