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:

  1. What broke: The PodSecurity admission controller rejected the pod creation.
  2. Why: The pod violates the restricted PSA profile.
  3. Where: Specifically, in container nginx, the securityContext.privileged field is set to true, which is not allowed by the restricted profile.

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:

  1. 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: false or remove the field if not explicitly required. If elevated privileges are truly necessary, consider a more permissive namespace or a different security mechanism like PodSecurityPolicy (though PSA is the successor) or custom admission controllers.
    • Why it works: The privileged flag bypasses many container security mechanisms, giving the container near-root access to the host. PSA baseline and restricted profiles disallow this by default.
  2. HostPath Volumes: Mounting host directories into pods can be a security risk, especially sensitive ones like /, /etc, or /var.

    • Diagnosis: Check spec.volumes[*].hostPath and spec.containers[*].volumeMounts[*].mountPath.
    • Fix: For restricted namespaces, avoid hostPath volumes entirely. For baseline, avoid sensitive paths. If a hostPath is unavoidable and required by a restricted profile, you might need to restrict the path to a non-sensitive subdirectory (e.g., /var/lib/my-app-data instead of /var).
    • Why it works: hostPath mounts can expose sensitive host files or allow modification of host system files, which is a major security concern.
  3. Host Network/PID/IPC: Using hostNetwork: true, hostPID: true, or hostIPC: true allows 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: false and 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.
  4. Running as Root/Default User: By default, containers often run as the root user. PSA restricted and baseline profiles encourage or require running as a non-root user.

    • Diagnosis: Check spec.containers[*].securityContext.runAsNonRoot and spec.containers[*].securityContext.runAsUser.
    • Fix: For restricted and baseline, set spec.containers[*].securityContext.runAsNonRoot: true. If the application requires a specific user ID, set spec.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.
  5. Capabilities: Containers often inherit a broad set of Linux capabilities. PSA restricted and baseline profiles limit these capabilities.

    • Diagnosis: Check spec.containers[*].securityContext.capabilities. Specifically, look for add fields that grant excessive capabilities like SYS_ADMIN.
    • Fix: For restricted and baseline, remove unnecessary capabilities from spec.containers[*].securityContext.capabilities.add. The restricted profile often requires drop: ["ALL"] and then selectively adding back only the absolute minimum required capabilities. For example, NET_BIND_SERVICE might 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.
  6. 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.
  7. Read-Only Root Filesystem: The restricted profile 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.

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.

Want structured learning?

Take the full Aks course →