GCP IAM Policies Explained: Bindings, Inheritance, ETags, and Safe Changes

By the end of this page you will understand what a GCP IAM policy is, what its three fields do, how access flows across the resource hierarchy, when to set access at project level versus resource level, and how to make changes without accidentally deleting existing access.

The simple version

An IAM policy is the access document attached to a Google Cloud resource. It says who gets which role on that resource.

Every project has an IAM policy. Every Cloud Storage bucket has one. Every Secret Manager secret has one. When GCP receives an API request, it looks up the relevant policies and checks whether the caller has the required permission. If yes, the request succeeds. If no policy grants that permission, GCP returns a permission denied error.

That is the whole model. The rest of this page fills in the details that matter in practice.

What is an IAM policy in GCP?

An IAM policy is a structured document that controls access to a single GCP resource. Policies can be attached to:

  • Organisations
  • Folders
  • Projects
  • Cloud Storage buckets
  • BigQuery datasets
  • Pub/Sub topics and subscriptions
  • Secret Manager secrets
  • Cloud Run services
  • And many other resource types

The policy is not a global list of all access across your account. It belongs to one resource and governs access to that resource only. For background on how GCP organises projects and resources, see the resource hierarchy guide. For a broader introduction to how IAM fits together, see IAM in GCP.

The three fields in every IAM policy

Every IAM policy document contains exactly three top-level fields. Understanding what each one does is the foundation of working with IAM safely.

  • bindings: the access grants. An array of role-to-member mappings. This is the core of the policy.
  • etag: a concurrency control token that prevents two simultaneous updates from overwriting each other.
  • version: the policy schema version. Version 1 is standard. Version 3 is required when the policy contains IAM Conditions.
# A typical project IAM policy
bindings:
  - role: roles/storage.objectViewer
    members:
      - group:data-readers@example.com
  - role: roles/run.invoker
    members:
      - serviceAccount:api-gateway@my-app-prod.iam.gserviceaccount.com
  - role: roles/logging.viewer
    members:
      - group:platform-engineers@example.com
      - user:alice@example.com
etag: BwWWja0YfJA=
version: 1

Bindings

Each binding connects one role to a list of members. Members are prefixed by type to tell GCP what kind of identity it is:

  • user: a Google Account belonging to a person
  • serviceAccount: a service account used by an application or workload
  • group: a Google Group containing many users
  • domain: all users in a Google Workspace domain
  • allAuthenticatedUsers any authenticated Google identity (use with caution)
  • allUsers literally anyone, including unauthenticated requests
Never use allUsers on sensitive resources

Granting any role to allUsers makes that resource publicly accessible to the entire internet, including unauthenticated requests. This is appropriate for truly public assets like a static website bucket, but it is a critical misconfiguration on anything that contains user data, credentials, or internal services. Security Command Center will flag it.

One binding can have many members, but it can only reference one role. To grant two roles to the same person, you need two separate bindings.

Etag

The etag is a hash of the current policy state. When you call get-iam-policy, the response includes the current etag. When you call set-iam-policy, GCP checks that the etag in your file matches the current etag on the resource.

If another change was made between your read and your write, the etags will not match and the operation fails. This prevents concurrent updates from silently overwriting each other, which is a common problem in team environments where multiple engineers might adjust IAM around the same time.

Version

Most policies use version 1. Version 3 is only required when a policy contains IAM Conditions: conditional bindings that restrict when or where access applies. If you read a version 3 policy and reapply it using an older tool that does not understand conditions, those conditions may be silently stripped. Always check the version field before writing back a policy file.

Note

When you add a condition to a binding using gcloud, the policy version is automatically upgraded to 3. You do not need to set it manually.

How IAM policies work

When you make a GCP API call, IAM evaluates access like this:

  1. GCP identifies the resource you are trying to access.
  2. GCP collects all applicable IAM policies: from the resource itself, plus its parent project, any folders, and the organisation.
  3. GCP checks whether any binding in any of those policies grants the required permission to the caller.
  4. If yes, the call succeeds. If no binding matches, the call fails with a permission denied error.

Access is the union of all applicable policies. A binding at the project level is just as effective as one at the resource level. They are evaluated together, not in priority order.

Key insight

You cannot revoke inherited access by changing a lower-level policy. If a user has roles/viewer at the organisation level, they have that access everywhere. A project-level or resource-level policy cannot remove it. To remove broad access, you must edit the policy at the level where it was originally granted.

Policy levels and inheritance

Policies exist at four levels of the resource hierarchy:

  • Organisation: applies to every folder, project, and resource in the organisation
  • Folder: applies to all projects within that folder and everything inside them
  • Project: applies to all resources within the project
  • Individual resource: applies only to that specific resource. Buckets, secrets, topics, and datasets all support resource-level policies.

