Skip to main content

Verify RotateKubeletServerCertificate Argument Is Enabled

More Info:

Enable kubelet server certificate rotation.

Risk Level

Low

Address

Security

Compliance Standards

  • CIS Kubernetes

Triage and Remediation

Remediation

Manual Steps
  1. On every worker node, open the kubelet systemd drop-in file for editing:

    sudo vi /etc/systemd/system/kubelet.service.d/10-kubeadm.conf
  2. In that file, locate the line that defines KUBELET_CERTIFICATE_ARGS (create it if it does not exist), and set/merge the feature gate so it contains:

    Environment="KUBELET_CERTIFICATE_ARGS=--feature-gates=RotateKubeletServerCertificate=true"

    If other feature gates are already present, include this one in the same list, for example:

    Environment="KUBELET_CERTIFICATE_ARGS=--feature-gates=ExistingFeature=true,RotateKubeletServerCertificate=true"
  3. Ensure the kubelet command line in the same file actually uses KUBELET_CERTIFICATE_ARGS (add it if missing). The ExecStart= line should include:

    ExecStart=... $KUBELET_CERTIFICATE_ARGS ...
  4. Reload systemd and restart kubelet on the same worker node to apply the change (this will restart the kubelet process):

    sudo systemctl daemon-reload
    sudo systemctl restart kubelet.service
  5. Repeat steps 1–4 on every worker node in the cluster.

  6. Verification (on each worker node): confirm the kubelet process was started with the correct feature gate:

    /bin/ps -fC kubelet

    Check that the output command line includes:
    --feature-gates=RotateKubeletServerCertificate=true (or within a comma-separated --feature-gates= list).

Using kubectl

kubectl cannot configure kubelet process flags or edit host files like /etc/systemd/system/kubelet.service.d/10-kubeadm.conf or /var/lib/kubelet/config.yaml on worker nodes. To enable RotateKubeletServerCertificate, you must modify the kubelet systemd unit and configuration directly on every worker node; see the Manual Steps section for the exact host-level commands.

Automation
#!/usr/bin/env bash
#
# Enable kubelet server certificate rotation on all worker nodes.
# Run on each worker node via SSH (can be orchestrated by Ansible).
# Idempotent and safe to re-run.

set -euo pipefail

KUBELET_DROPIN="/etc/systemd/system/kubelet.service.d/10-kubeadm.conf"
BACKUP_SUFFIX="$(date +%Y%m%d%H%M%S)"

echo "==> Checking for kubelet systemd drop-in: ${KUBELET_DROPIN}"
if [[ ! -f "${KUBELET_DROPIN}" ]]; then
echo "ERROR: ${KUBELET_DROPIN} not found. This script assumes kubeadm-style systemd config."
echo " Adjust manually for your environment."
exit 1
fi

echo "==> Backing up existing file (once per unique content)"
CHECKSUM_FILE="${KUBELET_DROPIN}.sha256"
CURRENT_SUM="$(sha256sum "${KUBELET_DROPIN}" | awk '{print $1}')"
if [[ ! -f "${CHECKSUM_FILE}" ]] || [[ "$(cat "${CHECKSUM_FILE}")" != "${CURRENT_SUM}" ]]; then
cp -p "${KUBELET_DROPIN}" "${KUBELET_DROPIN}.bak-${BACKUP_SUFFIX}"
echo "${CURRENT_SUM}" > "${CHECKSUM_FILE}"
echo " Backup created at ${KUBELET_DROPIN}.bak-${BACKUP_SUFFIX}"
else
echo " No changes since last backup; skipping new backup."
fi

echo "==> Ensuring KUBELET_CERTIFICATE_ARGS contains RotateKubeletServerCertificate=true"

# Ensure the variable exists
if ! grep -q '^Environment="KUBELET_CERTIFICATE_ARGS=' "${KUBELET_DROPIN}"; then
echo 'Environment="KUBELET_CERTIFICATE_ARGS=--feature-gates=RotateKubeletServerCertificate=true"' \
>> "${KUBELET_DROPIN}"
echo " Added new KUBELET_CERTIFICATE_ARGS line."
else
# Update existing line, merging other feature-gates if present
tmpfile="$(mktemp)"
while IFS='' read -r line; do
if [[ "${line}" =~ ^Environment=\"KUBELET_CERTIFICATE_ARGS= ]]; then
# Strip prefix/suffix
payload="${line#Environment=\"KUBELET_CERTIFICATE_ARGS=}"
payload="${payload%\"}"

# If RotateKubeletServerCertificate already enabled, leave as-is
if [[ "${payload}" == *"RotateKubeletServerCertificate=true"* ]]; then
echo "${line}" >> "${tmpfile}"
continue
fi

# Remove any existing RotateKubeletServerCertificate flag (true/false) to avoid duplicates
payload="$(echo "${payload}" | sed -E 's/--feature-gates=([^"]*)RotateKubeletServerCertificate=(true|false),?([^"]*)/--feature-gates=\1\3/g' )"
# Clean potential trailing commas in feature-gates
payload="$(echo "${payload}" | sed -E 's/--feature-gates=,*/--feature-gates=/')"
payload="$(echo "${payload}" | sed -E 's/,\"/\"/')"

# If there is an existing --feature-gates, append to it; otherwise add a new one
if [[ "${payload}" == *"--feature-gates="* ]]; then
payload="$(echo "${payload}" | sed -E 's/(--feature-gates=[^"]*)/\1,RotateKubeletServerCertificate=true/')"
else
if [[ -n "${payload}" ]]; then
payload="${payload} --feature-gates=RotateKubeletServerCertificate=true"
else
payload="--feature-gates=RotateKubeletServerCertificate=true"
fi
fi

echo "Environment=\"KUBELET_CERTIFICATE_ARGS=${payload}\"" >> "${tmpfile}"
else
echo "${line}" >> "${tmpfile}"
fi
done < "${KUBELET_DROPIN}"
mv "${tmpfile}" "${KUBELET_DROPIN}"
echo " Updated existing KUBELET_CERTIFICATE_ARGS line."
fi

echo "==> Reloading systemd and restarting kubelet (this restarts the kubelet process)"
systemctl daemon-reload
systemctl restart kubelet.service

echo "==> Verifying kubelet process flags"
/bin/ps -fC kubelet || {
echo "ERROR: kubelet process not found after restart."
exit 1
}

if /bin/ps -fC kubelet | grep -q 'RotateKubeletServerCertificate=true'; then
echo "SUCCESS: kubelet is running with RotateKubeletServerCertificate=true"
else
echo "WARNING: kubelet process does not show RotateKubeletServerCertificate=true in arguments."
echo " Verify that systemd drop-in is being used and that no other unit overrides it:"
echo " systemctl cat kubelet.service"
exit 1
fi

Additional Reading: