> ## 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 Service Account Key File Should Be Set

### More Info:

Verifies that --service-account-key-file is set so the API server uses a dedicated key to verify service account tokens instead of the TLS serving key.

### 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, confirm the current kube-apiserver static pod manifest path and back it up:

           ```bash theme={null}
           sudo ls -l /etc/kubernetes/manifests/kube-apiserver.yaml
           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, ensure you have a dedicated *public* key file for service accounts (for example `/etc/kubernetes/pki/sa.pub`). If you only have a private key (e.g. `/etc/kubernetes/pki/sa.key`), extract or create a matching public key as appropriate for your environment and place it at:

           ```bash theme={null}
           sudo ls -l /etc/kubernetes/pki/sa.pub
           ```

           (If the file is missing, follow your cluster’s key-management procedure to generate a service account keypair; this step is environment-specific and cannot be automated generically.)

        3. On every control plane node, edit the kube-apiserver static pod manifest to add the `--service-account-key-file` flag pointing to the public key file. Use a text editor:

           ```bash theme={null}
           sudo vi /etc/kubernetes/manifests/kube-apiserver.yaml
           ```

           Under the `command:` section for `kube-apiserver`, add a line similar to:

           ```yaml theme={null}
           - --service-account-key-file=/etc/kubernetes/pki/sa.pub
           ```

           Keep the YAML indentation consistent with the existing list of `- --...` flags.

        4. On every control plane node, if the public key file is not already mounted into the kube-apiserver container, add a corresponding `volumeMounts` entry and `volumes` entry in `/etc/kubernetes/manifests/kube-apiserver.yaml` so the container can read `/etc/kubernetes/pki/sa.pub`. For example, in the container spec:

           ```yaml theme={null}
           volumeMounts:
             - mountPath: /etc/kubernetes/pki
               name: k8s-certs
               readOnly: true
           ```

           And in the pod-level `volumes:`:

           ```yaml theme={null}
           volumes:
             - name: k8s-certs
               hostPath:
                 path: /etc/kubernetes/pki
                 type: DirectoryOrCreate
           ```

           (Adjust names if a similar volume already exists; do not create duplicates.)

        5. On every control plane node, 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 disruption during the restart.

        6. On every control plane node, verify that the kube-apiserver is now running with the `--service-account-key-file` argument set to the intended public key file:

           ```bash theme={null}
           /bin/ps -ef | grep kube-apiserver | grep -v grep
           ```

           Confirm the output includes a flag like:

           ```text theme={null}
           --service-account-key-file=/etc/kubernetes/pki/sa.pub
           ```
      </Accordion>

      <Accordion title="Using kubectl">
        kubectl cannot modify the kube-apiserver static pod manifest or its process flags, so this finding cannot be fixed via the Kubernetes API. The required change must be made directly on each control plane node by editing `/etc/kubernetes/manifests/kube-apiserver.yaml`; see the Manual Steps section for exact host-level instructions.
      </Accordion>

      <Accordion title="Automation">
        ```bash theme={null}
        #!/usr/bin/env bash
        #
        # Remediation: Ensure kube-apiserver --service-account-key-file is set
        # Scope: Run on every control plane node (with root privileges)
        #
        # Behavior:
        # - Creates a dedicated service account keypair under /etc/kubernetes/pki if missing
        # - Ensures /etc/kubernetes/manifests/kube-apiserver.yaml has --service-account-key-file flag
        # - Uses the public key as the flag value
        # - Idempotent: safe to re-run
        # - Editing the manifest will restart the kube-apiserver static pod

        set -euo pipefail

        APISERVER_MANIFEST="/etc/kubernetes/manifests/kube-apiserver.yaml"
        PKI_DIR="/etc/kubernetes/pki"
        SA_KEY="${PKI_DIR}/sa.key"
        SA_PUB="${PKI_DIR}/sa.pub"
        BACKUP_SUFFIX="$(date +%Y%m%d%H%M%S)"

        echo "[INFO] Running on control plane node: configuring kube-apiserver --service-account-key-file"

        if [[ $EUID -ne 0 ]]; then
          echo "[ERROR] This script must be run as root on each control plane node." >&2
          exit 1
        fi

        if [[ ! -f "${APISERVER_MANIFEST}" ]]; then
          echo "[ERROR] kube-apiserver manifest not found at ${APISERVER_MANIFEST}" >&2
          exit 1
        fi

        mkdir -p "${PKI_DIR}"
        chmod 700 "${PKI_DIR}"

        # Generate service account keypair if missing
        if [[ ! -f "${SA_KEY}" || ! -f "${SA_PUB}" ]]; then
          echo "[INFO] Generating service account keypair in ${PKI_DIR}"
          openssl genrsa -out "${SA_KEY}" 2048
          chmod 600 "${SA_KEY}"
          openssl rsa -in "${SA_KEY}" -pubout -out "${SA_PUB}"
          chmod 644 "${SA_PUB}"
        else
          echo "[INFO] Existing service account keypair found: ${SA_KEY}, ${SA_PUB}"
        fi

        # Backup manifest once per run
        cp "${APISERVER_MANIFEST}" "${APISERVER_MANIFEST}.bak.${BACKUP_SUFFIX}"

        # Ensure --service-account-key-file flag is present and points to the public key
        if grep -q -- "--service-account-key-file" "${APISERVER_MANIFEST}"; then
          echo "[INFO] Existing --service-account-key-file flag found, updating path to ${SA_PUB}"
          # Replace existing value (handles both --flag=val and '--flag val' styles in args list)
          # For YAML args list with '    - --service-account-key-file=...'
          sed -i \
            "s#^\(\s*-\s*--service-account-key-file\)=\?.*#\1=${SA_PUB}#g" \
            "${APISERVER_MANIFEST}"
        else
          echo "[INFO] Adding --service-account-key-file flag with value ${SA_PUB}"
          # Insert the flag under the 'command:' or 'args:' section.
          # This assumes a standard kubeadm-style static pod manifest where args are a list.
          # We append the new arg line after the first occurrence of 'kube-apiserver' container args.
          if grep -q "^- kube-apiserver" "${APISERVER_MANIFEST}"; then
            # kubeadm-style: command as list, args as list under container
            awk -v sa_pub="${SA_PUB}" '
              /- kube-apiserver/ { in_container=1 }
              in_container && /args:/ && !added {
                print $0
                getline
                # print the next line (first arg) and then our new arg
                print $0
                sub(/^[[:space:]]*-/,"& --service-account-key-file=" sa_pub) # no-op placeholder
                added=1
                next
              }
              { print $0 }
            ' "${APISERVER_MANIFEST}" > "${APISERVER_MANIFEST}.tmp" || true

            if ! grep -q "--service-account-key-file" "${APISERVER_MANIFEST}.tmp" 2>/dev/null; then
              # Fallback: simple append under 'args:' if awk method failed/mismatched
              sed -i "/args:/a\    - --service-account-key-file=${SA_PUB}" "${APISERVER_MANIFEST}"
            else
              mv "${APISERVER_MANIFEST}.tmp" "${APISERVER_MANIFEST}"
            fi
          else
            # Generic fallback: append under first 'args:' occurrence
            sed -i "/args:/a\    - --service-account-key-file=${SA_PUB}" "${APISERVER_MANIFEST}"
          fi
        fi

        echo "[INFO] Updated ${APISERVER_MANIFEST}. kubelet will restart the kube-apiserver static pod."

        # Wait for kube-apiserver process to restart and pick up new flag
        echo "[INFO] Waiting up to 60 seconds for kube-apiserver to reflect new flag..."
        end=$((SECONDS+60))
        while (( SECONDS < end )); do
          if /bin/ps -ef | grep kube-apiserver | grep -v grep | grep -q -- "--service-account-key-file=${SA_PUB}"; then
            break
          fi
          sleep 3
        done

        # Verification
        echo "[INFO] Verifying remediation with audit command..."
        if /bin/ps -ef | grep kube-apiserver | grep -v grep | grep -q -- "--service-account-key-file=${SA_PUB}"; then
          echo "[SUCCESS] kube-apiserver is running with --service-account-key-file=${SA_PUB}"
          exit 0
        else
          echo "[WARNING] kube-apiserver process does not yet show --service-account-key-file=${SA_PUB}" >&2
          echo "[WARNING] Check kubelet and static pod status; verify ${APISERVER_MANIFEST} contents." >&2
          /bin/ps -ef | grep kube-apiserver | grep -v grep || true
          exit 1
        fi
        ```
      </Accordion>
    </AccordionGroup>
  </Tab>
</Tabs>
