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).
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 type | ResourcePolicy |
|---|---|
| target type | Project |
| source | <subscription ResourcePolicy node> |
| target | <GCP project of the external member> |
| 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 — 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}.