Skip to main content

Client Certificate Authentication Should Not Be Used For

More Info:

Client certificates are hard to revoke and manage, making them weak for user authentication. Use OIDC instead.

Risk Level

High

Address

Security

Compliance Standards

  • CIS Kubernetes

Triage and Remediation

Remediation

Manual Steps
  1. Identify how users currently authenticate

    • On any machine with access to kubeconfig files (e.g. admin workstation, bastion):
      grep -RniE 'client-certificate|client-key|auth-provider: oidc|idp-issuer-url|id-token' ~/.kube /etc/kubernetes 2>/dev/null
    • Review the output to determine which user contexts use client-certificate/client-key versus OIDC (auth-provider: oidc or exec plugins that obtain OIDC tokens).
  2. Inspect API server authentication configuration

    • On every control plane node:
      grep -E -- '-(--client-ca-file|--requestheader-client-ca-file|--oidc-issuer-url|--oidc-client-id|--oidc-username-claim|--oidc-groups-claim)' \
      /etc/kubernetes/manifests/kube-apiserver.yaml
    • Confirm whether --client-ca-file is configured (enables client cert auth) and whether OIDC flags (--oidc-issuer-url, --oidc-client-id, etc.) are already present.
  3. Determine which client certificate subjects correspond to human users

    • On any machine that has user client certificates (often where kubeconfigs are stored):
      find ~/.kube -type f \( -name '*.crt' -o -name '*.pem' \) -print -exec openssl x509 -in {} -noout -subject -issuer -enddate \;
    • For each certificate found in kubeconfigs used by people (not components like kubelets/controllers), record the Subject (CN/O) and map them to real users and groups in your identity system.
  4. Plan and, if appropriate, implement migration of human users to OIDC

    • Work with your identity provider team to either confirm an existing OIDC integration or define: issuer URL, client ID, redirect URI, groups/username claims.
    • Update user kubeconfigs (on user machines) to remove client-certificate/client-key and configure OIDC instead, per your IdP’s guidance (e.g. auth-provider: oidc or an exec plugin).
    • Keep client certificate access in place during a transition window so users can switch without lockout.
  5. Restrict or disable client certificate authentication for users (post‑migration)

    • On every control plane node, before changing anything, back up the manifest:
      cp -p /etc/kubernetes/manifests/kube-apiserver.yaml /etc/kubernetes/manifests/kube-apiserver.yaml.bak
    • Edit /etc/kubernetes/manifests/kube-apiserver.yaml and, following your migration plan:
      • Ensure any --client-ca-file referenced is only used for non‑user components (e.g. kubelet, front-proxy) and that certificates issued for human users are no longer trusted by that CA; or
      • Point --client-ca-file to a CA that signs only component certs, not human user certs; or, if you have an alternative authentication mechanism for all access, remove --client-ca-file.
    • Be aware: saving this file will cause the kube-apiserver static pod to be restarted on that control plane node.
  6. Verify that human user access no longer depends on client certificates

    • On any machine with user kubeconfigs, for a migrated user:
      kubectl --context <OIDC-user-context> auth can-i get pods -A
    • Attempt the same with a kubeconfig that previously used a client certificate for that user but no longer has OIDC configured; it should now fail.
    • On every control plane node, confirm the running apiserver has the intended flags:
      crictl ps | grep kube-apiserver
      crictl inspect $(crictl ps -q --name kube-apiserver) | grep -E '"--client-ca-file=|--oidc-issuer-url=|--oidc-client-id='
    • Confirm that only non‑user components (e.g. kubelets, front-proxy) use client certificate authentication and that human users authenticate via OIDC.
Using kubectl

kubectl cannot be used to change client certificate authentication for users because this configuration lives in the kube-apiserver static pod manifest on each control plane node at /etc/kubernetes/manifests/kube-apiserver.yaml. To review and adjust this setting, follow the guidance in the Manual Steps section on the control plane nodes directly.

Automation
#!/usr/bin/env bash
# Purpose: Report use of client-certificate auth for *users* so it can be
# reviewed and migrated to OIDC or other mechanisms.
#
# Requirements:
# - Run on any machine with kubectl access and cluster-admin privileges.
# - kubectl must be configured to talk to the target cluster.

set -euo pipefail

echo "=== Cluster-wide authentication configuration review ==="
echo

###############################################################################
# 1) Inspect kube-apiserver authentication flags (from static pod manifest)
###############################################################################
echo "[1] kube-apiserver authentication flags"

# Try all namespaces in case control-plane is not in kube-system
for ns in kube-system kube-public default; do
if kubectl get pod -n "$ns" 2>/dev/null | grep -q 'kube-apiserver'; then
APISERVER_NS="$ns"
break
fi
done

if [ -z "${APISERVER_NS:-}" ]; then
echo " !! No kube-apiserver pod found via kubectl (managed control-plane or restricted access)."
echo " Review /etc/kubernetes/manifests/kube-apiserver.yaml directly on every control plane node."
else
echo " Using namespace: $APISERVER_NS"
echo

# Extract command-line flags from kube-apiserver container spec
kubectl get pod -n "$APISERVER_NS" -l component=kube-apiserver -o jsonpath='{range .items[*]}Name: {.metadata.name}{"\n"}Command: {.spec.containers[0].command}{"\n\n"}{end}' 2>/dev/null \
|| kubectl get pod -n "$APISERVER_NS" -o jsonpath='{range .items[*]}Name: {.metadata.name}{"\n"}Command: {.spec.containers[0].command}{"\n\n"}{end}'

