gcp-gcs-read-data-cmek
storage.objects.get on a CMEK bucket; the Storage service agent (not the caller) decrypts - CanReadData is CONDITIONAL(key_permission) on the service agent's KMS grant.
match (effective permission)
{
"action": "storage.objects.get",
"resource_type": "google.storage.Object"
}
where
bucket IS CMEK-encrypted (defaultKmsKeyName set)
emit
| source type | Identity |
|---|---|
| target type | Storage |
| source | <principal> |
| target | <CMEK GCS Bucket / ObjectStorage node> |
| permissions | storage.objects.get |
| conditions | iam_permission resource_policy key_permission |
| state logic | ACTIVE if the Storage service agent (service-PROJECT_NUMBER@gs-project-accounts.iam.gserviceaccount.com) holds cloudkms.cryptoKeyVersions.useToDecrypt (roles/cloudkms.cryptoKeyEncrypterDecrypter) on defaultKmsKeyName 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 (storage.objects.get then returns encrypted bytes that cannot be decrypted, or a KMS error). Note: objects uploaded before CMEK was configured use Google-managed keys and are always readable by authorized callers regardless of KMS state. |
Narrative
{principal.name} holds storage.objects.get on CMEK bucket {bucket.name} (defaultKmsKeyName: {key.name}); the Storage service agent - not the caller - decrypts objects using cloudkms.cryptoKeyEncrypterDecrypter on {key.name}. Access is ACTIVE while the service agent retains that grant and the key is enabled.
Raw rule rules/derived/gcp/gcs.yaml
id: gcp-gcs-read-data-cmek
emits: CanReadData
description: "storage.objects.get on a CMEK bucket; the Storage service agent (not the caller) decrypts\
\ \u2014 CanReadData is CONDITIONAL(key_permission) on the service agent's KMS grant."
match_effective_permission:
action: storage.objects.get
resource_type: google.storage.Object
where:
- bucket IS CMEK-encrypted (defaultKmsKeyName set)
emit:
source_type: Identity
target_type: Storage
source: <principal>
target: <CMEK GCS Bucket / ObjectStorage node>
permissions:
- storage.objects.get
conditions:
- iam_permission
- resource_policy
- key_permission
state_logic: 'ACTIVE if the Storage service agent (service-PROJECT_NUMBER@gs-project-accounts.iam.gserviceaccount.com)
holds cloudkms.cryptoKeyVersions.useToDecrypt (roles/cloudkms.cryptoKeyEncrypterDecrypter) on defaultKmsKeyName
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 (storage.objects.get then returns encrypted bytes that cannot be decrypted, or a KMS
error). Note: objects uploaded before CMEK was configured use Google-managed keys and are always readable
by authorized callers regardless of KMS state.'
confidence: min(contributing_confidences) * 0.95
derived_from:
- storage.objects.get effective permission on CMEK bucket
- Storage service-agent KMS binding on defaultKmsKeyName
false_positive_note: "GCP-SPECIFIC: the key_permission gate is on the STORAGE SERVICE AGENT, not the\
\ caller. Do NOT require the accessing principal to hold cloudkms.* \u2014 a caller with only storage.objects.get\
\ reads CMEK objects while the service agent keeps useToDecrypt. Requiring caller KMS decrypt (AWS-style)\
\ produces false negatives. Downgrade to CONDITIONAL/BLOCKED only based on the service agent's grant\
\ and the key's enabled state. Objects with per-object kmsKeyName (different from defaultKmsKeyName)\
\ are gated on THEIR OWN key \u2014 handle per-object CMEK separately when the object metadata shows\
\ a different key. Objects with no kmsKeyName field use Google-managed keys (transparent, always ACTIVE\
\ for authorized callers)."
narrative: "{principal.name} holds storage.objects.get on CMEK bucket {bucket.name} (defaultKmsKeyName:\
\ {key.name}); the Storage service agent \u2014 not the caller \u2014 decrypts objects using cloudkms.cryptoKeyEncrypterDecrypter\
\ on {key.name}. Access is ACTIVE while the service agent retains that grant and the key is enabled."