Skip to main content
RunBook Academy

KubernetesLXII · Pod Security StandardsPod Security Standards

Restricted profile — minimum-allow for production

Advanced⏱ ~13 minkubectl

What you'll learn

  • Identify every field required by `restricted`
  • Write a manifest that passes `restricted`
  • Use the namespace label to enforce `restricted` cluster-wide
  • Recognise the failure modes (over-restriction, system workloads)

Prerequisites

Verified against Kubernetes 1.34.x · kubeadm 1.34.x · kubectl 1.34.x · etcd 3.6.x · CoreDNS 1.11.x · containerd 1.7.x / 2.x · 2026-08-16

Not yet marked complete on this device.

The restricted profile is the production target for Kubernetes workloads. It is the strictest profile and forbids every common privilege escalation path. Workloads that pass restricted are hardened to the operating-system level: they run as non-root, drop every Linux capability, cannot escalate privilege, and carry a constrained seccomp profile.

What restricted requires

FieldRequiredRationale
securityContext.runAsNonRoot: trueRequiredPrevent root execution
securityContext.runAsUser (non-zero)Required if runAsNonRoot is not setSame
securityContext.allowPrivilegeEscalation: falseRequiredPrevent setuid binaries from escalating
securityContext.capabilities.drop: ["ALL"]RequiredDefault-deny capabilities
securityContext.capabilities.addLimited (only NET_BIND_SERVICE)Allow network binding for non-root
securityContext.seccompProfile.typeRuntimeDefault or LocalhostConstrain syscalls
Volume typesOnly configMap, csi, downwardAPI, emptyDir, ephemeral, persistentVolumeClaim, projected, secretKeep the Pod off host storage
hostNetwork, hostPID, hostIPCForbiddenPrevent host namespace sharing
hostPath volumesForbiddenPrevent host filesystem access
securityContext.privilegedForbiddenPrevent privilege escalation

A workload that meets every requirement is hardened to the OS level: the kernel’s capability set is constrained, the syscall surface is constrained, and the Pod cannot reach the host.

A restricted manifest

apiVersion: v1
kind: Pod
metadata:
  name: api
  namespace: prod
spec:
  securityContext:
    runAsNonRoot: true
    runAsUser: 1000
    runAsGroup: 1000
    fsGroup: 1000
    seccompProfile:
      type: RuntimeDefault
  containers:
  - name: api
    image: myapp:v1.0
    securityContext:
      allowPrivilegeEscalation: false
      readOnlyRootFilesystem: true
      capabilities:
        drop: ["ALL"]
    volumeMounts:
    - name: tmp
      mountPath: /tmp
    - name: cache
      mountPath: /var/cache/myapp
    resources:
      requests:
        cpu: 100m
        memory: 128Mi
      limits:
        cpu: 500m
        memory: 512Mi
    livenessProbe:
      httpGet:
        path: /healthz
        port: 8080
      initialDelaySeconds: 5
      periodSeconds: 10
    readinessProbe:
      httpGet:
        path: /readyz
        port: 8080
      initialDelaySeconds: 2
      periodSeconds: 5
  volumes:
  - name: tmp
    emptyDir: {}
  - name: cache
    emptyDir: {}

Every field meets the restricted profile:

  • runAsNonRoot: true, runAsUser: 1000 — runs as a non-root user.
  • allowPrivilegeEscalation: false — setuid binaries cannot escalate.
  • capabilities.drop: ["ALL"] — no capabilities; the workload can do nothing privileged.
  • seccompProfile.type: RuntimeDefault — constrained to the runtime’s default seccomp profile.
  • Only emptyDir volumes — one of the eight types restricted permits.
  • No host namespaces, no hostPath, no privileged.

The manifest also sets readOnlyRootFilesystem: true and mounts emptyDir at /tmp and /var/cache/myapp so the application can still write. That is hardening the team chose, not something restricted demanded; the Pod would pass the profile without it.

flowchart LR
    A[restricted Pod] --> B[runAsNonRoot, runAsUser not 0]
    A --> C[Allowed volume types only]
    A --> D[drop ALL capabilities]
    A --> E[seccomp RuntimeDefault]
    A --> F[allowPrivilegeEscalation: false]
    A --> G[No hostNetwork/PID/IPC]
    A --> H[No hostPath]
    A --> I[No privileged]

The namespace label

apiVersion: v1
kind: Namespace
metadata:
  name: prod
  labels:
    pod-security.kubernetes.io/enforce: restricted
    pod-security.kubernetes.io/audit: restricted
    pod-security.kubernetes.io/warn: restricted

A Pod in prod that violates any field is rejected. The audit log captures the violation; the warning event surfaces it in kubectl get events.

Over-restriction and exceptions

Some workloads cannot meet restricted:

  • System workloads. kube-proxy, CNI agents, CSI drivers need host access. They run in kube-system with enforce: privileged.
  • Log shippers. Some log shippers need to read /var/log on the host. They run in dedicated namespaces with enforce: baseline or privileged.
  • Debug tools. kubectl debug node/<node> needs host access. The debug Pod is in default (or a dedicated debug namespace with enforce: privileged).

The discipline is to default to restricted and document every exception.

Production patterns

  1. restricted cluster-wide for production namespaces. Every production namespace has enforce: restricted.
  2. restricted for new namespaces. New namespaces are created with enforce: restricted from the start.
  3. Migration to restricted. Existing namespaces use warn or audit first, then switch to enforce.
  4. Document every exception. A list of namespaces that use baseline or privileged, with the reason for each, is reviewed quarterly.

Production failure modes

  1. A workload cannot meet restricted and the team changes the namespace label. The label is a control surface; the workload must be fixed, not the namespace.
  2. The namespace is created without a PSS label. The namespace is implicitly privileged. The fix is to add enforce: restricted at creation.
  3. runAsNonRoot: true is set but the image requires root. The Pod fails to start with Permission denied. The fix is to fix the image (USER directive) or to mount the necessary directories as writable.
  4. readOnlyRootFilesystem: true is set but the workload writes to the root fs. The Pod fails to start with Read-only file system. The fix is to mount an emptyDir at the write path.

Cross-course references

  • The Linux course covers the kernel features (capabilities, seccomp, namespaces) that restricted enforces.
  • The Observability course covers the audit log entries for restricted violations.

Quiz

Knowledge check · 4 questions

  1. Q1. Which of the following is *required* by the `restricted` profile?

  2. Q2. The `restricted` Pod Security Standard requires `readOnlyRootFilesystem: true`.

  3. Q3. Your `prod` namespace has `enforce: restricted`. A workload sets `readOnlyRootFilesystem: true` but the application writes to `/var/log/myapp.log` and to `/tmp/myapp.sock`. The Pod fails to start with `Read-only file system`. How do you fix it?

    The workload is a hardened Java application. It writes logs to `/var/log/myapp.log` and uses a Unix domain socket at `/tmp/myapp.sock`. Both paths are on the read-only root filesystem. The fix is to mount `emptyDir` at both paths.

  4. Q4. Name four fields required by `restricted` and explain what each one prevents.

Passing score: 75%. Answers are checked in this browser.

Production discipline

The restricted profile is the production target. Every production namespace enforces restricted; exceptions (privileged, baseline) are explicit and documented. A cluster whose namespaces are mostly restricted has a workload security programme that is auditable. The audit log records every violation; the SIEM alerts on the violations. A workload that cannot meet restricted is a finding — the team either fixes the workload or documents the exception with a deadline.