TTechclick ⚡ XP 0% All lessons
GCP · IAM · IAM & Least PrivilegeInteractive · L1 / L2 / L3

Google Cloud IAM: — Least Privilege Without Breaking Everything

Someone grants Editor on the production project "just to unblock the deploy," a service-account key lands in a public Git repo, and suddenly one leaked file can read every bucket. This lesson shows you how to give exactly enough access — members, roles, the resource hierarchy, and the service-account traps — without grinding work to a halt.

📅 2026-06-11 · ⏱ 13 min · 3 live demos · 4 infographics · 🏷 10-Q assessment + AI Tutor inline

⚡ Quick Answer

GCP IAM least privilege for L1/L2 engineers and the Professional Cloud Security Engineer exam: members, basic vs predefined vs custom roles, the resource hierarchy, service-account key risk, impersonation, and IAM Recommender.

🎯 By the end you will be able to

Read as:

Pick where you want to start

1

The IAM model

Members + roles + the inheriting allow policy.

2

Pick the right role

Basic vs predefined vs custom — and Editor's danger.

3

Service-account traps

Leaked keys + impersonation = the #1 GCP risk.

4

Tighten it down

Recommender, Analyzer, and a real investigation.

🧠 Warm-up — 3 questions, no score

Just notice which ones make you pause. We answer all three inside the lesson.

1. You grant roles/viewer to a group at the Organization node. Which projects does that grant touch?

Answered in The IAM model.

2. A developer needs to publish to ONE Pub/Sub topic. Where should the role be bound?

Answered in Service-account traps.

3. A service-account JSON key gets committed to a public GitHub repo. What's the realistic worst case?

Answered in Pick the right role.

Most engineers think…

Most engineers think least privilege means "lock everything down, then add permissions back when people complain" — so they either over-grant Editor to avoid the complaints, or under-grant and spend all day firefighting AccessDenied tickets.

Wrong framing. Least privilege in GCP is a scope + role-type + identity decision you make once, with data: grant the narrowest predefined (or custom) role, at the smallest resource in the hierarchy that works, to a group not a person — then let IAM Recommender trim what's unused after 90 days. The tools (Recommender, Policy Analyzer, deny policies, org-policy guardrails) exist precisely so you don't have to choose between over-granting and firefighting.

① The GCP IAM model — members, roles, resources & the inheriting allow policy

Picture Sneha, an L1 cloud engineer at Flipkart. On her first day she's told "give the payments team read access to the logs." To do that safely she has to understand three nouns and one rule. The three nouns: member (who), role (what they can do), and resource (on what). The rule: those three are tied together by an allow policy that inherits downward through the resource tree. Get those four ideas and the rest of IAM is detail.

