Skip to main content
cd ../config-traps
risk/register/kubernetes-etcd-on-2379-no-client-cert-no-cluster.html
Kubernetes Cluster Securitycritical severityKubernetes etcd

Kubernetes etcd on 2379: No Client Cert, No Cluster

Overview

etcd's client port 2379 accepts unauthenticated connections when -client-cert-auth is false or unset, leaving only transport encryption in place. Anyone reaching the port dumps every Secret, ServiceAccount token, and RBAC object, then writes forged ClusterRoleBindings directly into the Raft log, bypassing the API server and audit trail entirely.

At a glance

Unsafe setting
etcd client listener without --client-cert-auth=true, bound to 0.0.0.0 instead of a scoped private interface
Failure trigger
Broad security group or flat pod network reaches port 2379, allowing unauthenticated etcdctl calls against the datastore
Blast radius
Full Secret and RBAC exfiltration plus forged ClusterRoleBindings written straight into Raft, invisible to API server audit logs.
Recommended control
Enable --client-cert-auth and --peer-client-cert-auth, bind listeners to private interfaces, restrict CIDR, and enable EncryptionConfiguration for Secrets.

The Trap

etcd exposed on port 2379 without –client-cert-auth enabled, meaning transport is TLS-encrypted but not mutually authenticated, so any TCP connection reaching the listener can issue raw etcd API calls against the cluster’s backing store.

The Default State

kubeadm-bootstrapped clusters generate etcd certificates automatically, but self-managed installs using bare binaries, kops, kubespray templates, or bring-your-own-etcd Helm charts frequently leave –client-cert-auth=false or omit the flag, relying only on –cert-file and –key-file for encryption without mutual auth. Because –listen-client-urls defaults to binding all interfaces rather than the node’s private address, and cloud security groups are often opened for a broad “control plane” CIDR rather than scoped to the three API server IPs, the client port ends up reachable from the pod network, and sometimes from the VPC at large.

The Blast Radius

An attacker with reach to 2379 runs etcdctl –insecure-skip-tls-verify get / –prefix –keys-only and enumerates every key in the cluster: Secrets in base64 (unencrypted by default without an EncryptionConfiguration resource), ServiceAccount tokens, TLS private keys stored as Secrets, and the full RBAC graph. From there they mint a kubeconfig from a captured cluster-admin-bound token, or write directly into etcd with etcdctl put to fabricate a ClusterRoleBinding, bypassing the API server’s admission chain and audit log entirely. Writes land straight in the Raft log, and kube-apiserver picks up the forged object on its next watch cycle with no webhook check, no PodSecurity evaluation, and no entry beyond etcd’s own logs, which most SOC pipelines never ingest.

The Lead Mechanic Fix

Enforce mutual TLS on every etcd member: set –client-cert-auth=true, –trusted-ca-file, –cert-file, –key-file for the client listener, and –peer-client-cert-auth=true with matching peer flags for inter-member traffic. Bind –listen-client-urls to the node’s private interface only, never 0.0.0.0, and restrict the security group or NetworkPolicy to the exact API server IPs on 2379 and 2380. Apply an EncryptionConfiguration using aescbc or a KMS provider so Secrets are encrypted at the API server layer as a second control, and rotate the etcd CA if any exposure window existed. Confirm with etcdctl –insecure-skip-tls-verify endpoint health run from outside the allowed CIDR; it must fail closed with connection refused, not a TLS handshake.