Skip to content

Instantly share code, notes, and snippets.

@wvanderdeijl
Last active August 30, 2023 14:44
Show Gist options
  • Save wvanderdeijl/d27c6ab048744f3cc95eb1e36359e12d to your computer and use it in GitHub Desktop.
Save wvanderdeijl/d27c6ab048744f3cc95eb1e36359e12d to your computer and use it in GitHub Desktop.
Google Cloud Service Account to Azure Federation

Google Cloud Service Account to Azure Federation

  • Create an azure app that we will use for federation
    az ad app create --display-name my-sample-app \
        --oauth2-allow-implicit-flow false
  • Note the objectId of the created app
    OBJECT_ID=32f3c6d0-f8a0-42c2-8ae3-962d79e4cb14
  • Get the appId (also known as clientId) for the create app
    CLIENT_ID=$(az ad app show --id 4ccb1668-07cd-4570-ad17-dd7182c1e269 --query 'appId' --output tsv)
  • Create a service principal for the app
    az ad sp create --id $CLIENT_ID
  • Allow the service principal what it has to do (this example let's it use storage). Note that this can take 5+ minutes before it is active :-(
    az role assignment create \
        --role "Storage Blob Data Contributor" \
        --assignee $CLIENT_ID \
        --scope "/subscriptions/[SUBSCRIPTION_GUID]/resourceGroups/[RESOURCE_GROUP_NAME]/providers/Microsoft.Storage/storageAccounts/[STORAGE_ACCOUNT_NAME]"
  • Allow federation to the service principal from google service account (using the numerical id of the google service account). We are using jq to build the request payload so it does al the escaping for us. Recently the microsoft documentation has been updated with similar instructions for the console or cli.
    GOOGLE_SERVICE_ACCOUNT_ID=109999999999999999999
    jq -n \
        --arg googleServiceAccountId $GOOGLE_SERVICE_ACCOUNT_ID \
        '{
            name: "my-federated-credentials",
            issuer: "https://accounts.google.com",
            subject: $googleServiceAccountId,
            description: "Test federation from a google service account",
            audiences: ["api://AzureADTokenExchange"]
        }' | az rest --method POST \
        --uri "https://graph.microsoft.com/beta/applications/$OBJECT_ID/federatedIdentityCredentials" \
        --body @-
  • Retrieve details about the created federation
    az rest -m GET -u "https://graph.microsoft.com/beta/applications/$OBJECT_ID/federatedIdentityCredentials"
  • See the other file in this Gist for an example how to use the federated identity from Google Cloud to write to Azure storage

If you ever want to remove the federated identity you can use the following commands:

FEDERATED_ID=$(az rest -m GET -u "https://graph.microsoft.com/beta/applications/$OBJECT_ID/federatedIdentityCredentials" --query 'value[0].id' --output tsv)
az rest -m DELETE  -u "https://graph.microsoft.com/beta/applications/$OBJECT_ID/federatedIdentityCredentials/$FEDERATED_ID"
import type { TokenCredential } from '@azure/core-auth';
import { BlobServiceClient, RestError } from '@azure/storage-blob';
import axios from 'axios';
import { GoogleAuth } from 'google-auth-library';
import * as querystring from 'querystring';
const GOOGLE_SERVICE_ACCOUNT = 'my-google-service-account@my-project.iam.gserviceaccount.com';
const AZURE_TENANT_ID = '9834004a-????-????-????-????????????';
const AZURE_CLIENT_ID = 'e4acb7e3-27f3-4604-b6a1-17da84146c54'; // the appId of the azure app (service principal)
const AZURE_STORAGE_ACCOUNT = 'my-storage-account';
const AZURE_STORAGE_CONTAINER = 'my-container';
(async () => {
// Acquire source credentials:
const auth = new GoogleAuth(); // uses default credentials when running in google cloud
const gClient = await auth.getClient();
// FIXME: `@azure/identity` now has support for this. See example at
// https://blog.identitydigest.com/azuread-federate-gcp/
const credential: TokenCredential = {
async getToken(scope, options) {
const gToken = await gClient.request<{ token: string }>({
url: `https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/${encodeURIComponent(
GOOGLE_SERVICE_ACCOUNT,
)}:generateIdToken`,
method: 'POST',
data: { audience: 'api://AzureADTokenExchange' },
});
const tenant = options?.tenantId ?? AZURE_TENANT_ID;
const msToken = await axios.post<{ token_type: 'Bearer'; expires_in: number; ext_expires_in: number; access_token: string }>(
`https://login.microsoftonline.com/${tenant}/oauth2/v2.0/token`,
querystring.stringify({
client_assertion_type: 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer',
client_id: AZURE_CLIENT_ID,
client_assertion: gToken.data.token,
grant_type: 'client_credentials',
scope,
}),
);
return {
token: msToken.data.access_token,
expiresOnTimestamp: Date.now() + msToken.data.expires_in * 1000,
};
},
};
const client = new BlobServiceClient(`https://${AZURE_STORAGE_ACCOUNT}.blob.core.windows.net`, credential);
const containerClient = client.getContainerClient(AZURE_STORAGE_CONTAINER);
const blobClient = containerClient.getBlockBlobClient(`${Date.now()}.json`);
const data = JSON.stringify({ foo: 'bar', now: Date.now() });
await blobClient.upload(data, data.length);
})().catch(e => {
console.log(e);
process.exit(1);
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment