gcp-composer-trigger-dag-run
composer.environments.executeAirflowCommand lets the principal trigger an existing Airflow DAG run; CanExecuteAs is CONDITIONAL on the current DAG definition being attacker-influenced.
match (effective permission)
{
"action": "composer.environments.executeAirflowCommand",
"resource_type": "google.composer.Environment"
}
emit
| source type | Identity |
|---|---|
| target type | AnalyticsService |
| source | <principal> |
| target | <Composer Environment node> |
| permissions | composer.environments.executeAirflowCommand |
| conditions | attacker_influenced_definition |
| state logic | CONDITIONAL(attacker_influenced_definition): executeAirflowCommand triggers the CURRENT DAG definition. Code execution as the environment SA is only ACTIVE if the triggered DAG already runs attacker-influenceable code (e.g., a DAG the attacker has already modified via gcp-composer-dag-write-modify-code, a DAG with a BashOperator running an unpinned script from GCS, or a DAG with a PythonOperator calling an attacker-controlled function). A trigger over a clean, pinned, reviewed DAG that does not run attacker-influenced code remains CONDITIONAL. Cite cicd-chains cicd-trigger-runner-executes-as for the CONDITIONAL CanExecuteAs collapse. BLOCKED if an IAM deny policy applies. |
Narrative
{principal.name} can issue Airflow CLI commands against environment {env.name} (composer.environments.executeAirflowCommand), including triggering a DAG run; if the triggered DAG already runs attacker-influenced code, this executes as environment SA {sa.name} (CONDITIONAL on definition being attacker-influenced).
Raw rule rules/derived/gcp/composer.yaml
id: gcp-composer-trigger-dag-run
emits: CanTrigger
description: composer.environments.executeAirflowCommand lets the principal trigger an existing Airflow
DAG run; CanExecuteAs is CONDITIONAL on the current DAG definition being attacker-influenced.
match_effective_permission:
action: composer.environments.executeAirflowCommand
resource_type: google.composer.Environment
emit:
source_type: Identity
target_type: AnalyticsService
source: <principal>
target: <Composer Environment node>
permissions:
- composer.environments.executeAirflowCommand
conditions:
- attacker_influenced_definition
state_logic: 'CONDITIONAL(attacker_influenced_definition): executeAirflowCommand triggers the CURRENT
DAG definition. Code execution as the environment SA is only ACTIVE if the triggered DAG already runs
attacker-influenceable code (e.g., a DAG the attacker has already modified via gcp-composer-dag-write-modify-code,
a DAG with a BashOperator running an unpinned script from GCS, or a DAG with a PythonOperator calling
an attacker-controlled function). A trigger over a clean, pinned, reviewed DAG that does not run attacker-influenced
code remains CONDITIONAL. Cite cicd-chains cicd-trigger-runner-executes-as for the CONDITIONAL CanExecuteAs
collapse. BLOCKED if an IAM deny policy applies.'
confidence: 0.7
derived_from:
- <composer.environments.executeAirflowCommand effective permission>
false_positive_note: "Strictly weaker than the DAG-write path (rule 1). Do NOT emit ACTIVE merely because\
\ the principal can trigger a DAG run \u2014 the current DAG definition must already contain attacker-controlled\
\ code for this to yield CanExecuteAs ACTIVE. The classic ACTIVE case is: attacker has already written\
\ a malicious DAG (rule 1) and can also trigger it. In that case rule 1's ACTIVE CanModifyCode + ExecutesAs\
\ dominates; this rule provides the explicit trigger edge. For a clean DAG, keep CONDITIONAL and do\
\ not elevate to high-value without confirming attacker influence on the definition. Cite cicd-chains\
\ for the collapse; do not re-derive CanExecuteAs."
narrative: '{principal.name} can issue Airflow CLI commands against environment {env.name} (composer.environments.executeAirflowCommand),
including triggering a DAG run; if the triggered DAG already runs attacker-influenced code, this executes
as environment SA {sa.name} (CONDITIONAL on definition being attacker-influenced).'