Pod Security Standards

Pod Security Standards (PSS) provide a built-in way to enforce security policies on pods at the namespace level. They replace the deprecated PodSecurityPolicy and offer three predefined security profiles: Privileged, Baseline, and Restricted.

What are Pod Security Standards?

Pod Security Standards define three security profiles with increasing levels of restrictions:

  • Privileged - Unrestricted, allows almost everything (for system workloads)
  • Baseline - Minimally restrictive, prevents known privilege escalations
  • Restricted - Highly restrictive, follows current pod security best practices

Think of them as security levels: Privileged is like having admin access, Baseline is like standard user access, and Restricted is like a locked-down kiosk mode.

Security Profiles

Privileged

The unrestricted profile provides the widest range of capabilities. Use only for trusted, system-level workloads.

Allowed:

  • All host namespaces
  • Privileged containers
  • Any volume type
  • Any user/group IDs
  • All capabilities

Baseline

The baseline profile prevents known privilege escalations while maintaining compatibility with most containerized applications.

Restrictions:

  • No host namespaces (hostNetwork, hostIPC, hostPID)
  • No privileged containers
  • No hostPath volumes
  • Must run as non-root (runAsNonRoot: true)
  • No NET_RAW capability

Example - Baseline compliant pod:

apiVersion: v1
kind: Pod
metadata:
  name: baseline-pod
spec:
  securityContext:
    runAsNonRoot: true
    seccompProfile:
      type: RuntimeDefault
  containers:
  - name: app
    image: nginx
    securityContext:
      allowPrivilegeEscalation: false
      capabilities:
        drop:
        - ALL

Restricted

The restricted profile enforces the strongest security settings and requires significant configuration.

Additional restrictions beyond Baseline:

  • Must drop ALL capabilities
  • Must set runAsNonRoot: true
  • Must set seccompProfile.type: RuntimeDefault or Localhost
  • Must set allowPrivilegeEscalation: false
  • Must set readOnlyRootFilesystem: true (if possible)
  • Must set runAsUser to a non-zero value

Example - Restricted compliant pod:

apiVersion: v1
kind: Pod
metadata:
  name: restricted-pod
spec:
  securityContext:
    runAsNonRoot: true
    runAsUser: 1000
    seccompProfile:
      type: RuntimeDefault
  containers:
  - name: app
    image: nginx
    securityContext:
      allowPrivilegeEscalation: false
      readOnlyRootFilesystem: true
      capabilities:
        drop:
        - ALL
    volumeMounts:
    - name: tmp
      mountPath: /tmp
  volumes:
  - name: tmp
    emptyDir: {}

Enforcement Modes

Pod Security Standards can be enforced in three modes:

1. Enforce

Pods that violate the policy are rejected and cannot be created.

apiVersion: v1
kind: Namespace
metadata:
  name: production
  labels:
    pod-security.kubernetes.io/enforce: restricted
    pod-security.kubernetes.io/enforce-version: latest

2. Audit

Violations are logged but pods are allowed. Useful for gradual migration.

apiVersion: v1
kind: Namespace
metadata:
  name: staging
  labels:
    pod-security.kubernetes.io/audit: restricted
    pod-security.kubernetes.io/audit-version: latest

3. Warn

Violations trigger warnings but pods are allowed. Best for development.

apiVersion: v1
kind: Namespace
metadata:
  name: development
  labels:
    pod-security.kubernetes.io/warn: restricted
    pod-security.kubernetes.io/warn-version: latest

Applying Pod Security Standards

At Namespace Level

Set labels on the namespace:

kubectl label namespace production \
  pod-security.kubernetes.io/enforce=restricted \
  pod-security.kubernetes.io/enforce-version=latest \
  pod-security.kubernetes.io/audit=restricted \
  pod-security.kubernetes.io/audit-version=latest \
  pod-security.kubernetes.io/warn=restricted \
  pod-security.kubernetes.io/warn-version=latest

Using Namespace Manifest

apiVersion: v1
kind: Namespace
metadata:
  name: secure-apps
  labels:
    pod-security.kubernetes.io/enforce: restricted
    pod-security.kubernetes.io/enforce-version: v1.28
    pod-security.kubernetes.io/audit: restricted
    pod-security.kubernetes.io/audit-version: v1.28
    pod-security.kubernetes.io/warn: restricted
    pod-security.kubernetes.io/warn-version: v1.28

