gcp-deploy-rollout-approve-unblocks
clouddeploy.rollouts.approve unblocks a NEEDS_APPROVAL rollout, triggering the deploy Cloud Build worker (no actAs required).
match (effective permission)
{
"action": "clouddeploy.rollouts.approve",
"resource_type": "google.cloud.deploy.DeliveryPipeline"
}
where
a rollout is in NEEDS_APPROVAL state on a target in this pipeline
emit
| source type | Identity |
|---|---|
| target type | BuildWorker |
| source | <principal> |
| target | <deploy-phase Cloud Build worker (BuildWorker) for the approved rollout> |
| permissions | clouddeploy.rollouts.approve |
| conditions | iam_permission trigger_exists |
| state logic | CONDITIONAL(trigger_exists) — the trigger only fires when a pending rollout exists in NEEDS_APPROVAL state. ACTIVE if a rollout is currently pending approval for a target in this pipeline. BLOCKED when an IAM deny policy denies clouddeploy.rollouts.approve. Note: an attacker who has BOTH rollouts.create (to create the rollout) and rollouts.approve (to approve it) on the same target can complete the full chain unilaterally; when both permissions are confirmed, the approval gate provides no separation — the CanExecuteAs edge (from gcp-deploy-rollout-promote-executes-as) becomes ACTIVE instead of CONDITIONAL(approval_required). |
Narrative
{principal.name} can approve a pending Cloud Deploy rollout (clouddeploy.rollouts.approve) in pipeline {pipeline.name}, unblocking the deploy Cloud Build worker. If a pending rollout exists and {principal.name} also created it (holding both rollouts.create + iam.serviceAccounts.actAs on the deploy SA), the approval gate provides no isolation and the full execution chain is ACTIVE.
Raw rule rules/derived/gcp/clouddeploy.yaml
id: gcp-deploy-rollout-approve-unblocks
emits: CanTrigger
description: clouddeploy.rollouts.approve unblocks a NEEDS_APPROVAL rollout, triggering the deploy Cloud
Build worker (no actAs required).
applies_to:
- gcp
match_effective_permission:
action: clouddeploy.rollouts.approve
resource_type: google.cloud.deploy.DeliveryPipeline
where:
- a rollout is in NEEDS_APPROVAL state on a target in this pipeline
emit:
source_type: Identity
target_type: BuildWorker
source: <principal>
target: <deploy-phase Cloud Build worker (BuildWorker) for the approved rollout>
permissions:
- clouddeploy.rollouts.approve
conditions:
- iam_permission
- trigger_exists
state_logic: "CONDITIONAL(trigger_exists) \u2014 the trigger only fires when a pending rollout exists\
\ in NEEDS_APPROVAL state. ACTIVE if a rollout is currently pending approval for a target in this\
\ pipeline. BLOCKED when an IAM deny policy denies clouddeploy.rollouts.approve. Note: an attacker\
\ who has BOTH rollouts.create (to create the rollout) and rollouts.approve (to approve it) on the\
\ same target can complete the full chain unilaterally; when both permissions are confirmed, the approval\
\ gate provides no separation \u2014 the CanExecuteAs edge (from gcp-deploy-rollout-promote-executes-as)\
\ becomes ACTIVE instead of CONDITIONAL(approval_required)."
confidence: 0.88
derived_from:
- clouddeploy.rollouts.approve effective permission
false_positive_note: "clouddeploy.rollouts.approve alone is the approval capability \u2014 it does NOT\
\ require actAs or create permissions. A pure approver (holding only approve, not create) can still\
\ trigger rollout execution. The full execute-as chain requires a rollout to exist in NEEDS_APPROVAL\
\ state (someone must have called rollouts.create first, which DOES require actAs). The dangerous\
\ case is when the SAME principal holds both rollouts.create and rollouts.approve, defeating role\
\ separation. Confirm that a pending rollout exists before emitting ACTIVE."
narrative: '{principal.name} can approve a pending Cloud Deploy rollout (clouddeploy.rollouts.approve)
in pipeline {pipeline.name}, unblocking the deploy Cloud Build worker. If a pending rollout exists
and {principal.name} also created it (holding both rollouts.create + iam.serviceAccounts.actAs on
the deploy SA), the approval gate provides no isolation and the full execution chain is ACTIVE.'