Get started

OpenSourceOM Team ·

Cross-Cloud Identity Impersonation (AWS↔GCP↔Azure)

Cross-cloud identity impersonation: federation trust as a graph edge, confused-deputy conditions, workload identity across AWS, GCP, and Azure.

  • workload identity
  • federation
  • confused deputy
  • multi-cloud
  • CIEM

A GKE pod with no AWS access key still called sts:AssumeRoleWithWebIdentity and listed every bucket in prod-logs. The trust lived in an IAM role document that named accounts.google.com. Nobody drew that edge because the security group between the clouds was empty. Cross-cloud identity impersonation is federation as a privilege path, not as a connectivity project.

This page: trust as a graph edge, confused deputy, workload identity across AWS/GCP/Azure, and how to query it. Human SSO design is zero trust. Entitlement analytics are CIEM. GCP binding mechanics: GCP IAM. AWS org hygiene: AWS security best practices.

GKE SA / Azure WI / GitHub OIDC
  → token from issuer A
       → STS / token exchange in cloud B
            → cloud B role / MI / SA
                 → data in B (and maybe back to A)

No packet path required.

Federation trust as an edge

Model each trust as:

Issuer principal (cloud A) --FEDERATES_TO--> Role/app (cloud B)

Concrete objects:

Direction Trust object What to inventory
GCP → AWS IAM OIDC provider accounts.google.com or WIF pool; role trust Federated sub / attribute.google conditions; role permissions
AWS → GCP Workload Identity Federation pool + provider (AWS); SA impersonation Attribute mapping google.subject; who can impersonate the SA
Azure → AWS IAM OIDC provider for Entra / Azure WI issuer; role trust aud, sub of the federated credential
AWS → Azure Entra app federated identity credential (issuer + subject) Subject is the AWS role session or GitHub sub
GitHub → any OIDC token.actions.githubusercontent.com sub repo/environment, not only aud: sts.amazonaws.com
{
  "Effect": "Allow",
  "Principal": { "Federated": "arn:aws:iam::111111111111:oidc-provider/accounts.google.com" },
  "Action": "sts:AssumeRoleWithWebIdentity",
  "Condition": {
    "StringEquals": {
      "accounts.google.com:aud": "111111111111.apps.googleusercontent.com"
    }
  }
}

That snippet is a confused deputy waiting to happen: aud only. Any Google token with that audience (more workloads than you think, depending on how the provider was built) can become the role.

Inventory command sketches:

aws iam list-open-id-connect-providers
aws iam get-role --role-name gke-to-aws --query 'Role.AssumeRolePolicyDocument'
gcloud iam workload-identity-pools providers list --location=global --workload-identity-pool=POOL
az ad app federated-credential list --id "$APP_ID"

CSPM check: “OIDC provider exists” is informational. The finding is missing sub / assertion.sub / federated subject.

Confused deputy

Classic: service B trusts issuer A too broadly; attacker uses a legitimate token from a different workload on A.

Pin:

  • AWS IAM: accounts.google.com:sub to the numeric SA unique id, or GitHub token.actions.githubusercontent.com:sub to repo:org/payments:environment:prod (not repo:org/*)
  • GCP WIF: CEL on assertion.arn (AWS) or assertion.sub (Azure/GitHub) to one role/SA
  • Azure federated credentials: subject exact match; issuer exact; do not create a credential per org with a wildcard subject if the portal offers one

Also pin audience. Wrong aud is how a token minted for Grafana is accepted by AWS STS.

External ID (S3 / AssumeRole account-to-account) is the cousin of this problem inside AWS. Cross-cloud OIDC is the same class: unauthenticated callers with a token you did not intend.

Failure mode: copying a blog trust policy that uses StringLike repo:org/*:ref:refs/heads/*. Every branch on every repo in the org is prod.

Workload identity across clouds

Patterns that are supposed to exist (they are not bugs if constrained):

GKE → AWS (IRSA-shaped): GKE Workload Identity issues a Google token; AWS role trust names the Google OIDC provider and the GKE SA. The pod never has an AWS key. The AWS role must be least privilege (CIEM). The GKE SA must not be shared across namespaces (Kubernetes RBAC plus WI annotation).

EKS → GCP: AWS IAM role credentials via WIF provider type AWS; GCP SA has roles/iam.workloadIdentityUser for that AWS principal. Map attribute.aws_role to one role ARN. Node instance roles must not be in that map.

AKS → AWS: Azure Workload Identity (Entra federated credential on a user-assigned MI) → AWS OIDC. Subject is the MI’s application id / federated subject string. Do not reuse the cluster’s kubelet identity.

GitHub Actions → all three: one OIDC token, three cloud trusts. Separate environments (prod vs dev) so a PR workflow cannot hit the prod role. This is the same pin as cloud-to-cloud, with job_workflow_ref if you need it.

Humans: Workforce Identity (GCP) and Identity Center (AWS) should not be the same issuer string you used for GKE unless conditions make that impossible to confuse. Zero trust wants per-app authz; a god federation from Entra to AdministratorAccess in every cloud is a VPN with extra YAML.

Graphing the trust

Put FEDERATES_TO and CAN_ASSUME in the same graph as CAN_REACH (the graph). Queries that matter:

  1. Issuer → role → data: which GKE SAs (or Azure MIs) can read which S3 buckets / GCS / blobs
  2. Unconstrained trusts: OIDC providers whose conditions lack sub / subject
  3. Bidirectional: AWS role that can impersonate a GCP SA that can roles/iam.serviceAccountTokenCreator on a third SA (chain)
  4. Human vs workload: edges from Entra groups vs from cluster SAs—do not mix in one “federation” finding

Without the graph you get three CSPM tickets: “OIDC provider,” “overprivileged role,” “public bucket.” With the graph you get one path. That is the same reason attack paths exist inside one cloud; cross-cloud is just an extra issuer node.

Failure mode: modeling only network peering (TGW, VPN, interconnect) and declaring “no path” while STS still works over the internet. Federation does not need your WAN.

Checklist

  • Every OIDC/WIF/federated credential inventoried as an edge, not a checkbox
  • Conditions pin sub/subject and aud; no org-wide StringLike
  • Workload issuers separate from human IdPs
  • Roles at the far end are least privilege, not AdministratorAccess “for the demo”
  • Graph query: foreign issuer → data; unconstrained trusts alert
  • GitHub environments and GKE SA namespacing match the pins

Related: Zero trust cloud architecture · CIEM explained · GCP IAM hardening · AWS security best practices · The graph