gcp-gke-update-nodepool-sa

container.nodePools.update + iam.serviceAccounts.actAs on a privileged SA allows changing an existing node pool's service account - converts existing pod workloads to run as the new (more privileged) SA via legacy metadata access.

derived gcp emits CanModifyConfiguration

match

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

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

where

?principal has EFFECTIVE container.nodePools.update on the cluster ?sa is the new node SA being bound to an existing node pool ?principal has iam.serviceAccounts.actAs on ?sa (GKE requires actAs to bind a SA to a node pool) the node pool uses EXPOSE_ALL_SCOPES metadata mode OR WI is disabled on the cluster (so pods can reach the node SA metadata)

emit

source typeIdentity
target typeKubernetesCluster
source?principal
target<GKE cluster / node pool>
permissionscontainer.nodePools.update iam.serviceAccounts.actAs
conditionsiam_permission role_compatibility service_state
state logicACTIVE when both container.nodePools.update AND iam.serviceAccounts.actAs are confirmed and the node pool will use EXPOSE_ALL_SCOPES (or the cluster has WI disabled). The node SA change is applied immediately to the node pool metadata, but running pods only read the new token after restart or after the metadata server cache expires. POTENTIAL(role_compatibility) if the node pool SA permissibility is uncertain. CONDITIONAL(service_state) until running pods refresh their metadata-cached tokens (typically within seconds to minutes).

Narrative

{principal.name} can update an existing node pool in {cluster.name} (container.nodePools.update + iam.serviceAccounts.actAs) to change its node SA to {sa.name}; pods on those nodes (in legacy metadata mode) immediately gain {sa.name}'s OAuth token and cloud IAM privileges, compromising existing workloads without creating new resources.

Raw rule rules/derived/gcp/gke.yaml

id: gcp-gke-update-nodepool-sa
emits: CanModifyConfiguration
description: "container.nodePools.update + iam.serviceAccounts.actAs on a privileged SA allows changing\
  \ an existing node pool's service account \u2014 converts existing pod workloads to run as the new (more\
  \ privileged) SA via legacy metadata access."
match:
- - principal: null
  - CanPassIdentity
  - sa: null
where:
- ?principal has EFFECTIVE container.nodePools.update on the cluster
- ?sa is the new node SA being bound to an existing node pool
- ?principal has iam.serviceAccounts.actAs on ?sa (GKE requires actAs to bind a SA to a node pool)
- the node pool uses EXPOSE_ALL_SCOPES metadata mode OR WI is disabled on the cluster (so pods can reach
  the node SA metadata)
emit:
  source_type: Identity
  target_type: KubernetesCluster
  source: ?principal
  target: <GKE cluster / node pool>
  permissions:
  - container.nodePools.update
  - iam.serviceAccounts.actAs
  conditions:
  - iam_permission
  - role_compatibility
  - service_state
  state_logic: ACTIVE when both container.nodePools.update AND iam.serviceAccounts.actAs are confirmed
    and the node pool will use EXPOSE_ALL_SCOPES (or the cluster has WI disabled). The node SA change
    is applied immediately to the node pool metadata, but running pods only read the new token after restart
    or after the metadata server cache expires. POTENTIAL(role_compatibility) if the node pool SA permissibility
    is uncertain. CONDITIONAL(service_state) until running pods refresh their metadata-cached tokens (typically
    within seconds to minutes).
  confidence: 0.87
  derived_from:
  - <gcp/identity-escalation.yaml gcp-sa-act-as CanPassIdentity edge>
  - '<effective iam_permission: container.nodePools.update on cluster>'
  false_positive_note: "container.nodePools.update requires iam.serviceAccounts.actAs on the new SA to\
    \ bind it to the node pool \u2014 without actAs GKE rejects the request. The node SA token is only\
    \ accessible to pods if workloadMetadataConfig is NOT set to GKE_METADATA. This is a CanModifyConfiguration(target=cluster)\
    \ emission because the attack modifies the cluster's node pool configuration; it differs from gcp-gke-create-nodepool-as-sa\
    \ (CanCreateWorkloadAs) in that no new workload is created \u2014 existing pods are compromised in-place\
    \ when they read the updated node SA token from metadata. GKE Autopilot clusters do NOT allow node\
    \ pool mutations \u2014 BLOCKED for Autopilot."
  narrative: '{principal.name} can update an existing node pool in {cluster.name} (container.nodePools.update
    + iam.serviceAccounts.actAs) to change its node SA to {sa.name}; pods on those nodes (in legacy metadata
    mode) immediately gain {sa.name}''s OAuth token and cloud IAM privileges, compromising existing workloads
    without creating new resources.'
move · open · esc close