Write rules for custom cloud controls

When you create a custom cloud control, you use Common Expression Language (CEL) expressions to create the rules that evaluate the properties of the scanned resource. Supported resources are Cloud Asset Inventory resource types. REST APIs are not supported.

Your expressions can be checks of a single value or more complex compound expressions that check multiple values or conditions. Either way, the expression must resolve to a boolean false to trigger a finding.

CEL expressions that evaluate resource properties must conform to the following rules:

  • The properties that you specify in a CEL expression must be properties of the scanned resource, as defined in the API definition of the resource type.

  • All enums in a CEL expression must be represented as strings. For example, the following is a valid expression for the cloudkms.googleapis.com/CryptoKeyVersion resource type:

    resource.data.state = "PENDING_GENERATION"
  • The result of the CEL expressions that you define in the condition property must be a Boolean. A finding is triggered only if the result is false.

To find the name for a resource, see Asset types.

For more information about CEL, see the following:

Example CEL expressions

The following table lists some CEL expressions that you can use to evaluate resource properties.

Resource type Description CEL expression
cloudkms.googleapis.com/CryptoKey Check the Cloud KMS key rotation period has(resource.data.rotationPeriod) && resource.data.rotationPeriod < duration('60h')
compute.googleapis.com/Network Match Virtual Private Cloud peering rule to network peers resource.data.selfLink.matches('https://www.googleapis.com/compute/v1/projects/PROJECT_ID/global/networks/default') || resource.data.peerings.exists(p, p.network.matches('https://www.googleapis.com/compute/v1/projects/PROJECT_ID/global/networks/shared$'))
cloudfunctions.googleapis.com/CloudFunction Only allow internal ingress traffic for a Cloud Run function has(resource.data.ingressSettings) && resource.data.ingressSettings.matches('ALLOW_INTERNAL_ONLY')
compute.googleapis.com/Instance Resource name matches pattern resource.data.name.matches('^gcp-vm-(linux|windows)-v\\\\d+$')
serviceusage.googleapis.com/Service Only allow storage-related APIs to be enabled resource.data.state == 'ENABLED' && !( resource.data.name.matches('storage-api.googleapis.com') || resource.data.name.matches('bigquery-json.googleapis.com') || resource.data.name.matches('bigquery.googleapis.com') || resource.data.name.matches('sql-component.googleapis.com') || resource.data.name.matches('spanner.googleapis.com'))
sqladmin.googleapis.com/Instance Only permit public IP addresses that are on the allowlist (resource.data.instanceType == 'CLOUD_SQL_INSTANCE' && resource.data.backendType == 'SECOND_GEN' && resource.data.settings.ipConfiguration.ipv4Enabled ) && (resource.data.ipAddresses.all(ip, ip.type != 'PRIMARY' || ip.ipAddress.matches('IP_ADDRESS')))
dataproc.googleapis.com/Cluster Check if project IDs in a Managed Service for Apache Spark cluster contain the substrings testing or development has(resource.data.projectId) && !resource.data.projectId.contains('testing') || !resource.data.projectId.contains('development')