gcp-cloudscheduler-create-pubsub-job-write
A principal with cloudscheduler.jobs.create can establish a Pub/Sub job that publishes attacker-controlled data to any topic in the project on schedule. No actAs required. Feeds messaging-chains rules 1+2 for CanTrigger -> CanExecuteAs chain.
match
A conjunctive graph pattern. Variables (?x) bind node ids; every clause must hold.
where
node_type(?job) ==
Scheduler
?principal intends to / has created a Pub/Sub target job (pubsubTarget.topicName is set)
emit
| source type | Identity |
|---|---|
| source | <principal> |
| target | <target Pub/Sub topic (Topic node in Messaging class)> |
| permissions | cloudscheduler.jobs.create |
| conditions | iam_permission api_enabled |
| state logic | ACTIVE when the principal holds cloudscheduler.jobs.create AND the Cloud Scheduler API is enabled. The Cloud Scheduler service agent automatically holds roles/pubsub.publisher on project-local topics — no additional pubsub grant needed for the creator. Cross-project topic targets require the service agent to hold pubsub.topics.publish on the external topic (separate grant; downgrade to CONDITIONAL(resource_policy) for cross-project targets). |
Narrative
{principal.name} can create a Cloud Scheduler Pub/Sub job (cloudscheduler.jobs.create) that publishes attacker-controlled messages to {topic.name} on schedule. If {topic.name} has a bound consumer, messaging-chains rolls this up to CanTrigger -> CanExecuteAs(consumer runtime SA).
Raw rule rules/derived/gcp/cloudscheduler.yaml
id: gcp-cloudscheduler-create-pubsub-job-write
emits: CanWriteData
description: A principal with cloudscheduler.jobs.create can establish a Pub/Sub job that publishes attacker-controlled
data to any topic in the project on schedule. No actAs required. Feeds messaging-chains rules 1+2 for
CanTrigger -> CanExecuteAs chain.
match:
- - principal: null
- CanSchedule
- job: null
where:
- node_type(?job) == Scheduler
- ?principal intends to / has created a Pub/Sub target job (pubsubTarget.topicName is set)
emit:
source_type: Identity
source: <principal>
target: <target Pub/Sub topic (Topic node in Messaging class)>
permissions:
- cloudscheduler.jobs.create
conditions:
- iam_permission
- api_enabled
state_logic: "ACTIVE when the principal holds cloudscheduler.jobs.create AND the Cloud Scheduler API\
\ is enabled. The Cloud Scheduler service agent automatically holds roles/pubsub.publisher on project-local\
\ topics \u2014 no additional pubsub grant needed for the creator. Cross-project topic targets require\
\ the service agent to hold pubsub.topics.publish on the external topic (separate grant; downgrade\
\ to CONDITIONAL(resource_policy) for cross-project targets)."
confidence: min(contributing_confidences) * 0.85
derived_from:
- ?principal CanSchedule ?job (gcp-cloudscheduler-can-schedule)
false_positive_note: This CanWriteData is from the PRINCIPAL (via job creation) to the TOPIC, not from
the job itself. The intermediate step is the job creation (gcp-cloudscheduler-can-schedule). The actual
publish is performed by the Cloud Scheduler service agent; the creator controls the message payload
(pubsubTarget.data and pubsubTarget.attributes). If the topic has no consumer bound (no CanTrigger
edge from topic to a Compute node), this is CanWriteData with no CanExecuteAs consequence. Messaging-chains
rule 1 (messaging-publish-triggers-consumer) handles the CanWriteData + CanTrigger => CanTrigger roll-up
for the principal.
narrative: '{principal.name} can create a Cloud Scheduler Pub/Sub job (cloudscheduler.jobs.create) that
publishes attacker-controlled messages to {topic.name} on schedule. If {topic.name} has a bound consumer,
messaging-chains rolls this up to CanTrigger -> CanExecuteAs(consumer runtime SA).'