Minimize Access To The Proxy Sub-Resource Of Nodes
More Info:
Access to the Node proxy sub-resource exposes the kubelet API and can bypass audit logging and admission control. It should be limited to trusted administrators only.
Risk Level
High
Address
Security
Compliance Standards
- CIS AKS
Triage and Remediation
- Remediation
Remediation
Manual Steps
-
List all roles/clusterroles that can access the Node
proxysub-resource- Run on: any machine with
kubectlaccess
kubectl get clusterrole -o yaml | grep -En 'kind: ClusterRole|(^[[:space:]]*- apiGroups:)|(^[[:space:]]*resources:)|(^[[:space:]]*verbs:)|(^[[:space:]]*resourceNames:)|proxy'kubectl get role --all-namespaces -o yaml | grep -En 'kind: Role|(^[[:space:]]*- apiGroups:)|(^[[:space:]]*resources:)|(^[[:space:]]*verbs:)|(^[[:space:]]*resourceNames:)|proxy'Focus on entries where
resources:includesnodes/proxyornodesandverbs:includesget,list,create, or*. - Run on: any machine with
-
Extract detailed definitions of suspicious roles/clusterroles
- From the names found in step 1, get the full YAML for review.
- Run on: any machine with
kubectlaccess
kubectl get clusterrole <CLUSTERROLE_NAME> -o yamlkubectl get role -n <NAMESPACE> <ROLE_NAME> -o yamlReview whether
nodes/proxy(or broadnodeswith*verbs) is really needed for the intended purpose of that role. -
Identify who is actually getting this proxy access (bindings)
- Run on: any machine with
kubectlaccess
kubectl get clusterrolebinding -o yaml | grep -En 'kind: ClusterRoleBinding|roleRef:|name:|subjects:'kubectl get rolebinding --all-namespaces -o yaml | grep -En 'kind: RoleBinding|roleRef:|name:|subjects:'For each role/clusterrole from step 2 that has
nodes/proxyaccess, find the correspondingroleRefin these bindings and note whichusers,groups, orserviceAccountsreceive that access. - Run on: any machine with
-
Decide which subjects truly need Node
proxyaccess- For each bound subject identified in step 3, validate with your operational/incident-response requirements:
- Trusted cluster administrators: generally may retain this access.
- Automation/service accounts, application accounts, and regular users: usually should not need direct kubelet API access via Node proxy.
- Document a list of subjects and whether
nodes/proxyis required, optional, or not justified.
- For each bound subject identified in step 3, validate with your operational/incident-response requirements:
-
Tighten or remove excessive
nodes/proxypermissions- Run on: any machine with
kubectlaccess - Option A – Remove
nodes/proxyfrom the role/clusterrole (preferred):
kubectl edit clusterrole <CLUSTERROLE_NAME># or for namespace-scoped:kubectl edit role -n <NAMESPACE> <ROLE_NAME>In the editor, under
rules:, removenodes/proxy(or overly broadnodesrules) fromresources:for subjects that should not have it; narrowverbs:if needed. Save and exit.- Option B – If a shared role is too broad, create a new, restricted role and rebind:
kubectl create role <NEW_ROLE_NAME> -n <NAMESPACE> --verb=<VERBS_WITHOUT_PROXY> --resource=<RESOURCES_WITHOUT_NODES/PROXY>kubectl edit rolebinding -n <NAMESPACE> <ROLEBINDING_NAME>Update
roleRefto point to the new restricted role for non-admin subjects. - Run on: any machine with
-
Re-verify that only trusted admins retain Node
proxyaccess- Run on: any machine with
kubectlaccess
# Re-audit roles/clusterroles for nodes/proxykubectl get clusterrole -o yaml | grep -En 'nodes/proxy'kubectl get role --all-namespaces -o yaml | grep -En 'nodes/proxy'For any remaining
nodes/proxyentries, repeat the binding review:kubectl get clusterrolebinding -o yaml | grep -En 'roleRef:|name:|subjects:'kubectl get rolebinding --all-namespaces -o yaml | grep -En 'roleRef:|name:|subjects:'Confirm that all roles with
nodes/proxyare bound only to explicitly approved, trusted administrator identities. - Run on: any machine with
Using kubectl
# 1. List all Roles/ClusterRoles that reference 'nodes' with 'proxy' access
# Run on: any machine with kubectl access
kubectl get clusterroles,roles -A -o json \
| jq -r '
.items[]
| . as $role
| (.rules[]? // [])
| select(
(.resources[]? == "nodes" or .resources[]? == "nodes/proxy") and
(.verbs[]? == "proxy" or .verbs[]? == "*")
)
| "\($role.kind)/\($role.metadata.namespace // "cluster")/\($role.metadata.name)"
' | sort -u
What to look for:
Each line is a ClusterRole or Role that can reach the nodes proxy sub-resource (either explicitly via proxy or implicitly via * on verbs). Any entry here must be reviewed; these are the only candidates that can expose the kubelet API through the Node proxy.
# 2. For each suspicious role, show full rule details
# Example for a ClusterRole named 'system:node-proxy-access'
kubectl get clusterrole system:node-proxy-access -o yaml
What to look for in the output:
In rules::
-
Problematic resource/verb combinations (need human judgement):
rules:- apiGroups: [""]resources:- nodes- nodes/proxyverbs:- get- list- watch- proxy # explicit proxy access# or:- '*' # implicitly includes proxy -
Higher risk patterns:
resources: ["*"]withverbs: ["*"]or includingproxyresources: ["nodes"]+verbs: ["*"]- Any role intended for non-admins (developers, CI, apps) that has
nodeswithproxyor*verbs.
Roles with such rules and bound to non-admin users/groups/service accounts are likely too permissive.
# 3. Show which subjects are bound to each identified ClusterRole/Role
# Replace NAME and NAMESPACE based on step 1 output
# For ClusterRole bindings:
kubectl get clusterrolebindings -o json \
| jq -r '
.items[]
| select(.roleRef.kind == "ClusterRole" and .roleRef.name == "system:node-proxy-access")
| .metadata.name as $crb
| .subjects[]?
| "\($crb): \(.kind)/\(.namespace // "cluster")/\(.name)"
'
# For Role bindings (namespace-scoped):
kubectl get rolebindings -A -o json \
| jq -r '
.items[]
| select(.roleRef.kind == "Role" and .roleRef.name == "node-proxy-role")
| .metadata.namespace as $ns
| .metadata.name as $rb
| .subjects[]?
| "\($ns)/\($rb): \(.kind)/\(.namespace // "cluster")/\(.name)"
'
What to look for:
- Non-admin or broad subjects, for example:
system:authenticatedorsystem:unauthenticated- Wildcard groups (e.g.,
system:serviceaccounts,system:serviceaccounts:<ns>) - Application service accounts in app namespaces
- External user groups used by regular developers
Access to nodes proxy should normally be restricted to a small, trusted administrator group or break-glass account.
# 4. Cluster-wide view: all roles that can proxy nodes with their bindings
# (This gives a review worksheet; still needs human judgement)
kubectl get clusterroles,roles -A -o json \
| jq -r '
def has_node_proxy:
any(.rules[]?; (
any(.resources[]?; . == "nodes" or . == "nodes/proxy") and
any(.verbs[]?; . == "proxy" or . == "*")
));
.items[]
| select(has_node_proxy)
| {
kind: .kind,
name: .metadata.name,
namespace: (.metadata.namespace // "cluster")
}
' | while read -r line; do
kind=$(echo "$line" | jq -r '.kind')
name=$(echo "$line" | jq -r '.name')
ns=$(echo "$line" | jq -r '.namespace')
echo "=== $kind $ns/$name ==="
if [ "$kind" = "ClusterRole" ]; then
kubectl get clusterrole "$name" -o json \
| jq '.rules[] | select(
( .resources? // [] | index("nodes") or index("nodes/proxy") ) and
( .verbs? // [] | index("proxy") or index("*") )
)'
echo "-- Bindings --"
kubectl get clusterrolebindings -o json \
| jq -r "
.items[]
| select(.roleRef.kind == \"ClusterRole\" and .roleRef.name == \"$name\")
| .metadata.name as \$crb
| .subjects[]?
| \"\(\$crb): \(.kind)/\(.namespace // \"cluster\")/\(.name)\"
"
else
kubectl get role -n "$ns" "$name" -o json \
| jq '.rules[] | select(
( .resources? // [] | index("nodes") or index("nodes/proxy") ) and
( .verbs? // [] | index("proxy") or index("*") )
)'
echo "-- Bindings --"
kubectl get rolebindings -n "$ns" -o json \
| jq -r "
.items[]
| select(.roleRef.kind == \"Role\" and .roleRef.name == \"$name\")
| .metadata.name as \$rb
| .subjects[]?
| \"${ns}/\(\$rb): \(.kind)/\(.namespace // \"cluster\")/\(.name)\"
"
fi
echo
done
What to look for:
- Any role where:
- The rule snippet shows
nodes/nodes/proxywithproxyor*verbs, and - Bindings list subjects that are not clearly trusted administrators.
- The rule snippet shows
Those combinations are candidates to tighten or remove based on your access policy.
# 5. Verification after you adjust RBAC (manual policy decision required)
kubectl get clusterroles,roles -A -o json \
| jq -r '
.items[]
| . as $role
| (.rules[]? // [])
| select(
(.resources[]? == "nodes" or .resources[]? == "nodes/proxy") and
(.verbs[]? == "proxy" or .verbs[]? == "*")
)
| "\($role.kind)/\($role.metadata.namespace // "cluster")/\($role.metadata.name)"
' | sort -u
Success indication:
- The output is empty, or
- Any remaining roles are intentionally approved for trusted administrators only, with bindings you have reviewed and accepted.
Automation
#!/usr/bin/env bash
# Report Roles/ClusterRoles that can access the Node proxy sub‑resource
# Run from any machine with kubectl access and cluster‑wide RBAC read permissions.
set -euo pipefail
echo "=== ClusterRoles with Node proxy access ==="
kubectl get clusterroles -o json \
| jq -r '
.items[]
| {
name: .metadata.name,
rules: .rules[]
}
| select(
(
(.rules.resources? // []) | index("nodes/proxy")
)
or
(
(.rules.resources? // []) | any(. == "nodes" or . == "nodes/*")
and ((.rules.verbs? // []) | any(. == "proxy" or . == "*" ))
)
)
| [
"ClusterRole: " + .name,
" Resources: " + ((.rules.resources // []) | join(", ")),
" Verbs: " + ((.rules.verbs // []) | join(", ")),
" API grps: " + ((.rules.apiGroups // []) | join(", ")),
""
]
| .[]
'
echo
echo "=== Roles with Node proxy access (all namespaces) ==="
kubectl get roles --all-namespaces -o json \
| jq -r '
.items[]
| {
ns: .metadata.namespace,
name: .metadata.name,
rules: .rules[]
}
| select(
(
(.rules.resources? // []) | index("nodes/proxy")
)
or
(
(.rules.resources? // []) | any(. == "nodes" or . == "nodes/*")
and ((.rules.verbs? // []) | any(. == "proxy" or . == "*" ))
)
)
| [
"Role: " + .ns + "/" + .name,
" Resources: " + ((.rules.resources // []) | join(", ")),
" Verbs: " + ((.rules.verbs // []) | join(", ")),
" API grps: " + ((.rules.apiGroups // []) | join(", ")),
""
]
| .[]
'
echo
echo "=== Note ==="
echo "Any entry above represents a candidate for review."
echo "- High risk: rules that include 'nodes/proxy', 'nodes/*' or verb 'proxy' or '*' on 'nodes'."
echo "- Ensure only tightly controlled admin roles retain such access; others should be removed or scoped down."
Explanation of problematic output:
- Any listed
ClusterRoleorRoleis granting some form of Node proxy access. - Especially concerning:
resourcescontainsnodes/proxyornodes/*.resourcescontainsnodesandverbscontainsproxyor*.
- For each such role, review who is bound to it (via
RoleBinding/ClusterRoleBinding) and decide whether they are trusted administrators. If not, adjust or remove the permission manually.