Join our Newsletter — 33% off our NHI Course
Home› Guides› GCP IAM Deny Policies and IAM Conditions Guide:…
Guide Cloud & Workload Identity

GCP IAM Deny Policies and IAM Conditions Guide: Writing CEL Conditions and Rolling Out Guardrails

← All guides
By NHI Mgmt Group, edited by Lalit Choda Updated 14 November 2026 17 min read
On this page

Allow policies in Google Cloud only ever add access, so without IAM Conditions and deny policies the only way to limit a powerful role is to not grant it at all. IAM Conditions let you attach a Common Expression Language (CEL) expression to a role binding so it applies only to certain resources, times or tags, and deny policies let you block named permissions for named principals whatever roles they hold. This guide is the hands-on companion to the Google Cloud IAM and Service Accounts Guide: it covers writing conditions that do what you intend, the tag, time and API attributes worth knowing, how to roll out deny policies without breaking Google's own service agents, and where Principal Access Boundary policies fit. It is written for cloud platform, IAM and security engineers who already know basic Google Cloud IAM.

This is an independent guide from NHI Mgmt Group, based on Google Cloud's public IAM documentation as of November 2026. It is not sponsored or reviewed by Google. Product features change, so confirm settings against Google Cloud's current documentation.

Key takeaways

  • Deny is evaluated first. IAM checks relevant deny policies before allow policies, and a deny set on an organization or folder cannot be undone by a more permissive policy lower down.
  • Conditions narrow, they never override. A conditional binding does not limit an unconditional binding of the same role to the same principal, so remove the unconditional grant or the condition does nothing.
  • Deny conditions understand tags only. Denial conditions use the resource tag functions; time, name and type attributes belong in allow bindings.
  • Tag control is access control. When conditions depend on tags, whoever can bind tag values can change who has access, so govern tag permissions as tightly as IAM itself.
  • Exempt service agents and break-glass accounts deliberately. Broad deny rules need exception principals for Google service agents and a tested emergency group, or they cause outages.

How conditions, deny and boundaries combine

Google Cloud now has three policy types that decide whether a request succeeds. Knowing which one to reach for is most of the design work.

Policy typeAttached toWhat it doesCondition attributesTypical use
Allow policy with conditional bindingsResource (one allow policy per resource), inherited down the hierarchyGrants a role only when the CEL expression is trueResource name, type, service and tags; request time; destination IP and port; request host and path; access levels; API attributesTemporary access, per-bucket or per-prefix grants, tag-scoped grants, limiting which roles an IAM admin can grant
Deny policyOrganization, folder or project (up to 500 deny policies and 500 deny rules per resource), inheritedBlocks listed permissions for listed principals, with optional exceptionsResource tag functions onlyGuardrails such as "nobody outside the platform group can create custom roles"
Principal Access Boundary (PAB) policyPrincipal sets through policy bindings, not inheritedLimits which projects, folders or organizations principals are eligible to accessprincipal.type and principal.subject on the bindingKeeping workforce or workload identities inside your own organization

Two evaluation facts drive everything else. Deny rules are checked before allow policies, and each deny policy is evaluated independently. A deny rule with a condition applies when the condition is true or cannot be evaluated, so a deny condition fails closed. A PAB policy can only block: it makes resources eligible, and allow policies still have to grant the access. PAB policies also fail closed when evaluation errors occur.

Writing IAM Conditions that do what you intend

A condition has a required title, an optional description and a required expression written in a subset of CEL. Statements combine with &&, || and !, with a limit of 12 logical operators per expression in an allow binding or deny rule (10 in a PAB binding). Conditions cannot be used with the basic roles (Owner, Editor, Viewer) or with allUsers and allAuthenticatedUsers, and only some resource types accept conditional bindings.

Policy version 3 and the etag

Conditional bindings require allow policy version 3. If a client reads a policy without asking for version 3, IAM returns version 1, omits the condition and shows the role with a _withcond_ suffix and a hash. A version 1 write without the etag can overwrite the policy and delete all its conditions. The console and gcloud CLI set the version automatically; scripts and Terraform providers calling the REST API should send "requestedPolicyVersion": 3 and always round-trip the etag, retrying the whole read-modify-write on a 409 Conflict.

Resource attributes

  • Always pair resource.name with resource.type: resource.name is only populated for some resource types. A condition on an inherited binding that uses an attribute the target resource does not provide can silently fail to grant access.
  • No wildcards: use startsWith(), endsWith(), extract(), == or !=. startsWith('projects/_/buckets/exampleco-site-assets/') matches objects in that bucket; == matches only an exact name.
  • Watch prefix over-match: Google's reference gives the example that startsWith('security-admin') also matches [email protected]. End prefixes with a delimiter such as /.
  • Equality only on type and service: resource.type and resource.service support only == and !=; prefer resource.type when the condition also uses resource.name.
  • Cloud Storage: bucket-level conditions need uniform bucket-level access. The storage.googleapis.com/objectListPrefix attribute works only in Credential Access Boundaries; putting it in a conditional role binding makes Cloud Storage methods fail. See the Google Cloud Storage Security Guide.

