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.

derived gcp emits CanTrigger

match

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

{'principal': None} CanModifyCode {'registry': None} {'registry': None} CanTrigger {'pubsub_topic': None} {'pubsub_topic': None} CanTrigger {'build_job': None} {'build_job': None} ExecutesAs {'build_sa': None}

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

Present → upgrade the emitted state; absent → downgrade (not a hard requirement).

{'principal': None} CanInvoke {'build_job': None}

emit

source typeIdentity
target typeCompute
source?principal
target?build_job
permissionsstorage.objects.create cloudbuild.builds.create (via Pub/Sub event subscription)
conditionstrigger_exists
state logicCONDITIONAL(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."
move · open · esc close