CKSKubernetes 1.35

Lessons

Restricting access to the API server

updated 2026-10-22 by seed

The request path, in one breath

TLS → authentication (client cert, SA token, OIDC, webhook) → authorisation
(Node, RBAC, webhook) → admission (mutating, then validating: PSA, Gatekeeper,
ImagePolicyWebhook) → etcd.

Every "restrict access to the API" task is a flag on one of those stages.

The flags, in the static pod manifest

/etc/kubernetes/manifests/kube-apiserver.yaml:

spec:
  containers:
  - command:
    - kube-apiserver
    - --advertise-address=10.0.0.10
    - --authorization-mode=Node,RBAC      # never AlwaysAllow
    - --enable-admission-plugins=NodeRestriction
    - --anonymous-auth=false              # default is true
    - --insecure-port=0                   # gone in modern versions, still asked
    - --profiling=false
    - --audit-policy-file=/etc/kubernetes/audit/policy.yaml
    - --audit-log-path=/var/log/kubernetes/audit.log
    - --tls-min-version=VersionTLS12

Why NodeRestriction: without it, the kubelet credential of any node can
modify Pods on every node. With it, a kubelet only touches its own Node
object and the Pods bound to it.

Bound the address too

    - --bind-address=127.0.0.1     # only if nothing off-node needs the API

More common in the exam: a firewall rule or a NetworkPolicy on the
kube-system namespace so only the control plane can reach 6443. Check what is
actually listening with ss -ltnp | grep 6443 on the node.

Restart the static pod, then prove it

sudo cp /etc/kubernetes/manifests/kube-apiserver.yaml /root/apiserver.bak
sudo vi /etc/kubernetes/manifests/kube-apiserver.yaml
sleep 20 && kubectl get --raw /healthz
sudo crictl ps | grep kube-apiserver

When you break it (and you will)

The API server is a static pod. If the manifest is invalid, the kubelet keeps
trying, the container never starts, and kubectl is dead. Recovery:

sudo crictl ps -a | grep kube-apiserver     # CrashLoopBackOff / Exited
sudo crictl logs <container-id>            # the actual parse or flag error
sudo journalctl -u kubelet -n 100 --no-pager | grep -i apiserver
sudo vi /etc/kubernetes/manifests/kube-apiserver.yaml   # fix the YAML

Then wait. The kubelet retries within ~20 seconds. Do not reboot, do not
reinstall, do not delete the manifest (that removes the control plane).

Admission plugins worth knowing

  • NodeRestriction — mandatory in practice
  • PodSecurity — enforces PSA namespace labels (built in since 1.25)
  • ImagePolicyWebhook — registry/image allow-listing (see supply chain)
  • AlwaysPullImages — forces a registry check on every start
  • LimitRanger, ResourceQuota — resource abuse
  • ServiceAccount — the one that mounts tokens; on by default

Enabling a plugin means a flag change and a restart. --disable-admission-plugins
is how you remove one that breaks the cluster.

kubelet-side restriction

# /var/lib/kubelet/config.yaml
authentication:
  anonymous: {enabled: false}
  webhook: {enabled: true}
authorization:
  mode: Webhook
readOnlyPort: 0
systemctl restart kubelet
curl -sk https://localhost:10250/pods    # 401/403 now, not pod JSON