Skip to content

Commit eca0d2c

Browse files
authored
Merge pull request #171 from klihub/devel/pluggable-validation/add-default-validator
[pluggable-validation / 3]: implement default validator.
2 parents 6120e63 + 00d85a9 commit eca0d2c

File tree

15 files changed

+1359
-100
lines changed

15 files changed

+1359
-100
lines changed

README.md

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,11 @@ provides functions for
7373
- hooking the plugin into pod/container lifecycle events
7474
- shutting down the plugin
7575

76+
An additional interface is provided for validating the changes active plugins
77+
have requested to containers. This interface allows one to set up and enforce
78+
cluster- or node-wide boundary conditions for changes NRI plugins are allowed
79+
to make.
80+
7681
### Plugin Registration
7782

7883
Before a plugin can start receiving and processing container events, it needs
@@ -267,6 +272,80 @@ can be updated this way:
267272
- Block I/O class
268273
- RDT class
269274

275+
### Container Adjustment Validation
276+
277+
NRI plugins operate as trusted extensions of the container runtime, granting
278+
them significant privileges to alter container specs. While this extensibility
279+
is powerful with valid use cases, some of the capabilities granted to plugins
280+
allow modifying security-sensitive settings of containers. As such they also
281+
come with the risk that a plugin could inadvertently or maliciously weaken a
282+
container's isolation or security posture, potentially overriding policies set
283+
by cluster orchestrators such as K8s.
284+
285+
NRI offers cluster administrators a mechanism to exercise fine-grained control
286+
over what changes plugins are allowed to make to containers, allowing cluster
287+
administrators to lock down selected features in NRI or allowing them to only
288+
be used a subset of plugins. Changes in NRI are made in two phases: “Mutating”
289+
plugins propose changes, and “Validating” plugins approve or deny them.
290+
291+
Validating plugins are invoked during container creation after all the changes
292+
requested to containers have been collected. Validating plugins receive the
293+
changes with extra information about which of the plugins requested what
294+
changes. They can then choose to reject the changes if they violate some of the
295+
conditions being validated.
296+
297+
Validation has transactional semantics. If any validating plugin rejects an
298+
adjustment, creation of the adjusted container will fail and none of the other
299+
related changes will be made.
300+
301+
#### Validation Use Cases
302+
303+
Some key validation uses cases include
304+
305+
1. Functional Validators: These plugins care about the final state and
306+
consistency. They check if the combined effect of all mutations result
307+
in a valid configuration (e.g. are the resource limits sane).
308+
309+
2. Security Validators: These plugins are interested in which plugin is
310+
attempting to modify sensitive fields. They use the extra data passed to
311+
plugins in addition to adjustments to check if a potentially untrusted
312+
plugin tried to modify a restricted field, regardless of the value.
313+
Rejection might occur simply because a non-approved plugin touched a
314+
specific field. Plugins like this may need to be assured to run, and to
315+
have workloads fail-closed if the validator is not running.
316+
317+
3. Mandatory Plugin Validators: These ensure that specific plugins, required
318+
for certain workloads have successfully run. They might use the extra metadata
319+
passed to validator in addition to adjustments to confirm the mandatory
320+
plugin owns certain critical fields and potentially use the list of plugins
321+
that processed the container to ensure all mandatory plugins were consulted.
322+
323+
#### Default Validation
324+
325+
The default built-in validator plugin provides configurable minimal validation.
326+
It is disabled by default. It can be enabled and selectively configured to
327+
328+
1. Reject OCI Hook injection: Reject any adjustment which tries to inject
329+
OCI Hooks into a container.
330+
331+
2. Verify global mandatory plugins: Verify that all configured mandatory
332+
plugins are present and have processed a container. Otherwise reject the
333+
creation of the container.
334+
335+
3. Verify annotated mandatory plugins: Verify that an annotated set of
336+
container-specific mandatory plugins are present and have processed a
337+
container. Otherwise reject the creation of the container.
338+
339+
Containers can be annotated to tolerate missing required plugins. This
340+
allows one to deploy mandatory plugins as containers themselves.
341+
342+
#### Default Validation Scope
343+
344+
Currently only OCI hook injection can be restricted using the default
345+
validator. However, this probably will change in the future. Especially
346+
when NRI is extended with control over new container parameters. If such
347+
parameters will have security implications, corresponding configurable
348+
restrictions will be introduced to the default validator.
270349

