gcp-pubsub-cross-project-consume-trust

A Pub/Sub subscription IAM binding granting pubsub.subscriptions.consume to a principal from another GCP project enables the external principal to read/drain the subscription (data exfiltration via messaging-chains).

derived gcp emits CrossProjectTrust

match

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

{'match_record': {'resource_type': 'google.pubsub.Subscription', 'field': 'iamPolicy.bindings'}, 'where': ['a binding grants pubsub.subscriptions.consume or roles/pubsub.subscriber or roles/pubsub.admin', "the member's project (extracted from SA email or user domain) != the subscription's project", 'the member is NOT allUsers or allAuthenticatedUsers']}

where

node_type(?policy) == ResourcePolicy

emit

source typeResourcePolicy
target typeProject
source<subscription ResourcePolicy node>
target<GCP project of the external member>
conditionsresource_policy condition_expression
state logicACTIVE when the subscription IAM binding unconditionally grants pubsub.subscriptions.consume to a named external SA or user (no IAM condition). CONDITIONAL(condition_expression) when the binding has an IAM condition expression that the external principal must satisfy — the cross-project consume trust is gated. BLOCKED by an org policy (constraints/iam.allowedPolicyMemberDomains or constraints/gcp.restrictCrossProjectCommunication) that prevents the cross-project grant from being effective.

Narrative

The IAM policy on Pub/Sub subscription {subscription.name} grants pubsub.subscriptions.consume to a principal in project {foreign_project}; this cross-project trust lets that external principal read/drain messages from {subscription.name}.

Raw rule rules/derived/gcp/pubsub.yaml

id: gcp-pubsub-cross-project-consume-trust
emits: CrossProjectTrust
description: A Pub/Sub subscription IAM binding granting pubsub.subscriptions.consume to a principal from
  another GCP project enables the external principal to read/drain the subscription (data exfiltration
  via messaging-chains).
match:
- match_record:
    resource_type: google.pubsub.Subscription
    field: iamPolicy.bindings
  where:
  - a binding grants pubsub.subscriptions.consume or roles/pubsub.subscriber or roles/pubsub.admin
  - the member's project (extracted from SA email or user domain) != the subscription's project
  - the member is NOT allUsers or allAuthenticatedUsers
where:
- node_type(?policy) == ResourcePolicy
emit:
  source_type: ResourcePolicy
  target_type: Project
  source: <subscription ResourcePolicy node>
  target: <GCP project of the external member>
  permissions: []
  conditions:
  - resource_policy
  - condition_expression
  state_logic: "ACTIVE when the subscription IAM binding unconditionally grants pubsub.subscriptions.consume\
    \ to a named external SA or user (no IAM condition). CONDITIONAL(condition_expression) when the binding\
    \ has an IAM condition expression that the external principal must satisfy \u2014 the cross-project\
    \ consume trust is gated. BLOCKED by an org policy (constraints/iam.allowedPolicyMemberDomains or\
    \ constraints/gcp.restrictCrossProjectCommunication) that prevents the cross-project grant from being\
    \ effective."
  confidence: 0.95
  derived_from:
  - pubsub.subscriptions.getIamPolicy[bindings, member, condition]
  false_positive_note: "This rule covers CONSUMPTION (pull/read) from a subscription, distinct from rule\
    \ 4 (cross-project publish to a topic). A cross-project consume grant enables CanReadData(external\
    \ principal \u2192 subscription) for the external project's principal. The CanEnterProject consequence\
    \ is produced by can-control consuming this CrossProjectTrust together with CanReadData/CanExfiltrate\
    \ on the subscription. Honor IAM condition expressions \u2014 a condition-scoped binding is CONDITIONAL,\
    \ not ACTIVE. A foreign SA binding that is also denied by an IAM deny policy at project/ folder/org\
    \ level is BLOCKED."
  narrative: The IAM policy on Pub/Sub subscription {subscription.name} grants pubsub.subscriptions.consume
    to a principal in project {foreign_project}; this cross-project trust lets that external principal
    read/drain messages from {subscription.name}.
move · open · esc close