> ## 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 Should Enable The DenyServiceExternalIPs Admission Plugin

### More Info:

Verifies that the DenyServiceExternalIPs admission plugin is enabled. This plugin blocks use of Service external IPs, mitigating a known man-in-the-middle vector.

### 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. On every control plane node, back up the API server manifest before editing:
           ```bash theme={null}
           sudo cp /etc/kubernetes/manifests/kube-apiserver.yaml /etc/kubernetes/manifests/kube-apiserver.yaml.bak
           ```

        2. On every control plane node, open the API server manifest for editing:
           ```bash theme={null}
           sudo vi /etc/kubernetes/manifests/kube-apiserver.yaml
           ```

        3. In the `spec.containers[0].command` section, locate any existing `--disable-admission-plugins=` flag.
           * Remove `ServiceAccount` from its comma-separated list if present, ensuring it is **not** listed.
           * If the flag becomes empty, remove the flag line entirely.
           * Save and exit the editor.\
             (Editing this static pod manifest will cause the kube-apiserver pod to restart automatically.)

        4. On every control plane node, confirm that the kube-apiserver pod has restarted and is running:
           ```bash theme={null}
           sudo crictl ps | grep kube-apiserver
           ```
           (Use `docker ps` instead if Docker is the container runtime.)

        5. On any machine with access to the node, verify the running kube-apiserver process no longer disables `ServiceAccount` via `--disable-admission-plugins`:
           ```bash theme={null}
           /bin/ps -ef | grep kube-apiserver | grep -v grep
           ```
           Inspect the command line output and confirm that either `--disable-admission-plugins` is absent, or if present, its value does **not** contain `ServiceAccount`.
      </Accordion>

      <Accordion title="Using kubectl">
        kubectl cannot modify kube-apiserver process flags or the static pod manifest at `/etc/kubernetes/manifests/kube-apiserver.yaml` on control plane nodes, so this finding cannot be fixed via the Kubernetes API. Make the required changes directly on each control plane node’s host configuration as described in the Manual Steps section.
      </Accordion>

      <Accordion title="Automation">
        ```bash theme={null}
        #!/usr/bin/env bash
        #
        # Automation: Enable DenyServiceExternalIPs admission plugin on all control plane nodes
        #
        # Usage:
        #   1) From any machine with SSH access to control plane nodes, create an inventory file:
        #        cat > controlplane_hosts.txt <<EOF
        #        10.0.0.11
        #        10.0.0.12
        #        10.0.0.13
        #        EOF
        #   2) Run:
        #        ./fix_apiserver_deny_service_external_ips.sh controlplane_hosts.txt
        #
        # This script:
        #   - Connects to each control plane node over SSH.
        #   - Edits /etc/kubernetes/manifests/kube-apiserver.yaml to ensure
        #     DenyServiceExternalIPs is present in --enable-admission-plugins and NOT present
        #     in --disable-admission-plugins.
        #   - Creates backup copies before changes.
        #   - Relies on the kubelet to restart the kube-apiserver static pod.
        #   - Verifies via `ps` that the plugin settings are correct.
        #
        # Requirements on the machine running this script:
        #   - bash, ssh, sed, grep
        #   - SSH key or passwordless sudo on target nodes (script uses sudo on remote).
        #
        # Runs on: any machine with SSH access to control plane nodes.
        # Remote edits on: every control plane node.

        set -euo pipefail

        INVENTORY_FILE="${1:-}"

        if [[ -z "${INVENTORY_FILE}" || ! -f "${INVENTORY_FILE}" ]]; then
          echo "Usage: $0 controlplane_hosts.txt"
          echo "controlplane_hosts.txt should contain one hostname/IP per line for each control plane node."
          exit 1
        fi

        SSH_OPTS="-o BatchMode=yes -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null"

        remote_fix_node() {
          local NODE="$1"

          echo "==== [${NODE}] Processing control plane node ===="

          ssh ${SSH_OPTS} "${NODE}" 'bash -s' <<'EOF'
        set -euo pipefail

        APISERVER_MANIFEST="/etc/kubernetes/manifests/kube-apiserver.yaml"
        BACKUP_DIR="/etc/kubernetes/manifests/backup_deny_service_externalips"
        TIMESTAMP="$(date +%Y%m%d%H%M%S)"

        if [[ ! -f "${APISERVER_MANIFEST}" ]]; then
          echo "  [WARN] kube-apiserver manifest not found at ${APISERVER_MANIFEST}; skipping."
          exit 0
        fi

        sudo mkdir -p "${BACKUP_DIR}"

        BACKUP_FILE="${BACKUP_DIR}/kube-apiserver.yaml.${TIMESTAMP}"
        sudo cp "${APISERVER_MANIFEST}" "${BACKUP_FILE}"
        echo "  [INFO] Backup created at ${BACKUP_FILE}"

        TMP_FILE="$(mktemp)"
        sudo cp "${APISERVER_MANIFEST}" "${TMP_FILE}"

        # Ensure DenyServiceExternalIPs is enabled and not disabled.

        # 1) Remove DenyServiceExternalIPs from any existing --disable-admission-plugins value.
        if grep -qE -- '--disable-admission-plugins=' "${TMP_FILE}"; then
          sudo sed -i \
            -e 's/\(--disable-admission-plugins=[^-]*\)DenyServiceExternalIPs,\{0,1\}/\1/g' \
            -e 's/\(--disable-admission-plugins=[^-]*\),\{0,1\}DenyServiceExternalIPs/\1/g' \
            "${TMP_FILE}"
        fi

        # 2) Ensure --enable-admission-plugins includes DenyServiceExternalIPs.
        if grep -qE -- '--enable-admission-plugins=' "${TMP_FILE}"; then
          # If flag exists but does not yet contain DenyServiceExternalIPs, append it.
          if ! grep -qE -- '--enable-admission-plugins=[^"]*DenyServiceExternalIPs' "${TMP_FILE}"; then
            sudo sed -i \
              -e 's/\(--enable-admission-plugins=[^-"]*\)"/\1,DenyServiceExternalIPs"/' \
              -e "s/\(--enable-admission-plugins=[^-']*\)'/\1,DenyServiceExternalIPs'/" \
              -e 's/\(--enable-admission-plugins=[^ ]*\)$/\1,DenyServiceExternalIPs/' \
              "${TMP_FILE}" || true
          fi
        else
          # No --enable-admission-plugins flag; add it alongside other args.
          # Try to append as a new - --enable-admission-plugins=... line in YAML args list.
          if grep -q '^\s*- kube-apiserver' "${TMP_FILE}"; then
            # Static pod style manifest; append line under existing args if present.
            if grep -q '^\s*- --' "${TMP_FILE}"; then
              sudo awk '
                /- --/ && !added {
                  print $0
                  print "    - --enable-admission-plugins=DenyServiceExternalIPs"
                  added=1
                  next
                }
                { print $0 }
                END {
                  if (!added) {
                    print "    - --enable-admission-plugins=DenyServiceExternalIPs"
                  }
                }
              ' "${TMP_FILE}" | sudo tee "${TMP_FILE}.new" >/dev/null
              sudo mv "${TMP_FILE}.new" "${TMP_FILE}"
            else
              # Fallback: just append a generic line near the command args.
              echo "    - --enable-admission-plugins=DenyServiceExternalIPs" | sudo tee -a "${TMP_FILE}" >/dev/null
            fi
          else
            # Very old-style manifest or different layout: append a generic arg.
            echo "    - --enable-admission-plugins=DenyServiceExternalIPs" | sudo tee -a "${TMP_FILE}" >/dev/null
          fi
        fi

        # Install updated manifest (this will cause kubelet to restart the kube-apiserver static pod).
        sudo mv "${TMP_FILE}" "${APISERVER_MANIFEST}"
        echo "  [INFO] Updated ${APISERVER_MANIFEST}; kubelet will restart kube-apiserver."

        # Wait for kube-apiserver process to restart and pick up new flags.
        echo "  [INFO] Waiting for kube-apiserver to reflect new flags..."
        RETRIES=30
        SLEEP_SECONDS=10

        for i in $(seq 1 "${RETRIES}"); do
          if ps -ef | grep kube-apiserver | grep -v grep >/dev/null 2>&1; then
            # Verify DenyServiceExternalIPs is enabled and not disabled.
            if ps -ef | grep kube-apiserver | grep -v grep | grep -q -- '--enable-admission-plugins=[^ ]*DenyServiceExternalIPs'; then
              if ! ps -ef | grep kube-apiserver | grep -v grep | grep -q -- '--disable-admission-plugins=[^ ]*DenyServiceExternalIPs'; then
                echo "  [OK] DenyServiceExternalIPs is enabled and not disabled on kube-apiserver."
                exit 0
              else
                echo "  [WARN] DenyServiceExternalIPs still appears in --disable-admission-plugins."
              fi
            else
              echo "  [WARN] DenyServiceExternalIPs not yet present in --enable-admission-plugins."
            fi
          else
            echo "  [INFO] kube-apiserver process not detected yet."
          fi
          sleep "${SLEEP_SECONDS}"
        done

        echo "  [ERROR] Timed out waiting for kube-apiserver to show correct DenyServiceExternalIPs flags."
        exit 1
        EOF

          echo "==== [${NODE}] Done ===="
        }

        while IFS= read -r NODE; do
          [[ -z "${NODE}" || "${NODE}" =~ ^# ]] && continue
          remote_fix_node "${NODE}"
        done < "${INVENTORY_FILE}"

        echo "All listed control plane nodes processed."
        ```
      </Accordion>
    </AccordionGroup>
  </Tab>
</Tabs>
