Skip to main content

Ensure Client Ca File Argument Is Appropriate

More Info:

Enable Kubelet authentication using certificates.

Risk Level

Low

Address

Security

Compliance Standards

  • CIS Kubernetes

Triage and Remediation

Remediation

Manual Steps
  1. On every control plane node, identify the API server static pod manifest and current flags:

    sudo ls -l /etc/kubernetes/manifests/kube-apiserver.yaml
    sudo grep -n 'client-ca-file' /etc/kubernetes/manifests/kube-apiserver.yaml || echo "no client-ca-file set"

    Also confirm you have (or create) a suitable client CA file (typically the cluster CA):

    sudo ls -l /etc/kubernetes/pki/ca.crt
  2. On every control plane node, back up the API server manifest before editing:

    sudo cp /etc/kubernetes/manifests/kube-apiserver.yaml /etc/kubernetes/manifests/kube-apiserver.yaml.bak.$(date +%F-%H%M%S)
  3. On every control plane node, edit /etc/kubernetes/manifests/kube-apiserver.yaml and add or adjust the --client-ca-file argument under the container command list to point to your CA file (example uses /etc/kubernetes/pki/ca.crt):

    sudo sed -i '/kube-apiserver/,$ s#\(\s*-\s*--client-ca-file=\).*#\1/etc/kubernetes/pki/ca.crt#' /etc/kubernetes/manifests/kube-apiserver.yaml

    If the flag does not exist yet, append a new line under the other -- flags (edit with an editor such as vi):

    sudo vi /etc/kubernetes/manifests/kube-apiserver.yaml

    Add, aligned with the other args:

    - --client-ca-file=/etc/kubernetes/pki/ca.crt

    Saving this file will cause the kube-apiserver static pod to restart automatically.

  4. On every control plane node, ensure the CA file is readable by the kube-apiserver process (usually runs as root, so default permissions are fine). For standard kubeadm layouts:

    sudo chmod 600 /etc/kubernetes/pki/ca.crt
    sudo chown root:root /etc/kubernetes/pki/ca.crt
  5. On every control plane node, wait for the kube-apiserver pod to restart and become Ready:

    sudo crictl ps | grep kube-apiserver || sudo docker ps | grep kube-apiserver
  6. Verification on every control plane node: confirm the running kube-apiserver process now has the expected --client-ca-file argument set:

    /bin/ps -ef | grep kube-apiserver | grep -v grep | grep -- '--client-ca-file='

    You should see an argument like: --client-ca-file=/etc/kubernetes/pki/ca.crt.

Using kubectl

kubectl cannot change the kube-apiserver’s --client-ca-file setting or any other host-level flags; this must be fixed by editing /etc/kubernetes/manifests/kube-apiserver.yaml directly on every control plane node. Refer to the Manual Steps section for the exact on-node configuration changes and verification commands.

Automation
#!/usr/bin/env bash
#
# Automation: Ensure kube-apiserver is started with --client-ca-file on every control plane node.
#
# Usage:
# 1) Copy this script to each CONTROL PLANE node.
# 2) Run as root: bash ./fix-kube-apiserver-client-ca.sh
#
# Idempotent: safe to re-run. It will:
# - Ensure the chosen client CA file exists.
# - Ensure --client-ca-file=<path> is present (and unique) in /etc/kubernetes/manifests/kube-apiserver.yaml
# - Trigger kube-apiserver static pod restart via manifest edit.
# - Verify via process inspection.

set -euo pipefail

# ---------------- CONFIGURATION (EDIT AS NEEDED) ----------------

# Path to the client CA file that the API server should trust for client cert auth.
# This must be a valid CA bundle that signs Kubelet client certificates.
CLIENT_CA_FILE="/etc/kubernetes/pki/apiserver-client-ca.crt"

# Path to the kube-apiserver static pod manifest.
APISERVER_MANIFEST="/etc/kubernetes/manifests/kube-apiserver.yaml"

# ---------------- HELPER FUNCTIONS ----------------

log() {
printf '[%s] %s\n' "$(date -u +'%Y-%m-%dT%H:%M:%SZ')" "$*" >&2
}

fail() {
log "ERROR: $*"
exit 1
}

require_root() {
if [[ "$(id -u)" -ne 0 ]]; then
fail "This script must be run as root."
fi
}

backup_file() {
local file="$1"
if [[ -f "$file" ]]; then
local ts
ts="$(date -u +'%Y%m%dT%H%M%SZ')"
local backup="${file}.bak.${ts}"
cp -p "$file" "$backup"
log "Backup created: $backup"
fi
}

