Skip to content

Commit 34a751d

Browse files
committed
default-validator: implement finer-grained validation.
Implement role based overrides for authenticated plugins. This allows setting a restrictive default configuration and override it with more liberal configuration for some plugins/roles. Signed-off-by: Krisztian Litkey <krisztian.litkey@intel.com>
1 parent 93daf74 commit 34a751d

File tree

5 files changed

+168
-67
lines changed

5 files changed

+168
-67
lines changed

pkg/adaptation/adaptation.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ func WithBuiltinPlugins(plugins ...*builtin.BuiltinPlugin) Option {
137137
}
138138

139139
// WithDefaultValidator sets up builtin validator plugin if it is configured.
140-
func WithDefaultValidator(cfg *validator.DefaultValidatorConfig) Option {
140+
func WithDefaultValidator(cfg *validator.DefaultConfig) Option {
141141
return func(r *Adaptation) error {
142142
if plugin := validator.GetDefaultValidator(cfg); plugin != nil {
143143
r.builtin = append([]*builtin.BuiltinPlugin{plugin}, r.builtin...)

pkg/adaptation/adaptation_suite_test.go

Lines changed: 40 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1093,9 +1093,11 @@ var _ = Describe("Plugin container creation adjustments", func() {
10931093
&mockRuntime{
10941094
options: []nri.Option{
10951095
nri.WithDefaultValidator(
1096-
&validator.DefaultValidatorConfig{
1097-
Enable: true,
1098-
RejectOCIHookAdjustment: true,
1096+
&validator.DefaultConfig{
1097+
Enable: true,
1098+
Config: &validator.Config{
1099+
RejectOCIHookAdjustment: boolptr(true),
1100+
},
10991101
},
11001102
),
11011103
},
@@ -1186,9 +1188,11 @@ var _ = Describe("Plugin container creation adjustments", func() {
11861188
&mockRuntime{
11871189
options: []nri.Option{
11881190
nri.WithDefaultValidator(
1189-
&validator.DefaultValidatorConfig{
1190-
Enable: true,
1191-
RejectRuntimeDefaultSeccompAdjustment: true,
1191+
&validator.DefaultConfig{
1192+
Enable: true,
1193+
Config: &validator.Config{
1194+
RejectRuntimeDefaultSeccompAdjustment: boolptr(true),
1195+
},
11921196
},
11931197
),
11941198
},
@@ -1289,9 +1293,11 @@ var _ = Describe("Plugin container creation adjustments", func() {
12891293
&mockRuntime{
12901294
options: []nri.Option{
12911295
nri.WithDefaultValidator(
1292-
&validator.DefaultValidatorConfig{
1293-
Enable: true,
1294-
RejectRuntimeDefaultSeccompAdjustment: false,
1296+
&validator.DefaultConfig{
1297+
Enable: true,
1298+
Config: &validator.Config{
1299+
RejectRuntimeDefaultSeccompAdjustment: boolptr(false),
1300+
},
12951301
},
12961302
),
12971303
},
@@ -1392,9 +1398,11 @@ var _ = Describe("Plugin container creation adjustments", func() {
13921398
&mockRuntime{
13931399
options: []nri.Option{
13941400
nri.WithDefaultValidator(
1395-
&validator.DefaultValidatorConfig{
1396-
Enable: true,
1397-
RejectCustomSeccompAdjustment: true,
1401+
&validator.DefaultConfig{
1402+
Enable: true,
1403+
Config: &validator.Config{
1404+
RejectCustomSeccompAdjustment: boolptr(true),
1405+
},
13981406
},
13991407
),
14001408
},
@@ -1496,9 +1504,11 @@ var _ = Describe("Plugin container creation adjustments", func() {
14961504
&mockRuntime{
14971505
options: []nri.Option{
14981506
nri.WithDefaultValidator(
1499-
&validator.DefaultValidatorConfig{
1500-
Enable: true,
1501-
RejectCustomSeccompAdjustment: false,
1507+
&validator.DefaultConfig{
1508+
Enable: true,
1509+
Config: &validator.Config{
1510+
RejectCustomSeccompAdjustment: boolptr(false),
1511+
},
15021512
},
15031513
),
15041514
},
@@ -1600,9 +1610,11 @@ var _ = Describe("Plugin container creation adjustments", func() {
16001610
&mockRuntime{
16011611
options: []nri.Option{
16021612
nri.WithDefaultValidator(
1603-
&validator.DefaultValidatorConfig{
1604-
Enable: true,
1605-
RejectUnconfinedSeccompAdjustment: true,
1613+
&validator.DefaultConfig{
1614+
Enable: true,
1615+
Config: &validator.Config{
1616+
RejectUnconfinedSeccompAdjustment: boolptr(true),
1617+
},
16061618
},
16071619
),
16081620
},
@@ -1703,9 +1715,11 @@ var _ = Describe("Plugin container creation adjustments", func() {
17031715
&mockRuntime{
17041716
options: []nri.Option{
17051717
nri.WithDefaultValidator(
1706-
&validator.DefaultValidatorConfig{
1707-
Enable: true,
1708-
RejectUnconfinedSeccompAdjustment: false,
1718+
&validator.DefaultConfig{
1719+
Enable: true,
1720+
Config: &validator.Config{
1721+
RejectUnconfinedSeccompAdjustment: boolptr(false),
1722+
},
17091723
},
17101724
),
17111725
},
@@ -1807,7 +1821,7 @@ var _ = Describe("Plugin container creation adjustments", func() {
18071821
&mockRuntime{
18081822
options: []nri.Option{
18091823
nri.WithDefaultValidator(
1810-
&validator.DefaultValidatorConfig{
1824+
&validator.DefaultConfig{
18111825
Enable: true,
18121826
RequiredPlugins: []string{
18131827
"foo",
@@ -3052,3 +3066,7 @@ func protoDiff(a, b proto.Message) string {
30523066
func protoEqual(a, b proto.Message) bool {
30533067
return cmp.Equal(a, b, cmpopts.EquateEmpty(), protocmp.Transform())
30543068
}
3069+
3070+
func boolptr(v bool) *bool {
3071+
return &v
3072+
}

plugins/default-validator/builtin/plugin.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,13 @@ import (
2626
)
2727

2828
type (
29-
DefaultValidatorConfig = validator.DefaultValidatorConfig
29+
Config = validator.Config
30+
DefaultConfig = validator.DefaultConfig
3031
)
3132

3233
// GetDefaultValidator returns a configured instance of the default validator.
3334
// If default validation is disabled nil is returned.
34-
func GetDefaultValidator(cfg *DefaultValidatorConfig) *builtin.BuiltinPlugin {
35+
func GetDefaultValidator(cfg *DefaultConfig) *builtin.BuiltinPlugin {
3536
if cfg == nil || !cfg.Enable {
3637
log.Infof(context.TODO(), "built-in NRI default validator is disabled")
3738
return nil

plugins/default-validator/default-validator.go

Lines changed: 113 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -30,20 +30,12 @@ import (
3030
yaml "gopkg.in/yaml.v3"
3131
)
3232

33-
type DefaultValidatorConfig struct {
33+
type DefaultConfig struct {
3434
// Enable the default validator plugin.
3535
Enable bool `yaml:"enable" toml:"enable"`
36-
// RejectOCIHookAdjustment fails validation if OCI hooks are adjusted.
37-
RejectOCIHookAdjustment bool `yaml:"rejectOCIHookAdjustment" toml:"reject_oci_hook_adjustment"`
38-
// RejectRuntimeDefaultSeccompAdjustment fails validation if a runtime default seccomp
39-
// policy is adjusted.
40-
RejectRuntimeDefaultSeccompAdjustment bool `yaml:"rejectRuntimeDefaultSeccompAdjustment" toml:"reject_runtime_default_seccomp_adjustment"`
41-
// RejectUnconfinedSeccompAdjustment fails validation if an unconfined seccomp policy is
42-
// adjusted.
43-
RejectUnconfinedSeccompAdjustment bool `yaml:"rejectUnconfinedSeccompAdjustment" toml:"reject_unconfined_seccomp_adjustment"`
44-
// RejectCustomSeccompAdjustment fails validation if a custom seccomp policy (aka LOCALHOST)
45-
// is adjusted.
46-
RejectCustomSeccompAdjustment bool `yaml:"rejectCustomSeccompAdjustment" toml:"reject_custom_seccomp_adjustment"`
36+
*Config
37+
// Overrides provide per-role overrides to the default configuration.
38+
Overrides map[string]*Config `yaml:"overrides" toml:"overrides"`
4739
// RequiredPlugins list globally required plugins. These must be present
4840
// or otherwise validation will fail.
4941
// WARNING: This is a global setting and will affect all containers. In
@@ -59,9 +51,24 @@ type DefaultValidatorConfig struct {
5951
TolerateMissingAnnotation string `yaml:"tolerateMissingPluginsAnnotation" toml:"tolerate_missing_plugins_annotation"`
6052
}
6153

54+
// Config provides validation defaults or per role overrides.
55+
type Config struct {
56+
// RejectOCIHookAdjustment fails validation if OCI hooks are adjusted.
57+
RejectOCIHookAdjustment *bool `yaml:"rejectOCIHookAdjustment" toml:"reject_oci_hook_adjustment"`
58+
// RejectRuntimeDefaultSeccompAdjustment fails validation if a runtime default seccomp
59+
// policy is adjusted.
60+
RejectRuntimeDefaultSeccompAdjustment *bool `yaml:"rejectRuntimeDefaultSeccompAdjustment" toml:"reject_runtime_default_seccomp_adjustment"`
61+
// RejectUnconfinedSeccompAdjustment fails validation if an unconfined seccomp policy is
62+
// adjusted.
63+
RejectUnconfinedSeccompAdjustment *bool `yaml:"rejectUnconfinedSeccompAdjustment" toml:"reject_unconfined_seccomp_adjustment"`
64+
// RejectCustomSeccompAdjustment fails validation if a custom seccomp policy (aka LOCALHOST)
65+
// is adjusted.
66+
RejectCustomSeccompAdjustment *bool `yaml:"rejectCustomSeccompAdjustment" toml:"reject_custom_seccomp_adjustment"`
67+
}
68+
6269
// DefaultValidator implements default validation.
6370
type DefaultValidator struct {
64-
cfg DefaultValidatorConfig
71+
cfg DefaultConfig
6572
}
6673

6774
const (
@@ -75,12 +82,12 @@ var (
7582
)
7683

7784
// NewDefaultValidator creates a new instance of the validator.
78-
func NewDefaultValidator(cfg *DefaultValidatorConfig) *DefaultValidator {
85+
func NewDefaultValidator(cfg *DefaultConfig) *DefaultValidator {
7986
return &DefaultValidator{cfg: *cfg}
8087
}
8188

8289
// SetConfig sets new configuration for the validator.
83-
func (v *DefaultValidator) SetConfig(cfg *DefaultValidatorConfig) {
90+
func (v *DefaultValidator) SetConfig(cfg *DefaultConfig) {
8491
if cfg == nil {
8592
return
8693
}
@@ -92,50 +99,64 @@ func (v *DefaultValidator) ValidateContainerAdjustment(ctx context.Context, req
9299
log.Debugf(ctx, "Validating adjustment of container %s/%s/%s",
93100
req.GetPod().GetNamespace(), req.GetPod().GetName(), req.GetContainer().GetName())
94101

95-
if err := v.validateOCIHooks(req); err != nil {
102+
plugins := req.GetPluginMap()
103+
104+
if err := v.validateOCIHooks(req, plugins); err != nil {
96105
log.Errorf(ctx, "rejecting adjustment: %v", err)
97106
return err
98107
}
99108

100-
if err := v.validateSeccompPolicy(req); err != nil {
109+
if err := v.validateSeccompPolicy(req, plugins); err != nil {
101110
log.Errorf(ctx, "rejecting adjustment: %v", err)
102111
return err
103112
}
104113

105-
if err := v.validateRequiredPlugins(req); err != nil {
114+
if err := v.validateRequiredPlugins(req, plugins); err != nil {
106115
log.Errorf(ctx, "rejecting adjustment: %v", err)
107116
return err
108117
}
109118

110119
return nil
111120
}
112121

113-
func (v *DefaultValidator) validateOCIHooks(req *api.ValidateContainerAdjustmentRequest) error {
122+
func (v *DefaultValidator) validateOCIHooks(req *api.ValidateContainerAdjustmentRequest, plugins map[string]*api.PluginInstance) error {
114123
if req.Adjust == nil {
115124
return nil
116125
}
117126

118-
if !v.cfg.RejectOCIHookAdjustment {
127+
owners, claimed := req.Owners.HooksOwner(req.Container.Id)
128+
if !claimed {
119129
return nil
120130
}
121131

122-
owners, claimed := req.Owners.HooksOwner(req.Container.Id)
123-
if !claimed {
132+
defaults := v.cfg.Config
133+
rejected := []string{}
134+
135+
for _, p := range strings.Split(owners, ",") {
136+
if instance, ok := plugins[p]; ok {
137+
cfg := v.cfg.GetConfig(instance.GetRole())
138+
if cfg.DenyOCIHookInjection(defaults) {
139+
rejected = append(rejected, p)
140+
}
141+
}
142+
}
143+
144+
if len(rejected) == 0 {
124145
return nil
125146
}
126147

127148
offender := ""
128149

129-
if !strings.Contains(owners, ",") {
130-
offender = fmt.Sprintf("plugin %q", owners)
150+
if len(rejected) == 1 {
151+
offender = fmt.Sprintf("plugin %q", rejected[0])
131152
} else {
132-
offender = fmt.Sprintf("plugins %q", owners)
153+
offender = fmt.Sprintf("plugins %q", strings.Join(rejected, ","))
133154
}
134155

135156
return fmt.Errorf("%w: %s attempted restricted OCI hook injection", ErrValidation, offender)
136157
}
137158

138-
func (v *DefaultValidator) validateSeccompPolicy(req *api.ValidateContainerAdjustmentRequest) error {
159+
func (v *DefaultValidator) validateSeccompPolicy(req *api.ValidateContainerAdjustmentRequest, plugins map[string]*api.PluginInstance) error {
139160
if req.Adjust == nil {
140161
return nil
141162
}
@@ -145,22 +166,31 @@ func (v *DefaultValidator) validateSeccompPolicy(req *api.ValidateContainerAdjus
145166
return nil
146167
}
147168

169+
var (
170+
cfg *Config
171+
defaults = v.cfg.Config
172+
)
173+
174+
if instance, ok := plugins[owner]; ok {
175+
cfg = v.cfg.GetConfig(instance.GetRole())
176+
}
177+
148178
profile := req.Container.GetLinux().GetSeccompProfile()
149179
switch {
150180
case profile == nil || profile.GetProfileType() == api.SecurityProfile_UNCONFINED:
151-
if v.cfg.RejectUnconfinedSeccompAdjustment {
181+
if cfg.DenyUnconfinedSeccompAdjustment(defaults) {
152182
return fmt.Errorf("%w: plugin %s attempted restricted "+
153183
" unconfined seccomp policy adjustment", ErrValidation, owner)
154184
}
155185

156186
case profile.GetProfileType() == api.SecurityProfile_RUNTIME_DEFAULT:
157-
if v.cfg.RejectRuntimeDefaultSeccompAdjustment {
187+
if cfg.DenyRuntimeDefaultSeccompAdjustment(defaults) {
158188
return fmt.Errorf("%w: plugin %s attempted restricted "+
159189
"runtime default seccomp policy adjustment", ErrValidation, owner)
160190
}
161191

162192
case profile.GetProfileType() == api.SecurityProfile_LOCALHOST:
163-
if v.cfg.RejectCustomSeccompAdjustment {
193+
if cfg.DenyCustomSeccompAdjustment(defaults) {
164194
return fmt.Errorf("%w: plugin %s attempted restricted "+
165195
" custom seccomp policy adjustment", ErrValidation, owner)
166196
}
@@ -169,7 +199,7 @@ func (v *DefaultValidator) validateSeccompPolicy(req *api.ValidateContainerAdjus
169199
return nil
170200
}
171201

172-
func (v *DefaultValidator) validateRequiredPlugins(req *api.ValidateContainerAdjustmentRequest) error {
202+
func (v *DefaultValidator) validateRequiredPlugins(req *api.ValidateContainerAdjustmentRequest, plugins map[string]*api.PluginInstance) error {
173203
var (
174204
container = req.GetContainer().GetName()
175205
required = slices.Clone(v.cfg.RequiredPlugins)
@@ -200,7 +230,6 @@ func (v *DefaultValidator) validateRequiredPlugins(req *api.ValidateContainerAdj
200230
return nil
201231
}
202232

203-
plugins := req.GetPluginMap()
204233
missing := []string{}
205234

206235
for _, r := range required {
@@ -223,3 +252,56 @@ func (v *DefaultValidator) validateRequiredPlugins(req *api.ValidateContainerAdj
223252

224253
return fmt.Errorf("%w: %s not present", ErrValidation, offender)
225254
}
255+
256+
// GetConfig returns overrides for the named role if it exists in the
257+
// configuration.
258+
func (cfg *DefaultConfig) GetConfig(role string) *Config {
259+
if cfg == nil || cfg.Overrides == nil {
260+
return nil
261+
}
262+
return cfg.Overrides[role]
263+
}
264+
265+
// DenyOCIHookInjection checks whether OCI hook injection should be denied
266+
// based on the configuration, using an optional fallbak configuration if
267+
// this one is nil or omits configuration.
268+
func (cfg *Config) DenyOCIHookInjection(fallback *Config) bool {
269+
if cfg != nil && cfg.RejectOCIHookAdjustment != nil {
270+
return *cfg.RejectOCIHookAdjustment
271+
}
272+
273+
return fallback != nil && fallback.DenyOCIHookInjection(nil)
274+
}
275+
276+
// DenyUnconfinedSeccompAdjustment checks whether adjustment of an unconfined
277+
// seccomp policy should be denied based on the configuration, using an optional
278+
// fallback configuration if this one is nil or omits configuration.
279+
func (cfg *Config) DenyUnconfinedSeccompAdjustment(fallback *Config) bool {
280+
if cfg != nil && cfg.RejectUnconfinedSeccompAdjustment != nil {
281+
return *cfg.RejectUnconfinedSeccompAdjustment
282+
}
283+
284+
return fallback != nil && fallback.DenyUnconfinedSeccompAdjustment(nil)
285+
}
286+
287+
// DenyRuntimeDefaultSeccompAdjustment checks whether adjustment of a runtime
288+
// default seccomp policy should be denied based on the configuration, using an
289+
// optional fallback configuration if this one is nil or omits configuration.
290+
func (cfg *Config) DenyRuntimeDefaultSeccompAdjustment(fallback *Config) bool {
291+
if cfg != nil && cfg.RejectRuntimeDefaultSeccompAdjustment != nil {
292+
return *cfg.RejectRuntimeDefaultSeccompAdjustment
293+
}
294+
295+
return fallback != nil && fallback.DenyRuntimeDefaultSeccompAdjustment(nil)
296+
}
297+
298+
// DenyCustomSeccompAdjustment checks whether adjustment of a custom (localhost)
299+
// seccomp policy should be denied based on the configuration, using an optional
300+
// fallback configuration if this one is nil or omits configuration.
301+
func (cfg *Config) DenyCustomSeccompAdjustment(fallback *Config) bool {
302+
if cfg != nil && cfg.RejectCustomSeccompAdjustment != nil {
303+
return *cfg.RejectCustomSeccompAdjustment
304+
}
305+
306+
return fallback != nil && fallback.DenyCustomSeccompAdjustment(nil)
307+
}

0 commit comments

Comments
 (0)