Homeβ€Ί Blogβ€Ί GCP Architecture Series #37 β€” IAM Conditions: Attribute-Based Access Control…
GCP Architecture GCP Architecture Series

GCP Architecture Series #37 β€” IAM Conditions: Attribute-Based Access Control

A condition looks like one feature: a title, a description, and a CEL expression. It is three. The same field appears in allow policy bindings, deny rules and Principal Access Boundary bindings, and in each one a different set of attributes is available and a different thing happens when the expression cannot be decided.

Verified against current vendor documentation on 19 September 2026. Pricing, limits and API behaviour were checked against the official docs on that date. Cloud services change fast — if you are reading this much later, treat the specifics as a starting point and re-check the linked sources.

Business Challenge

#36 noted in passing that denial conditions are a cut-down dialect. That turns out to be the general case rather than the exception.

1
"We will time-box the Owner grant with a condition"

Not possible. You cannot use conditions when you grant legacy basic roles — Owner, Editor and Viewer. This is the second functional consequence of the #32 rename, and the one people meet first.

Correct approach

Replace the legacy role with the narrowest predefined role that fits, then condition that binding. The condition requirement is often the best argument for finally removing an Owner grant.

2
"We will keep the public grant but restrict it by IP"

Also not possible. You cannot use conditions when you grant roles to all users or all authenticated users. The two principals from #31 that need constraining the most are the two that cannot carry one.

Correct approach

Public is binary in IAM. If access must be conditional, the grant cannot be to allUsers — use a real principal set, or put the control in front of the resource.

3
"We reused the expression from the role binding in a deny rule"

It will not mean the same thing, because conditions in deny policies only recognize resource tag functions. A resource.type or request.time test has no equivalent there.

Correct approach

Treat each policy type as a separate language with a shared syntax. Translate the intent through tags, or accept that the restriction cannot be expressed as a deny.

4
"We will add one conditional binding per project"

You have roughly a hundred. Google recommends no more than 100 conditional role bindings to a single allow policy, and beyond that you might exceed the overall size limit for allow policies — the #31 budget, consumed faster because conditions are verbose.

Correct approach

Push the variability into resource tags and write one conditional binding that reads them, rather than one binding per case.

Architecture

A condition is a title, an optional description, and an expression. The expression defines an attribute-based logic expression using a subset of the Common Expression Language, and can contain multiple statements where each statement evaluates one attribute.

Diagram: the structure of a Google Cloud IAM condition, the three policy types that accept one, the different attribute sets available in each, the opposite failure directions when an expression cannot be decided, and the two grants that cannot carry a condition at all
One structure, three homes, three attribute sets, and two opposite answers to the same failure.

Three places a condition can live

Policy typeAttributes availableIf the expression cannot be decided
Allow policy role binding
including Privileged Access Manager entitlements
Resource (type, name, service, tags), request (date and time, access levels, IAP host and path), and API attributes. Not stated in the same terms as the other two.
Deny policy rule Resource tag functions only. The deny applies. Fails closed.
Principal Access Boundary binding Principal attributes — the type of principal in the request, and its identity. Not enforced — the policy applies only if the condition evaluates to true.

Read the third column twice. An undecidable condition on a deny rule denies; an undecidable condition on a boundary binding leaves the boundary switched off. Both are guardrails, and they fail in opposite directions.

What you can actually test in a role binding

  • Resource — the classic use. resource.type == 'compute.googleapis.com/Instance' allows access to Compute Engine VM instances, but no other type of resource; resource.name.startsWith(...) scopes to a bucket prefix.
  • Date and time — expiry via request.time, or working-hours windows with getHours and getDayOfWeek in a named time zone.
  • Access levels — Access Context Manager levels such as a corporate network range. Narrower than it looks: the access levels attribute is available only when you use IAP.
  • API attributes — the sharpest and least known. api.getAttribute('iam.googleapis.com/modifiedGrantsByRole', []).hasOnly([...]) lets you grant someone the ability to manage bindings for one specific role and no other.
  • Resource tags — and note you can use the resource tags attribute in deny policy deny rules, which is what makes tags the only shared vocabulary across policy types.
