API Server Should Set TLS Certificate And Private Key
More Info:
Verifies that --tls-cert-file and --tls-private-key-file are set so the API server serves connections over TLS rather than plaintext HTTP.
Risk Level
High
Address
Security
Compliance Standards
- CIS Kubernetes
Triage and Remediation
- Remediation
Remediation
Manual Steps
-
On every control plane node, confirm where the API server static pod manifest is located and back it up:
sudo ls -l /etc/kubernetes/manifests/kube-apiserver.yamlsudo cp -a /etc/kubernetes/manifests/kube-apiserver.yaml /etc/kubernetes/manifests/kube-apiserver.yaml.bak.$(date +%F-%H%M%S) -
On the same control plane node, ensure you have or create a TLS certificate and key for the API server (adjust CN/SANs as appropriate for your cluster):
sudo mkdir -p /etc/kubernetes/pkicd /etc/kubernetes/pkisudo openssl req -newkey rsa:4096 -nodes -keyout apiserver.key \-x509 -days 365 -out apiserver.crt \-subj "/CN=kube-apiserver"sudo chmod 600 apiserver.keysudo chmod 644 apiserver.crt -
On every control plane node, edit the API server manifest to reference the TLS cert and key (this will cause the kube-apiserver static pod to restart when you save):
sudo sed -i '/- --tls-cert-file/d' /etc/kubernetes/manifests/kube-apiserver.yamlsudo sed -i '/- --tls-private-key-file/d' /etc/kubernetes/manifests/kube-apiserver.yamlsudo sed -i '/- kube-apiserver/a\ - --tls-cert-file=/etc/kubernetes/pki/apiserver.crt\n - --tls-private-key-file=/etc/kubernetes/pki/apiserver.key' \/etc/kubernetes/manifests/kube-apiserver.yamlIf the
- kube-apiserverline does not exist exactly as matched above, instead open the file in an editor and add these two flags undercommand:orargs::- --tls-cert-file=/etc/kubernetes/pki/apiserver.crt- --tls-private-key-file=/etc/kubernetes/pki/apiserver.key -
On every control plane node, ensure the certificate and key are mounted into the kube-apiserver container if not already (edit with an editor if needed):
Add underspec.containers[0].volumeMountsin/etc/kubernetes/manifests/kube-apiserver.yaml:- mountPath: /etc/kubernetes/pkiname: k8s-certsreadOnly: trueAnd ensure a matching volume exists under
spec.volumes:- name: k8s-certshostPath:path: /etc/kubernetes/pkitype: DirectoryOrCreateSaving the file will restart the kube-apiserver static pod.
-
Wait for the kube-apiserver pod to restart and become Running on each control plane node:
# From any machine with kubectl accesskubectl -n kube-system get pods -l component=kube-apiserver -o wide -
Verify on every control plane node that the kube-apiserver process is now using the TLS certificate and key flags:
/bin/ps -ef | grep kube-apiserver | grep -v grep | \egrep -- '--tls-cert-file=|--tls-private-key-file='
Using kubectl
kubectl cannot modify the kube-apiserver static pod manifest or its process flags; this setting must be changed directly in /etc/kubernetes/manifests/kube-apiserver.yaml on every control plane node. Refer to the Manual Steps section for the exact edits and verification commands to remediate this finding.
Automation
#!/usr/bin/env bash
#
# Remediation for CIS Kubernetes 4.2.9:
# Ensure kube-apiserver has --tls-cert-file and --tls-private-key-file set.
#
# Run on: every control plane node in the cluster (as root or with sudo).
#
# This script:
# - Backs up /etc/kubernetes/manifests/kube-apiserver.yaml
# - Ensures TLS cert/key files exist (placeholders if you don't have real ones yet)
# - Ensures the kube-apiserver manifest has the required flags
# - Relies on the kubelet to restart the static pod automatically
# - Verifies via the process flags
#
# NOTE: You must replace the placeholder certificate and key contents
# with valid TLS materials following your PKI/cluster design.
#
set -euo pipefail
APISERVER_MANIFEST="/etc/kubernetes/manifests/kube-apiserver.yaml"
TLS_CERT_FILE="/etc/kubernetes/pki/apiserver.crt"
TLS_KEY_FILE="/etc/kubernetes/pki/apiserver.key"
BACKUP_SUFFIX="$(date +%Y%m%d%H%M%S)"
require_root() {
if [ "$EUID" -ne 0 ]; then
echo "ERROR: This script must be run as root or with sudo." >&2
exit 1
fi
}
check_manifest_exists() {
if [ ! -f "$APISERVER_MANIFEST" ]; then
echo "ERROR: $APISERVER_MANIFEST not found on this node. Is this a control plane node?" >&2
exit 1
fi
}
backup_manifest() {
local backup="${APISERVER_MANIFEST}.${BACKUP_SUFFIX}.bak"
cp -p "$APISERVER_MANIFEST" "$backup"
echo "Backup created: $backup"
}
ensure_tls_files_exist() {
# If you already have correct TLS files from kubeadm or your PKI,
# this will not overwrite them.
if [ ! -f "$TLS_CERT_FILE" ]; then
echo "WARNING: $TLS_CERT_FILE not found. Creating placeholder file."
mkdir -p "$(dirname "$TLS_CERT_FILE")"
cat > "$TLS_CERT_FILE" <<'EOF'
-----BEGIN CERTIFICATE-----
PLACEHOLDER-APISERVER-CERTIFICATE
Replace this placeholder with a valid apiserver TLS certificate
following your cluster's PKI design.
-----END CERTIFICATE-----
EOF
chmod 600 "$TLS_CERT_FILE"
fi
if [ ! -f "$TLS_KEY_FILE" ]; then
echo "WARNING: $TLS_KEY_FILE not found. Creating placeholder file."
mkdir -p "$(dirname "$TLS_KEY_FILE")"
cat > "$TLS_KEY_FILE" <<'EOF'
-----BEGIN PRIVATE KEY-----
PLACEHOLDER-APISERVER-PRIVATE-KEY
Replace this placeholder with a valid apiserver TLS private key
matching the certificate above.
-----END PRIVATE KEY-----
EOF
chmod 600 "$TLS_KEY_FILE"
fi
}
ensure_arg_in_manifest() {
local arg="$1"
local value="$2"
local file="$3"
# If argument is already present (with any value), do nothing
if grep -E "^\s*- ${arg}=" "$file" >/dev/null 2>&1; then
# Optionally normalize the value if you want strict paths;
# here we leave existing values untouched for safety.
return 0
fi
# Insert argument into the 'command:' section for kube-apiserver container
# We look for the first '- kube-apiserver' line and add the flag immediately after.
if grep -E "^\s*- kube-apiserver" "$file" >/dev/null 2>&1; then
# Use awk to insert line after '- kube-apiserver'
awk -v a="$arg" -v v="$value" '
{
print $0
if ($1 == "-" && $2 == "kube-apiserver") {
printf " - %s=%s\n", a, v
}
}
' "$file" > "${file}.tmp"
mv "${file}.tmp" "$file"
else
echo "ERROR: Could not locate '- kube-apiserver' command entry in $file; manual edit required." >&2
exit 1
fi
}
main() {
require_root
check_manifest_exists
backup_manifest
ensure_tls_files_exist
echo "Ensuring --tls-cert-file and --tls-private-key-file are set in $APISERVER_MANIFEST"
ensure_arg_in_manifest "--tls-cert-file" "$TLS_CERT_FILE" "$APISERVER_MANIFEST"
ensure_arg_in_manifest "--tls-private-key-file" "$TLS_KEY_FILE" "$APISERVER_MANIFEST"
echo "Manifest updated. The kubelet will automatically restart the kube-apiserver static pod."
echo "Waiting 30 seconds for the kube-apiserver to restart..."
sleep 30
echo "Verification: checking kube-apiserver process flags"
if /bin/ps -ef | grep kube-apiserver | grep -v grep | grep -- "--tls-cert-file=${TLS_CERT_FILE}" >/dev/null 2>&1 \
&& /bin/ps -ef | grep kube-apiserver | grep -v grep | grep -- "--tls-private-key-file=${TLS_KEY_FILE}" >/dev/null 2>&1; then
echo "SUCCESS: kube-apiserver is running with --tls-cert-file and --tls-private-key-file set."
/bin/ps -ef | grep kube-apiserver | grep -v grep
exit 0
else
echo "WARNING: kube-apiserver process does not yet show the expected TLS flags." >&2
echo "Current kube-apiserver processes:" >&2
/bin/ps -ef | grep kube-apiserver | grep -v grep >&2
exit 1
fi
}
main "$@"