In the previous article of this multi-cloud identity federation series, we saw how to securely exchange an Azure access token (on the form of a JWT token) with a GCP access token, to access private GCP resource from Azure. You are probably wondering how to do the reverse operation, you are in the right spot !
The big picture
To request Azure APIs from GCP environment, we will need the same two objects as before : a GCP service account and an Azure App Registration. The process is straightforward, because there is no Workload Identity Federation-like product on Azure, everything happens in the App registration configuration :
- Generate a GCP ID token for the source service account, either via the Metadata Server (recommended way for production applications), or via the CLI or IAM REST API (need to have impersonate permissions on the SA)
- Ask the Azure OAuth2 Authorization server to exchange the token for an Azure access token representing the target App registration.
- Enjoy your APIs requests 🙂
1. Generate a GCP ID token
First you need to generate your ID token on behalf of the source service account. Why ID token and not access token ? Because access token on GCP are opaque and are not decodable ! We need a JWT token here : Azure need to be able to check the content of the token to map the App registration on the other side and check if the issuer is Google.
For this step you can either use the Metadata Server if your workload is running in GCP Compute context (recommended way for production applications), or use gcloud
CLI (or any other method available). The result will be a valid identity token. Note that you need to match the audience with the audience you will configure in the next step. The recommended value (according to Azure) is api://AzureADTokenExchange
.
- Using the magic Metadata Server URL :
curl -H "Metadata-Flavor: Google" \
'http://metadata/computeMetadata/v1/instance/service-accounts/default/identity?audience=api://AzureADTokenExchange'
b. Using the gcloud CLI (for testing purpose only)
gcloud auth print-identity-token \
--impersonate-service-account=SOURCE_SERVICE_ACCOUNT_EMAIL \
--audiences=api://AzureADTokenExchange
2. Create a federated credentials in your Azure App Registration
Secondly, configure your target Azure App Registration to allow impersonation from the source GCP service account. This operation happens in the Federated Credential section of your App registration in Azure Active Directory. You will need to specify the trusted issuer, the subject and the audience.
How to get these values ? By decoding the GCP identity token, of course ! Just as usual, you can go to https://jwt.io/ and inspect the content of your token’s payload :
{
"aud": "api://AzureADTokenExchange",
"azp": "106697322240068434726",
"exp": 1676301170,
"iat": 1676297570,
"iss": "https://accounts.google.com",
"sub": "106697322240068434726"
}
-
iss
= https://accounts.google.com is the issuer of the token (Google) -
sub
= subject is the source service account ID. You can also find this info in the GCP console, on the service account page (Unique ID) -
aud
is the default audience value, defined in the first step.
From these informations you can create your federated-credentials settings :
az ad app federated-credential create --id APPLICATION_ID --parameters credential.json
("credential.json" contains the following content)
{
"name": "GcpFederation",
"issuer": "https://accounts.google.com",
"subject": "106697322240068434726",
"description": "Test GCP federation",
"audiences": [
"api://AzureADTokenExchange"
]
}
3. Exchange your GCP ID token for an Azure Access token
Last but not least, you need to exchange your GCP ID token for an Azure Access token to do whatever you want on Azure side : you need to make a request to the Azure Oauth2 Authorization Server by specifying the following parameters :
-
client_id
to your App registration ID, -
scope
to the desired scope depending on the future usage of your token, -
client_assertion_type
fixed tourn:ietf:params:oauth:client-assertion-type:jwt-bearer
for this operation -
grant_type
toclient_credentials
- And the most important: your GCP ID token under
client_assertion
curl GET 'https://login.partner.microsoftonline.cn/TENANT_ID/oauth2/v2.0/token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'client_id=APP_ID' \
--data-urlencode 'scope=https://storage.azure.com/.default' \ # or whatever other scope you might want
--data-urlencode 'client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer' \
--data-urlencode 'client_assertion=GCP_ID_TOKEN' \
--data-urlencode 'grant_type=client_credentials'
# Reponse
{
"token_type": "Bearer",
"expires_in": 3599,
"ext_expires_in": 3599,
"access_token": "eyJ0eXAiO********" # JWT token
}
After decoding the GCP token, if the audience, issuer and subject match, your are good to go for a brand new Azure access token ! You can now access the APIs that match the scope you specified (here the Azure Storage API) :
curl GET 'https://STORAGE_ACCOUNT_NAME.blob.core.windows.net/CONTAINER' \
--header 'x-ms-version: 2020-04-08' \
--header 'Authorization: Bearer AZURE_TOKEN'
# Response 200 OK
No need to store client_id and client_secret in GCP and risk a security breach ! Just use Azure Active Directory federated credentials !
In the next 2 articles we will see concret implementation in Python and Terraform for your production applications.