LearnKubernetes
Kubernetes6. Security·EXPERT·5 min read

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 selectively add back only what’s genuinely needed (like NET_BIND_SERVICE to 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: requires runAsNonRoot, 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=restricted without first running audit/warn and 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 the restricted bar 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.

🔒 Locked
Sign in and unlock the full syllabus to keep reading.
Sign in to continue
Knowledge check
5 scenario questions on this topic
Take the quiz →
Related in 6. Security
SSL/TLS Fundamentals
EXPERT
TLS in Kubernetes
EXPERT
Authentication & Authorization
EXPERT
RBAC
EXPERT