Part 3 of 818 min read · 3 diagramsAI-assisted

IAM & Identity

.mdPDF

Assumes you're comfortable with Part 1's resource hierarchy and inheritance model. This chapter is the entirety of the ACE exam's "configuring access and security" domain (~20% of the exam on its own), so it earns the deepest treatment of any single chapter in this course.

Table of Contents#

  1. What This Chapter Covers
  2. IAM's Core Model: Principals, Roles, Permissions, Policies
  3. Basic, Predefined, and Custom Roles: Choosing the Right Type
  4. Policy Inheritance Across the Resource Hierarchy
  5. IAM Conditions: Scoping Access Beyond the Role Itself
  6. Service Accounts: Creation, Types, and Google-Managed Accounts
  7. Service Account Impersonation and Short-Lived Credentials
  8. Service Account Keys: Why They're Disabled by Default Now
  9. Workload Identity Federation for External Workloads
  10. Workload Identity Federation for GKE
  11. Workforce vs. Workload Identity Federation: The Distinction That Matters
  12. A Full Worked Example: Meridian's IAM Model End to End
  13. Real-World Scenario: The Service Account Key Found in a Public Repository
  14. Second Real-World Scenario: The Impersonation Chain Nobody Could Audit
  15. Part 3 gcloud IAM Cheat Sheet
  16. Pre-Flight Checklist: Is This IAM Model Actually Production-Ready?
  17. Common Mistakes and Interview Traps
  18. Worked Practice Problems
  19. Summary and What's Next

What This Chapter Covers#

🎯 By the end of this chapter, you'll be able to design an IAM model that grants every human and every workload exactly the access it needs, nothing more, using groups instead of individuals, short-lived credentials instead of keys, and federation instead of a directory entry wherever one will do.

This chapter is, on its own, the ACE exam's entire "configuring access and security" domain: managing IAM (policies, role types, inheritance) and managing service accounts (creation, minimum permissions, impersonation, short-lived credentials, Workload Identity Federation). Nothing here is optional exam ground; every subsection maps to a named exam bullet.

IAM's Core Model: Principals, Roles, Permissions, Policies#

Four terms, precisely defined, that the exam expects you to use correctly rather than interchangeably:

TermWhat it is
PermissionThe smallest unit, a specific allowed API operation (compute.instances.start)
RoleA named bundle of permissions (roles/compute.instanceAdmin.v1 bundles dozens of compute.* permissions)
PrincipalWho or what is being granted access: a user, a group, a service account, or a federated identity
PolicyThe actual binding: which principal(s) get which role(s), attached to a specific resource

A policy is the object that ties everything together: { role: "roles/editor", members: ["group:gcp-freight-team@meridianlogistics.com"] }, attached to a specific project, folder, or organization. GCP evaluates a principal's effective permissions as the union of every policy binding that applies to them, directly or through inheritance, there's no "explicit deny wins" default the way some systems work; a Deny policy (a separate, newer mechanism layered on top of the classic allow-only model) is required if you specifically need to block access regardless of what an allow policy elsewhere grants.

Basic, Predefined, and Custom Roles: Choosing the Right Type#

Role typeGranularityExampleWhen to use
Basic (legacy: Owner, Editor, Viewer)Extremely coarse, thousands of permissions across every serviceroles/editorAlmost never in production; a personal sandbox project at most
PredefinedScoped to one service or task, Google-maintained and updated as the service evolvesroles/compute.instanceAdmin.v1, roles/storage.objectViewerThe default choice for the vast majority of real access grants
CustomExactly the permission set you define, capped at 64 KB per roleroles/meridian.dispatchOperatorWhen no predefined role matches your actual least-privilege need, and the gap is worth the ongoing maintenance

Tip