echo
echo ">>> Review for these flags indicating client certificate auth is enabled:"
echo " --client-ca-file=..."
echo " --requestheader-client-ca-file=..."
echo
echo " Presence of these flags means the API server accepts client certificates."
echo " You must determine whether they are used for *human users* vs nodes/services."
fi

###############################################################################
# 2) List client-certificate usage in kubeconfigs (users & contexts)
###############################################################################
echo
echo "[2] kubeconfig objects in the cluster that reference client certificates"
echo

# Get all kubeconfig-like Secrets and ConfigMaps in kube-system and default
# (common locations), and show entries that use client-certificate-data/authority
for ns in kube-system default; do
echo " Namespace: $ns"
echo " - Secrets with potential kubeconfigs using client certificates:"
kubectl get secrets -n "$ns" -o jsonpath='{range .items[?(@.type=="Opaque")]}{.metadata.name}{"\n"}{end}' 2>/dev/null | while read -r s; do
[ -z "$s" ] && continue
DATA_KEYS=$(kubectl get secret "$s" -n "$ns" -o jsonpath='{range .data}{@}{" "}{end}' 2>/dev/null | base64 -d 2>/dev/null || true)
if echo "$DATA_KEYS" | grep -q "client-certificate-data"; then
echo " * $s (contains client-certificate-data)"
fi
done

echo " - ConfigMaps that may store kubeconfigs with client certificates:"
kubectl get configmaps -n "$ns" -o name 2>/dev/null | while read -r cm; do
CONTENT=$(kubectl get "$cm" -n "$ns" -o yaml 2>/dev/null)
if printf '%s\n' "$CONTENT" | grep -q "client-certificate-data"; then
echo " * ${cm#configmap/} (contains client-certificate-data)"
fi
done
echo
done

echo ">>> Any kubeconfig used by *human users* that contains client-certificate-data"
echo " indicates client certificate authentication is in use for users and should"
echo " be considered for migration to OIDC or another revocable mechanism."

###############################################################################
# 3) Enumerate cluster RoleBindings/ClusterRoleBindings tied to cert-based users
###############################################################################
echo
echo "[3] RoleBindings/ClusterRoleBindings granted to likely certificate-based users"
echo

echo " ClusterRoleBindings:"
kubectl get clusterrolebindings -o json | \
jq -r '.items[] | select(.subjects!=null) |
.metadata.name as $rbname |
.subjects[] |
select(.kind=="User") |
"\($rbname) -> User: \(.name)"' 2>/dev/null || echo " (jq not available; skipping detailed subject parsing)"

echo
echo " RoleBindings (all namespaces):"
kubectl get rolebindings --all-namespaces -o json | \
jq -r '.items[] | select(.subjects!=null) |
.metadata.namespace as $ns |
.metadata.name as $rbname |
.subjects[] |
select(.kind=="User") |
"\($ns)/\($rbname) -> User: \(.name)"' 2>/dev/null || echo " (jq not available; skipping detailed subject parsing)"

echo
echo ">>> Users listed above are authenticated somehow (certs, OIDC, etc.)."
echo " Cross-reference these users with your known OIDC identities."
echo " Any user principals that are *not* OIDC identities likely rely on"
echo " client certificates or other non-OIDC mechanisms."

###############################################################################
# 4) Check for OIDC configuration on kube-apiserver
###############################################################################
echo
echo "[4] OIDC configuration on kube-apiserver (from pod spec)"
echo

if [ -n "${APISERVER_NS:-}" ]; then
kubectl get pod -n "$APISERVER_NS" -l component=kube-apiserver -o jsonpath='{range .items[*]}Name: {.metadata.name}{"\n"}Args: {.spec.containers[0].command}{"\n\n"}{end}' 2>/dev/null \
|| kubectl get pod -n "$APISERVER_NS" -o jsonpath='{range .items[*]}Name: {.metadata.name}{"\n"}Args: {.spec.containers[0].command}{"\n\n"}{end}'

echo
echo ">>> Look for these OIDC flags:"
echo " --oidc-issuer-url"
echo " --oidc-client-id"
echo " --oidc-username-claim"
echo " --oidc-groups-claim"
echo
echo " Absence of OIDC flags, combined with presence of client-ca-file and"
echo " user kubeconfigs using client certificates, indicates a problematic"
echo " reliance on client certificates for user authentication."
else
echo " (kube-apiserver not visible via kubectl; verify OIDC flags in"
echo " /etc/kubernetes/manifests/kube-apiserver.yaml on every control plane node.)"
fi

echo
echo "=== Interpretation summary ==="
echo "- Problematic state for this check:"
echo " * kube-apiserver has --client-ca-file configured AND"
echo " * Human user kubeconfigs (outside node/kubelet/system accounts) contain"
echo " client-certificate-data AND"
echo " * OIDC flags are absent or not used for those users."
echo
echo "- Acceptable direction for this check:"
echo " * Human users authenticate via OIDC (or another centrally revocable"
echo " mechanism), with no remaining human user kubeconfigs relying on"
echo " client certificates."

What output indicates a problem

  • --client-ca-file is present in the kube-apiserver command line and
  • One or more kubeconfigs used by human users (for example, developer/admin configs stored in Secrets/ConfigMaps or known off-cluster files) contain client-certificate-data and
  • The kube-apiserver command line does not show OIDC flags (--oidc-issuer-url, --oidc-client-id, etc.), or you know the listed users in RoleBindings/ClusterRoleBindings are authenticating via client certificates instead of OIDC.

This script only reports state; migration away from client certificates to OIDC must be planned and executed manually.