Time attributes

  • Expiring grants: request.time < timestamp('2026-12-31T23:59:59Z'). Use <, <=, > or >=; avoid == because timestamps carry millisecond precision. An invalid timestamp string means the condition never grants access.
  • Working hours: getters such as getHours() and getDayOfWeek() accept an IANA time zone, for example request.time.getHours('Europe/London') >= 8. The default is UTC. Indexing differs: getDayOfWeek() starts at Sunday = 0 and getHours() is zero-based, while getDate() is one-based.
  • Durations: duration('3600s') takes seconds with an s suffix and can be added to or subtracted from a timestamp.

Expiring conditions are useful for one-off access, but for routine elevation the Google Cloud Privileged Access Manager Guide covers entitlements with approval and audit, which can themselves carry conditions.

Tag attributes

resource.matchTag('123456789012/env', 'prod') and resource.hasTagKey('123456789012/env') use namespaced names; resource.matchTagId() and resource.hasTagKeyId() use permanent IDs. Google's guidance on the trade-off is direct: names are easier to read and manage declaratively, but a deleted and recreated tag with the same name inherits the old conditions, which can grant unintended access. Permanent IDs avoid that, at the cost of updating policies when tags are recreated. For production guardrails, prefer IDs.

API attributes: limiting what an IAM admin can grant

The iam.googleapis.com/modifiedGrantsByRole attribute lists the roles a set-allow-policy request changes. A binding for a project IAM admin with the condition api.getAttribute('iam.googleapis.com/modifiedGrantsByRole', []).hasOnly(['roles/pubsub.editor', 'roles/pubsub.publisher']) lets that admin grant only those two roles. The attribute is accepted on projects, folders and organizations and recognised by services including Compute Engine, Cloud Run, Cloud Storage, Secret Manager and Resource Manager. Where a service does not recognise it, the limited admin's grant on that resource fails; BigQuery datasets are a documented exception, where dataset role grants are not limited. This is the cleanest way to delegate IAM administration to application teams without handing them T1098.003 Additional Cloud Roles as a privilege escalation path.

Condition patterns reference

GoalExpression patternWhere it worksWatch out for
Grant expires at a fixed timerequest.time < timestamp('2026-12-31T23:59:59Z')Allow bindingsExpired bindings stay in the policy; clean them up
Office hours onlyrequest.time.getHours('Europe/London') >= 8 && request.time.getHours('Europe/London') < 18Allow bindingsZero-based hours; time zone defaults to UTC
Only Compute Engine instancesresource.type == 'compute.googleapis.com/Instance'Allow bindingsOther resource types in the project get nothing from this binding
Objects under one bucketresource.type == 'storage.googleapis.com/Object' && resource.name.startsWith('projects/_/buckets/BUCKET/')Allow bindingsTrailing slash; uniform bucket-level access
Only resources tagged non-productionresource.matchTagId('tagKeys/KEY_ID', 'tagValues/VALUE_ID')Allow bindings and deny rulesTag binding permissions decide access
Deny everywhere except test!resource.matchTag('ORG_ID/env', 'test')Deny rulesUntagged resources are denied too; that is usually the intent
Limit which roles an admin can grantapi.getAttribute('iam.googleapis.com/modifiedGrantsByRole', []).hasOnly([...])Allow bindings for IAM admin rolesServices that do not recognise the attribute block the grant
IAP access from managed devices'accessPolicies/NUM/accessLevels/LEVEL' in request.auth.access_levelsBindings of roles/iap.tunnelResourceAccessor or roles/iap.httpsResourceAccessorAccess level name is case sensitive; role must contain only the IAP access permissions
Apply a PAB policy only to service accountsprincipal.type == 'iam.googleapis.com/ServiceAccount'PAB policy bindingsOnly principal.type and principal.subject are available

Keep conditional bindings under about 100 per allow policy, as Google recommends, and remember the limit of 20 bindings for the same role and principal with different conditions.

Authoring deny policies

