Pod Security
Pod Security: Security Contexts, PSS, and Kernel-Level Isolation
TL;DR
- SecurityContext: controls user/group, privileged mode, capabilities, read-only filesystem, seccomp/AppArmor.
- Pod Security Standards: baseline (minimal), restricted (hardened); PodSecurityPolicy replaced by Pod Security Admission (namespace labels).
- Enforcement modes: enforce (block), audit (log), warn (warn to logs).
- readOnlyRootFilesystem: prevents compromised container from writing persistent malware.
- seccomp/AppArmor: enforced by kernel LSM; blocks syscalls (seccomp) or operations (AppArmor).
- Adopting restricted: audit first (label namespace
audit=restricted), fix violations, then enforce.
Core Concepts
SecurityContext
Runs container as user, disables privileged mode, restricts capabilities.
Pod Security Standards
Baseline (permissive), restricted (hardened); Pod Security Admission enforces via namespace labels.
Configuration & Command Reference
Set namespace to audit restricted PSS:
kubectl label namespace default pod-security.kubernetes.io/audit=restricted
Common Pitfalls
- Pitfall: Set enforce=restricted on namespace; workloads start failing on scale. Why: non-compliant existing workloads now blocked. Fix: audit first, fix violations, then enforce.
Interview Questions
- Why must you audit before enforcing restricted Pod Security Standards?
apiVersion: v1
kind: Pod
metadata:
name: hardened-app
spec:
securityContext: # pod-level, applies to all containers
runAsNonRoot: true
runAsUser: 1000
fsGroup: 2000
seccompProfile:
type: RuntimeDefault
containers:
- name: app
image: myorg/app:1.0
securityContext: # container-level, overrides pod-level
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: ["ALL"]
add: ["NET_BIND_SERVICE"]
runAsNonRoot: true— API server rejects the pod outright if the container’s image is configured to run as UID 0.readOnlyRootFilesystem: true— the container’s own filesystem is immutable; any writes must go to an explicitly mounted volume (emptyDir, etc.) — dramatically limits what a compromised process can persist or modify.capabilities.drop: ["ALL"]then selectivelyaddback only what’s genuinely needed (likeNET_BIND_SERVICEto bind ports <1024 without full root) — the principle of least privilege applied to Linux kernel capabilities specifically.
2. Pod Security Standards (PSS) — The PodSecurityPolicy Replacement
PodSecurityPolicy was deprecated in 1.21 and removed in 1.25, replaced by the much simpler Pod Security Admission built directly into the API server, enforced via namespace labels:
kubectl label namespace payments \
pod-security.kubernetes.io/enforce=restricted \
pod-security.kubernetes.io/audit=restricted \
pod-security.kubernetes.io/warn=restricted
Three standard levels:
privileged— unrestricted, effectively no policy (default if unlabeled).baseline— blocks known privilege escalations (no privileged containers, no host namespaces) while remaining broadly compatible with most existing workloads.restricted— heavily hardened: requiresrunAsNonRoot, drops all capabilities by default, requires seccomp, disallows privilege escalation — the target posture for genuinely security-sensitive workloads.
enforce blocks non-compliant pods outright; audit/warn log or surface a warning without blocking, useful for testing a stricter level’s impact before actually enforcing it.
3. seccomp and AppArmor
seccomp restricts which Linux syscalls a container’s process can make at all — most container escapes and kernel exploits rely on syscalls that ordinary application code never legitimately needs.
securityContext:
seccompProfile:
type: RuntimeDefault # or Localhost, referencing a custom profile file
AppArmor provides path-based mandatory access control (which files/paths a process can read/write/execute) — configured via node-level profiles and referenced by annotation:
metadata:
annotations:
container.apparmor.security.beta.kubernetes.io/app: runtime/default
Both are enforced by the container runtime and the underlying kernel, not Kubernetes itself — Kubernetes only passes along which profile to apply; actual enforcement is a Linux kernel (LSM) feature.
4. Hands-on Command Cheat Sheet
Check current PSS enforcement level on a namespace:
kubectl get namespace payments -o jsonpath='{.metadata.labels}'
Test what would happen under a stricter PSS level before enforcing (dry-run):
kubectl label --dry-run=server namespace payments pod-security.kubernetes.io/enforce=restricted
Inspect a running container’s effective security context:
kubectl get pod hardened-app -o jsonpath='{.spec.securityContext}'
Check what Linux capabilities a running container actually has:
kubectl exec hardened-app -- cat /proc/1/status | grep Cap
5. Architectural Interview Q&A
Q: A namespace is labeled pod-security.kubernetes.io/enforce=restricted, but an existing Deployment’s pods keep failing to be recreated during a routine rollout. What’s likely going on?
A: The restricted level requires things like runAsNonRoot: true, dropped capabilities, and a seccomp profile — if the existing Deployment’s pod spec was written without these (common for older workloads or images that assume root), the API server now rejects new pod creation attempts outright since enforce blocks non-compliant pods. This is exactly why teams first apply audit/warn labels (which only log/surface violations without blocking) to a namespace, observe what would actually break, and fix those workloads’ security contexts before ever flipping to enforce in production.
Q: Why is readOnlyRootFilesystem: true considered a high-value hardening control even for applications that don’t obviously write to disk?
A: Even applications that don’t intentionally write files often incidentally do (writing PID files, temp files, cache directories, or logs to unexpected locations) — more importantly, this control specifically blocks a compromised process (via a code injection vulnerability, for example) from writing a malicious binary or modifying application code on disk to persist itself or escalate further. It converts what might otherwise be a full remote-code-execution-with-persistence scenario into one where the attacker’s writes are confined to explicitly mounted, more tightly controlled volumes only.
6. Common Pitfalls
[!WARNING] Enforcing “restricted” PSS Without First Auditing Existing Workloads Flipping a namespace directly to
pod-security.kubernetes.io/enforce=restrictedwithout first runningaudit/warnand reviewing the results is one of the most common ways to cause a self-inflicted production outage — any existing Deployment whose pod spec doesn’t already meet therestrictedbar will simply fail to create new pods on the very next rollout, scale event, or node eviction, often hours or days after the label was applied (whenever the next pod creation naturally happens), making the root cause non-obvious at the time it actually manifests.