Inheritance flows downward and is additive. If a user has roles/viewer at the folder level, they have viewer access on every project and resource inside that folder. Nothing at the project or resource level can take that away.

Organisation (roles/viewer granted to alice)
  └── Folder: production
        └── Project: my-app-prod    ← alice has viewer here too
              └── Bucket: my-app-prod-data  ← and here
Debugging unexpected access

Running get-iam-policy on a project only shows bindings set directly on that project. Inherited bindings from parent folders or the organisation do not appear in the project-level output. If a user appears to have access you cannot explain, check the folder and organisation policies too.

Project-level vs resource-level IAM policies

One of the most practical decisions in GCP IAM is where to set a binding. You usually have two realistic options: on the project, or on the specific resource.

Project-level policies

A binding on the project applies to every resource in that project. This works well for broad, team-wide access, such as giving an engineering team read-only access to monitor everything in a project.

The trade-off is scope. A service account with roles/storage.objectViewer at the project level can read every bucket in the project, not just the one you intended.

Resource-level policies

A binding on an individual resource applies only to that resource. This is the safer option when access should be narrow, and it directly supports the principle of least privilege by limiting what a compromised identity can reach.

When to use each

  • Use project-level when giving a team broad read-only monitoring access to a project
  • Use project-level when a CI/CD service account needs to deploy across multiple services in the same project
  • Use resource-level when one service account needs access to one specific bucket for an ETL job
  • Use resource-level when a workload needs to access one specific secret in Secret Manager
  • Use resource-level when an external identity should be allowed to invoke only one specific Cloud Run service
When in doubt, start narrow

Set the binding at resource level first. You can always widen scope later. Narrowing scope after the fact is harder to reason about and easier to get wrong, especially once other teams start relying on the broader access.

IAM policy vs IAM role

These two terms are often confused. They are related but distinct:

  • Role: a named set of permissions. For example, roles/storage.objectViewer includes permissions like storage.objects.get and storage.objects.list. Roles do not contain any information about who holds them.
  • Binding: a single entry inside a policy that says β€œthese members hold this role on this resource.”
  • Policy: the full document attached to a resource, containing all bindings for that resource.

For a deeper look at the types of roles available, see IAM Roles Explained and Basic vs Predefined vs Custom Roles.

IAM policies and IAM Conditions

Standard IAM bindings apply unconditionally. If the member has the role on the resource, access is always active. IAM Conditions let you attach a conditional expression to a binding so that access only applies in specific circumstances.

Common uses include:

  • Time-based expiry: access only during business hours or before a specific date
  • Resource attribute conditions: access only to resources tagged a certain way
  • Request attribute conditions: access only from a specific IP range

Using conditions requires policy version 3. When you add a condition via gcloud, the version upgrades automatically. For the full picture, see IAM Conditions.

How to read an IAM policy

Pull a policy with get-iam-policy like this:

# View the policy on a project
gcloud projects get-iam-policy my-app-prod

# View the policy on a specific resource
gcloud storage buckets get-iam-policy gs://my-app-prod-data
gcloud secrets get-iam-policy projects/my-app-prod/secrets/api-token

When reading the output, look for:

  • The role: check it matches your intent. Prefer predefined roles over basic roles like roles/editor.
  • The members: watch for allUsers or allAuthenticatedUsers, which grant public or near-public access.
  • The scope: remember that project-level output does not show inherited bindings from parent folders.
  • Conditions: if a binding has a condition block, check what it restricts. Conditions only work with version 3.
  • The etag: save this before making any changes. It protects your write from overwriting a concurrent update.
# Filter to find all roles held by one specific member
gcloud projects get-iam-policy my-app-prod \
  --flatten="bindings[].members" \
  --filter="bindings.members:alice@example.com" \
  --format="table(bindings.role)"

How to modify policies safely

There are two approaches to modifying a policy. For a complete CLI walkthrough with more examples, see Managing IAM with gcloud. For infrastructure-as-code workflows, see Managing IAM with Terraform.

Incremental binding commands (use this by default)

These commands add or remove a single binding atomically. They handle the read-modify-write cycle internally and will not touch any other bindings.

# Add one binding to a project
gcloud projects add-iam-policy-binding my-app-prod \
  --member="group:platform-engineers@example.com" \
  --role="roles/logging.viewer"

# Remove the same binding
gcloud projects remove-iam-policy-binding my-app-prod \
  --member="group:platform-engineers@example.com" \
  --role="roles/logging.viewer"

# Resource-level binding on a bucket (narrower than project level)
gcloud storage buckets add-iam-policy-binding gs://my-app-prod-data \
  --member="serviceAccount:etl-job@my-app-prod.iam.gserviceaccount.com" \
  --role="roles/storage.objectCreator"

