PodSecurity admits the DaemonSet and refuses its pods
Pod Security Admission enforces on Pods, not on workload templates. A privileged DaemonSet is created, reports healthy and never runs a pod, and a server-side dry run of it will not tell you.
Say you apply a DaemonSet that needs privileged, hostNetwork and a
hostPath: a CNI plugin installer, a node agent, anything that touches the
host. The apply succeeds, the GitOps controller reports the Kustomization
Ready, and the DaemonSet exists. No pod ever shows up, though, because every
one of them gets refused at admission.
Enforcement applies to Pods, not to templates
Pod Security Admission’s enforce mode checks Pod objects. If a
Deployment, DaemonSet or StatefulSet has a pod template that violates the
namespace’s level, the workload itself is accepted with at most a warning.
Then the controller tries to create each pod, and those requests are rejected.
From outside, that looks like a healthy workload with zero replicas. The only
symptom I saw was kubectl rollout status hanging until it timed out. The
actual reason is in the DaemonSet’s events; its status doesn’t mention it:
kubectl -n <ns> describe daemonset <name> | sed -n '/Events/,$p'It also means kubectl apply --dry-run=server of the DaemonSet tells you
nothing about privilege. It does run the real admission chain, but for the
DaemonSet, and the DaemonSet isn’t what gets refused. It reports created in
a namespace where every pod it would create is refused. A server-side dry run
has other blind spots too.
Dry-run a bare Pod instead
To find out whether a namespace admits a privileged workload, dry-run the pod that workload would create:
apiVersion: v1
kind: Pod
metadata:
name: psa-probe
namespace: <ns>
spec:
hostNetwork: true
hostPID: true
containers:
- name: probe
image: busybox
securityContext:
privileged: truekubectl apply --dry-run=server -f psa-probe.yamlIf it’s refused, the error names the level and every control it violated. If it’s accepted, the real pods will be admitted too. It takes about a second and creates nothing.
There’s one limit. A dry run can’t validate a resource whose namespace is
created by the same manifest; it fails with namespaces "x" not found,
because the dry run didn’t actually create the namespace. Point the probe at
an existing namespace that carries the same labels.
You cannot read exemption off the namespace
The obvious shortcut is to look at the namespace’s
pod-security.kubernetes.io/* labels. They only tell you part of it, because
the cluster-wide default lives in the API server’s admission configuration,
and exemptions can be configured there as well.
Talos Linux shows this nicely. Its stock admission config sets
enforce: baseline, warn: restricted and audit: restricted for every
namespace, and exempts kube-system. So on such a cluster, kube-system has
no PodSecurity labels at all and still accepts a privileged hostNetwork pod,
while default, just as unlabelled, rejects it. The labels are identical and
the answers are opposite.
On Talos you can read the live policy directly:
talosctl -n <node> get admissioncontrolconfigs -o yamlAnywhere else, the probe pod gives you an answer without your having to know where the policy is configured.
Where this bites in practice
I ran into it installing the macvlan and static CNI plugins onto Talos
nodes with a small DaemonSet in its own namespace. The manifest was correct.
It would have applied cleanly and never run, because baseline rejects all
three of privileged, hostNetwork and hostPath. The fix was a single label
on the namespace:
metadata:
labels:
pod-security.kubernetes.io/enforce: privilegedThe next task was deploying Multus, which also needs host privileges. That one
needed no label, because upstream ships it into kube-system. Since then I
check the namespace of any vendored third-party manifest before deciding
whether it needs anything. The manifest can be perfectly correct either way;
it’s the namespace that decides whether a privileged workload runs at all.