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
Remediation
Manual Steps
-
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-keyversus OIDC (auth-provider: oidcorexecplugins that obtain OIDC tokens).
- On any machine with access to kubeconfig files (e.g. admin workstation, bastion):
-
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-fileis configured (enables client cert auth) and whether OIDC flags (--oidc-issuer-url,--oidc-client-id, etc.) are already present.
- On every control plane node:
-
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.
- On any machine that has user client certificates (often where kubeconfigs are stored):
-
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-keyand configure OIDC instead, per your IdP’s guidance (e.g.auth-provider: oidcor anexecplugin). - Keep client certificate access in place during a transition window so users can switch without lockout.
-
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.yamland, following your migration plan:- Ensure any
--client-ca-filereferenced 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-fileto 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.
- Ensure any
- Be aware: saving this file will cause the kube-apiserver static pod to be restarted on that control plane node.
- On every control plane node, before changing anything, back up the manifest:
-
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-apiservercrictl 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.
- On any machine with user kubeconfigs, for a migrated user:
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-fileis 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-dataand - 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.