Pod Security Standards#
Kubernetes Pod Security Standards define three security profiles that control what pods are allowed to do. Pod Security Admission (PSA) enforces these standards at the namespace level. This is the replacement for PodSecurityPolicy, which was removed in Kubernetes 1.25.
The Three Levels#
Privileged – Unrestricted. No security controls applied. Used for system-level workloads like CNI plugins, storage drivers, and logging agents that genuinely need host access.
Baseline – Prevents known privilege escalations. Blocks hostNetwork, hostPID, hostIPC, privileged containers, and most host path mounts. Allows most workloads to run without modification.
Restricted – Maximum security. Requires running as non-root, drops all capabilities, enforces read-only root filesystem, requires seccomp profile, and blocks privilege escalation. This is the target for all application workloads.
Pod Security Admission#
PSA is built into Kubernetes 1.23+ and enabled by default. It works through namespace labels. There are three modes per security level:
- enforce – Rejects pods that violate the standard.
- audit – Allows the pod but logs the violation in the audit log.
- warn – Allows the pod but returns a warning to the user.
Applying Standards to a Namespace#
apiVersion: v1
kind: Namespace
metadata:
name: production
labels:
pod-security.kubernetes.io/enforce: restricted
pod-security.kubernetes.io/enforce-version: latest
pod-security.kubernetes.io/audit: restricted
pod-security.kubernetes.io/warn: restrictedThis enforces the restricted standard in the production namespace. Any pod that does not meet the restricted requirements is rejected on creation.
For a staged rollout, start with audit and warn before enforcing:
metadata:
labels:
pod-security.kubernetes.io/enforce: baseline
pod-security.kubernetes.io/audit: restricted
pod-security.kubernetes.io/warn: restrictedThis enforces baseline (blocking the most dangerous configurations) while logging and warning about restricted violations. Once all workloads pass restricted, switch enforce to restricted.
Exemptions#
Some namespaces need privileged access. Label them explicitly:
apiVersion: v1
kind: Namespace
metadata:
name: kube-system
labels:
pod-security.kubernetes.io/enforce: privilegedKeep the list of privileged namespaces as small as possible: kube-system, ingress-nginx, istio-system, and similar infrastructure namespaces.
Writing Secure Pod Specs#
A pod that passes the restricted standard looks like this:
apiVersion: v1
kind: Pod
metadata:
name: secure-app
namespace: production
spec:
automountServiceAccountToken: false
securityContext:
runAsNonRoot: true
runAsUser: 1000
runAsGroup: 1000
fsGroup: 1000
seccompProfile:
type: RuntimeDefault
containers:
- name: app
image: myapp:1.0.0
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop:
- ALL
seccompProfile:
type: RuntimeDefault
volumeMounts:
- name: tmp
mountPath: /tmp
volumes:
- name: tmp
emptyDir: {}Key settings explained:
- runAsNonRoot: true – The container must run as a non-root user. If the container image has
USER root, the pod will fail to start. - readOnlyRootFilesystem: true – The container filesystem is read-only. Applications that need to write temporary files must use an emptyDir volume mounted at the write path.
- allowPrivilegeEscalation: false – Prevents a process from gaining more privileges than its parent. This blocks setuid binaries and other escalation vectors.
- capabilities.drop: ALL – Removes all Linux capabilities. Most applications do not need any. If your application needs to bind to a port below 1024, add
NET_BIND_SERVICEback. - seccompProfile: RuntimeDefault – Applies the container runtime’s default seccomp profile, which blocks dangerous system calls like
unshare,mount, andreboot. - automountServiceAccountToken: false – Prevents the service account token from being mounted in the pod. Most application pods do not need Kubernetes API access.
Common Violations and Fixes#
“container must not set runAsUser to 0” – The container image runs as root. Add USER 1000 to the Dockerfile, or set runAsUser: 1000 in the pod spec.
“container must set readOnlyRootFilesystem to true” – Add readOnlyRootFilesystem: true and mount emptyDir volumes for write paths like /tmp, /var/cache, or /var/run.
“container must drop ALL capabilities” – Add capabilities: {drop: [ALL]}. If the application needs specific capabilities, add only the minimum required:
securityContext:
capabilities:
drop:
- ALL
add:
- NET_BIND_SERVICE“container must set seccompProfile” – Add seccompProfile: {type: RuntimeDefault} to the container’s securityContext.
Migrating from PodSecurityPolicy#
PodSecurityPolicy (PSP) was removed in Kubernetes 1.25. Migration to PSA:
- Audit existing PSPs to understand what they enforce.
- Label namespaces with
auditandwarnfor the equivalent PSA level. - Review audit logs and warnings to identify non-compliant workloads.
- Fix workload specs to meet the target standard.
- Switch namespace labels to
enforce. - Remove PSP resources and the PSP admission controller flag.
OPA Gatekeeper and Kyverno#
PSA covers the common cases but does not support custom policies. For requirements like “all images must come from our private registry” or “all pods must have resource limits,” use a policy engine.
Kyverno#
Kyverno uses Kubernetes-native YAML for policies:
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: require-image-registry
spec:
validationFailureAction: Enforce
rules:
- name: check-registry
match:
any:
- resources:
kinds:
- Pod
validate:
message: "Images must come from registry.example.com"
pattern:
spec:
containers:
- image: "registry.example.com/*"OPA Gatekeeper#
Gatekeeper uses Rego policies, which are more powerful but harder to write:
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sRequiredLabels
metadata:
name: require-team-label
spec:
match:
kinds:
- apiGroups: [""]
kinds: ["Namespace"]
parameters:
labels:
- key: team
allowedRegex: "^[a-z]+-[a-z]+$"Choose Kyverno for straightforward policies that fit YAML patterns. Choose Gatekeeper when you need complex logic, external data, or mutation policies that Kyverno cannot express.
Both tools complement PSA rather than replacing it. Use PSA for the baseline security posture and a policy engine for organization-specific requirements.