Skip to content

Generate a seccomp profile from a workload

A useful seccomp profile is one your workload actually exercises - not one a security analyst guessed at. The seccomp-gen operator records the syscall surface of a target Pod through the Tetragon bridge during a fixed time window, then emits a SeccompTrainingProfile CR carrying a complete OCI seccomp.json. You apply the profile to your workload as a separate, explicit step.

  • ugallu umbrella chart installed with seccomp-gen and the tetragon-bridge subchart enabled.
  • Tetragon installed and the bridge service reachable at ugallu-tetragon-bridge.ugallu-system-privileged.svc:50051.
  • A workload with stable behaviour. New deployments still warming up are bad training subjects - capture them once they’ve reached steady state.
Terminal window
kubectl -n ugallu-system get deploy ugallu-seccomp-gen
kubectl -n ugallu-system-privileged get service ugallu-tetragon-bridge

Pick a Pod (or a label selector across multiple replicas). For a single pod:

Terminal window
kubectl -n payments get pods -l app=api
# api-canary-7c4d 1/1 Running
train.yaml
apiVersion: security.ugallu.io/v1alpha1
kind: SeccompTrainingRun
metadata:
name: payments-api-canary
namespace: ugallu-system
spec:
targetSelector:
matchLabels: { app: api, tier: canary }
targetNamespace: payments
duration: 15m
replicaRatio: 50
defaultAction: SCMP_ACT_ERRNO
Terminal window
kubectl apply -f train.yaml
kubectl -n ugallu-system get seccomptrainingruns -w

The run walks Pending -> Running -> Succeeded once the duration expires. While running, the engine subscribes to the bridge filtered by the matched Pod UIDs and accumulates the unique syscall surface, debounced with a 30-second sliding window so a flurry of fork/exec at workload start doesn’t drown out the steady-state surface.

Terminal window
kubectl -n ugallu-system get seccomptrainingprofiles
# NAME AGE SYSCALLS
# payments-api-canary 15m 42
kubectl -n ugallu-system get seccomptrainingprofile payments-api-canary \
-o jsonpath='{.spec.profileJSON}' | jq .

The output is a runtime-spec-compatible JSON:

{
"defaultAction": "SCMP_ACT_ERRNO",
"syscalls": [
{
"names": ["read", "write", "openat", "close", "fstat",
"mmap", "munmap", "brk", "rt_sigaction", "futex",
"clone", "execve", "exit_group"],
"action": "SCMP_ACT_ALLOW"
}
]
}

The operator does not apply the profile to your workload for you - that’s an explicit human step. There are two ways:

Option A - localhost profile (most permissive cluster-wide)

Section titled “Option A - localhost profile (most permissive cluster-wide)”

Drop the JSON onto every node where the workload runs (typically under /var/lib/kubelet/seccomp/profiles/), then reference it from the pod template:

spec:
template:
spec:
securityContext:
seccompProfile:
type: Localhost
localhostProfile: payments-api-canary.json

A DaemonSet that mirrors SeccompTrainingProfile CRs into the node directory is on the roadmap.

Option B - inline RuntimeDefault override (Kubernetes 1.27+)

Section titled “Option B - inline RuntimeDefault override (Kubernetes 1.27+)”

Some clusters allow Localhost profiles to be loaded as ConfigMaps via container runtime extensions. Check your runtime’s docs. Failing that, copy the RuntimeDefault profile and merge the training output by hand.

Roll the new profile to one canary first. The profile is deny-by-default with an allowlist of observed syscalls - if your workload executes a code path that wasn’t exercised during training, the kernel will SIGSYS the offending syscall. Treat training duration like a coverage exercise: the longer the run + broader the workload, the safer the resulting profile.

Watch for SeccompTrainingFailed SEs - that fires when the training engine couldn’t attach to the bridge, or the window expired with no observed syscalls (target wasn’t doing anything).

Terminal window
kubectl -n ugallu-system delete seccomptrainingrun payments-api-canary

The corresponding SeccompTrainingProfile is owned by the run via ownerRef and is GC’d by Kubernetes once the run is deleted - so hold onto the profile JSON externally (committed to the GitOps repo, ideally) before deleting the run.