API Server Should Set Kubelet Certificate Authority
More Info:
Verifies that --kubelet-certificate-authority is set so the API server verifies kubelet certificates, preventing man-in-the-middle attacks against the kubelet connection.
Risk Level
High
Address
Security
Compliance Standards
- CIS Kubernetes
Triage and Remediation
- Remediation
Remediation
Manual Steps
- On every control plane node, confirm the current API server flags and locate the manifest:
ps -ef | grep kube-apiserver | grep -v grep
ls -l /etc/kubernetes/manifests/kube-apiserver.yaml
- Ensure you have or generate the CA certificate that issued the kubelet client certificates (example path used below). Copy it to the control plane node if needed and place it somewhere readable by the kube-apiserver, for example:
sudo mkdir -p /etc/kubernetes/pki
sudo cp /path/to/your/kubelet-ca.crt /etc/kubernetes/pki/kubelet-ca.crt
sudo chmod 600 /etc/kubernetes/pki/kubelet-ca.crt
sudo chown root:root /etc/kubernetes/pki/kubelet-ca.crt
- On every control plane node, edit the API server static pod manifest:
sudo vi /etc/kubernetes/manifests/kube-apiserver.yaml
Under the command: section (or - -- arguments list), add or modify the flag so it reads:
- --kubelet-certificate-authority=/etc/kubernetes/pki/kubelet-ca.crt
Ensure there is only one --kubelet-certificate-authority entry.
- Save the file and exit. The kubelet will automatically restart the
kube-apiserverstatic pod when the manifest changes. Monitor the restart and confirm the pod is running:
sudo crictl ps | grep kube-apiserver || sudo docker ps | grep kube-apiserver
- Check the API server logs for successful startup and no certificate validation errors toward kubelets:
sudo journalctl -u kubelet -f
# in another terminal
sudo crictl logs $(sudo crictl ps | awk '/kube-apiserver/ {print $1}') 2>&1 | grep -i kubelet
- Verify that the
--kubelet-certificate-authorityflag is now set correctly on every control plane node:
ps -ef | grep kube-apiserver | grep -v grep | grep -- '--kubelet-certificate-authority='
Using kubectl
kubectl cannot modify the kube-apiserver static pod manifest or its process flags, so it cannot be used to set --kubelet-certificate-authority. This must be fixed directly on every control plane node by editing /etc/kubernetes/manifests/kube-apiserver.yaml; see the Manual Steps section for the required procedure.
Automation
#!/usr/bin/env bash
#
# Automation: Ensure kube-apiserver sets --kubelet-certificate-authority
# Scope: run on every control plane node
# Prereqs:
# - kube-apiserver is a static pod from /etc/kubernetes/manifests/kube-apiserver.yaml
# - A valid CA file exists at /etc/kubernetes/pki/kubelet-ca.crt
#
# Idempotent: safe to re-run.
set -euo pipefail
APISERVER_MANIFEST="/etc/kubernetes/manifests/kube-apiserver.yaml"
KUBELET_CA="/etc/kubernetes/pki/kubelet-ca.crt"
BACKUP_DIR="/etc/kubernetes/manifests/backup-$(date +%Y%m%d)"
if [[ $EUID -ne 0 ]]; then
echo "ERROR: Run this script as root on each control plane node." >&2
exit 1
fi
if [[ ! -f "$APISERVER_MANIFEST" ]]; then
echo "ERROR: $APISERVER_MANIFEST not found on this node; nothing to do." >&2
exit 1
fi
if [[ ! -f "$KUBELET_CA" ]]; then
echo "ERROR: Expected CA file $KUBELET_CA not found. Create or place the kubelet CA there, then re-run." >&2
exit 1
fi
mkdir -p "$BACKUP_DIR"
cp -n "$APISERVER_MANIFEST" "$BACKUP_DIR/kube-apiserver.yaml.$(date +%H%M%S)"
# Detect if the flag is already set correctly
if grep -q -- "--kubelet-certificate-authority=${KUBELET_CA}" "$APISERVER_MANIFEST"; then
echo "INFO: --kubelet-certificate-authority already set to ${KUBELET_CA} in manifest. Skipping edit."
else
echo "INFO: Updating $APISERVER_MANIFEST with --kubelet-certificate-authority=${KUBELET_CA}"
# Remove any existing --kubelet-certificate-authority flags (with any value)
sed -i '/--kubelet-certificate-authority=/d' "$APISERVER_MANIFEST"
# Insert the flag under the existing --kubelet-client-certificate arg if present,
# otherwise append it at the end of the arguments list.
if grep -q -- "--kubelet-client-certificate" "$APISERVER_MANIFEST"; then
# Insert on the next line after kubelet-client-certificate
awk -v flag=" - --kubelet-certificate-authority=${KUBELET_CA}" '
/--kubelet-client-certificate/ && !inserted {
print $0
print flag
inserted=1
next
}
{ print $0 }
' "$APISERVER_MANIFEST" > "${APISERVER_MANIFEST}.tmp"
mv "${APISERVER_MANIFEST}.tmp" "$APISERVER_MANIFEST"
else
# Append near the end of the args list
awk -v flag=" - --kubelet-certificate-authority=${KUBELET_CA}" '
/- --/ && inargs==0 { inargs=1 } # first arg line
inargs && $0 ~ /^ *-/ { lastarg=NR } # track last arg line
{ lines[NR]=$0 }
END {
for (i=1; i<=NR; i++) {
print lines[i]
if (i==lastarg) print flag
}
}
' "$APISERVER_MANIFEST" > "${APISERVER_MANIFEST}.tmp"
mv "${APISERVER_MANIFEST}.tmp" "$APISERVER_MANIFEST"
fi
echo "INFO: Manifest updated. kube-apiserver static pod will be restarted by kubelet."
fi
echo "INFO: Waiting for kube-apiserver process to reflect new flag..."
sleep 20
# Verification: match the audit command and confirm the flag is present
if /bin/ps -ef | grep kube-apiserver | grep -v grep | grep -q -- "--kubelet-certificate-authority=${KUBELET_CA}"; then
echo "SUCCESS: kube-apiserver is running with --kubelet-certificate-authority=${KUBELET_CA}"
exit 0
else
echo "ERROR: kube-apiserver process does not show --kubelet-certificate-authority=${KUBELET_CA} yet." >&2
echo "Current kube-apiserver command line:"
/bin/ps -ef | grep kube-apiserver | grep -v grep || true
exit 1
fi