Skip to content

Commit 914fbf3

Browse files
committed
default-validator: restrict sysctl adjustment
Signed-off-by: Samuel Karp <[email protected]>
1 parent a418956 commit 914fbf3

2 files changed

Lines changed: 114 additions & 3 deletions

File tree

plugins/default-validator/default-validator.go

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,11 @@ import (
2424
"strconv"
2525
"strings"
2626

27+
yaml "gopkg.in/yaml.v3"
28+
2729
"github.com/containerd/nri/pkg/api"
2830
"github.com/containerd/nri/pkg/log"
2931
"github.com/containerd/nri/pkg/plugin"
30-
yaml "gopkg.in/yaml.v3"
3132
)
3233

3334
// DefaultValidatorConfig is the configuration for the default validator plugin.
@@ -47,6 +48,8 @@ type DefaultValidatorConfig struct {
4748
RejectCustomSeccompAdjustment bool `yaml:"rejectCustomSeccompAdjustment" toml:"reject_custom_seccomp_adjustment"`
4849
// RejectNamespaceAdjustment fails validation if any plugin adjusts Linux namespaces.
4950
RejectNamespaceAdjustment bool `yaml:"rejectNamespaceAdjustment" toml:"reject_namespace_adjustment"`
51+
// RejectSysctlAdjustment fails validation if any plugin adjusts sysctls
52+
RejectSysctlAdjustment bool `yaml:"rejectSysctlAdjustment" toml:"reject_sysctl_adjustment"`
5053
// RequiredPlugins list globally required plugins. These must be present
5154
// or otherwise validation will fail.
5255
// WARNING: This is a global setting and will affect all containers. In
@@ -115,6 +118,11 @@ func (v *DefaultValidator) ValidateContainerAdjustment(ctx context.Context, req
115118
return err
116119
}
117120

121+
if err := v.validateSysctl(req); err != nil {
122+
log.Errorf(ctx, "rejecting adjustment: %v", err)
123+
return err
124+
}
125+
118126
return nil
119127
}
120128

@@ -203,6 +211,31 @@ func (v *DefaultValidator) validateNamespaces(req *api.ValidateContainerAdjustme
203211
ErrValidation, offenders)
204212
}
205213

214+
func (v *DefaultValidator) validateSysctl(req *api.ValidateContainerAdjustmentRequest) error {
215+
if req.Adjust == nil || req.Adjust.Linux == nil {
216+
return nil
217+
}
218+
219+
if !v.cfg.RejectSysctlAdjustment {
220+
return nil
221+
}
222+
223+
var owners []string
224+
for key := range req.Adjust.Linux.Sysctl {
225+
owner, claimed := req.Owners.SysctlOwner(req.Container.Id, key)
226+
if !claimed {
227+
continue
228+
}
229+
owners = append(owners, owner)
230+
}
231+
232+
if len(owners) == 0 {
233+
return nil
234+
}
235+
236+
return fmt.Errorf("%w: attempted restricted sysctl adjustment by plugin(s) %s", ErrValidation, strings.Join(owners, ", "))
237+
}
238+
206239
func (v *DefaultValidator) validateRequiredPlugins(req *api.ValidateContainerAdjustmentRequest) error {
207240
var (
208241
container = req.GetContainer().GetName()

plugins/default-validator/default-validator_test.go

Lines changed: 80 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,12 @@ package validator
1919
import (
2020
"testing"
2121

22-
"github.com/containerd/nri/pkg/api"
2322
"github.com/stretchr/testify/require"
23+
24+
"github.com/containerd/nri/pkg/api"
2425
)
2526

26-
func TestValidateReqiredPlugins(t *testing.T) {
27+
func TestValidateRequiredPlugins(t *testing.T) {
2728
type testCase struct {
2829
name string
2930
cfg *DefaultValidatorConfig
@@ -252,5 +253,82 @@ func TestValidateReqiredPlugins(t *testing.T) {
252253
}
253254
})
254255
}
256+
}
257+
258+
func TestValidateSysctl(t *testing.T) {
259+
type testCase struct {
260+
name string
261+
cfg *DefaultValidatorConfig
262+
pod *api.PodSandbox
263+
container *api.Container
264+
plugins []*api.PluginInstance
265+
adjust *api.ContainerAdjustment
266+
claim func(f *api.FieldOwners) error
267+
fail bool
268+
}
269+
270+
for _, tc := range []*testCase{
271+
{
272+
name: "disallowed sysctl adjustment",
273+
cfg: &DefaultValidatorConfig{
274+
Enable: true,
275+
RejectSysctlAdjustment: true,
276+
},
277+
pod: &api.PodSandbox{
278+
Id: "pod-id",
279+
Name: "pod-name",
280+
Namespace: "pod-namespace",
281+
},
282+
container: &api.Container{
283+
Id: "container-id",
284+
Name: "container-name",
285+
},
286+
plugins: []*api.PluginInstance{
287+
{
288+
Name: "plugin2",
289+
Index: "00",
290+
},
291+
},
292+
adjust: &api.ContainerAdjustment{
293+
Linux: &api.LinuxContainerAdjustment{
294+
Sysctl: map[string]string{
295+
"foo": "bar",
296+
},
297+
},
298+
},
299+
claim: func(f *api.FieldOwners) error {
300+
return f.ClaimSysctl("foo", "plugin2")
301+
},
302+
fail: true,
303+
},
304+
} {
305+
t.Run(tc.name, func(t *testing.T) {
306+
v := NewDefaultValidator(tc.cfg)
307+
owners := &api.OwningPlugins{
308+
Owners: make(map[string]*api.FieldOwners),
309+
}
310+
owners.Owners[tc.container.Id] = &api.FieldOwners{
311+
Simple: make(map[int32]string),
312+
Compound: make(map[int32]*api.CompoundFieldOwners),
313+
}
314+
if tc.claim != nil {
315+
require.NoError(t, tc.claim(owners.Owners[tc.container.Id]))
316+
}
255317

318+
req := &api.ValidateContainerAdjustmentRequest{
319+
Pod: tc.pod,
320+
Container: tc.container,
321+
Plugins: tc.plugins,
322+
Adjust: tc.adjust,
323+
Owners: owners,
324+
}
325+
326+
err := v.validateSysctl(req)
327+
if tc.fail {
328+
require.Error(t, err)
329+
} else {
330+
require.NoError(t, err)
331+
}
332+
})
333+
}
256334
}

0 commit comments

Comments
 (0)