Eliminating long-lived cloud credentials is a solvable problem this quarter. The federation mechanics take an afternoon. What determines whether the pattern holds at a hundred projects or quietly turns into a different kind of credential sprawl is four decisions you make before you create the first pool.
Shijin Nair's InfoQ account of taking Workload Identity Federation past 120 GCP projects in six months is worth reading for the operational detail. Three of the four decisions below are ones his team made, and they are the ones enterprise programs routinely get wrong. The fourth, and the register described in the second, are what you have to add when the estate spans clouds and agent runtimes. Here is the order to make them in.
Draw the line at provisioning, not at migration
The instinct with hundreds of no-expiry service account keys in the estate is to run a retirement program. Inventory them, find the owners, coordinate rotation windows, confirm each one is dead. That program consumes two quarters and carries outage risk at every step.
Nair's team refused to migrate, and that was the right call. Set the organization policy constraint that disables service account key creation (iam.disableServiceAccountKeyCreation, with disableServiceAccountKeyUpload alongside it) at the folder or organization level, so the landing zone cannot issue a key even when a team asks. Enforce it in provisioning, not in a standards document teams opt into. A pipeline check can be bypassed. An org policy cannot.
This caps the legacy population. New keys stop appearing, so the exposure has a ceiling and every retirement lowers it. That is the only version of the problem you can finish, and it gives you an answer for the auditor: a known count, a downward slope, and a date.
The date needs a mechanism. GCP's Activity Analyzer reports when each key last authenticated, so you can identify idle keys. Idle is not the same as unused; a quarterly reconciliation job authenticates four times a year. So disable rather than delete. A disabled key fails loudly and can be re-enabled in minutes. Set a disable date for the legacy population, hold a rollback window, then delete what nobody reclaimed. That is the proof of death a rotation program never produces.
Before the disable date, retire keys opportunistically. When a consuming system is being touched for another reason, a platform upgrade, a pipeline refactor, a vendor renewal, federate it then, inside work that is already funded and already has a change window.
The compounding benefit is the one Nair noticed after the fact. When the pattern is mandatory from day one, the scoping conventions are decided once by whoever knows the most. Every later integration inherits a configuration that has already been reviewed, instead of being designed independently by a team under delivery pressure. That is the difference between a pattern and a policy.
The provider configuration is now your credential
Federation replaces a secret you store with a trust relationship you declare. The declaration is the provider configuration: an issuer, an attribute mapping that translates the issuer's claims, a condition evaluated against those claims, and the binding that names which principals get in. Get any of the four wrong and tokens you did not intend to trust pass.
Understand what that does to your control environment. A secret store has rotation, scanning, an owner, and an access log. A provider configuration sitting in IAM has none of those unless you build them. Repository scanning does not catch an over-broad condition. Nobody is paged when one is missing. There is no expiry to force a review.
So govern it as the credential it now is. The rule for conditions is: pin the tenant with an immutable identifier, then pin the workload. What that means varies by issuer. For GitHub, filter on repository_owner_id and repository_id, not the names, because names can be reclaimed after an org or repository is deleted. For AWS, the account ID is the trust anchor and the role ARN pins the workload. For Harness, Nair points out that filtering on account ID alone is fine while the account holds one org and dangerously permissive once it holds several, which is what every account looks like after a year and a half. The mapping deserves the same review as the condition. A mapping that sets google.subject from a claim the issuer lets the caller influence undoes the condition entirely.
Bind a single specific principal rather than an attribute set wherever you can. Nair's guidance here is correct and worth repeating: an attribute-set binding is a standing grant to every present and future identity that matches, which is convenient during rollout and unbounded afterward. Widen only when an operational need forces it, and record why.
All of this belongs in infrastructure as code, under review, with a named owner per provider and a review date. What you need on the other side is an artifact most organizations do not have: a register of non-human identities that answers, in one place, which external systems can reach production, through which trust anchor, under what condition, bound to which permissions, owned by whom. This is the document an examiner asks for under privileged access and third-party access requirements, whether the frame is NYDFS Part 500, DORA's third-party register, or an internal audit of machine credentials. Each cloud implements the same conceptual model in its own dialect (AWS IAM OIDC providers and Roles Anywhere, Entra Workload ID federated credentials, GCP pools and providers), so nothing gives you that view for free across a mixed estate. Build the register or accept that nobody can answer the question.
Choose impersonation for one reason: revocation surface
There are two ways to give a federated identity permissions. Grant roles directly to the external principal on each resource, or bind the principal to a service account it impersonates. Google recommends direct access as the default. Nair's team chose impersonation anyway, and the deciding criterion generalizes beyond one cloud.
Take a concrete case. An analytics vendor runs functions in their own AWS account that read from three of your GCS buckets and write results to a fourth, across two projects. Six months later a fifth bucket is added, then a second pipeline in a third project. The vendor relationship ends.
Under direct access, revocation means removing every binding that identity accumulated across every resource it touched. Cloud Asset Inventory can search IAM policies org-wide for a principal, so this is not blind, but it is a search-and-remove exercise whose completeness you have to prove. Deleting the pool provider would cut everything at once, except that providers are shared: one GitHub issuer, one AWS pool serving a dozen consumers. You cannot disable the provider for one vendor without cutting everyone. Under impersonation, you disable one service account and the identity reaches nothing.
That is the criterion. Ask how many places you cut when the relationship ends, and whether you can prove the cut was complete. Direct access keeps each grant visible on the resource it applies to, which is a real advantage when reasoning about a single bucket. Impersonation gives you a single revocation point and a single audit target, which is what you need when the identity is long-lived and its footprint grows.
Nair gives a second reason that matters more in regulated environments than the offboarding one. Direct access has per-service coverage gaps that you re-check for every API a pipeline calls, and it is brittle across VPC Service Controls perimeters. If your estate runs behind perimeters, impersonation is the path Google itself points you toward.
There is also a hard constraint. Generating a Cloud Storage signed URL requires signing with a service account's key material or the signBlob permission on that account, and a bare federated principal has neither. If any workflow in scope produces signed URLs, impersonation is not a choice.
One cost to plan for: with impersonation, the service account is the principal in your audit logs. Attribution to the original caller is there, in the delegation info on each entry, but your log queries have to read it. Build that into the register's audit view from the start.
If this sounds like the argument for putting a control plane between callers and capabilities, that is because it is the same argument. The reason to route identity through a construct you own is that you can then revoke, scope, and observe in one place.
Where the pattern stops: agents
Workload identity federation works because the caller has provenance somebody will sign for. A pipeline run carries a repository and a branch. A cloud workload carries a role. An issuer attests to those claims, you write a condition over them, and the trust holds.
An agent has none of that. It is invoked dynamically, often acts on behalf of a named human, frequently delegates to sub-agents, and produces a different sequence of calls on every run. The token it presents attests to the runtime host, not to the agent, its task, or the person it is acting for. Conditions written over static claims cannot express "this agent, acting for this user, for this purpose, at this delegation depth." The standards can: RFC 8693 token exchange defines an actor claim for exactly this, and enterprise identity providers implement it. The issuers your pipelines and cloud workloads actually run under do not, and cloud attribute conditions have no way to consume a delegation chain even when one is present. The IETF WIMSE working group and SPIFFE are moving toward attestable workload identity that could carry this, but that is direction of travel, not something you can bind a pool to today.
The practical consequence is a scoping rule. Do not attempt to federate the agent. Put agent-initiated access behind a service or gateway that holds the federated identity, checks each call against the invoking user's entitlements, and logs the decision. What the agent receives is a short-lived, task-scoped token minted by that gateway, not the cloud identity itself. Federation still terminates at something with real provenance, the gateway, and the delegation logic lives where you can reason about it rather than in a CEL expression that was never designed to carry it.
The gateway's log is where the accountability lives, so decide its shape now: invoking user, agent identity, task or intent, delegation depth, resource requested, decision, and the entitlement the decision rested on. That record is what you will hand to an auditor when they ask what an agent did on someone's behalf and why it was allowed.
Design for this even if agents are not yet in scope, because the alternative is discovering later that agent access was quietly attached to a pipeline's service account and inherited its permissions wholesale.
Architects: make federated identity the provisioning default this quarter, enforce it with org policy rather than convention, and treat provider configurations as reviewed, owned artifacts in code. CIOs and CTOs: the question to ask is not how many keys were eliminated. It is whether anyone can produce, on request, the register of every non-human identity that can reach production and the conditions gating each one.
Nair's framing is that keys are secrets you manage while federated identities are trust you configure. He is right, and it is incomplete. Configuration is only safer than a secret when you govern it like one, with an owner, a review cycle, and a blast radius somebody has actually calculated. Otherwise you have moved the exposure somewhere your scanners do not look.