gcp-kms-service-agent-cmek-gate
A CMEK-encrypted Secret Manager secret is readable while the service agent holds useToDecrypt on the key.
match
A conjunctive graph pattern. Variables (?x) bind node ids; every clause must hold.
where
?secret is CMEK-encrypted (customerManagedEncryption.kmsKeyName references a Cloud KMS key ?key)
the CONSUMING service agent (e.g. service-PROJECT_NUMBER@gcp-sa-secretmanager.iam.gserviceaccount.com) holds cloudkms.cryptoKeyVersions.useToDecrypt (roles/cloudkms.cryptoKeyEncrypterDecrypter) on ?key
emit
| source | <principal> |
|---|---|
| target | <CMEK-encrypted Secret> |
| permissions | secretmanager.versions.access cloudkms.cryptoKeyVersions.useToDecrypt |
| conditions | iam_permission resource_policy key_permission service_state |
| state logic | ACTIVE if the caller holds versions.access AND the SERVICE AGENT holds useToDecrypt on ?key AND >=1 key version 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 errors). |
Narrative
{principal.name} can read CMEK secret {secret.name}; the consuming service agent (not the caller) decrypts it via cryptoKeyEncrypterDecrypter on {key.name} - access holds while the agent retains that grant and the key is enabled.
Raw rule rules/derived/gcp/kms.yaml
id: gcp-kms-service-agent-cmek-gate
emits: CanReadSecret
description: A CMEK-encrypted Secret Manager secret is readable while the service agent holds useToDecrypt
on the key.
match:
- - principal: null
- CanReadSecret
- secret: null
where:
- ?secret is CMEK-encrypted (customerManagedEncryption.kmsKeyName references a Cloud KMS key ?key)
- the CONSUMING service agent (e.g. service-PROJECT_NUMBER@gcp-sa-secretmanager.iam.gserviceaccount.com)
holds cloudkms.cryptoKeyVersions.useToDecrypt (roles/cloudkms.cryptoKeyEncrypterDecrypter) on ?key
emit:
source: <principal>
target: <CMEK-encrypted Secret>
permissions:
- secretmanager.versions.access
- cloudkms.cryptoKeyVersions.useToDecrypt
conditions:
- iam_permission
- resource_policy
- key_permission
- service_state
state_logic: ACTIVE if the caller holds versions.access AND the SERVICE AGENT holds useToDecrypt on
?key AND >=1 key version 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 errors).
confidence: min(contributing_confidences) * 0.93
derived_from:
- <CanReadSecret edge_id>
- <service-agent useToDecrypt binding on ?key>
false_positive_note: 'GCP-SPECIFIC: the key_permission gate is on the SERVICE AGENT, NOT the caller.
A caller with only secretmanager.versions.access reads a CMEK secret while the service agent keeps
useToDecrypt. Requiring caller KMS decrypt (AWS Secrets Manager model) produces false negatives. Only
downgrade to CONDITIONAL/BLOCKED based on the SERVICE AGENT''s grant and the key''s enabled state.
This rule REFINES an existing CanReadSecret from Secret Manager; it does not invent a new secret read
from KMS (KMS holds no secret payloads).'
narrative: "{principal.name} can read CMEK secret {secret.name}; the consuming service agent (not the\
\ caller) decrypts it via cryptoKeyEncrypterDecrypter on {key.name} \u2014 access holds while the\
\ agent retains that grant and the key is enabled."