Read-modify-write (for controlled bulk changes)

When you need to make several changes at once, or manage IAM as code, export the current policy first, edit the exported file, and apply it back. The etag in the exported file is your safety net.

# Step 1: export the current policy
gcloud projects get-iam-policy my-app-prod --format=json > policy.json

# Step 2: edit policy.json (add, remove, or modify bindings)

# Step 3: apply the updated policy
gcloud projects set-iam-policy my-app-prod policy.json
set-iam-policy is destructive

set-iam-policy replaces the full policy on the target resource in one operation. Bindings that exist in the current policy but are absent from your file are deleted immediately, with no warning and no undo. This can silently remove production access for services and users that depend on it. Always start from an exported file, review the diff carefully, and prefer incremental binding commands for routine changes.

When to use this

Working with IAM policies directly comes up in several common scenarios:

  • Giving a team viewer access to a project: set one binding at the project level so every member of a Google Group can view resources without individual setup.
  • Granting a workload access to one specific resource: set a resource-level binding so a service account can only reach the one bucket or secret it needs. See service accounts for how to set these up.
  • Auditing who can access a secret or service: read the policy at every relevant level to get the full picture of who has access and why.
  • Preparing to use IAM Conditions: understand the current policy structure and version before adding conditional bindings to avoid unintended changes.
  • Debugging a permission denied error: read the policy on the resource, then check parent levels too. See Fixing IAM Access Denied errors for a step-by-step approach.
  • Moving from basic roles to predefined roles: audit current project-level policies and replace overly broad bindings with narrower, resource-specific ones.

Common mistakes

  1. Writing a policy file from scratch and running set-iam-policy. A hand-written file will not include any existing bindings. Running set-iam-policy deletes everything not in the file. Always export the current policy first, make your edits to that file, and review the diff before applying.

  2. Using set-iam-policy for a single binding change. For routine additions and removals, use add-iam-policy-binding and remove-iam-policy-binding. They are atomic and cannot accidentally remove unrelated bindings.

  3. Setting a binding at project level when resource level is enough. If a service account needs access to one bucket, setting the binding at project level gives it access to all buckets in the project. Use the most specific scope the resource supports.

  4. Forgetting that access is inherited from parent resources. When debugging unexpected access, check the folder and organisation policies as well as the project policy. Inherited bindings do not appear in the project-level output from get-iam-policy.

  5. Confusing a role with a policy. A role is a set of permissions. A policy is the document that grants roles to members. Granting someone a role means adding a binding inside a policy β€” it does not change the role itself.

  6. Not checking the policy version before re-applying a version 3 policy. If you export a policy that contains conditions (version 3) and apply it using an older tool or workflow, conditions may be silently dropped. Verify the version field and use a compatible tool chain.

Frequently asked questions

What is an IAM policy in GCP?

An IAM policy is a JSON document attached to a GCP resource such as a project, bucket, or secret. It defines who has what access by listing bindings, each of which maps a role to one or more members. Policies exist at the organisation, folder, project, and individual resource level, and they all apply together.

What is the difference between an IAM policy and an IAM role?

A role is a named collection of permissions. For example, roles/storage.objectViewer grants read access to objects. A policy is the document attached to a resource that contains bindings. A binding says which members hold which role on that resource. The policy holds the bindings; the role defines the permissions those bindings grant.

Can a child resource override or block inherited IAM access?

No. IAM access is additive across the hierarchy. If a user has a role at the folder or organisation level, that access applies to every resource below it. Child resources can grant additional access but cannot revoke permissions granted by a parent. This is why it matters to set bindings at the right level.

When should I use project-level IAM instead of resource-level IAM?

Use project-level IAM when access naturally applies across the whole project, such as giving a team viewer access to monitor everything. Use resource-level IAM when you want to grant narrow access to one specific resource, such as giving a service account access to a single bucket or secret. Resource-level is safer because it limits what a compromised identity can reach.

What does the etag field do in a GCP IAM policy?

The etag is a concurrency control token. When you read a policy with get-iam-policy, the response includes an etag representing the current state. When you write back a modified policy with set-iam-policy, GCP checks that the etag in your file still matches the current etag on the resource. If someone else changed the policy between your read and write, the etags will not match and the write fails, preventing you from overwriting their changes.

Does set-iam-policy replace the full policy?

Yes. set-iam-policy replaces the entire policy on the target resource. Any bindings that exist in the current policy but are missing from your file are permanently deleted. Always export the current policy first, edit that exported file, and use add-iam-policy-binding or remove-iam-policy-binding for routine single-binding changes instead.

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