Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
79 changes: 79 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,11 @@ provides functions for
- hooking the plugin into pod/container lifecycle events
- shutting down the plugin

An additional interface is provided for validating the changes active plugins
have requested to containers. This interface allows one to set up and enforce
cluster- or node-wide boundary conditions for changes NRI plugins are allowed
to make.

### Plugin Registration

Before a plugin can start receiving and processing container events, it needs
Expand Down Expand Up @@ -277,6 +282,80 @@ can be updated this way:
- Block I/O class
- RDT class

### Container Adjustment Validation

NRI plugins operate as trusted extensions of the container runtime, granting
them significant privileges to alter container specs. While this extensibility
is powerful with valid use cases, some of the capabilities granted to plugins
allow modifying security-sensitive settings of containers. As such they also
come with the risk that a plugin could inadvertently or maliciously weaken a
container's isolation or security posture, potentially overriding policies set
by cluster orchestrators such as K8s.

NRI offers cluster administrators a mechanism to exercise fine-grained control
over what changes plugins are allowed to make to containers, allowing cluster
administrators to lock down selected features in NRI or allowing them to only
be used a subset of plugins. Changes in NRI are made in two phases: “Mutating”
plugins propose changes, and “Validating” plugins approve or deny them.

Validating plugins are invoked during container creation after all the changes
requested to containers have been collected. Validating plugins receive the
changes with extra information about which of the plugins requested what
changes. They can then choose to reject the changes if they violate some of the
conditions being validated.

Validation has transactional semantics. If any validating plugin rejects an
adjustment, creation of the adjusted container will fail and none of the other
related changes will be made.

#### Validation Use Cases

Some key validation uses cases include

1. Functional Validators: These plugins care about the final state and
consistency. They check if the combined effect of all mutations result
in a valid configuration (e.g. are the resource limits sane).

2. Security Validators: These plugins are interested in which plugin is
attempting to modify sensitive fields. They use the extra data passed to
plugins in addition to adjustments to check if a potentially untrusted
plugin tried to modify a restricted field, regardless of the value.
Rejection might occur simply because a non-approved plugin touched a
specific field. Plugins like this may need to be assured to run, and to
have workloads fail-closed if the validator is not running.

3. Mandatory Plugin Validators: These ensure that specific plugins, required
for certain workloads have successfully run. They might use the extra metadata
passed to validator in addition to adjustments to confirm the mandatory
plugin owns certain critical fields and potentially use the list of plugins
that processed the container to ensure all mandatory plugins were consulted.

#### Default Validation

The default built-in validator plugin provides configurable minimal validation.
It is disabled by default. It can be enabled and selectively configured to

1. Reject OCI Hook injection: Reject any adjustment which tries to inject
OCI Hooks into a container.

2. Verify global mandatory plugins: Verify that all configured mandatory
plugins are present and have processed a container. Otherwise reject the
creation of the container.

3. Verify annotated mandatory plugins: Verify that an annotated set of
container-specific mandatory plugins are present and have processed a
container. Otherwise reject the creation of the container.

Containers can be annotated to tolerate missing required plugins. This
allows one to deploy mandatory plugins as containers themselves.

#### Default Validation Scope

Currently only OCI hook injection can be restricted using the default
validator. However, this probably will change in the future. Especially
when NRI is extended with control over new container parameters. If such
parameters will have security implications, corresponding configurable
restrictions will be introduced to the default validator.

## Runtime Adaptation

Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ require (
golang.org/x/sys v0.21.0
google.golang.org/grpc v1.57.1
google.golang.org/protobuf v1.34.1
gopkg.in/yaml.v3 v3.0.1
)

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

replace github.com/opencontainers/runtime-tools v0.9.0 => github.com/opencontainers/runtime-tools v0.0.0-20221026201742-946c877fa809
35 changes: 35 additions & 0 deletions pkg/adaptation/adaptation.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,10 @@ import (
"sort"
"sync"

"github.com/containerd/nri/pkg/adaptation/builtin"
"github.com/containerd/nri/pkg/api"
"github.com/containerd/nri/pkg/log"
validator "github.com/containerd/nri/plugins/default-validator/builtin"
"github.com/containerd/ttrpc"
"github.com/tetratelabs/wazero"
"github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1"
Expand Down Expand Up @@ -70,6 +72,7 @@ type Adaptation struct {
listener net.Listener
plugins []*plugin
validators []*plugin
builtin []*builtin.BuiltinPlugin
syncLock sync.RWMutex
wasmService *api.PluginPlugin
}
Expand Down Expand Up @@ -123,6 +126,24 @@ func WithTTRPCOptions(clientOpts []ttrpc.ClientOpts, serverOpts []ttrpc.ServerOp
}
}

// WithBuiltinPlugins sets extra builtin plugins to register.
func WithBuiltinPlugins(plugins ...*builtin.BuiltinPlugin) Option {
return func(r *Adaptation) error {
r.builtin = append(r.builtin, plugins...)
return nil
}
}

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

// New creates a new NRI Runtime.
func New(name, version string, syncFn SyncFn, updateFn UpdateFn, opts ...Option) (*Adaptation, error) {
var err error
Expand Down Expand Up @@ -433,6 +454,20 @@ func (r *Adaptation) startPlugins() (retErr error) {
}
}()

for _, b := range r.builtin {
log.Infof(noCtx, "starting builtin NRI plugin %q...", b.Index+"-"+b.Base)
p, err := r.newBuiltinPlugin(b)
if err != nil {
return fmt.Errorf("failed to initialize builtin NRI plugin %q: %v", b.Base, err)
}

if err := p.start(r.name, r.version); err != nil {
return fmt.Errorf("failed to start builtin NRI plugin %q: %v", b.Base, err)
}

plugins = append(plugins, p)
}

for i, name := range names {
log.Infof(noCtx, "starting pre-installed NRI plugin %q...", name)

Expand Down
Loading
Loading