> ## Documentation Index
> Fetch the complete documentation index at: https://cloudanix.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# 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

<Tabs>
  <Tab title="Remediation">
    ### Remediation

    <AccordionGroup>
      <Accordion title="Manual Steps" defaultOpen="true">
        1. **List all ClusterRoles and Roles that can use the `proxy` subresource on Nodes**
           * Run on: any machine with `kubectl` access
           ```bash theme={null}
           kubectl get clusterroles -o json |
             jq -r '
               .items[] |
               select(
                 .rules[]? |
                 (.resources[]? | select(.=="nodes/proxy")) or
                 (.resourceNames[]? | contains("nodes/proxy"))
               ) |
               .metadata.name
             ' | sort -u
           ```
           ```bash theme={null}
           kubectl 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
           ```

        2. **Inspect each identified Role/ClusterRole to confirm the exact permissions**
           * Run on: any machine with `kubectl` access
           * For each ClusterRole name from step 1:
             ```bash theme={null}
             kubectl get clusterrole <CLUSTERROLE_NAME> -o yaml
             ```
           * For each Role `NAMESPACE:NAME` from step 1:
             ```bash theme={null}
             kubectl get role -n <NAMESPACE> <NAME> -o yaml
             ```
           * Manually review `rules` entries that reference `nodes/proxy` or `resources: ["nodes"]` with `resourceNames: ["proxy"]` and note who should legitimately have these permissions (typically only a small set of cluster admin roles).

        3. **Identify which subjects are bound to those Roles/ClusterRoles**
           * Run on: any machine with `kubectl` access
           ```bash theme={null}
           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
             '
           ```
           ```bash theme={null}
           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:
             ```bash theme={null}
             kubectl get clusterrolebinding <CRB_NAME> -o yaml
             ```
             ```bash theme={null}
             kubectl 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.

        4. **Restrict or remove unnecessary `nodes/proxy` access in Roles/ClusterRoles**
           * Run on: any machine with `kubectl` access
           * For each Role/ClusterRole where non-admin use is not required, edit to remove the `nodes/proxy` permission (or scope it more narrowly if absolutely required):
             ```bash theme={null}
             kubectl edit clusterrole <CLUSTERROLE_NAME>
             ```
             or
             ```bash theme={null}
             kubectl edit role -n <NAMESPACE> <ROLE_NAME>
             ```
           * In the editor, delete the `rules` entries granting `nodes/proxy` where not needed, or move such permissions into a dedicated admin-only ClusterRole. Save and exit.

        5. **Tighten bindings so only trusted administrators receive `nodes/proxy` rights**
           * Run on: any machine with `kubectl` access
           * 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`:
               ```bash theme={null}
               kubectl edit clusterrolebinding <CRB_NAME>
               ```
               ```bash theme={null}
               kubectl edit rolebinding -n <NAMESPACE> <RB_NAME>
               ```

        6. **Verify that only intended admin roles retain `nodes/proxy` access**
           * Run on: any machine with `kubectl` access
           * Re-run discovery and confirm the remaining roles and bindings correspond only to trusted admins:
             ```bash theme={null}
             kubectl get clusterroles -o json |
               jq -r '
                 .items[] |
                 select(.rules[]? | .resources[]? | select(.=="nodes/proxy")) |
                 .metadata.name
               ' | sort -u
             ```
             ```bash theme={null}
             kubectl 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/proxy` are intentionally granted only to trusted administrator subjects.
      </Accordion>

      <Accordion title="Using kubectl">
        ```bash theme={null}
        # 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 `ClusterRole` or `Role` that:
          * Lists `resources: ["nodes", "nodes/proxy"]` or includes `nodes/proxy`, and
          * Includes `verbs` such as `proxy`, `*`, or broad verbs (e.g., `get`, `list`, `watch`, `create`) that include `proxy`.
        * 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 `ServiceAccount`s not intended to have direct kubelet access.
          * Used by default/built-in bindings in a way that grants many identities access to `nodes/proxy`.

        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.
      </Accordion>

      <Accordion title="Automation">
        ```bash theme={null}
        #!/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
        ```
      </Accordion>
    </AccordionGroup>
  </Tab>
</Tabs>
