Configuring resource-based access

This topic describes how to set resource-based access restrictions using conditional role bindings in your Cloud Identity and Access Management (Cloud IAM) policies. By using resource attributes in a condition expression, you can grant a sub scope of the role binding based on the resource name, resource type, and/or Google Cloud service.

Before you begin

  • Read the Cloud IAM Conditions overview to understand the basics of Cloud IAM conditional policies.
  • Review the resource attributes that can be used in a condition expression.
  • The resource name attribute can control access to the following Google Cloud services:
    • Compute Engine
    • Cloud Key Management Service
    • Cloud Storage
    • Cloud Spanner

Granting access to a group of resources based on resource name prefixes

A conditional role binding can be used to grant access to members for resources whose resource names match a prefix, such as Compute Engine virtual machine (VM) instances whose names start with a certain string. The resource name prefix is typically used to group resources intended for certain functionality or that have certain properties.

Consider the following example: The software company ExampleCo runs workloads on certain VM instances that may operate on sensitive healthcare data. Other non-sensitive workloads must run in the same project, and ExampleCo wants to ensure its developers have limited access to VM instances that operate on sensitive data. To accomplish this goal, data-sensitive VM instances are named with a sensitiveAccess prefix, and other VM instances are named with a devAccess prefix. Then, conditional role bindings are used to ensure that the developers can remain productive with normal devAccess VM instances, while restricting access to sensitiveAccess VM instances.

While you can use the resource.name condition attribute alone to restrict access, it's common to accompany the restriction with specific resource.type and resource.service attributes to decrease the chance of over/under granting on resources of different types but similar name. The example in this section specifies access restrictions using both the resource.name and resource.type attributes.

To grant access based on a name prefix to Compute Engine disks and instances in a project:

Console

  1. Open the IAM page in the Cloud Console.

    Open the IAM page

  2. Click Select a project, choose a project, and click Open.
  3. From the list of members, locate the desired member and click the button.
  4. From the Edit permissions panel, locate the desired role to configure a condition for. Then under Condition, click Add condition.
  5. In the Edit condition panel, enter a title and optional description for the condition.
  6. You can add a condition expression using either the Condition Builder or the Condition Editor. The condition builder provides an interactive interface to select your desired condition type, operator, and other applicable details about the expression. The condition editor provides a text-based interface to manually enter an expression using CEL syntax.

    Condition Builder:

    1. Click the Add drop-down and click Grouped conditions.
    2. From the Condition type drop-down, select Resource Type.
    3. From the Operator drop-down, select is.
    4. From the Resource Type drop-down, select compute.googleapis.com/Disk.
    5. Click the first Add button immediately beneath the condition you just entered to add another clause to the expression.
    6. From the Condition type drop-down, select Resource Name.
    7. From the Operator drop-down, select Starts with.
    8. In the Value field, enter the resource name in the appropriate format, such as projects/project-123/zones/us-central1-a/disks/devAccess for a disk whose name starts with devAccess.
    9. To the left of each condition type, click And to ensure both clauses must be true.
    10. Click the Add button directly above the Save button to add another grouped set of conditions.
    11. From the Condition type drop-down, select Resource Type.
    12. From the Operator drop-down, select is.
    13. From the Resource Type drop-down, select compute.googleapis.com/Instance.
    14. Click the first Add button immediately beneath the condition you just entered to add another clause to the expression.
    15. From the Condition type drop-down, select Resource Name.
    16. From the Operator drop-down, select Starts with.
    17. In the Value field, enter the resource name in the appropriate format, such as projects/project-123/zones/us-central1-a/instances/devAccess for an instance whose name starts with devAccess.
    18. To the left of each condition type, click And to ensure both clauses must be true.
    19. Click the Add button directly above the Save button to add the third grouped set of conditions.
    20. To ensure this condition does not impact other resources, add the following clauses as well: From the Condition type drop-down, select Resource Type.
    21. From the Operator drop-down, select is not.
    22. From the Resource Type drop-down, select compute.googleapis.com/Disk.
    23. Click the first Add button immediately beneath the condition you just entered to add another clause to the expression.
    24. From the Condition type drop-down, select Resource Type.
    25. From the Operator drop-down, select is not.
    26. From the Resource Type drop-down, select compute.googleapis.com/Instance.
    27. To the left of each condition type, click And to ensure both clauses must be true.
    28. When you're finished, the condition builder should look similar to the following:

    29. Click Save to apply the condition.
    30. Once the Edit condition panel is closed, click Save again from the Edit permissions panel to update your Cloud IAM policy.

    Condition Editor:

    1. Click the Condition Editor tab and enter the following expression:

      (resource.type == "compute.googleapis.com/Disk" &&
      resource.name.startsWith("projects/project-123/regions/us-central1/disks/devAccess")) ||
      (resource.type == "compute.googleapis.com/Instance" &&
      resource.name.startsWith("projects/project-123/zones/us-central1-a/instances/devAccess")) ||
      (resource.type != "compute.googleapis.com/Disk" &&
      resource.type != "compute.googleapis.com/Instance")
    2. After entering your expression, you can optionally choose to lint the CEL syntax by clicking Run Linter above the text box on the top-right.
    3. Click Save to apply the condition.
    4. Once the Edit condition panel is closed, click Save again from the Edit permissions panel to update your Cloud IAM policy.