271350
## Runtime Adaptation
272351

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ require (
1717
golang.org/x/sys v0.21.0
1818
google.golang.org/grpc v1.57.1
1919
google.golang.org/protobuf v1.34.1
20+
gopkg.in/yaml.v3 v3.0.1
2021
)
2122

2223
require (
@@ -35,7 +36,6 @@ require (
3536
golang.org/x/tools v0.21.0 // indirect
3637
google.golang.org/genproto/googleapis/rpc v0.0.0-20230731190214-cbb8c96f2d6d // indirect
3738
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
38-
gopkg.in/yaml.v3 v3.0.1 // indirect
3939
)
4040

4141
replace github.com/opencontainers/runtime-tools v0.9.0 => github.com/opencontainers/runtime-tools v0.0.0-20221026201742-946c877fa809

pkg/adaptation/adaptation.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,10 @@ import (
2727
"sort"
2828
"sync"
2929

30+
"github.com/containerd/nri/pkg/adaptation/builtin"
3031
"github.com/containerd/nri/pkg/api"
3132
"github.com/containerd/nri/pkg/log"
33+
validator "github.com/containerd/nri/plugins/default-validator/builtin"
3234
"github.com/containerd/ttrpc"
3335
"github.com/tetratelabs/wazero"
3436
"github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1"
@@ -70,6 +72,7 @@ type Adaptation struct {
7072
listener net.Listener
7173
plugins []*plugin
7274
validators []*plugin
75+
builtin []*builtin.BuiltinPlugin
7376
syncLock sync.RWMutex
7477
wasmService *api.PluginPlugin
7578
}
@@ -123,6 +126,24 @@ func WithTTRPCOptions(clientOpts []ttrpc.ClientOpts, serverOpts []ttrpc.ServerOp
123126
}
124127
}
125128

129+
// WithBuiltinPlugins sets extra builtin plugins to register.
130+
func WithBuiltinPlugins(plugins ...*builtin.BuiltinPlugin) Option {
131+
return func(r *Adaptation) error {
132+
r.builtin = append(r.builtin, plugins...)
133+
return nil
134+
}
135+
}
136+
137+
// WithDefaultValidator sets up builtin validator plugin if it is configured.
138+
func WithDefaultValidator(cfg *validator.DefaultValidatorConfig) Option {
139+
return func(r *Adaptation) error {
140+
if plugin := validator.GetDefaultValidator(cfg); plugin != nil {
141+
r.builtin = append([]*builtin.BuiltinPlugin{plugin}, r.builtin...)
142+
}
143+
return nil
144+
}
145+
}
146+
126147
// New creates a new NRI Runtime.
127148
func New(name, version string, syncFn SyncFn, updateFn UpdateFn, opts ...Option) (*Adaptation, error) {
128149
var err error
@@ -433,6 +454,20 @@ func (r *Adaptation) startPlugins() (retErr error) {
433454
}
434455
}()
435456

457+
for _, b := range r.builtin {
458+
log.Infof(noCtx, "starting builtin NRI plugin %q...", b.Index+"-"+b.Base)
459+
p, err := r.newBuiltinPlugin(b)
460+
if err != nil {
461+
return fmt.Errorf("failed to initialize builtin NRI plugin %q: %v", b.Base, err)
462+
}
463+
464+
if err := p.start(r.name, r.version); err != nil {
465+
return fmt.Errorf("failed to start builtin NRI plugin %q: %v", b.Base, err)
466+
}
467+
468+
plugins = append(plugins, p)
469+
}
470+
436471
for i, name := range names {
437472
log.Infof(noCtx, "starting pre-installed NRI plugin %q...", name)
438473

0 commit comments

Comments
 (0)