IAM Conditions in GCP Explained: Examples, CEL, and Use Cases
Temporary access grants are one of the most common causes of permission creep. You give a contractor access to logs for a three-week engagement. The project ends. Six months later they still have access, but nobody remembered to remove the binding. IAM Conditions solve this by encoding the expiry or scope restriction directly into the binding. When the condition becomes false, the access disappears automatically. No cleanup step, no reminder needed.
What are IAM Conditions in GCP?
IAM Conditions let you attach a conditional rule to a role binding in a GCP IAM policy. Normally, a binding says: “this principal has this role”. With a condition, it says: “this principal has this role, but only when this expression is true”.
The condition is evaluated at request time by Google Cloud. If the expression
evaluates to true, the binding applies and access is granted.
If the expression evaluates to false, the binding is ignored
for that request, as if it were not there at all.
This is useful when you need time-limited access, resource-scoped access within a broader project binding, or context-aware access without creating a separate set of roles. It is not a replacement for correct least privilege design. It is a tool you layer on top of it.
IAM Conditions in simple terms
Think of it like a time-limited visitor badge
A regular IAM binding is like a permanent access badge — the person always gets in. An IAM Condition turns it into a visitor badge with an expiry date printed on it. Or one that only works on weekdays. Or one that only opens doors on floor 3. The badge still grants the same access when it works. It just does not work all the time.
You are not changing what the role grants. You are adding a rule that controls when the binding is active. The access is real and full-strength when the condition is met. Conditions are not a way to make access “weaker”. They are a way to make it conditional.
Conditions are written in Common Expression Language (CEL), but you do not need to be a programmer to understand them. Most useful conditions are simple one-line expressions like “the current time is before this date” or “the resource name starts with this prefix”.
How IAM Conditions work
When Google Cloud receives an API request, it evaluates the IAM policy on the resource being accessed. If a binding includes a condition, the evaluation follows these steps:
- A principal (user, group, or service account) makes a request to a GCP resource.
- Google Cloud looks up the IAM policy on that resource and any parent resources.
- For each binding that matches the principal and role, it checks whether a condition is present.
- If there is a condition, it evaluates the CEL expression against the request attributes (time, resource name, tags).
- If the condition evaluates to
true, the binding applies and the permission is granted. - If the condition evaluates to
false, that binding is skipped. The request is denied by that binding unless another binding covers the permission.
A few things to be clear about, because they trip people up:
- The condition lives on the binding, not on the role.
- It does not modify the role or narrow the list of permissions the role grants.
- It does not replace binding the role at the right resource level. It is an addition to that, not a substitute.
- If the condition is
true, the role applies with full strength.
A condition restricting access to business hours on a binding with
roles/editor still grants full project-wide editor access
during those hours. Conditions control when the binding is
active. They do not change what it grants.
Fix the role first. Then add a condition if you need one.
Conditions require policy version 3. When you add a condition via
gcloud, the policy version is automatically upgraded. If you
later read and re-apply the policy with a tool that does not support
version 3, the conditions may be silently stripped. This is a real risk
when mixing Terraform and manual gcloud changes on the same
policy. See IAM Policy Structure
for more on versions and safe policy updates.
Common attributes used in IAM Conditions
Conditions can only reference attributes that GCP exposes in the request context. The four most commonly used are:
request.time
The timestamp of the API request. Use this for expiry dates, business-hours windows, and date-range restrictions.
request.time < timestamp("2026-09-30T23:59:59Z")request.time is supported by the widest range of GCP
services. If you are new to IAM Conditions, start here. Expiry dates are
the lowest-risk way to experiment with conditions in production.
resource.name
The full resource identifier string, such as
projects/_/buckets/my-bucket/objects/reports/q3.csv.
Use this to restrict a binding to resources whose name matches a specific
prefix. This is the most common way to scope access within a shared bucket
without creating a separate one.
resource.name.startsWith("projects/_/buckets/my-bucket/objects/etl-team/")resource.type
The resource type string, such as storage.googleapis.com/Object
or bigquery.googleapis.com/Dataset. Use this when a binding
includes a role that covers multiple service types and you want to restrict
it to one. Less commonly needed than resource.name.
resource.type == "storage.googleapis.com/Object"resource.tags
Resource tags attached to a resource (not labels; tags are a separate system in GCP). Use this for environment-based or sensitivity-level access patterns, where you want bindings to apply automatically to resources tagged with a particular value without listing them by name. Requires that resources are consistently tagged before the condition can take effect.
resource.tags["123456789/environment"] == "staging"Not every attribute is supported by every GCP service. Most modern
services support request.time and resource.name.
Support for resource.tags varies more widely. Check the
IAM conditions documentation for the specific service before writing
tag-based conditions for production use.
IAM Conditions examples
Expiring contractor access
Grant a contractor access to logs for the duration of an audit engagement. When the end date passes, the binding becomes inactive automatically with no manual cleanup required.
gcloud projects add-iam-policy-binding my-app-prod \
--member="user:contractor@external.com" \
--role="roles/logging.viewer" \
--condition="expression=request.time < timestamp('2026-09-30T23:59:59Z'),\
title=expires-end-of-q3-2026,\
description=Temporary access for infrastructure audit ending Q3 2026"The title field appears in the IAM Console and in audit logs.
Always write a descriptive title that explains the intent and expected
expiry. The next reviewer should not have to guess why the condition exists.
Restricting a service account to one folder in a shared bucket
An ETL pipeline shares a bucket with other teams. Rather than giving it a separate bucket, you can restrict it to only access objects under its own prefix using a resource name condition on the binding.
gcloud storage buckets add-iam-policy-binding gs://my-app-prod-shared \
--member="serviceAccount:etl-job@my-app-prod.iam.gserviceaccount.com" \
--role="roles/storage.objectAdmin" \
--condition="expression=resource.name.startsWith(\
'projects/_/buckets/my-app-prod-shared/objects/etl-team/'),\
title=restrict-to-etl-prefix,\
description=ETL job is restricted to the etl-team/ prefix in the shared bucket"If this ETL job is the only workload needing a prefix restriction, a dedicated bucket scoped to that service account is simpler and harder to misconfigure. Use prefix conditions when sharing a bucket genuinely makes operational or cost sense, not just because it is possible.
Business-hours access to a sensitive dataset
Restrict an analyst’s BigQuery access to weekday business hours in a specific timezone, reducing the risk window for credential misuse outside working hours.
# Set this condition expression on the BigQuery dataset IAM binding.
# This restricts access to Monday-Friday, 09:00-18:00 London time.
#
# expression:
# request.time.getDayOfWeek("Europe/London") >= 1 &&
# request.time.getDayOfWeek("Europe/London") <= 5 &&
# request.time.getHours("Europe/London") >= 9 &&
# request.time.getHours("Europe/London") < 18
#
# getDayOfWeek returns 0 for Sunday, 6 for Saturday.
# Adjust the timezone string to match the user's actual working hours.When to use IAM Conditions
IAM Conditions are well suited to these situations:
Temporary access that should expire automatically. Contractors, incident responders, or auditors who need short-term access are the ideal use case. Set an expiry date in the condition and you never have to remember to revoke it.
Restricting access within a shared resource. When multiple teams share a bucket and giving each team its own bucket is not practical, a
resource.nameprefix condition can fence each team into its own area of the bucket.Time-windowed access for sensitive operations. Restricting production database access to business hours, or limiting deployment permissions to your change-window schedule, adds a timing control without extra infrastructure.
Tag-based access without managing explicit resource lists. When your resources are consistently tagged by environment or sensitivity,
resource.tagsconditions let bindings apply automatically to the right resources without listing them by name.Adding a time dimension without creating additional roles. When you already have the right role at the right scope and you just need to limit when it is active, a condition is the correct tool.
When not to use IAM Conditions
Conditions are not the right tool in every situation. Know when something else solves the problem more cleanly:
Conditions do not narrow what a role grants. If roles/editor
is too permissive, the fix is a narrower predefined role from
the predefined role list.
Adding a time or resource condition on top of an overly broad role still
grants that broad access whenever the condition is true.
When you should just bind the role at a narrower resource level. If a service account needs access to one specific bucket, grant the role on that bucket directly using a resource-level binding. That is simpler, clearer, and harder to get wrong than a project-level binding with a resource name condition.
When you need organisation-wide guardrails. If you want to prevent anyone from creating resources in a specific region, or block public access to Cloud Storage across all projects, those are governance constraints. Use Organisation Policies rather than conditions on individual bindings.
When simpler IAM design would be clearer. A conditional binding is harder to read, audit, and debug than a plain binding. If a simpler approach (narrower role, lower resource scope, or a separate service account) achieves the same outcome, prefer it.
When the service does not support conditions. Using conditions on a binding that includes permissions from an unsupported service may result in the condition being silently ignored, granting broader access than intended. Verify service support first.
IAM Conditions vs narrower resource scoping
This is the most common source of confusion. Both approaches can restrict which resources an identity can access, but they work differently and have different trade-offs.
Resource-level IAM binding
You bind a role directly on a specific resource, for example granting
roles/storage.objectViewer on a single bucket rather than at
project level. This is the simplest and most readable approach. GCP enforces
the scope automatically; there is no expression to maintain or debug.
# Grant access to one specific bucket only
gcloud storage buckets add-iam-policy-binding gs://my-team-data \
--member="serviceAccount:analytics@my-app-prod.iam.gserviceaccount.com" \
--role="roles/storage.objectViewer"Project-level binding with a resource name condition
You bind the role at project level but add a condition that restricts it to resources matching a name prefix. This achieves a similar effect but requires correctly written CEL, is harder to read in the policy output, and applies to all resources in the project that share the prefix, even ones you did not intend.
Use resource-level binding when you can. Use a resource name condition when you genuinely cannot use a resource-level binding, such as when you want to grant access to an object prefix within a Cloud Storage bucket rather than the bucket itself.
Choose the correct role first. Then bind it at the narrowest applicable resource level. Then, if you also need a time or context dimension, add a condition. Do not use a condition to compensate for a binding that is already too broad.
IAM Conditions vs Organisation Policies
Personal schedule vs building rules
An IAM Condition is like a schedule printed on your access card. It controls when you can get in. An Organisation Policy is like a fire safety rule that applies to the whole building regardless of what is on anyone’s card. The two systems are independent. Even someone with a permanent badge cannot override a building-wide rule.
IAM Conditions control when an individual binding is active. They are attached to a specific role grant for a specific principal. They say: “this identity has this role, but only when X is true.”
Organisation Policies enforce governance constraints across all projects in a folder or organisation, regardless of identity. They say: “this operation is not allowed for anyone.” For example: no public Cloud Storage buckets, no resources outside approved regions, no service account key creation.
If you want to prevent a class of action across your whole organisation and not just for one principal, use an Organisation Policy. IAM Conditions cannot replicate that. For the specific case of location restrictions, see Restricting Resource Locations.
How to test and troubleshoot IAM Conditions
Conditions add complexity. A mistake can either grant broader access than intended or block legitimate access silently. Test carefully before rolling out to production.
Test with a low-risk principal first. Before applying a condition-bearing binding to a production service account, test the expression on a non-critical identity to confirm it evaluates as expected.
Verify the policy version is 3. Run
gcloud projects get-iam-policy PROJECT_IDand check theversionfield. If you seeversion: 1on a policy that should contain a condition, the condition has been stripped. See Managing IAM with gcloud for safe policy read and write patterns.Check denied requests in audit logs. When a condition evaluates to false, the denial is recorded in the Policy Denied audit log. The log entry includes the condition expression and its result, which tells you exactly why the request was blocked.
Confirm service support before production rollout. For services that do not fully support conditions, the condition may be ignored and grant broader access than intended.
Keep expressions simple and focused. A single-purpose expression is easier to verify than one combining time, resource name, and tags. If you need multiple constraints, consider whether splitting them across separate bindings is clearer.
# Find Policy Denied log entries to see blocked conditional access
gcloud logging read \
'logName="projects/my-app-prod/logs/cloudaudit.googleapis.com%2Fpolicy"' \
--project=my-app-prod \
--limit=20 \
--format='table(timestamp,protoPayload.authenticationInfo.principalEmail,protoPayload.status.message)'For broader strategies around reading and acting on audit log data, see Cloud Audit Logs and Detecting Suspicious Activity with Logs.
Common mistakes
Using a condition instead of narrowing the resource scope. If you want to restrict a service account to one bucket, bind the role on that bucket directly. A project-level binding with a
resource.namecondition achieves a similar effect but is harder to read, easier to get wrong, and breaks in subtle ways if the resource name format changes.Thinking the condition narrows the role’s permissions. A condition only controls whether the binding is active. When the condition evaluates to true, the full role applies with all its permissions. If the role is too broad, the fix is a narrower role, not a condition.
Writing complex CEL expressions. Expressions combining time, resource name, and tags in one block are hard to read, harder to test, and easy to get wrong in ways that silently block legitimate access or grant access you did not intend. Keep each condition focused on one thing.
Not writing a descriptive condition title. The
titlefield appears in the Console and in audit logs. Without a clear title likeexpires-contractor-q3-2026, the next person reviewing the policy cannot tell why the condition was added or when it should expire.Not testing the expression before production rollout. A typo in a CEL expression, an incorrect resource name format, or an off-by-one error in a time comparison can block access entirely or grant it when it should be denied. Test on a non-production binding first, then check the audit logs to confirm the expression is behaving as expected.
Assuming all services support the same condition attributes. Support for
resource.tagsin particular varies. Do not assume a condition that works correctly on Cloud Storage will behave identically on a less common service.
Summary
- IAM Conditions attach a conditional rule to a role binding; the binding is only active when the condition evaluates to
true - Common uses: automatic expiry for temporary access, resource name prefix restrictions, business-hours time windows, tag-based access
- Conditions are written in CEL and use attributes including
request.time,resource.name,resource.type, andresource.tags - Policy version 3 is required for any policy containing conditions
- Conditions add a “when” dimension; they do not narrow the role’s permissions or replace correct resource-level binding
- When you can bind the role directly on the target resource, do that instead of using a project-level binding with a resource name condition
- Organisation Policies enforce governance constraints across all identities; IAM Conditions control when one binding is active. These are different tools with different purposes.
- Policy Denied audit logs record when a condition evaluated to false, showing exactly why access was blocked
- Always write a descriptive title on every condition so future reviewers understand its intent and expected expiry
Frequently asked questions
What are IAM Conditions in GCP?
IAM Conditions are rules you attach to an IAM role binding. The binding is only active when the condition evaluates to true. You write conditions in Common Expression Language (CEL), and they can test attributes like the time of the request, the resource name, the resource type, or resource tags. They live inside the binding itself and require policy version 3.
What can IAM Conditions check?
The most commonly supported attributes are: request.time (the timestamp of the request), resource.name (the full resource identifier), resource.type (the resource type string), and resource.tags (tags assigned to the resource). Not every attribute is supported by every GCP service, so check service-specific documentation before writing conditions for production use.
Do IAM Conditions replace IAM roles?
No. Conditions only control when a binding is active. They do not change which permissions the role grants or which resources the binding applies to. A broad role like roles/editor is still broad when the condition evaluates to true. Get role selection and resource scope right first, then add conditions to layer on a time or context dimension.
Do IAM Conditions work with all GCP services?
No. A service must explicitly support the IAM conditions framework for conditions on bindings to be evaluated. Most modern GCP services do, but some older or specialised services do not. On unsupported services, a condition on a binding that includes permissions for that service may be ignored. Always verify service support before relying on conditions in production.
What happens when an IAM Condition evaluates to false?
The binding is treated as if it does not exist. The request is denied by that binding unless another binding grants the required permission without a condition. The denial is recorded in the Policy Denied audit log, which includes the condition expression, its result, and the requesting identity. This gives you visibility into exactly when and why conditional access was blocked.