This post was originally published on this site

In Google Cloud, Identity and Access Management (IAM) helps you maintain access control over your cloud resources and operations. While it includes other features, this is its primary purpose. If you ever tried to harden security over your application, you know the importance of the Principle of Least Privilege (PoLP) ‒ grant the absolute minimum permissions to your users and workloads to allow them to perform their tasks. You reach it through use of predefined roles and custom roles and setting up a combination of Allow and Deny IAM policies at project, folder, or organization level. Using a combination of Allow and Deny policies along the resource hierarchy is an effective way to control access. This approach lets you enforce PoLP across many different scenarios.

The existing flexible control can be insufficient when resources in the project are shared between multiple workloads or used by more than one team. In many such scenarios, it is possible to bind IAM policies to a specific resource in the project. For example, consider the difference between granting the role Artifact Registry Editor (roles/artifactregistry.editor) on a project vs. granting it on a specific repository in the project. In the former case, the access is granted to ANY repository in the project. In the latter case, users will have the editor access only to a specific repository. However, binding IAM policies to a resource or service level isn’t always possible. This is when it is time to use IAM conditions. Let’s look at two distinct examples that demonstrate the power of conditions when hardening access management: one for traditional administrative roles, and one for modern AI integrations.

Use Case 1: Constraining the Power of Admins

This case demonstrates how to restrict the specific operations that broad IAM roles are authorized to perform. You can easily scope administrative privileges for managing specific resources in a project by granting a “resource creator” role at the project level and an editor role on a selected resource. It is far more challenging to constrain IAM Admin Roles that are intended to grant access to operations rather than specific resources. A representative example would be the IAM Admin role (roles/iam.admin). Users granted this role can grant themselves any other role or create a new one. It greatly exceeds practical needs. The first step is to narrow the access by using the Project IAM Admin role (roles/resourcemanager.projectIamAdmin) that provides administrative privileges only at the level of the project.

It is possible, however, to restrict the granted privileges even further. For example, suppose you grant the Project IAM Admin role to your builder service account that creates resources and deploys workloads. The workloads only need access to the BigQuery and Agent Platform APIs (formerly Vertex APIs) and permission to write logs and traces. For such a case you can use the following gcloud CLI command or its alternative in Terraform:

code_block
<ListValue: [StructValue([('code', 'gcloud projects add-iam-policy-binding "${PROJECT_ID}" \rn –member="serviceAccount:${SA_MAIL}" \rn –role="roles/resourcemanager.projectIamAdmin" \rn –condition="^:^\rntitle=LimitedIAMAdmin:\rnexpression=api.getAttribute('iam.googleapis.com/modifiedGrantsByRole', [])\rn.hasOnly([\rn'roles/aiplatform.user',\rn'roles/bigquery.jobUser',\rn'roles/bigquery.dataViewer',\rn'roles/cloudtrace.agent',\rn'roles/logging.logWriter'\rn])"'), ('language', ''), ('caption', )])]>

The value of the condition parameter is defined using Common Expression Language (CEL) syntax. First it customizes a field delimiter to be a colon instead of a comma and then describes the condition fields title and expression. The expression field uses functions for API attributes to identify which roles are being granted to allow granting only the roles in the comma delimited list. The same operation in Terraform will look very similar. Using input variables instead of environment variables, it will look like this:

code_block
<ListValue: [StructValue([('code', 'resource "google_project_iam_member" "limited_project_iam_admin" {rn project = var.project_idrn role = "roles/resourcemanager.projectIamAdmin"rn member = "serviceAccount:${var.sa_email}"rn condition {rn title = "LimitedIAMAdmin"rn expression = <<-EOTrn api.getAttribute('iam.googleapis.com/modifiedGrantsByRole', []).hasOnly([rn 'roles/aiplatform.user',rn 'roles/bigquery.jobUser',rn 'roles/bigquery.dataViewer',rn 'roles/cloudtrace.agent',rn 'roles/logging.logWriter'rn ])rn EOTrn }rn}'), ('language', ''), ('caption', )])]>

Use Case 2: Control over MCP Server Access

This case is about hardening access to specific services behind a single set of permissions.

Google exposes access to a subset of cloud resources and services via MCP Servers that expose Model Context Protocol (MCP) endpoints. The access to these servers is granted using the predefined MCP Tool User (roles/mcp.toolUser) role. This role grants access to ALL available MCP servers (for a project where an IAM policy is set). Using conditions helps to narrow the access to a specific MCP server.

code_block
<ListValue: [StructValue([('code', 'gcloud projects add-iam-policy-binding $PROJECT_ID \rn –member="serviceAccount:$SA_EMAIL" \rn –role="roles/mcp.toolUser" \rn –condition="^:^\rntitle=bigquery_mcp_server_only:\rnexpression=resource.service == 'bigquery.googleapis.com'"'), ('language', ''), ('caption', )])]>

Notice that the value compared to the resource.service attribute is not the MCP server endpoint (which is bigquery.googleapis.com/mcp) but the endpoint of the service. It is possible to narrow the access scope further to the level of the specific MCP tools. For this you will need to use API attributes again. The following expression limits the service account access to the level of only two BigQuery MCP tools.

code_block
<ListValue: [StructValue([('code', "expression=api.getAttribute('mcp.googleapis.com/tool.name', '') in [\rn'mcp_bigquery-mcp_execute_sql',\rn'mcp_bigquery-mcp_execute_sql_readonly'\rn]"), ('language', ''), ('caption', )])]>

Note that if you condition the IAM policy binding at the MCP tool level, you don’t need to validate the resource.service attribute.

For experimenting with MCP server access you can use the Getting Started with Google MCP Servers codelab and modify its gcloud projects add-iam-policy-binding commands.

And Even More

Besides enforcing precise control when using predefined roles, IAM conditions let you craft access management based on the time of the request. For example, the following condition’s expression allows access only during daytime on weekdays:

code_block
= 9 &&\rnrequest.time.getHours(‘Europe/Berlin’) = 1 &&\rnrequest.time.getDayOfWeek(‘Europe/Berlin’) <= 5"), ('language', ''), ('caption', )])]>

The expression limits access from 9 o’clock in the morning to 5 o’clock in the evening according to the “Europe/Berlin” timezone from Monday to Friday (days of the week range from 0 to 6, starting with Sunday).

IAM conditions allow controlling the identity of the actor using the principal attributes. However, it can easily become an anti-pattern. The recommended practice is to control the identity of actors allowed to use the policy through the list of the IAM policy’s principals instead of using the conditions.

Conclusion and More Resources

While IAM conditions give you surgical precision over Allow policies, you can take your defense-in-depth strategy even further with IAM Deny policies. With Deny Policies you can grant access using the predefined IAM roles with Allow policies and remove excessive permissions of the role to enforce PoLP. See the following resources for additional information about Deny policies:

You can use Google Skills for hands-on experience with IAM policies.