Most real-world architectures span at least two cloud providers: primary workloads on AWS, data pipelines on GCP, or SaaS components on Azure. Each cloud's workload identity primitive is different, and the differences create a practical problem: when a GCP workload needs to access an AWS resource, or an Azure function needs to call your internal API, you either maintain static credentials for cross-cloud access or build a federation layer that bridges the identity models.
This post covers the native workload identity primitives on each major cloud, the cross-cloud federation patterns, and where SPIFFE fits as an abstraction layer over all of them.
AWS Workload Identity
AWS workload identity is built on IAM roles and STS (Security Token Service). An EC2 instance assumes an IAM role at launch; the instance metadata service (IMDS) provides temporary credentials for that role. ECS tasks have task-level roles. Lambda functions have execution roles. EKS pods can have pod-level IAM roles via IRSA (IAM Roles for Service Accounts).
The primitives are mature and the tooling is good, but AWS's identity model is flat: the unit of identity is an IAM role ARN, and the trust policy on the role controls who can assume it. There's no native concept of workload attestation beyond "this is running on an EC2 instance that was launched with this role" or "this is a pod running under this Kubernetes service account."
For EKS, IRSA is the recommended mechanism. The EKS cluster has an OIDC issuer URL; Kubernetes projected service account tokens from that issuer can be exchanged for IAM role credentials using STS AssumeRoleWithWebIdentity. The pod's service account token carries the namespace and service account name; the IAM role's trust policy specifies which service accounts are allowed to assume it.
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::123456789:oidc-provider/oidc.eks.us-east-1.amazonaws.com/id/CLUSTER_ID"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"oidc.eks.us-east-1.amazonaws.com/id/CLUSTER_ID:sub":
"system:serviceaccount:payments:payment-processor"
}
}
}]
}
This is solid for AWS-native workloads. The problem is that the resulting credential is an AWS STS token scoped to AWS — it doesn't help when the workload needs to access a GCP service or call an internal API that doesn't accept AWS STS.
GCP Workload Identity
GCP's workload identity model is similar in structure but uses a different trust mechanism. GKE workloads use Workload Identity Federation, which binds a Kubernetes service account to a GCP IAM service account. The Kubernetes service account token is exchanged for a GCP access token via the GCP Security Token Service (also confusingly named STS).
gcloud iam service-accounts add-iam-policy-binding \
[email protected] \
--role roles/iam.workloadIdentityUser \
--member "serviceAccount:my-project.svc.id.goog[payments/payment-processor]"
The member string format my-project.svc.id.goog[namespace/service-account] encodes the GKE cluster's project, the namespace, and the service account name. The resulting GCP access token is scoped to the bound GCP service account's permissions.
GCP also supports Workload Identity Federation for non-GCP workloads, allowing AWS IAM roles, Azure managed identities, and arbitrary OIDC providers to exchange their native credentials for GCP access tokens. This is where cross-cloud identity federation starts to become usable: an AWS workload can call GCP APIs using its IAM role's OIDC token without needing a GCP service account key.
Azure Workload Identity
Azure Kubernetes Service uses workload identity via Azure AD Federated Identity Credentials. The pattern is similar to AWS IRSA and GCP WI: a Kubernetes service account is annotated with an Azure Managed Identity client ID, and projected service account tokens from the AKS cluster are exchangeable for Azure AD access tokens.
apiVersion: v1
kind: ServiceAccount
metadata:
name: payment-processor
namespace: payments
annotations:
azure.workload.identity/client-id: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
The Azure SDK automatically detects this annotation and handles the token exchange transparently when making calls to Azure services. The Azure AD token that results is scoped to the managed identity's RBAC permissions.
The Cross-Cloud Problem
Each cloud's identity model is self-contained. An AWS IAM role credential is valid for AWS services. A GCP service account token is valid for GCP services. An Azure managed identity token is valid for Azure services. None of these natively cross cloud boundaries.
Cross-cloud access typically requires one of:
- Static credentials: An IAM access key stored in GCP Secret Manager to let a GCP workload call AWS. This is the worst pattern — it's a long-lived credential with all the associated rotation and storage risks.
- Cloud-to-cloud federation: AWS Roles Anywhere, GCP Workload Identity Federation, or Azure Federated Identity Credentials can each accept OIDC tokens from the other clouds. A GCP workload can obtain a GCP service account token, exchange that at AWS Roles Anywhere for an AWS STS token, and then call AWS services. This works but requires managing federation configurations in both cloud providers.
- SPIFFE-based normalization: A SPIFFE identity issued to the workload becomes the primary identity, exchangeable for cloud-specific credentials at each provider through their respective OIDC federation mechanisms. The workload has one identity; the identity system handles the translation to whatever the target service accepts.
SPIFFE as the Cross-Cloud Substrate
The SPIFFE/SPIRE model establishes a trust domain that spans cloud providers. A SPIRE server can run in one environment and issue SVIDs to workloads in AWS, GCP, and Azure, with node attestors specific to each cloud (the AWS instance attestor validates EC2 instance identity documents, the GCP attestor validates GCE instance metadata, the Azure attestor validates Azure IMDS tokens).
Once a workload has a SPIFFE SVID — regardless of which cloud it's running in — that SVID can be used to authenticate to any system that trusts the SPIFFE trust domain. The cross-cloud credential exchange problem becomes: configure each cloud provider's OIDC federation to trust the SPIRE server's OIDC issuer, and workloads in any cloud can exchange their SPIFFE SVID for cloud-specific credentials on demand.
A GCP Cloud Run service with a SPIFFE SVID can call AWS APIs by exchanging its SVID at AWS Roles Anywhere. An Azure AKS pod with a SPIFFE SVID can call GCP APIs by exchanging at GCP Workload Identity Federation. The SPIFFE identity is the primary identity; cloud-specific tokens are derived on demand and short-lived.
Audit Trail Fragmentation
The operational pain of multi-cloud workload identity isn't just the technical setup — it's the fragmented audit trail. Access events recorded in AWS CloudTrail, GCP Cloud Audit Logs, and Azure Monitor Activity Log use different formats, different identity representations, and different retention and query interfaces.
When you need to investigate an access event that involved a workload calling resources in two clouds, you're correlating across two audit systems with different schemas. The AWS CloudTrail record will show the IAM role ARN. The GCP audit log will show the GCP service account email. Neither will directly reference the other unless you built correlation IDs into your application layer.
This is the argument for a workload identity management layer that sits above the cloud providers: it normalizes the identity representation (SPIFFE IDs in all logs) and provides a single audit source for authorization decisions, even when the underlying credential exchange touches multiple clouds.
In Aembit's model, every credential request is logged with the workload's SPIFFE ID and the server workload being accessed, regardless of which cloud the workload is running in or which credential type is being issued. The audit trail lives at the Aembit layer, not in three separate cloud providers with three different query interfaces.
Misconfiguration Surfaces Per Cloud
Each cloud's workload identity mechanism has its own common misconfiguration patterns:
AWS IRSA: OIDC condition too broad (using StringLike with wildcard rather than StringEquals on the service account sub claim), allowing multiple namespaces to assume a sensitive role by accident; not scoping the audience claim, allowing tokens from any audience to be used; EKS cluster OIDC thumbprint expiry causing authentication failures.
GCP Workload Identity: Granting workload identity user permissions to entire namespaces rather than specific service accounts (my-project.svc.id.goog[namespace/*] is significantly too broad); GCP service account impersonation chains where workload identity is granted through a service account that has broad IAM permissions.
Azure Federated Identity Credentials: Issuer URL mismatch due to OIDC discovery endpoint inconsistency between AKS versions; subject claim format differences between AKS clusters using different authentication configurations; managed identity RBAC assignments that are broader than the federated workload needs.
Across all three, the common failure mode is configuration drift: the federation was set up correctly at some point, but changes to the Kubernetes cluster (API server upgrade, OIDC issuer URL change), the cloud IAM configuration (trust policy edit, service account permission change), or the workload deployment (service account annotation removed in a Helm upgrade) break the federation silently — the workload falls back to whatever credential it has in the environment, which may be a static key from the previous pre-federation setup.
The monitoring requirement is: alert on any authentication that uses a static credential in a workload that's supposed to be using federated identity. If a workload that should be using IRSA is suddenly using an IAM access key, that's either a regression to a fallback credential or someone manually added a key because the federation was broken. Both cases need investigation.
Practical Sequencing for Multi-Cloud Identity
For teams building multi-cloud identity from scratch: start with a single cloud and get the SPIFFE trust domain established there first. Add SPIRE node attestors for other clouds as secondary environments. The cross-cloud federation configuration is substantially easier once the SPIFFE issuer is working and you can verify SVID issuance in the first environment.
The alternative — configuring each cloud's native workload identity independently and trying to correlate them later — creates exactly the audit trail fragmentation problem described above, and makes it harder to adopt a unified identity layer if you decide you need one.