Skip to main content

Ensure Client Ca File Argument Is Set Appropriate

More Info:​

Enable Kubelet authentication using certificates.

Risk Level​

Low

Address​

Security

Compliance Standards​

  • APRA CPS 234 (Australia)
  • BSI C5 (Germany)
  • Brazil LGPD
  • CCPA / CPRA (California)
  • CIS Critical Security Controls v8
  • CIS EKS
  • CMMC 2.0
  • CSA Cloud Controls Matrix v4
  • DPDPA
  • Digital Operational Resilience Act (EU)
  • Essential 8
  • ISO/IEC 27017
  • ISO/IEC 27018
  • ISO/IEC 27701
  • KSA PDPL
  • MAS Technology Risk Management (Singapore)
  • MITRE ATT&CK (Cloud)
  • NIS2 Directive
  • NIST SP 800-171
  • NYDFS 23 NYCRR 500
  • SWIFT Customer Security Controls Framework
  • Sarbanes-Oxley IT General Controls
  • UK NCSC Cyber Assessment Framework

Triage and Remediation​

Remediation​

Manual Steps
  1. On every worker node, inspect the current kubelet configuration file to see if clientCAFile is already set:

    sudo grep -A5 'authentication:' /var/lib/kubelet/config.yaml || sudo sed -n '1,120p' /var/lib/kubelet/config.yaml
  2. On every worker node, identify the correct client CA file path (this is an operational/security decision; typically the same CA used by the API server for client auth, e.g. /etc/kubernetes/pki/ca.crt). Confirm the file exists and is readable by kubelet:

    sudo ls -l /etc/kubernetes/pki/ca.crt
    sudo stat /etc/kubernetes/pki/ca.crt
  3. On every worker node, edit /var/lib/kubelet/config.yaml to configure x509 client auth with the chosen CA file. For example, using vi:

    sudo vi /var/lib/kubelet/config.yaml

    Ensure you have (add or modify as needed):

    authentication:
    anonymous:
    enabled: false
    webhook:
    enabled: true
    x509:
    clientCAFile: "/etc/kubernetes/pki/ca.crt"

    Keep indentation consistent with the rest of the file.

  4. If the kubelet is also started with command‑line flags that might override this setting, review the systemd drop‑in to ensure no conflicting --client-ca-file flag is present or that it matches the same path:

    sudo cat /etc/systemd/system/kubelet.service.d/10-kubeadm.conf

    If needed, edit:

    sudo vi /etc/systemd/system/kubelet.service.d/10-kubeadm.conf

    and either remove any --client-ca-file=... argument or set it to:

    --client-ca-file=/etc/kubernetes/pki/ca.crt
  5. On every worker node, reload systemd and restart kubelet (this will temporarily disrupt workloads managed by that kubelet while it reconnects):

    sudo systemctl daemon-reload
    sudo systemctl restart kubelet.service
  6. Verification on every worker node: confirm the kubelet process is running with the intended config and/or flag. First, check the process:

    /bin/ps -fC kubelet

    Then confirm the effective configuration contains the clientCAFile you set:

    sudo grep -A5 'authentication:' /var/lib/kubelet/config.yaml
Using kubectl

kubectl cannot modify kubelet host-level configuration such as /var/lib/kubelet/config.yaml or the kubelet systemd units. To address this finding, make the changes directly on every worker node as described in the Manual Steps section.

Automation
#!/usr/bin/env bash
#
# Configure kubelet to use an X.509 client CA file for authentication
# Target: every worker node
#
# This script is idempotent and safe to re-run.
# REQUIREMENT: You must set CLIENT_CA_FILE to an existing readable CA file.

set -euo pipefail

CLIENT_CA_FILE="/etc/kubernetes/pki/ca.crt" # <-- ADJUST IF NEEDED

if [[ ! -r "${CLIENT_CA_FILE}" ]]; then
echo "ERROR: CLIENT_CA_FILE '${CLIENT_CA_FILE}' does not exist or is not readable."
echo "Update CLIENT_CA_FILE in this script to the correct CA path, then re-run."
exit 1
fi

echo "Using client CA file: ${CLIENT_CA_FILE}"

KUBELET_CONFIG_FILE="/var/lib/kubelet/config.yaml"
KUBELET_SYSTEMD_DROPIN="/etc/systemd/system/kubelet.service.d/10-kubeadm.conf"

changed=0

############################################
# 1. Ensure Kubelet config file has clientCAFile
############################################
if [[ -f "${KUBELET_CONFIG_FILE}" ]]; then
echo "Found kubelet config file at ${KUBELET_CONFIG_FILE}"

# Ensure top-level 'authentication:' block exists
if ! grep -qE '^[[:space:]]*authentication:' "${KUBELET_CONFIG_FILE}"; then
echo "Adding 'authentication:' block to ${KUBELET_CONFIG_FILE}"
cat <<'EOF' >> "${KUBELET_CONFIG_FILE}"

