gcp-gcr-push-triggers-cloud-build
GCR push event fires to the default gcr Pub/Sub topic, triggering any Cloud Build subscription - indirect trigger of build jobs running as the Cloud Build SA.
match
A conjunctive graph pattern. Variables (?x) bind node ids; every clause must hold.
CanModifyCode {'registry': None}
{'registry': None} CanTrigger {'pubsub_topic': None}
{'pubsub_topic': None} CanTrigger {'build_job': None}
{'build_job': None} ExecutesAs {'build_sa': None}
where
ContainerRegistry node (GCR in the project)
?pubsub_topic is the default gcr Pub/Sub topic for the project
?build_job is a Cloud Build job subscribed to the gcr topic
The principal has storage.objects.create on the registry (push capability) and can thus emit events to the gcr topic
optional
Present → upgrade the emitted state; absent → downgrade (not a hard requirement).
CanInvoke {'build_job': None}
emit
| source type | Identity |
|---|---|
| target type | Compute |
| source | ?principal |
| target | ?build_job |
| permissions | storage.objects.create cloudbuild.builds.create (via Pub/Sub event subscription) |
| conditions | trigger_exists |
| state logic | CONDITIONAL(trigger_exists) when a Cloud Build trigger is subscribed to the gcr topic and the principal holds storage.objects.create on the registry (push). ACTIVE if the principal can also directly invoke the build (cloudbuild.builds.create). The trigger is realized only when the push event fires and the Cloud Build subscriber processes it. The build runs as the Cloud Build SA by default (roles/cloudbuild.builds.editor on the project) — a principal with this CanTrigger edge and the build SA's permissions produces CanExecuteAs(principal -> build_SA) via cicd-chains and escalation rules. |
Narrative
{principal.name} can push container images to the GCR registry for project {project.name} (storage.objects.create). GCR push events are published to the default gcr Pub/Sub topic. If {project.name} has a Cloud Build trigger subscribed to the gcr topic, pushing an image will fire that trigger and invoke the Cloud Build job {build_job.name}, which executes as {build_sa.name}. This creates an indirect supply-chain path: push → trigger → execute as Cloud Build SA. If the build pipeline modifies infrastructure or deploys code, the attacker reaches every deployment target the build can reach.
Raw rule rules/derived/gcp/gcr.yaml
id: gcp-gcr-push-triggers-cloud-build
emits: CanTrigger
description: "GCR push event fires to the default gcr Pub/Sub topic, triggering any Cloud Build subscription\
\ \u2014 indirect trigger of build jobs running as the Cloud Build SA."
match:
- - principal: null
- CanModifyCode
- registry: null
- - registry: null
- CanTrigger
- pubsub_topic: null
- - pubsub_topic: null
- CanTrigger
- build_job: null
- - build_job: null
- ExecutesAs
- build_sa: null
where:
- ?registry is a ContainerRegistry node (GCR in the project)
- ?pubsub_topic is the default gcr Pub/Sub topic for the project
- ?build_job is a Cloud Build job subscribed to the gcr topic
- The principal has storage.objects.create on the registry (push capability) and can thus emit events
to the gcr topic
optional:
- - principal: null
- CanInvoke
- build_job: null
emit:
source_type: Identity
target_type: Compute
source: ?principal
target: ?build_job
permissions:
- storage.objects.create
- cloudbuild.builds.create (via Pub/Sub event subscription)
conditions:
- trigger_exists
state_logic: "CONDITIONAL(trigger_exists) when a Cloud Build trigger is subscribed to the gcr topic\
\ and the principal holds storage.objects.create on the registry (push). ACTIVE if the principal can\
\ also directly invoke the build (cloudbuild.builds.create). The trigger is realized only when the\
\ push event fires and the Cloud Build subscriber processes it. The build runs as the Cloud Build\
\ SA by default (roles/cloudbuild.builds.editor on the project) \u2014 a principal with this CanTrigger\
\ edge and the build SA's permissions produces CanExecuteAs(principal -> build_SA) via cicd-chains\
\ and escalation rules."
confidence: 0.85
derived_from:
- CanModifyCode(principal, registry)
- ContainerRegistry push events fire to default gcr Pub/Sub topic (GCP documented behavior)
- Cloud Build subscription to gcr topic (infrastructure-dependent; confirmed by scanning build triggers)
false_positive_note: 'This rule fires only when ALL of the following are confirmed: (1) the principal
holds storage.objects.create on the artifacts bucket (gcp-gcr-push-can-modify-code base); (2) the
project has a Cloud Build trigger subscribed to the gcr Pub/Sub topic (must scan Cloud Build trigger
configuration to confirm); (3) the build job is stored in the project''s Cloud Source Repositories
or GitHub and is in-scope. If the gcr Pub/Sub topic exists but no trigger subscribes to it, the rule
should not fire (no CanTrigger edge exists from the topic to any build job). Not all projects have
Cloud Build triggers; this rule only fires in projects where the scanning adapter confirms at least
one trigger subscription to gcr.'
narrative: "{principal.name} can push container images to the GCR registry for project {project.name}\
\ (storage.objects.create). GCR push events are published to the default gcr Pub/Sub topic. If {project.name}\
\ has a Cloud Build trigger subscribed to the gcr topic, pushing an image will fire that trigger and\
\ invoke the Cloud Build job {build_job.name}, which executes as {build_sa.name}. This creates an\
\ indirect supply-chain path: push \u2192 trigger \u2192 execute as Cloud Build SA. If the build pipeline\
\ modifies infrastructure or deploys code, the attacker reaches every deployment target the build\
\ can reach."