Pod Security Admission (PSA) lets you enforce security standards on your Kubernetes pods without needing to write custom admission controllers.
Here’s a cluster with a restricted namespace and a baseline namespace.
apiVersion: v1
kind: Namespace
metadata:
name: restricted
labels:
pod-security.kubernetes.io/enforce: restricted
pod-security.kubernetes.io/warn: restricted
pod-security.kubernetes.io/audit: restricted
---
apiVersion: v1
kind: Namespace
metadata:
name: baseline
labels:
pod-security.kubernetes.io/enforce: baseline
pod-security.kubernetes.io/warn: baseline
pod-security.kubernetes.io/audit: baseline
When you try to deploy a pod that violates the PSA policy for a namespace, the admission controller rejects it.
Let’s try deploying a privileged pod into the restricted namespace:
apiVersion: v1
kind: Pod
metadata:
name: privileged-pod
namespace: restricted
spec:
containers:
- name: nginx
image: nginx:latest
securityContext:
privileged: true
Running kubectl apply -f privileged-pod.yaml will result in an error:
Error from server (Forbidden): error when creating "privileged-pod.yaml": pods "privileged-pod" is forbidden: violates PodSecurity "restricted": privileged (container "nginx" must not set securityContext.privileged=true)
This error message is actually quite informative. It tells you:
- What broke: The
PodSecurityadmission controller rejected the pod creation. - Why: The pod violates the
restrictedPSA profile. - Where: Specifically, in container
nginx, thesecurityContext.privilegedfield is set totrue, which is not allowed by therestrictedprofile.
Now, let’s try deploying the same pod into the baseline namespace:
apiVersion: v1
kind: Pod
metadata:
name: privileged-pod
namespace: baseline
spec:
containers:
- name: nginx
image: nginx:latest
securityContext:
privileged: true
Running kubectl apply -f privileged-pod.yaml will also result in an error, but slightly different:
Error from server (Forbidden): error when creating "privileged-pod.yaml": pods "privileged-pod" is forbidden: violates PodSecurity "baseline": privileged (container "nginx" must not set securityContext.privileged=true)
The baseline profile also disallows privileged: true, but it’s less restrictive than restricted. The restricted profile has even more stringent requirements.
Let’s try a pod that is allowed by baseline but might be flagged by restricted. Consider a pod that mounts the host’s /var directory.
apiVersion: v1
kind: Pod
metadata:
name: hostpath-pod
namespace: baseline
spec:
containers:
- name: nginx
image: nginx:latest
volumeMounts:
- name: var-volume
mountPath: /host/var
volumes:
- name: var-volume
hostPath:
path: /var
This pod will be allowed in the baseline namespace. However, if you try to deploy it into the restricted namespace:
apiVersion: v1
kind: Pod
metadata:
name: hostpath-pod
namespace: restricted
spec:
containers:
- name: nginx
image: nginx:latest
volumeMounts:
- name: var-volume
mountPath: /host/var
volumes:
- name: var-volume
hostPath:
path: /var
kubectl apply -f hostpath-pod.yaml will yield:
Error from server (Forbidden): error when creating "hostpath-pod.yaml": pods "hostpath-pod" is forbidden: violates PodSecurity "restricted": hostPath volumes must not mount "/var" (container "nginx" volume "var-volume" mount path "/host/var")
This demonstrates how different profiles enforce different levels of security.
Common Causes of Pod Security Admission Violations:
-
Privileged Containers: This is the most frequent offender. Applications that require elevated host privileges (e.g., for kernel module loading, direct hardware access) will fail PSA checks.
- Diagnosis: Check
spec.containers[*].securityContext.privileged. - Fix: Set
spec.containers[*].securityContext.privileged: falseor remove the field if not explicitly required. If elevated privileges are truly necessary, consider a more permissive namespace or a different security mechanism likePodSecurityPolicy(though PSA is the successor) or custom admission controllers. - Why it works: The
privilegedflag bypasses many container security mechanisms, giving the container near-root access to the host. PSAbaselineandrestrictedprofiles disallow this by default.
- Diagnosis: Check
-
HostPath Volumes: Mounting host directories into pods can be a security risk, especially sensitive ones like
/,/etc, or/var.- Diagnosis: Check
spec.volumes[*].hostPathandspec.containers[*].volumeMounts[*].mountPath. - Fix: For
restrictednamespaces, avoidhostPathvolumes entirely. Forbaseline, avoid sensitive paths. If ahostPathis unavoidable and required by arestrictedprofile, you might need to restrict thepathto a non-sensitive subdirectory (e.g.,/var/lib/my-app-datainstead of/var). - Why it works:
hostPathmounts can expose sensitive host files or allow modification of host system files, which is a major security concern.
- Diagnosis: Check
-
Host Network/PID/IPC: Using
hostNetwork: true,hostPID: true, orhostIPC: trueallows pods to directly access the host’s network namespace, process ID space, or inter-process communication, respectively.- Diagnosis: Check
spec.hostNetwork,spec.hostPID,spec.hostIPC. - Fix: Set
hostNetwork: false,hostPID: false,hostIPC: falseand use standard Kubernetes networking and pod communication mechanisms. - Why it works: These settings break container isolation by allowing pods to see and interact with the host’s system resources directly, posing significant security risks.
- Diagnosis: Check
-
Running as Root/Default User: By default, containers often run as the root user. PSA
restrictedandbaselineprofiles encourage or require running as a non-root user.- Diagnosis: Check
spec.containers[*].securityContext.runAsNonRootandspec.containers[*].securityContext.runAsUser. - Fix: For
restrictedandbaseline, setspec.containers[*].securityContext.runAsNonRoot: true. If the application requires a specific user ID, setspec.containers[*].securityContext.runAsUser: <non-root-uid>(e.g.,1001). Ensure your container image is built to support running as a non-root user. - Why it works: Running as root inside a container, even if not privileged on the host, grants extensive permissions within the container’s filesystem and process space, making it easier for a compromise to escalate.
- Diagnosis: Check
-
Capabilities: Containers often inherit a broad set of Linux capabilities. PSA
restrictedandbaselineprofiles limit these capabilities.- Diagnosis: Check
spec.containers[*].securityContext.capabilities. Specifically, look foraddfields that grant excessive capabilities likeSYS_ADMIN. - Fix: For
restrictedandbaseline, remove unnecessary capabilities fromspec.containers[*].securityContext.capabilities.add. Therestrictedprofile often requiresdrop: ["ALL"]and then selectively adding back only the absolute minimum required capabilities. For example,NET_BIND_SERVICEmight be needed for a web server on port 80. - Why it works: Linux capabilities allow fine-grained control over privileges. Granting broad capabilities can expose the system to vulnerabilities that a more tightly scoped set would mitigate.
- Diagnosis: Check
-
SELinux/AppArmor: While not directly enforced by PSA’s default profiles, misconfigurations or overly restrictive policies on the node can lead to pod startup failures. PSA policies can sometimes interact with these.
- Diagnosis: Check node logs (
journalctl -u kubelet) for SELinux or AppArmor denial messages. - Fix: Adjust SELinux/AppArmor profiles on the node, or ensure your PSA configuration doesn’t conflict. This is less common with PSA itself but can be a related issue.
- Why it works: These are mandatory access control systems that further restrict what processes can do. If a pod’s security context (or the PSA policy) implies an action that conflicts with an active SELinux/AppArmor policy, the action is denied.
- Diagnosis: Check node logs (
-
Read-Only Root Filesystem: The
restrictedprofile often enforces a read-only root filesystem for containers.- Diagnosis: Check
spec.containers[*].securityContext.readOnlyRootFilesystem. - Fix: Set
spec.containers[*].securityContext.readOnlyRootFilesystem: true. Ensure your application is designed to write logs or temporary files to specific writable volumes (e.g.,emptyDir). - Why it works: A read-only root filesystem prevents attackers from modifying critical system binaries or libraries within the container, significantly reducing the attack surface.
- Diagnosis: Check
The next thing you’ll likely encounter is understanding how to define custom PSA policies if the built-in restricted, baseline, and privileged profiles don’t meet your specific needs, or how to integrate PSA with other security tools like network policies.