Skip to main content

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​

Manual Steps
  1. On every control plane node, open the kube-controller-manager static pod manifest for editing:

    sudo vi /etc/kubernetes/manifests/kube-controller-manager.yaml
  2. In the spec.containers[0].command list, ensure there is a --feature-gates= entry that includes RotateKubeletServerCertificate=true.

    • If --feature-gates does not exist, add a new line under the other -- flags, for example:
      - --feature-gates=RotateKubeletServerCertificate=true
    • If --feature-gates already exists, append RotateKubeletServerCertificate=true to the comma-separated list, for example:
      - --feature-gates=SomeOtherFeature=true,RotateKubeletServerCertificate=true
  3. 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.

  4. 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 -w

    Press Ctrl+C once the relevant pod is in Running state and no longer restarting.

  5. 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 grep

    Confirm the output contains a --feature-gates= argument with RotateKubeletServerCertificate=true present 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

Additional Reading:​