gcp-gke-exec-reads-node-sa-token

Executing inside a pod on a legacy-metadata node pool (no GKE_METADATA) allows the code to call the GCE metadata server and retrieve the node pool SA's access token.

derived gcp emits CanRetrieveToken

match

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

{'principal': None} CanExecuteCommand {'pod': None} {'node_pool': None} ExposesCredential {'node_sa': None}

where

node_type(?pod) in [KubernetesWorkload, Container] ?pod runs on ?node_pool (pod is scheduled on a node from the node pool) node_type(?node_sa) == ServiceAccount

emit

source typeIdentity
target typeServiceAccount
source?principal
target?node_sa
conditionsservice_state network_reachability
state logicACTIVE when CanExecuteCommand is ACTIVE AND gcp-gke-node-sa-legacy-exposure is ACTIVE (node pool not using GKE_METADATA). CONDITIONAL(service_state) if the pod is not running. BLOCKED if node_pool uses GKE_METADATA mode (metadata server intercepts and scopes token to WI identity only).

Narrative

{principal.name} can exec into {pod.name} on node pool {node_pool.name} (which uses EXPOSE_ALL_SCOPES metadata mode), then call the GCE metadata server to retrieve {node_sa.name}'s OAuth token - effectively impersonating the node pool SA with no GCP IAM permission beyond the exec capability.

Raw rule rules/derived/gcp/gke.yaml

id: gcp-gke-exec-reads-node-sa-token
emits: CanRetrieveToken
description: Executing inside a pod on a legacy-metadata node pool (no GKE_METADATA) allows the code to
  call the GCE metadata server and retrieve the node pool SA's access token.
match:
- - principal: null
  - CanExecuteCommand
  - pod: null
- - node_pool: null
  - ExposesCredential
  - node_sa: null
where:
- node_type(?pod) in [KubernetesWorkload, Container]
- ?pod runs on ?node_pool (pod is scheduled on a node from the node pool)
- node_type(?node_sa) == ServiceAccount
emit:
  source_type: Identity
  target_type: ServiceAccount
  source: ?principal
  target: ?node_sa
  permissions: []
  conditions:
  - service_state
  - network_reachability
  state_logic: ACTIVE when CanExecuteCommand is ACTIVE AND gcp-gke-node-sa-legacy-exposure is ACTIVE (node
    pool not using GKE_METADATA). CONDITIONAL(service_state) if the pod is not running. BLOCKED if node_pool
    uses GKE_METADATA mode (metadata server intercepts and scopes token to WI identity only).
  confidence: 0.88
  derived_from:
  - <gcp-gke-rbac-exec-pod or gcp-gke-credentials-cluster-admin-exec CanExecuteCommand edge>
  - <gcp-gke-node-sa-legacy-exposure ExposesCredential edge>
  false_positive_note: "GKE_METADATA mode BLOCKS this path \u2014 the metadata proxy at 169.254.169.254\
    \ returns only the pod's own WI-scoped token. Only emit when the node pool's workloadMetadataConfig.mode\
    \ is NOT GKE_METADATA. Network reachability to the metadata endpoint (169.254.169.254 / metadata.google.internal)\
    \ is granted by default from within the pod's network namespace; no additional firewall rule needed."
  narrative: "{principal.name} can exec into {pod.name} on node pool {node_pool.name} (which uses EXPOSE_ALL_SCOPES\
    \ metadata mode), then call the GCE metadata server to retrieve {node_sa.name}'s OAuth token \u2014\
    \ effectively impersonating the node pool SA with no GCP IAM permission beyond the exec capability."
move · open · esc close