Safeguard
Cloud Security

GCP IAM Best Practices: Least Privilege on Google Cloud

Principle-driven Google Cloud IAM guidance: avoiding primitive roles, service account hygiene, Workload Identity Federation, the IAM Recommender, and inheritance — with gcloud and Terraform examples.

Marcus Chen
Cloud Security Engineer
5 min read

Google Cloud IAM is powerful and, done wrong, quietly dangerous. The single most common GCP breach path is a service account key — a JSON file with a private key that never expires — exported to a developer laptop, checked into a repo, or left in a CI variable, then used to authenticate as a heavily privileged identity months later. Close behind it is the roles/editor primitive granted "so the deploy works," which hands write access across nearly every service in a project. GCP gives you everything you need to avoid both, but the defaults don't force your hand. This guide is organized around the principles that keep a GCP org least-privileged as it grows.

Principle 1: Never Use Primitive Roles in Production

GCP's primitive roles — Owner, Editor, Viewer — predate fine-grained IAM and are almost always too broad. Editor alone grants write access to resources across dozens of services. Use predefined roles scoped to a service and job function, and reach for a custom role only when nothing predefined fits.

# Audit any principal still holding a primitive role
gcloud projects get-iam-policy PROJECT_ID \
  --flatten="bindings[].members" \
  --filter="bindings.role:(roles/owner OR roles/editor)" \
  --format="table(bindings.role, bindings.members)"

Principle 2: Bind at the Right Level and Let Inheritance Work

IAM policies inherit down the hierarchy: Organization → Folder → Project → Resource. Bind broad, stable roles high (a security team's org-wide viewer) and narrow, workload-specific roles low (object access on one bucket). Prefer binding to groups, not individual users, so joiners and leavers are handled by group membership rather than by editing dozens of policies.

resource "google_storage_bucket_iam_member" "reports_reader" {
  bucket = google_storage_bucket.reports.name
  role   = "roles/storage.objectViewer"
  member = "serviceAccount:${google_service_account.reporter.email}"
}

Principle 3: Treat Service Account Keys as a Last Resort

A downloaded service account key is a long-lived credential with no built-in expiry — exactly the thing you don't want loose. Disable key creation org-wide with the iam.disableServiceAccountKeyCreation constraint, and replace keys with short-lived credentials:

  • Workload Identity Federation lets external workloads (GitHub Actions, AWS, Azure, on-prem) exchange their native identity for short-lived GCP tokens — no key file at all.
  • Service account impersonation lets an authorized principal mint short-lived tokens for a service account on demand, leaving no standing credential.
  • Workload Identity on GKE binds Kubernetes service accounts to Google service accounts so pods authenticate without mounted keys.

Principle 4: Right-Size Continuously With the Recommender

Grants that made sense at launch drift out of use. The IAM Recommender analyzes ~90 days of actual usage and proposes tighter roles, surfacing the gap between what a principal was granted and what it used. Review its recommendations on a schedule and act on them — it's the least-disruptive way to shrink privilege because you're removing access that demonstrably isn't exercised. Pair it with Policy Analyzer to answer the inverse question — "who can access this sensitive resource, and through which grant?" — which is exactly what an auditor or an incident responder needs. Right-sizing is not a one-time cleanup; schedule it quarterly so the privilege you removed doesn't slowly creep back as new bindings accumulate.

Principle 5: Guard the Dangerous Permissions

A few permissions are effectively privilege escalation and deserve extra scrutiny: iam.serviceAccounts.actAs (lets a principal deploy resources running as a service account), iam.roles.update (edit a custom role you can assume), and resourcemanager.*.setIamPolicy (rewrite who has access). Grant these narrowly and alert on their use in Cloud Audit Logs.

Principle 6: Add Conditions to Narrow Grants Further

IAM Conditions let you attach a logical expression to a role binding so the grant only applies in specific circumstances — a particular resource name prefix, a time window, or a request origin. They're the tool for the grants you can't fully scope with the resource hierarchy alone.

resource "google_project_iam_member" "temp_debug_access" {
  project = var.project_id
  role    = "roles/logging.viewer"
  member  = "user:oncall@example.com"

  condition {
    title       = "expires-end-of-quarter"
    description = "Temporary log access, auto-expires"
    expression  = "request.time < timestamp('2026-09-30T00:00:00Z')"
  }
}

Conditional bindings are ideal for break-glass and temporary access: a grant that expires on its own can't become a forgotten standing permission. Combine conditions with resource-scoped bindings for defense in depth — the condition limits when and the scope limits what.

GCP IAM Checklist

PrincipleVerify
No primitivesNo roles/owner or roles/editor on service accounts in prod
Right binding levelRoles bound to groups; workload roles at resource scope
No standing keysdisableServiceAccountKeyCreation enforced; federation/impersonation used
Continuous right-sizingIAM Recommender reviewed and acted on regularly
Escalation guardrailsactAs, setIamPolicy, roles.update granted narrowly and audited

How Safeguard Helps

Every IAM binding above lives in Terraform before it lives in Google Cloud, and that's the cheapest place to stop an over-broad grant. Safeguard's infrastructure-as-code scanning reads your Terraform and flags roles/editor and roles/owner bindings on service accounts, service-account-key creation, and dangerous actAs/setIamPolicy grants in the pull request — mapped to the exact resource block so the fix lands before merge. Griffin, Safeguard's AI triage engine, prioritizes those identity findings by real blast radius, so a service account with project-wide Editor on production outranks a scoped reader binding. The workloads those identities run — including images pushed to Artifact Registry — are covered by container image scanning and software composition analysis so a least-privilege identity isn't undermined by a vulnerable dependency it can reach. It all runs through the Safeguard CLI as a merge gate.

Stop over-privileged GCP identities before they deploy — get started free or read the documentation.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.