AppArmor and seccomp Profiles: Restricting Container System Calls
A Senior DevSecOps Guide to Kernel-Level Container Hardening
Target Audience: Platform engineers, security engineers, Kubernetes operators
Tone: Direct, opinionated, battle-hardened
Prerequisites: Basic Linux kernel concepts, container runtime familiarity
Introduction — Why Your Containers Have Too Much Kernel Access
I've spent 15 years watching teams treat container security as a checkbox exercise. The worst part? Docker's default seccomp profile isn't a security boundary — it's a courtesy. It's designed to not break your applications, not to stop attackers.
The uncomfortable truth is that most container escapes (CVE-2022-0492, CVE-2023-25173) exploit syscalls that neither AppArmor nor seccomp block by default. Every unshare(), mount(), or keyctl() call is a potential privilege escalation vector. And most teams are running with the kernel equivalent of a screen door.
Here's a quick reality check:
# Show default seccomp profile in Docker
docker run --rm -it alpine sh -c 'cat /proc/$$/status | grep Seccomp'
# Returns: Seccomp: 2 (filtered, but you'd be surprised what's allowed)
I once audited a production cluster where 60% of containers ran with --privileged because "it was faster than debugging permission issues." The CISO had a stroke. The CTO didn't care. We fixed it with profiles — but it took three months of convincing.
Here's the actionable takeaway: If you're not running custom AppArmor and seccomp profiles, you're running with training wheels on a motorcycle. One wrong turn and you're eating pavement.
:::note[TL;DR]
- Default container profiles allow 300+ syscalls — most workloads need <50
- AppArmor blocks file paths, seccomp blocks syscalls — you need both
- Capabilities don't replace profiles; they complement them
- Kubernetes doesn't enforce profile content, only existence — use admission controllers
- Test profiles in complain/log mode for 24+ hours before enforcing :::
Prerequisites
Before we dive in, you'll need:
- A Linux system with AppArmor enabled (check with
sudo aa-status) - Docker or containerd runtime (v20.10+ for full seccomp support)
-
strace,auditd, andapparmor-utilsinstalled - Kubernetes cluster (v1.25+) if deploying profiles to K8s
- Basic familiarity with kernel syscalls and security contexts
Understanding the Linux Syscall Attack Surface in Containers
The 300+ Syscalls Your Container Doesn't Need
The problem is straightforward: default profiles allow 300+ syscalls. Most containers need fewer than 50. Every extra syscall is a potential weapon for an attacker who gets code execution inside your container.
Consider unshare(CLONE_NEWNS) — a single syscall that can break out of mount namespaces. A good seccomp profile blocks it. The default Docker profile? It allows it.
Here's how to see what your container actually calls:
# Capture syscalls from a running container
docker run --rm -it --security-opt seccomp=unconfined alpine sh -c "strace -c -S name ls / 2>&1 | tail -20"
This runs ls with strace and shows a syscall count. You'll see maybe 30 syscalls for a simple command. Now imagine what a web server or database needs — probably 40-60, not 300.
Comparison: Default Docker profile vs. minimal web server profile
⚠️ DECLASSIFIED / TRUNCATED VERSION
You are reading a truncated version of this technical guide.
To read the full, unedited deep-dive (including all configuration files, architecture diagrams, and high-res images), visit the original post on Valters IT Docs.
Top comments (0)