DevOps Peach

Security (DevSecOps) · Kubernetes security · Lesson 2 of 5

Pod Security Standards and Pod Security Admission

Kubernetes defines three levels, Privileged, Baseline and Restricted; the built-in Pod Security Admission applies them per namespace through labels in enforce, audit or warn mode.

Practice interactively →

The three standard levels

Level Meaning For
Privileged No restrictions System components such as CNI, CSI, monitoring agents
Baseline Blocks known escalations: privileged, hostNetwork, hostPath, dangerous capabilities The minimum for every app
Restricted Adds: non-root required, drop ALL, seccomp, no privilege escalation Ordinary apps; the target to aim for

The old PodSecurityPolicy was removed in Kubernetes 1.25. The built-in replacement is Pod Security Admission (PSA), an integrated admission controller configured with namespace labels.

Three modes

Label On violation
pod-security.kubernetes.io/enforce Reject the Pod
pod-security.kubernetes.io/audit Allow it, record it in the audit log
pod-security.kubernetes.io/warn Allow it, return a warning to the user
apiVersion: v1
kind: Namespace
metadata:
  name: app
  labels:
    pod-security.kubernetes.io/enforce: baseline
    pod-security.kubernetes.io/warn: restricted
    pod-security.kubernetes.io/audit: restricted

This pattern blocks Baseline violations and warns about anything not yet Restricted, so teams can progress step by step.

kubectl label --overwrite ns app pod-security.kubernetes.io/enforce=restricted
kubectl label --dry-run=server --overwrite ns app pod-security.kubernetes.io/enforce=restricted

The second command is a trial: the server warns about running Pods that would violate the level, without changing anything.

flowchart LR
  U["kubectl apply Pod"] --> API["API server"]
  API --> PSA{"Pod Security Admission<br/>namespace labels"}
  PSA -- "violates enforce" --> X["403 Forbidden"]
  PSA -- "violates warn" --> W["Created + Warning"]
  PSA -- "passes" --> OK["Pod created"]

Notes

Traps: enabling enforce: restricted on a busy namespace without trying --dry-run=server; leaving namespaces unlabelled (the default is Privileged); labelling a whole app namespace privileged because one Pod needs it.

Practice interactively →