Ensure Rotate Kubelet Server Certificate Argument Is Enabled
More Info:
Enable kubelet server certificate rotation on controller-manager.
Risk Level
Low
Address
Security
Compliance Standards
- CIS Kubernetes
Triage and Remediation
- Remediation
Remediation
Manual Steps
-
On every control plane node, open the kube-controller-manager static pod manifest for editing:
sudo vi /etc/kubernetes/manifests/kube-controller-manager.yaml -
In the
spec.containers[0].commandlist, ensure there is a--feature-gates=entry that includesRotateKubeletServerCertificate=true.- If
--feature-gatesdoes not exist, add a new line under the other--flags, for example:- --feature-gates=RotateKubeletServerCertificate=true - If
--feature-gatesalready exists, appendRotateKubeletServerCertificate=trueto the comma-separated list, for example:- --feature-gates=SomeOtherFeature=true,RotateKubeletServerCertificate=true
- If
-
Save the file and exit the editor. The kube-controller-manager static pod will be automatically restarted by the kubelet on that control plane node due to the manifest change.
-
Wait for the kube-controller-manager pod to restart and become Running (from any machine with kubectl access):
kubectl -n kube-system get pods -l component=kube-controller-manager -wPress
Ctrl+Conce the relevant pod is inRunningstate and no longer restarting. -
On each control plane node, verify the kube-controller-manager process includes the correct feature gate:
/bin/ps -ef | grep kube-controller-manager | grep -v grepConfirm the output contains a
--feature-gates=argument withRotateKubeletServerCertificate=truepresent in its value.
Using kubectl
kubectl cannot modify the kube-controller-manager static pod manifest or its process flags on the control plane node. To enable RotateKubeletServerCertificate, you must edit /etc/kubernetes/manifests/kube-controller-manager.yaml directly on every control plane node; follow the guidance in the Manual Steps section.
Automation
#!/usr/bin/env bash
#
# Automation: Enable RotateKubeletServerCertificate feature-gate on kube-controller-manager
# Scope: Run on every control plane node with direct access to /etc/kubernetes/manifests
# Usage: sudo ./enable-rotate-kubelet-server-cert.sh
set -euo pipefail
MANIFEST="/etc/kubernetes/manifests/kube-controller-manager.yaml"
BACKUP_DIR="/etc/kubernetes/manifests/backup-rotate-kubelet-server-cert"
TIMESTAMP="$(date +%Y%m%d%H%M%S)"
if [[ $EUID -ne 0 ]]; then
echo "ERROR: This script must be run as root (or with sudo)." >&2
exit 1
fi
if [[ ! -f "$MANIFEST" ]]; then
echo "ERROR: Manifest not found at $MANIFEST. Are you on a control plane node?" >&2
exit 1
fi
mkdir -p "$BACKUP_DIR"
backup_file="$BACKUP_DIR/kube-controller-manager.yaml.$TIMESTAMP"
cp -p "$MANIFEST" "$backup_file"
echo "Backup created at: $backup_file"
# Normalize feature-gates lines and ensure RotateKubeletServerCertificate=true is present
tmpfile="$(mktemp)"
trap 'rm -f "$tmpfile"' EXIT
# Idempotent edit:
# - If a --feature-gates arg exists:
# * If it already includes RotateKubeletServerCertificate=true: leave as-is
# * If it includes RotateKubeletServerCertificate=false: change to true
# * Else append ,RotateKubeletServerCertificate=true
# - If no --feature-gates exists: add a new - --feature-gates=RotateKubeletServerCertificate=true
python3 - "$MANIFEST" > "$tmpfile" << 'PYEOF'
import sys, re
path = sys.argv[1]
with open(path, "r", encoding="utf-8") as f:
lines = f.readlines()
out = []
feature_gates_key = "- --feature-gates="
found_fg = False
for i, line in enumerate(lines):
stripped = line.lstrip()
if stripped.startswith(feature_gates_key):
found_fg = True
indent = line[:len(line) - len(stripped)]
value = stripped[len(feature_gates_key):].rstrip("\n")
# Ensure RotateKubeletServerCertificate is set to true
# Remove any existing RotateKubeletServerCertificate=<...> and append =true
parts = [p for p in value.split(",") if not p.startswith("RotateKubeletServerCertificate=")]
parts.append("RotateKubeletServerCertificate=true")
# Deduplicate while preserving order
seen = set()
new_parts = []
for p in parts:
if p not in seen:
seen.add(p)
new_parts.append(p)
new_value = ",".join(new_parts)
out.append(f"{indent}{feature_gates_key}{new_value}\n")
else:
out.append(line)
if not found_fg:
# Insert new feature-gates arg under the kube-controller-manager container args
inserted = False
for i, line in enumerate(out):
if re.match(r'^\s*name:\s*kube-controller-manager\s*$', line):
# Look for the args: block following this
j = i + 1
while j < len(out) and not re.match(r'^\s*args:\s*$', out[j]):
j += 1
if j < len(out):
indent = re.match(r'^(\s*)args:\s*$', out[j]).group(1) + " "
out.insert(j + 1, f"{indent}- --feature-gates=RotateKubeletServerCertificate=true\n")
inserted = True
break
if not inserted:
# Fallback: append at end, minimally valid for static pod
out.append(" - --feature-gates=RotateKubeletServerCertificate=true\n")
sys.stdout.writelines(out)
PYEOF
# Only overwrite if there is a change
if cmp -s "$MANIFEST" "$tmpfile"; then
echo "No changes needed; RotateKubeletServerCertificate=true already configured."
else
mv "$tmpfile" "$MANIFEST"
sync
echo "Updated $MANIFEST with RotateKubeletServerCertificate=true in --feature-gates."
fi
# Note: kubelet will automatically restart the static pod after manifest change.
echo "Waiting 30 seconds for kube-controller-manager to restart..."
sleep 30
echo "Verification: checking running kube-controller-manager arguments..."
if /bin/ps -ef | grep kube-controller-manager | grep -v grep | grep -q -- '--feature-gates=.*RotateKubeletServerCertificate=true'; then
echo "SUCCESS: kube-controller-manager is running with RotateKubeletServerCertificate=true enabled."
exit 0
else
echo "WARNING: kube-controller-manager process not yet showing RotateKubeletServerCertificate=true."
echo "Current kube-controller-manager processes:"
/bin/ps -ef | grep kube-controller-manager | grep -v grep || true
exit 1
fi