gcp-secretmanager-read-cmek-gate

Read a CMEK-encrypted secret; decrypt is done by the Secret Manager service agent, not the caller.

derived gcp emits CanReadSecret

match (effective permission)

{ "action": "secretmanager.versions.access", "resource_type": "google.secretmanager.Secret" }

where

secret IS CMEK-encrypted (customerManagedEncryption.kmsKeyName set)

emit

source<principal>
target<CMEK-encrypted secret in scope>
permissionssecretmanager.versions.access
conditionsiam_permission resource_policy key_permission
state logicACTIVE if the Secret Manager service agent (service-PROJECT_NUMBER@gcp-sa-secretmanager.iam.gserviceaccount.com) holds cloudkms.cryptoKeyVersions.useToDecrypt (roles/cloudkms.cryptoKeyEncrypterDecrypter) on kmsKeyName AND the key/version is ENABLED; CONDITIONAL(key_permission) if the service-agent grant or key state is unknown; BLOCKED if the service agent lacks the grant OR the key is DISABLED/DESTROYED OR a KMS deny applies (versions.access then returns an error).

Narrative

{principal.name} can read CMEK-encrypted secret {secret.name} (secretmanager.versions.access); the Secret Manager service agent (not the caller) decrypts it via cloudkms.cryptoKeyEncrypterDecrypter on {key.name} - access holds while the agent retains that grant and the key is enabled.

Raw rule rules/derived/gcp/secretmanager.yaml

id: gcp-secretmanager-read-cmek-gate
emits: CanReadSecret
description: Read a CMEK-encrypted secret; decrypt is done by the Secret Manager service agent, not the
  caller.
match_effective_permission:
  action: secretmanager.versions.access
  resource_type: google.secretmanager.Secret
where:
- secret IS CMEK-encrypted (customerManagedEncryption.kmsKeyName set)
emit:
  source: <principal>
  target: <CMEK-encrypted secret in scope>
  permissions:
  - secretmanager.versions.access
  conditions:
  - iam_permission
  - resource_policy
  - key_permission
  state_logic: ACTIVE if the Secret Manager service agent (service-PROJECT_NUMBER@gcp-sa-secretmanager.iam.gserviceaccount.com)
    holds cloudkms.cryptoKeyVersions.useToDecrypt (roles/cloudkms.cryptoKeyEncrypterDecrypter) on kmsKeyName
    AND the key/version is ENABLED; CONDITIONAL(key_permission) if the service-agent grant or key state
    is unknown; BLOCKED if the service agent lacks the grant OR the key is DISABLED/DESTROYED OR a KMS
    deny applies (versions.access then returns an error).
  confidence: min(contributing_confidences) * 0.95
  derived_from:
  - <effective secretmanager.versions.access grant>
  - <service-agent KMS binding on kmsKeyName>
  false_positive_note: "GCP-SPECIFIC: the key_permission gate is on the SERVICE AGENT, NOT the caller.\
    \ Do NOT require the accessing principal to hold cloudkms.* \u2014 a caller with only versions.access\
    \ reads a CMEK secret while the service agent keeps useToDecrypt. Requiring caller KMS decrypt (AWS\
    \ Secrets Manager model) produces false negatives here. Only downgrade to CONDITIONAL/BLOCKED based\
    \ on the SERVICE AGENT's grant and the key's enabled state."
  narrative: "{principal.name} can read CMEK-encrypted secret {secret.name} (secretmanager.versions.access);\
    \ the Secret Manager service agent (not the caller) decrypts it via cloudkms.cryptoKeyEncrypterDecrypter\
    \ on {key.name} \u2014 access holds while the agent retains that grant and the key is enabled."
move · open · esc close