At Cluster Level

Enable Pod Security Standards admission controller:

# kube-apiserver.yaml
apiVersion: v1
kind: Pod
metadata:
  name: kube-apiserver
spec:
  containers:
  - name: kube-apiserver
    command:
    - kube-apiserver
    - --enable-admission-plugins=PodSecurity
    - --admission-control-config-file=/etc/kubernetes/admission-config.yaml

Create admission config:

# admission-config.yaml
apiVersion: apiserver.config.k8s.io/v1
kind: AdmissionConfiguration
plugins:
- name: PodSecurity
  configuration:
    apiVersion: pod-security.admission.config.k8s.io/v1
    kind: PodSecurityConfiguration
    defaults:
      enforce: "restricted"
      enforce-version: "latest"
      audit: "restricted"
      audit-version: "latest"
      warn: "restricted"
      warn-version: "latest"
    exemptions:
      usernames: []
      runtimeClasses: []
      namespaces: ["kube-system", "kube-public"]

Migration Guide

Step 1: Audit Current State

Enable audit mode to see what would be blocked:

kubectl label namespace production \
  pod-security.kubernetes.io/audit=restricted \
  pod-security.kubernetes.io/audit-version=latest

Step 2: Fix Violations

Update pods to meet the standard. For example, add security context:

apiVersion: v1
kind: Pod
metadata:
  name: my-app
spec:
  securityContext:
    runAsNonRoot: true
    seccompProfile:
      type: RuntimeDefault
  containers:
  - name: app
    image: my-app:latest
    securityContext:
      allowPrivilegeEscalation: false
      capabilities:
        drop:
        - ALL

Step 3: Enable Enforcement

Once all pods are compliant, switch to enforce mode:

kubectl label namespace production \
  pod-security.kubernetes.io/enforce=restricted \
  pod-security.kubernetes.io/enforce-version=latest \
  --overwrite

Common Violations and Fixes

Violation: Running as Root

Error:

runAsRoot: true is not allowed

Fix:

securityContext:
  runAsNonRoot: true
  runAsUser: 1000

Violation: Privileged Container

Error:

privileged: true is not allowed

Fix:

securityContext:
  privileged: false

Violation: Host Network

Error:

hostNetwork: true is not allowed

Fix: Remove hostNetwork: true or use a different networking approach.

Violation: Missing Capabilities Drop

Error:

capabilities must include drop: ["ALL"]

Fix:

securityContext:
  capabilities:
    drop:
    - ALL

Violation: Missing Seccomp Profile

Error:

seccompProfile.type must be RuntimeDefault or Localhost

Fix:

securityContext:
  seccompProfile:
    type: RuntimeDefault

Best Practices

  1. Start with Baseline - Begin with baseline profile, then move to restricted
  2. Use audit mode first - Enable audit mode to identify issues before enforcing
  3. Exempt system namespaces - Exempt kube-system and other system namespaces
  4. Version pinning - Pin to specific PSS version for consistency
  5. Document exceptions - If exemptions are needed, document why
  6. Regular reviews - Periodically review and tighten security standards

Exemptions

You can exempt specific users, namespaces, or runtime classes:

exemptions:
  usernames:
  - "system:serviceaccount:kube-system:my-service"
  namespaces:
  - "kube-system"
  - "kube-public"
  runtimeClasses:
  - "privileged"

Checking Compliance

Check namespace labels:

kubectl get namespace production -o jsonpath='{.metadata.labels}' | jq

Test pod creation:

# Try creating a pod that violates the policy
kubectl run test-pod --image=nginx --restart=Never -n production

# Check for warnings or rejections
kubectl get events -n production --field-selector involvedObject.name=test-pod

List all namespaces with PSS:

kubectl get namespaces -l pod-security.kubernetes.io/enforce

Troubleshooting

Pod rejected with PSS violation:

  1. Check namespace labels for enforcement mode
  2. Review the error message for specific violation
  3. Update pod spec to meet requirements
  4. Test in a namespace with warn mode first

System pods failing:

Exempt system namespaces from enforcement:

exemptions:
  namespaces:
  - "kube-system"
  - "kube-public"
  - "kube-node-lease"

See Also