Azure DevOps is a widely adopted CI/CD platform among enterprise Google Cloud customers and developers. When an Azure Pipelines workflow needs to provision or mutate resources on Google Cloud—such as calling Google Cloud APIs, publishing a container image to Artifact Registry, updating a Google Kubernetes Engine (GKE) cluster, or deploying infrastructure with Terraform—it must first authenticate to Google Cloud.
Traditionally, authenticating from Azure DevOps to Google Cloud required exporting a long-lived JSON service account key and storing it in Azure Pipeline Library secure files or Azure Key Vault, turning an identity management problem into a secrets management problem. Not only did this introduce security risks if the service account key were ever leaked, but it also blocked developers whose organizations enforce Google Cloud security best practices by disabling service account key creation via the constraints/iam.disableServiceAccountKeyCreation organization policy constraint.
With OpenID Connect (OIDC) in Azure DevOps Service Connections and Google Cloud Workload Identity Federation, you can authenticate from Azure Pipelines to Google Cloud without exporting, storing, or rotating a single service account key.
Why Workload Identity Federation for Azure DevOps?
- Fine-grained scoping: Workload Identity Pools and Providers let you define fine-grained attribute mappings and Common Expression Language (CEL) conditions between the Azure DevOps OIDC token and Google Cloud IAM permissions. Whereas an exported JSON service account key is an all-or-nothing bearer credential, Workload Identity Federation restricts authentication to a specific Azure DevOps organization, project, and service connection—and you can enforce pipeline-level approvals and branch checks directly on the Service Connection in Azure DevOps.
- Short-lived credentials: Unlike static JSON service account keys, Workload Identity Federation exchanges the per-job Azure DevOps OIDC JWT (
$idToken) for a short-lived Google Cloud OAuth 2.0 access token via the Google Cloud Security Token Service (STS). By default, these credentials expire after one hour, drastically reducing the blast radius of any compromised build environment. - Minimal management overhead: Static keys must be securely stored, audited, and rotated across every pipeline. With Workload Identity Federation, credentials are minted on demand for each pipeline job, leaving zero static secrets to rotate or manage after the initial setup.
Step 1: Configure a Workload Identity Service Connection in Azure DevOps
Because Azure DevOps issues pipeline OIDC tokens through Azure Resource Manager (ARM) service connections, you create an ARM service connection configured for Workload identity federation.
Tip: Do I need an Azure Subscription if I only deploy to Google Cloud?
No. If you select App registration or managed identity (manual) as the identity type, Azure DevOps immediately generates the OIDC Issuer and Subject identifier on the creation screen without requiring an active Azure Subscription. If your organization already links Azure DevOps to Microsoft Entra ID and an Azure Subscription, you can also use App registration (automatic).
- Sign in to your Azure DevOps organization and select your project.
- In the bottom-left corner, click Project settings → Service connections (under Pipelines).
- Click New service connection, select Azure Resource Manager, and click Next.
- Configure the connection using either the Manual or Automatic workflow:
- Option A — Manual (No Azure Subscription required):
- Under Identity type, select App registration or managed identity (manual).
- Under Credential, select Workload identity federation.
- Enter a Service connection name (for example,
google-cloud-wif). Leave Grant access permission to all pipelines unchecked if you want to authorize only specific pipelines. - Click Next. Copy and save the generated Issuer and Subject identifier displayed under Workload Identity federation details. (To finish saving a manual connection in Azure DevOps without an Azure subscription, enter your Entra Tenant ID and placeholder GUIDs
00000000-0000-0000-0000-000000000000for Subscription ID and Application client ID, then click Save Without Verification).
- Option B — Automatic (Uses Microsoft Entra ID & Azure Subscription):
- Under Identity type, select App registration (automatic) and set Credential to Workload identity federation.
- Select your Subscription (and optional Resource group), enter the Service connection name (
google-cloud-wif), and click Save. - Open the newly created
google-cloud-wifservice connection and copy the Issuer and Subject identifier from the Overview tab.
- Option A — Manual (No Azure Subscription required):
Understanding Azure DevOps Issuer, Subject, and Audience formats
Microsoft has transitioned Azure DevOps Workload Identity Federation service connections from the legacy vstoken.dev.azure.com issuer to the Microsoft Entra v2.0 issuer (login.microsoftonline.com). Depending on when your Azure DevOps organization or service connection was created, you will see one of two OIDC formats:
| Format | OIDC Issuer (iss) |
OIDC Audience (aud) |
OIDC Subject Identifier (sub) |
Length & Google Cloud Impact |
|---|---|---|---|---|
| Microsoft Entra Issuer (Default since Nov 2025) | https://login.microsoftonline.com/<TENANT_ID>/v2.0 |
fb60f99c-7a34-4190-8149-302f77469936 (Entra v2.0 App ID for api://AzureADTokenExchange) |
/eid1/c/pub/t/<TENANT_B64>/a/<APP_B64>/sc/<ORG_ID>/<SC_ID> |
~138 bytes — Exceeds Google Cloud’s 127-byte google.subject limit unless extracted via CEL (assertion.sub.extract('/sc/{sc_id}')). |
| Legacy Azure DevOps Issuer (Deprecated July 2026; retires July 2027) | https://vstoken.dev.azure.com/<ORG_ID> |
api://AzureADTokenExchange |
sc://<ORG_NAME>/<PROJECT_NAME>/<CONNECTION_NAME> |
~40–60 bytes — Fits within 127 bytes directly. |
The gcloud configuration in Step 2 below handles both formats seamlessly using --allowed-audiences="api://AzureADTokenExchange,fb60f99c-7a34-4190-8149-302f77469936" and a single CEL expression.
Step 2: Set Up Workload Identity Federation in Google Cloud
Now configure Google Cloud to trust the OIDC tokens issued by your Azure DevOps service connection.
1. Set environment variables and enable required APIs
Open a terminal with the Google Cloud CLI (gcloud) authenticated to your target project and export the variables below using the Issuer and Subject identifier copied from Azure DevOps. Then enable the IAM, Security Token Service (STS), and Resource Manager APIs:
export PROJECT_ID="your-gcp-project-id"
export PROJECT_NUMBER=$(gcloud projects describe "${PROJECT_ID}" --format="value(projectNumber)")
export POOL_ID="azure-devops-pool"
export PROVIDER_ID="azure-devops-provider"
export SERVICE_ACCOUNT_NAME="my-service-account"
# Paste the exact Issuer and Subject identifier from your Azure DevOps Service Connection:
export ADO_ISSUER_URI="https://login.microsoftonline.com/YOUR_TENANT_ID/v2.0"
export ADO_FULL_SUBJECT="/eid1/c/pub/t/YOUR_TENANT_B64/a/YOUR_APP_B64/sc/YOUR_ORG_ID/YOUR_SC_ID"
# Enable required Google Cloud APIs
gcloud services enable \
iam.googleapis.com \
iamcredentials.googleapis.com \
sts.googleapis.com \
cloudresourcemanager.googleapis.com \
--project="${PROJECT_ID}"
2. Create the Google Cloud Service Account and grant least-privilege roles
Create a dedicated Google Cloud Service Account for your Azure DevOps pipeline and grant it only the IAM roles required for your workload (for example, roles/secretmanager.secretAccessor to read secrets or roles/storage.admin to manage Cloud Storage buckets):
gcloud iam service-accounts create "${SERVICE_ACCOUNT_NAME}" \
--project="${PROJECT_ID}" \
--display-name="Azure DevOps Workload Identity Service Account"
export SA_EMAIL="${SERVICE_ACCOUNT_NAME}@${PROJECT_ID}.iam.gserviceaccount.com"
# Grant resource permissions to the Service Account (follow least privilege)
gcloud projects add-iam-policy-binding "${PROJECT_ID}" \
--member="serviceAccount:${SA_EMAIL}" \
--role="roles/secretmanager.secretAccessor"
3. Create the Workload Identity Pool and OIDC Provider
Google Cloud’s google.subject attribute has a maximum length of 127 bytes. Because Microsoft Entra-backed Azure DevOps subject identifiers (/eid1/c/pub/t/.../sc/<ORG_ID>/<SC_ID>) are 138 bytes long—and because Entra v2.0 OIDC tokens resolve api://AzureADTokenExchange to its first-party Microsoft Application ID GUID (fb60f99c-7a34-4190-8149-302f77469936) in the aud claim—we configure three controls on the OIDC provider:
--allowed-audiences: Includes bothapi://AzureADTokenExchangeandfb60f99c-7a34-4190-8149-302f77469936so Google Cloud STS accepts tokens from both Microsoft Entra v2.0 and legacy Azure DevOps issuers.--attribute-mapping: Uses CELassertion.sub.contains('/sc/') ? assertion.sub.extract('/sc/{sc_id}') : assertion.subto extract the unique<ORG_ID>/<SC_ID>suffix (73 bytes) intogoogle.subjectfor new Entra-backed connections, while passing legacysc://...subjects through unchanged.--attribute-condition: Evaluatesassertion.sub == '${ADO_FULL_SUBJECT}'against the entire un-truncated 138-bytesubclaim before any token exchange is permitted.
Run the following commands to create the Workload Identity Pool and OIDC Provider:
# 1. Create the Workload Identity Pool
gcloud iam workload-identity-pools create "${POOL_ID}" \
--project="${PROJECT_ID}" \
--location="global" \
--display-name="Azure DevOps Pool"
# 2. Create the OIDC Provider with CEL extraction, full subject condition, and both Entra v2.0 + legacy audiences
gcloud iam workload-identity-pools providers create-oidc "${PROVIDER_ID}" \
--project="${PROJECT_ID}" \
--location="global" \
--workload-identity-pool="${POOL_ID}" \
--display-name="Azure DevOps OIDC Provider" \
--issuer-uri="${ADO_ISSUER_URI}" \
--allowed-audiences="api://AzureADTokenExchange,fb60f99c-7a34-4190-8149-302f77469936" \
--attribute-mapping="google.subject=assertion.sub.contains('/sc/') ? assertion.sub.extract('/sc/{sc_id}') : assertion.sub" \
--attribute-condition="assertion.sub == '${ADO_FULL_SUBJECT}'"
4. Allow the Azure DevOps identity to impersonate the Service Account
Compute the mapped subject (<ORG_ID>/<SC_ID> for Microsoft Entra issuers, or sc://... for legacy issuers) and bind roles/iam.workloadIdentityUser on the Service Account using the singular principal:// prefix:
# Compute the mapped google.subject value (<= 127 bytes)
if [[ "${ADO_FULL_SUBJECT}" == *"/sc/"* ]]; then
export MAPPED_SUBJECT="${ADO_FULL_SUBJECT#*/sc/}"
else
export MAPPED_SUBJECT="${ADO_FULL_SUBJECT}"
fi
echo "Mapped google.subject (${#MAPPED_SUBJECT} bytes): ${MAPPED_SUBJECT}"
# Grant roles/iam.workloadIdentityUser on the Service Account to the federated principal
gcloud iam service-accounts add-iam-policy-binding "${SA_EMAIL}" \
--project="${PROJECT_ID}" \
--role="roles/iam.workloadIdentityUser" \
--member="principal://iam.googleapis.com/projects/${PROJECT_NUMBER}/locations/global/workloadIdentityPools/${POOL_ID}/subject/${MAPPED_SUBJECT}"
Important: Always use singular
principal://(notprincipalSet://) when referencing/subject/${MAPPED_SUBJECT}.principalSet://is only valid when matching custom attributes (/attribute.<name>/<value>), groups (/group/<value>), or all pool identities (/*).
Step 3: Authenticate from Azure Pipelines to Google Cloud
With Workload Identity Federation configured in both Azure DevOps and Google Cloud, your Azure Pipelines jobs can now authenticate without any stored keys. Below are two common patterns:
- Option 1 (CLI & REST APIs): Authenticate
gcloudand mint a short-lived OAuth 2.0 access token to call Google Cloud REST APIs directly viacurl. - Option 2 (Application Default Credentials with Client Libraries & Terraform): Export
GOOGLE_APPLICATION_CREDENTIALSso tools like Terraform and Google Cloud Client Libraries authenticate automatically.
Option 1: Authenticating gcloud CLI and calling Google Cloud REST APIs
In the pipeline below, the AzureCLI@2 task with addSpnToEnvironment: true exposes the signed OIDC JWT in $idToken. The script saves $idToken to a local file in $(Pipeline.Workspace), generates an external account credential configuration file with gcloud iam workload-identity-pools create-cred-config, and mints a short-lived OAuth 2.0 access token to invoke the Secret Manager API:
# azure-pipelines.yml
trigger:
- main
pool:
vmImage: 'ubuntu-latest'
variables:
ADO_SERVICE_CONNECTION: 'google-cloud-wif'
GCP_PROJECT_ID: 'your-gcp-project-id'
GCP_PROJECT_NUMBER: '123456789012'
GCP_WIF_POOL: 'azure-devops-pool'
GCP_WIF_PROVIDER: 'azure-devops-provider'
GCP_SERVICE_ACCOUNT: 'my-service-account@your-gcp-project-id.iam.gserviceaccount.com'
GOOGLE_APPLICATION_CREDENTIALS: '$(Pipeline.Workspace)/.gcp_wif_credentials.json'
OIDC_TOKEN_FILE: '$(Pipeline.Workspace)/.ado_oidc_token.jwt'
steps:
- task: AzureCLI@2
displayName: 'Authenticate to Google Cloud via Workload Identity Federation'
inputs:
azureSubscription: $(ADO_SERVICE_CONNECTION)
addSpnToEnvironment: true
scriptType: 'bash'
scriptLocation: 'inlineScript'
inlineScript: |
set -euo pipefail
if [ -z "${idToken:-}" ]; then
echo "ERROR: idToken is empty. Ensure the service connection uses Workload Identity Federation and addSpnToEnvironment is true."
exit 1
fi
printf "%s" "$idToken" > "$(OIDC_TOKEN_FILE)"
chmod 600 "$(OIDC_TOKEN_FILE)"
# Generate the Google Cloud external_account credential configuration file
gcloud iam workload-identity-pools create-cred-config \
"projects/$(GCP_PROJECT_NUMBER)/locations/global/workloadIdentityPools/$(GCP_WIF_POOL)/providers/$(GCP_WIF_PROVIDER)" \
--service-account="$(GCP_SERVICE_ACCOUNT)" \
--credential-source-file="$(OIDC_TOKEN_FILE)" \
--output-file="$(GOOGLE_APPLICATION_CREDENTIALS)"
# Authenticate the gcloud CLI
gcloud auth login --cred-file="$(GOOGLE_APPLICATION_CREDENTIALS)" --quiet
gcloud auth list
# Mint a short-lived OAuth 2.0 access token and call a Google Cloud REST API (e.g., Secret Manager)
ACCESS_TOKEN=$(gcloud auth application-default print-access-token)
curl -sS "https://secretmanager.googleapis.com/v1/projects/$(GCP_PROJECT_ID)/secrets/my-secret/versions/latest:access" \
--header "Authorization: Bearer ${ACCESS_TOKEN}"
Option 2: Authenticating client libraries and Terraform via Application Default Credentials
Because GOOGLE_APPLICATION_CREDENTIALS points to the generated Workload Identity Federation configuration file in $(Pipeline.Workspace), any tool built on Google Cloud Application Default Credentials (ADC)—including Google Cloud client libraries (Go, Python, Java, Node.js, .NET) and Terraform—automatically exchanges the OIDC token in the background with zero code or provider block changes.
For example, define a main.tf file without any hardcoded credentials field:
# main.tf — No "credentials" attribute needed; Terraform automatically uses GOOGLE_APPLICATION_CREDENTIALS
terraform {
required_version = ">= 1.5.0"
required_providers {
google = {
source = "hashicorp/google"
version = ">= 5.0"
}
}
}
provider "google" {
project = var.project_id
region = "us-central1"
}
resource "google_storage_bucket" "demo_bucket" {
name = var.bucket_name
location = "US"
uniform_bucket_level_access = true
}
Then add the Terraform steps after the Workload Identity Federation authentication step in your azure-pipelines.yml:
# Add these steps after the "Authenticate to Google Cloud via Workload Identity Federation" step:
- task: TerraformInstaller@1
displayName: 'Install Terraform'
inputs:
terraformVersion: 'latest'
- script: |
set -euo pipefail
terraform init
terraform plan \
-var="project_id=$(GCP_PROJECT_ID)" \
-var="bucket_name=ado-wif-demo-$(GCP_PROJECT_ID)-$(Build.BuildId)" \
-out=tfplan
terraform apply -auto-approve tfplan
displayName: 'Terraform Init, Plan, and Apply'
Note: Using a Manual Service Connection without an Azure Subscription?
TheAzureCLI@2task always runsaz login --service-principalagainst Microsoft Entra ID before executing yourinlineScript. If you saved your Manual Service Connection with placeholder GUIDs (00000000-0000-0000-0000-000000000000) because you do not have an Azure Subscription, use aBash@3step that requests the OIDC token directly from Azure DevOps’s built-in$(System.OidcRequestUri)REST endpoint:jobs: - job: AuthenticateToGCP uses: endpoints: - $(ADO_SERVICE_CONNECTION) steps: - task: Bash@3 displayName: 'Fetch Azure DevOps OIDC Token & Configure Google Cloud WIF' env: SYSTEM_ACCESSTOKEN: $(System.AccessToken) inputs: targetType: 'inline' script: | set -euo pipefail OIDC_URL="$(System.OidcRequestUri)?api-version=7.1&serviceConnectionId=$(ADO_SERVICE_CONNECTION_ID)" ID_TOKEN=$(curl -sS -X POST -H "Authorization: Bearer ${SYSTEM_ACCESSTOKEN}" \ -H "Content-Type: application/json" -d '{}' "${OIDC_URL}" | jq -r '.oidcToken') printf "%s" "${ID_TOKEN}" > "$(OIDC_TOKEN_FILE)" chmod 600 "$(OIDC_TOKEN_FILE)" gcloud iam workload-identity-pools create-cred-config \ "projects/$(GCP_PROJECT_NUMBER)/locations/global/workloadIdentityPools/$(GCP_WIF_POOL)/providers/$(GCP_WIF_PROVIDER)" \ --service-account="$(GCP_SERVICE_ACCOUNT)" \ --credential-source-file="$(OIDC_TOKEN_FILE)" \ --output-file="$(GOOGLE_APPLICATION_CREDENTIALS)"
Authenticating from Azure DevOps Pipelines to Google Cloud without a long-lived JSON service account key eliminates one of the most common sources of credential leakage in multi-cloud CI/CD environments. By combining Azure DevOps OIDC service connections with Google Cloud Workload Identity Federation and CEL attribute conditions, your pipelines gain short-lived, cryptographically verifiable credentials with zero secret rotation overhead.
To learn more, check out the Workload Identity Federation documentation and best practices for using Workload Identity Federation.
