Pod Security #
One of the most common misconceptions in container architecture is considering containers as isolated security sandboxes equivalent to Virtual Machines (VMs). In reality, containers are just ordinary processes running on top of the same host operating system kernel, logically isolated using Linux kernel features like Namespaces and Control Groups (cgroups). If a container runs without adequate security restrictions, it has excessive privileges.
If that container gets exploited by an attacker (e.g. through a Remote Code Execution vulnerability), the attacker can easily leverage the root access inside the container to break through the host kernel isolation boundary (container breakout/escape). Once the host kernel is breached, the attacker gains full control over the physical worker node and can potentially paralyze the entire cluster. To prevent this scenario, we must apply container runtime hardening using SecurityContext and enforce Pod Security Standards (PSS) policies disciplined at the cluster level.
1. SecurityContext Anatomy: Pod-Level vs Container-Level #
Kubernetes provides the securityContext property on Deployment/Pod manifests to define container operational security restrictions. This property can be configured at two levels with different inheritance scopes:
Pod Spec
└── securityContext (Pod-Level: Applies globally to all containers inside the Pod)
├── container 1 (Inherits the Pod-level settings)
└── container 2 (Sets its own securityContext: Does an OVERRIDE)
- Pod-Level SecurityContext: Defined directly under
.spec.securityContext. Parameters set here (like process User IDs, volume Group IDs, and Seccomp profiles) are inherited by all containers (including application containers, init containers, and sidecar containers) inside that Pod. - Container-Level SecurityContext: Defined under
.spec.containers[*].securityContext. This configuration is specific to that container and can override parameters inherited from the Pod level. Some filesystem hardening features (likereadOnlyRootFilesystem) can only be set at this container level.
2. Key Container Hardening Parameters #
Let’s dissect the critical securityContext parameters we must enable to secure production container runtime environments:
A. User Protection: runAsNonRoot and runAsUser
#
By default, if we don’t specify a user in the Dockerfile or Kubernetes manifest, the container runs as the root (UID 0) user. In Linux operating systems, UID 0 inside a container is mapped identically to UID 0 (root) on the host machine.
spec:
securityContext:
# Instructs Kubernetes to validate that the container is NOT run as root (UID 0)
runAsNonRoot: true
# Forces the container to run using User ID 1000 (Non-Root User)
runAsUser: 1000
# Forces the container to run using Group ID 1000
runAsGroup: 1000
- Why Is This Important?: If a container escape happens, the attacker’s process successfully getting out to the host machine only has ordinary user access rights (UID 1000) on that host, limiting their ability to damage the node’s operating system.
- fsGroup: When we attach a Persistent Volume to a non-root container, the container often fails to write data due to disk access permission issues (permission denied). The
fsGroup: 1000property instructs Kubernetes to automatically change the mounted volume’s ownership so it can be read/written by GID 1000.
B. Preventing Privilege Escalation: allowPrivilegeEscalation: false
#
Privilege escalation happens when a child process obtains higher access rights than its parent process. This is usually achieved by executing binaries with the setuid or setcap bit active (like sudo or passwd programs).
spec:
containers:
- name: app
securityContext:
# Must be set to 'false' for all production containers
allowPrivilegeEscalation: false
- Mechanism: This parameter enables the
no_new_privskernel flag on the container process. Once this flag is active, the Linux OS refuses to raise child process access rights even if they execute binaries with thesetuidbit. This rule instantly blocks most local privilege escalation exploit tricks.
C. Filesystem Hardening: readOnlyRootFilesystem: true
#
By default, the container filesystem is writable. Attackers who successfully get into the container will immediately try downloading hacking tools (like curl, wget, nmap, or backdoor scripts) and writing them into the container system directories.
spec:
containers:
- name: app
securityContext:
# Locks the container filesystem to Read-Only
readOnlyRootFilesystem: true
- Mechanism: Locks the entire container root filesystem to Read-Only. Attackers can’t modify application binaries, overwrite configuration files, or write malware to disk.
- Temporary File Writing Solution (Volume Mounts): Many application frameworks (like Node.js, Python, or Java) still need special folders for writing temporary files (temporary files, logs, or caches). We must provide those special directories using
emptyDirvolumes mounted in memory (tmpfs/RAM).
# ✓ SOLUTION: Combining a Read-Only Filesystem with emptyDir Volume Mounts
spec:
containers:
- name: app
securityContext:
readOnlyRootFilesystem: true
volumeMounts:
- name: ephemeral-storage
mountPath: /tmp # Temporary write directory for the application
volumes:
- name: ephemeral-storage
emptyDir:
# Uses host RAM as the storage medium (never written to physical disks)
medium: Memory
# Limits temporary storage size to avoid RAM waste
sizeLimit: 128Mi
D. Kernel Privilege Restriction: Linux Capabilities #
Linux splits the absolute power of the super-user root into smaller, independent access right units called Capabilities. By default, Kubernetes container runtimes give containers a subset of built-in capabilities (like CAP_CHOWN, CAP_NET_RAW, CAP_MKNOD).
Many microservice web applications don’t need these capabilities at all to serve HTTP traffic. Giving unused capabilities only makes it easier for attackers to exploit the host operating system.
[!TIP] The Drop All, Add Selective Principle: The best approach is removing all built-in container capabilities first using the
drop: ["ALL"]query, then selectively adding back only what the application’s business logic actually needs.
spec:
containers:
- name: app
securityContext:
capabilities:
drop:
- ALL # ← Mandatory: Remove all default Linux kernel capabilities
add:
- NET_BIND_SERVICE # ← Example: Only allow the app to bind socket ports below 1024 (e.g. port 80)
Capability Risk Analysis: #
CAP_SYS_ADMIN: The peak of danger. Grants almost full root-equivalent access. Never enable this for regular application containers.CAP_NET_RAW: Allows containers to craft raw network packets. Useful for ping queries, but very dangerous because it lets attackers sniff cluster traffic laterally.CAP_NET_BIND_SERVICE: Allows non-root processes to bind privileged ports below1024(like port80or443). If our application runs on port8080, we don’t need this capability.
E. System Call Filtering: seccompProfile
#
Seccomp (Secure Computing Mode) is a Linux kernel security feature restricting the system calls (syscalls) containers may submit to the host kernel. The Linux kernel has over 300 syscalls, but the average application container only needs fewer than 50 syscalls to run.
spec:
securityContext:
seccompProfile:
# Uses the container runtime's default profile (runc / containerd)
type: RuntimeDefault
- Mechanism: The
RuntimeDefaultprofile blocks around 300 dangerous syscalls (likereboot,sys_ptrace, or other kernel manipulation queries) often used by zero-day exploits to break container isolation. This setting must be enabled at the Pod level for all production environments.
3. Pod Security Standards (PSS) & Pod Security Admission (PSA) #
To uniformly enforce container hardening policies across the entire cluster, Kubernetes provides a built-in admission control mechanism called Pod Security Admission (PSA). PSA evaluates every Pod to be created against the three Pod Security Standards (PSS) levels:
+---------------------------------------------------------------------------------+
| POD SECURITY STANDARDS (PSS) LEVELS: |
| |
| 1. PRIVILEGED (No restrictions) |
| - Allows hostNetwork, hostPath, running as root, and full privileges. |
| - Only for CNI plugins, storage drivers, kube-proxy. |
| |
| 2. BASELINE (Prevents dangerous escalation) |
| - Blocks hostNetwork, hostPID, hostIPC, and hostPath volumes. |
| - The safe default standard for general applications. |
| |
| 3. RESTRICTED (Maximum Security Hardening) |
| - Mandatory runAsNonRoot, readOnlyRootFilesystem, seccompProfile, drop capabilities.|
| - The mandatory standard for high compliance (PCI-DSS / HIPAA). |
+---------------------------------------------------------------------------------+
Enforcing Policies at the Namespace Level #
We enable these PSS controls by giving special labels to Namespace objects. PSA has three action modes that can be combined:
enforce: Hard-rejects Pod creation if the Pod violates the standard rules.warn: Allows the Pod to be created, but sends a warning message to the client (e.g. to the developer CLI).audit: Allows the Pod to be created, but records the violation in the cluster audit log.
# Production Namespace Manifest with Strict Security (Restricted)
apiVersion: v1
kind: Namespace
metadata:
name: payment-processing
labels:
# Enforce Mode: Flat-out reject Pods violating the Restricted standard
pod-security.kubernetes.io/enforce: restricted
pod-security.kubernetes.io/enforce-version: latest
# Warn Mode: Give a visual warning when developers run kubectl apply
pod-security.kubernetes.io/warn: restricted
pod-security.kubernetes.io/warn-version: latest
The PSS Restricted Validation Flow #
When a Pod manifest is submitted to the API Server, the Admission Controller evaluates the namespace label and blocks the Pod if it doesn’t meet the security criteria:
sequenceDiagram
participant Dev as Operator / CI Pipeline
participant API as Kubernetes API Server
participant PSA as Pod Security Admission
participant Kube as Kubelet Daemon
Dev->>API: Submit the Pod manifest (kubectl apply)
API->>PSA: Validate the manifest against the Namespace Policy
Note over PSA: Evaluate Namespace: payment-processing<br>(Standard: Restricted Enforce)
Alt Pod Violates the Rules (e.g., runAsNonRoot: false)
PSA-->>API: Validation Failed (Reject the Pod)
API-->>Dev: HTTP 403 Forbidden (Error: Pod does not meet Restricted standard)
Else Pod Meets the Rules (e.g., runAsNonRoot: true & seccomp active)
PSA-->>API: Validation Passed (Allow the Pod)
API->>Kube: Schedule the container to the Worker Node
Kube-->>Dev: Pod Successfully Created (Running)
EndAnti-Patterns vs Best Solutions #
Let’s study several container runtime security configuration mistakes often found in the industry along with their fixes.
Anti-Pattern 1: Using hostPath Volumes for Temporary Storage
#
Using hostPath-type volumes to give containers write access to log folders or temporary caches.
# ✗ ANTI-PATTERN: Using hostPath triggering host directory leaks
spec:
containers:
- name: web-app
image: company/web-app:v1.0.0
volumeMounts:
- name: host-log
mountPath: /var/log/app
volumes:
- name: host-log
hostPath:
path: /var/log/host-system # ← CRITICAL: The container can access host system files
Best Solution #
Use the Pod-level isolated emptyDir volume. Data is stored in worker node memory and automatically cleaned up completely by Kubernetes when the Pod shuts down, without inserting host directory access holes.
Anti-Pattern 2: Running Containers with privileged: true
#
Setting the privileged: true flag on regular container specs with the excuse of easing network connectivity debugging or device access.
# ✗ ANTI-PATTERN: Giving full system privileges to the container
spec:
containers:
- name: debug-tool
image: company/debug-tool:latest
securityContext:
privileged: true # ← FATAL: Removes all namespace and kernel cluster isolation
Best Solution #
Use the kubectl debug feature with ephemeral containers carrying special images like netshoot to do targeted network debugging without sacrificing the main Deployment’s security.
Anti-Pattern 3: Using hostNetwork, hostPID, or hostIPC #
Enabling physical host namespace sharing to regular application containers so containers can see host system processes or directly manipulate host network cards.
# ✗ ANTI-PATTERN: Enabling host namespace sharing
spec:
hostNetwork: true # ← Binds container ports directly to physical host node ports
hostPID: true # ← The container can see and kill host machine processes
hostIPC: true # ← The container can manipulate host memory sharing
containers:
- name: bad-app
image: company/bad-app:v1.0.0
Best Solution #
Keep the default false values for all three parameters. Use the Kubernetes network isolation model (CNI flat network model) and let the Kubernetes Service manage container port routing in an abstracted way.
Best Production Deployment Manifest (PSS Restricted Hardened) #
Here’s a production-ready Deployment manifest example that has gone through maximum security hardening (highly hardened) meeting the PSS Restricted standard:
apiVersion: apps/v1
kind: Deployment
metadata:
name: secured-api-gateway
namespace: payment-processing
labels:
app.kubernetes.io/name: secured-api-gateway
spec:
replicas: 3
selector:
matchLabels:
app: secured-api-gateway
template:
metadata:
labels:
app: secured-api-gateway
spec:
# 1. Security Configuration at the Pod Spec Level
securityContext:
runAsNonRoot: true # Rejects containers trying to run as root
runAsUser: 1000 # Sets the non-root User ID
runAsGroup: 3000 # Sets the non-root Group ID
fsGroup: 2000 # Aligns volume mount ownership to GID 2000
seccompProfile:
type: RuntimeDefault # Enables default syscall filtering
containers:
- name: gateway
image: company/api-gateway:sha-9d8f3e2
resources:
requests:
cpu: "250m"
memory: "256Mi"
limits:
cpu: "1000m"
memory: "512Mi"
# 2. Security Configuration at the Container Spec Level
securityContext:
allowPrivilegeEscalation: false # Prevents access escalation via setuid
readOnlyRootFilesystem: true # Locks the container root filesystem to read-only
capabilities:
drop:
- ALL # Removes all built-in Linux kernel capabilities
volumeMounts:
- name: tmp-storage
mountPath: /tmp # Temporary write directory for framework caches
ports:
- containerPort: 8080
volumes:
- name: tmp-storage
emptyDir:
medium: Memory
sizeLimit: 128Mi
Pod Security Hardening Audit Checklist #
Make sure all container Pods running in our production cluster pass the security hardening audit using the following checklist:
USER & ACCESS RIGHT MANAGEMENT:
□ The 'runAsNonRoot' parameter is set to 'true' at the Pod spec level.
□ The 'runAsUser' parameter is configured using a specific non-root UID (not 0).
□ The 'allowPrivilegeEscalation' parameter is set to 'false' on all containers.
□ The 'privileged: true' property is confirmed disabled from all regular applications.
CAPABILITIES & FILE SYSTEMS:
□ The 'readOnlyRootFilesystem' property is enabled at the container spec level.
□ 'emptyDir' (RAM-backed/Memory) volumes are used for temporary write directories (/tmp).
□ 'hostPath' volume usage is fully eliminated for stateless applications.
LINUX CAPABILITIES & SYSCALLS:
□ The 'capabilities.drop' block is set to 'ALL' to remove default Linux capabilities.
□ Capability additions ('capabilities.add') are only done selectively and documented.
□ The 'seccompProfile.type' parameter is configured to 'RuntimeDefault' at the Pod level.
NAMESPACE ISOLATION & PSA ENFORCEMENT:
□ Production namespaces are labeled with 'pod-security.kubernetes.io/enforce: restricted'.
□ The 'hostNetwork', 'hostPID', and 'hostIPC' parameters are confirmed 'false' (default).
□ Runtime security is tested to run smoothly without triggering CrashLoopBackOff due to permission issues.
Summary #
- Must run non-root — Enable the
runAsNonRoot: trueparameter at the Pod level to ensure no production containers operate as the root user (UID 0).- Prevent escalation via
no_new_privs— ConfigureallowPrivilegeEscalation: falseto blocksetuidbinary execution that can trigger access escalation in containers.- Lock the container filesystem — Use the
readOnlyRootFilesystem: trueproperty to reject attacker modifications or malware installations in the container filesystem.- Use RAM emptyDir for /tmp — Provide temporary write directories using
emptyDirvolumes with theMemorymedia type to secure temporary application write processes.- Drop ALL Linux capabilities — Apply the Drop All, Add Selective tactic on container capability parameters to minimize unused kernel access rights.
- Enable Pod Security Admission — Label production namespaces with the
restrictedstandard to automatically reject Pod manifests that aren’t security-hardening friendly.
← Previous: RBAC (Role-Based Access Control) Next: Network Security →