> ## 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 Nodes

### More Info:

Access to the node proxy sub-resource allows direct interaction with the kubelet API, bypassing normal controls. Restrict it.

### Risk Level

Medium

### Address

Security

### Compliance Standards

* CIS Kubernetes

### Triage and Remediation

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

    <AccordionGroup>
      <Accordion title="Manual Steps" defaultOpen="true">
        1. **Identify ClusterRoles granting node/proxy access**
           * Run on: any machine with `kubectl` access
           * Command:
             ```bash theme={null}
             kubectl get clusterroles -o json | \
               jq -r '
                 .items[]
                 | select(.rules[]? 
                   | select(
                       (.resources[]? == "nodes/proxy")
                       and (.verbs[]? == "get" or .verbs[]? == "create" or .verbs[]? == "update" or .verbs[]? == "delete" or .verbs[]? == "*")
                     )
                 )
                 | .metadata.name
               ' | sort -u
             ```
           * Save the resulting ClusterRole names for review.

        2. **Review detailed rules on those ClusterRoles**
           * For each ClusterRole name found in step 1, inspect its full spec:
             ```bash theme={null}
             kubectl get clusterrole <CLUSTERROLE_NAME> -o yaml
             ```
           * Confirm which `rules` entries reference `resources: ["nodes/proxy"]` (or include it among multiple resources) and what `verbs` are allowed. Determine whether each use is strictly required (for example, by an infrastructure component that legitimately needs `nodes/proxy`).

        3. **Map ClusterRoles to subjects using them**
           * Run:
             ```bash theme={null}
             kubectl get clusterrolebindings -o yaml > /tmp/clusterrolebindings.yaml
             ```
           * For each ClusterRole from step 1, list the bindings and subjects (users, groups, service accounts) that receive it:
             ```bash theme={null}
             yq '.items[]
                | select(.roleRef.kind == "ClusterRole" and .roleRef.name == "<CLUSTERROLE_NAME>")
                | {name: .metadata.name, subjects: .subjects}' /tmp/clusterrolebindings.yaml
             ```
           * Decide, per subject, whether they truly require node proxy access.

        4. **Remove unnecessary nodes/proxy permissions from ClusterRoles**
           * For any ClusterRole where `nodes/proxy` access is not justified, edit it to drop that resource from the rules (or restrict verbs to only what is essential; ideally remove it entirely):
             ```bash theme={null}
             kubectl edit clusterrole <CLUSTERROLE_NAME>
             ```
           * In your editor, remove `nodes/proxy` from `resources:` lists, or delete the entire `rules` entry if that is its only resource. Save and exit to apply.
           * If some subjects only need a subset of permissions, consider:
             * Creating a new, more restrictive ClusterRole without `nodes/proxy`.
             * Rebinding those subjects to the new role with `kubectl edit clusterrolebinding <BINDING_NAME>`.

        5. **Verify no unintended nodes/proxy access remains**
           * Re-run the discovery query:
             ```bash theme={null}
             kubectl get clusterroles -o json | \
               jq -r '
                 .items[]
                 | select(.rules[]? 
                   | select(
                       (.resources[]? == "nodes/proxy")
                       and (.verbs[]? == "get" or .verbs[]? == "create" or .verbs[]? == "update" or .verbs[]? == "delete" or .verbs[]? == "*")
                     )
                 )
                 | .metadata.name
               ' | sort -u
             ```
           * Confirm that only explicitly justified ClusterRoles (if any) appear, and that you have a documented rationale for each.

        6. **Optionally test from a non-privileged subject**
           * Using a user or service account that should not have node proxy access, attempt a proxy call and confirm it is forbidden:
             ```bash theme={null}
             kubectl --as=<USER_OR_SA_SUBJECT> get --raw /api/v1/nodes/<NODE_NAME>/proxy/ 2>&1 | sed -n '1,10p'
             ```
           * Ensure the response indicates lack of authorization (e.g., “forbidden”) for all subjects that are not explicitly approved to use the node proxy sub-resource.
      </Accordion>

      <Accordion title="Using kubectl">
        ```bash theme={null}
        # 1) List all ClusterRoles that grant any access to nodes/proxy
        # Run on: any machine with kubectl access
        kubectl get clusterroles -o json \
          | jq -r '
            .items[]
            | select(
                .rules[]
                | select(
                    (.resources // []) | index("nodes/proxy")
                  )
              )
            | .metadata.name
          '
        ```

        **Problem indication:** Any ClusterRole name listed here is potentially problematic and must be manually reviewed.

        ***

        ```bash theme={null}
        # 2) Show full definitions of those ClusterRoles for detailed review
        # Replace <clusterrole-name> with names from the previous command
        kubectl get clusterrole <clusterrole-name> -o yaml
        ```

        **What to look for as a problem:**

        In the `rules` section, find entries where `resources` includes `nodes/proxy`. For example:

        ```yaml theme={null}
        rules:
        - apiGroups: [""]
          resources:
          - nodes/proxy
          verbs:
          - get
          - list
          - watch
          - create
          - update
          - delete
          - proxy
          - *           # especially high risk
        ```

        Risk indicators (subject to human judgment):

        * `resources` includes `nodes/proxy` with broad `verbs` (e.g., `*`, `proxy`, `create`, `update`, `delete`).
        * ClusterRole is bound to many subjects or to wide scopes (e.g., groups like `system:authenticated`).

        ***

        ```bash theme={null}
        # 3) Find which subjects are actually using these ClusterRoles
        # Run for each ClusterRole identified
        kubectl get clusterrolebindings -o json \
          | jq -r '
            .items[]
            | select(.roleRef.kind == "ClusterRole" and .roleRef.name == "<clusterrole-name>")
            | {
                binding: .metadata.name,
                subjects: (.subjects // [])
              }
          '
        ```

        **Problem indication:**

        * Bindings where `subjects` include:
          * Broad groups (e.g., `system:authenticated`, `system:unauthenticated`).
          * ServiceAccounts in many namespaces.
          * Human users who do not need direct kubelet access.

        These combinations suggest access to `nodes/proxy` is too wide and should be reconsidered.

        ***

        ```bash theme={null}
        # 4) (Optional) Check namespaced Roles as well, in case your cluster uses them for node access
        # Run on: any machine with kubectl access
        kubectl get roles -A -o json \
          | jq -r '
            .items[]
            | select(
                .rules[]
                | select(
                    (.resources // []) | index("nodes/proxy")
                  )
              )
            | "\(.metadata.namespace)/\(.metadata.name)"
          '
        ```

        Again, inspect each with:

        ```bash theme={null}
        kubectl get role <role-name> -n <namespace> -o yaml
        kubectl get rolebinding -n <namespace> -o yaml \
          | jq -r '
            .items[]
            | select(.roleRef.kind == "Role" and .roleRef.name == "<role-name>")
            | {
                binding: .metadata.name,
                subjects: (.subjects // [])
              }
          '
        ```

        **Problem indication:** Same as for ClusterRoles—`nodes/proxy` plus broad subjects or unnecessary use cases.

        ***

        After you complete your review and any manual adjustments, re-run the initial listing:

        ```bash theme={null}
        kubectl get clusterroles -o json \
          | jq -r '
            .items[]
            | select(
                .rules[]
                | select(
                    (.resources // []) | index("nodes/proxy")
                  )
              )
            | .metadata.name
          '
        ```

        If no ClusterRoles remain (or only tightly scoped, justified ones), the risk from `nodes/proxy` access has been minimized per your policy.
      </Accordion>

      <Accordion title="Automation">
        ```bash theme={null}
        #!/usr/bin/env bash
        # Report ClusterRoles and Roles that can access the node/proxy sub-resource
        # Run on any machine with kubectl context to the target cluster

        set -euo pipefail

        echo "=== Searching ClusterRoles with access to nodes/proxy ==="
        kubectl get clusterroles -o json \
          | jq -r '
            .items[]
            | select(
                .rules[]
                | select(
                    ((.resources // []) | index("nodes/proxy")) != null
                    or (
                      ((.resources // []) | index("nodes")) != null
                      and ((.verbs // []) | index("proxy")) != null
                    )
                  )
              )
            | .metadata.name
          ' | sort -u | awk '{print "ClusterRole:", $0}'

        echo
        echo "=== Detailed rules for matching ClusterRoles ==="
        kubectl get clusterroles -o json \
          | jq -r '
            .items[]
            | . as $cr
            | .rules[]
            | select(
                ((.resources // []) | index("nodes/proxy")) != null
                or (
                  ((.resources // []) | index("nodes")) != null
                  and ((.verbs // []) | index("proxy")) != null
                )
              )
            | "ClusterRole: \($cr.metadata.name)\n  apiGroups: \(.apiGroups // [])\n  resources: \(.resources // [])\n  verbs: \(.verbs // [])\n"
          '

        echo
        echo "=== Searching namespace-scoped Roles with access to nodes/proxy ==="
        kubectl get roles --all-namespaces -o json \
          | jq -r '
            .items[]
            | . as $role
            | .rules[]
            | select(
                ((.resources // []) | index("nodes/proxy")) != null
                or (
                  ((.resources // []) | index("nodes")) != null
                  and ((.verbs // []) | index("proxy")) != null
                )
              )
            | "Role: \($role.metadata.name)  Namespace: \($role.metadata.namespace)\n  apiGroups: \(.apiGroups // [])\n  resources: \(.resources // [])\n  verbs: \(.verbs // [])\n"
          '

        echo
        echo "=== Showing Subjects bound to suspect ClusterRoles (ClusterRoleBindings) ==="
        suspect_crs=$(kubectl get clusterroles -o json \
          | jq -r '
            .items[]
            | select(
                .rules[]
                | select(
                    ((.resources // []) | index("nodes/proxy")) != null
                    or (
                      ((.resources // []) | index("nodes")) != null
                      and ((.verbs // []) | index("proxy")) != null
                    )
                  )
              )
            | .metadata.name
          ' | sort -u)

        if [ -n "${suspect_crs}" ]; then
          kubectl get clusterrolebindings -o json \
            | jq -r --argjson names "$(printf '%s\n' $suspect_crs | jq -R . | jq -s .)" '
                .items[]
                | select( (.roleRef.kind == "ClusterRole") and ( ($names | index(.roleRef.name)) != null ) )
                | "ClusterRoleBinding: \(.metadata.name)\n  RoleRef: \(.roleRef.kind)/\(.roleRef.name)\n  Subjects: \(.subjects // [])\n"
              '
        else
          echo "No ClusterRoles with nodes/proxy or nodes+proxy detected."
        fi

        echo
        echo "=== Showing Subjects bound to suspect Roles (RoleBindings) ==="
        kubectl get roles --all-namespaces -o json \
          | jq -r '
            .items[]
            | select(
                .rules[]
                | select(
                    ((.resources // []) | index("nodes/proxy")) != null
                    or (
                      ((.resources // []) | index("nodes")) != null
                      and ((.verbs // []) | index("proxy")) != null
                    )
                  )
              )
            | {name: .metadata.name, namespace: .metadata.namespace}
          ' | while read -r line; do
                name=$(printf '%s\n' "$line" | jq -r '.name')
                ns=$(printf '%s\n' "$line" | jq -r '.namespace')
                echo "Namespace: ${ns}  Role: ${name}"
                kubectl get rolebindings -n "${ns}" -o json \
                  | jq -r --arg role "${name}" '
                      .items[]
                      | select(.roleRef.kind == "Role" and .roleRef.name == $role)
                      | "  RoleBinding: \(.metadata.name)\n    Subjects: \(.subjects // [])\n"
                    '
                echo
              done
        ```

        **How to interpret the output**

        * Any `ClusterRole` or `Role` whose rules include:
          * `resources: ["nodes/proxy"]` with any verbs, **or**
          * `resources: ["nodes"]` (or including `nodes`) with `verbs` containing `"proxy"`,
            is a potential problem and should be reviewed.
        * For each such role:
          * Check the bindings sections to see which users, groups, or service accounts (Subjects) are granted this access.
          * Access is overly broad if:
            * The role is bound to generic groups (e.g. `system:authenticated`, `system:masters`, large user groups), or
            * The role’s permissions are not strictly required for a narrowly scoped, documented use case (such as specific node debugging workflows).
      </Accordion>
    </AccordionGroup>
  </Tab>
</Tabs>