Start with members (Google's docs now call them principals; older APIs still say members). Four kinds matter on day one: a Google account (sneha@flipkart.com), a Google group (payments-readers@flipkart.com — grant to the group, not the person, so leavers lose access automatically), a service account (the robot identity your app runs as), and a workload identity (an outside workload — GitHub Actions, a Kubernetes pod — that gets short-lived GCP credentials with no key file to leak).

Next, roles. A role is a bundle of permissions, and every permission is written service.resource.verb — for example logging.logEntries.list or storage.objects.get. You grant the role, never the raw permission. A binding is one member plus one role on one resource — that's literally a line in the allow policy.

👉 So far: member + role on a resource = a binding, stored in that resource's allow policy. Next: the rule that makes one careless binding dangerous — inheritance.

Now the rule that L1 engineers underestimate: the allow policy inherits down the hierarchy. GCP arranges everything as Organization → Folder → Project → resource. If you set roles/viewer for a group at the Organization node, that group can now view every folder, every project, every bucket beneath it — possibly hundreds of projects you've never seen. Inheritance is one-directional (downhill): a grant on a single bucket does NOT bubble up to the project. That asymmetry is the whole game — bind at the smallest scope that does the job.

Figure 1 — Members + role = binding, attached to a resource, inherited down the hierarchy
A role granted at the Organization level falls on every Folder, Project and resource below it — inheritance is downhill only The GCP IAM model drawn as a pyramid. At the top is the Organization, below it Folders, then Projects, then resources like a Cloud Storage bucket and a Compute VM. A role binding (member plus role) attached at the Organization node flows down to every node below through inheritance, shown by green arrows. Members on the left are a Google account, a Google group, a service account and a workload identity. The role is the bundle of permissions in service dot resource dot verb format. The amber binding box is the allow policy decision point. members + role = binding → attached to a resource → inherited DOWN Members (who) 👤 Google account 👥 Google group 🤖 service account 🔗 workload identity + role (bundle)storage.objects.get= service.resource.verb binding = member+role Organizationbind roles/viewer here… 📁 Folder (prod / dev) 📦 Project (flipkart-pay-prod) 🪣 bucket 🖥️ VM Inheritance is downhill ONLYA grant at Org lands on EVERYfolder, project & resource below.Bind at the SMALLEST scope thatworks — not at the Org. untrusted / attackertrusted / inspectedpolicy / decisionkey insightallowed
Read the green arrows: a binding attached high in the tree rains down on everything below. Bind at the bucket, not the Org, unless every project truly needs it.
gcloud — read the allow policy on a project and see its bindings
gcloud projects get-iam-policy flipkart-pay-prod --format=json
Expected output
{
  "bindings": [
    { "role": "roles/logging.viewer",
      "members": ["group:payments-readers@flipkart.com"] },
    { "role": "roles/owner",
      "members": ["user:sneha@flipkart.com"] }
  ],
  "etag": "BwYg7x1kQ2c=", "version": 1
}

The four nouns, one tap each

Tap each card — every IAM question on the job and the exam is built from these.

👤
Member
tap to flip

Who is asking: account, group, service account, or workload identity. So: prefer groups — access follows the team, not the person.

🎁
Role
tap to flip

A named bundle of permissions (service.resource.verb). So: you grant a role, never a raw permission.

📦
Resource
tap to flip

What's protected — Org, Folder, Project, bucket, VM. So: where you attach the binding decides the blast radius.

⬇️
Inheritance
tap to flip

Allow policies flow DOWN the tree, never up. So: a grant at the Org touches every project below it.

Daily-life analogy — the society gate-pass register

Think of your apartment society's gate-pass register. A pass issued at the main gate (the Organization) lets the visitor into every tower and flat inside — that's an Org-level binding. A pass issued by one flat's resident (a single bucket) only opens that flat. The guard always checks the register (the allow policy) before letting anyone in, and a pass for the whole society is exactly what you should hesitate to hand out. Least privilege is just: issue the narrowest pass, for the smallest area, that gets the job done.

Quick check · Q1 of 10

Sneha needs to let the payments team read logs in ONE project. A senior says "just grant roles/viewer at the Organization, it's faster." Why is that the wrong move?

Correct: b. Allow policies inherit downward, so a binding at the Organization applies to every folder, project and resource beneath it — far more than the one project. roles/viewer DOES include log-read; Owner is overkill; and logs are project resources, squarely inside the hierarchy. Bind roles/logging.viewer on the single project (or a log bucket) instead.

Pause & Predict

Predict: you grant roles/storage.admin to a group on a single bucket. Does that group now also have storage.admin on a DIFFERENT bucket in the same project? Type your guess.

Answer: No. Inheritance only flows DOWNWARD from a container to the things inside it — never sideways between siblings and never upward. A binding on bucket-A stays on bucket-A; bucket-B in the same project is unaffected unless you bind the role at the project (or higher) level. This is exactly why scoping to the single resource is the safest default — the blast radius is contained to that one resource.

② Roles — basic, predefined & custom (and why Editor on prod hurts)

There are exactly three families of role, and picking the right family is most of least privilege. Basic rolesroles/owner, roles/editor, roles/viewer — are the oldest and the broadest. Editor alone carries thousands of permissions across nearly every service. Predefined roles are service-scoped and maintained by Google (e.g. roles/storage.objectViewer, roles/pubsub.publisher) — this is your default. Custom roles are ones you build from a hand-picked list of permissions, for when even the tightest predefined role is still too wide.

Why is Editor on a production project the classic interview red flag? Because Editor can create, modify and delete resources across the whole project — start and stop VMs, edit firewall rules, rewrite Cloud Functions, drop database instances. The trap is that Editor cannot change IAM, so people convince themselves it's "safe." It isn't: an attacker (or a buggy script) with Editor can still take down or tamper with your entire payments stack. Google's own guidance is blunt — don't grant basic roles in production unless there's genuinely no predefined alternative.

👉 So far: basic = too broad, predefined = default, custom = tightest. Next: how to actually find the right predefined role instead of reaching for Editor.

The practical workflow when someone asks for access: name the service they need (Cloud Storage? BigQuery? Pub/Sub?), name the verb (read? write? admin?), then look up the predefined role that matches — for read-only object access it's roles/storage.objectViewer, not roles/editor. Only if no predefined role fits — say they need three specific compute permissions and nothing more — do you build a custom role. The cost of custom is that you now own it: when Google adds a new permission to a service, your custom role won't pick it up automatically.

Figure 2 — Three kinds of key
Editor on a production project is a master key to thousands of permissions; a predefined or custom role is a single labelled key A three-column comparison of role types. Left, basic roles Owner Editor Viewer drawn as a giant master key opening every door — thousands of permissions, marked red as too broad and dangerous on production. Middle, predefined service-scoped roles drawn as a labelled key opening one service door — the sensible default, marked blue. Right, a custom role drawn as a key cut to exactly the few permissions needed, marked green as true least privilege. A note warns Editor can change almost anything but cannot manage IAM, which lulls people into thinking it is safe. Three kinds of key: master, labelled, or cut-to-fit Basic — Owner / Editor / Viewer 🗝️ ✗ thousands of perms, all services ✗ Editor can change almost anything ✗ Owner can also re-grant IAM ✗ huge blast radius on prod avoid on production projects"tighten later" rarely happens one master key for the whole project Predefined — the default choice 🔑 ✓ scoped to one service + job roles/storage.objectViewer roles/pubsub.publisher ✓ Google maintains it as APIs change start here for 90% of grantsa labelled key for one door opens just the service it names Custom — true least privilege 🗝 ✓ only the exact perms you list compute.instances.get compute.instances.list ✓ when predefined is still too wide you maintain it as perms appearkey cut to exactly these teeth opens nothing more than listed untrusted / attackertrusted / inspectedpolicy / decisionkey insightallowed
Left (red) = a master key to the whole project. Middle (blue) = a labelled key for one service — your default. Right (green) = a key cut to exactly the teeth you listed.
🖥️ This is the screen you'll use to grant a role — GCP Console → IAM & Admin → IAM → Grant access. (Recreated for clarity — your console matches this.)
console.cloud.google.com · IAM & Admin · IAM · Grant access
1
New principals
payments-readers@flipkart.com
2
Select a role
Cloud Storage › Storage Object Viewer
3
Resource
Project: flipkart-pay-prod
IAM condition (optional)
Add condition — limit by time / resource name
4
Role being added
roles/storage.objectViewer
Save
gcloud — grant a narrow predefined role at the smallest scope (a single bucket)
gcloud storage buckets add-iam-policy-binding gs://flipkart-pay-receipts \
  --member="group:payments-readers@flipkart.com" \
  --role="roles/storage.objectViewer"
Expected output
bindings:
- members:
  - group:payments-readers@flipkart.com
  role: roles/storage.objectViewer
etag: CAE=
Updated IAM policy for bucket [gs://flipkart-pay-receipts].
Common mistake — "Editor is fine, it can't touch IAM"

Symptom you'll hear: "why did our staging DB get deleted overnight? The CI service account only had Editor." Cause: Editor includes cloudsql.instances.delete (and thousands more) — not being able to change IAM does not make it harmless. A compromised key with Editor can still wipe or tamper with the whole project. Fix: replace the basic role with the specific predefined role the pipeline actually needs (often just roles/cloudsql.client + a deploy role), and let IAM Recommender confirm what's truly used.

Quick check · Q2 of 10

Rahul at TCS needs a service account that ONLY uploads objects to one bucket from a nightly job. Which role choice best follows least privilege?

Correct: c. A predefined, service-scoped role bound on the one bucket gives exactly the upload ability and nothing else. Editor and Owner are massively over-scoped; roles/viewer is read-only (uploads would fail) AND binding it at the Org sprays view access across every project. Match the service + verb, bind at the smallest resource.

Pause & Predict

Predict: you build a custom role with compute.instances.get and compute.instances.list. Six months later Google adds compute.instances.osLogin as a new permission needed to SSH. Does your custom role get it automatically? Type your guess.

Answer: No — that's the maintenance cost of custom roles. Predefined roles are updated by Google as services evolve, so a predefined role would pick up sensible new permissions. A custom role is frozen to the exact list you wrote; it won't gain the new permission until YOU add it. That's why the guidance is: reach for a predefined role first, and only drop to custom when no predefined role is tight enough — then accept that you now own its upkeep.

③ Service accounts — the #1 GCP risk (keys, impersonation & how to cap it)

If there's one thing to remember from this lesson for the job, it's this: service-account keys are GCP's number-one IAM risk. A service-account key is a JSON file holding a long-lived private key. Whoever has the file can authenticate as that service account and inherit everything it can do. There's no password, no MFA, no expiry. So when a key lands in a public Git repo, a CI log, a Slack message, or a leaked laptop, the attacker doesn't need to break anything — they just use the file.

The fix Google now pushes hard: stop downloading keys. For workloads outside GCP (GitHub Actions, an on-prem job, an EKS pod) use Workload Identity Federation; for one identity acting as another inside GCP use impersonation to mint short-lived tokens on demand. Both replace a permanent, leakable file with credentials that expire in about an hour. If your org was created on/after 3 May 2024, GCP even blocks external key creation by default via an org policy.

👉 So far: downloaded keys leak and never expire; prefer workload identity / short-lived tokens. Next: the escalation that turns a small role into a big one — impersonation and actAs.

Now the privilege-escalation trap that the Professional Cloud Security Engineer exam loves. Two role/permission names matter. roles/iam.serviceAccountTokenCreator grants iam.serviceAccounts.getAccessToken — meaning the holder can mint a token for the target SA and become it. actAs (from roles/iam.serviceAccountUser) lets a principal attach an SA to a new VM or function, so that resource runs as the SA. The danger: if either role is granted at the project, folder or org level, the holder can act as every current and future SA below it — and if one of those SAs is an Owner, a low-privileged user has just escalated to Owner. Worse, impersonation chains: A impersonates B impersonates C, walking up to the most powerful account.

Figure 3 — How a small grant becomes Owner
When a call hits a resource, IAM checks deny first, then allow — and an org-policy guardrail can block the whole class of action regardless A request evaluation flow. A principal makes an API call to a resource. First an organisation-policy guardrail (a constraint) is checked: if it forbids the action class, access is denied no matter what. Next the deny policy is evaluated: an explicit deny wins over any allow. Finally the allow policy (inherited bindings) is evaluated: if a binding grants the permission, the call is allowed; otherwise it is denied by default. Amber diamonds are decision points, red is denied, green is allowed. How one API call is decided: guardrail → deny → allow Priya (member)calls storage.objects.delete Org-policyguardrail? Deny policy?explicit deny wins Allow binding?role grants it? pass no deny ✗ DENIED — constraint forbids ite.g. iam.disableServiceAccountKeyCreation blocked class ✗ DENIED — explicit deny rule ✓ ALLOWEDa binding grants the permission ✗ DENIED by default no binding Key insight: a deny policy and an org-policy guardrail are how you CAP a role you can't fully remove yet. untrusted / attackertrusted / inspectedpolicy / decisionkey insightallowed
Follow the red arrows: a junior with tokenCreator at the PROJECT level can impersonate a high-privilege SA and chain up to Owner. The fix (green) is to grant it on ONE specific SA only.

▶ Watch a leaked key escalate — and where each cap stops it

Karthik's nightly-backup service account key gets pasted into a public Gist. Follow the attacker's path, and notice the three points where a guardrail would have stopped them. Press Play for the healthy path, then Break it to see the failure.

① Key leaksbackup-sa key.json found in a public Gist; attacker authenticates as the SA
② Enumerateattacker runs get-iam-policy; sees backup-sa holds roles/iam.serviceAccountTokenCreator on the project
③ Impersonatemints a token for deploy-sa (an Owner) via getAccessToken — now effectively Owner
④ Exfiltratereads every bucket, adds an SSH key, leaves; only the audit log noticed
Press Play to step through the healthy path. Then press Break it.
gcloud — the RIGHT way: impersonate per-task (short-lived), no downloaded key
# grant tokenCreator on ONE specific SA, to ONE group — not the project
gcloud iam service-accounts add-iam-policy-binding \
  deploy@flipkart-pay-prod.iam.gserviceaccount.com \
  --member="group:release-eng@flipkart.com" \
  --role="roles/iam.serviceAccountTokenCreator"

# now run a command AS that SA with a 1-hour token, no key file
gcloud storage ls gs://flipkart-pay-receipts \
  --impersonate-service-account=deploy@flipkart-pay-prod.iam.gserviceaccount.com
Expected output
Updated IAM policy for serviceAccount [deploy@flipkart-pay-prod.iam.gserviceaccount.com].
WARNING: This command is using service account impersonation.
All API calls will be executed as [deploy@flipkart-pay-prod...].
gs://flipkart-pay-receipts/2026-06/
gs://flipkart-pay-receipts/2026-05/
Prove no leakable keys exist for an SA

Don't assume — list them. Run gcloud iam service-accounts keys list --iam-account=deploy@PROJECT.iam.gserviceaccount.com --managed-by=user. The --managed-by=user filter shows only the downloadable keys you created (Google-managed keys never leave Google and are fine). If that list is empty, there's no JSON file out there to leak. If it isn't, rotate/delete the key and switch the workload to impersonation or workload identity.

Meera at ICICI faces this

Meera, an L2 cloud-security analyst, gets a CSPM alert: a low-privilege analytics service account can somehow read the production secrets bucket it was never granted on.

Likely cause

The analytics SA holds roles/iam.serviceAccountTokenCreator at the PROJECT level. That lets it impersonate the data-pipeline SA, which DOES have access to the secrets bucket — classic impersonation escalation, not a direct grant.

Diagnosis

She stops looking at the bucket's own bindings and instead asks 'who can become an SA that can reach this bucket?' — she checks the project-level tokenCreator/actAs grants and the audit logs for GenerateAccessToken calls.

GCP Console → IAM & Admin → IAM (filter role: Service Account Token Creator) → then Logging → GenerateAccessToken events
Fix

Remove tokenCreator at the project level; re-grant it only on the one SA that truly needs it, scoped to the release-eng group. Add an org policy disabling SA key creation, and an IAM deny policy on iam.serviceAccounts.getAccessToken for non-release identities.

Verify

Re-run a Policy Analyzer query 'who can access gs://icici-prod-secrets' — the analytics SA no longer appears in the access path, and a test impersonation attempt returns PERMISSION_DENIED.

Quick check · Q3 of 10

Arjun at Airtel finds roles/iam.serviceAccountTokenCreator granted to a developer group at the PROJECT level. Why is the scope, not just the role, the problem?

Correct: b. Granting tokenCreator (or actAs) at the project/folder/org level extends to all SAs below it, so the group can impersonate any SA in the project — if one is an Owner, that's a path to Owner. The role absolutely mints access tokens (iam.serviceAccounts.getAccessToken), and project-scope grants aren't read-only. The fix is scope: bind it on one named SA, not the project.

Pause & Predict

Predict: your org sets the iam.disableServiceAccountKeyCreation constraint at the Organization. A team genuinely needs a key for one legacy app in one project. What happens, and what's the clean way out? Type your guess.

Answer: Key creation is blocked everywhere the constraint is inherited — so that one legacy project also can't create keys, and you'll see a 'Service account key creation is disabled' error. The clean way out is NOT to remove the org-wide guardrail. Instead, set an exception (override) on that single project's org-policy node to allow it, or better, migrate the legacy app to workload identity / impersonation. Guardrail stays org-wide; exception is surgical and scoped to the one place that needs it.

④ Tightening down — Recommender, Policy Analyzer & a real investigation

You've granted access carefully — but access drifts. People change teams, projects get over-permissioned "to unblock something," and nobody walks it back. GCP gives you tooling so you don't have to audit by hand. The headliner is IAM Recommender: it compares each principal's granted permissions against what they actually used in the last 90 days and recommends a tighter role. If a service account holds Editor but only ever called a handful of compute read APIs, the Recommender will suggest swapping Editor for roles/compute.viewer.

In the Console, open IAM & Admin → IAM and watch the Analyzed permissions column. A green "Recommendation available" tick next to a principal means the Recommender has a safe trim ready — one click applies it. The column also shows exercised-vs-total permissions, so you can see at a glance who's wildly over-granted. Internally, Google found most granted permissions go unused in a 90-day window — which is exactly the slack least privilege removes.

👉 So far: Recommender trims by real usage; the IAM page flags it for you. Next: answering the question every audit asks — 'who can access this bucket?'

Two more tools finish the kit. Policy Analyzer answers the two audit questions directly: who can access this bucket? and what can this principal reach? — and crucially it follows impersonation paths, so it catches the indirect access Meera found in section ③. Policy Troubleshooter answers the opposite: why was this exact call allowed or denied? — it walks the allow policy, deny policy and org-policy guardrails for you, which turns a 30-minute AccessDenied hunt into a 30-second one. And the resource hierarchy you learned in section ① is itself the control: it's your blast-radius boundary — keep prod in its own folder/project so a mistake in dev can't reach it.

GCP Console → IAM & Admin → Policy Analyzer → Create query. (Recreated for clarity — your console matches this.)">
🖥️ The "who can access this?" investigation — GCP Console → IAM & Admin → Policy Analyzer → Create query. (Recreated for clarity — your console matches this.)
console.cloud.google.com · IAM & Admin · Policy Analyzer · Create query
1
Query scope
Project: flipkart-pay-prod
2
Resource
//storage.googleapis.com/.../flipkart-pay-receipts
3
Permission
storage.objects.get
Identity selector
All principals (include impersonation)
4
Result
3 principals · 1 via impersonation of deploy-sa
Analyze
gcloud — the audit one-two: who can read the bucket, then trim what's unused
# 1) WHO can access this bucket? (follows impersonation)
gcloud asset analyze-iam-policy \
  --scope=projects/flipkart-pay-prod \
  --full-resource-name="//storage.googleapis.com/flipkart-pay-receipts" \
  --permissions="storage.objects.get"

# 2) WHAT least-privilege trims does GCP suggest?
gcloud recommender recommendations list \
  --project=flipkart-pay-prod \
  --recommender=google.iam.policy.Recommender \
  --location=global
Expected output
analysisResults:
- identityList: {identities: [group:payments-readers@flipkart.com]}
  accessControlList: {accesses: [{role: roles/storage.objectViewer}]}
- identityList: {identities: [serviceAccount:analytics@...]}   # via impersonation of deploy-sa
RECOMMENDATION                       PRIMARY_IMPACT  DESCRIPTION
projects/.../recommendations/abc123  SECURITY        Replace roles/editor with roles/compute.viewer
Daily-life analogy — Aadhaar e-KYC vs photocopying your card

Handing out a downloaded SA key is like giving every shop a photocopy of your Aadhaar card — once it's out, you can't take it back, and anyone can reuse it. Impersonation / workload identity is like Aadhaar OTP e-KYC: the verifier gets a one-time, time-limited proof for one transaction, and it expires on its own. Same access, but nothing permanent leaks. When you see a JSON key in a repo, picture a photocopy of your Aadhaar blowing down the street.

Figure 4 — GCP IAM least-privilege cheat-sheet
GCP IAM least-privilege on one card — role types, the SA-key escalation map, the guardrails and the first commands A nine-tile cheat sheet. Tiles cover the three role types, the permission format, the resource hierarchy and inheritance, the service-account key risk, the impersonation and actAs escalation map, the org-policy and deny guardrails, the IAM Recommender, Policy Analyzer, and the first gcloud commands to run. GCP IAM least privilege — your one-glance card 3 role typesbasic (Owner/Editor/Viewer) = avoidpredefined = default · custom = tightestperm = service.resource.verb Hierarchy = blast radiusOrg → Folder → Project → resourceallow policy inherits DOWN onlybind at smallest scope that works SA key = #1 riskdownloaded JSON key leaks → game overprefer workload identity / short-livedrotate ≤90d, delete unused Escalation maptokenCreator → getAccessToken → become SAactAs (serviceAccountUser) → attach SAgrant on ONE SA, never project-wide Guardrails (the caps)iam.disableServiceAccountKeyCreationdeny policy beats every allowIAM Conditions = time/IP limits IAM Recommenderreads last 90 days of usagesuggests a narrower role to swap in✓ Recommendation available icon Policy Analyzer"who can access this bucket?"+ "what can this principal reach?"Troubleshooter = why a call was denied First commandsgcloud projects get-iam-policy PROJECTgcloud asset analyze-iam-policygcloud recommender recommendations list Golden rulegrant the smallest role,at the smallest scope,to a group not a person — then audit.
Your one-card map of this lesson — role types, the SA-key escalation map, the guardrails, Recommender/Analyzer, and the first commands to run. Keep it open in week one.
Prove you own least privilege

Cold, in 60 seconds: name the three role families and when to use each (basic=avoid, predefined=default, custom=tightest); say why a grant at the Org is dangerous (inheritance is downhill); explain the SA-key risk (leakable, no expiry) and the escalation (tokenCreator/actAs at project scope → impersonate any SA); and name the tool that answers "who can access this bucket?" (Policy Analyzer) and the one that trims unused roles (IAM Recommender, 90-day usage). If you can do that without notes, you're ready for the Security Engineer exam's IAM domain.

Next: GCP VPC Service Controls — a perimeter around your data
Quick check · Q4 of 10

An interviewer asks Neha: "You inherit a project where a service account has roles/editor. What's your first, lowest-risk step toward least privilege?"

Correct: c. IAM Recommender uses real 90-day usage to suggest a safe, narrower role you can apply with one click — the lowest-risk way to tighten without breaking the workload. Deleting the SA breaks whatever runs as it; Owner is broader than Editor (the wrong direction); and granting tokenCreator at project scope opens an escalation path. Measure usage first, then tighten.

🤖 Ask the AI Tutor

Tap any question — instant, scoped to this lesson. No login, no waiting.

Pre-curated from GCP docs + community Q&A, scoped to this lesson. For a live prod issue, paste your export into chat.techclick.in.

📝 Wrap-up assessment — six more

You've answered 4 inline. Six left. 70% (7 of 10) marks the lesson complete on your profile. Tap Submit all answers at the end.

Q5 · Remember

In GCP IAM, what does an allow policy do as you move down the resource hierarchy (Organization → Folder → Project → resource)?

Correct: d. Allow policies inherit downward: set one on a container (Org/Folder/Project) and it applies to everything inside. It does not bubble upward (a bucket grant doesn't reach the project) and it isn't limited to a single resource or random — so the other options are wrong.
Q6 · Apply

A Wipro developer needs to publish messages to exactly one Pub/Sub topic. Following least privilege, what do you grant and where?

Correct: a. roles/pubsub.publisher is the service-scoped predefined role for publishing; binding it on the one topic is the smallest scope that works. Editor/Owner on the project are wildly over-scoped, and roles/viewer is read-only AND binding it at the Org sprays access across every project.
Q7 · Apply

You need to find out which principals can read objects in gs://flipkart-pay-receipts, including any who get there by impersonating another SA. Which tool/command fits?

Correct: b. Policy Analyzer (analyze-iam-policy) answers 'who can access this resource?' and follows impersonation paths, so it catches indirect access. get-iam-policy shows only direct project bindings and misses impersonation; instances list is unrelated; Recommender trims unused roles, it doesn't answer access-path queries.
Q8 · Analyze

A low-privilege analytics service account can read a production secrets bucket it was never directly granted on. Direct bindings on the bucket look clean. Most likely root cause?

Correct: c. Clean direct bindings plus unexpected access is the signature of impersonation escalation: tokenCreator/actAs at project scope lets the SA become a stronger SA that can reach the bucket. A public bucket would show in the bindings; roles/viewer is read-only and Org-wide view ≠ secrets write; and Recommender only suggests trims, it never grants access.
Q9 · Analyze

Your org enforces iam.disableServiceAccountKeyCreation at the Organization. One legacy project legitimately needs a downloadable key. What's the correct, least-disruptive action?

Correct: a. Keep the org-wide guardrail and make a surgical exception on the single project node (or, better, remove the need for a key by moving to workload identity). Removing the org constraint re-exposes every project; Owner doesn't bypass an org policy and is over-granting; disabling IAM isn't a thing. Guardrail stays broad, exception stays narrow.
Q10 · Evaluate

Two ways to 'tighten' an over-permissioned service account: (A) immediately strip it to a guessed minimal role; (B) read IAM Recommender's 90-day usage and apply its suggested narrower role, plus scope any tokenCreator to one SA. Which is the stronger approach and why?

Correct: b. B is evidence-based: Recommender's 90-day usage means the narrower role won't break what the SA actually does, and scoping tokenCreator removes the impersonation escalation that a guessed role-strip would miss. A risks an outage from guessing and leaves the escalation path open; they're clearly not identical.
Lesson complete — saved to your profile.
Almost! You need 70% (7 of 10) — re-read the path that tripped you up and tap "Try again".

🧠 In your own words

Type one line: In one line, why is binding roles/iam.serviceAccountTokenCreator at the project level dangerous even for a 'read-only' analyst group? Then compare to the expert version.

Expert version: Because at project scope it lets the group impersonate every service account in the project — including any high-privilege one — so a read-only grant becomes a path to whatever the strongest SA can do; bind it on a single named SA instead.

🗣 Teach a friend

Best way to lock it in — explain it in one line to a teammate. Tap to generate a paste-ready summary.

📖 Glossary

Member / principal
An identity that makes a request — Google account, group, service account, or federated workload identity.
Role
A named bundle of permissions you grant (e.g. roles/storage.objectViewer). You grant roles, never raw permissions.
Permission
The atomic unit of access, written service.resource.verb — e.g. storage.objects.get.
Allow policy (IAM policy)
The JSON/YAML object on a resource listing role bindings — which members hold which roles there.
Resource hierarchy
Organization → Folder → Project → resource. Allow policies inherit downward through it.
Basic roles
Owner / Editor / Viewer — very broad, thousands of permissions. Avoid in production.
Predefined role
A service-scoped role maintained by Google (e.g. roles/pubsub.publisher). The default choice.
Custom role
A role you build from a hand-picked permission list, for true least privilege when predefined is too wide.
Service account key
A downloadable JSON private key that authenticates as an SA with no expiry — GCP's #1 IAM risk if leaked.
Workload Identity Federation
Exchanges an external identity's token for a short-lived GCP token, so no JSON key is downloaded or leaked.
Impersonation (tokenCreator)
roles/iam.serviceAccountTokenCreator + iam.serviceAccounts.getAccessToken — mint a short-lived token to act as an SA.
actAs (serviceAccountUser)
iam.serviceAccounts.actAs — attach an SA to a resource so it runs as the SA; GCP's iam:PassRole.
IAM Recommender
Compares granted vs last-90-days-used permissions and suggests a narrower role to swap in.
Policy Analyzer / Troubleshooter
Analyzer answers who-can-access / what-can-reach; Troubleshooter explains why a call was allowed or denied.

📚 Sources

  1. Google Cloud IAM documentation — "IAM overview" (principals: Google account, group, service account, workload identity; basic/predefined/custom roles; permission format service.resource.verb; allow policy = role bindings; inheritance down Organization → Folder → Project → resource). docs.cloud.google.com/iam/docs/overview
  2. Google Cloud IAM documentation — "Best practices for using service accounts securely" (avoid downloaded keys; org-policy to limit key creation; rotate ≤90 days; dedicated SA per app; credential-leak / privilege-escalation threats). docs.cloud.google.com/iam/docs/best-practices-service-accounts
  3. Google Cloud IAM documentation — "Service account impersonation" + "Requiring permission to attach service accounts (actAs)" (iam.serviceAccounts.getAccessToken via roles/iam.serviceAccountTokenCreator; actAs via roles/iam.serviceAccountUser; granting at project/folder/org extends to all current & future SAs). cloud.google.com/iam/docs/service-account-impersonation · docs.cloud.google.com/iam/docs/service-accounts-actas
  4. Google Cloud blog + IAM docs — "IAM Recommender: least privilege with less effort" and "recommender overview" (compares granted permissions to last-90-days usage; Analyzed-permissions column + 'Recommendation available' icon; gcloud recommender recommendations list --recommender=google.iam.policy.Recommender). cloud.google.com/blog/products/identity-security/achieve-least-privilege-with-less-effort-using-iam-recommender · docs.cloud.google.com/iam/docs/recommender-overview
  5. Cisco-equivalent for GCP attackers — HackTricks Cloud "GCP - IAM Privesc" + Stratus Red Team "Impersonate GCP Service Accounts" (real tokenCreator/actAs escalation and impersonation chaining A→B→C used by red teams). cloud.hacktricks.xyz/pentesting-cloud/gcp-security/gcp-privilege-escalation/gcp-iam-privesc · stratus-red-team.cloud/attack-techniques/GCP/gcp.privilege-escalation.impersonate-service-accounts/
  6. Google Cloud Organization Policy docs — "Restrict IAM service account usage" / iam.disableServiceAccountKeyCreation (enforced by default for orgs created on/after 3 May 2024) + IAM deny policies (explicit deny beats allow). docs.cloud.google.com/organization-policy/restrict-service-accounts · docs.cloud.google.com/iam/docs/deny-overview
  7. Google Professional Cloud Security Engineer — official exam guide (Section 2: configuring access within a cloud solution — resource hierarchy, least privilege, predefined vs custom roles, service-account & workload-identity security, Policy Intelligence). services.google.com/fh/files/misc/professional_cloud_security_engineer_exam_guide_english.pdf

What's next?

You've made sure the right identities have the right roles. But IAM alone can't stop a legitimately-authorised principal from copying data straight out to a personal project. Next we build a perimeter around the data itself.