Security
11 min readMay 1, 2026

Kubernetes Pod Security Standards: Replacing PodSecurityPolicy

Pod Security Standards (PSS) replaced PodSecurityPolicy (PSP) in Kubernetes 1.25. Three predefined security profiles — Privileged, Baseline, and Restricted — are enforced via the built-in Pod Security Admission controller, namespace labels, and optionally Kyverno or OPA Gatekeeper for custom policies. This covers the migration from PSP, the production enforcement patterns, and the security contexts that satisfy the Restricted profile without breaking your workloads.

AJ
Ajeet Yadav
Platform & Cloud Engineer
Kubernetes Pod Security Standards: Replacing PodSecurityPolicy

PodSecurityPolicy (PSP) was deprecated in Kubernetes 1.21 and removed in 1.25. Pod Security Standards (PSS) replaced it with three predefined profiles enforced by a built-in admission controller. The profiles are intentionally opinionated — they cover the security posture of most workloads without requiring custom policy authoring.

For policies beyond what PSS profiles express (requiring specific images, enforcing labels, blocking specific capabilities), pair PSS with Kyverno or OPA Gatekeeper.


The Three Profiles

ProfileUse caseWhat it prevents
PrivilegedSystem-level components (CNI plugins, storage drivers, monitoring agents)Nothing — fully permissive
BaselineMost workloads; blocks known dangerous configurationshostNetwork, hostPID, hostIPC, hostPorts, privileged containers, NET_RAW and other non-default capabilities, unsafe sysctls, custom /proc mounts. hostPath volumes and allowPrivilegeEscalation are NOT restricted by Baseline — that is a Restricted-only requirement. Use the Restricted profile or Kyverno/OPA to enforce them.
RestrictedSecurity-sensitive workloads; highest securityEverything in Baseline + requires runAsNonRoot: true, requires allowPrivilegeEscalation: false, drops all capabilities, requires seccompProfile.type: RuntimeDefault or Localhost, restricts volumes to configMap, secret, projected, downwardAPI, emptyDir, csi, ephemeral, persistentVolumeClaim

The Restricted profile is the target for production workloads that don't need node-level access.


Enforcing PSS via Namespace Labels

PSS is enforced by labeling namespaces. Three modes per profile:

  • enforce — reject pods that violate the profile
  • audit — allow pods but log violations to the audit log
  • warn — allow pods but return a warning to the kubectl caller
bash
1# Set Restricted enforcement on the payments namespace
2kubectl label namespace payments \
3  pod-security.kubernetes.io/enforce=restricted \
4  pod-security.kubernetes.io/enforce-version=latest \
5  pod-security.kubernetes.io/audit=restricted \
6  pod-security.kubernetes.io/audit-version=latest \
7  pod-security.kubernetes.io/warn=restricted \
8  pod-security.kubernetes.io/warn-version=latest

Pinning enforce-version=latest uses the current Kubernetes version's definition of Restricted. Use a specific version (e.g., v1.29) to avoid surprise enforcement changes across Kubernetes upgrades.


Security Context: Satisfying the Restricted Profile

A pod that satisfies the Restricted profile:

yaml
1apiVersion: apps/v1
2kind: Deployment
3metadata:
4  name: payments-api
5  namespace: payments
6spec:
7  template:
8    spec:
9      # Pod-level security context
10      securityContext:
11        runAsNonRoot: true          # Required by Restricted
12        runAsUser: 1000             # UID for the process; must be non-zero
13        runAsGroup: 1000
14        fsGroup: 1000               # Files in mounted volumes owned by this GID
15        seccompProfile:
16          type: RuntimeDefault      # Required by Restricted — RuntimeDefault or Localhost are both accepted
17
18      containers:
19        - name: payments-api
20          image: payments-api:1.2.0
21          # Container-level security context
22          securityContext:
23            allowPrivilegeEscalation: false    # Required by Restricted
24            readOnlyRootFilesystem: true        # Best practice; NOT required by PSS Restricted — enforce via Kyverno if needed
25            capabilities:
26              drop:
27                - ALL                           # Required by Restricted — drop all Linux capabilities
28              add: []                           # Add back only what you need (NET_BIND_SERVICE for port < 1024)
29
30          # If the container needs to write files, use emptyDir volumes
31          volumeMounts:
32            - name: tmp
33              mountPath: /tmp
34            - name: cache
35              mountPath: /app/cache
36
37      volumes:
38        - name: tmp
39          emptyDir: {}
40        - name: cache
41          emptyDir: {}

