Client Certificate Authentication Should Not Be Used For
More Info:
Alternative mechanisms provided by Kubernetes such as the use of OIDC should be implemented in place of client certificates.
Risk Level
Low
Address
Security
Compliance Standards
- CIS OKE
Triage and Remediation
- Remediation
Remediation
Manual Steps
-
Identify user certificates currently in use (any control plane node)
grep -R --include="*.kubeconfig" -nE "client-certificate:|client-key:" /etc/kubernetes /var/lib/kubelet $HOME/.kube 2>/dev/null || trueopenssl x509 -in /etc/kubernetes/pki/apiserver-kubelet-client.crt -text -noout 2>/dev/null || trueReview which subjects/common names correspond to human users vs system components.
-
Design and configure an alternative auth mechanism (OIDC recommended) (control plane node or bastion with API access)
- Work with your IdP team to create an OIDC client for Kubernetes, defining:
- Issuer URL
- Client ID
- Trusted CA / JWKS
- User claim (
sub,email, or similar) and group claim
- In your OKE/IaC or control-plane configuration, plan to add/verify API server flags matching your IdP (for example only; adapt values to your environment):
# These are conceptual examples; on OKE you must set them via OKE/OCI configuration, not by editing files:--oidc-issuer-url=https://idp.example.com/--oidc-client-id=kubernetes--oidc-username-claim=email--oidc-groups-claim=groups--oidc-ca-file=/etc/kubernetes/pki/oidc-ca.crt
Do not change anything yet; complete this design before removing client-certificate usage.
- Work with your IdP team to create an OIDC client for Kubernetes, defining:
-
Update RBAC to use OIDC identities instead of certificate subjects (any machine with kubectl access)
- For each RoleBinding/ClusterRoleBinding currently referencing certificate CNs (users), inspect:
kubectl get clusterrolebindings,rolebindings -A -o yaml | grep -nE "kind: User|name: .*@.*" -C3
- Create or update bindings to map OIDC user/group identities to the same roles. Example pattern (adapt to your IdP claims):
kubectl apply -f - <<'EOF'apiVersion: rbac.authorization.k8s.io/v1kind: ClusterRoleBindingmetadata:name: oidc-adminssubjects:- kind: Groupname: oidc-k8s-admins # matches OIDC group claimapiGroup: rbac.authorization.k8s.ioroleRef:kind: ClusterRolename: cluster-adminapiGroup: rbac.authorization.k8s.ioEOF
Confirm at least one non-certificate auth path exists for each admin/support persona.
- For each RoleBinding/ClusterRoleBinding currently referencing certificate CNs (users), inspect:
-
Disable or deprecate human use of client certificates (control plane node + user workstations)
- On each admin workstation, locate kubeconfigs that reference
client-certificate/client-key:grep -R -nE "client-certificate:|client-key:" ~/.kube 2>/dev/null || true - For each human user:
- Create a new context that uses OIDC tokens (via your OIDC plugin, cloud CLI, or
execauth). - Remove or comment out
client-certificateandclient-keyentries for that user’s context once OIDC login is confirmed working.
- Create a new context that uses OIDC tokens (via your OIDC plugin, cloud CLI, or
- On the control plane node, avoid issuing new client certs for users; if you maintain your own CA, document that it must only issue certs for system components (kubelets, controllers, etc.), not people.
- On each admin workstation, locate kubeconfigs that reference
-
Harden API server configuration to restrict client certificate usage (control plane node)
On OKE, the API server manifest/etc/kubernetes/manifests/kube-controller-manager.yamlis managed by the control plane and cannot be edited directly; use the OKE/OCI console, CLI, or your IaC definitions to:- Ensure an alternative auth method (OIDC, cloud IAM, etc.) is fully enabled and tested.
- Review API server authn flags to ensure that certificate authentication is only relied on for system components, not users (for example by ensuring no RBAC bindings grant cluster roles to CNs used by humans).
If your OKE control plane exposes options to narrow certificate auth (e.g., mapping only kubelet/client cert CN patterns), apply them there; otherwise document this limitation and rely on RBAC to prevent human access via cert CNs.
-
Verification – confirm no human users depend on client cert auth (any machine with kubectl access)
- Verify no RBAC bindings grant roles to user identities that match certificate CNs:
Manually confirm that any remainingkubectl get clusterrolebindings,rolebindings -A -o yaml \| grep -nE "kind: User" -C3
kind: Usersubjects are service accounts or system identities, not humans. - Optionally, inspect recent authentication logs (API server or cloud audit logs) for
clientCert/X.509-based user entries and confirm they are only system components.
- Verify no RBAC bindings grant roles to user identities that match certificate CNs:
Using kubectl
kubectl cannot remediate this finding because it requires changing the kube-controller-manager static pod manifest on each control plane node at /etc/kubernetes/manifests/kube-controller-manager.yaml. To address it, review and apply the configuration changes described in the Manual Steps section directly on the control plane nodes.
Automation
#!/usr/bin/env bash
#
# Disable client certificate authentication for users on OKE control plane nodes
# by ensuring kube-controller-manager is not configured to issue client certs
# from a dedicated CSR signer. This script is idempotent.
#
# RUN ON: every control plane node (with root privileges)
set -euo pipefail
MANIFEST="/etc/kubernetes/manifests/kube-controller-manager.yaml"
BACKUP_DIR="/etc/kubernetes/manifests/backup-clientcert-auth"
TIMESTAMP="$(date +%Y%m%d-%H%M%S)"
echo "=== [INFO] Ensuring kube-controller-manager manifest exists at ${MANIFEST}"
if [ ! -f "${MANIFEST}" ]; then
echo "ERROR: ${MANIFEST} not found. This script is intended for static pod-based control planes."
exit 1
fi
echo "=== [INFO] Creating backup directory ${BACKUP_DIR} (if not exists)"
mkdir -p "${BACKUP_DIR}"
BACKUP_FILE="${BACKUP_DIR}/kube-controller-manager.yaml.${TIMESTAMP}"
cp "${MANIFEST}" "${BACKUP_FILE}"
echo "=== [INFO] Backup created at ${BACKUP_FILE}"
echo "=== [INFO] Checking for flags related to client certificate issuance in kube-controller-manager manifest"
# We focus on disabling explicit CSR signer configuration that is typically
# used to issue client certificates for users. The most relevant flags are:
# --cluster-signing-cert-file
# --cluster-signing-key-file
# If these are present specifically for user cert issuance, they should be
# removed and an alternate auth mechanism like OIDC should be used instead.
#
# This script removes these flags if present. It is line-based and safe to re-run.
WORK_FILE="${MANIFEST}.tmp.$TIMESTAMP"
cp "${MANIFEST}" "${WORK_FILE}"
remove_flag() {
local flag="$1"
if grep -qE "^\s*- ${flag}(=|\s|$)" "${WORK_FILE}"; then
echo "=== [INFO] Removing flag ${flag} from kube-controller-manager manifest"
# Remove the whole line that contains the flag (line starts with "- ")
# Using sed -i in-place on the temp file
sed -i "/^[[:space:]]*-[[:space:]]*${flag}\(=\\|[[:space:]]\\|$\)/d" "${WORK_FILE}"
else
echo "=== [INFO] Flag ${flag} not present. Nothing to remove."
fi
}
remove_flag "--cluster-signing-cert-file"
remove_flag "--cluster-signing-key-file"
# If nothing changed, avoid touching the live manifest
if cmp -s "${MANIFEST}" "${WORK_FILE}"; then
echo "=== [INFO] No client certificate issuance flags found to remove. No changes applied."
rm -f "${WORK_FILE}"
else
echo "=== [INFO] Updating ${MANIFEST} with sanitized configuration"
mv "${WORK_FILE}" "${MANIFEST}"
echo "=== [INFO] kube-controller-manager static pod manifest updated."
echo "=== [NOTE] Because this is a static pod manifest under /etc/kubernetes/manifests,"
echo " the kube-controller-manager pod will be restarted automatically by kubelet."
fi
echo "=== [VERIFY] Waiting for kube-controller-manager pod to be Ready"
# We assume kubectl is installed and configured on the control plane node.
# If not, run these verification commands from any machine with kubectl access.
RETRIES=30
SLEEP_SECONDS=10
READY=0
for i in $(seq 1 "${RETRIES}"); do
echo "Attempt ${i}/${RETRIES}..."
if command -v kubectl >/dev/null 2>&1; then
# Check that kube-controller-manager pod is Ready
if kubectl get pods -n kube-system -l component=kube-controller-manager \
-o jsonpath='{range .items[*]}{.metadata.name}{" "}{.status.phase}{" "}{range .status.conditions[?(@.type=="Ready")]}{.status}{"\n"}{end}{end}' 2>/dev/null \
| grep -qE "Running True$"; then
READY=1
echo "=== [INFO] kube-controller-manager pod is Running and Ready."
break
fi
else
echo "WARNING: kubectl not found on this node; skipping pod readiness verification."
break
fi
sleep "${SLEEP_SECONDS}"
done
if [ "${READY}" -eq 0 ] && command -v kubectl >/dev/null 2>&1; then
echo "WARNING: kube-controller-manager pod did not report Ready within the expected time."
echo "Please investigate with: kubectl get pods -n kube-system -l component=kube-controller-manager -o wide"
fi
echo "=== [VERIFY] Confirming kube-controller-manager is not configured to issue client certs via cluster-signing flags"
if [ -f "${MANIFEST}" ]; then
echo "--- Current relevant flags in ${MANIFEST} ---"
grep -E "cluster-signing-(cert|key)-file" "${MANIFEST}" || echo "(no cluster-signing cert/key flags present)"
else
echo "ERROR: ${MANIFEST} missing after modification; please restore from backup ${BACKUP_FILE}."
exit 1
fi
echo "=== [DONE] Client certificate issuance flags removed from kube-controller-manager where present."
echo "Ensure alternative user authentication (e.g., OIDC) is configured at the API server and in your clients."