gcp-secretmanager-read-cmek-gate
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). |
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."