Chapter 11: Container and Virtualization Security
Chapter 11: Container and Virtualization Security
The migration from physical bare-metal hardware to containers and hypervisors transformed infrastructure delivery. However, abstraction layers introduce unique security boundaries and shared-resource risks.
Containers are not lightweight virtual machines; they are ordinary Linux processes executing on a shared host kernel, isolated solely by namespaces, cgroups, seccomp filters, and Linux Security Modules (LSM). Virtual machines provide hardware-enforced hypervisor boundaries, but introduce complex device emulation layers.
This chapter details the mechanisms required to secure containerized and virtualized workloads: container runtime hardening (containerd, Docker), Kubernetes node defense, container image supply chain integrity, hypervisor isolation (KVM, libvirt, sVirt), and defensive analysis of container breakout vectors.
Container Security Architecture
To secure containers, one must understand how the container isolation perimeter is constructed in the Linux kernel:
┌────────────────────────────────────────────────────────────────────────┐
│ THE CONTAINER DEFENSE LAYERS │
│ │
│ Application Process (e.g., nodejs, python) │
│ ├─ User Identity: Runs as non-root UID 10001 (USER directive) │
│ │ │
│ ▼ │
│ Layer 1: Capabilities & Primitives │
│ ├─ Capability Bounding Set: Stripped of CAP_SYS_ADMIN, CAP_NET_ADMIN │
│ ├─ NoNewPrivileges = true: Blocks setuid execution │
│ │ │
│ ▼ │
│ Layer 2: Syscall Boundary │
│ └─ Seccomp-BPF Profile: Blocks dangerous syscalls (ptrace, unshare) │
│ │ │
│ ▼ │
│ Layer 3: Mandatory Access Control (LSM) │
│ └─ AppArmor / SELinux (sVirt/container_t): Confinements on paths │
│ │ │
│ ▼ │
│ Layer 4: Virtualization & Isolation │
│ └─ Namespaces (mnt, pid, net, ipc, uts, user) & cgroups v2 limits │
│ │ │
│ ▼ │
│ Linux Host Kernel (Ring 0) │
└────────────────────────────────────────────────────────────────────────┘
If any single layer is misconfigured—for example, granting --privileged or mounting the host filesystem—the isolation model collapses, granting the container process direct control over host hardware.
Docker and Containerd Hardening
Production container hosts should run hardened runtime configurations:
Hardening Docker Engine via /etc/docker/daemon.json
{
"icc": false,
"no-new-privileges": true,
"userns-remap": "default",
"log-driver": "journald",
"live-restore": true,
"userland-proxy": false,
"seccomp-profile": "/etc/docker/hardened-seccomp.json",
"storage-driver": "overlay2"
}"icc": false: Disables inter-container communication on the default bridge network, preventing an exploited container from directly sniffing or attacking adjacent containers."no-new-privileges": true: Prevents processes inside containers from acquiring additional privileges via setuid binaries."userns-remap": "default": Remaps container UID 0 (root inside container) to an unprivileged subordinate UID (e.g., UID 100000) on the host. If an attacker escapes to the host, they arrive as an unprivileged nobody.
Rootless Containers with Podman
Where possible, eliminate the root-owned container daemon entirely by adopting rootless container engines like Podman:
# Launch a fully rootless container as an unprivileged user
podman run -d --name secure-web -p 8080:80 nginx:alpineIn rootless mode, the entire container engine, namespace creation, and filesystem mounts execute within the unprivileged user’s ambient authority.
Kubernetes Node Security
In a Kubernetes cluster, the Linux operating system serves as the node substrate. If a worker node’s operating system is compromised, the attacker can hijack all pods scheduled on that node and steal cluster service account secrets.
┌────────────────────────────────────────────────────────────────────────┐
│ HARDENED KUBELET TOPOLOGY │
│ │
│ API Server ──► Webhook Authentication (x509 Client Certs) │
│ │ │
│ ▼ │
│ Kubelet (Node Worker) ──► NodeRestriction Admission Controller │
│ ├─ Anonymous Auth: Disabled (anonymous-auth: false) │
│ ├─ Read-Only Port: Disabled (readOnlyPort: 0) │
│ ├─ Protect Node PKI: /var/lib/kubelet/pki/ mode 0700 (root only) │
│ └─ Enforce AppArmor Profile: runtime/default │
└────────────────────────────────────────────────────────────────────────┘
Kubelet Configuration Hardening
Apply the following settings in /var/lib/kubelet/config.yaml:
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
authentication:
anonymous:
enabled: false
webhook:
enabled: true
x509:
clientCAFile: /etc/kubernetes/pki/ca.crt
authorization:
mode: Webhook
readOnlyPort: 0
protectKernelDefaults: true
makeIPTablesParseable: true
eventRecordQPS: 50protectKernelDefaults: true: Ensures kubelet exits with an error if required kernel security sysctls are not properly configured.
Container Image and Registry Security
Container security begins during the build phase, long before images are scheduled on nodes:
Minimal Base Images: Avoid heavy OS base images (Ubuntu, Debian) containing package managers, compilers, and utilities (
curl,nc,bash). Usescratchor Google’sdistrolessimages containing only the compiled application binary and its direct runtime dependencies:FROM golang:1.24 AS builder WORKDIR /app COPY . . RUN CGO_ENABLED=0 go build -o server . FROM gcr.io/distroless/static:nonroot COPY --from=builder /app/server /server USER nonroot:nonroot ENTRYPOINT ["/server"]Automated Vulnerability Scanning: Integrate scanners (such as Trivy or Grype) into CI/CD pipelines to block images containing critical CVEs:
trivy image --exit-code 1 --severity CRITICAL,HIGH mycompany/api:v1.2.0Cryptographic Image Signing with Cosign: Sign images upon successful build and enforce signature validation in Kubernetes admission webhooks (Kyverno or Gatekeeper):
cosign sign --key k8s.key myregistry.corp.internal/mycompany/api:v1.2.0
Virtualization Host Hardening
For hypervisors running KVM and QEMU:
- sVirt Isolation: libvirt integrates with SELinux and AppArmor via sVirt. Each virtual machine process (
qemu-system-x86_64) is launched with a unique, randomly assigned MCS security label (e.g.,svirt_t:s0:c123,c456). Even if an attacker escapes the guest VM via a QEMU device emulation bug, sVirt prevents the process from reading or modifying the disk images of other tenant VMs. - Dedicated Management Networks: Hypervisor management interfaces (SSH, libvirt RPC) must reside on isolated physical VLANs separated from guest VM tenant traffic.
Container Escapes and Privilege Boundaries
Understanding how containers break out is crucial for building resilient defenses:
1. The --privileged Escape
Running a container with --privileged disables all seccomp filters, drops no capabilities, and exposes all host device nodes (/dev) inside the container. An attacker escapes in three commands:
# Inside a privileged container:
mkdir /tmp/host_root
mount /dev/nvme0n1p2 /tmp/host_root
chroot /tmp/host_root
# Total root access over the host operating system achieved2. The Docker Socket Mount Escape
Mounting /var/run/docker.sock inside a container allows the container to talk directly to the host Docker daemon API. The attacker simply instructs the daemon to spin up a new container with root filesystem mounts:
# Inside container with mounted docker.sock:
docker run -it -v /:/host-root ubuntu chroot /host-rootDefensive Rule: Never run containers with --privileged. Never mount /var/run/docker.sock. Use unprivileged containers, read-only root filesystems (--read-only), and user namespace remapping.