Chapter 8: Identity & Access on GCP
Topic 52

Impersonation and actAs Chains

IAMIdentity

Whenever you deploy a workload that runs as a service account, two different identities are in play, and confusing them produces the most baffling "permission denied" on GCP. Deploy-time identity is who runs terraform apply — you, or the CI service account. Run-time identity is the service account the deployed resource will actually run as. They are not the same, and GCP checks both.

To deploy a Cloud Run service that runs as event-processor@hatch-app-prod.iam.gserviceaccount.com, the deployer needs the actAs permission on that SA — packaged in roles/iam.serviceAccountUser. This is a distinct grant from the token-creator impersonation in Chapter 2, where you borrow an SA's token directly. Same family, different role, different purpose — and routinely mixed up, which is why this topic exists and why it carries a comparison box.

Deploy-Time vs Run-Time Identity

Applying a Cloud Run service is done as you, or as the CI service account that runs the pipeline. But the service runs as a different SA you assign it — the run-time identity. GCP requires the deployer to prove it is allowed to hand a workload that run-time identity. Without that check, any developer who could deploy code could deploy it running as a privileged account, and quietly inherit that account's access. The two-identity split is the guardrail that stops it.

So a deploy involves two questions GCP answers separately: are you allowed to create the Cloud Run service, and are you allowed to attach this run-time SA to it? The first is ordinary Cloud Run permission. The second is actAs, and it is the one people forget.

The actAs Permission

The permission is iam.serviceAccounts.actAs, packaged in the predefined role roles/iam.serviceAccountUser, and it is granted on the run-time SA — not on the project, not on Cloud Run. Without it, the apply fails with a permission error on the service account, not on Cloud Run, which is exactly why the error confuses people: they go debug Cloud Run for an hour when the missing grant is on the SA.

The binding below grants the developers group actAs on the event-processor SA, so they can deploy a workload that runs as it. Note that the role is granted on the service account resource using google_service_account_iam_member — this is the SA's own policy from the previous topic, the "who may use this account" surface.

actAs granted on the run-time SA — the right to deploy as it
resource "google_service_account_iam_member" "deploy_as_processor" {
  service_account_id = google_service_account.event_processor.name
  role               = "roles/iam.serviceAccountUser"   # grants actAs
  member             = "group:gcp-developers@hatch.io"
}                                          # on the SA, not on the project

A Concrete Chain

Put it together for one real deploy. To deploy event-processor running as its SA, gcp-developers@hatch.io — or the CI SA if the pipeline deploys — needs roles/iam.serviceAccountUser on event-processor@hatch-app-prod.iam.gserviceaccount.com. The binding lives on the service-account resource, granting the right to attach that account to a workload. That is the entire chain: deployer, the actAs grant, the run-time SA.

One thing the chain does not do is give the workload its permissions. actAs only lets the deployer attach the SA; the workload's actual access comes from what the run-time SA is itself granted on buckets, topics, and datasets. Forget that and you will deploy successfully and then watch the service fail every API call, because you wired the deploy permission but never granted the run-time SA anything to do.

Distinct from Token-Creator Impersonation

Chapter 2's impersonation uses a different role: roles/iam.serviceAccountTokenCreator, which lets you mint a short-lived token and act with the SA's identity directly — useful for running Terraform itself as an SA without a key. roles/iam.serviceAccountUser and its actAs permission instead let you deploy a resource that will run as the SA. Different role, different permission, different purpose. Granting token-creator when the deployer needed serviceAccountUser leaves the deploy still failing, because token-creator does not include actAs.

Two roles on the SA — attach it, or become it
serviceAccountUser · actAs
Deploy a resource that runs as the SA — assign it to a Cloud Run service, VM, or function. For the deployer or CI SA.
serviceAccountTokenCreator
Mint a short-lived token and act as the SA yourself, directly — e.g. run Terraform as the SA without a key. It does not include actAs.

actAs Chains Across the Hierarchy

A CI service account often needs serviceAccountUser on several run-time SAs — the processor SA in hatch-app-prod, the staging processor in hatch-app-staging, and so on. Grant it on each specific SA resource, not project-wide, so "who can deploy as what" stays least-privileged and auditable as a set of bindings on named accounts. Grant serviceAccountUser at the project level and the CI SA can actAs every SA in the project, which is far more than it needs and impossible to reason about later.

