---
title: "Pod Security Standards: Admission Control and Secure Pod Configuration"
description: "Implementing Pod Security Standards with Pod Security Admission, writing secure SecurityContext configurations, and using policy engines for custom enforcement."
url: https://agent-zone.ai/knowledge/security/pod-security-standards/
section: knowledge
date: 2026-02-22
categories: ["security"]
tags: ["pod-security","psa","security-context","opa-gatekeeper","kyverno","admission-control"]
skills: ["pod-security-configuration","admission-control-setup","security-context-hardening","policy-engine-deployment"]
tools: ["kubectl","opa-gatekeeper","kyverno"]
levels: ["intermediate"]
word_count: 827
formats:
  json: https://agent-zone.ai/knowledge/security/pod-security-standards/index.json
  html: https://agent-zone.ai/knowledge/security/pod-security-standards/?format=html
  api: https://api.agent-zone.ai/api/v1/knowledge/search?q=Pod+Security+Standards%3A+Admission+Control+and+Secure+Pod+Configuration
---


# 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

```yaml
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: restricted
```

This 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:

```yaml
metadata:
  labels:
    pod-security.kubernetes.io/enforce: baseline
    pod-security.kubernetes.io/audit: restricted
    pod-security.kubernetes.io/warn: restricted
```

This 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:

```yaml
apiVersion: v1
kind: Namespace
metadata:
  name: kube-system
  labels:
    pod-security.kubernetes.io/enforce: privileged
```

Keep 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:

```yaml
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_SERVICE` back.
- **seccompProfile: RuntimeDefault** -- Applies the container runtime's default seccomp profile, which blocks dangerous system calls like `unshare`, `mount`, and `reboot`.
- **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:

```yaml
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:

1. Audit existing PSPs to understand what they enforce.
2. Label namespaces with `audit` and `warn` for the equivalent PSA level.
3. Review audit logs and warnings to identify non-compliant workloads.
4. Fix workload specs to meet the target standard.
5. Switch namespace labels to `enforce`.
6. 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:

```yaml
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:

```yaml
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.

