Restrict privileged Kubernetes containers
spec: securityContext: runAsNonRoot: true runAsUser: 10001 seccompProfile: { type: RuntimeDefault } containers: - name: app securityContext: allowPrivilegeEscalation: false privileged: false readOnlyRootFilesystem: true capabilities: drop: ["ALL"]Enforcing this by hand on every pod is error-prone; let
Pod Security Admission require it and use
this as the spec that passes restricted.
Why it matters
Section titled “Why it matters”A container is isolated from its node by namespaces, capabilities, seccomp and the user it runs as — the same mechanisms as Docker, because it’s the same runtime underneath. A pod that requests its way out of those is a route to the node, and a node is every pod scheduled on it plus a kubelet credential.
The dangerous requests, worst first:
privileged: true— disables the isolation entirely, exposes host devices. The container can mount the host disk. This is the Docker--privilegedproblem with a Kubernetes wrapper.hostPID,hostNetwork,hostIPC— share the node’s process, network or IPC namespace.hostNetworkputs the pod on the node’s network, past network policies and onto the metadata service.hostPathvolumes — mount a node directory into the pod.hostPath: /is the node’s filesystem; even/var/run/docker.sockor the kubelet’s directory is a route to node control.- Running as root (
runAsUser: 0, the default) — a container escape lands as root on the node instead of an unprivileged user. allowPrivilegeEscalation: true(the default) — a setuid binary inside the container can re-elevate even after you’ve dropped to a non-root user.
The hardened securityContext, line by line
Section titled “The hardened securityContext, line by line”| Setting | Effect |
|---|---|
runAsNonRoot: true |
Refuse to start if the image runs as root |
runAsUser: 10001 |
A specific unprivileged UID |
allowPrivilegeEscalation: false |
No re-elevation via setuid (no_new_privs) |
readOnlyRootFilesystem: true |
Can’t write a payload into the container |
capabilities.drop: ["ALL"] |
Start from zero, add back only what’s needed |
seccompProfile.type: RuntimeDefault |
Block the syscalls the runtime’s profile blocks |
RuntimeDefault seccomp is worth calling out — Kubernetes does not apply a
seccomp profile by default (historically Unconfined), so unless you set this, the
container can make every syscall. It’s one line and it closes a lot.
Pod vs container level
Section titled “Pod vs container level”Some settings live on the pod securityContext (runAsNonRoot, runAsUser,
seccompProfile, fsGroup), some on the container (allowPrivilegeEscalation,
readOnlyRootFilesystem, capabilities, privileged). Container-level overrides
pod-level. Set both, and don’t assume a pod-level runAsNonRoot covers a container
that sets its own.
Related
Section titled “Related”- Pod Security Admission — enforce this automatically instead of per-pod.
- Network policies — contain a pod that is compromised anyway.