Seccomp & AppArmor

Seccomp and AppArmor are Linux security modules that restrict what system calls and resources containers can access. They provide an additional layer of security by limiting the attack surface even if a container is compromised.

Seccomp (Secure Computing Mode)

Seccomp restricts the system calls a process can make. Think of it as a whitelist of allowed system calls—if a process tries to use a system call that’s not on the list, it’s killed.

How Seccomp Works

flowchart TD A[Container Process] --> B{Makes System Call} B --> C{Allowed in Profile?} C -->|Yes| D[System Call Executed] C -->|No| E[Process Killed] style A fill:#e1f5ff style D fill:#e8f5e9 style E fill:#ffebee

Seccomp Profile Types

Kubernetes supports three seccomp profile types:

  1. RuntimeDefault - Uses the container runtime’s default profile
  2. Localhost - Uses a custom profile from the node’s filesystem
  3. Unconfined - No restrictions (not recommended)

Using RuntimeDefault

The simplest and recommended approach:

apiVersion: v1
kind: Pod
metadata:
  name: seccomp-pod
spec:
  securityContext:
    seccompProfile:
      type: RuntimeDefault
  containers:
  - name: app
    image: nginx:latest

Creating Custom Seccomp Profiles

Create a profile file on each node:

{
  "defaultAction": "SCMP_ACT_ERRNO",
  "architectures": ["SCMP_ARCH_X86_64"],
  "syscalls": [
    {
      "names": ["read", "write", "open", "close", "stat"],
      "action": "SCMP_ACT_ALLOW"
    },
    {
      "names": ["execve"],
      "action": "SCMP_ACT_ALLOW",
      "args": [
        {
          "index": 0,
          "value": "/usr/bin/nginx",
          "op": "SCMP_CMP_EQ"
        }
      ]
    }
  ]
}

Save to /var/lib/kubelet/seccomp/profiles/my-profile.json on each node.

Using Custom Profile

apiVersion: v1
kind: Pod
metadata:
  name: custom-seccomp-pod
spec:
  securityContext:
    seccompProfile:
      type: Localhost
      localhostProfile: profiles/my-profile.json
  containers:
  - name: app
    image: nginx:latest

AppArmor

AppArmor restricts what files, network ports, and capabilities a process can access. It’s like a firewall for file and network access.

How AppArmor Works

flowchart TD A[Container Process] --> B{Access Resource} B --> C{Allowed in Profile?} C -->|Yes| D[Access Granted] C -->|No| E[Access Denied] style A fill:#e1f5ff style D fill:#e8f5e9 style E fill:#ffebee

AppArmor Profile Structure

AppArmor profiles define:

  • File system access (read, write, execute)
  • Network access (bind, connect)
  • Capabilities
  • Mount operations

Creating AppArmor Profiles

Create a profile on each node:

# /etc/apparmor.d/k8s-nginx
#include <tunables/global>

profile k8s-nginx flags=(attach_disconnected,mediate_deleted) {
  #include <abstractions/base>
  
  # Allow read access to application files
  /usr/share/nginx/html/** r,
  
  # Deny write access to most files
  deny /usr/share/nginx/html/** w,
  
  # Allow network access
  network,
  
  # Allow binding to port 80
  capability net_bind_service,
}

Load the profile:

apparmor_parser /etc/apparmor.d/k8s-nginx

Using AppArmor in Pods

apiVersion: v1
kind: Pod
metadata:
  name: apparmor-pod
  annotations:
    container.apparmor.security.beta.kubernetes.io/app: localhost/k8s-nginx
spec:
  containers:
  - name: app
    image: nginx:latest

Practical Examples

Seccomp for Web Server

apiVersion: v1
kind: Pod
metadata:
  name: nginx-secure
spec:
  securityContext:
    seccompProfile:
      type: RuntimeDefault
  containers:
  - name: nginx
    image: nginx:latest
    securityContext:
      allowPrivilegeEscalation: false
      capabilities:
        drop:
        - ALL
        add:
        - NET_BIND_SERVICE

AppArmor for Database

apiVersion: v1
kind: Pod
metadata:
  name: postgres-secure
  annotations:
    container.apparmor.security.beta.kubernetes.io/postgres: localhost/k8s-postgres
spec:
  containers:
  - name: postgres
    image: postgres:latest

With AppArmor profile:

# /etc/apparmor.d/k8s-postgres
#include <tunables/global>

profile k8s-postgres flags=(attach_disconnected,mediate_deleted) {
  #include <abstractions/base>
  
  # Allow database files
  /var/lib/postgresql/data/** rw,
  
  # Deny access to system files
  deny /etc/** rw,
  deny /usr/bin/** x,
  
  # Network access
  network,
}

Best Practices

  1. Start with RuntimeDefault - Use the default seccomp profile first
  2. Test thoroughly - Ensure applications work with restrictions
  3. Create custom profiles gradually - Add restrictions incrementally
  4. Document exceptions - If custom profiles are needed, document why
  5. Use both together - Seccomp and AppArmor complement each other
  6. Monitor violations - Watch for denied system calls or file access

Troubleshooting

Seccomp Violations

If a container is killed due to seccomp:

# Check dmesg for seccomp violations
dmesg | grep seccomp

# Check container logs
kubectl logs <pod-name>

AppArmor Denials

Check AppArmor denials:

# On the node
sudo aa-status
sudo cat /var/log/kern.log | grep apparmor

Testing Profiles

Test seccomp profiles:

# Create a test pod
kubectl run test-pod --image=nginx --overrides='
{
  "spec": {
    "securityContext": {
      "seccompProfile": {
        "type": "RuntimeDefault"
      }
    }
  }
}'

# Test system calls
kubectl exec test-pod -- strace -e trace=openat ls

See Also