Pod and Cluster Security
The idea in one sentence
Section titled “The idea in one sentence”Hardening a Pod is a stack of independent controls — a per-container securityContext, a namespace-wide Pod Security Admission level, and cluster-level protections like etcd encryption — none of which substitutes for the others.
SecurityContext: hardening a container from the inside
Section titled “SecurityContext: hardening a container from the inside”securityContext can be set at the Pod level (defaults for every container) and per-container (overrides for that one container). The fields that matter most for hardening:
apiVersion: v1kind: Podmetadata: name: checkoutspec: securityContext: runAsNonRoot: true runAsUser: 10001 runAsGroup: 10001 containers: - name: checkout image: checkout:2.3.0 securityContext: readOnlyRootFilesystem: true allowPrivilegeEscalation: false privileged: false capabilities: drop: ["ALL"] add: ["NET_BIND_SERVICE"]runAsNonRoot— refuses to start the container if its image would run as UID 0.runAsUser/runAsGroup— pins the exact UID/GID the container runs as, regardless of what the image’sDockerfilespecifies.readOnlyRootFilesystem— mounts the container’s root filesystem read-only, so an attacker who gets code execution cannot write a persistent payload to disk.allowPrivilegeEscalation: false— blocks a process from gaining more privileges than its parent (for example, via setuid binaries).privileged: false— never grants the container full access to the host’s devices and kernel capabilities; a privileged container is effectively equivalent to root on the node itself, so treat it as a last resort, not a default.capabilities— Linux splits root’s power into discrete capabilities. DroppingALLand adding back only what is genuinely required (likeNET_BIND_SERVICEto bind a port below 1024) is far safer than running as a user with the full default capability set.
kubectl get pod checkout -o jsonpath='{.spec.securityContext}'kubectl get pod checkout -o jsonpath='{.spec.containers[0].securityContext}'Pod Security Admission: enforcing a baseline at the namespace level
Section titled “Pod Security Admission: enforcing a baseline at the namespace level”Pod Security Admission is built into the API server and has been GA since Kubernetes 1.25. It replaced the older PodSecurityPolicy, which was removed in 1.25 — PodSecurityPolicy is gone, not merely deprecated, so any cluster running 1.25+ needs Pod Security Admission (or a third-party policy engine) instead.
Pod Security Admission works entirely through namespace labels — no separate policy objects to create and bind:
kubectl label --overwrite ns checkout \ pod-security.kubernetes.io/enforce=restricted \ pod-security.kubernetes.io/warn=restricted \ pod-security.kubernetes.io/audit=restrictedapiVersion: v1kind: Namespacemetadata: name: checkout labels: pod-security.kubernetes.io/enforce: restrictedThere are three standard Pod Security Standards levels, least to most restrictive:
privileged— no restrictions at all; effectively opts out of Pod Security Admission for that namespace.baseline— blocks the known, most obvious privilege-escalation paths (host namespaces, privileged containers, certain dangerous volume types) while staying broadly compatible with common workloads.restricted— heavily hardened, requiring things likerunAsNonRoot, dropped capabilities, and no privilege escalation — it follows current Pod-hardening best practices closely.
Each level has three independent modes: enforce actually blocks non-compliant Pods from being admitted, while audit and warn are non-blocking — they log a violation or show a client-side warning without stopping the Pod. A common rollout pattern is to enforce a level the cluster already meets while warn/audit at a stricter level, to see what would break before tightening enforce.
Beyond Pods: cluster-level and network controls
Section titled “Beyond Pods: cluster-level and network controls”SecurityContext and Pod Security Admission both operate at the Pod level, but a real security posture needs cluster-level controls too.
Secrets are only base64-encoded by default — that is encoding, not encryption, and anyone with API read access to a Secret (or access to etcd’s data files directly) can trivially decode it. The cluster-level control that actually protects Secret data at rest is etcd encryption at rest, which the API server applies transparently so Secret data is encrypted before it is written to etcd’s storage.
NetworkPolicy (covered in the Networking module) is also a core part of the security posture — RBAC and Pod Security Admission control what a Pod’s identity can do against the API and how a Pod is configured, but neither one restricts which Pods can talk to which other Pods over the network. That is NetworkPolicy’s job, and a complete security story needs all three layers working together.
flowchart LR
submit["Pod submitted to namespace"] --> check{"Complies with\nnamespace enforce level?"}
ns["Namespace label:\npod-security.kubernetes.io/enforce"] --> check
check -- "yes" --> admitted["Pod admitted"]
check -- "no" --> rejected["Pod rejected"]