Hardening the API server & kubelet
anonymous-auth, authorization-mode, kubelet webhook.
The API server is the single front door to the cluster: every kubectl command, every controller, every kubelet, and every attacker goes through it, and it is the one component that reads and writes etcd. Every request runs the same pipeline — authenticate (who are you), authorize (are you allowed), admission (should this specific write be mutated or rejected), then persist. A handful of flags on that server decide how much of this is actually enforced, and getting them wrong exposes the whole cluster at once.
The non-negotiables: turn off anonymous auth so unauthenticated requests are rejected rather than mapped to system:anonymous; set authorization to Node,RBAC so the real authorizers run (never AlwaysAllow); enable audit logging so there is a record; and switch off profiling, which exposes internals on the debug endpoints. Modern versions already removed the old insecure port, but confirm it. These live in the static pod manifest, so saving the file restarts the API server within seconds.
spec:containers:- command:- kube-apiserver- --anonymous-auth=false- --authorization-mode=Node,RBAC- --profiling=false- --service-account-lookup=true # honor token revocation- --audit-log-path=/var/log/kubernetes/audit.log- --audit-log-maxage=30- --tls-min-version=VersionTLS12
The kubelet is the soft underbelly
Each node runs a kubelet with its own HTTPS API on port 10250, and its historical defaults are the weakest link in a cluster: anonymous access allowed and authorization set to AlwaysAllow means anyone who can reach the port can list pods and exec into containers on that node. Lock it down in the kubelet config file: disable anonymous auth, require the API server’s CA for client certs, switch authorization to Webhook so each call is checked against the API server, and set the read-only port to 0.
apiVersion: kubelet.config.k8s.io/v1beta1kind: KubeletConfigurationauthentication:anonymous:enabled: falsewebhook:enabled: true # bearer tokens checked via the API serverx509:clientCAFile: /etc/kubernetes/pki/ca.crtauthorization:mode: Webhook # not AlwaysAllowreadOnlyPort: 0 # close the unauthenticated :10255 port
Restart the kubelet to apply (systemctl restart kubelet), then prove the lockdown from the node: an unauthenticated call to the kubelet API should now be refused instead of dumping the pod list. That one curl is the difference between “I set the flag” and “the flag works.”
$ sudo systemctl restart kubelet$ curl -sk https://localhost:10250/pods | head -c 40Unauthorized # before hardening this returned the full pod list