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).
The three profiles
- Privileged is unrestricted, and allows known privilege escalations. It is for trusted, system-level workloads.
- Baseline is minimally restrictive. It prevents known privilege escalations while allowing the default Pod configuration, which suits most common workloads.
- 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:
enforcerejects Pods that violate the policy.auditallows Pods but records violations in the audit log.warnallows 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=latestThis 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:
-
Make sure the AppArmor kernel module is enabled on every worker node.
-
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 } -
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 runningsudo apparmor_parser -r -W <profile-path>. -
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 hostThis 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: RuntimeDefaultCustom 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.
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:latestGood 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.