serviceAccountUser (actAs) vs serviceAccountTokenCreator

roles/iam.serviceAccountUser — grants actAs: the right to deploy a resource that runs as the SA, assigning it to a Cloud Run service, a VM, or a Cloud Function. Use it for the deployer or CI SA that attaches a run-time identity to a workload.

roles/iam.serviceAccountTokenCreator — grants the right to mint a short-lived token and act as the SA yourself, directly. Use it when you (or a tool like Terraform) need to borrow the SA's identity. Both are granted on the SA resource, but one attaches a run-time identity and the other borrows a token — using the wrong one is a common dead end.

Common Mistakes
  • Deploying a Cloud Run service that runs as event-processor's SA without roles/iam.serviceAccountUser on that SA — the apply fails with a permission error on the service account, and people waste an hour debugging Cloud Run instead.
  • Granting roles/iam.serviceAccountTokenCreator when the deployer actually needed serviceAccountUser — token-creator lets you impersonate but does not grant actAs, so the deploy still fails.
  • Granting serviceAccountUser at the project level so the CI SA can actAs every SA in the project — over-broad; scope it to the specific run-time SAs it must deploy.
  • Forgetting that the run-time SA still needs its own roles to do its job — actAs only lets you attach it, so the workload deploys and then fails every API call.
  • Granting actAs to user:bob@hatch.io instead of group:gcp-developers@hatch.io — the canon grants to groups, and a per-user grant orphans the day Bob changes teams.
Best Practices
  • Grant roles/iam.serviceAccountUser on the specific run-time service account to the deploying group or CI SA, never project-wide.
  • Keep deploy-time and run-time identities distinct and documented: who deploys, and what each workload runs as.
  • Use token-creator impersonation for acting as an SA and serviceAccountUser for deploying a resource that runs as one — do not substitute one for the other.
  • Scope every actAs binding to the SA resource so an audit of "who can deploy as this identity" is a single policy read.
  • Grant the run-time SA its own least-privilege roles separately, since actAs attaches the identity but supplies none of its permissions.
Comparable models AWS iam:PassRole the direct analog of actAs — handing a role to a service that will assume it AWS sts:AssumeRole the analog of token-creator impersonation gcloud run deploy --service-account the imperative form of the same attach

Knowledge Check

What is the difference between deploy-time identity and run-time identity?

  • Deploy-time identity is who runs the apply; run-time identity is the service account the deployed resource runs as — and GCP checks both
  • They are the same single identity, just referred to by two different names at the plan stage versus the apply stage of the same deploy
  • Deploy-time identity is the project; run-time identity is the folder it lives in
  • Run-time identity only matters for Compute Engine VMs, never for Cloud Run services or Cloud Functions

A deploy of a Cloud Run service running as event-processor's SA fails with a permission error on the service account. What grant is missing, and where?

  • roles/iam.serviceAccountUser (the actAs permission) on the run-time SA, for the deployer
  • roles/run.admin on the Cloud Run service, granted to the deployer who is creating it
  • roles/editor on the whole project, granted to the run-time service account that runs the workload
  • roles/owner on the folder, for the CI pipeline

How does roles/iam.serviceAccountUser differ from roles/iam.serviceAccountTokenCreator?

  • serviceAccountUser lets you deploy a resource that runs as the SA; serviceAccountTokenCreator lets you mint a token and act as the SA yourself
  • They are identical in effect; serviceAccountTokenCreator is simply the older, since-renamed name for the very same serviceAccountUser role today
  • serviceAccountUser is granted on the project and serviceAccountTokenCreator on the org
  • serviceAccountTokenCreator includes actAs, so it is a strict superset that also covers every deploy-time attach

You grant the deployer actAs on the run-time SA and the deploy succeeds, but the workload fails every API call. Why?

  • The run-time SA still needs its own roles — actAs only attaches the identity, it grants none of the SA's permissions
  • The deployer needed roles/owner on the project in addition to actAs before the workload's API calls would function
  • The actAs grant must be re-applied after every deploy or it expires
  • Cloud Run silently ignores the assigned run-time SA and executes the workload under the deployer's own identity instead

You got correct