ensure_client_ca_file_exists() {
if [[ -f "$CLIENT_CA_FILE" ]]; then
log "Client CA file already exists at: $CLIENT_CA_FILE"
return 0
fi

log "Client CA file not found at: $CLIENT_CA_FILE"
log "You must provision a proper client CA bundle that signs kubelet client certificates."
log "For now, creating a placeholder that will NOT enable real authentication."

mkdir -p "$(dirname "$CLIENT_CA_FILE")"
# Placeholder self-signed root CA (dummy). Replace with your real CA.
cat >"$CLIENT_CA_FILE" <<'EOF'
-----BEGIN CERTIFICATE-----
MIIBszCCAVmgAwIBAgIUYJ5vjHZyN+dummy-placeholder-CA-cert-ONLYwCgYIKoZIzj0EAwIw
EjEQMA4GA1UEAwwHRE9OT1RVUzAeFw0yMDAxMDEwMDAwMDBaFw00OTEyMzEyMzU5NTlaMBIxEDAO
BgNVBAMMB0RPTk9UVVMwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAQAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
o1MwUTAdBgNVHQ4EFgQUdummy-placeholder-CA-key-ONLYhEoU06YfBgNVHSMEEDAOmAwh
dummy-placeholder-CA-subject-ONLYMA8GA1UdEwEB/wQFMAMBAf8wCgYIKoZIzj0EAwIDSAAw
RQIhAJy4dummyplaceholderSigONLY1zq2dGJSou1qg7Z+R7B9xEQAiAF+d
-----END CERTIFICATE-----
EOF

chmod 0644 "$CLIENT_CA_FILE"
log "Placeholder client CA file created at $CLIENT_CA_FILE"
log "IMPORTANT: Replace this placeholder with your real client CA as per your PKI design."
}

update_apiserver_manifest() {
if [[ ! -f "$APISERVER_MANIFEST" ]]; then
fail "API server manifest not found at $APISERVER_MANIFEST. This node may not be a static-pod control plane."
fi

# Normalize to absolute path
local ca_path="$CLIENT_CA_FILE"

log "Ensuring --client-ca-file is correctly configured in $APISERVER_MANIFEST"

# Backup before modifying
backup_file "$APISERVER_MANIFEST"

# Remove any existing --client-ca-file= arguments to avoid duplicates
sed -i 's/ *--client-ca-file=[^" ]*//g' "$APISERVER_MANIFEST"

# Ensure the manifest has a kube-apiserver command line with args.
# Append the flag to the command section or args list.
if grep -q -- '--client-ca-file=' "$APISERVER_MANIFEST"; then
log "--client-ca-file already present after cleanup; nothing to add."
else
# Try to append to an existing line with kube-apiserver command flags.
if grep -q 'kube-apiserver' "$APISERVER_MANIFEST"; then
# Add the flag to the first line containing 'kube-apiserver' that looks like a command line.
# This covers the common manifest style: - kube-apiserver ...flags...
awk -v flag=" --client-ca-file=${ca_path}" '
/kube-apiserver/ && /--advertise-address|--secure-port|--etcd-servers/ && !done {
sub(/kube-apiserver/, "kube-apiserver" flag, $0)
done=1
}
{ print }
' "$APISERVER_MANIFEST" >"${APISERVER_MANIFEST}.tmp" && mv "${APISERVER_MANIFEST}.tmp" "$APISERVER_MANIFEST"
log "Injected --client-ca-file into kube-apiserver command line."
else
fail "Could not find kube-apiserver command in $APISERVER_MANIFEST to inject --client-ca-file. Manual edit required."
fi
fi
}

verify_apiserver_flag() {
log "Waiting up to 120s for kube-apiserver static pod to restart and pick up changes..."
local end=$((SECONDS + 120))
local found=0

while (( SECONDS < end )); do
if /bin/ps -ef | grep kube-apiserver | grep -v grep | grep -q -- "--client-ca-file=${CLIENT_CA_FILE}"; then
found=1
break
fi
sleep 5
done

if (( found == 1 )); then
log "VERIFICATION SUCCESS: kube-apiserver is running with --client-ca-file=${CLIENT_CA_FILE}"
/bin/ps -ef | grep kube-apiserver | grep -v grep
else
log "VERIFICATION FAILED: kube-apiserver process not showing --client-ca-file=${CLIENT_CA_FILE}"
log "Current kube-apiserver processes:"
/bin/ps -ef | grep kube-apiserver | grep -v grep || true
fail "Please check kubelet logs and /etc/kubernetes/manifests/kube-apiserver.yaml for issues."
fi
}

main() {
require_root
log "Starting remediation for CIS Kubernetes 4.2.3 on this control plane node."

ensure_client_ca_file_exists
update_apiserver_manifest
verify_apiserver_flag

log "Remediation for this node completed."
}

main "$@"

Additional Reading: