All writing
Updated 4 min readNaren

Pod security: PSA, AppArmor, seccomp and sandboxes

Enforce Pod Security Standards per namespace, limit what a container can do with AppArmor and seccomp, and isolate untrusted workloads with gVisor or Kata Containers.

The last post hardened the cluster with the CIS Benchmarks and restricted API access with RBAC. This one moves down to the Pod, and the ways to secure individual workloads:

  • Pod Security Standards (PSS) and Pod Security Admission (PSA), Kubernetes' built-in way to enforce security policies on Pods
  • AppArmor and seccomp, Linux kernel features that restrict what a container can do
  • container runtime sandboxes, gVisor and Kata Containers, which add stronger isolation for critical workloads

All of it is relevant to the CKS exam.

Pod Security Standards and Pod Security Admission

The Pod Security Standards are a set of predefined security profiles, each with a different level of restriction for Pods. The Pod Security Admission controller enforces them. It replaced the deprecated PodSecurityPolicy (PSP).

Pod Security Standards Pod Security Admission (Namespace Labels) enforced by enforced by enforced by audited by audited by audited by warned by warned by warned by Privileged Baseline Restricted enforce audit warn

The three profiles

  1. Privileged is unrestricted, and allows known privilege escalations. It is for trusted, system-level workloads.
  2. Baseline is minimally restrictive. It prevents known privilege escalations while allowing the default Pod configuration, which suits most common workloads.
  3. Restricted is heavily restricted and follows current Pod hardening practice. It prevents privileged actions, disallows host access and makes containers run as non-root. Use it in production and other high-security environments.

Enforcing a profile with Pod Security Admission

Pod Security Admission applies the profiles per namespace, using labels, in three modes:

  • enforce rejects Pods that violate the policy.
  • audit allows Pods but records violations in the audit log.
  • warn allows Pods but shows the user a warning.

You apply them by labeling a namespace:

kubectl label namespace my-secure-app \
  pod-security.kubernetes.io/enforce=restricted \
  pod-security.kubernetes.io/enforce-version=latest

This enforces the restricted profile on the my-secure-app namespace, and any Pod that violates it is rejected. This Pod is allowed:

apiVersion: v1
kind: Pod
metadata:
  name: compliant-nginx
spec:
  containers:
  - name: nginx
    image: nginxinc/nginx-unprivileged:latest
    securityContext:
      runAsNonRoot: true
      seccompProfile:
        type: RuntimeDefault
      allowPrivilegeEscalation: false
      capabilities:
        drop: ["ALL"]

The restricted profile requires containers to drop all Linux capabilities, hence drop: ["ALL"]. The Pod uses the unprivileged Nginx image because the standard nginx image runs as root, and runAsNonRoot would stop it from starting.

AppArmor

AppArmor (Application Armor) is a Linux kernel security module that restricts what a program can do through a profile for each program. A profile can allow or deny access to network resources, raw sockets and files. Applied to a container, it limits what the container can do, which reduces the attack surface and contains the damage if the container is compromised.

