Credential isolation between clients on a shared GCP foundation

This post is for cloud engineers building a GCP foundation with Terraform that will host workloads for more than one client. Isolation between clients comes from giving each client principals and trust domains that cannot overlap. Naming conventions and the secrecy of identifiers do not provide it. If two clients’ workloads can end up as the same IAM principal, IAM cannot tell them apart, and a role granted to one applies to both. Below is how I would lay out service accounts, keys and pools so they can’t overlap, and when you can relax.

A naming note first. Older material, such as a 2021 Google Cloud blog post on authenticating service accounts, says “Workload Identity” for the GKE feature. Current documentation calls it Workload Identity Federation for GKE and keeps “Workload Identity Federation” for workloads outside Google Cloud. I’ll use the current names.

Service accounts belong to one workload of one client

Google’s best practices for service accounts describe a service account as both a principal and a resource. You limit what it can do, and you protect it from being impersonated. The same guide recommends single-purpose service accounts. One reason matters most with multiple clients: Cloud Audit Logs record the service account that made a change, not the application that used it. If two clients’ workloads share an account, you lose the ability to say which client did what.

The guide also warns that some services, including App Engine and Compute Engine, create a default service account with the Editor role on the project. A resource can pick that account up automatically when you don’t specify one. I’d treat the default service account as off-limits in every client project. Make an explicit, dedicated account a required input to your Terraform modules so the default is never the fallback. The guide’s naming prefixes (wlifgke-, wlif-, vm-) are worth adopting, with a client identifier added. Names help you audit, but they are not a boundary.

No keys, including for Terraform itself

The guide also recommends avoiding service account keys whenever possible. For your own Terraform runs on a workstation, the 2021 post recommends using your own credentials through gcloud auth application-default login rather than a key. For CI, Workload Identity Federation supports GitHub, GitLab and Terraform as providers, among others.

There is a multi-client trap here. Google’s Workload Identity Federation best practices say to use attribute conditions when federating with GitHub or other multi-tenant identity providers. An attribute condition restricts which identities can authenticate through a pool. I would write the condition so that it pins each provider to the specific repositories and organization of a single client. A broad match is exactly the overlap you’re avoiding.

Pools are the trust domain, so don’t share them

That page also recommends:

The overview generally recommends a new pool for each non-Google Cloud environment, such as development, staging and production. I’d extend that to one pool per client per environment.

Google’s page on identity sameness in multi-tenant fleets explains that a principal identifier is built from the pool, the namespace and the Kubernetes ServiceAccount. Pods that share all three get the same identifier regardless of cluster or project. With fleet Workload Identity Federation, the fleet host project’s pool becomes the default for every registered cluster. So if two clients each run a namespace with the same name and ServiceAccount name in a shared fleet pool, IAM sees one principal.

Be careful with Google’s mitigations on that page. A dedicated fleet host project that can’t run clusters targets a different overlap: fleet members versus non-member clusters. Registered clusters still share the fleet pool, so it does not separate two clients inside one fleet. For mutually untrusted clients, I’d give each client’s clusters their own self-managed pool, or their own fleet, so their identifiers can’t match. Within a cluster, the 2021 post advises against attaching service accounts to clusters or node pools, because every pod on that node pool can then impersonate the account.

Identifiers are not secrets

The Azure Service Operator project published a 2026 design record on exactly this failure. Its credential secrets held only identifiers that aren’t secret. All namespaces shared one ServiceAccount token for the token exchange. So a tenant in one namespace could reference another team’s identity and use it. The proposed fix binds each identity to a per-namespace ServiceAccount subject. That record concerns Azure, not GCP, but I think the lesson carries over. Whatever presents the token for exchange must be specific to one client, and authorization should never rest on knowing an ID.

When you can relax

If the foundation serves a single client, the fleet guidance says that a fleet whose member clusters all sit in the host project, with every cluster registered, carries no additional risk over plain Workload Identity Federation for GKE. In that case, project-level pools and simple namespace conventions are fine. Single-purpose accounts and no keys still apply.

For anything multi-client, decide the boundary before the first client lands. Each client gets its own projects, its own pools and providers, its own single-purpose service accounts, and attribute conditions scoped to that client. Build that into the Terraform module that onboards a client, so isolation is the default and nobody has to remember it.