Common capabilities to add back when needed:

CapabilityNeeded for
NET_BIND_SERVICEBinding to ports < 1024 (HTTP on port 80/443)
NET_RAWRaw socket access (ping, some network tools)
CHOWNChanging file ownership (avoid — run as correct UID instead)

For most web services: drop ALL, add nothing. Run the application on port 8080 instead of 80.


Cluster-Wide Defaults

Set a cluster-wide default policy in the Pod Security Admission configuration. This applies to namespaces that don't have explicit PSS labels:

yaml
1# /etc/kubernetes/psa.yaml — passed to the API server via --admission-control-config-file
2apiVersion: apiserver.config.k8s.io/v1
3kind: AdmissionConfiguration
4plugins:
5  - name: PodSecurity
6    configuration:
7      apiVersion: pod-security.admission.config.k8s.io/v1
8      kind: PodSecurityConfiguration
9      defaults:
10        enforce: baseline          # Default enforcement for unlabeled namespaces
11        enforce-version: latest
12        audit: restricted
13        audit-version: latest
14        warn: restricted
15        warn-version: latest
16      exemptions:
17        # Exempt system namespaces from enforcement
18        namespaces:
19          - kube-system
20          - kube-public
21          - monitoring
22          - cert-manager

On EKS, you can't pass custom API server flags. Use namespace labels directly, and rely on Kyverno or OPA Gatekeeper to enforce organization-wide baselines across all namespaces.


Audit Mode First: Finding Violations Before Enforcing

Before switching to enforce, run audit and warn to find violations:

bash
1# Add audit and warn labels without enforcing — see what would fail
2kubectl label namespace payments \
3  pod-security.kubernetes.io/audit=restricted \
4  pod-security.kubernetes.io/warn=restricted
5
6# Look at audit logs for violations
7# On EKS (CloudWatch Logs):
8aws logs filter-log-events \
9  --log-group-name "/aws/eks/production/cluster" \
10  --filter-pattern '{ $.annotations["pod-security.kubernetes.io/audit-violations"] = "*" }'
11
12# Locally — kubectl apply will print warnings for non-compliant pods:
13# Warning: would violate PodSecurity "restricted:latest": ...

Switch to enforce only after resolving all violations. Common violations to fix before enforcing Restricted:

  • Running as root (UID 0) — fix: add runAsNonRoot: true, set a non-zero runAsUser
  • Missing seccompProfile — fix: add seccompProfile.type: RuntimeDefault
  • allowPrivilegeEscalation not set to false — fix: add allowPrivilegeEscalation: false
  • Capabilities not dropped — fix: add capabilities.drop: [ALL]

Helm Chart Compatibility

Many Helm charts ship with security contexts that don't satisfy Restricted. The kube-prometheus-stack, for example, uses hostNetwork: true for some components and requires the monitoring namespace to be Privileged or Baseline. Check the chart's documentation for the minimum PSS profile it supports.

For charts you control, add securityContext values to the values.yaml defaults:

yaml
1# values.yaml defaults that satisfy Restricted
2podSecurityContext:
3  runAsNonRoot: true
4  runAsUser: 1000
5  fsGroup: 1000
6  seccompProfile:
7    type: RuntimeDefault
8
9securityContext:
10  allowPrivilegeEscalation: false
11  readOnlyRootFilesystem: true
12  capabilities:
13    drop:
14      - ALL

