gcp-dataproc-init-action-inject-code

dataproc.clusters.update on initializationActions (or the GCS path they reference) injects code that executes as the cluster SA on the next node join or cluster restart.

derived gcp emits CanModifyCode

match (effective permission)

{ "action": "dataproc.clusters.update", "resource_type": "google.dataproc.Cluster" }

where

the update targets config.initializationActions (shell scripts executed on node start) OR the principal can write to the GCS path referenced by an init action (storage.objects.create on the init-script GCS path)

emit

source typeIdentity
target typeAnalyticsService
source<principal>
target<Dataproc cluster>
permissionsdataproc.clusters.update
conditionsiam_permission service_state
state logicCONDITIONAL(service_state): the injected init script runs only on the NEXT node join (scale-out) or full cluster restart — not on currently running nodes. Upgrade to ACTIVE if the attacker can also trigger a cluster restart (dataproc.clusters.start after stop) or if the cluster auto-scales (which adds nodes, running init scripts). BLOCKED if an IAM deny prevents clusters.update. Also CONDITIONAL when the attack vector is a GCS-write to the init-script path (requires CanWriteData on the GCS bucket separately).

Narrative

{principal.name} can inject code into {cluster.name}'s initialization actions (dataproc.clusters.update on initializationActions); the injected script will run as {sa.name} on the next node join or cluster restart.

Raw rule rules/derived/gcp/dataproc.yaml

id: gcp-dataproc-init-action-inject-code
emits: CanModifyCode
description: dataproc.clusters.update on initializationActions (or the GCS path they reference) injects
  code that executes as the cluster SA on the next node join or cluster restart.
match_effective_permission:
  action: dataproc.clusters.update
  resource_type: google.dataproc.Cluster
where:
- the update targets config.initializationActions (shell scripts executed on node start)
- OR the principal can write to the GCS path referenced by an init action (storage.objects.create on the
  init-script GCS path)
emit:
  source_type: Identity
  target_type: AnalyticsService
  source: <principal>
  target: <Dataproc cluster>
  permissions:
  - dataproc.clusters.update
  conditions:
  - iam_permission
  - service_state
  state_logic: "CONDITIONAL(service_state): the injected init script runs only on the NEXT node join (scale-out)\
    \ or full cluster restart \u2014 not on currently running nodes. Upgrade to ACTIVE if the attacker\
    \ can also trigger a cluster restart (dataproc.clusters.start after stop) or if the cluster auto-scales\
    \ (which adds nodes, running init scripts). BLOCKED if an IAM deny prevents clusters.update. Also\
    \ CONDITIONAL when the attack vector is a GCS-write to the init-script path (requires CanWriteData\
    \ on the GCS bucket separately)."
  false_positive_note: This is a DEFERRED code execution path, not immediate RCE like job submission.
    The injected init script only runs when a node starts (new node added, or cluster restarted). Do NOT
    emit ACTIVE unless a node-join or restart is confirmed to follow. For a static, fully provisioned
    cluster with no auto-scaling, this may never trigger until an operator action. CITE can-execute-as
    execute-as-via-code- modify + cicd-chains cicd-modify-buildspec-executes-as for the CanExecuteAs collapse
    once the code runs.
  narrative: '{principal.name} can inject code into {cluster.name}''s initialization actions (dataproc.clusters.update
    on initializationActions); the injected script will run as {sa.name} on the next node join or cluster
    restart.'
move · open · esc close