Skip to main content

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

Manual Steps
  1. 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 || true
    openssl x509 -in /etc/kubernetes/pki/apiserver-kubelet-client.crt -text -noout 2>/dev/null || true

    Review which subjects/common names correspond to human users vs system components.

  2. 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.

  3. 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/v1
      kind: ClusterRoleBinding
      metadata:
      name: oidc-admins
      subjects:
      - kind: Group
      name: oidc-k8s-admins # matches OIDC group claim
      apiGroup: rbac.authorization.k8s.io
      roleRef:
      kind: ClusterRole
      name: cluster-admin
      apiGroup: rbac.authorization.k8s.io
      EOF

    Confirm at least one non-certificate auth path exists for each admin/support persona.

  4. 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 exec auth).
      • Remove or comment out client-certificate and client-key entries for that user’s context once OIDC login is confirmed working.
    • 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.
  5. Harden API server configuration to restrict client certificate usage (control plane node)
    On OKE, the API server manifest /etc/kubernetes/manifests/kube-controller-manager.yaml is 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.
  6. 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:
      kubectl get clusterrolebindings,rolebindings -A -o yaml \
      | grep -nE "kind: User" -C3
      Manually confirm that any remaining kind: User subjects 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.
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."