gcloud command

Cloud IAM policies are set using the read-modify-write pattern.

Execute the gcloud projects get-iam-policy command to get the current Cloud IAM policy for the project. In the following example, the JSON version of the policy is downloaded to a path on disk.

Command:

gcloud projects get-iam-policy [PROJECT-ID] --format json > [FILE-PATH]

The JSON format of the Cloud IAM policy is downloaded:

{
  "bindings": [
    {
      "members": [
        "group:devs@example.com"
      ],
      "role": "roles/compute.instanceAdmin"
    }
  ],
  "etag": "BwWKmjvelug=",
  "version": 1
}

To configure the policy with a resource name prefix condition, add the following highlighted condition expression. If you are not using version 263.0.0 or newer of the gcloud tool, ensure that you've updated the version value to 3. If you are using a newer version of the gcloud, the maximum policy value will automatically be set for you:

{
  "bindings": [
    {
      "members": [
        "group:devs@example.com"
      ],
      "role": "roles/compute.instanceAdmin",
      "condition": {
          "title": "Dev_access_only",
          "description": "Only access to devAccess* VMs",
          "expression":
            "(resource.type == 'compute.googleapis.com/Disk' &&
            resource.name.startsWith('projects/project-123/regions/us-central1/disks/devAccess')) ||
            (resource.type == 'compute.googleapis.com/Instance' &&
            resource.name.startsWith('projects/project-123/zones/us-central1-a/instances/devAccess')) ||
            (resource.type != 'compute.googleapis.com/Instance' &&
            resource.type != 'compute.googleapis.com/Disk')"
      }
    }
  ],
  "etag": "BwWKmjvelug=",
  "version": 3
}

Next, set the new conditional policy by executing the gcloud projects set-iam-policy command:

gcloud projects set-iam-policy [PROJECT-ID] [FILE-PATH]

The new conditional role binding will grant devs@example.com permissions in the following way:

  • All disk and instance permissions are granted only if the resource name starts with devAccess
  • All other permissions in the Instance Admin role are granted for all other resource types

REST API

Call projects.getIamPolicy() to get the current Cloud IAM policy for the project.

POST https://cloudresourcemanager.googleapis.com/v1/projects/[PROJECT-ID]:getIamPolicy

The response body will contain the project's Cloud IAM policy:

{
  "bindings": [
    {
      "members": [
        "user:example@gmail.com"
      ],
      "role": "roles/compute.instanceAdmin"
    }
  ],
  "etag": "BwWKmjvelug=",
  "version": 1
}

To configure the policy with resource-based access, add the following highlighted condition expression. Ensure that you've updated the version value to 3:

{
  "bindings": [
    {
      "members": [
        "group:devs@example.com"
      ],
      "role": "roles/compute.instanceAdmin",
      "condition": {
          "title": "Dev_access_only",
          "description": "Only access to devAccess* VMs",
          "expression":
            "(resource.type == 'compute.googleapis.com/Disk' &&
            resource.name.startsWith('projects/project-123/regions/us-central1/disks/devAccess')) ||
            (resource.type == 'compute.googleapis.com/Instance' &&
            resource.name.startsWith('projects/project-123/zones/us-central1-a/instances/devAccess')) ||
            (resource.type != 'compute.googleapis.com/Instance' &&
            resource.type != 'compute.googleapis.com/Disk')"
      }
    }
  ],
  "etag": "BwWKmjvelug=",
  "version": 3
}

