azure-synapse-execute-as-via-spark-submit
Synapse Compute Operator submits a Spark batch job via Livy API (CanExecuteCommand) + Spark pool runs as workspace MI (ExecutesAs) => execute attacker code as the workspace MI.
match
A conjunctive graph pattern. Variables (?x) bind node ids; every clause must hold.
{'principal': None}
CanExecuteCommand {'sparkPool': None}
{'workspace': None} ExecutesAs {'mi': None}
where
node_type(?sparkPool) ==
AnalyticsService
node_type(?workspace) == AnalyticsService
node_type(?mi) == ManagedIdentity
?sparkPool.provider_type == 'Microsoft.Synapse/workspaces/bigDataPools' OR ?sparkPool is the workspace itself
?mi.privilege_level >= ?principal.privilege_level
emit
| source type | Identity |
|---|---|
| target type | Identity |
| source | <principal> |
| target | <workspace managed identity> |
| permissions | Synapse RBAC: Synapse Compute Operator |
| conditions | service_state |
| state logic | ACTIVE: CanExecuteCommand means the principal has already submitted the Spark job. The job runs on Spark workers executing as the workspace MI. Execution is immediate (batch jobs start processing right away). Inherit condition from CanExecuteCommand. |
Narrative
{principal.name} has Synapse Compute Operator; submitted a Spark batch job to pool {sparkPool.name} (Livy API), which executes on Spark workers running as workspace MI {mi.name}.
Raw rule rules/derived/azure/synapse.yaml
id: azure-synapse-execute-as-via-spark-submit
emits: CanExecuteAs
description: Synapse Compute Operator submits a Spark batch job via Livy API (CanExecuteCommand) + Spark
pool runs as workspace MI (ExecutesAs) => execute attacker code as the workspace MI.
match:
- - principal: null
- CanExecuteCommand
- sparkPool: null
- - workspace: null
- ExecutesAs
- mi: null
where:
- node_type(?sparkPool) == AnalyticsService
- node_type(?workspace) == AnalyticsService
- node_type(?mi) == ManagedIdentity
- ?sparkPool.provider_type == 'Microsoft.Synapse/workspaces/bigDataPools' OR ?sparkPool is the workspace
itself
- ?mi.privilege_level >= ?principal.privilege_level
emit:
source_type: Identity
target_type: Identity
source: <principal>
target: <workspace managed identity>
permissions:
- 'Synapse RBAC: Synapse Compute Operator'
conditions:
- service_state
state_logic: 'ACTIVE: CanExecuteCommand means the principal has already submitted the Spark job. The
job runs on Spark workers executing as the workspace MI. Execution is immediate (batch jobs start
processing right away). Inherit condition from CanExecuteCommand.'
derived_from:
- <CanExecuteCommand edge_id>
- <ExecutesAs edge_id>
note: "The generic linchpin can-execute-as.yaml execute-as-via-command does NOT apply here because it\
\ checks node_class(?compute) == Compute; AnalyticsService is Data class. This rule is the sole authoritative\
\ emitter of CanExecuteAs for the Livy Spark submit path and must not be deduped with the generic\
\ linchpin \u2014 there is no deduplication. The CanExecuteAs roll-up to CanEscalateTo is can-control.yaml\
\ escalate-via-execute-as \u2014 CITE, DO NOT re-derive."
false_positive_note: The principal must hold CanExecuteCommand (Synapse Compute Operator role or higher)
to reach this rule. The privilege-level guard ensures the workspace MI's privilege exceeds the principal's
(escalation condition). Deny assignment on Synapse RBAC roles -> BLOCKED.
narrative: '{principal.name} has Synapse Compute Operator; submitted a Spark batch job to pool {sparkPool.name}
(Livy API), which executes on Spark workers running as workspace MI {mi.name}.'