A deny policy contains rules, and each rule has deniedPrincipals, optional exceptionPrincipals, deniedPermissions, optional exceptionPermissions and an optional denialCondition. Deny basics, such as why deny exists at all, are covered in the Google Cloud IAM and Service Accounts Guide; the details below are the ones that trip up authors.

  • Permission format: deny rules use the v2 format SERVICE_FQDN/RESOURCE.ACTION, so the familiar serviceusage.apiKeys.regenerate becomes apikeys.googleapis.com/apiKeys.regenerate, and creating custom roles is iam.googleapis.com/roles.create. Only some permissions can be denied: check each one in Google's supported permissions table.
  • Permission groups: SERVICE_FQDN/RESOURCE.*, SERVICE_FQDN/*.* and SERVICE_FQDN/*.VERB are accepted for certain services and include permissions added later. That future coverage is useful for guardrails but means a rule can start blocking a new feature without anyone changing the policy.
  • Principal identifiers: deny rules use IAM v2 identifiers such as principal://goog/subject/[email protected], principalSet://goog/group/[email protected] and principalSet://goog/public:all (everyone). Groups keep rules short and stay inside the limit of 500 groups and domains and 2,500 principals across a resource's deny policies.
  • Exceptions, not carve-outs in allow: because deny wins, the only way to let the platform team through is exceptionPrincipals. Use one well-governed group per rule.
  • Service agents: when the denied set includes service agents, for example principalSet://goog/public:all, Google advises adding your service agents as exception principals so Google-managed services keep working.
  • Who manages deny: Deny Admin (roles/iam.denyAdmin) can create, update and delete deny policies and Deny Reviewer (roles/iam.denyReviewer) can view them; both are granted on the organization. Keep Deny Admin to a very small group outside day-to-day project administration.

Policies are created with gcloud iam policies create POLICY_ID --attachment-point=cloudresourcemanager.googleapis.com/projects/PROJECT_ID --kind=denypolicies --policy-file=policy.json. Changes generally take effect within two minutes but can take seven minutes or more, so do not test immediately after a change and assume the result is final.

GuardrailDenied permissions (v2 format)Denied principalsExceptions
Only the IAM platform team manages custom rolesiam.googleapis.com/roles.create, iam.googleapis.com/roles.delete and related role permissionsprincipalSet://goog/public:allIAM platform group, break-glass group, service agents
No API key regeneration outside the owning teamapikeys.googleapis.com/apiKeys.regenerateAll principalsOwning team group
Protect production from deletionProject and resource deletion permissions confirmed in the supported permissions tableAll principalsChange automation service account, break-glass group; condition on the production tag

Rolling out deny policies safely

Because a deny rule takes effect for every matching request as soon as it propagates, do the testing before you widen the attachment point.

  1. Design from evidence: list the permissions you want to block and confirm each is deniable. Use Cloud Audit Logs to see who actually uses them today; see the Google Cloud Audit Logs and Log Sinks Guide.
  2. Create the exception groups first: a platform group, a break-glass group whose use pages the security team, and the service agent exceptions. See the Break-Glass Account Guide.
  3. Pilot on a sandbox project: attach the policy at a test project, wait for propagation and test with a denied and an excepted identity.
  4. Check with Policy Troubleshooter: gcloud policy-intelligence troubleshoot-policy iam RESOURCE --principal-email=EMAIL --permission=PERMISSION evaluates allow and deny policies on the resource and its ancestors (the beta version adds PAB, in Preview). Tag conditions are evaluated automatically; time and name conditions need context you supply. Running it needs Security Reviewer and Deny Reviewer.
  5. Widen in rings: move the attachment point from project to a non-production folder, then to production folders, then to the organization, leaving time between steps for teams to report breakage.
  6. Keep it as code: store policy files in version control with review, and include the etag in updates. See the Policy-as-Code for Authorisation Guide.
  7. Rollback: deleting or editing the deny policy restores access once the change propagates. Make sure Deny Admin holders can act out of hours, because they are the only people who can undo a bad rule.

Tags as an access control dependency

Tags are inherited from organization, folder and project, a child can override an inherited value with a different value for the same key, and tag changes take effect in about two to seven minutes. That makes them powerful for conditions and also means binding or unbinding a tag, or moving a project, can change access. Google recommends binding a safe default value for each key used in conditions at the organization (for example enforcement: off) and overriding it only on specific resources. Treat the permission to bind tag values (granted through the Resource Manager tag user role) as a privileged permission, scope it to the specific tag values a team needs, and review tags and conditional policies before moving resources between folders. Keep sensitive data out of tag names and values, as Google advises. The Google Cloud Organization Policy Guide covers organization policy constraints that can also reference tags, including mandatory tags (in Preview).

Principal Access Boundary in practice

PAB policies answer a different question: not "may this principal do this?" but "which resources is this principal eligible to touch at all?". Each rule lists projects, folders or organizations with the only valid effect, ALLOW; bindings attach policies to principal sets such as workforce or workload identity pools, Google Workspace domains, or project, folder and organization principal sets (and agent identities). A principal set can have up to 10 PAB policies, and eligibility is the union of them.

  • Pin the enforcement version: details.enforcementVersion controls which permissions the policy can block. Using latest can remove access unexpectedly as new permissions are added.
  • Do not leave principals unbound by accident: a principal subject to no PAB policy is eligible for everything, so when narrowing, keep each principal under at least one policy.
  • Google-owned resources: principals under PAB lose access to Google-owned public resources (organization 433637338589, which holds resources such as BigQuery public datasets) unless a policy makes that organization eligible.
  • Agents and workloads: PAB is a strong fit for workload identity federation pools and AI agent identities that should never act outside your organization. See the Google Cloud Agent Identity guide.

Where identity fits

The principals most in need of conditions and deny rules are usually non-human: CI/CD service accounts that need a deployer role only on tagged environments, automation that should never create service account keys or custom roles, and federated workloads that should stay inside one organization. Write deny rules against principal sets that include service accounts, use PAB conditions on principal.type to target service accounts, and keep exceptions to named automation identities with owners. The Top 10 GCP IAM Mistakes page lists the over-privilege patterns these controls are meant to stop.

Detection and measurement

  • Policy changes: alert on any create, update or delete of deny policies and PAB policies and bindings, and on SetIamPolicy calls that remove conditions or add unconditional bindings for roles you normally grant conditionally (T1098 Account Manipulation).
  • Tag changes: alert on tag binding changes for keys used in conditions, and on project moves between folders.
  • Denied requests: watch PERMISSION_DENIED errors after each rollout ring; the Google Cloud PERMISSION_DENIED Troubleshooting Guide covers telling deny, PAB and missing-allow failures apart.
  • Break-glass use: every action by an exception principal on a guarded permission should open a ticket.
MetricTarget
Guardrail permissions (custom roles, key creation, deletion of production) covered by an organization or folder deny rule100 percent of the agreed list
Unconditional bindings of roles your standard says must be conditionalZero
Expired time-bound bindings still present in allow policiesZero after the monthly clean-up
Holders of Deny Admin and tag administration rolesNamed, reviewed quarterly, kept to a handful
Workload identity pools and agent identities bound to a PAB policy100 percent

Common failure patterns

  • Condition added, unconditional grant left in place. The conditional binding looks restrictive in review but the older unconditional binding of the same role still applies.
  • Version 1 tooling stripping conditions. A script that reads without requestedPolicyVersion: 3 and writes without the etag silently deletes every condition on the policy.
  • Name-based tags recreated. A tag deleted and recreated with the same namespaced name inherits conditions written for the old one.
  • Deny on everyone without service agent exceptions. Managed services that act through service agents start failing hours after rollout, when a scheduled job first needs the permission.
  • Prefix conditions without a delimiter. startsWith('projects/_/buckets/prod') also matches prod-archive and any other bucket sharing the prefix.
  • Testing too soon. Engineers test a deny policy seconds after creating it, see access still working, and widen the rule before propagation completes.

Framework mapping

ControlNIST SP 800-53 Rev 5NIST CSF 2.0CIS Controls v8.1ISO/IEC 27001:2022 Annex A
Conditional and time-bound grantsAC-2, AC-6PR.AA-0565.15, 5.18
Deny guardrails and PAB boundariesAC-3PR.AA-0568.2, 8.3
Limiting delegated IAM administrationAC-6PR.AA-0558.2
Policy as code with review and rollbackCM-3GV.RR48.32

Practitioner checklist

  • Make every API client request policy version 3 and send the etag on writes.
  • Pair every resource.name condition with a resource.type check and end prefixes with a delimiter.
  • Remove unconditional bindings that shadow conditional ones.
  • Use permanent tag IDs in production conditions and bind a safe default tag value at the organization.
  • Restrict tag binding permissions to the values each team needs.
  • Limit delegated IAM admins with modifiedGrantsByRole and hasOnly().
  • Write deny rules in v2 permission format, confirm each permission is deniable, and add service agent and break-glass exceptions.
  • Pilot deny policies on a sandbox project, verify with Policy Troubleshooter, then widen folder by folder.
  • Bind PAB policies to workload identity pools and agent identities with a pinned enforcement version.
  • Alert on changes to deny policies, PAB bindings, tags used in conditions and Deny Admin membership.

Standards and references

Related NHI Mgmt Group resources: Google Cloud IAM and Service Accounts Guide · Google Cloud Organization Policy Guide · Top 10 GCP IAM Mistakes · Google Cloud Privileged Access Manager Guide

Free weekly newsletter

Subscribe to the NHI & AI Identity Journal

The latest on NHI and Agentic AI security – articles, research, breaches, news and events every week.

Bonus 33% off our NHI Course when you subscribe.

NHIMG Editorial Note
Researched and written by the NHI Mgmt Group editorial team using AI tools, under the editorial direction of Lalit Choda. Facts are drawn from the sources listed on this page and checked by our automated quality process. Last updated 14 November 2026. Spotted an error? Use our contact page.
NHI Mgmt Group, the independent authority on Non-Human Identity, IAM and Agentic AI security. nhimg.org