To use AppArmor in Kubernetes:

  1. Make sure the AppArmor kernel module is enabled on every worker node.

  2. Write an AppArmor profile. A profile is a text file of rules. This one, k8s-apparmor-example-deny-write, denies all file writes:

    #include <tunables/global>
    
    profile k8s-apparmor-example-deny-write flags=(attach_disconnected) {
      #include <abstractions/base>
      file,
      deny /** w, # Deny all file writes
    }
  3. Load the profile onto the nodes. Kubernetes does not distribute profiles, so you load them on each worker node that needs them, for example by copying them to /etc/apparmor.d/ and running sudo apparmor_parser -r -W <profile-path>.

  4. Apply it to a Pod with securityContext.appArmorProfile, available from Kubernetes v1.30.

apiVersion: v1
kind: Pod
metadata:
  name: nginx-apparmor-deny-write
spec:
  containers:
  - name: nginx
    image: nginx:latest
    securityContext:
      appArmorProfile:
        type: Localhost
        localhostProfile: k8s-apparmor-example-deny-write # Name of the profile loaded on the host

This Pod runs under the profile, so it cannot write to files.

Seccomp

Seccomp (secure computing mode) is a Linux kernel feature that restricts the system calls, or syscalls, a process can make to the kernel. It works like a firewall for syscalls. You get precise control over which syscalls a container can make, and blocking the unnecessary and dangerous ones reduces the risk from kernel vulnerabilities.

Seccomp profiles are JSON files that define the syscall filtering rules.

The RuntimeDefault profile

Container runtimes such as containerd ship a default seccomp profile that allows the commonly used syscalls and blocks the dangerous ones. It is a good place to start.

apiVersion: v1
kind: Pod
metadata:
  name: myapp-seccomp-runtime-default
spec:
  containers:
  - name: myapp
    image: busybox:1.36
    command: ["sh", "-c", "echo Hello Seccomp!; sleep 3600"]
    securityContext:
      seccompProfile:
        type: RuntimeDefault

Custom profiles

You can write your own JSON profiles to allow or deny very specific syscalls. This one blocks mkdir:

{
  "defaultAction": "SCMP_ACT_ALLOW",
  "syscalls": [
    {
      "names": [
        "mkdir"
      ],
      "action": "SCMP_ACT_ERRNO"
    }
  ]
}

To use a custom profile, place the JSON file on each worker node under /var/lib/kubelet/seccomp/, for example at /var/lib/kubelet/seccomp/profiles/block-mkdir.json. Then apply it to a Pod:

apiVersion: v1
kind: Pod
metadata:
  name: myapp-seccomp-custom
spec:
  containers:
  - name: myapp
    image: busybox:1.36
    command: ["sh", "-c", "echo Hello Custom Seccomp!; sleep 3600"]
    securityContext:
      seccompProfile:
        type: Localhost
        localhostProfile: profiles/block-mkdir.json # Path relative to /var/lib/kubelet/seccomp/

Container runtime sandboxes

Some workloads need stronger isolation than ordinary containers give them. For those, Kubernetes can use a container runtime sandbox, which runs each Pod in a lightweight virtual machine or another more isolated environment. That puts a stronger security boundary between the container and the host kernel.

Pod Security Container enforces policies on restricts capabilities of filters syscalls of provides isolated environment for Pod Security Standards AppArmor Seccomp Runtime Sandboxes Application

gVisor

gVisor is an open-source user-space kernel from Google. It intercepts the application's syscalls and handles them in user space, which puts a strong isolation boundary between the application and the host kernel and greatly reduces how much of the host kernel is exposed. Use it for untrusted or highly sensitive workloads.

To use it, install gVisor's containerd shim, containerd-shim-runsc-v1, and configure your cluster to use it. Then set runtimeClassName: gvisor in the Pod spec.

Kata Containers

Kata Containers is an open-source project that combines the isolation of virtual machines with the speed and manageability of containers. Each Kata container runs inside its own lightweight VM. That hardware-level isolation makes it very hard for a compromised container to affect the host or other containers, which suits multi-tenant environments, highly sensitive data processing and untrusted code.

To use it, install the Kata Containers runtime and configure your cluster to use it. Then set runtimeClassName: kata in the Pod spec.

apiVersion: v1
kind: Pod
metadata:
  name: sandboxed-pod
spec:
  runtimeClassName: gvisor # Or 'kata'
  containers:
  - name: my-untrusted-app
    image: my-untrusted-app:latest

Good practices

Enforce the restricted profile for all production workloads, and apply the RuntimeDefault seccomp profile to every Pod.

Use AppArmor profiles to restrict containers further, especially critical applications.

Run containers as non-root users, with runAsNonRoot: true. Avoid privileged: true and allowPrivilegeEscalation: true unless you truly need them.

For highly sensitive or untrusted workloads, consider gVisor or Kata Containers for stronger isolation.

Pod security is one layer of a layered defense. Combine it with RBAC, network policies, image scanning and secrets management.

The next post continues with supply chain security: protecting your container images and the way they are deployed. Supply chain security for Kubernetes.