Kyverno for Policies Beyond PSS Profiles

PSS profiles cover privilege escalation. For policies that PSS doesn't express — requiring specific image registries, enforcing resource limits on all pods, blocking latest tags — use Kyverno:

yaml
1# Block pods that don't set CPU/memory limits (PSS doesn't require this)
2apiVersion: kyverno.io/v1
3kind: ClusterPolicy
4metadata:
5  name: require-resource-limits
6spec:
7  validationFailureAction: Enforce
8  rules:
9    - name: check-cpu-memory-limits
10      match:
11        any:
12          - resources:
13              kinds: [Pod]
14      exclude:
15        any:
16          - resources:
17              kinds: [Pod]
18              namespaces: ["kube-system", "kube-public", "kube-node-lease"]
19      validate:
20        message: "CPU and memory limits are required on all containers"
21        pattern:
22          spec:
23            containers:
24              - resources:
25                  limits:
26                    cpu: "?*"
27                    memory: "?*"

PSS handles the security posture (privilege escalation, host access); Kyverno handles operational policies (resource limits, image registries, required labels). They complement each other.


Secure by Default: Kyverno for PSS Mutation

In 2026, rather than blocking non-compliant pods, platform teams use Kyverno to mutate them into compliance. This "secure by default" pattern automatically injects the required securityContext fields, allowing developers to focus on code rather than security YAML:

yaml
1apiVersion: kyverno.io/v1
2kind: ClusterPolicy
3metadata:
4  name: mutate-pss-restricted
5spec:
6  rules:
7    - name: set-restricted-context
8      match:
9        any:
10          - resources:
11              kinds: [Pod]
12              namespaces: ["payments", "orders"]
13      mutate:
14        patchStrategicMerge:
15          spec:
16            securityContext:
17              runAsNonRoot: true
18              seccompProfile:
19                type: RuntimeDefault
20            containers:
21              - name: "*"
22                securityContext:
23                  allowPrivilegeEscalation: false
24                  capabilities:
25                    drop: [ALL]

By mutating pods to meet the Restricted profile, you enforce a high security baseline across the cluster without requiring every team to update their Helm charts manually.


Frequently Asked Questions

What's the difference between seccompProfile.type: RuntimeDefault and Localhost?

RuntimeDefault uses the container runtime's built-in seccomp profile (the one Docker/containerd ships with). It blocks ~300 system calls not needed by typical applications. Localhost points to a custom seccomp profile file on the node. RuntimeDefault is the right starting point for most workloads — it satisfies the Restricted profile requirement and reduces syscall attack surface without requiring custom profiles.

Does PSS enforce security contexts on init containers?

Yes. The Pod Security admission controller checks init containers, ephemeral containers, and regular containers — all must satisfy the profile's requirements. A pod with a compliant main container but a non-compliant init container will be rejected.

How do I handle a third-party container that runs as root?

Three options: (1) Add runAsUser to override the UID at the pod/container level — works if the container doesn't require root privileges for its actual function, just was built with root as default. (2) Use the Baseline profile for that namespace instead of Restricted. (3) Run the container in a dedicated namespace with a more permissive profile while enforcing Restricted everywhere else. Option 1 is best when feasible — check that the application actually works as non-root.


For admission webhook policies that complement PSS (enforcing image registries, blocking privileged namespaces), see Kubernetes Admission Webhooks: OPA Gatekeeper and Kyverno. For RBAC policies that limit who can create privileged pods, see Kubernetes RBAC Advanced Patterns. For supply chain security and image signing that pairs with PSS enforcement, see Container Image Security: Supply Chain from Build to Production.

Migrating from PodSecurityPolicy to PSS or enforcing the Restricted profile across a multi-team cluster? Talk to us at Coding Protocols — we help platform teams implement security baselines that don't break running workloads.

Related Topics

Kubernetes
Pod Security
PSS
Security Context
Platform Engineering
EKS
Security
Kyverno

Read Next