NEWS

Workload Identity Federation on GCP: how to eliminate long-lived keys in production

An engineering account shows how an organization moved more than 120 GCP projects to federated authentication in six months, without migrating old keys. See the model and the configuration.

Workload Identity Federation on GCP: how to eliminate long-lived keys in production
Image: Redação iMasters

Every time a team connects a new CI/CD tool or grants third-party access to a GCP project, the recipe tends to be the same: create a service account in IAM, download a JSON key, and paste that file as a secret somewhere. It works, but it leaves a trail of long-lived credentials scattered across secret stores, environment variables, and, in the worst case, version control.

In an article published on InfoQ, engineer Shijin Nair describes how his organization tackled this problem with Workload Identity Federation (WIF), the Google Cloud feature that lets external workloads authenticate without permanent keys. The piece is not a theoretical introduction: it's an account of adoption at scale, with the numbers, the architecture decisions, and a scoping mistake worth knowing before replicating it.

The problem with JSON keys

It's possible to configure service account keys with an expiration date, and for security's sake you should. But that creates its own operational cost: every time a key expires, you need to generate a new one, find every system and team using the old one, and distribute the new one. According to Nair, this overhead is large enough that many teams simply skip expiration altogether and leave keys open indefinitely, a far worse solution.

What makes it worse is the blast radius. If a key leaks, you generally only discover its reach after combing through audit logs, and that depends on audit logging having been enabled beforehand.

What changes in the mental model

The article's central point is a conceptual shift:

Keys are secrets you manage, while federated identities are trust relationships you configure.

>

-- Shijin Nair, InfoQ

Instead of handing a credential to an external system, you declare to GCP in advance which external identity providers you trust and under which conditions. When the external workload needs access, it presents its own native token to GCP's Security Token Service (STS). If the token passes validation, GCP issues a short-lived access token. The job runs, the token expires, nothing is stored, and nothing needs to be rotated.

The three mandatory components

WIF always follows the same three-part model, regardless of the provider:

| Component | Function | |---|---| | Workload Identity Pool | A named container in the GCP project that groups external identity configurations. On its own, it does nothing. | | Provider (connector) | Lives inside the pool and defines the actual trust: where tokens come from, how to validate them, and which identities pass. | | Service Account Binding | Links identities from the pool to a service account, whose IAM permissions the workload temporarily inherits. |

The ceremony of three objects seemed excessive at first glance, Nair writes, but he concludes that the ceremony is the whole point: the trust relationship is declared once and never handled again.

The model isn't exclusive to GCP. Microsoft Entra Workload ID implements the same idea on Azure with OIDC-based federated credentials. What changes between clouds is the syntax. The CEL (Common Expression Language) attribute conditions described here are specific to Google's implementation.

Adoption: a mandate at creation, not migration

The most interesting decision in the account is what the organization didn't do. It didn't migrate existing keys. The environment had hundreds of service account keys without expiration, some years old. Retrofitting those keys would require coordinating every team and system that held copies, with a risk of downtime at every step and no clean way to prove a key was truly dead.

Instead, the line was drawn at the point of creation. As part of a modernization initiative, WIF became mandatory for every new GCP project, enforced at provisioning rather than left to each team's discretion. No new project received a JSON key; deployments started running through Harness pipelines with federated identity.

The result, according to the piece: within six months, more than 120 projects were authenticating this way. Legacy keys didn't disappear, but they stopped multiplying, turning a problem that only kept growing into a fixed, shrinking one.

The attribute condition is the security gate

The most dangerous mistake when configuring WIF is omitting the attribute condition. Without it, any valid token from the issuer gets through, which is too broad for production. The condition is a CEL expression that filters which identities actually get in.

In a Harness case described by Nair, the condition was:

attribute.account_id == "abcdrfghijklmnop" &&
attribute.organization_id == "my_org_platform"

With this, even a valid Harness JWT coming from another account would be rejected. The author warns of a specific pitfall: in Harness accounts with multiple organizations, filtering only by account_id and forgetting organization_id is too permissive.

GitHub Actions to GCP in practice

The most common case is GitHub Actions, which already delivers an OIDC token automatically to each job, containing the repository, branch, and workflow. There's no configuration needed on the GitHub side. On the GCP side, you create the pool and the connector:

bash
gcloud iam workload-identity-pools create github-pool \
  --location="global" \
  --project=YOUR_PROJECT

gcloud iam workload-identity-pools providers create-oidc github-connector \
  --location="global" \
  --workload-identity-pool=github-pool \
  --issuer-uri="https://token.actions.githubusercontent.com" \
  --attribute-mapping="google.subject=assertion.sub,attribute.repository=assertion.repository" \
  --attribute-condition="assertion.repository=='your-org/your-repo'" \
  --project=YOUR_PROJECT

Next comes the binding with the service account and, in the workflow, the id-token: write permission plus the google-github-actions/auth@v2 action. No secret is stored in GitHub, and the token expires when the run ends.

The AWS case: how GCP validates a token that isn't OIDC

The most surprising part, according to the article, is the integration with AWS, which doesn't use OIDC for its workload tokens but rather its own format based on AssumeRole. Here the trust anchor is an AWS account ID, not an issuer URL, and the provider uses a dedicated type.

The flow under the hood is worth understanding:

  1. The workload sends GCP's STS its AWS credentials packaged as a signed request to GetCallerIdentity.
  2. GCP makes the GetCallerIdentity call to AWS itself using that signed request, asking whether the token is real.
  3. AWS validates the signature internally and responds with the IAM role's ARN and the account ID.
  4. GCP compares the returned account ID with the one configured on the provider. If it matches, the token is genuine; if not, it's rejected.

The signature on that response can only be produced by AWS itself, which prevents forgery from outside.

What this means for those building software in Brazil

For Brazilian teams running infrastructure on GCP, the account is actionable on two levels. On the technical side, the three-part model and the gcloud commands work the same way in any region, and the pattern of mandating this at project creation is replicable in any landing zone strategy. On the strategic side, the lesson of treating legacy keys as a fixed, shrinking set rather than facing a risky migration is useful for anyone sitting on a backlog of credentials who never finds a window to deal with it.

What remains open in the article is precisely the fate of the old keys: they stop growing, but they're still there. And there's an architecture choice the author makes differently from Google's recommended pattern (impersonation instead of direct access) whose details are worth reading in the full source on InfoQ before deciding on the design for your own environment.

Translated from the Brazilian Portuguese original · Read the original