Skip to main content

Ensure Etcd Peer-Client-Cert-Auth Argument Is Set To True

More Info:

The --peer-client-cert-auth argument must be set to true so etcd requires valid certificates for peer connections. If disabled, an unauthorized node can join the cluster and access all etcd data.

Risk Level

Critical

Address

Security

Compliance Standards

  • CIS Kubernetes

Triage and Remediation

Remediation

Manual Steps
  1. On every etcd (control plane) node, back up the existing manifest:
sudo cp -a /etc/kubernetes/manifests/etcd.yaml /etc/kubernetes/manifests/etcd.yaml.bak
  1. Edit the etcd static pod manifest to set --peer-client-cert-auth=true:
sudo sed -i 's/--peer-client-cert-auth=false/--peer-client-cert-auth=true/' /etc/kubernetes/manifests/etcd.yaml

If the flag is missing, open the file in an editor and add it under the command: list, for example:

sudo vi /etc/kubernetes/manifests/etcd.yaml

Add (or ensure) a line like:

- --peer-client-cert-auth=true
  1. Save the file and exit the editor (if used). The kubelet will automatically restart the etcd static pod when /etc/kubernetes/manifests/etcd.yaml changes. Be aware this briefly restarts the etcd container on this node.

  2. Wait for the etcd pod to restart and become ready on this node:

sudo crictl ps | grep etcd

(or docker ps | grep etcd if using Docker as the container runtime).

  1. Verify on this node that etcd is now running with --peer-client-cert-auth=true:
/bin/ps -ef | /bin/grep etcd | /bin/grep -v grep

Confirm the etcd process includes --peer-client-cert-auth=true and does not include --peer-client-cert-auth=false. Repeat all steps on every etcd node.

Using kubectl

This configuration is not exposed through Kubernetes API objects, so kubectl cannot change it. The required fix must be made directly on every etcd node by editing /etc/kubernetes/manifests/etcd.yaml; see the Manual Steps section for the exact host-level remediation.

Automation
#!/usr/bin/env bash
#
# Ensure Etcd --peer-client-cert-auth argument is set to true
# Target: every etcd node (control plane nodes)
# This script is idempotent and safe to re-run.

set -euo pipefail

ETCD_MANIFEST="/etc/kubernetes/manifests/etcd.yaml"
BACKUP_DIR="/etc/kubernetes/manifests/backup_peer_client_cert_auth"
TIMESTAMP="$(date +%Y%m%d-%H%M%S)"

echo "[INFO] Starting etcd --peer-client-cert-auth remediation"

# 1) Pre-checks
if [[ $EUID -ne 0 ]]; then
echo "[ERROR] This script must be run as root on each etcd (control plane) node."
exit 1
fi

if [[ ! -f "$ETCD_MANIFEST" ]]; then
echo "[ERROR] Etcd manifest not found at $ETCD_MANIFEST"
exit 1
fi

# 2) Backup current manifest
mkdir -p "$BACKUP_DIR"
cp -p "$ETCD_MANIFEST" "$BACKUP_DIR/etcd.yaml.$TIMESTAMP"
echo "[INFO] Backup created at $BACKUP_DIR/etcd.yaml.$TIMESTAMP"

# 3) Ensure --peer-client-cert-auth=true is present under etcd container args
# Logic:
# - If any line contains --peer-client-cert-auth= and is already true: leave as is.
# - If any line contains --peer-client-cert-auth= and is not true: replace value with true.
# - If no such line exists: append a new arg line under the etcd container args.
#
# We modify only lines containing 'etcd' container args block or the specific flag.

TMP_FILE="$(mktemp)"

# First, normalize any existing --peer-client-cert-auth flag to true
# This sed changes e.g. --peer-client-cert-auth=false or any other value to true.
sed -E 's/(--peer-client-cert-auth=)[^[:space:]]*/\1true/g' "$ETCD_MANIFEST" > "$TMP_FILE"

# Next, check if the flag exists at all; if not, we need to insert it.
if ! grep -q -- "--peer-client-cert-auth=" "$TMP_FILE"; then
echo "[INFO] --peer-client-cert-auth flag not found; inserting into etcd container args"

