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.
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.
gcloud projects get-iam-policy flipkart-pay-prod --format=json
{
"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.
Who is asking: account, group, service account, or workload identity. So: prefer groups — access follows the team, not the person.
A named bundle of permissions (service.resource.verb). So: you grant a role, never a raw permission.
What's protected — Org, Folder, Project, bucket, VM. So: where you attach the binding decides the blast radius.
Allow policies flow DOWN the tree, never up. So: a grant at the Org touches every project below it.
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.
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?
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.
② 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 roles — roles/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.
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.
gcloud storage buckets add-iam-policy-binding gs://flipkart-pay-receipts \ --member="group:payments-readers@flipkart.com" \ --role="roles/storage.objectViewer"
bindings: - members: - group:payments-readers@flipkart.com role: roles/storage.objectViewer etag: CAE= Updated IAM policy for bucket [gs://flipkart-pay-receipts].
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.
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?
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.
③ 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.
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.
▶ 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.
# 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
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/
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.
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.
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 eventsRemove 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.
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.
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?
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.
④ 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.
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.
# 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
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.viewerHanding 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.
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.
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?"
🤖 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.
🧠 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.
🗣 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
- 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
- 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
- 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
- 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
- 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/
- 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
- 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.