> ## 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 ServiceAccount Admission Plugin

### More Info:

Verifies that the ServiceAccount admission plugin is not disabled so automated service account tokens are managed and enforced for pods.

### 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 current API server manifest (run as root):
           ```sh theme={null}
           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 manifest for editing:
           ```sh theme={null}
           vi /etc/kubernetes/manifests/kube-apiserver.yaml
           ```

        3. In the `spec.containers[0].command` list, locate any `--disable-admission-plugins=` entry.
           * If it exists and contains `ServiceAccount`, remove `ServiceAccount` from the comma-separated list (leave the other plugins unchanged).
           * If the list becomes empty after removal, delete the entire `--disable-admission-plugins=...` argument line.\
             Save and exit the editor.\
             (Changing this file will automatically restart the kube-apiserver static pod on that node.)

        4. Wait for the kube-apiserver pod to restart and become Running on the control plane node:
           ```sh theme={null}
           crictl ps | grep kube-apiserver
           ```
           Ensure the most recent kube-apiserver container is in a `Running` state.

        5. From any control plane node, verify that the `ServiceAccount` admission plugin is not disabled:
           ```sh theme={null}
           /bin/ps -ef | grep kube-apiserver | grep -v grep | grep -- '--disable-admission-plugins'
           ```
           Confirm that:
           * Either no `--disable-admission-plugins` flag is present, **or**
           * The flag is present but its value does **not** include `ServiceAccount`.
      </Accordion>

      <Accordion title="Using kubectl">
        kubectl cannot modify the kube-apiserver static pod manifest or its process flags. This finding must be remediated by editing `/etc/kubernetes/manifests/kube-apiserver.yaml` directly on every control plane node; see the Manual Steps section for the required host-level changes and verification.
      </Accordion>

      <Accordion title="Automation">
        ```bash theme={null}
        #!/usr/bin/env bash
        #
        # Automation: Ensure ServiceAccount admission plugin is NOT disabled
        # Scope: every control plane node
        # Effect: Editing /etc/kubernetes/manifests/kube-apiserver.yaml will restart kube-apiserver (static pod)
        #

        set -euo pipefail

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

        echo "[INFO] Verifying kube-apiserver manifest exists at ${APISERVER_MANIFEST}"
        if [ ! -f "${APISERVER_MANIFEST}" ]; then
          echo "[ERROR] kube-apiserver manifest not found at ${APISERVER_MANIFEST}. Aborting."
          exit 1
        fi

        echo "[INFO] Creating backup in ${BACKUP_DIR}"
        mkdir -p "${BACKUP_DIR}"
        cp -p "${APISERVER_MANIFEST}" "${BACKUP_DIR}/"

        # Function to normalize YAML for simple text replacement:
        # We will operate only on the '--disable-admission-plugins=' argument line(s).
        # Idempotency: if ServiceAccount is already not present, no functional change.

        TMP_MANIFEST="$(mktemp)"
        cp "${APISERVER_MANIFEST}" "${TMP_MANIFEST}"

        # Remove 'ServiceAccount' from any --disable-admission-plugins flag.
        # Handles comma-separated lists with or without spaces.
        echo "[INFO] Ensuring ServiceAccount is not listed in --disable-admission-plugins"
        python3 - "$TMP_MANIFEST" > "${TMP_MANIFEST}.new" << 'PYCODE'
        import sys, re

        path = sys.argv[1]
        with open(path, 'r') as f:
            lines = f.readlines()

        def clean_plugins(value: str) -> str:
            # Split by comma, strip spaces, remove empty and 'ServiceAccount'
            plugins = [p.strip() for p in value.split(',')]
            plugins = [p for p in plugins if p and p != 'ServiceAccount']
            return ','.join(plugins)

        out = []
        pattern = re.compile(r'(--disable-admission-plugins=)(\S+)')
        for line in lines:
            if '--disable-admission-plugins=' in line:
                # Handle the simple case of the flag entirely on this line
                m = pattern.search(line)
                if m:
                    prefix, val = m.groups()
                    new_val = clean_plugins(val)
                    # If list becomes empty, remove the flag entirely from this line
                    if not new_val:
                        # Remove the whole argument from the line
                        # Handle possible preceding spaces and comma separation (args separated by spaces).
                        # Replace '--disable-admission-plugins=<...>' by '' and clean up spaces.
                        new_line = pattern.sub('', line)
                        # Collapse multiple spaces
                        new_line = re.sub(r' {2,}', ' ', new_line)
                        # Strip trailing spaces before newline
                        new_line = re.sub(r' +\n', '\n', new_line)
                        line = new_line
                    else:
                        line = pattern.sub(prefix + new_val, line)
            out.append(line)

        sys.stdout.writelines(out)
        PYCODE

        mv "${TMP_MANIFEST}.new" "${TMP_MANIFEST}"

        # Only replace the live manifest if there is a change
        if cmp -s "${APISERVER_MANIFEST}" "${TMP_MANIFEST}"; then
          echo "[INFO] No changes needed; ServiceAccount is already not disabled."
          rm -f "${TMP_MANIFEST}"
        else
          echo "[INFO] Updating ${APISERVER_MANIFEST} (this will restart kube-apiserver static pod)"
          mv "${TMP_MANIFEST}" "${APISERVER_MANIFEST}"
        fi

        # Verification: check running kube-apiserver process args do NOT disable ServiceAccount
        echo "[INFO] Waiting up to 120 seconds for kube-apiserver to restart (if changed)"
        end=$((SECONDS + 120))
        while (( SECONDS < end )); do
          if /bin/ps -ef | grep kube-apiserver | grep -v grep >/dev/null 2>&1; then
            break
          fi
          sleep 3
        done

        if ! /bin/ps -ef | grep kube-apiserver | grep -v grep >/dev/null 2>&1; then
          echo "[ERROR] kube-apiserver process not detected after waiting. Please investigate."
          exit 1
        fi

        echo "[INFO] Verifying that ServiceAccount is NOT disabled in kube-apiserver arguments"
        if /bin/ps -ef | grep kube-apiserver | grep -v grep | grep -- '--disable-admission-plugins' | grep -q 'ServiceAccount'; then
          echo "[ERROR] kube-apiserver still started with ServiceAccount in --disable-admission-plugins."
          echo "[ERROR] Please review ${APISERVER_MANIFEST} manually."
          exit 1
        fi

        echo "[INFO] Verification successful: ServiceAccount admission plugin is not disabled."
        exit 0
        ```
      </Accordion>
    </AccordionGroup>
  </Tab>
</Tabs>
