Accessing Google Cloud APIs from Azure DevOps using Workload Identity Federation

Christoph Grotz
Google Cloud - Community
5 min readMay 3, 2022

--

Photo by Rock’n Roll Monkey on Unsplash

So while exploring how to access Google Cloud APIs from Azure DevOps, I found an approach that I wanted to share with you using Service Principals and Service Connections. In order to implement it yourself, you will need an Azure Subscription. In general, when using Google Cloud Platform (GCP) you want to avoid exporting Service Account Keys. We will use GCP Workload Identity Federation to allow the Azure DevOps Pipeline to access GCP APIs.

My first approach was to use the Job Access Token, that is provided by Azure DevOps for each job. But the issuer is formatted in a non OIDC-compliant way (no leading schema), the public key of the JWT is nowhere to be found, and the issuer is not exporting an openid-configuration under .well-known/openid-configuration (In fact it’s not exposing any OIDC configuration). So this means at the moment, we need to go another route.

In Azure DevOps you can create Service Connections, that allow you to interact with Azure using Service Principals. Since this relies on Azure Active Directory, which is an OIDC-compliant Identity Provider, this approach will work with Workload Identity Federation.

Setup in Azure DevOps and Azure

Please be warned, I’m not an Azure expert. A proper Azure expert might have opinions about Service Principals and how to set them up.

So in order to set everything up, head to your Azure DevOps Project and configure a new Service Connection of type “Azure Resource Manager” (Project Settings > Pipelines > Service Connections > New Service Connection).

Create a Azure Resource Manager type Service Connection

Set this up with either a newly created or already generated Service Principal. I went with the automatic option since it was recommended to me by the wizard.

Next we need to extract a few IDs from the Service Principal of the Service Connection. Select your Service Connection and click “Manage Service Principal”. This will bring you to the Azure Portal.

In order for the JWT to contain an Audience Claim, you will need to set the Application ID URI

You will need to capture the Application (client) ID and the Directory (tenant) ID for the next steps. You should also setup an Application ID URI, so click “Add an Application ID URI”, and click Set on the next screen. In my test, the JWT didn’t contain an Audience claim if there was no Application ID URI set.

Set the Application ID URI, it seems that for this flow the suggested value is okay

Setup in Google Cloud

In GCP we will need to configure the Workload Identity Federation for our Azure Subscription and allow the Service Principal to impersonate a GCP Service Account. You can find a great guide on how to set it up in the GCP documentation, so I’m going to focus on a shortened guide here focusing on what we need to make the Azure DevOps/GCP integration work.

# Create a workload identity pool
gcloud iam workload-identity-pools create azure \
--location="global" \
--display-name="Azure"
# Create a workload identity provider for the Azure Tenant
gcloud iam workload-identity-pools providers create-oidc azure \
--location="global" \
--workload-identity-pool="azure" \
--issuer-uri="https://sts.windows.net/<azure_tenant_id>/" \
--attribute-mapping="google.subject=assertion.appid" \
--allowed-audiences="https://management.core.windows.net/"
# Create a Service Account and allow the Azure Service Principal to access it
gcloud iam service-accounts create SERVICE_ACCOUNT_ID
PRINCIPAL="principal://iam.googleapis.com/projects/<project_number>/locations/global/workloadIdentityPools/<pool_id>/subject/<azure_app_id>"gcloud iam service-accounts add-iam-policy-binding \
SERVICE_ACCOUNT_ID@PROJECT_ID.iam.gserviceaccount.com \
--member="$PRINCIPAL" \
--role="roles/iam.serviceAccountUser"
gcloud iam service-accounts add-iam-policy-binding \
SERVICE_ACCOUNT_ID@PROJECT_ID.iam.gserviceaccount.com \
--member="$PRINCIPAL" \
--role="roles/iam.serviceAccountUser"

Something to point out here: we use the App ID of the Service Principial as subject for the attribute mapping.

Our Azure DevOps Pipeline

Please make sure on the Security configuration of the Service Connection, that the Pipeline can access the Service Connection. Here is a simple example pipeline that uses the AzureCLI task to retrieve an Azure token for the Service Principal and then exchanges it against a Service Account token using Workload Identity Federation. At last an introspection endpoint is called to show that the token works.

name: $(Date:yyyyMMdd)$(Rev:.r)trigger:
- main
stages:
- stage: auth
displayName: "GCP WIF Auth"
jobs:
- job: demo
timeoutInMinutes: 20
steps:
- task: AzureCLI@2
inputs:
azureSubscription: '<service_connection_name>'
scriptType: 'bash'
scriptLocation: 'inlineScript'
inlineScript: |
SUBJECT_TOKEN_TYPE="urn:ietf:params:oauth:token-type:jwt"
SUBJECT_TOKEN=$(az account get-access-token --query accessToken --output tsv)
STS_TOKEN=$(curl --silent -0 -X POST https://sts.googleapis.com/v1/token \
-H 'Content-Type: text/json; charset=utf-8' \
-d @- <<EOF | jq -r .access_token
{
"audience" : "//iam.googleapis.com/projects/<project_number>/locations/global/workloadIdentityPools/<pool_id>/providers/<provider_id>",
"grantType" : "urn:ietf:params:oauth:grant-type:token-exchange",
"requestedTokenType" : "urn:ietf:params:oauth:token-type:access_token",
"scope" : "https://www.googleapis.com/auth/cloud-platform",
"subjectTokenType" : "$SUBJECT_TOKEN_TYPE",
"subjectToken" : "$SUBJECT_TOKEN"
}
EOF)
echo $STS_TOKEN
ACCESS_TOKEN=$(curl --silent -0 -X POST https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/<service_account_email>:generateAccessToken \
-H "Content-Type: text/json; charset=utf-8" \
-H "Authorization: Bearer $STS_TOKEN" \
-d @- <<EOF | jq -r .accessToken
{
"scope": [ "https://www.googleapis.com/auth/cloud-platform" ]
}
EOF)
echo $ACCESS_TOKEN
curl -H "Content-Type: application/x-www-form-urlencoded" -d "access_token=$ACCESS_TOKEN" https://www.googleapis.com/oauth2/v1/tokeninfo
The output of the pipeline should look similar to this (I omitted printing the tokens)

So, now you can access Google Cloud APIs from Azure DevOps without the need to export Service Account Keys. I think this approach is pretty nice, since you can have decent permission controls in Azure DevOps which Pipelines have access to the Service Connection. There is probably some room for improvement, but this gives you a good starting point.

--

--

Christoph Grotz
Google Cloud - Community

I’m a technology enthusiast and focusing on Digital Transformation, Internet of Things and Cloud.