authentication:
anonymous:
enabled: false
x509:
clientCAFile: ""
EOF
changed=1
fi

# Ensure 'authentication: x509: clientCAFile' is set to desired path
if grep -qE '^[[:space:]]*authentication:[[:space:]]*$' "${KUBELET_CONFIG_FILE}"; then
# Use python (if available) to do yaml-safe update; otherwise fall back to sed
if command -v python3 >/dev/null 2>&1; then
python3 <<PYEOF
import sys, yaml, pathlib
cfg_path = pathlib.Path("${KUBELET_CONFIG_FILE}")
data = yaml.safe_load(cfg_path.read_text()) or {}
auth = data.setdefault("authentication", {})
x509 = auth.setdefault("x509", {})
if x509.get("clientCAFile") != "${CLIENT_CA_FILE}":
x509["clientCAFile"] = "${CLIENT_CA_FILE}"
cfg_path.write_text(yaml.safe_dump(data, default_flow_style=False))
PYEOF
changed=$((changed + 1))
else
# Fallback: naive update (assumes existing x509 block or appends one)
if grep -qE '^[[:space:]]*clientCAFile:' "${KUBELET_CONFIG_FILE}"; then
sed -i "s#^\([[:space:]]*clientCAFile:\).*#\1 ${CLIENT_CA_FILE}#" "${KUBELET_CONFIG_FILE}"
else
awk -v ca="${CLIENT_CA_FILE}" '
{print}
/^[[:space:]]*authentication:[[:space:]]*$/ {
print " x509:"
print " clientCAFile: " ca
}
' "${KUBELET_CONFIG_FILE}" > "${KUBELET_CONFIG_FILE}.tmp"
mv "${KUBELET_CONFIG_FILE}.tmp" "${KUBELET_CONFIG_FILE}"
fi
changed=$((changed + 1))
fi
fi
else
echo "No kubelet config file at ${KUBELET_CONFIG_FILE}; skipping file-based configuration."
fi

############################################
# 2. Ensure systemd drop-in has --client-ca-file
############################################
if [[ -f "${KUBELET_SYSTEMD_DROPIN}" ]]; then
echo "Found kubelet systemd drop-in at ${KUBELET_SYSTEMD_DROPIN}"

if grep -q 'KUBELET_AUTHZ_ARGS' "${KUBELET_SYSTEMD_DROPIN}"; then
# Update or add --client-ca-file within existing KUBELET_AUTHZ_ARGS
if grep -q -- '--client-ca-file=' "${KUBELET_SYSTEMD_DROPIN}"; then
sed -i "s#--client-ca-file=[^\" ]*#--client-ca-file=${CLIENT_CA_FILE}#g" "${KUBELET_SYSTEMD_DROPIN}"
else
sed -i 's/^\(Environment="KUBELET_AUTHZ_ARGS=.*\)"/\1 --client-ca-file='"${CLIENT_CA_FILE}"'"/' "${KUBELET_SYSTEMD_DROPIN}"
fi
else
# Add a new KUBELET_AUTHZ_ARGS environment line
sed -i '/^\[Service\]/a Environment="KUBELET_AUTHZ_ARGS=--client-ca-file='"${CLIENT_CA_FILE}"'"' "${KUBELET_SYSTEMD_DROPIN}"
fi
changed=$((changed + 1))
else
echo "No kubelet systemd drop-in at ${KUBELET_SYSTEMD_DROPIN}; skipping flag-based configuration."
fi

############################################
# 3. Reload systemd and restart kubelet if anything changed
############################################
if [[ "${changed}" -gt 0 ]]; then
echo "Configuration updated; reloading systemd and restarting kubelet..."
systemctl daemon-reload
systemctl restart kubelet.service
else
echo "No changes made; kubelet restart not required."
fi

############################################
# 4. Verification (equivalent to audit)
############################################
echo "Verifying kubelet process arguments and config..."

/bin/ps -fC kubelet || {
echo "ERROR: kubelet process not found."
exit 1
}

echo
echo "Kubelet process line:"
/bin/ps -fC kubelet | sed -n '2p'

echo
echo "Searching for '--client-ca-file' in kubelet args:"
if /bin/ps -fC kubelet | grep -q -- '--client-ca-file='; then
/bin/ps -fC kubelet | sed -n '2p' | grep -- '--client-ca-file='
else
echo "No --client-ca-file flag found in kubelet process; relying on config file (if present)."
fi

if [[ -f "${KUBELET_CONFIG_FILE}" ]]; then
echo
echo "Effective clientCAFile from ${KUBELET_CONFIG_FILE}:"
grep -n 'clientCAFile' "${KUBELET_CONFIG_FILE}" || echo "clientCAFile not found in kubelet config."
fi

echo
echo "Done."

Additional Reading:​