Least Privilege in GCP Explained for Beginners

Least privilege is one of the most important security principles in GCP, and one of the most commonly skipped. This guide explains what it means, how it works across two distinct dimensions, and how to apply it to real workloads from day one.

What is least privilege?

Least privilege means giving an identity (a user, a service account, or a group) only the permissions it actually needs to do its job, on only the resources it actually touches, for only as long as it needs them.

In GCP, this plays out through IAM. Every time you grant a role, you are deciding two things: what actions are allowed, and where those actions apply. Least privilege is about keeping both of those as narrow as possible.

This page walks through what least privilege means in concrete GCP terms, how to apply it to common workloads, what makes it easy to get wrong, and how to review your access grants over time.

Simple explanation

Before diving into the GCP mechanics, here is the core idea in plain terms.

That is exactly what least privilege does in GCP. Instead of giving a service account a project-level roles/editor role (the master keycard), you give it roles/storage.objectCreator on one specific bucket (the loading dock key). If that service account is ever compromised, the attacker can only create objects in that one bucket. Everything else is out of reach.

How least privilege works in GCP

Applying least privilege in GCP means addressing two independent dimensions. Most teams address one and leave the other wide open.

Dimension 1: Role scope (which actions are allowed)

The IAM role you grant determines which API actions the identity can perform. Roles range from very broad (like roles/editor, which covers almost every service) to very narrow (like roles/storage.objectCreator, which only allows uploading objects to Cloud Storage).

Always start from the narrowest predefined role that covers the actual task. If no predefined role fits cleanly, consider a custom role.

Dimension 2: Resource scope (where those actions apply)

Even a narrow role grants access to all matching resources within the scope you bind it at. roles/storage.objectViewer at the project level gives read access to every bucket in the project. The same role bound to a specific bucket restricts it to that one bucket only.

GCP supports IAM bindings at four levels of the resource hierarchy:

  • Organisation: applies to all folders, projects, and resources underneath
  • Folder: applies to all projects in the folder
  • Project: applies to all resources in the project
  • Individual resource: applies only to that one resource (supported by Cloud Storage, BigQuery, Pub/Sub, Secret Manager, and others)

The lowest level that works for the workload is almost always the right choice. Project-level bindings are convenient but expose far more than intended.

Which services support resource-level bindings?

Not every GCP service supports IAM at the individual resource level. The ones that do include Cloud Storage (bucket or object), BigQuery (dataset or table), Pub/Sub (topic or subscription), Secret Manager (secret), and Cloud Run (service). If a service does not support resource-level IAM, the narrowest scope you can use is project-level.

Optional third dimension: conditions

IAM Conditions let you layer time and context restrictions on top of role and resource scope. You can grant a role that only applies during business hours, before a specific expiry date, or only to resources whose names match a given prefix. This is especially useful for temporary access during troubleshooting or incident response.

When to use least privilege

Least privilege is not just for large production environments. It applies any time an identity needs access to a GCP resource. Here are the most common situations:

  • Service accounts running workloads. Every Cloud Run service, Cloud Function, or Compute VM should run as a dedicated service account with only the permissions that specific workload needs. Never use the default Compute Engine service account without reviewing and narrowing its access first.

  • CI/CD pipelines deploying to GCP. A pipeline deploying to Cloud Run needs deploy permissions, not editor access across the project. Bind the deploy role to the specific Cloud Run service, not the whole project.

  • Cloud Storage access. If an app uploads files, it needs roles/storage.objectCreator on that bucket, not roles/storage.admin on all buckets.

  • Cloud SQL connections. An application reading from a database should have roles/cloudsql.client on the specific instance, not editor on the project.

  • Human admin access. Engineers should be granted scoped roles for the resources they actively work on. Avoid assigning roles/owner to human users. Use more specific admin roles instead.

  • Temporary troubleshooting access. When someone needs elevated permissions to investigate an incident, use IAM Conditions to set a time-based expiry. Temporary access that is never revoked becomes permanent risk.

Practical examples

Cloud Storage: upload service

# Wrong: project-level editor grants write access to every GCP service
gcloud projects add-iam-policy-binding my-app-prod \
  --member="serviceAccount:upload-svc@my-app-prod.iam.gserviceaccount.com" \
  --role="roles/editor"

# Right: narrow role scoped to the one bucket this service actually uses
gcloud storage buckets add-iam-policy-binding gs://my-app-prod-uploads \
  --member="serviceAccount:upload-svc@my-app-prod.iam.gserviceaccount.com" \
  --role="roles/storage.objectCreator"
