Dynamic AppArmor Profile Management for Kubernetes
Kapparmor is a cloud-native security enforcer that simplifies AppArmor profile management in Kubernetes clusters. Deploy, update, and manage AppArmor security profiles across your infrastructure through a simple ConfigMap interface—no manual node configuration required.
- Why AppArmor?
- Key Features
- Security-First Approach
- Getting Started
- Architecture
- Configuration
- Constraints & Limitations
- Testing
- Documentation
- Release Process
- Community & Support
- License
- Credits & Acknowledgments
Kapparmor dynamically loads and unloads AppArmor security profiles on Kubernetes cluster nodes via ConfigMap. It runs as a privileged DaemonSet on Linux nodes, eliminating the need for manual profile management on each node.
Key Capabilities:
- 🔄 Dynamic Loading – Apply profile changes without node restarts
- 📦 ConfigMap-Based – Version control your security policies as Kubernetes manifests
- 🧹 Auto-Cleanup – Automatically remove unused profiles
- 🔍 Change Detection – Detects and syncs profile modifications
- ✅ Validation – Validates syntax before kernel loading
- 📊 Observable – Health endpoints and structured logging
This work was inspired by kubernetes/apparmor-loader.
| Feature | AppArmor | SELinux | Seccomp |
|---|---|---|---|
| Type | MAC (Mandatory Access Control) | MAC | Syscall filtering |
| Scope | File access, capabilities, networking | File access, labels | System calls only |
| Learning Curve | 🟢 Easy (plain-text profiles) | 🔴 Steep (complex contexts) | 🟢 Simple (syscall lists) |
| Maintenance | 🟢 Low (profile-per-app) | 🟡 Medium (policy system) | 🟡 Medium (tool-dependent) |
| Kubernetes Support | ✅ Native via AppArmor | ✅ Via labels | ✅ Native (RuntimeDefault) |
| Use Case | Container workloads | Enterprise systems | Syscall restriction |
Choose AppArmor when you need:
- Easy-to-understand security profiles
- File and path-level access control
- Capability restrictions
- Port binding restrictions
- Network namespace access control
Choose SELinux when you need:
- Label-based context systems
- Enterprise policy frameworks (CIS profiles)
- Existing infrastructure investment
Choose Seccomp when you need:
- Only syscall filtering
- Lightweight containerized defaults
- Minimal overhead for simple restrictions
🔐 Enterprise-Grade Security
- Input validation with fuzz testing
- Secure coding practices (SSDLC)
- Supply chain security (signed commits, Harden-Runner, CodeQL)
- Zero external runtime dependencies
⚡ Kubernetes-Native
- DaemonSet-based deployment
- ConfigMap-driven configuration
- Health checks and readiness probes
- Optional Prometheus metrics
🛡️ Robust Profile Management
- Syntax validation before loading
- Filename/profile name consistency checks
- Path traversal protection
- Automatic cleanup of orphaned profiles
📈 Production-Ready
- Comprehensive test coverage
- CI/CD security gates
- OpenSSF Best Practices certified
- No privileged escalation vectors
Kapparmor is built with security as a core principle:
✅ Threat Modeling – Comprehensive STRIDE analysis
✅ Code Quality – 80%+ test coverage, zero high-severity CodeQL alerts
✅ Supply Chain – Pinned dependencies, signed commits, SBOM tracking
✅ Vulnerability Scanning – Trivy, Gosec, Snyk integration
✅ Least Privilege – Minimal RBAC, no elevated capabilities unless required
👉 Read the full security threat model for detailed analysis of risks and mitigations.
System Requirements:
- Kubernetes 1.23+
- Ubuntu 22.04+ or similar Debian-based Linux nodes
- AppArmor enabled on all nodes:
cat /sys/module/apparmor/parameters/enabled # Output should be: Y - Helm 3.0+ (for easy installation)
Verify AppArmor is enabled:
# On each node
sudo aa-status
# Expected output shows: "X profiles loaded" and "X processes are in enforce/complain mode"# Add the Kapparmor Helm repository
helm repo add tuxerrante https://tuxerrante.github.io/kapparmor
helm repo update
# Install with defaults
helm upgrade kapparmor --install \
--namespace kube-system \
--atomic \
--timeout 120s \
tuxerrante/kapparmor
# Or customize values
helm upgrade kapparmor --install \
--namespace kube-system \
--set image.tag=v1.0.0 \
--set app.pollTime=30 \
tuxerrante/kapparmorkubectl apply -f https://github.com/tuxerrante/kapparmor/releases/download/v1.0.0/kapparmor-manifest.yaml1. Create an AppArmor profile ConfigMap:
apiVersion: v1
kind: ConfigMap
metadata:
name: kapparmor-profiles
namespace: kube-system
data:
custom.deny-write-outside-home: |
#include <tunables/global>
profile custom.deny-write-outside-home flags=(attach_disconnected,mediate_deleted) {
#include <abstractions/base>
capability setuid,
capability setgid,
capability dac_override,
/home/** rw,
/tmp/** rw,
/var/tmp/** rw,
deny /etc/** w,
deny /root/** w,
deny / w,
}2. Apply the ConfigMap:
kubectl apply -f apparmor-profiles.yaml3. Deploy workload with the profile:
apiVersion: v1
kind: Pod
metadata:
name: secure-app
annotations:
container.apparmor.security.beta.kubernetes.io/app: localhost/custom.deny-write-outside-home
spec:
containers:
- name: app
image: ubuntu:24.04
command: ["/bin/bash", "-c", "sleep infinity"]4. Verify profile was loaded:
# Check on the node
sudo aa-status | grep custom.deny-write-outside-home
# Or from the pod
kubectl logs -n kube-system -l app=kapparmor | grep "Profile.*loaded"- Polling – Every
POLL_TIMEseconds (default: 30s), Kapparmor checks thekapparmor-profilesConfigMap - Comparison – Identifies new, modified, or deleted profiles by comparing with local state
- Validation – Validates profile syntax before kernel loading:
- Profile name must start with
custom. - Filename must match profile name
- Must contain
profilekeyword and opening brace{ - Path traversal checks on filename
- Profile name must start with
- Loading – Executes
apparmor_parser --replace <profile>for new/updated profiles - Unloading – Executes
apparmor_parser --remove <profile>for deleted profiles - Cleanup – Removes profile files from
/etc/apparmor.d/custom/
┌──────────────────────────────────────┐
│ Kubernetes Control Plane │
│ (ConfigMap: kapparmor-profiles) │
└────────────┬─────────────────────────┘
│
│ (mount via volume)
▼
┌──────────────────────────────────────┐
│ Kapparmor DaemonSet Pod │
│ ┌────────────────────────────────┐ │
│ │ Poll ConfigMap every 30s │ │
│ │ Validate profiles │ │
│ │ Copy to /etc/apparmor.d/custom │ │
│ │ Execute apparmor_parser │ │
│ └────────────────────────────────┘ │
└────────────┬─────────────────────────┘
│
│ (apparmor_parser binary)
▼
┌──────────────────────────────────────┐
│ Host Linux Kernel │
│ (AppArmor module) │
│ /sys/kernel/security/apparmor/ │
└──────────────────────────────────────┘
| Parameter | Default | Description |
|---|---|---|
app.pollTime |
30 |
Polling interval in seconds (1-86400) |
app.configmapPath |
/app/profiles |
ConfigMap mount path |
app.profilesDir |
/etc/apparmor.d/custom |
Host directory for profiles |
image.repository |
ghcr.io/tuxerrante/kapparmor |
Container image |
image.tag |
latest |
Image tag/version |
resources.limits.cpu |
200m |
CPU limit per pod |
resources.limits.memory |
128Mi |
Memory limit per pod |
# values.yaml
app:
pollTime: 30
configmapPath: /app/profiles
profilesDir: /etc/apparmor.d/custom
logLevel: "INFO"
image:
repository: ghcr.io/tuxerrante/kapparmor
tag: "v1.0.0"
pullPolicy: IfNotPresent
resources:
limits:
cpu: 200m
memory: 128Mi
requests:
cpu: 100m
memory: 64Mi
nodeSelector:
kubernetes.io/os: linux-
Profile Naming – Custom profiles MUST start with
custom.prefix and match the filename❌ BAD: myprofile (missing prefix) ✅ GOOD: custom.myprofile (filename must also be custom.myprofile) -
Profile Syntax – Profiles must be valid AppArmor syntax:
✅ REQUIRED: profile custom.name { ... } ❌ NOT SUPPORTED: hat name { ... } (nested profiles) -
Polling Interval – Must be between 1 and 86400 seconds (24 hours)
-
Node State – Start on clean nodes (remove old orphaned profiles first)
# Cleanup before initial deployment sudo rm -f /etc/apparmor.d/custom/* sudo systemctl reload apparmor
-
Pod Dependencies – Always delete pods using a profile before removing the profile from ConfigMap
# BAD: This can crash Kapparmor kubectl delete configmap kapparmor-profiles # GOOD: Delete pods first kubectl delete pod -l app-profile=myprofile kubectl patch configmap kapparmor-profiles --type json -p='[{"op":"remove","path":"/data/custom.myprofile"}]'
Comprehensive testing is documented in docs/testing.md.
Quick test:
# Run Go tests
make test
# Run security checks
make lint
# Deploy to local MicroK8s cluster (if available)
./build/test_on_microk8s.shSee the KAppArmor Demo project for practical examples.
| Document | Purpose |
|---|---|
| ThreatModel.md | Complete security threat model (STRIDE analysis, risk assessment, mitigations) |
| testing.md | Testing strategies and local cluster setup |
| microk8s.md | MicroK8s-specific deployment guide |
| kapparmor-architecture.drawio | Architecture diagrams (editable Drawio format) |
- Kubernetes AppArmor Tutorial – Official K8s guide
- AppArmor Documentation – Ubuntu reference
- AppArmor Profile Reference – Complete profile syntax
- AppArmor Profiles – SUSE documentation
- AppArmor Profiles are easier to learn than SELinux policies and more flexible than Seccomp
- Start with simple restrictive profiles (deny certain paths/capabilities)
- Use
complainmode for testing before enablingenforcemode - The included sample profiles are good starting points
- ✏️ Update
config/configwith new versions (app, chart, Go) - ✏️ Update
charts/kapparmor/Chart.yamlwith matching version - 🧪 Run unit and integration tests (see
Makefile) - ✏️ Update
charts/kapparmor/CHANGELOG.md - 📝 Open PR, get reviews
- ✅ Merge to main
- 🏷️ Create signed Git tag:
git tag -s v1.0.0 - 🚀 GitHub Actions automatically builds and publishes
Note: Commits must be signed (git config commit.gpgsign true)
- 🐛 Found a bug? Open an issue
- 💡 Feature request? Start a discussion
- 📚 Need help? Check the docs
This project is licensed under the Apache 2.0 License.
- 🎨 Logo design by @Noblesix960
- 📝 Inspired by kubernetes/apparmor-loader
- 🔐 Security guidance from Microsoft SDL and OWASP
- ☁️ Cloud-native architecture patterns from CNCF ecosystem
Made with ❤️ for cloud-native security

