HackBlue

Tools · Containers and Kubernetes security · 10 / 20

Checking Kubernetes exposure: public API servers, anonymous auth, and kubelet ports

Verify from outside and inside that your API server and kubelets do not accept anonymous requests, and restrict who can reach the control plane.

9 min read · practice only with authorization

The network surfaces

The API server usually listens on 6443 (or 443 on managed services). Each kubelet listens on 10250 for its authenticated API and historically on 10255 for a read-only port. etcd listens on 2379 and should only be reachable by the API server. A dashboard or other admin UI exposed through a LoadBalancer service is another common mistake.

Settings that close them

On the API server, --anonymous-auth=false or tight RBAC for system:anonymous ensures unauthenticated requests get nothing useful. On kubelets, set authentication.anonymous.enabled: false, authorization.mode: Webhook, and readOnlyPort: 0. On managed clusters, enable private endpoints or authorized IP ranges for the API server.

Lab: test your own lab cluster

From a machine allowed to reach your lab cluster, send unauthenticated requests. A hardened API server returns 401 for anonymous requests to most paths, or 403 if anonymous is enabled but has no permissions. /version and /healthz may be readable by design. On the node, check the kubelet configuration and whether 10255 is listening. Only run these checks against clusters you own.

Snippet
curl -sk https://<api-server>:6443/api/v1/namespaces -o /dev/null -w '%{http_code}\n'
curl -sk https://<api-server>:6443/version
curl -sk https://<node-ip>:10250/pods -o /dev/null -w '%{http_code}\n'

kubectl get clusterrolebindings -o json | jq -r '.items[] | select(.subjects[]?.name=="system:anonymous" or .subjects[]?.name=="system:unauthenticated") | .metadata.name'

Interpreting results

A 200 with a namespace list from an unauthenticated request is a critical finding. Any ClusterRoleBinding to system:anonymous or system:unauthenticated beyond the defaults for discovery and health endpoints should be removed. Put the API server behind a private endpoint and use a VPN or bastion with SSO for administrators.

Diagram

Unauthenticated requests to a lab cluster

What good and bad look like.

644310250anonymous

Hardened API server

GET /api/v1/namespaces (no token)
401 Unauthorized

Misconfigured API server

GET /api/v1/namespaces (no token)
200 OK with namespace list

Hardened kubelet

GET https://node:10250/pods (no token)
401 Unauthorized

Check yourself

1. Which port does the kubelet’s authenticated API use?

2. Which kubelet setting disables the legacy read-only port?

3. An unauthenticated request returns a list of namespaces. What is the likely cause?