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

# Ensure Service Account key File Argument Is Appropriate

### More Info:

Explicitly set a service account public key file for service accounts on the apiserver

### 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 current API server manifest:
           ```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. Ensure a service account public key file exists on the node (example path used below); if you already have a specific key file, skip this generation and use that path instead:
           ```bash theme={null}
           sudo mkdir -p /etc/kubernetes/pki
           cd /etc/kubernetes/pki
           sudo openssl genrsa -out sa.key 2048
           sudo openssl rsa -in sa.key -pubout -out sa.pub
           ```

        3. Edit the API server pod specification file on the control plane node:
           ```bash theme={null}
           sudo vi /etc/kubernetes/manifests/kube-apiserver.yaml
           ```
           In the `spec.containers[0].command` list, add (or update) the argument so there is a line like:
           ```yaml theme={null}
             - --service-account-key-file=/etc/kubernetes/pki/sa.pub
           ```
           Ensure the path matches the actual public key file you intend to use.

        4. If the public key file is not already available inside the kube-apiserver container, add/ensure a hostPath volume and volumeMount in the same manifest so the file is accessible:
           ```yaml theme={null}
           spec:
             containers:
             - name: kube-apiserver
               volumeMounts:
               - mountPath: /etc/kubernetes/pki
                 name: sa-key
                 readOnly: true
             volumes:
             - name: sa-key
               hostPath:
                 path: /etc/kubernetes/pki
                 type: DirectoryOrCreate
           ```
           Save and exit the editor. Editing this static pod manifest will cause the kubelet to automatically restart the kube-apiserver pod.

        5. Wait for the kube-apiserver pod to restart and become Running:
           ```bash theme={null}
           sudo crictl ps | grep kube-apiserver
           ```
           (If `crictl` is not available, use the node’s container runtime CLI to confirm the kube-apiserver container is running.)

        6. Verify that the kube-apiserver process is now using the `--service-account-key-file` argument with the correct path:
           ```bash theme={null}
           /bin/ps -ef | grep kube-apiserver | grep -v grep | grep -- '--service-account-key-file='
           ```
      </Accordion>

      <Accordion title="Using kubectl">
        kubectl cannot be used to configure the API server’s `--service-account-key-file` flag because it is set in the static pod manifest on each control plane node. To remediate this finding, 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
        #
        # automation_fix_service_account_key_file.sh
        #
        # Purpose:
        #   Ensure kube-apiserver manifest has an explicit --service-account-key-file
        #   argument pointing at a public key file for service accounts.
        #
        # Usage:
        #   Run on every control plane node as root:
        #     sudo bash automation_fix_service_account_key_file.sh
        #
        # Notes:
        #   - Editing /etc/kubernetes/manifests/kube-apiserver.yaml will trigger a
        #     kube-apiserver restart via kubelet (static pod).
        #   - Script is idempotent and safe to re-run.

        set -euo pipefail

        APISERVER_MANIFEST="/etc/kubernetes/manifests/kube-apiserver.yaml"
        # Path to the PUBLIC key file to be used by kube-apiserver
        SERVICE_ACCOUNT_KEY_FILE="/etc/kubernetes/pki/sa.pub"

        echo "[INFO] Running on host: $(hostname)"

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

        if [[ ! -f "$APISERVER_MANIFEST" ]]; then
          echo "[ERROR] Manifest not found: $APISERVER_MANIFEST" >&2
          exit 1
        fi

        # Ensure public key exists. If not, try to derive it from private key if present.
        if [[ ! -f "$SERVICE_ACCOUNT_KEY_FILE" ]]; then
          SA_KEY_PRIV="/etc/kubernetes/pki/sa.key"
          if [[ -f "$SA_KEY_PRIV" ]]; then
            echo "[INFO] Public key $SERVICE_ACCOUNT_KEY_FILE not found, generating from $SA_KEY_PRIV"
            # Requires openssl; generate RSA public key in PEM format
            openssl rsa -in "$SA_KEY_PRIV" -pubout -out "$SERVICE_ACCOUNT_KEY_FILE"
            chmod 644 "$SERVICE_ACCOUNT_KEY_FILE"
          else
            echo "[ERROR] Neither public key $SERVICE_ACCOUNT_KEY_FILE nor private key $SA_KEY_PRIV found." >&2
            echo "[ERROR] Create or provision an appropriate service account public key file first." >&2
            exit 1
          fi
        else
          echo "[INFO] Found existing service account public key: $SERVICE_ACCOUNT_KEY_FILE"
        fi

        BACKUP="${APISERVER_MANIFEST}.$(date +%Y%m%d%H%M%S).bak"
        cp "$APISERVER_MANIFEST" "$BACKUP"
        echo "[INFO] Backup created at $BACKUP"

        # Normalize YAML to ensure there's a 'command:' section to work with.
        # Most kubeadm-based clusters already have it; we only handle that layout here.
        if ! grep -qE '^\s*-\s*--service-account-key-file=' "$APISERVER_MANIFEST"; then
          echo "[INFO] --service-account-key-file not present, adding it."
          # Insert new flag under the kube-apiserver command list.
          # We append it right after the last existing -- flag line, or after 'kube-apiserver' line.
          if grep -qE '^\s*-\s*--' "$APISERVER_MANIFEST"; then
            # Append after last existing --* flag
            awk -v flag="--service-account-key-file=${SERVICE_ACCOUNT_KEY_FILE}" '
              /^\s*-\s*--/ { last_flag_line=NR }
              { lines[NR]=$0 }
              END {
                for (i=1; i<=NR; i++) {
                  print lines[i]
                  if (i==last_flag_line) {
                    # Maintain same indentation as existing flags
                    sub(/^([[:space:]]*).*/, "&", lines[last_flag_line])
                    match(lines[last_flag_line], /^([[:space:]]*).*/, m)
                    indent=m[1]
                    print indent "- " flag
                  }
                }
              }
            ' "$APISERVER_MANIFEST" > "${APISERVER_MANIFEST}.tmp"
          else
            # Fallback: just append a new command entry with the flag near the top
            awk -v flag="--service-account-key-file=${SERVICE_ACCOUNT_KEY_FILE}" '
              !inserted && /kube-apiserver/ {
                print
                print "    - " flag
                inserted=1
                next
              }
              { print }
            ' "$APISERVER_MANIFEST" > "${APISERVER_MANIFEST}.tmp"
          fi
          mv "${APISERVER_MANIFEST}.tmp" "$APISERVER_MANIFEST"
        else
          echo "[INFO] --service-account-key-file already present, ensuring it points to $SERVICE_ACCOUNT_KEY_FILE"
          # Replace existing value with desired path (idempotent)
          sed -i "s#^\(\s*-\s*--service-account-key-file=\).*#\1${SERVICE_ACCOUNT_KEY_FILE}#g" "$APISERVER_MANIFEST"
        fi

        echo "[INFO] kube-apiserver manifest updated. kubelet will restart the static pod automatically."

        # Wait for kube-apiserver process to reflect new flag
        echo "[INFO] Waiting for kube-apiserver to restart with new arguments..."
        RETRIES=30
        SLEEP_SEC=10
        success=0

        for i in $(seq 1 $RETRIES); do
          if /bin/ps -ef | grep kube-apiserver | grep -v grep | grep -q -- "--service-account-key-file=${SERVICE_ACCOUNT_KEY_FILE}"; then
            success=1
            break
          fi
          echo "[INFO] Attempt $i/$RETRIES: kube-apiserver not yet running with desired flag, sleeping ${SLEEP_SEC}s..."
          sleep "$SLEEP_SEC"
        done

        echo
        echo "================ Verification ================"
        /bin/ps -ef | grep kube-apiserver | grep -v grep || true

        if [[ $success -eq 1 ]]; then
          echo "[SUCCESS] kube-apiserver is running with --service-account-key-file=${SERVICE_ACCOUNT_KEY_FILE}"
          exit 0
        else
          echo "[WARNING] kube-apiserver process does not yet show --service-account-key-file=${SERVICE_ACCOUNT_KEY_FILE}."
          echo "[WARNING] Check kubelet and kube-apiserver pod status manually."
          exit 1
        fi
        ```
      </Accordion>
    </AccordionGroup>
  </Tab>
</Tabs>

### Additional Reading:

* [https://kubernetes.io/docs/admin/kube-apiserver/](https://kubernetes.io/docs/admin/kube-apiserver/)
