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

# API Server Authorization Mode Should Include Node

### More Info:

Verifies that --authorization-mode includes Node. The Node authorizer restricts what kubelets can read and write, limiting the blast radius of a compromised node.

### Risk Level

High

### Address

Security

### Compliance Standards

* CIS Kubernetes

### Triage and Remediation

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

    <AccordionGroup>
      <Accordion title="Manual Steps" defaultOpen="true">
        1. On every control plane node, back up the existing manifest before making changes:
           ```bash theme={null}
           sudo cp -a /etc/kubernetes/manifests/kube-apiserver.yaml /etc/kubernetes/manifests/kube-apiserver.yaml.bak.$(date +%F-%H%M%S)
           ```

        2. On every control plane node, open the API server static pod manifest for editing:
           ```bash theme={null}
           sudo vi /etc/kubernetes/manifests/kube-apiserver.yaml
           ```
           (Use any text editor available if `vi` is not preferred.)

        3. In the `command:` section of the `kube-apiserver` container, locate any existing `--authorization-mode=` argument.
           * If present, ensure it includes `Node` (for example: `--authorization-mode=Node,RBAC`).
           * If not present, add a new line under the other `--` flags, for example:
           ```yaml theme={null}
               - --authorization-mode=Node,RBAC
           ```

        4. Save the file and exit the editor. The kubelet will automatically detect the manifest change and restart the `kube-apiserver` static pod; expect a brief control plane interruption while it restarts.

        5. On every control plane node, verify that the running API server process includes `--authorization-mode` with `Node`:
           ```bash theme={null}
           /bin/ps -ef | grep kube-apiserver | grep -v grep
           ```
           Confirm in the output that the `kube-apiserver` command line contains an argument similar to:
           ```
           --authorization-mode=Node,RBAC
           ```
           (or another value that includes `Node`).
      </Accordion>

      <Accordion title="Using kubectl">
        kubectl cannot modify the kube-apiserver static pod manifest or its process flags, so this setting cannot be fixed via the Kubernetes API. To remediate, you must edit `/etc/kubernetes/manifests/kube-apiserver.yaml` directly on every control plane node as described in the Manual Steps section.
      </Accordion>

      <Accordion title="Automation">
        ```bash theme={null}
        #!/usr/bin/env bash
        #
        # Remediation: Ensure kube-apiserver --authorization-mode includes Node
        # Target: Every control plane node
        #
        # This script:
        #   - Backs up /etc/kubernetes/manifests/kube-apiserver.yaml
        #   - Ensures --authorization-mode includes "Node" and "RBAC"
        #   - Leaves any other existing modes intact (deduplicated)
        #   - Relies on kubelet to restart the kube-apiserver static pod
        #   - Verifies the running kube-apiserver process args
        #
        # Run on: every control plane node (with root privileges)
        # Usage: sudo bash fix-apiserver-authorization-mode-node.sh

        set -euo pipefail

        APISERVER_MANIFEST="/etc/kubernetes/manifests/kube-apiserver.yaml"
        BACKUP_SUFFIX=".pre_authmode_node_$(date +%Y%m%d%H%M%S)"
        REQUIRED_MODES=("Node" "RBAC")

        # --- Helper functions ---

        err() {
          echo "ERROR: $*" >&2
        }

        info() {
          echo "INFO:  $*"
        }

        check_root() {
          if [[ "$(id -u)" -ne 0 ]]; then
            err "This script must be run as root."
            exit 1
          fi
        }

        check_manifest_exists() {
          if [[ ! -f "${APISERVER_MANIFEST}" ]]; then
            err "Manifest ${APISERVER_MANIFEST} not found. This node may not be a control plane node or uses a different path."
            exit 1
          fi
        }

        backup_manifest() {
          local backup_path="${APISERVER_MANIFEST}${BACKUP_SUFFIX}"
          cp "${APISERVER_MANIFEST}" "${backup_path}"
          info "Backup created at ${backup_path}"
        }

        # Extract the current --authorization-mode value (comma-separated list)
        get_current_modes() {
          # Grep the line that contains --authorization-mode and extract its value
          # Handles patterns like:
          #   - --authorization-mode=RBAC,Webhook
          #   - - --authorization-mode=RBAC,Webhook
          #   - --authorization-mode RBAC,Webhook
          #   - - --authorization-mode
          #     - RBAC,Webhook
          #
          # We get the first occurrence only.
          python3 - "$APISERVER_MANIFEST" << 'PYEOF'
        import sys, re, yaml, os

        manifest_path = sys.argv[1]
        with open(manifest_path) as f:
            doc = yaml.safe_load(f)

        c = doc.get("spec", {}).get("containers", [])
        if not c:
            sys.exit(0)

        args = c[0].get("command") or c[0].get("args") or []
        modes = None

        # Normalize args to list of strings
        args = [str(a) for a in args]

        for i, a in enumerate(args):
            if a.startswith("--authorization-mode="):
                modes = a.split("=", 1)[1].strip()
                break
            if a == "--authorization-mode" and i + 1 < len(args):
                modes = args[i+1].strip()
                break

        if modes:
            print(modes)
        PYEOF
        }

        # Write updated modes back into the manifest, preserving YAML structure
        set_modes() {
          local new_modes="$1"
          python3 - "$APISERVER_MANIFEST" "$new_modes" << 'PYEOF'
        import sys, yaml, copy

        manifest_path, new_modes = sys.argv[1], sys.argv[2]

        with open(manifest_path) as f:
            doc = yaml.safe_load(f)

        c = doc.get("spec", {}).get("containers", [])
        if not c:
            sys.exit(0)

        container = c[0]
        args = container.get("command") or container.get("args") or []

        # Normalize args to list of strings
        args = [str(a) for a in args]

        updated = False
        for i, a in enumerate(args):
            if a.startswith("--authorization-mode="):
                args[i] = f"--authorization-mode={new_modes}"
                updated = True
                break
            if a == "--authorization-mode" and i + 1 < len(args):
                args[i+1] = new_modes
                updated = True
                break

        if not updated:
            # Append as a new argument: --authorization-mode=<modes>
            args.append(f"--authorization-mode={new_modes}")

        # Decide whether original used "command" or "args"
        if container.get("command"):
            container["command"] = args
        else:
            container["args"] = args

        with open(manifest_path, "w") as f:
            yaml.safe_dump(doc, f, default_flow_style=False)
        PYEOF
        }

        normalize_modes() {
          local current="$1"
          # Split on commas into an array, trim spaces, dedupe, ensure REQUIRED_MODES present
          python3 - << PYEOF
        modes_str = """${current}"""
        required = ${REQUIRED_MODES[@]/#/\"}
        required = [${required}]
        modes = [m.strip() for m in modes_str.split(",") if m.strip()] if modes_str.strip() else []

        # ensure required modes are present
        for r in required:
            if r not in modes:
                modes.append(r)

        # dedupe while preserving order
        seen = set()
        result = []
        for m in modes:
            if m not in seen:
                seen.add(m)
                result.append(m)

        print(",".join(result))
        PYEOF
        }

        verify_running_process() {
          info "Waiting for kube-apiserver static pod to be restarted by kubelet (up to 120s)..."
          local timeout=120
          local interval=5
          local elapsed=0

          while (( elapsed < timeout )); do
            if /bin/ps -ef | grep kube-apiserver | grep -v grep >/dev/null 2>&1; then
              break
            fi
            sleep "${interval}"
            elapsed=$((elapsed + interval))
          done

          if ! /bin/ps -ef | grep kube-apiserver | grep -v grep >/dev/null 2>&1; then
            err "kube-apiserver process not detected after waiting. Check pod status with 'crictl ps' or 'docker ps' depending on your runtime."
            return 1
          fi

          info "Verifying that kube-apiserver is running with --authorization-mode including Node..."
          if /bin/ps -ef | grep kube-apiserver | grep -v grep | grep -- '--authorization-mode' | grep -q 'Node'; then
            info "Verification succeeded: kube-apiserver --authorization-mode includes Node."
          else
            err "Verification FAILED: kube-apiserver --authorization-mode does not include Node in the running process."
            return 1
          fi
        }

        # --- Main ---

        check_root
        check_manifest_exists

        info "Reading current --authorization-mode from ${APISERVER_MANIFEST}..."
        CURRENT_MODES="$(get_current_modes || true)"

        if [[ -z "${CURRENT_MODES}" ]]; then
          info "No existing --authorization-mode found; will add Node,RBAC."
          NEW_MODES="$(normalize_modes "")"
        else
          info "Current --authorization-mode modes: ${CURRENT_MODES}"
          NEW_MODES="$(normalize_modes "${CURRENT_MODES}")"
        fi

        info "New desired --authorization-mode modes: ${NEW_MODES}"

        if [[ "${CURRENT_MODES}" == "${NEW_MODES}" ]]; then
          info "Manifest already configured with required modes; no change needed."
        else
          backup_manifest
          set_modes "${NEW_MODES}"
          info "Updated ${APISERVER_MANIFEST} with --authorization-mode=${NEW_MODES}"
        fi

        # Verification (running process)
        verify_running_process
        ```
      </Accordion>
    </AccordionGroup>
  </Tab>
</Tabs>
