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.
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 type | Identity |
|---|---|
| target type | ServiceAccount |
| source | ?principal |
| target | ?node_sa |
| 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). |
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."