cluster-rbac-exec
Exec into a pod (cluster RBAC / credential / run-command) inherits the pod's mounted ServiceAccount/IRSA/Workload-Identity token, executing as that identity.
match
A conjunctive graph pattern. Variables (?x) bind node ids; every clause must hold.
{'principal': None}
CanExecuteCommand {'pod': None}
{'pod': None} ExecutesAs {'workload_identity': None}
where
node_type(?pod) in [
KubernetesWorkload, Container, ContainerService, ContainerTask]
node_class(?workload_identity) == Identity
node_type(?workload_identity) in [ServiceAccount, WorkloadIdentity, MachineIdentity, ManagedIdentity, ServiceIdentity, Identity, Role]
emit
| source type | Identity |
|---|---|
| target type | Identity |
| source | ?principal |
| target | ?workload_identity |
| permissions | <pods/exec create | kubectl exec | run-command into container> |
| conditions | token_mounted |
| state logic | ACTIVE — exec into a running pod immediately exposes the mounted SA token and the cloud credential relay to the attacker's shell, so the attacker holds ?workload_identity. CONDITIONAL(token_mounted) when the SA token is NOT projected into the pod (automountServiceAccountToken: false AND no IRSA/WI projected volume) — then exec yields OS access but not the token. Inherit weakest with the contributing CanExecuteCommand: a CONDITIONAL/BLOCKED exec (RBAC verb scoped to a different namespace/pod, pods/exec denied, PodSecurity/OPA gate) caps or blocks this edge. |
Narrative
{principal.name} can exec into {pod.name} ({perm:CanExecuteCommand}), which runs as {workload_identity.name}; the pod's mounted ServiceAccount/IRSA/ Workload-Identity token is readable from inside, so {principal.name} executes as {workload_identity.name}.
Raw rule rules/derived/container-chains.yaml
id: cluster-rbac-exec
emits: CanExecuteAs
description: Exec into a pod (cluster RBAC / credential / run-command) inherits the pod's mounted ServiceAccount/IRSA/Workload-Identity
token, executing as that identity.
applies_to:
- '*'
match:
- - principal: null
- CanExecuteCommand
- pod: null
- - pod: null
- ExecutesAs
- workload_identity: null
where:
- node_type(?pod) in [KubernetesWorkload, Container, ContainerService, ContainerTask]
- node_class(?workload_identity) == Identity
- node_type(?workload_identity) in [ServiceAccount, WorkloadIdentity, MachineIdentity, ManagedIdentity,
ServiceIdentity, Identity, Role]
emit:
source_type: Identity
target_type: Identity
source: ?principal
target: ?workload_identity
permissions:
- <pods/exec create | kubectl exec | run-command into container>
conditions:
- token_mounted
state_logic: "ACTIVE \u2014 exec into a running pod immediately exposes the mounted SA token and the\
\ cloud credential relay to the attacker's shell, so the attacker holds ?workload_identity. CONDITIONAL(token_mounted)\
\ when the SA token is NOT projected into the pod (automountServiceAccountToken: false AND no IRSA/WI\
\ projected volume) \u2014 then exec yields OS access but not the token. Inherit weakest with the\
\ contributing CanExecuteCommand: a CONDITIONAL/BLOCKED exec (RBAC verb scoped to a different namespace/pod,\
\ pods/exec denied, PodSecurity/OPA gate) caps or blocks this edge."
confidence: min(contributing_confidences)
derived_from:
- ?principal CanExecuteCommand ?pod
- ?pod ExecutesAs ?workload_identity
false_positive_note: "This dedupes with can-execute-as execute-as-via-command on (CanExecuteAs, ?principal,\
\ ?workload_identity) \u2014 SAME collapse, k8s/pod-exec framing; emit once, do not double-count weight.\
\ The escalation consequence is produced by can-execute-as / can-control off this CanExecuteAs \u2014\
\ cite, do not duplicate. The token-mount gate matters: a pod with automountServiceAccountToken:false\
\ and no IRSA/WI projected volume gives OS access but not the identity => CONDITIONAL(token_mounted).\
\ Scope the exec verb to the ACTUAL pod/namespace: a `pods/exec` grant in namespace A does not exec\
\ a pod in namespace B. Cluster-admin kubeconfig / a broad pods/exec on all namespaces reaches every\
\ pod's identity \u2014 the fan-out is real but each edge is per (pod, workload_identity). Distinct\
\ from rule 1: exec is runtime access to an already-running pod, not a supply-chain code push."
narrative: '{principal.name} can exec into {pod.name} ({perm:CanExecuteCommand}), which runs as {workload_identity.name};
the pod''s mounted ServiceAccount/IRSA/ Workload-Identity token is readable from inside, so {principal.name}
executes as {workload_identity.name}.'