gcp-pubsub-cross-project-publish-trust
A Pub/Sub topic IAM binding granting pubsub.topics.publish to a principal from another GCP project is cross-project publish trust; feeds can-control's CanEnterProject derivation via messaging-chains.
match
A conjunctive graph pattern. Variables (?x) bind node ids; every clause must hold.
{'match_record': {'resource_type': 'google.pubsub.Topic', 'field': 'iamPolicy.bindings'}, 'where': ['a binding grants pubsub.topics.publish or roles/pubsub.publisher or roles/pubsub.admin', "the member's project (extracted from SA email or user domain) != the topic's project", 'the member is NOT allUsers or allAuthenticatedUsers (those are ExposedToInternet, see rule 5)']}
where
node_type(?policy) ==
ResourcePolicy
emit
| source type | ResourcePolicy |
|---|---|
| target type | Project |
| source | <topic ResourcePolicy node> |
| target | <GCP project of the foreign member> |
| conditions | resource_policy condition_expression |
| state logic | ACTIVE when the topic IAM binding unconditionally grants pubsub.topics.publish to a named external SA or user (no IAM condition on the binding). CONDITIONAL (condition_expression) when the binding has an IAM condition expression that the external principal must satisfy (e.g. request.time, resource.name prefix) — the cross-project 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 topic {topic.name} grants pubsub.topics.publish to a principal in project {foreign_project}; this cross-project trust lets that external principal publish to {topic.name} and thereby (via messaging-chains) trigger any push-subscription consumer in {topic.project}.
Raw rule rules/derived/gcp/pubsub.yaml
id: gcp-pubsub-cross-project-publish-trust
emits: CrossProjectTrust
description: A Pub/Sub topic IAM binding granting pubsub.topics.publish to a principal from another GCP
project is cross-project publish trust; feeds can-control's CanEnterProject derivation via messaging-chains.
match:
- match_record:
resource_type: google.pubsub.Topic
field: iamPolicy.bindings
where:
- a binding grants pubsub.topics.publish or roles/pubsub.publisher or roles/pubsub.admin
- the member's project (extracted from SA email or user domain) != the topic's project
- the member is NOT allUsers or allAuthenticatedUsers (those are ExposedToInternet, see rule 5)
where:
- node_type(?policy) == ResourcePolicy
emit:
source_type: ResourcePolicy
target_type: Project
source: <topic ResourcePolicy node>
target: <GCP project of the foreign member>
permissions: []
conditions:
- resource_policy
- condition_expression
state_logic: "ACTIVE when the topic IAM binding unconditionally grants pubsub.topics.publish to a named\
\ external SA or user (no IAM condition on the binding). CONDITIONAL (condition_expression) when the\
\ binding has an IAM condition expression that the external principal must satisfy (e.g. request.time,\
\ resource.name prefix) \u2014 the cross-project 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.topics.getIamPolicy[bindings, member, condition]
false_positive_note: "CITE, DO NOT DUPLICATE messaging-chains rule 4 and can-control: this rule asserts\
\ only the CrossProjectTrust FACT; the CanEnterProject consequence is produced by can-control consuming\
\ CrossProjectTrust together with CanTrigger/CanExecuteAs from messaging-chains rules 1-2. Honor IAM\
\ condition expressions on the binding \u2014 a condition-scoped binding is CONDITIONAL, not an unconditional\
\ cross-project trust. A foreign SA binding that is also denied by an IAM deny policy at project/folder/org\
\ level is BLOCKED. A user SA whose project is the SAME as the topic's project is not cross-project\
\ (compare the project number in the SA email, not just the project ID string). pubsub.subscriptions.consume\
\ granted cross-project is a separate CrossProjectTrust edge on the subscription ResourcePolicy \u2014\
\ not covered by this rule (emit separately from pubsub.subscriptions.getIamPolicy)."
narrative: The IAM policy on Pub/Sub topic {topic.name} grants pubsub.topics.publish to a principal
in project {foreign_project}; this cross-project trust lets that external principal publish to {topic.name}
and thereby (via messaging-chains) trigger any push-subscription consumer in {topic.project}.