# objectCreator allows: create objects, list objects
# It does NOT allow: read existing objects, delete objects, or access any other service

The better approach limits an incident to one bucket. The wrong approach exposes every service in the project.

Cloud Run: deployment pipeline

# Wrong: editor on the project gives the pipeline write access to every resource
gcloud projects add-iam-policy-binding my-app-prod \
  --member="serviceAccount:ci-deployer@my-app-prod.iam.gserviceaccount.com" \
  --role="roles/editor"

# Right: deploy role scoped to the specific Cloud Run service
gcloud run services add-iam-policy-binding my-api \
  --region=us-central1 \
  --member="serviceAccount:ci-deployer@my-app-prod.iam.gserviceaccount.com" \
  --role="roles/run.developer"

roles/run.developer lets the pipeline deploy new revisions. It cannot touch Cloud Storage, BigQuery, or any other service in the project.

Cloud SQL: application database access

An application that connects to a Cloud SQL instance should have roles/cloudsql.client on that specific instance, not roles/cloudsql.admin or roles/editor on the project. The client role allows connecting and running queries. It does not allow creating or deleting instances, modifying configuration, or accessing other databases.

Logging and monitoring: read-only access

A user who needs to read logs for debugging should have roles/logging.viewer, not roles/viewer. roles/viewer grants read access to almost every service in the project, including configuration data and stored data that the person has no reason to see.

Least privilege vs broad access

The most common mistake is reaching for basic roles (roles/viewer, roles/editor, roles/owner) because they are simple to apply. They are also much more powerful than almost any workload actually needs.

Here is how the approaches compare:

ApproachExampleRisk levelWhy
Project-level basic roleroles/editor on projectHighGrants write access to almost every service in the project
Project-level predefined roleroles/storage.objectViewer on projectMediumCorrect action scope, but exposes all resources of that type
Resource-level predefined roleroles/storage.objectViewer on one bucketLowCorrect action scope AND correct resource scope
Custom role at resource levelExact permissions on one resourceLowestPrecisely matched to the task, hardest to maintain

For production workloads, the goal is to reach the third or fourth row. Project-level basic roles are acceptable for development exploration but should not exist in production IAM policies.

roles/editor is not a safe default

roles/editor grants write access to almost every GCP service in the project: Cloud Storage, BigQuery, Cloud SQL, Pub/Sub, Secret Manager, and more. A service account or user holding Editor can delete databases, overwrite configuration, and read secrets. If that identity is compromised, the attacker has the keys to the whole project.

The viewer trap

roles/viewer sounds safe because it is read-only. But it grants read access to every service in the project: Cloud Storage buckets, Cloud SQL data, Secret Manager secrets, BigQuery datasets, and more. A person or process that only needs to view logs should not have access to your database or your secrets.

See

Basic vs predefined vs custom roles

for a full breakdown of what each category includes and when to use each.

The over-privileged defaults you need to fix

GCP creates two default service accounts in every new project. Both have roles/editor granted by default, which is one of the most common high-severity findings in GCP security reviews:

Check your existing projects now

Run gcloud projects get-iam-policy PROJECT_ID and look for bindings to the Compute Engine default service account. If it still holds roles/editor, that is a high-priority finding to remediate. Remove the Editor binding and create a dedicated service account with only the permissions your application needs.

Common mistakes

  1. Granting Editor to unblock a deployment, then forgetting to fix it. This is the most common pattern. When a permission error blocks a deployment, the fastest fix is roles/editor. The intent is to fix it later. Later never comes. Create a ticket immediately when you make a broad temporary grant, and treat it as a high-priority item. See

    troubleshooting IAM access denied errors

    for how to diagnose the correct narrow role instead.

  2. Narrowing the role but leaving the scope at project level. roles/storage.objectViewer at project level still gives read access to every bucket in the project. If the job only needs one bucket, bind the role to that specific bucket. Both dimensions must be addressed. Role scope alone is not enough.

  3. Leaving Compute Engine and App Engine default service accounts with Editor. Every VM or App Engine instance using the default service account inherits this risk. This is one of the most common high-severity findings in GCP security reviews.

  4. Not reviewing inherited access from folders or the org level. IAM bindings are inherited downward through the resource hierarchy. A role granted at the organisation or folder level flows into every project underneath. Teams focused on project-level IAM often miss org-level or folder-level bindings that are far too broad.

  5. Never removing temporary access. Permissions granted for incident response, a short-term project, or onboarding often stay forever. Without a regular review cadence, IAM policies accumulate grants that nobody remembers granting.

  6. Using custom roles as a shortcut without auditing them. Custom roles are powerful but need maintenance. If a workload is deleted or changes, the custom role may linger with permissions attached to identities that no longer need them.

