From Azure to GCP: A Practical Guide to Secure, Passwordless Authentication with AKS and Workload…
In the modern multi-cloud landscape, the ability to securely access resources across different cloud providers is no longer a luxury —…
From Azure to GCP: A Practical Guide to Secure, Passwordless Authentication with AKS and Workload Identity Federation
In the modern multi-cloud landscape, the ability to securely access resources across different cloud providers is no longer a luxury — it’s a necessity. However, the age-old challenge of managing credentials remains a significant security and operational burden. How do you grant an application running in Azure Kubernetes Service (AKS) access to a Google Cloud resource like a Storage bucket without storing long-lived service account keys in your cluster? The answer lies in a powerful, modern authentication pattern: Workload Identity Federation.
This blog post is a deep dive into a passwordless, keyless future. We will walk you through the end-to-end process of configuring Azure AKS Workload Identity to federate with GCP’s Workload Identity Federation. This allows a pod running in your AKS cluster to securely authenticate and consume GCP resources using short-lived, dynamically generated credentials, completely eliminating the need for storing and rotating static secrets.
We will not only explain the core concepts but also provide a practical, hands-on guide using a set of comprehensive shell scripts that can be found on **Github**. By the end of this article, you will understand:
- Core Concepts: What Azure AKS Workload Identity and GCP Workload Identity Federation are, and how they leverage OpenID Connect (OIDC) to establish trust.
- The End-to-End Flow: The complete token exchange journey, from a Kubernetes service account to a GCP access token.
- Practical Implementation: A step-by-step breakdown of scripts that provision all the necessary infrastructure in both Azure and GCP.
The Problem with Secrets
For years, the standard practice for cross-service authentication involved generating a static credential — a service account key, a client secret, or an API key — and storing it where the application could access it. In Kubernetes, this usually meant creating a Secret.
This approach is fraught with problems:
- Security Risk: A long-lived credential, if leaked, provides a persistent entry point for an attacker. The blast radius of a compromised secret can be enormous.
- Operational Overhead: Secrets need to be managed. This includes secure generation, storage, distribution, and, most importantly, rotation. Manual rotation is error-prone and often neglected. Automated rotation adds significant complexity to your CI/CD and operational workflows.
- Lack of Granularity: Often, a single powerful secret is shared among multiple applications, violating the principle of least privilege.
Workload Identity Federation provides a robust and elegant solution to all these problems by replacing static secrets with a trust relationship based on verifiable, short-lived tokens.
Core Concept 1: Azure AKS Workload Identity
Before Workload Identity, the go-to solution in Azure was Pod Identity. While functional, it had its complexities. AKS Workload Identity is the evolution of this concept, integrating more deeply and natively with Kubernetes.
It allows you to assign an Entra ID identity (either a Managed Identity or an App Registration) to a Kubernetes Service Account. A pod that uses this Service Account can then exchange its own Kubernetes-issued token for an Entra ID access token.
Here are the key components:
- AKS OIDC Issuer: When you enable Workload Identity on an AKS cluster (
--enable-oidc-issuer) and (--enable-workload-identity), the cluster exposes a public OpenID Connect (OIDC) discovery endpoint. This endpoint allows external systems, like Entra ID, to verify the authenticity of tokens issued by the Kubernetes API server. - Kubernetes Service Account (SA): This is the standard Kubernetes identity for a pod. With Workload Identity, we annotate this SA with the details of the Entra ID identity it should impersonate.
- Azure Managed Identity or App Registration: This is the identity in Entra ID that you want your pod to assume. It’s this identity that you will grant permissions to access other Azure resources (or, in our case, federate with GCP).
- Federated Identity Credential: This is the crucial link. It’s a configuration in Entra ID that establishes a trust relationship between the Entra ID identity and the Kubernetes Service Account. It essentially tells Entra ID: “I trust tokens issued by this specific Kubernetes OIDC issuer, for this specific namespace and service account. If you see such a token, please exchange it for one of my own.”
- Mutating Admission Webhook: When Workload Identity is enabled, AKS sets up a webhook that automatically modifies pods. If a pod uses a service account configured for federation, the webhook injects environment variables (
AZURE_CLIENT_ID,AZURE_TENANT_ID,AZURE_FEDERATED_TOKEN_FILE) and mounts a projected service account token volume. This token is a specially crafted JWT (JSON Web Token) with an audience claim that Entra ID will accept.
The Flow within Azure:
- A pod starts, using a specially annotated Service Account.
- The AKS webhook injects a projected token volume and environment variables into the pod.
- The application inside the pod reads the projected Kubernetes token from the mounted file path.
- The application uses an Azure Identity SDK (or
curl, as we'll see) to present this Kubernetes token to Entra ID. - Entra ID uses the OIDC issuer URL to retrieve the public keys from the AKS cluster and verify the token’s signature.
- It checks the
issuer,subject, andaudienceclaims against its configured Federated Identity Credential. - If everything matches, Entra ID issues a standard Entra ID access token, granting the pod the permissions of the associated Managed Identity.
Core Concept 2: GCP Workload Identity Federation
GCP Workload Identity Federation serves a similar purpose: it allows external, third-party identities to authenticate to GCP and access resources directly, without needing to impersonate a GCP Service Account or use a GCP service account key.
The key components in GCP are:
- Workload Identity Pool: A container for managing external identity providers. You can think of it as a logical grouping of trust relationships.
- Workload Identity Provider: This is where you configure the details of the external Identity Provider (IdP) you want to trust. For our use case, the IdP is Entra ID. We configure the provider with the issuer URI of our Entra ID tenant (
https://sts.windows.net/<YOUR_AZURE_TENANT_ID>/). - Attribute Mapping: This is a critical piece of the puzzle. When GCP receives a token from an external IdP (like Entra ID), it needs to know which claim in that token uniquely identifies the caller. The attribute mapping tells GCP how to extract this information. For example, we map the
sub(subject) claim from the Entra ID token to GCP's owngoogle.subject. - IAM Policy Binding: This is where you grant permissions. Instead of assigning a role to a Google user or a GCP service account, you assign it to the federated identity. The format for this principal is special:
principal://iam.googleapis.com/projects/<PROJECT_NUMBER>/locations/global/workloadIdentityPools/<POOL_ID>/subject/<UNIQUE_ID_FROM_IDP>.
The Big Picture: Chaining the Trust
Now, let’s combine these two concepts to achieve our goal. We want a pod in AKS to talk to a GCP service. We can achieve this by creating a chain of trust.

- [Step 1: AKS] A pod is scheduled in AKS. The Workload Identity webhook injects a projected JWT, signed by the cluster’s OIDC issuer.
- [Step 2: AKS to Entra ID] The application in the pod uses this K8s token to call the Entra ID token endpoint using the App Registration URI as the OAuth scope. This is the first token exchange.
- [Entra ID] Entra ID validates the K8s token against its configured Federated Credential. If valid, it returns an Entra ID access token. This token’s
sub(subject) claim contains the unique Object ID of the Azure Managed Identity and anaud(audience) claim matching the App Registration URI. - [Step 3: AKS to GCP STS] The application now takes this Entra ID token and presents it to the GCP Security Token Service (STS). This is the second token exchange.
- [GCP STS] GCP’s STS validates the Entra ID token. It checks the issuer (
https://sts.windows.net/...), the audience, and the signature. It then uses the configured attribute mapping to extract thesubclaim. - [GCP STS -> GCP Resource] If the token is valid, the GCP STS mints a short-lived GCP access token.
- [AKS -> GCP Resource] The application finally uses this GCP access token in the
Authorization: Bearerheader to make authenticated calls to the target GCP API (e.g., listing Cloud Storage buckets). The GCP IAM policy binding (not included in this diagram) ensures that this principal has the necessary permissions.
This entire chain reaction happens in milliseconds, and at no point is a static secret stored on disk or in a Kubernetes Secret object.
Part 1: Setting the Stage in Azure (azure-aks-setup.sh)
Now let’s translate theory into practice. The first script, [azure-aks-setup.sh](https://github.com/jeansson-01/azure-aks-to-gcp-wlif/blob/main/azure-aks-setup.sh), configures the entire Azure side of the equation.
You can find the full script here: **azure-aks-setup.sh**. Let’s break down the key phases.
Phase 1 & 2: Create the App Registration & Service Principal
Even though we will primarily use a Managed Identity for the pod, creating an App Registration is crucial because it gives us the Application ID URI. This URI acts as a unique, stable identifier for our application's "audience," which is a required parameter for the OIDC token exchange in both Azure and GCP.
# From azure-aks-setup.sh
APP_REG_NAME="jeansson-GCP-Federation-App"
APP_REG_JSON=$(az ad app create --display-name "$APP_REG_NAME")
APP_CLIENT_ID=$(echo "$APP_REG_JSON" | jq -r .appId)
# ... create service principal ...
APP_ID_URI="api://$APP_CLIENT_ID"
az ad app update \
--id "$APP_CLIENT_ID" \
--identifier-uris "$APP_ID_URI"
This section creates the App Registration and sets its Identifier URI to api://<CLIENT_ID>. This APP_ID_URI will be used later in the GCP setup.
Phase 7: AKS Cluster Creation
This is the core command for provisioning the cluster. The magic flags are --enable-oidc-issuer and --enable-workload-identity. These automate the setup of the OIDC discovery endpoint and the mutating admission webhook.
# From azure-aks-setup.sh
az aks create \
--resource-group "$RG_NAME" \
--name "$CLUSTER_NAME" \
# ... other networking and identity flags ...
--enable-oidc-issuer \
--enable-workload-identity \
--generate-ssh-keys
Phase 8: Configuring the Workload Identity Federation
This is the most critical part of the Azure setup. Here, we create the user-assigned Managed Identity that our pod will use and then establish the trust link to our Kubernetes Service Account.
First, create the identity:
# From azure-aks-setup.sh
APP_IDENTITY_NAME="jeansson-App-Workload-Identity"
APP_IDENTITY_CLIENT_ID=$(az identity create \
--name "$APP_IDENTITY_NAME" \
--resource-group "$RG_NAME" \
--query "clientId" -o tsv)
Next, get the AKS OIDC issuer URL:
# From azure-aks-setup.sh
AKS_OIDC_ISSUER=$(az aks show \
--name "$CLUSTER_NAME" \
--resource-group "$RG_NAME" \
--query "oidcIssuerProfile.issuerUrl" -o tsv)
Finally, create the federated credential. This command tells the APP_IDENTITY_NAME identity to trust tokens from our AKS cluster's OIDC issuer, but only if they are for the service account matching your name( jeansson-app-sa-mid ) in the specified (default ) namespace.
# From azure-aks-setup.sh
az identity federated-credential create \
--name "my-app-federation" \
--identity-name "$APP_IDENTITY_NAME" \
--resource-group "$RG_NAME" \
--issuer "$AKS_OIDC_ISSUER" \
--subject "system:serviceaccount:$K8S_NAMESPACE:$K8S_SERVICE_ACCOUNT_MID" \
--audience "api://AzureADTokenExchange"
Phase 9: Deploying the Authenticated Pod
To use this federation, we create a Kubernetes Service Account and annotate it with the Client ID of the Managed Identity we created.
# From azure-aks-setup.sh
apiVersion: v1
kind: ServiceAccount
metadata:
name: jeansson-app-sa-mid
namespace: default
annotations:
azure.workload.identity/client-id: "YOUR_APP_IDENTITY_CLIENT_ID"
Then we deploy a pod that uses this service account and adds the azure.workload.identity/use: "true" label to trigger the webhook.
# From azure-aks-setup.sh
apiVersion: v1
kind: Pod
metadata:
name: workload-identity-test-managed-id
namespace: default
labels:
azure.workload.identity/use: "true"
spec:
serviceAccountName: jeansson-app-sa-mid
# ...
When this pod starts, the webhook will automatically inject the AZURE_* environment variables and the projected token volume, setting the stage for the token exchange.
Part 2: Establishing Trust in GCP (gcp-wlif-setup-autogen.sh)
Now that Azure is ready, we need to configure GCP to trust our Entra ID tenant. The [**gcp-wlif-setup-autogen.sh](https://github.com/jeansson-01/azure-aks-to-gcp-wlif/blob/main/gcp-wlif-setup-autogen.sh)** script handles this.
Phase 3 & 4: Configure Workload Identity Federation
First, we create a Workload Identity Pool to act as a container for our external identity providers.
# From gcp-wlif-setup-autogen.sh
gcloud iam workload-identity-pools create "$GCP_POOL_NAME" \
--location="global" \
--display-name="Entra ID Federation Pool"
Next, we create an OIDC Provider within that pool. This is where we tell GCP about our Entra ID tenant.
# From gcp-wlif-setup-autogen.sh
gcloud iam workload-identity-pools providers create-oidc "$GCP_PROVIDER_NAME" \
--workload-identity-pool="$GCP_POOL_NAME" \
--location="global" \
--issuer-uri="https://sts.windows.net/$AZURE_TENANT_ID/" \
--allowed-audiences="$APP_ID_URI" \
--attribute-mapping="google.subject=assertion.sub"
Let’s break down the critical parameters:
--issuer-uri: This points to the public token issuer for your Entra ID tenant. This is how GCP can find the public keys needed to validate JWTs from Entra ID.--allowed-audiences: This is a security measure. GCP will only accept Entra ID tokens that have anaud(audience) claim matching this value. We use the stableAPP_ID_URIwe created in the Azure setup.--attribute-mapping: This is the translator. It tells GCP: "When you receive a valid token from Azure, look at thesubclaim. The value of that claim is what you should use as the unique identifier for this principal." Thesubclaim in an Entra ID token for a Managed Identity is its immutable Object ID.
Phase 5: Bind the Federated Identity to a GCP Role
This is the final step in the trust setup. We grant the federated identity from Azure the permissions it needs in GCP.
# From gcp-wlif-setup-autogen.sh
gcloud projects add-iam-policy-binding "$GCP_PROJECT_ID" \
--role="$GCP_ROLE_TO_GRANT" \
--member="principal://iam.googleapis.com/projects/$GCP_PROJECT_NUMBER/locations/global/workloadIdentityPools/$GCP_POOL_NAME/subject/$APP_MANAGED_ID" \
--condition=None
Notice the --member value. We are not granting the role to a user or a service account. We are granting it directly to the external identity. The subject in this string is the Object ID of the Azure Managed Identity ($APP_MANAGED_ID).
This IAM binding tells GCP: “Any token that comes through my Workload Identity Pool and is identified with this specific subject ID should be granted the roles/storage.bucketViewer role."
Part 3: The Moment of Truth (pod-wlif-validation-example.sh)
Both clouds are now configured. All that’s left is to execute the token exchange from within our running AKS pod. The [pod-wlif-validation-example.sh](https://github.com/jeansson-01/azure-aks-to-gcp-wlif/blob/main/pod-wlif-validation-example.sh) script demonstrates exactly how to do this. You would run this script inside the workload-identity-test-managed-id pod.
Step 1: Get the Entra ID Token
The first curl command performs the first token exchange (K8s token -> Entra ID token).
# From pod-wlif-validation-example.sh
PROJECTED_TOKEN=$(cat $AZURE_FEDERATED_TOKEN_FILE)
SUBJECT_TOKEN=$(curl -s -X POST "https://login.microsoftonline.com/$AZURE_TENANT_ID/oauth2/v2.0/token" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=client_credentials" \
-d "client_id=$AZURE_CLIENT_ID" \
-d "client_assertion-type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer" \
-d "client_assertion=${PROJECTED_TOKEN}" \
-d "scope=$APP_ID_URI/.default" | jq -r .access_token)
The pod uses the Kubernetes token from $AZURE_FEDERATED_TOKEN_FILE as a client_assertion to prove its identity and requests the OAuth scope for the App Registration. Entra ID validates this assertion and, if successful, returns an Entra ID token ($SUBJECT_TOKEN).
Step 2: Get the GCP Token
The second curl command performs the second token exchange (Entra ID token -> GCP token).
# From pod-wlif-validation-example.sh
STS_TOKEN=$(curl -s https://sts.googleapis.com/v1/token \
--data-urlencode "audience=//iam.googleapis.com/projects/$PROJECT_NUMBER/locations/global/workloadIdentityPools/$POOL_ID/providers/$PROVIDER_ID" \
--data-urlencode "grant_type=urn:ietf:params:oauth:grant-type:token-exchange" \
# ...
--data-urlencode "subject_token_type=urn:ietf:params:oauth:token-type:jwt" \
--data-urlencode "subject_token=$SUBJECT_TOKEN" | jq -r .access_token)
Here, we pass the Entra ID token we just obtained as the subject_token to the GCP Security Token Service. GCP STS validates the subject_token and it mints a short-lived GCP access token ($STS_TOKEN).
Step 3: Access the GCP Resource
With the GCP token in hand, the pod can now authenticate to GCP APIs. GCP IAM extracts the subject, and checks its IAM policies. Finding the principal:// binding we created, access to the storage bucket is granted
# From pod-wlif-validation-example.sh
curl -s -X GET \
-H "Authorization: Bearer $STS_TOKEN" \
"https://storage.googleapis.com/storage/v1/b?project=$PROJECT_ID"
Success! The pod has listed GCS buckets without ever having seen a GCP service account key.
Conclusion and Key Takeaways
We have successfully built a secure, passwordless authentication bridge between Azure and Google Cloud. By chaining together AKS Workload Identity and GCP Workload Identity Federation, we have enabled an application in an AKS pod to securely access GCP resources.
The key benefits of this approach are profound:
- Zero Static Secrets: We have completely eliminated the need to store, manage, and rotate long-lived credentials for cross-cloud access.
- Enhanced Security: Authentication is based on short-lived, dynamically generated tokens. The attack surface is drastically reduced.
- Principle of Least Privilege: We grant permissions directly to the specific workload identity, ensuring it has only the access it needs, for as long as it needs it.
- Simplified Operations: By removing the burden of secret management, we simplify our CI/CD pipelines and reduce operational overhead.
- Scalability: Whether running 1 or 1000’s of AKS clusters, this solution scales with ease unlike **THIS** per AKS cluster federation method.
The shift to workload identity federation is a fundamental step forward in cloud-native security. While the initial setup involves several components, the resulting architecture is far more robust, scalable, and secure than traditional, secret-based methods. By adopting this pattern, you are not just solving an authentication problem; you are investing in a more secure and manageable multi-cloud future.
References
메타데이터
- post_id
- 941e9df2d1e1
- slug
- from-azure-to-gcp-a-practical-guide-to-secure-passwordless-authentication-with-aks-and-workload-941e9df2d1e1
- url
- https://medium.com/@ejeansson/from-azure-to-gcp-a-practical-guide-to-secure-passwordless-authentication-with-aks-and-workload-941e9df2d1e1
- canonical_url
- https://medium.com/@ejeansson/from-azure-to-gcp-a-practical-guide-to-secure-passwordless-authentication-with-aks-and-workload-941e9df2d1e1
- author_url
- https://medium.com/@ejeansson
- status
- ok
- fetched_at
- 2026-08-27 11:53:23