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.

derived gcp emits CanReadData

match (effective permission)

{ "action": "storage.objects.get", "resource_type": "google.storage.Object" }

where

bucket IS CMEK-encrypted (defaultKmsKeyName set)

emit

source typeIdentity
target typeStorage
source<principal>
target<CMEK GCS Bucket / ObjectStorage node>
permissionsstorage.objects.get
conditionsiam_permission resource_policy key_permission
state logicACTIVE 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."
move · open · esc close