Call projects.setIamPolicy() to set the new conditional Cloud IAM policy for the project, including the updated policy in the request body:

POST https://cloudresourcemanager.googleapis.com/v1/projects/[PROJECT-ID]:setIamPolicy

The response will be the updated policy.

Important usage considerations for resource-based conditions

When adding a resource-based in condition, it's important to consider how the granted permissions will be affected by the condition.

Custom roles

Consider the following example, which involves custom roles. An admin wants to create a custom role that grants access to create VM instances, but only allows the user to create VM instances in a project with a resource name that starts with the name prefix staging, using the disks with the same name prefix.

To accomplish this goal, ensure that the granted role contains the required permissions to create a VM instance, which means permissions on disk and instance resource types. Then, ensure that the condition expression also enforces resource type restrictions for both disks and instances. Beyond these two types, other permissions in the role are not granted.

The following condition expression will result in unexpected behavior. Permissions to operate on Compute Engine VMs are blocked:

"expression":
"resource.type == 'compute.googleapis.com/Disk' &&
 resource.name.startsWith('projects/project-123/regions/us-central1/disks/staging')"

The following condition expression includes both disks and instances, and will enforce the naming restriction properly for these two types:

"expression":
"(resource.type == 'compute.googleapis.com/Disk' &&
  resource.name.startsWith('projects/project-123/regions/us-central1/disks/staging')) ||
 (resource.type == 'compute.googleapis.com/Instance' &&
  resource.name.startsWith('projects/project-123/zones/us-central1-a/instances/staging'))"

The following condition expression includes both disks and instances, and will enforce the naming restriction properly for these two types, and does not further restrict access for other permissions in the same role:

"expression":
"(resource.type == 'compute.googleapis.com/Disk' &&
  resource.name.startsWith('projects/project-123/regions/us-central1/disks/staging')) ||
 (resource.type == 'compute.googleapis.com/Instance' &&
  resource.name.startsWith('projects/project-123/zones/us-central1-a/instances/staging')) ||
 (resource.type != 'compute.googleapis.com/Disk' &&
  resource.type != 'compute.googleapis.com/Instance')"

Parent-only permissions

In Google Cloud's resource hierarchy, some of the permissions in a role that affect a child resource are intended to be enforced at the parent level only. For example, to list crypto keys for Cloud KMS, the user must be granted the cloudkms.cryptokeys.list permission on the key ring that contains the cryptographic keys, not the keys themselves. These kinds of permissions are called parent-only permissions, and only apply to list operations.

To properly grant access to *.*.list permissions when using conditions, the condition expression should set the resource.service and resource.type attributes according to the parent resource type of the target resources to be listed.

Consider the following examples. Using the Compute Engine example above, the following expression prevents access to compute.disks.list and compute.instances.list permissions, since the resource on which this permissions are checked have resource.type attribute value of cloudresourcemanager.googleapis.com/Project.

"expression":
"(resource.type == 'compute.googleapis.com/Disk' &&
  resource.name.startsWith('projects/project-123/regions/us-central1/disks/devAccess')) ||
 (resource.type == 'compute.googleapis.com/Instance' &&
  resource.name.startsWith('projects/project-123/zones/us-central1-a/instances/devAccess'))"

It is common that these list permissions are granted together with other permissions for regular operations on the resource. To increase to scope of grant in this case, you can either extend the scope for the cloudresourcemanager.googleapis.com/Project type only, or extend the scope to all other permissions not of type instance or disk.

"expression":
"(resource.type == 'compute.googleapis.com/Disk' &&
  resource.name.startsWith('projects/project-123/regions/us-central1/disks/devAccess')) ||
 (resource.type == 'compute.googleapis.com/Instance' &&
  resource.name.startsWith('projects/project-123/zones/us-central1-a/instances/devAccess')) ||
 resource.type == 'cloudresourcemanager.googleapis.com/Project'"

or

"expression":
"(resource.type == 'compute.googleapis.com/Disk' &&
  resource.name.startsWith('projects/project-123/regions/us-central1/disks/devAccess')) ||
 (resource.type == 'compute.googleapis.com/Instance' &&
  resource.name.startsWith('projects/project-123/zones/us-central1-a/instances/devAccess')) ||
 (resource.type != 'compute.googleapis.com/Disk' &&
  resource.type != 'compute.googleapis.com/Instance')"