Skip to main content

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

Manual Steps
  1. 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.yaml
    sudo cp -a /etc/kubernetes/manifests/kube-apiserver.yaml /etc/kubernetes/manifests/kube-apiserver.yaml.bak.$(date +%F-%H%M%S)
  2. 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/pki
    cd /etc/kubernetes/pki

    sudo openssl req -newkey rsa:4096 -nodes -keyout apiserver.key \
    -x509 -days 365 -out apiserver.crt \
    -subj "/CN=kube-apiserver"
    sudo chmod 600 apiserver.key
    sudo chmod 644 apiserver.crt
  3. 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.yaml
    sudo sed -i '/- --tls-private-key-file/d' /etc/kubernetes/manifests/kube-apiserver.yaml

    sudo 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.yaml

    If the - kube-apiserver line does not exist exactly as matched above, instead open the file in an editor and add these two flags under command: or args::

    - --tls-cert-file=/etc/kubernetes/pki/apiserver.crt
    - --tls-private-key-file=/etc/kubernetes/pki/apiserver.key
  4. 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 under spec.containers[0].volumeMounts in /etc/kubernetes/manifests/kube-apiserver.yaml:

    - mountPath: /etc/kubernetes/pki
    name: k8s-certs
    readOnly: true

    And ensure a matching volume exists under spec.volumes:

    - name: k8s-certs
    hostPath:
    path: /etc/kubernetes/pki
    type: DirectoryOrCreate

    Saving the file will restart the kube-apiserver static pod.

  5. Wait for the kube-apiserver pod to restart and become Running on each control plane node:

    # From any machine with kubectl access
    kubectl -n kube-system get pods -l component=kube-apiserver -o wide
  6. 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 "$@"