Kubernetes pods are surprisingly vulnerable because their default security settings are often too permissive, assuming a trusted internal network.

Let’s look at a typical pod definition and see how we can tighten it up.

apiVersion: v1
kind: Pod
metadata:
  name: vulnerable-pod
spec:
  containers:
  - name: nginx
    image: nginx:latest
    ports:
    - containerPort: 80

This nginx pod, by default, can do a lot of things it shouldn’t. For instance, it can run as the root user inside the container, which gives it elevated privileges. It can also access sensitive host system information and potentially escape its container boundary.

The Principle of Least Privilege

The core idea behind hardening is applying the principle of least privilege. Your containers and your cluster should only have the permissions they absolutely need to function, and nothing more. This means:

  • Run as non-root: Containers should not run processes as the root user.
  • Read-only root filesystem: The container’s root filesystem should be immutable.
  • Drop unnecessary capabilities: Linux capabilities grant specific root privileges. Most containers don’t need most of them.
  • Limit resource usage: Prevent noisy neighbors or denial-of-service through resource limits.
  • Network segmentation: Restrict what pods can talk to.

Hardening the Pod: securityContext

The securityContext field in your pod or container spec is where most of the magic happens.

apiVersion: v1
kind: Pod
metadata:
  name: hardened-pod
spec:
  containers:
  - name: nginx
    image: nginx:latest
    ports:
    - containerPort: 80
    securityContext:
      runAsNonRoot: true
      runAsUser: 1001
      allowPrivilegeEscalation: false
      readOnlyRootFilesystem: true
      capabilities:
        drop:
        - ALL

Let’s break down these securityContext settings:

  • runAsNonRoot: true: This is a crucial setting. It tells Kubernetes that the container must run as a non-root user. If the image’s ENTRYPOINT or CMD tries to execute as root, the pod will fail to start.
  • runAsUser: 1001: Here, we explicitly set the User ID (UID) that the container process should run as. This requires that the container image is built with a user and group that has this UID, or that the user with this UID has the necessary permissions to run the application. Many base images like Alpine or Debian allow you to create users with specific UIDs. For example, in a Dockerfile, you might have:
    RUN adduser -u 1001 -D myuser
    USER myuser
    
    This ensures your application runs with a specific, unprivileged UID.
  • allowPrivilegeEscalation: false: By default, a process can gain more privileges than its parent process. For example, a root process can fork and exec another process that retains root privileges. Setting this to false prevents a container from gaining more privileges than its parent, which is essential for preventing privilege escalation.
  • readOnlyRootFilesystem: true: This makes the container’s root filesystem immutable. Applications that need to write data should do so in mounted volumes (emptyDir, persistentVolumeClaim, etc.). This prevents malicious code from modifying the container’s binaries or configuration files. For example, Nginx needs to write logs. You’d typically configure it to write to /var/log/nginx and then make sure /var/log/nginx is a writable volume.
  • capabilities: This field allows you to manage Linux capabilities.
    • drop: ["ALL"]: This is a strong security measure. It drops all Linux capabilities from the container. Most user-space applications don’t need any special privileges beyond what a regular user has. If your application does require a specific capability (e.g., NET_BIND_SERVICE to bind to ports below 1024 as a non-root user, although this is less common with modern container practices), you would add it to an add list instead of dropping everything. For example:
      capabilities:
        add:
        - NET_BIND_SERVICE
      
      But for Nginx running on port 80, dropping all capabilities is usually sufficient and much safer.

Further Hardening: Pod Security Policies (Deprecated) and Pod Security Admission (PSA)

While securityContext hardens individual pods, Kubernetes also offers cluster-wide admission controllers to enforce security standards.

  • Pod Security Policies (PSP): These were a powerful, albeit complex, way to define granular security constraints that pods had to meet to be admitted to the cluster. They are now deprecated and removed in recent Kubernetes versions.
  • Pod Security Admission (PSA): This is the successor to PSPs and is built directly into the Kubernetes API server. PSA enforces three predefined security profiles: privileged, baseline, and restricted.
    • privileged: Unrestricted. Pods can do anything.
    • baseline: Minimally restrictive. Pods cannot violate known security risks.
    • restricted: Highly restrictive. Pods are heavily restricted and limited.

You can enforce PSA at the namespace level. For example, to enforce the restricted profile on a namespace named my-secure-namespace, you would apply labels:

kubectl label --overwrite ns my-secure-namespace pod-security.kubernetes.io/enforce=restricted
kubectl label --overwrite ns my-secure-namespace pod-security.kubernetes.io/warn=baseline
kubectl label --overwrite ns my-secure-namespace pod-security.kubernetes.io/audit=baseline
  • enforce: This is the policy that is enforced. If a pod violates it, it’s rejected.
  • warn: This policy generates a warning message when a pod violates it, but the pod is still admitted. Useful for understanding what would be blocked.
  • audit: This policy generates audit events when a pod violates it, but the pod is still admitted. Useful for logging and compliance.

The restricted profile, for instance, enforces many of the securityContext settings we discussed, such as runAsNonRoot, readOnlyRootFilesystem, and dropping all capabilities, by default.

By combining granular securityContext settings with cluster-wide PSA enforcement, you build a robust security posture for your Kubernetes pods.

The next common hurdle you’ll face is ensuring your container images themselves are free of vulnerabilities, often addressed by image scanning tools.

Want structured learning?

Take the full Infrastructure Security course →