azure-aks-swap-kubelet-mi
managedClusters/agentPools/write + assign on a target MI allows swapping the kubelet MI, binding all nodes to a more-privileged identity.
match
A conjunctive graph pattern. Variables (?x) bind node ids; every clause must hold.
{'principal': None}
CanModifyCode {'cluster': None}
{'principal': None} CanPassIdentity {'target_mi': None}
where
?principal holds Microsoft.
ContainerService/managedClusters/agentPools/write (or managedClusters/write)
?principal holds Microsoft.ManagedIdentity/userAssignedIdentities/assign on ?target_mi
node_type(?cluster) in [KubernetesCluster, ContainerCluster]
node_type(?target_mi) == ManagedIdentity
emit
| source type | Identity |
|---|---|
| target type | Compute |
| source | ?principal |
| target | ?cluster |
| permissions | Microsoft.ContainerService/managedClusters/agentPools/write Microsoft.ManagedIdentity/userAssignedIdentities/assign |
| conditions | iam_permission service_state |
| state logic | CONDITIONAL(iam_permission) — requires BOTH agentPools/write AND assign on the target MI. ACTIVE when both permissions are present and the cluster/node pool is running. BLOCKED if a deny assignment covers either permission at cluster/RG/subscription scope. |
Narrative
{principal.name} holds agentPools/write on cluster {cluster.name} and assign/action on MI {target_mi.name}; can reconfigure the kubelet identity to {target_mi.name}, binding all node-level code to this identity. Combined with node-level pod access (privileged pod), this enables CanExecuteAs {target_mi.name} via azure-aks-node-imds-executes-as.
Raw rule rules/derived/azure/aks.yaml
id: azure-aks-swap-kubelet-mi
emits: CanAttachIdentity
description: managedClusters/agentPools/write + assign on a target MI allows swapping the kubelet MI,
binding all nodes to a more-privileged identity.
match:
- - principal: null
- CanModifyCode
- cluster: null
- - principal: null
- CanPassIdentity
- target_mi: null
where:
- ?principal holds Microsoft.ContainerService/managedClusters/agentPools/write (or managedClusters/write)
- ?principal holds Microsoft.ManagedIdentity/userAssignedIdentities/assign on ?target_mi
- node_type(?cluster) in [KubernetesCluster, ContainerCluster]
- node_type(?target_mi) == ManagedIdentity
emit:
source_type: Identity
target_type: Compute
source: ?principal
target: ?cluster
permissions:
- Microsoft.ContainerService/managedClusters/agentPools/write
- Microsoft.ManagedIdentity/userAssignedIdentities/assign
conditions:
- iam_permission
- service_state
state_logic: "CONDITIONAL(iam_permission) \u2014 requires BOTH agentPools/write AND assign on the target\
\ MI. ACTIVE when both permissions are present and the cluster/node pool is running. BLOCKED if a\
\ deny assignment covers either permission at cluster/RG/subscription scope."
confidence: 0.85
derived_from: []
false_positive_note: "This escalation requires a two-permission composition: the agentPools write +\
\ assign on the target MI. The consequence is that every node in the pool runs as the target MI \u2014\
\ which, if more privileged than the attacker's current context, completes an escalation chain via\
\ azure-aks-node-imds-executes-as (node-level code reaches node IMDS, mints target-MI token). Verify\
\ both permissions are held before emitting. The target_mi privilege_level should be higher than\
\ the principal's for an escalation; see can-control escalate-via-execute-as for the privilege guard."
narrative: '{principal.name} holds agentPools/write on cluster {cluster.name} and assign/action on MI
{target_mi.name}; can reconfigure the kubelet identity to {target_mi.name}, binding all node-level
code to this identity. Combined with node-level pod access (privileged pod), this enables CanExecuteAs
{target_mi.name} via azure-aks-node-imds-executes-as.'