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.
match
A conjunctive graph pattern. Variables (?x) bind node ids; every clause must hold.
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 |
| 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. |
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.'