Introduction to Service Accounts
A Service Account is a special type of Google account that belongs to your application or a virtual machine (VM), instead of to an individual end user. In the context of the Professional Cloud Architect, service accounts are the backbone of secure, machine-to-machine communication. Managing them correctly is the difference between a secure environment and a massive data breach.
Plain-Language Explanation: Service Accounts
Analogy 1 — The Corporate Credit Card
Think of a Service Account as a Corporate Credit Card issued to a department, not a person. It has a specific limit (Permissions) and is used to pay for specific things (Access Resources). If the department head leaves, the card still works. However, if the card is stolen (Key Leak), anyone can use it until you cancel it.
Analogy 2 — The Robot Employee
A Service Account is like a Robot Employee that works in your factory. It doesn't have a personal life or a password; it just has a task (Code) and a badge (Service Account). You don't give the robot the keys to the entire factory; you only give it the key to the specific room it needs to work in.
Analogy 3 — The Valet Key
Service Account Impersonation is like a Valet Key for your car. You don't give the valet your entire keychain with your house keys. You give them a special key that only lets them park the car. In GCP, a developer "impersonates" a service account to perform a task without ever seeing the actual password or key.
An identity that an application or workload uses to make authenticated API calls. It is identified by its email address: [email protected].
Types of Service Accounts
- Default Service Accounts: Automatically created by GCP (e.g., Compute Engine default SA). Warning: These often have broad "Editor" permissions by default.
- User-Managed (Custom) Service Accounts: Created by you. This is the Best Practice. You define exactly what roles it has.
- Google-Managed Service Accounts: Used by Google services (like Cloud Dataflow) to act on your behalf.
Service Account Keys vs. Impersonation
- Service Account Keys (.json): High risk. If leaked, they are valid until deleted. They do not expire. Avoid them whenever possible.
- Impersonation: High security. A user or another service account "acts as" a service account. No keys are downloaded. Access is governed by the
iam.serviceAccounts.getAccessTokenpermission.
Architect's Insight: For the PCA exam, if you are asked how to provide access to a resource for a GKE workload or a VM, the correct answer is Workload Identity (for GKE) or Service Account Impersonation, never "downloading a JSON key." ::
Disabling Service Account Key Creation via Org Policy
The single highest-leverage control to prevent credential leaks at scale is to disable service account key creation organization-wide. The Org Policy constraint iam.disableServiceAccountKeyCreation blocks all iam.serviceAccountKeys.create calls under the affected node — folders, projects, or the entire org. A second constraint, iam.disableServiceAccountKeyUpload, blocks uploading externally-generated public keys (the user-managed key flow used to "bring your own key"). Together they eliminate the most common exfiltration vector: a developer downloading a long-lived .json and committing it to GitHub.
Recommended Org Policy stack
iam.disableServiceAccountKeyCreation→ Enforced at org root.iam.disableServiceAccountKeyUpload→ Enforced at org root.iam.disableServiceAccountCreation→ enforced at sandbox folders only (not org-wide, or you cannot deploy workloads).iam.automaticIamGrantsForDefaultServiceAccounts→ Enforced. This stops new projects from auto-granting the legacyEditorrole to the Compute and App Engine default SAs.
Exception workflow
Apply the constraint at the org node, then create a child policy with an exemption on a specific folder (e.g., legacy-batch-jobs/) where on-prem cron systems still require a downloaded key. The exemption folder should have VPC Service Controls and a hard 90-day key rotation runbook. The architect should pair this with a Cloud Asset Inventory export filtered to iam.googleapis.com/ServiceAccountKey so any new key created under the exemption surface in Security Command Center within minutes.
The constraint iam.disableServiceAccountKeyCreation only blocks new keys — it does not delete existing keys. Before enforcing it, run gcloud iam service-accounts keys list across every project (via Cloud Asset Inventory) and rotate or delete all user-managed keys older than 90 days. Otherwise the legacy keys remain valid indefinitely and become the prime target for attackers.
Workload Identity Federation for External Workloads
Workload Identity Federation (WIF) lets workloads running outside GCP — in AWS, Azure, GitHub Actions, on-prem Kubernetes, or any OIDC-compliant identity provider — call Google Cloud APIs without a downloaded service account key. The external IdP issues a token (e.g., an AWS STS session, an Azure managed identity token, or a GitHub Actions OIDC JWT), the Security Token Service (STS) at sts.googleapis.com exchanges it for a short-lived federated token, and that token is used to impersonate a GCP service account.
Building blocks
- Workload Identity Pool: a top-level container that groups external identities by trust boundary (e.g., one pool per AWS account or per GitHub org).
- Workload Identity Pool Provider: the per-IdP configuration. Supports
aws,oidc, andsamlprovider types. The provider defines the attribute mapping (e.g.,google.subject = assertion.sub) and an attribute condition (CEL expression) that gates which external principals are accepted. - Service Account binding: grant
roles/iam.workloadIdentityUseron the target GCP service account to a principal likeprincipalSet://iam.googleapis.com/projects/PROJECT_NUM/locations/global/workloadIdentityPools/POOL/attribute.repository/my-org/my-repo.
Provider patterns
- AWS: trust an AWS account; the attribute condition restricts to a specific IAM role ARN.
- Azure: OIDC provider pointing at
https://login.microsoftonline.com/TENANT_ID/v2.0, conditioned on the Azure AD application ID. - GitHub Actions: OIDC provider at
https://token.actions.githubusercontent.com, conditioned onassertion.repository == 'my-org/my-repo'andassertion.ref == 'refs/heads/main'.
The result: GitHub Actions deploys to GCP with zero stored secrets — no GCP_SA_KEY GitHub secret, no rotation toil, just an OIDC handshake on every workflow run.
GKE Workload Identity
For workloads running inside GKE, the analogous primitive is GKE Workload Identity, which binds a Kubernetes Service Account (KSA) to a Google Service Account (GSA) so that pods can call Google APIs as the GSA — again, without any downloaded JSON key. Under the hood, the GKE metadata server intercepts calls to metadata.google.com from the pod, sees which KSA the pod is running as, and mints a short-lived OAuth token for the bound GSA.
Setup steps
- Enable Workload Identity on the cluster:
--workload-pool=PROJECT_ID.svc.id.goog. - Enable it on the node pool:
--workload-metadata=GKE_METADATA. This disables the legacy node-level default SA path. - Create the KSA in the relevant namespace:
kubectl create sa my-app -n production. - Grant the IAM binding:
gcloud iam service-accounts add-iam-policy-binding \ my-gsa@PROJECT_ID.iam.gserviceaccount.com \ --role roles/iam.workloadIdentityUser \ --member "serviceAccount:PROJECT_ID.svc.id.goog[production/my-app]" - Reference the KSA in the pod spec:
spec.serviceAccountName: my-app.
Why it matters for PCA
The exam regularly contrasts three GKE patterns: (1) downloading a JSON key into a Kubernetes Secret (wrong — long-lived credential in etcd), (2) granting the node's default SA broad permissions (wrong — every pod on the node inherits them), and (3) Workload Identity (correct — per-pod least privilege, no stored credentials). Always pick option 3.
GKE Workload Identity requires the node pool to use GKE_METADATA, not GCE_METADATA. If the node pool was created before Workload Identity was enabled and still has the legacy metadata server, pods will silently fall through to the node's compute default service account — often with Editor permissions. This is the most common GKE privilege-escalation finding in PCA case studies.
Service Account Impersonation and the Token Creator Role
Impersonation is the in-GCP equivalent of sudo: a human user (or another service account) temporarily acts as a service account to perform a privileged operation, without ever holding the SA's long-lived credentials. The mechanism is controlled by two roles on the target service account:
roles/iam.serviceAccountTokenCreator— allows minting OAuth 2.0 access tokens and OpenID Connect ID tokens for the SA. This is the role you grant to the human or pipeline that needs to "act as" the SA.roles/iam.serviceAccountUser— allows attaching the SA to a resource (e.g., a Compute Engine VM, a Cloud Run service). Different from Token Creator: this is about who can deploy code that runs as the SA, not who can mint tokens for ad-hoc calls.
How impersonation looks in practice
# One-shot CLI call
gcloud storage ls gs://prod-bucket \
--impersonate-service-account=prod-reader@PROJECT.iam.gserviceaccount.com
# Persistent default
gcloud config set auth/impersonate_service_account \
[email protected]
The gcloud client calls iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/prod-reader@...:generateAccessToken, receives a token with a default 1-hour lifetime, and uses it for the underlying API call. The audit log records both the caller's identity (e.g., [email protected]) and the impersonated SA, giving you full attribution.
Impersonation chains
You can chain impersonation: SA-A impersonates SA-B impersonates SA-C. Each hop requires Token Creator on the next. Useful for separation of duties — e.g., a CI service account that can only impersonate a "deploy" SA in staging, which in turn can impersonate a "promote" SA in production after a manual approval gate.
Do not confuse Token Creator (roles/iam.serviceAccountTokenCreator) with Service Account User (roles/iam.serviceAccountUser). Token Creator lets a principal mint tokens for ad-hoc API calls (the impersonation flow). Service Account User lets a principal attach the SA to a resource (the deploy flow). Many real-world privilege escalations come from granting both when only one is needed — the gcloud deploy flow only requires Service Account User; CLI impersonation only requires Token Creator.
Short-Lived Credentials via the IAM Credentials API
The IAM Credentials API (iamcredentials.googleapis.com) is the GCP-native way to obtain short-lived credentials. Every WIF exchange, every impersonation call, and every gcloud auth print-access-token --impersonate-service-account invocation ultimately hits this API. Understanding its four methods is essential for the exam:
generateAccessToken— returns an OAuth 2.0 access token (default 1h, max 12h viaserviceAccounts.signJwtworkaround or org policyiam.allowServiceAccountCredentialLifetimeExtension).generateIdToken— returns an OIDC ID token, used to authenticate to Cloud Run, Cloud Functions, IAP-protected resources, or any audience that validates Google-signed JWTs.signBlob— signs an arbitrary byte string with the SA's Google-managed key. Used by tools that need to mint custom JWTs (e.g., generating a signed URL for GCS without exposing a key).signJwt— signs a JWT payload with the SA's Google-managed key.
Lifetime governance
The default 1-hour access token lifetime is a deliberate security boundary. If you need a longer lifetime (e.g., a 6-hour Dataflow batch job), set the org policy constraint constraints/iam.allowServiceAccountCredentialLifetimeExtension and pass --lifetime=21600s. Avoid this in production — it's almost always better to refresh tokens than to extend their lifetime. Tokens issued via WIF have a hard cap matching the external IdP's token TTL (e.g., GitHub OIDC tokens are valid for ~15 minutes; the GCP token cannot outlive them).
Monitoring Service Accounts: Last-Authenticated Time and Insights
You cannot secure what you cannot see. GCP exposes several telemetry surfaces specifically for service account hygiene:
Last-authenticated time
The serviceAccounts.get response includes lastAuthenticatedTime — a 7-day-granularity timestamp of when the SA last successfully authenticated. Query it via Cloud Asset Inventory or gcloud iam service-accounts get-iam-policy. The standard runbook:
- Enumerate all SAs in the org via Cloud Asset Inventory feed.
- Filter for
lastAuthenticatedTimeolder than 90 days anddisabled == false. - Disable (don't delete) those SAs. Wait 30 days. If nothing breaks, delete.
Service Account Insights and IAM Recommender
The Recommender API surfaces insights like:
google.iam.serviceAccount.UnusedServiceAccountInsight— SAs with no authentication in 90+ days.google.iam.policy.Recommender— over-granted roles on SAs (e.g.,Editorgranted but onlystorage.objectViewerpermissions actually used over the past 90 days).
Audit logs
Every SA token mint is logged in Cloud Audit Logs / Data Access logs under iamcredentials.googleapis.com. Pipe these into BigQuery or Chronicle and alert on: (a) Token Creator calls from unexpected source IPs, (b) impersonation chains deeper than two hops, (c) generateAccessToken calls with lifetime > 3600s.
For the PCA exam, when you see a scenario "the security team wants to identify unused service accounts," the correct answer is almost always "use the lastAuthenticatedTime field surfaced by Cloud Asset Inventory and the IAM Recommender's UnusedServiceAccountInsight" — not "write a custom script that parses audit logs." The platform already does the work for you.
Service Account Naming Conventions
In organizations with hundreds of projects and thousands of SAs, naming is not cosmetic — it is the primary mechanism for attribution during incident response. A good naming convention encodes purpose, environment, and ownership into the SA's local part (the bit before @).
Recommended pattern
<workload>-<role>-<env>@<project-id>.iam.gserviceaccount.com
Examples:
checkout-api-runtime-prod@retail-checkout-prod.iam.gserviceaccount.comdataflow-etl-writer-dev@analytics-etl-dev.iam.gserviceaccount.comgh-actions-deployer-stg@platform-cicd-stg.iam.gserviceaccount.com
Why this matters operationally
- Incident response: when an audit log shows
Token Creatorcalled oncheckout-api-runtime-prod, the responder knows immediately which team, environment, and workload to page. - IaC alignment: Terraform modules can derive the SA email from the workload name and environment variable, removing a class of typo-bugs where the wrong SA is bound to a resource.
- Policy automation: Org Policy custom constraints can require SA
displayNameto match a regex, blocking ad-hoctest-sa-1,temp,fooaccounts that accumulate over time.
Display name and description
Always set --display-name and --description on creation. Both fields are indexed and surface in the Cloud Console search bar; an SA with displayName="Checkout API runtime - production" is recoverable months later when its purpose has been forgotten.
Default Compute Engine SA vs. Custom Service Accounts
Every GCP project gets a Compute Engine default service account ([email protected]) and an App Engine default service account ([email protected]). Historically, both were granted the Editor role on project creation — a role that includes broad write access to almost every resource type in the project. Any VM, Cloud Function, or App Engine instance launched without an explicit --service-account flag inherits this Editor-level identity.
Why the default is dangerous
- A compromised VM gets
Editoron the project — read/write on GCS, BigQuery, Pub/Sub, Compute, etc. - Lateral movement from one VM to another is trivial because all of them share the same identity.
- Removing the Editor grant after the fact often breaks workloads silently (the App Engine deployment pipeline, the legacy backup script, etc.).
The fix
- Enforce the Org Policy
iam.automaticIamGrantsForDefaultServiceAccountsso new projects skip the auto-Editor grant. - For existing projects, audit via Cloud Asset Inventory for default SAs that still hold
roles/editorand replace with narrowly-scoped custom SAs. - For every new workload (VM, GKE node pool, Cloud Run service, Cloud Function), create a custom SA named per the convention above and attach it explicitly. Never rely on the default.
Migration cookbook
- Create the custom SA and grant only the roles the workload actually uses (verify via Policy Analyzer's "used permissions" report).
- Update the workload's Terraform/Deployment Manager config to set
service_account.email. - Re-deploy. Watch audit logs for
PERMISSION_DENIEDerrors over 24 hours. Add missing permissions one at a time — never grant a predefined role unless you've verified every permission inside it is needed. - Once stable, remove the Editor grant from the default SA. Disable the default SA entirely if no workload still uses it.
Three service account quick-recall facts for the exam: (1) Default Compute SA gets Editor unless automaticIamGrantsForDefaultServiceAccounts is enforced. (2) GKE Workload Identity needs --workload-pool on the cluster AND --workload-metadata=GKE_METADATA on the node pool. (3) Workload Identity Federation uses STS at sts.googleapis.com to exchange external IdP tokens; the SA binding uses principalSet:// and the role roles/iam.workloadIdentityUser.
Lateral Movement Prevention
A compromised service account is rarely the end of an attack — it's the beginning. Attackers chain iam.serviceAccounts.getAccessToken, iam.serviceAccounts.actAs, and iam.serviceAccounts.signBlob to pivot from a low-value SA to a high-value one. Architects must explicitly design against this.
Pivot points to control
- Token Creator graph: map every
roles/iam.serviceAccountTokenCreatorbinding in the org. If SA-A has Token Creator on SA-B, and SA-B has Token Creator on SA-C, then SA-A can reach SA-C in two hops. Break long chains. actAson cross-environment SAs: never grant Service Account User on aprodSA to adevSA or adevuser. This is the single most common privilege-escalation path in real-world breach reports.- Cross-project bindings: an SA in
project-shared-toolswithroles/owneronproject-prod-datais a massive blast radius. Use VPC Service Controls + IAM Conditions to scope by request context.
Defensive controls
- IAM Conditions: restrict Token Creator grants by source IP (
request.auth.access_levels), by time (request.time), or by destination resource (resource.name.startsWith). - VPC Service Controls: put high-value SAs and the data they access inside a perimeter. Even if an attacker obtains the SA's token from outside the perimeter, the API call fails with
VPC_SERVICE_CONTROLS_VIOLATION. - Policy Analyzer: run
gcloud asset analyze-iam-policyto ask "which identities can act asprod-database-admin@?" and prune the answer set ruthlessly. - Just-in-time elevation: integrate with a PAM system (or build one with Cloud Functions + Cloud Scheduler) that grants Token Creator on sensitive SAs only for a 30-minute window after a ticket is approved.
Attaching Service Accounts to Compute, Cloud Run, and Cloud Functions
The final practical concern is how an SA actually gets to a workload. Each compute surface has its own attachment model, and each model has its own pitfalls.
Compute Engine (GCE)
- Specify
[email protected]and--scopes=cloud-platformat instance creation. The metadata server at169.254.169.254exposes the SA's access token to in-VM processes. - Pitfall: the legacy
--scopesmechanism (e.g.,--scopes=https://www.googleapis.com/auth/devstorage.read_only) further restricts what the SA's token can do, but it is not a security boundary you should rely on. Always scope via IAM roles on the SA, and use--scopes=cloud-platformto defer all restriction to IAM. - The caller needs
roles/iam.serviceAccountUseron the SA to attach it.
Cloud Run (and Cloud Run Jobs)
- Specified at deploy time via
--service-account. Each revision can use a different SA, enabling blue/green migrations of identity. - The Cloud Run runtime exposes the token via the same metadata server endpoint, so SDK auto-discovery (
google.auth.default()) works transparently. - Pitfall: if no SA is specified, the service uses the Compute default SA — the same dangerous Editor-laden default discussed above. Always specify explicitly.
Cloud Functions (1st and 2nd gen)
- 1st gen:
--service-accountflag at deploy. Runtime SA defaults to the App Engine default SA ([email protected]), which historically has Editor. - 2nd gen (Cloud Functions on Cloud Run): same model as Cloud Run.
- Pitfall: Cloud Functions also has a build-time SA (used by Cloud Build to package the function). Configure both:
--service-accountfor runtime,--build-service-accountfor build. A common misconfiguration grants only the runtime SA the right roles, then the build fails with a confusing GCS permission error.
Common rule across all three
The deploying principal needs roles/iam.serviceAccountUser on the SA being attached. Without it, the deploy returns PERMISSION_DENIED: Permission iam.serviceAccounts.actAs denied on service account .... This is the second-most-common PCA exam trap after the GKE metadata server question.
FAQ — Service Account Management
Q1. What is the difference between a User and a Service Account?
Users represent people (identity); Service Accounts represent workloads (machine identity). Users login with passwords and MFA; Service Accounts use keys or attached metadata.
Q2. Is it safe to use the "Compute Engine default service account"?
Generally, no. It is granted the "Editor" role by default, which is far too broad for most production workloads. Always create custom service accounts.
Q3. How do I stop a leaked key from being used?
Immediately delete the key in the IAM console. If the entire service account is compromised, delete the service account itself (though this may break your application).
Q4. What is "Workload Identity"?
It is the recommended way for GKE applications to access Google Cloud services. It links a Kubernetes Service Account to a Google Service Account, eliminating the need for secret management.
Q5. Can a Service Account have its own IAM policy?
Yes. You can decide who can use the service account. This is called the "Resource-level IAM policy" for the service account.