gcp-pd-snapshot-use-cross-project

A principal with compute.snapshots.useReadOnly on a shared snapshot and compute.disks.create can restore it as a disk and read all on-disk data across project boundaries.

derived gcp emits CanReadData

match

A conjunctive graph pattern. Variables (?x) bind node ids; every clause must hold.

{'snapshot': None} CrossProjectTrust {'external_project': None}

where

node_type(?snapshot) == Snapshot ?principal (in ?external_project) has EFFECTIVE compute.snapshots.useReadOnly on ?snapshot ?principal has EFFECTIVE compute.disks.create in their project ?principal has EFFECTIVE compute.instances.attachDisk OR can create an instance to attach the new disk

emit

source typeIdentity
target typeStorage
source<principal in external project>
target<snapshot (disk data)>
permissionscompute.snapshots.useReadOnly compute.disks.create compute.instances.attachDisk
conditionsiam_permission resource_policy key_permission
state logicACTIVE if all three permissions confirmed and snapshot IAM grants the principal access; CONDITIONAL(key_permission) if the source disk was CMEK-encrypted (snapshot inherits the kmsKeyName) — the principal must also hold cloudkms.cryptoKeyVersions.useToDecrypt on the wrapping key to read the plaintext data (cite decrypt-gates-secret-read in credential-chains.yaml); BLOCKED if the disk was CSEK-encrypted (requires caller-supplied key bytes, not IAM-controllable).

Narrative

{principal.name} can create a disk from {snapshot.name} (compute.snapshots.useReadOnly + compute.disks.create) and attach it to an instance in their project (compute.instances.attachDisk), reading all on-disk data including secrets, credentials, and database files.

Raw rule rules/derived/gcp/pd.yaml

id: gcp-pd-snapshot-use-cross-project
emits: CanReadData
description: A principal with compute.snapshots.useReadOnly on a shared snapshot and compute.disks.create
  can restore it as a disk and read all on-disk data across project boundaries.
applies_to:
- gcp
match:
- - snapshot: null
  - CrossProjectTrust
  - external_project: null
where:
- node_type(?snapshot) == Snapshot
- ?principal (in ?external_project) has EFFECTIVE compute.snapshots.useReadOnly on ?snapshot
- ?principal has EFFECTIVE compute.disks.create in their project
- ?principal has EFFECTIVE compute.instances.attachDisk OR can create an instance to attach the new disk
emit:
  source_type: Identity
  target_type: Storage
  source: <principal in external project>
  target: <snapshot (disk data)>
  permissions:
  - compute.snapshots.useReadOnly
  - compute.disks.create
  - compute.instances.attachDisk
  conditions:
  - iam_permission
  - resource_policy
  - key_permission
  state_logic: "ACTIVE if all three permissions confirmed and snapshot IAM grants the principal access;\
    \ CONDITIONAL(key_permission) if the source disk was CMEK-encrypted (snapshot inherits the kmsKeyName)\
    \ \u2014 the principal must also hold cloudkms.cryptoKeyVersions.useToDecrypt on the wrapping key\
    \ to read the plaintext data (cite decrypt-gates-secret-read in credential-chains.yaml); BLOCKED if\
    \ the disk was CSEK-encrypted (requires caller-supplied key bytes, not IAM-controllable)."
  false_positive_note: "compute.snapshots.useReadOnly alone is insufficient \u2014 compute.disks.create\
    \ is also required to instantiate a disk from the snapshot, and compute.instances.attachDisk (or compute.instances.create)\
    \ is needed to mount it. Confirm all three in the effective permission set. CMEK gate: diskEncryptionKey.kmsKeyName\
    \ on the source disk propagates to the snapshot; without KMS decrypt on that key the raw snapshot\
    \ data cannot be decrypted -> downgrade to CONDITIONAL(key_permission). CSEK: immediately BLOCKED."
  derived_from:
  - '<CrossProjectTrust edge: snapshot -> external_project>'
  - <compute.snapshots.useReadOnly effective permission on ?snapshot>
  - <compute.disks.create + compute.instances.attachDisk effective permissions>
  narrative: '{principal.name} can create a disk from {snapshot.name} (compute.snapshots.useReadOnly +
    compute.disks.create) and attach it to an instance in their project (compute.instances.attachDisk),
    reading all on-disk data including secrets, credentials, and database files.'
move · open · esc close