Best Practice: reach for a predefined role first, always. Google maintains predefined roles as services gain new permissions, meaning your access grant stays correctly scoped as GCP evolves. A custom role is a maintenance commitment: it's a snapshot of permissions at the moment you wrote it, and it's your job (not Google's) to notice when a service adds a new permission your custom role should probably include. Build custom roles only when a documented gap exists between a predefined role and your actual least-privilege requirement, and document why the gap exists.

A custom role, once you've confirmed the gap is real, is defined as an explicit permission list rather than inherited from any existing role:

title: "Meridian Dispatch Operator"
description: "Read shipment data and publish dispatch events, nothing else"
stage: "GA"
includedPermissions:
  - pubsub.topics.publish
  - bigquery.tables.getData
  - bigquery.jobs.create
gcloud iam roles create meridianDispatchOperator \
  --project=meridian-freight-prod-8f2k \
  --file=dispatch-operator-role.yaml

Basic roles deserve one specific warning: roles/editor includes the ability to modify IAM policies on the resources it covers in many contexts and grants access to nearly every service, so granting it "just to keep things simple" for a new hire is a far larger blast radius than the requester usually intends. Meridian's platform team scans for basic-role grants outside personal sandbox projects as a standing Cloud Asset Inventory query, the same pattern from Part 1's shadow-project scenario.

Policy Inheritance Across the Resource Hierarchy#

IAM policies inherit down the resource hierarchy the same way org policies do (Part 1), but the composition rule is different and worth stating precisely: a principal's effective permissions at a resource are the union of every role bound to them at that resource and at every ancestor above it. There's no overriding a parent's grant at a lower level the way some org-policy constraints allow; an IAM grant at the Organization node reaches every project beneath it, permanently, until removed at the level it was granted.

Diagram

Caption: a platform-team member's effective access on that VM is the union of every role granted anywhere above it in the hierarchy, not just the role granted at the project itself, which is why an audit of "who can touch this VM" has to walk the entire ancestor chain, not just the project's own policy.

⚠️ This union-only model is exactly why the earlier basic-roles warning matters: a stray roles/editor grant at the Organization node reaches every single project in the company, forever, until someone finds and removes that specific binding.

IAM Conditions: Scoping Access Beyond the Role Itself#

IAM Conditions let you attach a Common Expression Language (CEL) expression to a role binding, restricting when it actually applies, by resource attribute, request time, or (as Part 1 mentioned) a Resource Manager tag value.

expression: resource.name.startsWith("projects/_/buckets/meridian-freight-staging") title: "staging-bucket-only"

A role bound with this condition grants access only to resources matching the expression, letting you scope a broad predefined role (roles/storage.objectAdmin) down to a specific bucket prefix without writing a custom role at all. Time-bound conditions (request.time < timestamp("2026-12-31T00:00:00Z")) are the standard mechanism for temporary access grants, a contractor's elevated access that expires automatically rather than depending on someone remembering to revoke it.

Service Accounts: Creation, Types, and Google-Managed Accounts#

A service account is an identity for a workload, not a human, used by applications, VMs, and automation to call GCP APIs. Every service account has an email-formatted identifier (meridian-dispatcher@meridian-freight-prod.iam.gserviceaccount.com) and can be granted IAM roles exactly like a user or group.

Three categories exist, and mixing them up is a real exam trap:

CategoryExampleWho manages it
User-managedmeridian-dispatcher@PROJECT.iam.gserviceaccount.com, created explicitly by youYou: creation, key/impersonation policy, role bindings, deletion
Google-managed, user-visibleThe default Compute Engine service account (PROJECT_NUMBER-compute@developer.gserviceaccount.com), auto-created per projectGoogle creates it; you control its IAM bindings and whether workloads use it
Google-managed, hidden (robot accounts)Service agents like service-PROJECT_NUMBER@gcp-sa-*.iam.gserviceaccount.com, used internally by GCP services to act on your behalfFully Google-managed; you generally never touch these directly

Warning

From the Trenches: the default Compute Engine service account is automatically granted roles/editor on its project unless the project was created after Google changed this default (project-wide, this changed for projects created after May 2024, granting no broad default role at all). A team running an older project, migrating a batch-processing VM to a new machine type, discovered their VM's default service account could modify IAM policies, delete Cloud Storage buckets, and touch resources entirely unrelated to the batch job, none of which the job needed. The immediate cause was inheriting a years-old legacy default; the underlying condition was that nobody had audited default service account permissions since the project was first created. The fix: create a purpose-scoped user-managed service account for every workload going forward, and treat "still using the default Compute Engine service account with its legacy broad role" as a finding in every access review.

Service Account Impersonation and Short-Lived Credentials#

Impersonation lets a principal (a user or another service account) temporarily act as a target service account, obtaining short-lived credentials through the IAM Credentials API rather than a long-lived key file. The caller needs roles/iam.serviceAccountTokenCreator on the target service account; the resulting token defaults to a one-hour lifetime, with a 12-hour maximum.

# Impersonate a service account for a single gcloud command,
# no key file involved anywhere
gcloud compute instances list \
  --project=meridian-freight-prod-8f2k \
  --impersonate-service-account=meridian-dispatcher@meridian-freight-prod.iam.gserviceaccount.com
Diagram

Caption: the audit trail records both identities, the engineer and the service account they impersonated, which is exactly the accountability a shared static key file can never provide.

💡 Impersonation's audit advantage over a static key is the whole point: every action is logged as "principal X, acting as service account Y," creating a clear chain of accountability that a leaked JSON key (usable by literally anyone who has it, with no record of who) simply cannot offer.

Service Account Keys: Why They're Disabled by Default Now#

A service account key is a long-lived JSON credential file that can authenticate as the service account indefinitely, from anywhere, with no expiration unless manually rotated. This is precisely the profile of a security liability: a key committed to a public repository, copied to a personal laptop, or embedded in a container image remains valid until someone notices and revokes it.

For any Google Cloud organization created on or after May 3, 2024, the constraints/iam.disableServiceAccountKeyCreation org policy is enforced by default, meaning new service account key creation is blocked out of the box, not something a security team has to remember to lock down. Older organizations should apply this constraint explicitly (dry-run first, per Part 1's guidance) rather than assuming it's already in place.

Service account keyImpersonation / short-lived credential
LifetimeIndefinite until manually revoked1 hour default, 12 hour maximum
Where it can be used fromAnywhere the file is copied toOnly by a principal explicitly granted Token Creator
Audit trail on useShows only the service account, not who used the keyShows both the caller and the impersonated account
Exposure if leakedFull account access until manually revokedExpires within hours regardless of exposure

Choose a service account key only when you have a genuine, documented reason no alternative fits, most commonly a third-party system entirely outside GCP that can't perform impersonation or federation and requires a static credential; treat every such case as an exception needing its own justification and rotation schedule, not a default.

Workload Identity Federation for External Workloads#

Workload Identity Federation lets a workload running outside GCP (in AWS, in Azure, in an on-premises Kubernetes cluster, in a CI/CD pipeline like GitHub Actions) authenticate to Google Cloud using its own platform's native identity, mapped into a workload identity pool, without a service account key ever being generated or stored.

Diagram

Caption: the GitHub Actions workflow never holds a GCP credential of its own; it exchanges a token its own platform already issued for temporary GCP access, gated by an explicit attribute-mapping condition.

Two access patterns exist, worth distinguishing precisely: direct access grants IAM roles straight to the federated identity itself (no service account involved at all), while impersonation-based access has the federated identity impersonate a specific service account, gaining that account's roles instead. Google's own guidance is to start with direct access for simplicity and fall back to impersonation only when you specifically need the extra indirection (a single service account representing "the CI pipeline" regardless of which specific external identity is calling, for instance).

Workload Identity Federation for GKE#

Workload Identity Federation for GKE is the GKE-specific application of the same underlying mechanism: it binds a Kubernetes service account (a namespace-scoped identity inside your cluster) to a Google Cloud service account, letting Pods authenticate to GCP APIs with no key file mounted into the container and no reliance on the node's own (often overly broad) default service account.

apiVersion: v1
kind: ServiceAccount
metadata:
  name: dispatcher-ksa
  namespace: freight
  annotations:
    iam.gke.io/gcp-service-account: meridian-dispatcher@meridian-freight-prod.iam.gserviceaccount.com
# Bind the Kubernetes SA to the GCP SA via Workload Identity
gcloud iam service-accounts add-iam-policy-binding \
  meridian-dispatcher@meridian-freight-prod.iam.gserviceaccount.com \
  --role=roles/iam.workloadIdentityUser \
  --member="serviceAccount:meridian-freight-prod.svc.id.goog[freight/dispatcher-ksa]"

This closes exactly the gap the "From the Trenches" callout above described: instead of every Pod on a node inheriting whatever broad role the node's default service account happens to carry, each workload gets precisely the GCP permissions its own Kubernetes service account is explicitly bound to, and nothing else.

Workforce vs. Workload Identity Federation: The Distinction That Matters#

Both mechanisms federate an external identity into GCP without a Cloud Identity account or a service account key, and the naming similarity is a deliberate, frequent exam trap:

Workforce Identity Federation (Part 1)Workload Identity Federation
FederatesHumans (employees, contractors, partners)Workloads (a CI pipeline, an application, a Kubernetes Pod)
Typical IdPOkta, Azure AD, a SAML/OIDC corporate IdPGitHub Actions OIDC, AWS/Azure workload identity, a Kubernetes cluster's own OIDC issuer
Console accessYes, humans sign in through the console via SSONo, this is purely for programmatic API calls
Requires an Organization resourceYesNo

If the exam question describes a person signing in, it's Workforce Identity Federation; if it describes a pipeline, a container, or a script calling an API, it's Workload Identity Federation. The two are not interchangeable and are frequently paired in the same distractor-style question.

A Full Worked Example: Meridian's IAM Model End to End#

# 1. Create a purpose-scoped service account, never reuse the default
gcloud iam service-accounts create meridian-dispatcher \
  --display-name="Freight Dispatch Service Account" \
  --project=meridian-freight-prod-8f2k

# 2. Grant it exactly the predefined roles it needs, nothing broader
gcloud projects add-iam-policy-binding meridian-freight-prod-8f2k \
  --member="serviceAccount:meridian-dispatcher@meridian-freight-prod-8f2k.iam.gserviceaccount.com" \
  --role="roles/pubsub.publisher"

# 3. Wire up Workload Identity Federation for GKE, no keys anywhere
gcloud iam service-accounts add-iam-policy-binding \
  meridian-dispatcher@meridian-freight-prod-8f2k.iam.gserviceaccount.com \
  --role=roles/iam.workloadIdentityUser \
  --member="serviceAccount:meridian-freight-prod-8f2k.svc.id.goog[freight/dispatcher-ksa]"

# 4. Grant human access to a group, with a time-bound condition
#    for a contractor's temporary elevated access
gcloud projects add-iam-policy-binding meridian-freight-prod-8f2k \
  --member="group:gcp-freight-team@meridianlogistics.com" \
  --role="roles/compute.instanceAdmin.v1" \
  --condition="expression=request.time < timestamp('2026-12-31T00:00:00Z'),title=contractor-engagement-2026"

# 5. Confirm nobody's holding a service account key on this project
gcloud iam service-accounts keys list \
  --iam-account=meridian-dispatcher@meridian-freight-prod-8f2k.iam.gserviceaccount.com

Real-World Scenario: The Service Account Key Found in a Public Repository#

An open-source contribution from a Meridian engineer, a small utility script shared publicly on GitHub, was found by an automated secret-scanning bot to contain a hardcoded service account key, committed accidentally eight months earlier during a debugging session and never removed. The key belonged to a service account with roles/storage.objectAdmin on Meridian's shared logging bucket.

The immediate symptom was the scanning bot's alert; the immediate cause was the accidental commit; the underlying condition was twofold: the organization hadn't yet enforced constraints/iam.disableServiceAccountKeyCreation (this predated Meridian's own adoption of that policy), so creating the key in the first place had been trivially easy, and the key had never been rotated or reviewed in eight months because nothing in Meridian's process treated "service account keys older than 90 days" as a thing worth tracking. The response: immediate key revocation, migration of that service account's workload to Workload Identity Federation, retroactive enforcement of the key-creation-blocking org policy organization-wide, and a new recurring Cloud Asset Inventory query specifically hunting for service account keys older than 90 days.

Second Real-World Scenario: The Impersonation Chain Nobody Could Audit#

During a security review, Meridian's platform team discovered a chain of impersonation: Service Account A had roles/iam.serviceAccountTokenCreator on Service Account B, which in turn had the same role on Service Account C, which held the actual production database access. A single compromised credential for Service Account A could, through two hops of impersonation, eventually reach production data, and no single IAM policy binding made that reachability obvious; it only became visible by manually tracing three separate bindings across three separate service accounts.

The underlying condition was that impersonation chains had grown organically, one convenience grant at a time, over roughly a year, with no one ever reviewing the transitive reachability the individual grants added up to. The fix Meridian adopted: a quarterly Cloud Asset Inventory query specifically constructing the full impersonation graph across every service account in the organization, flagging any chain longer than one hop for explicit review, treating transitive impersonation reach the same way a security team would treat transitive IAM role inheritance.

Part 3 gcloud IAM Cheat Sheet#

TaskCommand
Create a service accountgcloud iam service-accounts create NAME --project=PROJECT_ID
Grant a role to a principalgcloud projects add-iam-policy-binding PROJECT_ID --member=MEMBER --role=ROLE
Grant a role with a conditionAdd --condition="expression=EXPR,title=TITLE" to the above
Impersonate a service account for one command--impersonate-service-account=SA_EMAIL on any gcloud command
Grant impersonation rightsgcloud iam service-accounts add-iam-policy-binding TARGET_SA --role=roles/iam.serviceAccountTokenCreator --member=MEMBER
Bind a Kubernetes SA via Workload Identitygcloud iam service-accounts add-iam-policy-binding SA_EMAIL --role=roles/iam.workloadIdentityUser --member="serviceAccount:PROJECT.svc.id.goog[NAMESPACE/KSA_NAME]"
List a service account's keysgcloud iam service-accounts keys list --iam-account=SA_EMAIL
Create a custom rolegcloud iam roles create ROLE_ID --project=PROJECT_ID --permissions=PERM1,PERM2

Pre-Flight Checklist: Is This IAM Model Actually Production-Ready?#

  • No basic role (roles/owner, roles/editor, roles/viewer) is bound outside a personal sandbox project
  • Every service account is purpose-scoped; nothing depends on the default Compute Engine service account's legacy broad role
  • Service account key creation is blocked org-wide, with any exception explicitly justified and time-boxed
  • Every workload needing GCP access uses Workload Identity Federation (GKE or external), not a mounted key file
  • Contractor and temporary access uses an IAM Condition with an expiration, not a manual reminder to revoke later
  • Impersonation chains longer than one hop have been reviewed for transitive reachability

Common Mistakes and Interview Traps#

MistakeWhy it's wrongWhat to say instead
"A predefined role and a custom role provide the same level of control"Predefined roles are Google-maintained and update as services evolve; custom roles are a static snapshot you ownPredefined roles first; custom roles only for a documented least-privilege gap, with the maintenance burden explicitly accepted
"IAM policy inheritance lets a child override a parent's grant"Effective permissions are the union of every ancestor's grants; there's no lower-level override for a plain allow policyA grant at any ancestor level reaches everything beneath it permanently, until removed at the level it was granted
"Workforce and Workload Identity Federation are the same feature"One federates humans for console SSO; the other federates workloads for programmatic API accessWorkforce for people signing in, Workload for pipelines/applications calling APIs
"Service account keys are the standard way to authenticate a workload"They're a long-lived liability that newer organizations now block by defaultWorkload Identity Federation or impersonation-based short-lived credentials are the current standard
"The default Compute Engine service account is safe to use for any workload"On older projects it can still carry a broad legacy roleCreate a purpose-scoped service account per workload rather than relying on the default

Worked Practice Problems#

Problem 1: Meridian needs to grant a CI/CD pipeline running in GitHub Actions the ability to deploy to a GKE cluster, without storing any GCP credential in GitHub's secrets store. What's the correct mechanism, and which of the two access patterns (direct or impersonation-based) fits best if the pipeline should act with exactly one well-known service account's permissions regardless of which specific GitHub repository triggered it?

Answer: Workload Identity Federation, configured with a workload identity pool trusting GitHub Actions' OIDC token issuer. Since the requirement is for the pipeline to act as one consistent, well-known service account regardless of the calling repository, the impersonation-based access pattern fits better than direct access: the federated GitHub identity impersonates a single target service account, which holds the actual GKE deployment permissions, rather than each repository's federated identity needing its own direct IAM grants.

Problem 2: An engineer requests roles/editor on a project "to avoid getting blocked by permission errors while building out a new feature." What's wrong with granting this, and what should happen instead?

Answer: roles/editor is a basic role covering thousands of permissions across nearly every GCP service, far beyond what any single feature realistically needs, and it can grant the ability to modify IAM policies on covered resources in some contexts, a significant escalation risk. The correct response is to identify the specific predefined roles the feature actually requires (likely something like roles/compute.instanceAdmin.v1 plus roles/pubsub.publisher, scoped to what the feature touches) and grant those instead, adding more only if a genuine, specific permission gap surfaces.

Problem 3: During an audit, Meridian finds Service Account A can impersonate Service Account B, which can impersonate Service Account C, which holds production database access. No single IAM binding shows this reachability. Is this a misconfiguration, and how should it be found before an audit stumbles onto it by chance?

Answer: It's not a misconfiguration in the sense of any single binding being wrong; each individual roles/iam.serviceAccountTokenCreator grant may well have been a deliberate, reasonable decision at the time. The real gap is that nobody was tracking the transitive reachability those grants compose into. The fix is a standing, recurring Cloud Asset Inventory query that reconstructs the full impersonation graph across every service account and flags any chain longer than one hop, so this kind of compounding risk surfaces on a schedule rather than only during a manual audit.

Summary and What's Next#

This chapter covered the ACE exam's entire access-and-security domain: IAM's principal/role/permission/policy model, choosing between basic, predefined, and custom roles, how policy inheritance composes as a strict union up the resource hierarchy, IAM Conditions for scoping access by resource or time, and the full service-account lifecycle, from Google-managed defaults through impersonation, short-lived credentials, and Workload Identity Federation as the replacement for long-lived keys.

With identity and access now fully covered, Part 4 shifts to the first concrete compute platform: Compute Engine, its disk options (including the newer Hyperdisk family), autoscaling via managed instance groups, and the operational tasks (snapshots, images, OS Login, VM Manager) the exam's "ensuring successful operation" domain expects you to know cold.