Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
70 commits
Select commit Hold shift + click to select a range
c496723
feat(sandboxpolicy): drive the privilege gadget from the sandbox poli…
AlonLiwsky Nov 2, 2026
6c27bb0
fix(enforce_privilege): address review comments
AlonLiwsky Nov 2, 2026
c6b7570
feat(sandboxpolicy): log whether a privilege bind found its slot ready
AlonLiwsky Nov 2, 2026
1092ae8
docs(enforce_privilege): document the known limits
AlonLiwsky Nov 2, 2026
141b7e0
docs(enforce_privilege): correct the no-audit limit
AlonLiwsky Nov 2, 2026
02c40ef
fix(sandboxpolicy): read the process cgroup in the host cgroup namespace
AlonLiwsky Nov 2, 2026
e716b2d
fix(sandboxpolicy): pass host-mntns-id under the oci ebpf operator pr…
AlonLiwsky Nov 2, 2026
2e8a266
fix(enforcement): report cgroup listing errors other than a removed c…
AlonLiwsky Nov 2, 2026
06c4336
fix(sandboxpolicy): address review comments on the controller connection
AlonLiwsky Nov 2, 2026
6cb5100
refactor(enforce_privilege): make the gadget and common library portable
AlonLiwsky Nov 3, 2026
c41c144
refactor(sandboxpolicy): translate SandboxPolicy privilege sections i…
AlonLiwsky Nov 3, 2026
f272303
fix(sandboxpolicy): refuse privilege enforcement when BPF LSM is not …
AlonLiwsky Nov 3, 2026
ed1ee7c
docs(enforce_privilege): mountFsType does not cover bind mounts
AlonLiwsky Nov 3, 2026
09b5fdc
test(enforcement): only ../common/ includes may leave the gadget folder
AlonLiwsky Nov 3, 2026
128a014
test(sandboxpolicy): fail on an unexpected sync error in the LSM test
AlonLiwsky Nov 3, 2026
da81412
test(enforce_privilege): the kernel test takes an absolute cgroup v2 …
AlonLiwsky Nov 3, 2026
d0df3d4
test(sandboxpolicy): the kernel test takes an absolute cgroup v2 path
AlonLiwsky Nov 3, 2026
ebb52fd
test(enforcement): include guard also catches spaced, absolute and ne…
AlonLiwsky Nov 3, 2026
32f5277
fix(enforce_privilege): refuse a fork whose child cannot be armed in …
AlonLiwsky Nov 4, 2026
164ac33
test(merge): shared merge contract and test vectors
AlonLiwsky Nov 4, 2026
a2c1d9d
fix(sandboxpolicy): a tracking fault leaves privilege protection unco…
AlonLiwsky Nov 4, 2026
6605531
fix(enforcement): a same-slot retry arms again after a failed arming
AlonLiwsky Nov 4, 2026
4bc6785
fix(enforce_privilege): reject clone and clone3 rules without flags
AlonLiwsky Nov 4, 2026
772f07e
test(sandboxpolicy): a privilege rule on clone needs namespace flags
AlonLiwsky Nov 4, 2026
15828d0
fix(sandboxpolicy): adding a privilege policy does not restart exec
AlonLiwsky Nov 4, 2026
7abe05b
test(enforcement): a slot move retry arms again after a failed rollback
AlonLiwsky Nov 4, 2026
b2c4b4f
feat(sandboxpolicy): bind the pod cgroup before the containers start
AlonLiwsky Nov 3, 2026
d3ad5d5
fix(sandboxpolicy): learn of scheduled pods without the watcher cooldown
AlonLiwsky Nov 3, 2026
eb2926c
fix(sandboxpolicy): keep pod bindings in step with pod and policy cha…
AlonLiwsky Nov 3, 2026
1074f1d
fix(sandboxpolicy): load the privilege gadget before the first container
AlonLiwsky Nov 4, 2026
ab230ec
fix(sandboxpolicy): address review findings on pod bindings
AlonLiwsky Nov 4, 2026
5340fd6
fix(enforcement): arm the group leader of a thread that cannot be opened
AlonLiwsky Nov 4, 2026
ede3e9e
fix(enforce_privilege): a slot enforces only when one of its entries …
AlonLiwsky Nov 4, 2026
43b2c9c
docs(enforce_privilege): compat mknod and leaving the bound subtree
AlonLiwsky Nov 4, 2026
0fab6d4
Merge feat/privilege-gadget (#1067) into feat/privilege-controller
AlonLiwsky Nov 4, 2026
056e6d6
Merge feat/privilege-gadget (#1067) into feat/privilege-controller
AlonLiwsky Nov 4, 2026
75a8f84
fix(sandboxpolicy): heal tracking faults at most every 30 s
AlonLiwsky Nov 4, 2026
1529726
fix(sandboxpolicy): prepare again on a new namespace; lighter pod poll
AlonLiwsky Nov 4, 2026
4afc7c5
Merge feat/privilege-gadget (#1067) into feat/privilege-controller
AlonLiwsky Nov 5, 2026
e31abf9
Merge feat/privilege-gadget (#1067) into feat/privilege-controller
AlonLiwsky Nov 5, 2026
9840fdf
fix(sandboxpolicy): pod plans use the per-class errors of #1054
AlonLiwsky Nov 5, 2026
7a9f1ef
Merge feat/privilege-gadget (#1067) into feat/privilege-controller
AlonLiwsky Nov 5, 2026
01a4ce5
Merge feat/privilege-gadget (#1067) into feat/privilege-controller
AlonLiwsky Nov 5, 2026
f03a349
Merge feat/privilege-gadget (#1067) into feat/privilege-controller
AlonLiwsky Nov 5, 2026
7bc457e
Merge feat/privilege-gadget (#1067) into feat/privilege-controller
AlonLiwsky Nov 5, 2026
5ec6744
fix(sandboxpolicy): translate privilege rules with the new storage API
AlonLiwsky Nov 5, 2026
a8ef860
style(sandboxpolicy): gofmt the privilege backend test
AlonLiwsky Nov 5, 2026
47e327d
Merge feat/privilege-gadget (#1067) into feat/privilege-controller
AlonLiwsky Nov 5, 2026
8c625a7
Merge feat/privilege-gadget (#1067) into feat/privilege-controller
AlonLiwsky Nov 6, 2026
718feed
fix(sandboxpolicy): pod bindings keep their last-good plan
AlonLiwsky Nov 5, 2026
08b627e
Merge feat/privilege-gadget (#1067) into feat/privilege-controller
AlonLiwsky Nov 8, 2026
5c2a65e
fix(sandboxpolicy): pass privilege origins and the default mode to th…
AlonLiwsky Nov 8, 2026
47c6386
feat(sandboxpolicy): report the applied generation and a true tier pe…
AlonLiwsky Nov 9, 2026
bc0228a
fix(sandboxpolicy): keep tier and applied generation true in edge cases
AlonLiwsky Nov 9, 2026
a591caa
chore: pin storage to the node status API change
AlonLiwsky Nov 9, 2026
9f99a2f
fix(sandboxpolicy): count pod cgroup bindings in node status
AlonLiwsky Nov 9, 2026
54f943c
fix(sandboxpolicy): node status stale, message and revision details
AlonLiwsky Nov 9, 2026
2f1b68e
chore: pin storage to the merged node status API change
AlonLiwsky Nov 9, 2026
005c649
Merge branch 'feat/privilege-gadget' into feat/privilege-controller
AlonLiwsky Nov 10, 2026
9fb43b5
Merge branch 'feat/privilege-gadget' into feat/privilege-controller
AlonLiwsky Nov 10, 2026
20e75cc
Merge branch 'feat/privilege-gadget' into feat/privilege-controller
AlonLiwsky Nov 10, 2026
3544f77
fix(sandboxpolicy): count a pod binding only where witness counters c…
AlonLiwsky Nov 10, 2026
10d7a26
fix(sandboxpolicy): prepare the privilege slot before exec commits
AlonLiwsky Nov 10, 2026
85d9227
Merge branch 'feat/privilege-gadget' into feat/privilege-controller
AlonLiwsky Nov 10, 2026
e5d42e2
fix(sandboxpolicy): privilege reports Ready only on a qualified kernel
AlonLiwsky Nov 10, 2026
cc3cef5
fix(sandboxpolicy): keep a privilege withdraw pending while the insta…
AlonLiwsky Nov 10, 2026
b73a4bf
fix(sandboxpolicy): review follow-ups on pod confirmation, pending wi…
AlonLiwsky Nov 10, 2026
033fa79
fix(sandboxpolicy): reserve only a prepared slot that exists; recover…
AlonLiwsky Nov 10, 2026
1284c1b
fix(sandboxpolicy): a partial privilege removal never attests its old…
AlonLiwsky Nov 10, 2026
01fd141
Merge branch 'feat/privilege-gadget' into feat/privilege-controller
AlonLiwsky Nov 10, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -577,6 +577,7 @@ func main() {
Kubernetes: k8sClient.GetKubernetesClient(), Dynamic: k8sClient.GetDynamicClient(),
Containers: mainHandler.GetContainerCollection(), Tracers: mainHandler.GetTracerCollection(),
NodeName: cfg.NodeName, AgentNamespace: cfg.NamespaceName, ImageName: cfg.SandboxPolicyImage, ImageArchive: "tracers.tar",
PrivilegeImageName: cfg.SandboxPolicyPrivilegeImage, QualifiedKernels: cfg.SandboxPolicyQualifiedKernels,
ContainerdSocket: cfg.SandboxPolicyContainerdSocket, ControlRoot: cfg.SandboxPolicyControlRoot, MaxOwners: cfg.SandboxPolicyMaxOwners,
Sources: sandboxintegration.NewProfileResolver(storageClient.GetStorageClient()),
Receivers: thirdPartyTracers.ThirdPartyEventReceivers,
Expand Down
41 changes: 41 additions & 0 deletions docs/features/sandbox-policy.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,47 @@ This change does not grant chart RBAC, change a chart version or store policy in
SQLite/files. Kubernetes owns the CRD objects. Per-policy/node status resources
are output; the agent does not write parent aggregate status.

Each report carries `appliedGeneration`, the oldest policy generation still
installed on the node, counting container bindings and privilege pod cgroup
bindings. It stays on the last-good generation while a newer revision is
rejected, and is absent when no binding enforces the policy. `stale` is set only
when an installed binding lags the desired generation or its protection cannot be
confirmed: a failure that leaves the current generation enforced is `Ready=False`
with `stale: false`. At
startup the agent reads the host's active LSM list once. Every section enforces
through BPF LSM, so when `bpf` is missing every report says `tier: none`, and
every enabled policy reports `Ready=False` with reason `BPFLSMInactive`.

A privilege pod cgroup binding counts only where the witness counters confirm
it: the pod was bound before any of its containers existed, in the instance
that enforces now, and its slot has no tracking fault. "Its containers exist"
comes from the pod's API status and from the container collection (by pod UID
and by cgroup path); a container created after that snapshot is seen at the
next call. A pod whose containers already exist gets no pod binding; its
containers rely on their own bindings. A bound pod whose plan changes later
moves to its new slot, unconfirmed. While the instance is not ready, every pod
binding is unconfirmed.
A pod binding whose slot has a fault makes the policy report
`ProtectionUnconfirmed`. For a Strict slot a node tracking fault (a lost child
mark of a `CLONE_INTO_CGROUP` fork anywhere on the node) is a fault too; a new
instance clears it; node faults alone start a new instance at most every 10
minutes, because their cause may persist.

The privilege section reports Ready only on a qualified kernel:
`sandboxPolicyQualifiedKernels` lists kernel releases (`uname -r`); unset or empty uses
the built-in list of kernels where the kernel tests passed, today the AL2023 amd64
fleet kernel `6.12.113-138.209.amzn2023.x86_64` (`6.18.56-125.164.amzn2023.x86_64`
joins after its run), and `["*"]` admits every kernel. On another
kernel the section still enforces, but every privilege policy reports
`Ready=False` with reason `PrivilegeKernelUnqualified`.

A privilege change commits in two phases: the privilege slot is prepared first
(this can replace the node instance and binds nothing), then exec commits, then
the privilege bind is a live map write. A failed prepare leaves both sections on
their last-good plan. Removing the privilege section withdraws it first: a
withdraw on an instance that is not ready stays pending and is retried, and exec
does not change meanwhile.

## Reconciliation and failure behavior

Configuration watch events are **refresh hints**, not trusted queued payloads.
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ require (
github.com/kubescape/backend v0.0.39
github.com/kubescape/go-logger v0.0.35
github.com/kubescape/k8s-interface v0.0.214
github.com/kubescape/storage v0.0.388-0.20261105083812-136b4c98606f
github.com/kubescape/storage v0.0.388-0.20261109155453-fa55b25a70a3
github.com/kubescape/workerpool v0.0.0-20250526074519-0e4a4e7f44cf
github.com/moby/sys/mountinfo v0.7.2
github.com/opcoder0/fanotify v0.4.2
Expand Down
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -1459,6 +1459,8 @@ github.com/kubescape/storage v0.0.352-0.20261013133511-eb6406e01cb9 h1:pPFehIHOZ
github.com/kubescape/storage v0.0.352-0.20261013133511-eb6406e01cb9/go.mod h1:OSYXj9TzVAuuz+Gn2bIzp3Vg8rofTPH7x5Asek0N7mE=
github.com/kubescape/storage v0.0.388-0.20261105083812-136b4c98606f h1:KTA6ctZBLIi+XRZwKFtl8etQZOTPciSy/nfAtBizzlc=
github.com/kubescape/storage v0.0.388-0.20261105083812-136b4c98606f/go.mod h1:z3uiLdH1O3+eNiKuEOOalJy9uqH7ggNepaUSSWWm5ps=
github.com/kubescape/storage v0.0.388-0.20261109155453-fa55b25a70a3 h1:oSyi5Tiixb+25/HMG03DiKtmiQiyQbuXs0p1F/WKfik=
github.com/kubescape/storage v0.0.388-0.20261109155453-fa55b25a70a3/go.mod h1:z3uiLdH1O3+eNiKuEOOalJy9uqH7ggNepaUSSWWm5ps=
github.com/kubescape/syft v1.32.0-ks.2 h1:xdUksUmKEyyVKsTfJDYW8Z5HawVJtelsUolPOsWtDx0=
github.com/kubescape/syft v1.32.0-ks.2/go.mod h1:E6Kd4iBM2ljUOUQvSt7hVK6vBwaHkMXwcvBZmGMSY5o=
github.com/kubescape/workerpool v0.0.0-20250526074519-0e4a4e7f44cf h1:hI0jVwrB6fT4GJWvuUjzObfci1CUknrZdRHfnRVtKM0=
Expand Down Expand Up @@ -1863,6 +1865,7 @@ github.com/sebdah/goldie/v2 v2.8.0 h1:dZb9wR8q5++oplmEiJT+U/5KyotVD+HNGCAc5gNr8r
github.com/sebdah/goldie/v2 v2.8.0/go.mod h1:oZ9fp0+se1eapSRjfYbsV/0Hqhbuu3bJVvKI/NNtssI=
github.com/seccomp/libseccomp-golang v0.11.0 h1:SDkcBRqGLP+sezmMACkxO1EfgbghxIxnRKfd6mHUEis=
github.com/seccomp/libseccomp-golang v0.11.0/go.mod h1:5m1Lk8E9OwgZTTVz4bBOer7JuazaBa+xTkM895tDiWc=
github.com/seccomp/libseccomp-golang v0.11.1 h1:wuk4ZjSx6kyQII4rj6G6fvVzRHQaSiPvccJazDagu4g=
github.com/seccomp/libseccomp-golang v0.11.1/go.mod h1:5m1Lk8E9OwgZTTVz4bBOer7JuazaBa+xTkM895tDiWc=
github.com/secure-systems-lab/go-securesystemslib v0.10.0 h1:l+H5ErcW0PAehBNrBxoGv1jjNpGYdZ9RcheFkB2WI14=
github.com/secure-systems-lab/go-securesystemslib v0.10.0/go.mod h1:MRKONWmRoFzPNQ9USRF9i1mc7MvAVvF1LlW8X5VWDvk=
Expand Down Expand Up @@ -2917,6 +2920,7 @@ k8s.io/klog/v2 v2.140.0/go.mod h1:o+/RWfJ6PwpnFn7OyAG3QnO47BFsymfEfrz6XyYSSp0=
k8s.io/kms v0.35.0/go.mod h1:VT+4ekZAdrZDMgShK37vvlyHUVhwI9t/9tvh0AyCWmQ=
k8s.io/kms v0.37.0 h1:WDW3pSc5yFB1Od4YXXL3i5oK+vXI2v++LAV82+Ks1Z0=
k8s.io/kms v0.37.0/go.mod h1:0U6zfwLkfJr9O/jLgz2GARnKLh2uIhkq2EeaU66J5ow=
k8s.io/kms v0.37.1 h1:MD1kA8AdFF/N5sGP8gnvL6vTo/oeaQXbmLjWmmJHLc8=
k8s.io/kms v0.37.1/go.mod h1:0U6zfwLkfJr9O/jLgz2GARnKLh2uIhkq2EeaU66J5ow=
k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 h1:Y3gxNAuB0OBLImH611+UDZcmKS3g6CthxToOb37KgwE=
k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ=
Expand Down
5 changes: 5 additions & 0 deletions pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,8 @@ type Config struct {
EnableMetricsExporter bool `mapstructure:"prometheusExporterEnabled"`
EnableSandboxPolicy bool `mapstructure:"sandboxPolicyEnabled"`
SandboxPolicyImage string `mapstructure:"sandboxPolicyImage"`
SandboxPolicyPrivilegeImage string `mapstructure:"sandboxPolicyPrivilegeImage"`
SandboxPolicyQualifiedKernels []string `mapstructure:"sandboxPolicyQualifiedKernels"`
SandboxPolicyContainerdSocket string `mapstructure:"sandboxPolicyContainerdSocket"`
SandboxPolicyControlRoot string `mapstructure:"sandboxPolicyControlRoot"`
SandboxPolicyMaxOwners uint64 `mapstructure:"sandboxPolicyMaxOwners"`
Expand Down Expand Up @@ -226,6 +228,9 @@ func LoadConfigOptional(path string, errNotFound bool) (Config, error) {

viper.SetDefault("sandboxPolicyEnabled", false)
viper.SetDefault("sandboxPolicyImage", "")
viper.SetDefault("sandboxPolicyPrivilegeImage", "")
// Unset: the built-in qualified kernel list. ["*"] admits every kernel.
viper.SetDefault("sandboxPolicyQualifiedKernels", nil)
viper.SetDefault("sandboxPolicyContainerdSocket", "")
viper.SetDefault("sandboxPolicyControlRoot", "")
viper.SetDefault("sandboxPolicyMaxOwners", 4)
Expand Down
9 changes: 7 additions & 2 deletions pkg/ebpf/gadgets/common/enforcement/bindings.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@ type ContainerIdentity = execruntime.ContainerIdentity
type Binding struct {
Container ContainerIdentity
Slot uint32
// SkipArming binds without arming the tasks already in the cgroup. Use it
// for a parent cgroup bound before its containers start: a container
// runtime helper can be there between its namespace setup and its exec,
// and must not be judged. Tasks are still armed at their own exec.
SkipArming bool
// Subtree also covers the descendant cgroups. Scope is exact by default;
// set it only for a parent cgroup bound before its containers start, and
// report it in status.
Expand Down Expand Up @@ -302,8 +307,8 @@ func (b *Bindings) bind(c Binding) error {
}
// A slot move arms again too: a task that the old slot left unarmed (an
// audit slot allows a fork whose child mark failed) must not escape the
// new slot.
if b.armed != nil {
// new slot. A SkipArming binding never arms.
if b.armed != nil && !c.SkipArming {
if err := b.armExisting(b.armed, id.CgroupPath); err != nil {
if bound && !errors.Is(err, ErrContainerGone) {
// Undo the move, so a retry moves and arms again.
Expand Down
25 changes: 25 additions & 0 deletions pkg/ebpf/gadgets/common/enforcement/bindings_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,31 @@ func TestSlotFaults(t *testing.T) {
}
}

func TestSkipArmingBindsWithoutArming(t *testing.T) {
b, policy, members, _ := newFixture(t, func(Map) error { return nil })
var armedPaths []string
b.armExisting = func(_ Map, path string) error { armedPaths = append(armedPaths, path); return nil }
parent := ContainerIdentity{Runtime: "parent", ID: "p1", CgroupID: 7, CgroupPath: "/kubepods.slice/kubepods-pod1.slice"}
b.desired[selector(parent)] = Binding{Container: parent, SkipArming: true}
if err := b.start(policy, members, &fakeMap{typ: ebpf.TaskStorage}, nil, nil); err != nil {
t.Fatal(err)
}
// Only the initial container binding arms; the replayed parent does not.
if len(armedPaths) != 1 || armedPaths[0] != identity.CgroupPath || members.data[uint64(7)] != (bindingValue{}) {
t.Fatalf("startup armed %v bindings %v", armedPaths, members.data)
}
if err := b.Unbind(parent); err != nil {
t.Fatal(err)
}
if err := b.Bind(Binding{Container: parent, Slot: 1, SkipArming: true}); err != nil || len(armedPaths) != 1 || members.data[uint64(7)] != (bindingValue{Slot: 1}) {
t.Fatalf("live bind: %v armed %v bindings %v", err, armedPaths, members.data)
}
// A slot move of a SkipArming binding does not arm either.
if err := b.Bind(Binding{Container: parent, Slot: 0, SkipArming: true}); err != nil || len(armedPaths) != 1 {
t.Fatalf("slot move: %v armed %v", err, armedPaths)
}
}

func TestBindingFlags(t *testing.T) {
log := &[]string{}
policy := &fakeMap{typ: ebpf.Hash, data: map[interface{}]interface{}{}, log: log, name: "policy"}
Expand Down
11 changes: 10 additions & 1 deletion pkg/ebpf/gadgets/enforce_privilege/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,10 @@ policy:
- **Scope.** A binding covers its exact cgroup, as in enforce_exec. A binding
with `Subtree` (`BINDING_SUBTREE`) also covers its descendant cgroups at any
depth; the owner sets it only for a parent cgroup bound before its containers
start, and reports it. A subtree binding must be at cgroup level 31 or less
start, and reports it. With `SkipArming` a container is then judged from its
first exec; the tasks already in the parent are not armed, also not at a
slot move or in a new instance, because a runtime helper can be there during
its setup. A subtree binding must be at cgroup level 31 or less
(the controller refuses a deeper one; levels are counted in the owner's
cgroup namespace, the kernel counts from the host root). The task's own
binding wins, then the nearest parent that is a subtree binding or a Strict
Expand All @@ -150,6 +153,12 @@ policy:

## Known limits

- **Parent bindings.** A subtree parent binding also judges the armed tasks
of its child cgroups that have no binding of their own, for example an
infrastructure (pause) process. OCI hooks are not armed under
`RuntimeHelpers` (see the next item). A new instance does not arm the tasks
that run only under a parent binding; they are judged again when their own
cgroup is bound or at their next exec.
- **Runtime helpers are trusted under `RuntimeHelpers`.** In a binding with the
flag, a task forked by an unarmed task inside the container (an OCI hook, or
a process a hook leaves behind) is not judged, and neither are its children.
Expand Down
45 changes: 45 additions & 0 deletions pkg/sandboxpolicy/availability.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package sandboxpolicy

import (
"errors"
"fmt"
)

// EnforcementUnavailableError means a policy section cannot be enforced on this
// node. Reason becomes the Ready condition reason in the node status.
type EnforcementUnavailableError struct {
Section, Reason, Detail string
// Enforcing: the section enforces, but must not report Ready (for
// example on a kernel that is not qualified).
Enforcing bool
}

func (e *EnforcementUnavailableError) Error() string {
if e.Enforcing {
return fmt.Sprintf("%s enforces on this node but is not qualified: %s", e.Section, e.Detail)
}
return fmt.Sprintf("%s enforcement is unavailable on this node: %s", e.Section, e.Detail)
}

// PrivilegeAvailability is implemented by a backend that knows whether the
// privilege section can be enforced on this node.
type PrivilegeAvailability interface {
PrivilegeUnavailable() error
}

// privilegeUnavailable returns why the privilege section cannot be enforced,
// or nil. It is called with c.mu held.
func (c *Controller) privilegeUnavailable() error {
if a, ok := c.backend.(PrivilegeAvailability); ok {
return a.PrivilegeUnavailable()
}
return nil
}

func unavailableReason(err error) (string, string, bool) {
var u *EnforcementUnavailableError
if !errors.As(err, &u) {
return "", "", false
}
return u.Reason, u.Error(), true
}
16 changes: 11 additions & 5 deletions pkg/sandboxpolicy/compiler.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ func Compile(ctx context.Context, identity PolicyIdentity, spec api.PolicySpec,
if spec.Mode != api.ModeAudit && spec.Mode != api.ModeEnforce {
return result, fmt.Errorf("policy is not active: %q", spec.Mode)
}
if spec.File != nil || spec.Network != nil || spec.Privilege != nil || spec.Identity != nil {
return result, fmt.Errorf("non-exec sections are unsupported")
if spec.File != nil || spec.Network != nil || spec.Identity != nil {
return result, fmt.Errorf("file, network and identity sections are unsupported")
}
// JSON cloning preserves all pointers and slice fields of the API vocabulary.
data, err := json.Marshal(spec)
Expand All @@ -52,6 +52,7 @@ func Compile(ctx context.Context, identity PolicyIdentity, spec api.PolicySpec,
return result, fmt.Errorf("exec defaultMode requires native IG ABI support")
}
}
result.Privilege = copy.Privilege
names := make(map[string]bool)
for _, r := range result.Exec.Rules {
names[r.Name] = true
Expand Down Expand Up @@ -127,7 +128,7 @@ func Compile(ctx context.Context, identity PolicyIdentity, spec api.PolicySpec,
}
}
// Validate imported rules as well as authored rules after class expansion.
validated := api.PolicySpec{Containers: copy.Containers, Mode: copy.Mode, RootRole: result.RootRole, Exec: &result.Exec}
validated := api.PolicySpec{Containers: copy.Containers, Mode: copy.Mode, RootRole: result.RootRole, Exec: &result.Exec, Privilege: result.Privilege}
if err := api.ValidatePolicySpec(validated); err != nil {
return result, err
}
Expand Down Expand Up @@ -173,8 +174,13 @@ func validateShape(spec api.PolicySpec) error {
if spec.Mode == api.ModeOff {
return nil
}
if spec.File != nil || spec.Network != nil || spec.Privilege != nil || spec.Identity != nil {
return fmt.Errorf("non-exec sections are unsupported")
if spec.File != nil || spec.Network != nil || spec.Identity != nil {
return fmt.Errorf("file, network and identity sections are unsupported")
}
if spec.Privilege != nil {
if _, err := PrivilegeDocument("", spec.Mode, *spec.Privilege); err != nil {
return fmt.Errorf("privilege: %w", err)
}
}
if spec.Exec != nil {
if spec.Exec.Default != api.ActionAllow && spec.Exec.Default != api.ActionDeny {
Expand Down
Loading
Loading