Skip to main content

API Server Should Set Client CA File

More Info:

Verifies that --client-ca-file is set so the API server can authenticate clients using certificates signed by the configured certificate authority.

Risk Level

High

Address

Security

Compliance Standards

  • CIS Kubernetes

Triage and Remediation

Remediation

Manual Steps
  1. Prepare or identify the client CA file (every control plane node)

    • Ensure you have a PEM‑encoded CA certificate that signs client certs for kube-apiserver auth, e.g.:
      /etc/kubernetes/pki/client-ca.crt
    • If it does not exist yet, copy or create the appropriate CA file on each control plane node at that path with root ownership and restricted permissions:
      sudo install -o root -g root -m 600 client-ca.crt /etc/kubernetes/pki/client-ca.crt
  2. Edit the kube-apiserver static pod manifest (every control plane node)

    • Open the manifest in an editor:
      sudo vi /etc/kubernetes/manifests/kube-apiserver.yaml
    • In the spec.containers[0].command list, add or update the argument so it includes:
      - --client-ca-file=/etc/kubernetes/pki/client-ca.crt
    • Save and exit.
      Operational impact: because this is a static pod manifest, the kubelet will automatically restart the API server container when the file is saved.
  3. Ensure the CA file is mounted into the pod (if needed) (every control plane node)

    • In the same file /etc/kubernetes/manifests/kube-apiserver.yaml, confirm there is a volumeMounts entry for the directory containing the CA file, for example:
      volumeMounts:
      - mountPath: /etc/kubernetes/pki
      name: k8s-certs
      readOnly: true
    • And a corresponding volumes entry:
      volumes:
      - name: k8s-certs
      hostPath:
      path: /etc/kubernetes/pki
      type: DirectoryOrCreate
    • Adjust the mountPath, name, and path only if your existing manifest uses different values.
  4. Wait for kube-apiserver to restart and stabilize (every control plane node)

    • Monitor the pod restart (from any machine with kubectl access):
      kubectl -n kube-system get pods -l component=kube-apiserver -w
    • Wait until the kube-apiserver pod is in Running and Ready state.
  5. Verify the kube-apiserver process has the client-ca-file flag set (every control plane node)

    • Run:
      /bin/ps -ef | grep kube-apiserver | grep -v grep
    • Confirm the output includes the argument with the correct path, for example:
      --client-ca-file=/etc/kubernetes/pki/client-ca.crt
Using kubectl

kubectl cannot modify the API server’s host-level configuration or its static pod manifest. To set --client-ca-file, you must edit /etc/kubernetes/manifests/kube-apiserver.yaml directly on every control plane node; see the Manual Steps section for the exact procedure.

Automation
#!/usr/bin/env bash
#
# Automation: Ensure kube-apiserver is started with --client-ca-file on all control plane nodes.
#
# Usage:
# 1) Create /etc/kubernetes/pki/client-ca.crt on each control plane node with the desired CA.
# 2) From an admin machine with SSH access, run:
# ./fix-apiserver-client-ca.sh "node1 node2 node3"
# 3) Script is idempotent and safe to re-run.
#

set -euo pipefail

CONTROL_PLANE_NODES="${1:-}"
CLIENT_CA_FILE="/etc/kubernetes/pki/client-ca.crt"
APISERVER_MANIFEST="/etc/kubernetes/manifests/kube-apiserver.yaml"
SSH_USER="root" # change if your SSH user is different

if [[ -z "${CONTROL_PLANE_NODES}" ]]; then
echo "ERROR: Provide a space-separated list of control plane nodes as the first argument."
echo "Example: $0 \"cp-1 cp-2 cp-3\""
exit 1
fi

for NODE in ${CONTROL_PLANE_NODES}; do
echo "=== Processing control plane node: ${NODE} ==="

ssh -o StrictHostKeyChecking=no -o BatchMode=yes "${SSH_USER}@${NODE}" bash -s <<'EOF'
set -euo pipefail

CLIENT_CA_FILE="/etc/kubernetes/pki/client-ca.crt"
APISERVER_MANIFEST="/etc/kubernetes/manifests/kube-apiserver.yaml"
BACKUP_SUFFIX="$(date +%Y%m%d%H%M%S)"

echo "[${HOSTNAME}] Verifying prerequisites..."

if [[ ! -f "${CLIENT_CA_FILE}" ]]; then
echo "[${HOSTNAME}] ERROR: Client CA file ${CLIENT_CA_FILE} does not exist."
echo "[${HOSTNAME}] Create this file (per your PKI policy) before running this script."
exit 1
fi

if [[ ! -f "${APISERVER_MANIFEST}" ]]; then
echo "[${HOSTNAME}] ERROR: API server manifest ${APISERVER_MANIFEST} not found."
exit 1
fi

echo "[${HOSTNAME}] Checking if --client-ca-file is already set correctly..."

if grep -q -- "--client-ca-file=${CLIENT_CA_FILE}" "${APISERVER_MANIFEST}"; then
echo "[${HOSTNAME}] Correct --client-ca-file already present. No change needed."
else
if grep -q -- "--client-ca-file=" "${APISERVER_MANIFEST}"; then
echo "[${HOSTNAME}] Updating existing --client-ca-file argument..."
else
echo "[${HOSTNAME}] Adding new --client-ca-file argument..."
fi

cp "${APISERVER_MANIFEST}" "${APISERVER_MANIFEST}.bak-${BACKUP_SUFFIX}"

# Replace existing --client-ca-file or append a new one under the command section.
if grep -q -- "--client-ca-file=" "${APISERVER_MANIFEST}"; then
# Replace only the value, keep YAML structure intact.
sed -i "s#--client-ca-file=.*#--client-ca-file=${CLIENT_CA_FILE}#g" "${APISERVER_MANIFEST}"
else
# Append under the 'command:' list for kube-apiserver container.
# This assumes a standard kubeadm-generated manifest layout.
awk -v ca_arg=" - --client-ca-file=${CLIENT_CA_FILE}" '
/- kube-apiserver$/ && in_container==0 { in_container=1 }
in_container==1 && /command:/ && cmd_seen==0 { cmd_seen=1 }
in_container==1 && cmd_seen==1 && /\- --/ && !inserted {
print $0
print ca_arg
inserted=1
next
}
{ print $0 }
END {
if (inserted!=1) {
# Fallback: if we didn’t find a good place, just append at end of file
print ca_arg
}
}
' "${APISERVER_MANIFEST}" > "${APISERVER_MANIFEST}.tmp"

mv "${APISERVER_MANIFEST}.tmp" "${APISERVER_MANIFEST}"
fi

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

echo "[${HOSTNAME}] Waiting for kube-apiserver process to reflect the new flag..."

# Wait up to 120s for the process to restart with the desired flag
timeout=120
interval=5
elapsed=0
while true; do
if /bin/ps -ef | grep kube-apiserver | grep -v grep | grep -q -- "--client-ca-file=${CLIENT_CA_FILE}"; then
echo "[${HOSTNAME}] Verification SUCCESS: kube-apiserver running with --client-ca-file=${CLIENT_CA_FILE}."
break
fi
if (( elapsed >= timeout )); then
echo "[${HOSTNAME}] WARNING: kube-apiserver process did not show --client-ca-file=${CLIENT_CA_FILE} within ${timeout}s."
echo "[${HOSTNAME}] Current kube-apiserver processes:"
/bin/ps -ef | grep kube-apiserver | grep -v grep || true
exit 1
fi
sleep "${interval}"
elapsed=$((elapsed + interval))
done

EOF

echo "=== Completed node: ${NODE} ==="
echo
done

echo "All specified control plane nodes processed."