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
Remediation
Manual Steps
-
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
- Ensure you have a PEM‑encoded CA certificate that signs client certs for kube-apiserver auth, e.g.:
-
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].commandlist, 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.
- Open the manifest in an editor:
-
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 avolumeMountsentry for the directory containing the CA file, for example:volumeMounts:- mountPath: /etc/kubernetes/pkiname: k8s-certsreadOnly: true - And a corresponding
volumesentry:volumes:- name: k8s-certshostPath:path: /etc/kubernetes/pkitype: DirectoryOrCreate - Adjust the
mountPath,name, andpathonly if your existing manifest uses different values.
- In the same file
-
Wait for kube-apiserver to restart and stabilize (every control plane node)
- Monitor the pod restart (from any machine with
kubectlaccess):kubectl -n kube-system get pods -l component=kube-apiserver -w - Wait until the kube-apiserver pod is in
RunningandReadystate.
- Monitor the pod restart (from any machine with
-
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
- Run:
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."