# Insert the flag as a new arg line under the etcd container args section.
# This assumes a typical kubeadm-style manifest with:
# containers:
# - command:
# - etcd
# - ...
# - --some-flag=value
#
# We append our flag just after the last existing etcd command arg line.
# This is a conservative insertion method and preserves existing formatting.

awk '
/- name: etcd/ { in_etcd=1 }
in_etcd && /command:/ { in_command=1 }
in_command && /^\s*-/ && $0 ~ /etcd$/ { in_args=1 }
in_args && /^\s*-[^-]/ { in_args=0 } # next non-flag argument or section
{ print }
END {
# No-op in END; insertion is handled inline
}
' "$TMP_FILE" > "${TMP_FILE}.awkprep"

# Now append the flag line directly after the last etcd command arg.
# We detect the last line starting with " - --" under etcd args and insert after it.
python3 - "$TMP_FILE" << 'PYEOF' > "${TMP_FILE}.final"
import sys
from pathlib import Path

p = Path(sys.argv[1])
lines = p.read_text().splitlines()

# Find last index of a flag-like arg under etcd container.
last_flag_idx = -1
in_etcd = False
in_command = False
for i, line in enumerate(lines):
if "- name: etcd" in line:
in_etcd = True
in_command = False
if in_etcd and "containers:" in line and "- name: etcd" not in line:
# safety: if containers restarts, leave etcd block
in_etcd = False
in_command = False
if in_etcd and "command:" in line:
in_command = True
continue
if in_etcd and in_command:
# args are typically indented with " - ..."
stripped = line.lstrip()
if stripped.startswith("- --"):
last_flag_idx = i
# break at blank line or next section
if stripped.startswith("- name: ") and "etcd" not in stripped:
in_etcd = False
in_command = False

if last_flag_idx == -1:
# Fallback: just print as-is if we cannot safely detect where to insert
sys.stdout.write("\n".join(lines))
else:
new_lines = []
for i, line in enumerate(lines):
new_lines.append(line)
if i == last_flag_idx:
indent = line[:len(line) - len(line.lstrip())]
new_lines.append(f"{indent}--peer-client-cert-auth=true" if line.strip().startswith("-") else f"{indent}- --peer-client-cert-auth=true")
sys.stdout.write("\n".join(new_lines))
PYEOF

# If python-based insertion failed for some reason, fall back to appending near other flags
if ! grep -q -- "--peer-client-cert-auth=true" "${TMP_FILE}.final" 2>/dev/null; then
echo "[WARN] Structured insertion failed; falling back to simpler append method"
# Append flag right after any existing etcd flag line as a last resort
sed '/--initial-advertise-peer-urls=/a\ - --peer-client-cert-auth=true' "$TMP_FILE" > "${TMP_FILE}.final" || {
echo "[ERROR] Failed to insert --peer-client-cert-auth flag"
rm -f "$TMP_FILE" "${TMP_FILE}.awkprep" "${TMP_FILE}.final"
exit 1
}
fi

mv "${TMP_FILE}.final" "$TMP_FILE"
rm -f "${TMP_FILE}.awkprep" 2>/dev/null || true
fi

# 4) Replace manifest with updated version
cp -p "$TMP_FILE" "$ETCD_MANIFEST"
rm -f "$TMP_FILE"

echo "[INFO] Updated $ETCD_MANIFEST with --peer-client-cert-auth=true"

echo "[INFO] Kubelet will automatically restart the etcd static pod when it detects manifest changes."
echo "[INFO] Wait 20–30 seconds for etcd to restart before verification."

sleep 30

# 5) Verification: ensure the etcd process has the correct flag
echo "[INFO] Verifying etcd process flags"
/bin/ps -ef | /bin/grep etcd | /bin/grep -v grep || {
echo "[ERROR] etcd process not found; check kubelet and etcd pod status."
exit 1
}

if /bin/ps -ef | /bin/grep etcd | /bin/grep -v grep | /bin/grep -q -- "--peer-client-cert-auth=true"; then
echo "[INFO] Verification successful: etcd is running with --peer-client-cert-auth=true"
else
echo "[ERROR] Verification failed: etcd is not running with --peer-client-cert-auth=true"
echo "[INFO] Current etcd command line:"
/bin/ps -ef | /bin/grep etcd | /bin/grep -v grep
exit 1
fi

echo "[INFO] Remediation complete."