Minimize Access To The Proxy Sub-Resource Of Node Objects
More Info:​
The proxy sub-resource of Node objects allows direct access to the kubelet API, bypassing usual controls. Restrict access to this sub-resource to trusted administrators only.
Risk Level​
Medium
Address​
Security
Compliance Standards​
- CIS EKS
Triage and Remediation​
- Remediation
Remediation​
Manual Steps
-
List all ClusterRoles and Roles that can use the
proxysubresource on Nodes- Run on: any machine with
kubectlaccess
kubectl get clusterroles -o json |jq -r '.items[] |select(.rules[]? |(.resources[]? | select(.=="nodes/proxy")) or(.resourceNames[]? | contains("nodes/proxy"))) |.metadata.name' | sort -ukubectl get roles --all-namespaces -o json |jq -r '.items[] |select(.rules[]? |(.resources[]? | select(.=="nodes/proxy")) or(.resourceNames[]? | contains("nodes/proxy"))) |(.metadata.namespace + ":" + .metadata.name)' | sort -u - Run on: any machine with
-
Inspect each identified Role/ClusterRole to confirm the exact permissions
- Run on: any machine with
kubectlaccess - For each ClusterRole name from step 1:
kubectl get clusterrole <CLUSTERROLE_NAME> -o yaml
- For each Role
NAMESPACE:NAMEfrom step 1:kubectl get role -n <NAMESPACE> <NAME> -o yaml - Manually review
rulesentries that referencenodes/proxyorresources: ["nodes"]withresourceNames: ["proxy"]and note who should legitimately have these permissions (typically only a small set of cluster admin roles).
- Run on: any machine with
-
Identify which subjects are bound to those Roles/ClusterRoles
- Run on: any machine with
kubectlaccess
kubectl get clusterrolebindings -o json |jq -r '.items[] |select(.roleRef.kind=="ClusterRole" and .roleRef.name as $r |$r == "<CLUSTERROLE_NAME_1>" or$r == "<CLUSTERROLE_NAME_2>" # add others from step 1) |.metadata.name'kubectl get rolebindings --all-namespaces -o json |jq -r '.items[] |select(.roleRef.kind=="Role" and(.roleRef.name == "<ROLE_NAME_1>" or.roleRef.name == "<ROLE_NAME_2>")) | # add others(.metadata.namespace + ":" + .metadata.name)'- For each binding found, inspect subjects:
kubectl get clusterrolebinding <CRB_NAME> -o yamlkubectl get rolebinding -n <NAMESPACE> <RB_NAME> -o yaml
- Decide which users/groups/service accounts truly require node proxy access (trusted administrators) and which do not.
- Run on: any machine with
-
Restrict or remove unnecessary
nodes/proxyaccess in Roles/ClusterRoles- Run on: any machine with
kubectlaccess - For each Role/ClusterRole where non-admin use is not required, edit to remove the
nodes/proxypermission (or scope it more narrowly if absolutely required):orkubectl edit clusterrole <CLUSTERROLE_NAME>kubectl edit role -n <NAMESPACE> <ROLE_NAME> - In the editor, delete the
rulesentries grantingnodes/proxywhere not needed, or move such permissions into a dedicated admin-only ClusterRole. Save and exit.
- Run on: any machine with
-
Tighten bindings so only trusted administrators receive
nodes/proxyrights- Run on: any machine with
kubectlaccess - For roles that must retain
nodes/proxy:- Ensure they are only bound to trusted admin users/groups/service accounts.
- Edit bindings to remove non-admin subjects or move them to safer roles lacking
nodes/proxy:kubectl edit clusterrolebinding <CRB_NAME>kubectl edit rolebinding -n <NAMESPACE> <RB_NAME>
- Run on: any machine with
-
Verify that only intended admin roles retain
nodes/proxyaccess- Run on: any machine with
kubectlaccess - Re-run discovery and confirm the remaining roles and bindings correspond only to trusted admins:
kubectl get clusterroles -o json |jq -r '.items[] |select(.rules[]? | .resources[]? | select(.=="nodes/proxy")) |.metadata.name' | sort -ukubectl get roles --all-namespaces -o json |jq -r '.items[] |select(.rules[]? | .resources[]? | select(.=="nodes/proxy")) |(.metadata.namespace + ":" + .metadata.name)' | sort -u
- Manually confirm that any remaining roles with
nodes/proxyare intentionally granted only to trusted administrator subjects.
- Run on: any machine with
Using kubectl
# 1) List all ClusterRoles and Roles that grant node/proxy access
# Run on: any machine with kubectl access
kubectl get clusterroles,roles -A -o yaml \
| awk '
$1=="kind:"{k=$2}
$1=="name:"{n=$2}
/resources:/ {inres=1}
inres && /- nodes/ {nodes=1}
inres && /verbs:/ {inverbs=1}
inverbs && /- proxy/ && nodes {print k, n; inres=inverbs=nodes=0}
'
# 2) Show full definitions of the roles identified above
# Replace <ROLE_KIND> and <ROLE_NAME> with values from step 1
kubectl get <ROLE_KIND> <ROLE_NAME> -o yaml
# 3) Find which subjects are bound to these roles (ClusterRoleBindings)
# For ClusterRoles only
kubectl get clusterrolebindings -o yaml \
| awk '
$1=="kind:"{k=$2}
$1=="name:"{n=$2}
/roleRef:/ {inrole=1}
inrole && /kind: ClusterRole/ {getline; gsub(/name: /,""); cr=$2}
/subjects:/ {insub=1}
insub && /- kind:/ {subj_kind=$2}
insub && /name:/ {subj_name=$2; print k, n, "->", cr, "->", subj_kind, subj_name}
'
# 4) For namespace-scoped Roles, see which ServiceAccounts are bound
kubectl get rolebindings -A -o yaml \
| awk '
$1=="kind:"{k=$2}
$1=="name:"{n=$2}
/namespace:/ {ns=$2}
/roleRef:/ {inrole=1}
inrole && /kind: Role/ {getline; gsub(/name: /,""); r=$2}
/subjects:/ {insub=1}
insub && /- kind:/ {subj_kind=$2}
insub && /name:/ {subj_name=$2; print ns, k, n, "->", r, "->", subj_kind, subj_name}
'
What indicates a problem:
- In step 1, any
ClusterRoleorRolethat:- Lists
resources: ["nodes", "nodes/proxy"]or includesnodes/proxy, and - Includes
verbssuch asproxy,*, or broad verbs (e.g.,get,list,watch,create) that includeproxy.
- Lists
- In step 3 and 4, those roles are:
- Bound to broad subjects like
system:authenticated,system:unauthenticated,system:masters, or large user groups. - Bound to application
ServiceAccounts not intended to have direct kubelet access. - Used by default/built-in bindings in a way that grants many identities access to
nodes/proxy.
- Bound to broad subjects like
These cases require human review to decide whether to narrow or remove nodes/proxy and/or re-scope the bindings to only trusted administrator identities.
Automation
#!/usr/bin/env bash
#
# Report subjects (users/groups/serviceaccounts) that can access the
# "proxy" sub-resource of "nodes" (any verb) via RBAC.
#
# Run on: any machine with kubectl access and appropriate privileges.
# Requirements: kubectl, jq
set -euo pipefail
echo "Checking ClusterRoles with access to 'nodes/proxy' ..."
# 1) Find all ClusterRoles that grant any verb on nodes/proxy
kubectl get clusterroles -o json | jq -r '
.items[]
| {
name: .metadata.name,
rules: (
.rules // []
| map(
select(
# resource must include "nodes"
((.resources // []) | index("nodes"))
and
# resourceNames or apiGroups do not matter for subresource
# subresource must include "proxy"
((.resources // []) | any(. == "nodes/proxy" or . == "nodes/proxy" or . == "nodes/*"))
or
(
# some roles use "nodes/proxy" explicitly, others may use "nodes/*"
((.resources // []) | index("nodes/proxy"))
or
((.resources // []) | index("nodes/*"))
)
)
)
)
}
| select(.rules | length > 0)
| .name
' | sort -u | tee /tmp/nodes-proxy-clusterroles.txt
echo
echo "Checking Roles with access to 'nodes/proxy' (namespaced) ..."
# 2) Find all namespaced Roles that grant any verb on nodes/proxy
kubectl get roles -A -o json | jq -r '
.items[]
| {
ns: .metadata.namespace,
name: .metadata.name,
rules: (
.rules // []
| map(
select(
((.resources // []) | index("nodes/proxy"))
or
((.resources // []) | index("nodes/*"))
)
)
)
}
| select(.rules | length > 0)
| "\(.ns) \(.name)"
' | sort -u | tee /tmp/nodes-proxy-roles.txt
echo
echo "Resolving which subjects are bound to these roles ..."
echo "ClusterRoleBindings involving nodes/proxy-related ClusterRoles:"
if [[ -s /tmp/nodes-proxy-clusterroles.txt ]]; then
while read -r cr; do
echo "=== ClusterRole: ${cr} ==="
kubectl get clusterrolebindings -o json | jq -r --arg CR "$cr" '
.items[]
| select(.roleRef.kind == "ClusterRole" and .roleRef.name == $CR)
| {
crb: .metadata.name,
subjects: (.subjects // [])
}
| "ClusterRoleBinding: \(.crb)\nSubjects:\n" +
( .subjects[]
| " - kind=\(.kind) name=\(.name) namespace=\(.namespace // "-")"
)
' || true
done < /tmp/nodes-proxy-clusterroles.txt
else
echo " (none)"
fi
echo
echo "RoleBindings involving nodes/proxy-related Roles:"
if [[ -s /tmp/nodes-proxy-roles.txt ]]; then
while read -r ns name; do
echo "=== Role: ${name} (namespace: ${ns}) ==="
kubectl get rolebindings -n "$ns" -o json | jq -r --arg NS "$ns" --arg RN "$name" '
.items[]
| select(.roleRef.kind == "Role" and .roleRef.name == $RN)
| {
rb: .metadata.name,
subjects: (.subjects // [])
}
| "RoleBinding: \(.rb)\nSubjects:\n" +
( .subjects[]
| " - kind=\(.kind) name=\(.name) namespace=\(.namespace // "-")"
)
' || true
done < /tmp/nodes-proxy-roles.txt
else
echo " (none)"
fi
echo
echo "Summary:"
echo " - ClusterRoles granting nodes/proxy: $(wc -l < /tmp/nodes-proxy-clusterroles.txt || echo 0)"
echo " - Roles (namespaced) granting nodes/proxy: $(wc -l < /tmp/nodes-proxy-roles.txt || echo 0)"
cat <<'EOF'
How to interpret this output:
1. Any ClusterRole or Role listed above grants some form of access to the
'proxy' sub-resource of Node objects (often via resources like
'nodes/proxy' or 'nodes/*').
2. For each such role, review the bound subjects:
- kind=User: individual users
- kind=Group: user groups
- kind=ServiceAccount: workloads running in the cluster
3. Potential problems:
- Broad, generic roles (e.g. cluster-admin, system:masters, or
custom "admin"/"dev" roles) bound to many subjects.
- ServiceAccounts used by application workloads that do not need
direct kubelet/proxy access.
- Wildcard access (verbs: ["*"] or resources: ["nodes/*"]) granted
to non-privileged users/groups.
4. Acceptable/expected:
- A very small set of trusted administrator identities or tightly
controlled operator/service accounts, where this access is
explicitly justified.
This script does NOT change any configuration. Use it to identify where
'access to nodes/proxy' exists, then manually decide which role rules
or bindings to tighten or remove.
EOF