Tags are the portable layer, and that is a design instruction

Of everything in the condition language, resource tags are the one attribute family that works in both allow bindings and deny rules. Every other attribute is confined to one policy type. So if a distinction matters enough to be expressed in more than one kind of policy — production versus test, regulated versus not — it has to exist as a tag, not as a naming convention or a resource type. Getting the tag taxonomy right is therefore a prerequisite for conditional access, not an afterthought, and it is the same taxonomy #36 needs for denial conditions.

Why This Architecture Holds Up

Three limits define the shape of the feature more than its capabilities do.

It cannot narrow an existing grant

This is #33's rule and it is worth restating here because conditions are where people expect it to be false. Adding a conditional binding beside an unconditional one for the same role changes nothing — the union still contains the unconditional term. A condition constrains the binding it sits on, and a binding is only ever an addition. To time-box access you must replace the grant, not annotate it.

It cannot apply to the grants that most need it

Legacy basic roles cannot carry conditions. Neither can grants to all users or all authenticated users. Those are, respectively, the broadest role and the broadest principal in the product. The feature designed to narrow access is unavailable on the two things most in need of narrowing — which is coherent rather than perverse: a conditional Owner grant would be a way to make an unreviewable grant look reviewable.

It does not travel between policy types

The structure is identical, the syntax is CEL in all three, and the expression is still not portable. Take a working resource.type condition from a role binding, paste it into a deny rule, and the deny rule is wrong — not rejected for using the wrong grammar, but wrong, because deny recognises only tag functions. Paste a principal.type test into a role binding and it belongs to Principal Access Boundary bindings instead.

What to do with this

  1. Build the tag taxonomy first. It is the only vocabulary shared by allow and deny conditions.
  2. Replace, do not annotate. A conditional binding added next to an unconditional one has no effect.
  3. Treat a required condition as a reason to drop a basic role, since the two are mutually exclusive.
  4. Budget around 100 conditional bindings per allow policy, and prefer one tag-driven binding to many near-identical ones.
  5. Check the attribute is available for your case before designing around it — access levels in particular are IAP-only.
  6. Write down the failure direction for each condition you deploy. Deny fails closed; a boundary binding simply does not enforce.

Key Architecture Decisions

DecisionChoose thisBecause
Expressing a distinction in both allow and deny A resource tag Tags are the only attribute family both accept.
Time-boxing an existing grant Replace the binding A conditional binding beside an unconditional one adds nothing.
Conditioning an Owner or Editor grant Not possible — change the role Conditions cannot be used with legacy basic roles.
Conditioning public access Not possible — change the principal Conditions cannot be used when granting to all users.
Many similar conditional bindings One binding reading a tag Stay under 100 per allow policy and within the size limit.
Reusing an expression across policy types Rewrite it Deny recognises tag functions only; boundary bindings take principal attributes.
Restricting which roles someone may grant An API attribute condition modifiedGrantsByRole with hasOnly limits role granting.
Designing around access levels Confirm IAP is in the path The attribute is available only when you use IAP.
An undecidable deny condition Assume it denies If it cannot be evaluated, the deny rule applies.

Closing Thought

The tidiest way to describe IAM Conditions is that the product has one condition object and three condition languages. The object is the same everywhere — title, description, expression — and that sameness is exactly what misleads. An expression is not a portable statement of intent; it is a sentence in whichever dialect the enclosing policy speaks, and the dialects differ in vocabulary and in what they do when the sentence cannot be resolved.

The practical consequence is that tags stop being metadata and become the access model. They are the only nouns both the allow and deny dialects understand, which means any distinction you want enforced from both directions must be a tag before it can be a condition. That is a larger architectural commitment than the feature's presentation suggests, and it is worth making deliberately rather than discovering halfway through writing a deny rule that the expression you wanted does not exist.

Next in this series

#38 finally brings the tools: Policy Troubleshooter and Policy Analyzer — the machinery that answers the #34 question properly, including what conditions actually evaluated to on a real request.

Comments

How was your experience?
Your feedback helps improve this site.
PoorExcellent