README
¶
Kubernetes Security Profiles Merger
A standalone Go library for merging security profiles (seccomp, AppArmor, Landlock) used by Kubernetes CRI runtimes and the Security Profiles Operator.
Overview
This library provides two core operations on security profiles:
- Intersect: Produces an effective profile that permits an operation only if all input profiles permit it. Used by CRI runtimes (CRI-O, containerd) to merge OCI-pulled profiles with node baselines per KEP-6061.
- Union: Produces a profile that permits an operation if any input profile permits it. Used by the Security Profiles Operator to merge recorded profiles.
Installation
go get github.com/saschagrunert/security-profiles-merger
Packages
seccomp
Seccomp profile merge operating on specs.LinuxSeccomp from the
OCI runtime-spec.
import "github.com/saschagrunert/security-profiles-merger/seccomp"
Functions:
seccomp.Intersect(profiles ...*specs.LinuxSeccomp) (*specs.LinuxSeccomp, error)- Merge profiles via intersection. The resulting profile permits a syscall only if all input profiles permit it. For each syscall present in multiple profiles, the more restrictive action is chosen.seccomp.Union(profiles ...*specs.LinuxSeccomp) (*specs.LinuxSeccomp, error)- Merge profiles via union. The resulting profile permits a syscall if any input profile permits it. For each overlapping syscall, the less restrictive action is chosen.seccomp.UnionSyscalls(left, right []specs.LinuxSyscall) []specs.LinuxSyscall- Merge two bare syscall slices via union without a profile-level DefaultAction. UnlikeUnion, no entries are elided and unmatched entries keep their original action. Multi-name entries are normalized to one-name-per-entry.seccomp.IntersectSyscalls(left, right []specs.LinuxSyscall) []specs.LinuxSyscall- Merge two bare syscall slices via intersection without a profile-level DefaultAction. Syscalls present in only one list are dropped. Multi-name entries are normalized to one-name-per-entry and the result is sorted by name.seccomp.MoreRestrictive(first, second specs.LinuxSeccompAction) specs.LinuxSeccompAction- Returns the more restrictive of two seccomp actions.seccomp.LessRestrictive(first, second specs.LinuxSeccompAction) specs.LinuxSeccompAction- Returns the less restrictive of two seccomp actions.seccomp.Validate(profile *specs.LinuxSeccomp) error- Checks that a profile contains only known actions and that every syscall entry has at least one name.seccomp.ValidateStrict(profile *specs.LinuxSeccomp) error- Performs all checks from Validate and additionally detects duplicate syscall names across entries. Use Validate for merge inputs and ValidateStrict for user-authored profiles.seccomp.FormatProfile(profile *specs.LinuxSeccomp) string- Returns a human-readable representation of a seccomp profile.
Errors:
seccomp.ErrNoProfiles- returned when no profiles are provided.seccomp.ErrNilProfile- returned when a nil profile is provided.seccomp.ErrUnknownAction- returned when a profile contains an unrecognized action.seccomp.ErrEmptySyscallNames- returned when a syscall entry has no names.seccomp.ErrEmptySyscallName- returned when a syscall entry contains an empty string in its name list.seccomp.ErrDuplicateSyscallName- returned by ValidateStrict when the same syscall name appears in multiple entries.
Merge semantics:
- Default actions are merged using the same restrictiveness comparison as syscalls.
- Architectures: intersection keeps only architectures present in all profiles; union combines all. An empty architecture list is treated as "unspecified" and defers to the other profile. Per the OCI runtime-spec, empty means "native architecture only", but the native architecture is unknown at merge time. Callers that need precise architecture intersection should populate the native architecture explicitly before merging.
- Flags: intersection keeps only flags present in all profiles; union combines all. An empty flag list is treated as "unspecified" and defers to the other profile during intersection, matching the architecture behavior.
- Argument filters: during intersection, non-identical argument filters result
in a conservative denial (
SCMP_ACT_KILL_PROCESS). During union, argument filters from both sides are combined. When only one side has argument filters, intersection keeps them and union drops them. DefaultErrnoRetis taken from whichever profile's default action is selected. When both profiles share the same action, the earlier (leftmost) profile'sDefaultErrnoRetwins. The same applies to per-syscallErrnoRet.ListenerPathandListenerMetadataare taken from the first profile.
Action restrictiveness ordering (most to least restrictive):
KILL_PROCESS > KILL_THREAD > TRAP > ERRNO > NOTIFY > TRACE > LOG > ALLOW
Unknown actions are treated as maximally restrictive.
apparmor
AppArmor profile merge using structured profile types defined in this package.
import "github.com/saschagrunert/security-profiles-merger/apparmor"
Functions:
apparmor.Intersect(profiles ...*Profile) (*Profile, error)- Merge profiles via intersection. Capabilities, executables, libraries, and filesystem paths are intersected; boolean network permissions use AND semantics.apparmor.Union(profiles ...*Profile) (*Profile, error)- Merge profiles via union. All rule types are combined; boolean network permissions use OR semantics.apparmor.Validate(profile *Profile) error- Checks that no path appears in multiple filesystem categories (e.g. both read-only and write-only).apparmor.ValidateStrict(profile *Profile) error- Performs all checks from Validate and additionally verifies that no path appears more than once in AllowedExecutables or AllowedLibraries. Use Validate for merge inputs and ValidateStrict for user-authored profiles.apparmor.FormatProfile(profile *Profile) string- Returns a human-readable representation of an AppArmor profile.
Errors:
apparmor.ErrNoProfiles- returned when no profiles are provided.apparmor.ErrNilProfile- returned when a nil profile is provided.apparmor.ErrDuplicatePath- returned when a path appears in multiple filesystem categories.apparmor.ErrDuplicatePathInCategory- returned when a path appears more than once within the same filesystem category.apparmor.ErrDuplicateCapability- returned when the same capability appears more than once in AllowedCapabilities.apparmor.ErrEmptyPath- returned when a path rule contains an empty string.apparmor.ErrEmptyCapability- returned when a capability entry is an empty string.apparmor.ErrDuplicateExecutablePath- returned by ValidateStrict when the same path appears more than once in AllowedExecutables or AllowedLibraries.
Types:
Profile- Top-level profile containing all rule sections.CapabilityRules- Allowed Linux capabilities.ExecutableRules- Allowed executables and libraries.FilesystemRules- Read-only, write-only, and read-write path rules.NetworkRules- Raw socket access and protocol permissions.AllowedProtocols- TCP/UDP protocol permissions.
Profile, ExecutableRules, FilesystemRules, NetworkRules, and
CapabilityRules implement fmt.Stringer for human-readable formatting.
Nil vs empty semantics: A nil field means "unspecified" and defers to the
other profile during merge. A non-nil field with empty contents means "explicitly
no permissions". For example, intersecting {caps: [NET_ADMIN]} with
{caps: nil} yields [NET_ADMIN], while intersecting with {caps: []} yields
[].
Filesystem merge: Paths are expanded into read/write permission pairs, merged
per path (AND for intersection, OR for union), and collapsed back into
read-only, write-only, and read-write lists. A read-write path intersected with
a read-only path becomes read-only (only the shared permission survives). A
read-only path in one profile and write-only in the other is dropped on
intersection (no shared permissions) but becomes read-write on union. When two
non-nil filesystem rule sets produce no overlapping paths after intersection, the
result is a non-nil empty FilesystemRules (preserving the nil-vs-empty
distinction).
landlock
Landlock profile merge for Linux unprivileged sandboxing rulesets.
import "github.com/saschagrunert/security-profiles-merger/landlock"
Functions:
landlock.Intersect(profiles ...*Profile) (*Profile, error)- Merge profiles via intersection. HandledAccessFS and HandledAccessNet are unioned (more handled rights = more restrictive). Path and network rules are intersected per key.landlock.Union(profiles ...*Profile) (*Profile, error)- Merge profiles via union. HandledAccessFS and HandledAccessNet are intersected (fewer handled rights = less restrictive). Path and network rules are unioned per key.landlock.Validate(profile *Profile) error- Checks that a profile contains only known access rights, has no empty paths, and has no duplicate path or port rules.landlock.ValidateStrict(profile *Profile) error- Performs all checks from Validate and additionally verifies that every rule's access rights are a subset of the corresponding handled access set. Use Validate for merge inputs and ValidateStrict for user-authored profiles.landlock.FormatProfile(profile *Profile) string- Returns a human-readable representation of a Landlock profile.
Errors:
landlock.ErrNoProfiles- returned when no profiles are provided.landlock.ErrNilProfile- returned when a nil profile is provided.landlock.ErrUnknownRight- returned when a profile contains an unrecognized access right.landlock.ErrDuplicateRule- returned when a profile contains multiple rules for the same path or port.landlock.ErrEmptyPath- returned when a path rule has an empty path string.landlock.ErrUnhandledRight- returned by ValidateStrict when a rule grants an access right not listed in the handled access set.landlock.ErrDuplicateRight- returned when the same access right appears more than once in a handled set or rule.
Types:
Profile- Top-level Landlock ruleset containing handled access sets and rules.FSAccessRight- Filesystem access right (execute, read_file, write_file, etc.).NetAccessRight- Network access right (bind_tcp, connect_tcp, bind_udp, connect_udp, sendto_udp).PathRule- Per-path filesystem access rights.NetRule- Per-port network access rights.
Profile, PathRule, and NetRule implement fmt.Stringer for human-readable
formatting.
Handled access semantics: Landlock has inverted merge semantics for handled-access sets compared to rules. Unhandled access rights are implicitly allowed, so intersection unions the handled sets (handling more rights makes the ruleset more restrictive), and union intersects them (handling fewer rights makes it less restrictive).
Path and network rules: During intersection, rules for entries present in both profiles have their access rights intersected. Entries only in one profile are dropped if the access right is handled by the other profile, or kept if unhandled. During union, access rights are combined for matching entries, and all non-matching entries are kept.
Usage
CRI runtime: merge OCI-pulled profile with node baseline (intersection)
effective, err := seccomp.Intersect(nodeBaseline, ociPulledProfile)
if err != nil {
return err
}
// effective permits only syscalls allowed by both profiles
SPO: combine recorded profiles (union)
combined, err := seccomp.Union(recording1, recording2, recording3)
if err != nil {
return err
}
// combined permits all syscalls seen in any recording
AppArmor profile merge
aaEffective, err := apparmor.Intersect(baseProfile, ociProfile)
aaCombined, err := apparmor.Union(recorded1, recorded2)
Landlock profile merge
llEffective, err := landlock.Intersect(baseRuleset, ociRuleset)
llCombined, err := landlock.Union(recorded1, recorded2)
CLI
The spm command-line tool provides profile merging and validation without
writing Go code.
Install
Download a pre-built binary from the releases page. Each release includes cosign-signed checksums, SBOMs, and build provenance attestations.
To verify a downloaded binary:
# Verify checksums signature
cosign verify-blob \
--bundle checksums.txt.sigstore.json \
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
--certificate-identity-regexp 'github.com/saschagrunert/security-profiles-merger' \
checksums.txt
# Verify binary against signed checksums
sha256sum -c checksums.txt
# Or verify build provenance directly
gh attestation verify spm_*_linux_amd64 -R saschagrunert/security-profiles-merger
Or install from source:
go install github.com/saschagrunert/security-profiles-merger/cmd/spm@latest
Or build statically from source:
make build # produces build/spm
Merge profiles
spm merge --type seccomp --strategy intersect baseline.json oci.json
spm merge --type apparmor --strategy union recording1.json recording2.json
Profiles can also be read from stdin as a JSON array:
cat profiles.json | spm merge --type landlock --strategy intersect
Use - to read from stdin alongside file arguments:
spm merge --type seccomp --strategy intersect baseline.json - < recording.json
Use --format=human for human-readable output via FormatProfile:
spm merge --type seccomp --strategy intersect --format human a.json b.json
Validate profiles
spm validate --type seccomp profile.json
spm validate --type apparmor --strict user-profile.json
Profiles can also be read from stdin:
cat profile.json | spm validate --type seccomp
Use --format=human for human-readable output:
spm validate --type seccomp --format human profile.json
Validation outputs the profile on success (exit 0) or prints errors to
stderr on failure (exit 1). Use --strict for stricter checks intended
for user-authored profiles.
Community, discussion, contribution, and support
Learn how to engage with the Kubernetes community on the community page.
You can reach the maintainers of this project at the SIG Node mailing list.
Code of Conduct
Participation in the Kubernetes community is governed by the Kubernetes Code of Conduct.
Directories
¶
| Path | Synopsis |
|---|---|
|
Package apparmor provides merge operations for AppArmor profiles.
|
Package apparmor provides merge operations for AppArmor profiles. |
|
cmd
|
|
|
spm
command
Package main implements the spm CLI for merging and validating security profiles.
|
Package main implements the spm CLI for merging and validating security profiles. |
|
internal
|
|
|
merge
Package merge provides shared utilities for security profile merge operations.
|
Package merge provides shared utilities for security profile merge operations. |
|
Package landlock provides merge operations for Landlock profiles.
|
Package landlock provides merge operations for Landlock profiles. |
|
Package seccomp provides merge operations for seccomp profiles.
|
Package seccomp provides merge operations for seccomp profiles. |