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.
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. |
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.'