How to apply least privilege safely

Follow this process whenever you need to grant access to a GCP resource:

  1. Identify the task. What exactly does this identity need to do? Be specific: “read objects from a bucket” is better than “access storage.”

  2. Identify the exact resource. Which specific resource does it need access to? A specific bucket, a specific Cloud SQL instance, a specific Cloud Run service.

  3. Choose the narrowest role. Find the predefined role that covers the task without adding extra permissions. If nothing fits, consider a custom role. Check gcloud IAM commands or the Console to search available roles.

  4. Bind at the lowest sensible scope. Grant at the individual resource level where possible. Only move to project or folder level if the workload genuinely needs access to all resources of that type within that scope.

  5. Test with Policy Simulator before applying if in doubt. Policy Simulator lets you verify what a proposed IAM binding would allow or deny before it takes effect. Use it when reducing an existing role to confirm the narrower role still covers required operations.

  6. Set an expiry if access is temporary. Use IAM Conditions to add a time-based expiry for access that should not be permanent.

  7. Review later with IAM Recommender. After 90 days of activity, IAM Recommender will have enough data to tell you whether the role you granted is still too broad. Check it on a quarterly schedule.

If you manage IAM through code rather than the Console, see

managing IAM with Terraform

for how to express these bindings declaratively.

Org-level guardrails that go beyond IAM

IAM controls what individual identities can do. For organisation-wide constraints that apply regardless of individual role assignments, use Organisation Policies. For example, iam.disableServiceAccountKeyCreation prevents key file creation across all projects, even if an admin at the project level tries to allow it. These structural controls complement least-privilege IAM by removing whole categories of risk at the platform level.

For service accounts specifically, avoid creating or distributing long-lived keys where possible. See

why service account keys are dangerous

and

service account impersonation

for safer alternatives.

Keeping permissions minimal over time

Permissions accumulate. People join teams, get temporary access, and that access is never revoked. Service accounts gather bindings as requirements change. IAM policies that were correct six months ago are often wrong today.

Two tools help keep things clean:

  • IAM Recommender: analyses the last 90 days of API call history and recommends removing roles that have not been used. Available in the IAM section of the Console and via the API.

  • Policy Simulator: lets you test what a proposed IAM change would allow or deny before applying it. Verify a narrower role still covers required operations before switching.

Tip

Schedule a quarterly IAM review for each production project. Focus on privileged service accounts, any remaining basic role bindings, and accounts belonging to people who have changed roles or left the organisation. Thirty minutes on a recurring schedule prevents years of permission creep.

Frequently asked questions

What is least privilege in GCP?

Least privilege means every identity (user, service account, or group) gets only the minimum permissions needed for its specific job, scoped to the specific resources it actually uses, for no longer than necessary. In practice this means granting narrow predefined roles at the resource level rather than broad roles like Editor at the project level.

What is the difference between role scope and resource scope?

Role scope is about which permissions you grant: for example, choosing roles/storage.objectViewer instead of roles/editor. Resource scope is about where you grant them: binding that role to a specific bucket instead of the entire project. Both must be addressed. A narrow role at project level still gives access to every resource of that type across the whole project.

Why is roles/editor dangerous?

roles/editor grants write access to almost every GCP service in a project: Cloud Storage, BigQuery, Cloud SQL, Pub/Sub, and more. If a service account or user holding Editor is compromised, an attacker can read data, overwrite configuration, and delete resources across the entire project. Most workloads only need permissions for one or two services, making Editor far broader than necessary.

When should I use custom roles instead of predefined roles?

Use a custom role when no predefined role matches the exact permissions a workload needs. For example, if your CI pipeline needs to deploy a Cloud Run service but not update traffic or delete revisions, a custom role lets you grant exactly those three permissions. Custom roles add maintenance overhead, so only create them when the predefined options are meaningfully too broad.

How do I find out if my permissions are too broad?

Use IAM Recommender in the GCP Console under IAM & Admin. It analyses 90 days of API call history and flags role bindings where the principal has never used certain permissions. It then suggests narrower replacements. Use Policy Simulator before applying any change to confirm the narrower role still covers the operations you need.

Last verified: 21 March 2026 Cloud services change frequently. Verify details against official documentation before making infrastructure decisions.