Skip to main content
RunBook Academy

KubernetesLXIII · Linux Security Controls in KubernetesLinux security controls

Custom seccomp profiles — Localhost and workload-specific rules

Advanced⏱ ~14 minkubectlseccomp-profile-auditor

What you'll learn

  • Author a custom seccomp profile in JSON
  • Load the profile onto nodes via a DaemonSet or bootstrap script
  • Use the `Localhost` profile type in a Pod
  • Generate profiles using `seccomp-profile-auditor` or similar tools

Prerequisites

Verified against Kubernetes 1.34.x · kubeadm 1.34.x · kubectl 1.34.x · etcd 3.6.x · CoreDNS 1.11.x · containerd 1.7.x / 2.x · 2026-08-16

Not yet marked complete on this device.

A custom seccomp profile is a JSON file that whitelists the syscalls a workload needs. The Localhost profile type in Kubernetes references a profile loaded onto the node at a well-known path. This lesson covers the JSON profile format, the authoring workflow, and the operational patterns.

The JSON profile format

A seccomp profile is a JSON document with three sections: defaultAction, architectures, and syscalls:

{
  "defaultAction": "SCMP_ACT_ERRNO",
  "architectures": ["SCMP_ARCH_X86_64"],
  "syscalls": [
    {
      "names": ["read", "write", "openat", "close", "fstat"],
      "action": "SCMP_ACT_ALLOW"
    }
  ]
}

The defaultAction is the action for syscalls not listed: SCMP_ACT_ERRNO (return an error) or SCMP_ACT_KILL (kill the process). architectures limits the profile to specific CPU architectures. syscalls is the whitelist.

flowchart LR
    A[Process] --> B[Syscall]
    B --> C{In whitelist?}
    C -->|yes| D[SCMP_ACT_ALLOW]
    C -->|no| E{defaultAction}
    E -->|ERRNO| F[Return error]
    E -->|KILL| G[SIGKILL]

The profile path

The kubelet loads profiles from /var/lib/kubelet/seccomp/<profile-name>.json. The profile name in the Pod spec maps to the file:

securityContext:
  seccompProfile:
    type: Localhost
    localhostProfile: legacy-app.json
# kubelet reads /var/lib/kubelet/seccomp/legacy-app.json

The profile must be present on every node where the Pod may run. The convention is a DaemonSet that ships the profile to every node:

apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: seccomp-profiles
  namespace: kube-system
spec:
  selector:
    matchLabels:
      name: seccomp-profiles
  template:
    metadata:
      labels:
        name: seccomp-profiles
    spec:
      containers:
      - name: profiles
        image: busybox
        command: ["sleep", "infinity"]
        volumeMounts:
        - name: profiles
          mountPath: /var/lib/kubelet/seccomp
          readOnly: false
      volumes:
      - name: profiles
        hostPath:
          path: /var/lib/kubelet/seccomp
          type: Directory
      initContainers:
      - name: install
        image: myorg/seccomp-profiles:v1
        command: ["cp", "-r", "/profiles/.", "/var/lib/kubelet/seccomp/"]
        volumeMounts:
        - name: profiles
          mountPath: /var/lib/kubelet/seccomp

The DaemonSet copies the profiles to every node’s seccomp directory.

The authoring workflow

Three steps:

  1. Audit syscalls. Run the workload under RuntimeDefault (or Unconfined with syscall tracing) to identify the syscalls the workload needs.
  2. Author the profile. Create the JSON profile with the whitelist.
  3. Test. Run the workload under the custom profile; verify it starts and operates normally.
# 1. Audit syscalls
strace -f -e trace=syscall -p $(pidof myapp) 2>&1 | \
  grep -oP '^[a-z_]+\(' | sort -u

# 2. Generate the profile from the trace
seccomp-profile-auditor \
  --input trace.txt \
  --output /var/lib/kubelet/seccomp/legacy-app.json

# 3. Test
kubectl apply -f pod-with-localhost-profile.yaml

A typical profile

A profile for a Java application that does network I/O:

{
  "defaultAction": "SCMP_ACT_ERRNO",
  "architectures": ["SCMP_ARCH_X86_64"],
  "syscalls": [
    {
      "names": [
        "read", "write", "openat", "close", "fstat",
        "lseek", "mmap", "mprotect", "munmap", "brk",
        "rt_sigaction", "rt_sigprocmask", "rt_sigreturn",
        "ioctl", "select", "poll", "epoll_wait",
        "socket", "connect", "accept", "bind", "listen",
        "recvfrom", "sendto", "setsockopt", "getsockopt",
        "clone", "execve", "exit_group", "exit",
        "getpid", "getuid", "geteuid", "getgid", "getegid",
        "setuid", "setgid", "setgroups",
        "stat", "fstat", "lstat", "access", "readlink",
        "gettimeofday", "clock_gettime",
        "futex", "nanosleep"
      ],
      "action": "SCMP_ACT_ALLOW"
    }
  ]
}

The profile allows ~50 syscalls (the common ones for a network I/O application) and rejects all others via SCMP_ACT_ERRNO.

Operational discipline

  1. Profile per workload. Each workload has its own profile; profiles are not shared unless the workloads are identical.
  2. Version-controlled profiles. Profiles are in Git; the DaemonSet is updated when a profile changes.
  3. Audit the profiles. A profile that allows kexec_load or reboot is a Critical finding.
  4. Test before deploying. A workload under a custom profile is tested in a staging environment before production.

Production failure modes

  1. Profile not on every node. A Pod scheduled on a node without the profile is rejected. The fix is the DaemonSet.
  2. Profile too restrictive. The workload makes a syscall not in the whitelist; the process is killed. The fix is to add the syscall (or to use RuntimeDefault if the workload has no custom needs).
  3. Profile too permissive. The profile allows kexec_load or reboot. The fix is to remove the syscall from the profile.
  4. No audit of profiles. A profile is added but never reviewed. The fix is a CI check that compares the profile’s whitelist against the runtime’s safe baseline.

Cross-course references

  • The Linux course covers the BPF filter mechanism that seccomp uses.
  • The Observability course covers the audit log entries for seccomp violations.

Quiz

Knowledge check · 4 questions

  1. Q1. What is the right `defaultAction` for a custom seccomp profile?

  2. Q2. A `Localhost` seccomp profile is automatically distributed to every node by the kubelet.

  3. Q3. Your custom seccomp profile is too restrictive. The workload is killed at startup with `bad syscall`. Walk the response.

    The profile whitelists 30 syscalls. The workload needs 35. The 5 missing syscalls cause the workload to fail. The audit log shows `seccomp: blocked syscall` for each missing one.

  4. Q4. What path does the kubelet read `Localhost` seccomp profiles from, and how do you ship a profile to every node?

Passing score: 75%. Answers are checked in this browser.

Production discipline

Custom seccomp profiles are the right primitive for workloads with specific syscall needs. A defensible seccomp programme uses RuntimeDefault for most workloads, Localhost for the exceptions, and ships profiles via a DaemonSet. The profiles are version-controlled, audited for dangerous syscalls, and tested before deployment. A cluster whose profiles are all audited and shipped via a DaemonSet has a syscall-surface security programme that is auditable.