gcp-tasks-passidentity-per-task-sa

cloudtasks.tasks.create with a per-task httpRequest.oidcToken.serviceAccountEmail (or oauthToken) requires iam.serviceAccounts.actAs on the chosen SA - the per-task identity override path.

derived gcp emits CanPassIdentity

match (effective permission)

{ "action": "cloudtasks.tasks.create", "resource_type": "google.cloud.tasks.v2.Queue" }

where

?principal has effective iam.serviceAccounts.actAs on ?sa ?sa is the SA the attacker would specify in httpRequest.oidcToken.serviceAccountEmail per-task ?sa != queue.httpTarget.oidcToken.serviceAccountEmail (distinguishes from queue-level SA)

emit

source typeIdentity
target typeServiceAccount
source<principal>
target<per-task service account>
permissionscloudtasks.tasks.create iam.serviceAccounts.actAs
conditionsiam_permission role_compatibility
state logicACTIVE when cloudtasks.tasks.create on the queue AND iam.serviceAccounts.actAs on the chosen SA are both confirmed. POTENTIAL(role_compatibility) when actAs on the SA is unknown. BLOCKED when actAs is explicitly denied.

Narrative

{principal.name} has cloudtasks.tasks.create on {queue.name} and iam.serviceAccounts.actAs on {sa.name}. When creating tasks, the attacker can specify {sa.name} as the per-task OIDC/OAuth SA, causing Cloud Tasks to mint and attach a token for {sa.name} to that specific dispatch.

Raw rule rules/derived/gcp/tasks.yaml

id: gcp-tasks-passidentity-per-task-sa
emits: CanPassIdentity
description: "cloudtasks.tasks.create with a per-task httpRequest.oidcToken.serviceAccountEmail (or oauthToken)\
  \ requires iam.serviceAccounts.actAs on the chosen SA \u2014 the per-task identity override path."
match_effective_permission:
  action: cloudtasks.tasks.create
  resource_type: google.cloud.tasks.v2.Queue
where:
- ?principal has effective iam.serviceAccounts.actAs on ?sa
- ?sa is the SA the attacker would specify in httpRequest.oidcToken.serviceAccountEmail per-task
- ?sa != queue.httpTarget.oidcToken.serviceAccountEmail (distinguishes from queue-level SA)
emit:
  source_type: Identity
  target_type: ServiceAccount
  source: <principal>
  target: <per-task service account>
  permissions:
  - cloudtasks.tasks.create
  - iam.serviceAccounts.actAs
  conditions:
  - iam_permission
  - role_compatibility
  state_logic: ACTIVE when cloudtasks.tasks.create on the queue AND iam.serviceAccounts.actAs on the chosen
    SA are both confirmed. POTENTIAL(role_compatibility) when actAs on the SA is unknown. BLOCKED when
    actAs is explicitly denied.
  derived_from:
  - 'effective permission: cloudtasks.tasks.create on the queue'
  - 'effective permission: iam.serviceAccounts.actAs on the per-task SA'
  false_positive_note: "Per-task SA override is only possible when the caller holds actAs on the chosen\
    \ SA. Without actAs, the task create call fails if it specifies oidcToken.serviceAccountEmail or oauthToken.serviceAccountEmail.\
    \ A caller with only cloudtasks.tasks.create (no actAs on any SA) uses the queue-level SA if configured\
    \ \u2014 they do NOT get to choose a different SA. Only emit for an SA where actAs is independently\
    \ confirmed. Dedupes with gcp-tasks-passidentity-queue-sa when the same SA is configured both at queue\
    \ and per-task level."
  narrative: '{principal.name} has cloudtasks.tasks.create on {queue.name} and iam.serviceAccounts.actAs
    on {sa.name}. When creating tasks, the attacker can specify {sa.name} as the per-task OIDC/OAuth SA,
    causing Cloud Tasks to mint and attach a token for {sa.name} to that specific dispatch.'
move · open · esc close