gcp-cloudscheduler-cross-project-sa-trust

A Cloud Scheduler HTTP job whose configured SA holds role bindings in a different project establishes cross-project trust - an attacker who can create/update the job (with actAs on the SA) can invoke APIs in the other project as that SA.

derived gcp emits CrossProjectTrust

match

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

{'principal': None} CanPassIdentity {'sa': None}

where

?principal can create or update a Cloud Scheduler HTTP job naming ?sa ?sa has IAM role bindings in a project different from the Cloud Scheduler job's project

emit

source typeServiceAccount
target typeProject
source<service account with cross-project bindings>
target<project where the SA holds bindings>
permissionscloudscheduler.jobs.create | cloudscheduler.jobs.update iam.serviceAccounts.actAs
conditionsiam_permission resource_policy
state logicACTIVE when the SA unconditionally holds role bindings in the target project AND the principal can bind the SA to a job (actAs confirmed). CONDITIONAL(resource_policy) when the SA's bindings are conditional (IAM conditions on the binding). BLOCKED by org policy or IAM deny overriding the cross-project binding.

Narrative

{sa.name} holds role bindings in {project.name} (a different project). An attacker who can create a Cloud Scheduler job authenticated as {sa.name} (actAs confirmed) can use that job to call GCP APIs in {project.name} on schedule, establishing cross-project trust that can-control rolls up to CanEnterProject.

Raw rule rules/derived/gcp/cloudscheduler.yaml

id: gcp-cloudscheduler-cross-project-sa-trust
emits: CrossProjectTrust
description: "A Cloud Scheduler HTTP job whose configured SA holds role bindings in a different project\
  \ establishes cross-project trust \u2014 an attacker who can create/update the job (with actAs on the\
  \ SA) can invoke APIs in the other project as that SA."
match:
- - principal: null
  - CanPassIdentity
  - sa: null
where:
- ?principal can create or update a Cloud Scheduler HTTP job naming ?sa
- ?sa has IAM role bindings in a project different from the Cloud Scheduler job's project
emit:
  source_type: ServiceAccount
  target_type: Project
  source: <service account with cross-project bindings>
  target: <project where the SA holds bindings>
  permissions:
  - cloudscheduler.jobs.create | cloudscheduler.jobs.update
  - iam.serviceAccounts.actAs
  derived_from:
  - ?principal CanPassIdentity ?sa (gcp-cloudscheduler-passidentity-actas)
  - SA role bindings in external project (from IAM policy collection)
  conditions:
  - iam_permission
  - resource_policy
  state_logic: ACTIVE when the SA unconditionally holds role bindings in the target project AND the principal
    can bind the SA to a job (actAs confirmed). CONDITIONAL(resource_policy) when the SA's bindings are
    conditional (IAM conditions on the binding). BLOCKED by org policy or IAM deny overriding the cross-project
    binding.
  false_positive_note: "CITE, DO NOT DUPLICATE can-control: this rule asserts ONLY the cross-project trust\
    \ fact via the SA. The CanEnterProject consequence (create job with SA -> SA calls APIs in the other\
    \ project -> foothold there) is produced by can-control consuming this CrossProjectTrust. Only emit\
    \ when the SA's bindings are confirmed in a different project AND the principal can actAs that SA.\
    \ A SA with only roles/viewer in another project yields limited cross-project access \u2014 honor\
    \ privilege_level gate."
  narrative: '{sa.name} holds role bindings in {project.name} (a different project). An attacker who can
    create a Cloud Scheduler job authenticated as {sa.name} (actAs confirmed) can use that job to call
    GCP APIs in {project.name} on schedule, establishing cross-project trust that can-control rolls up
    to CanEnterProject.'
move · open · esc close