diff --git a/daemon/config/config.go b/daemon/config/config.go index fde369a3a5..e4d6271fef 100644 --- a/daemon/config/config.go +++ b/daemon/config/config.go @@ -7,7 +7,6 @@ import ( "net" "net/url" "os" - "path/filepath" "strings" "golang.org/x/text/encoding" @@ -15,7 +14,6 @@ import ( "golang.org/x/text/transform" "github.com/container-orchestrated-devices/container-device-interface/pkg/cdi" - "github.com/containerd/containerd/runtime/v2/shim" "github.com/docker/docker/opts" "github.com/docker/docker/registry" "github.com/imdario/mergo" @@ -56,9 +54,6 @@ const ( // DefaultPluginNamespace is the name of the default containerd namespace used for plugins. DefaultPluginNamespace = "plugins.moby" - // LinuxV2RuntimeName is the runtime used to specify the containerd v2 runc shim - LinuxV2RuntimeName = "io.containerd.runc.v2" - // SeccompProfileDefault is the built-in default seccomp profile. SeccompProfileDefault = "builtin" // SeccompProfileUnconfined is a special profile name for seccomp to use an @@ -66,11 +61,6 @@ const ( SeccompProfileUnconfined = "unconfined" ) -var builtinRuntimes = map[string]bool{ - StockRuntimeName: true, - LinuxV2RuntimeName: true, -} - // flatOptions contains configuration keys // that MUST NOT be parsed as deep structures. // Use this to differentiate these options @@ -637,26 +627,10 @@ func Validate(config *Config) error { return errors.Errorf("invalid max download attempts: %d", config.MaxDownloadAttempts) } - // validate that "default" runtime is not reset - if runtimes := config.GetAllRuntimes(); len(runtimes) > 0 { - if _, ok := runtimes[StockRuntimeName]; ok { - return errors.Errorf("runtime name '%s' is reserved", StockRuntimeName) - } - } - if _, err := ParseGenericResources(config.NodeGenericResources); err != nil { return err } - if config.DefaultRuntime != "" { - if !builtinRuntimes[config.DefaultRuntime] { - runtimes := config.GetAllRuntimes() - if _, ok := runtimes[config.DefaultRuntime]; !ok && !IsPermissibleC8dRuntimeName(config.DefaultRuntime) { - return fmt.Errorf("specified default runtime '%s' does not exist", config.DefaultRuntime) - } - } - } - for _, h := range config.Hosts { if _, err := opts.ValidateHost(h); err != nil { return err @@ -676,37 +650,3 @@ func MaskCredentials(rawURL string) string { parsedURL.User = url.UserPassword("xxxxx", "xxxxx") return parsedURL.String() } - -// IsPermissibleC8dRuntimeName tests whether name is safe to pass into -// containerd as a runtime name, and whether the name is well-formed. -// It does not check if the runtime is installed. -// -// A runtime name containing slash characters is interpreted by containerd as -// the path to a runtime binary. If we allowed this, anyone with Engine API -// access could get containerd to execute an arbitrary binary as root. Although -// Engine API access is already equivalent to root on the host, the runtime name -// has not historically been a vector to run arbitrary code as root so users are -// not expecting it to become one. -// -// This restriction is not configurable. There are viable workarounds for -// legitimate use cases: administrators and runtime developers can make runtimes -// available for use with Docker by installing them onto PATH following the -// [binary naming convention] for containerd Runtime v2. -// -// [binary naming convention]: https://github.com/containerd/containerd/blob/main/runtime/v2/README.md#binary-naming -func IsPermissibleC8dRuntimeName(name string) bool { - // containerd uses a rather permissive test to validate runtime names: - // - // - Any name for which filepath.IsAbs(name) is interpreted as the absolute - // path to a shim binary. We want to block this behaviour. - // - Any name which contains at least one '.' character and no '/' characters - // and does not begin with a '.' character is a valid runtime name. The shim - // binary name is derived from the final two components of the name and - // searched for on the PATH. The name "a.." is technically valid per - // containerd's implementation: it would resolve to a binary named - // "containerd-shim---". - // - // https://github.com/containerd/containerd/blob/11ded166c15f92450958078cd13c6d87131ec563/runtime/v2/manager.go#L297-L317 - // https://github.com/containerd/containerd/blob/11ded166c15f92450958078cd13c6d87131ec563/runtime/v2/shim/util.go#L83-L93 - return !filepath.IsAbs(name) && !strings.ContainsRune(name, '/') && shim.BinaryName(name) != "" -} diff --git a/daemon/config/config_linux.go b/daemon/config/config_linux.go index da0f6c168d..124722ff8a 100644 --- a/daemon/config/config_linux.go +++ b/daemon/config/config_linux.go @@ -81,11 +81,6 @@ type Config struct { Rootless bool `json:"rootless,omitempty"` } -// GetAllRuntimes returns a copy of the runtimes map -func (conf *Config) GetAllRuntimes() map[string]types.Runtime { - return conf.Runtimes -} - // GetExecRoot returns the user configured Exec-root func (conf *Config) GetExecRoot() string { return conf.ExecRoot diff --git a/daemon/config/config_linux_test.go b/daemon/config/config_linux_test.go index 9333ae64df..8fa31d7b8e 100644 --- a/daemon/config/config_linux_test.go +++ b/daemon/config/config_linux_test.go @@ -3,10 +3,8 @@ package config // import "github.com/docker/docker/daemon/config" import ( "testing" - "github.com/docker/docker/api/types" "github.com/docker/docker/opts" units "github.com/docker/go-units" - "github.com/imdario/mergo" "github.com/spf13/pflag" "gotest.tools/v3/assert" is "gotest.tools/v3/assert/cmp" @@ -123,34 +121,6 @@ func TestDaemonConfigurationMergeShmSize(t *testing.T) { assert.Check(t, is.Equal(int64(expectedValue), cc.ShmSize.Value())) } -func TestUnixValidateConfigurationErrors(t *testing.T) { - testCases := []struct { - doc string - config *Config - expectedErr string - }{ - { - doc: `cannot override the stock runtime`, - config: &Config{ - Runtimes: map[string]types.Runtime{ - StockRuntimeName: {}, - }, - }, - expectedErr: `runtime name 'runc' is reserved`, - }, - } - for _, tc := range testCases { - tc := tc - t.Run(tc.doc, func(t *testing.T) { - cfg, err := New() - assert.NilError(t, err) - assert.Check(t, mergo.Merge(cfg, tc.config, mergo.WithOverride)) - err = Validate(cfg) - assert.ErrorContains(t, err, tc.expectedErr) - }) - } -} - func TestUnixGetInitPath(t *testing.T) { testCases := []struct { config *Config diff --git a/daemon/config/config_windows.go b/daemon/config/config_windows.go index d13ff00144..1d70e8c169 100644 --- a/daemon/config/config_windows.go +++ b/daemon/config/config_windows.go @@ -3,8 +3,6 @@ package config // import "github.com/docker/docker/daemon/config" import ( "os" "path/filepath" - - "github.com/docker/docker/api/types" ) const ( @@ -30,11 +28,6 @@ type Config struct { // for the Windows daemon.) } -// GetAllRuntimes returns a copy of the runtimes map -func (conf *Config) GetAllRuntimes() map[string]types.Runtime { - return map[string]types.Runtime{} -} - // GetExecRoot returns the user configured Exec-root func (conf *Config) GetExecRoot() string { return "" diff --git a/daemon/container_unix_test.go b/daemon/container_unix_test.go index 6aab11ee2a..5cdeaa94b4 100644 --- a/daemon/container_unix_test.go +++ b/daemon/container_unix_test.go @@ -34,7 +34,6 @@ func TestContainerWarningHostAndPublishPorts(t *testing.T) { d := &Daemon{} cfg, err := config.New() assert.NilError(t, err) - configureRuntimes(cfg) runtimes, err := setupRuntimes(cfg) assert.NilError(t, err) daemonCfg := &configStore{Config: *cfg, Runtimes: runtimes} diff --git a/daemon/daemon.go b/daemon/daemon.go index b4cc02b729..34f1ff061b 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -960,7 +960,7 @@ func NewDaemon(ctx context.Context, config *config.Config, pluginStore *plugin.S shimOpts interface{} ) if runtime.GOOS != "windows" { - shim, shimOpts, err = runtimes.Get(configStore.DefaultRuntime) + shim, shimOpts, err = runtimes.Get("") if err != nil { return nil, err } diff --git a/daemon/daemon_linux.go b/daemon/daemon_linux.go index a3d39fc83c..f046ebee18 100644 --- a/daemon/daemon_linux.go +++ b/daemon/daemon_linux.go @@ -244,7 +244,7 @@ func supportsRecursivelyReadOnly(cfg *configStore, runtime string) error { return fmt.Errorf("rro is not supported: %w (kernel is older than 5.12?)", err) } if runtime == "" { - runtime = cfg.DefaultRuntime + runtime = cfg.Runtimes.Default } features := cfg.Runtimes.Features(runtime) if features == nil { diff --git a/daemon/daemon_unix.go b/daemon/daemon_unix.go index 447407f0f6..0776c4e2d8 100644 --- a/daemon/daemon_unix.go +++ b/daemon/daemon_unix.go @@ -698,7 +698,7 @@ func verifyPlatformContainerSettings(daemon *Daemon, daemonCfg *configStore, hos } } if hostConfig.Runtime == "" { - hostConfig.Runtime = daemonCfg.DefaultRuntime + hostConfig.Runtime = daemonCfg.Runtimes.Default } if _, _, err := daemonCfg.Runtimes.Get(hostConfig.Runtime); err != nil { @@ -754,15 +754,6 @@ func verifyDaemonSettings(conf *config.Config) error { if conf.Rootless && UsingSystemd(conf) && cgroups.Mode() != cgroups.Unified { return fmt.Errorf("exec-opt native.cgroupdriver=systemd requires cgroup v2 for rootless mode") } - - configureRuntimes(conf) - if rtName := conf.DefaultRuntime; rtName != "" { - if _, ok := conf.Runtimes[rtName]; !ok { - if !config.IsPermissibleC8dRuntimeName(rtName) { - return fmt.Errorf("specified default runtime '%s' does not exist", rtName) - } - } - } return nil } diff --git a/daemon/info.go b/daemon/info.go index 241a8ebb9c..d1eeb699a5 100644 --- a/daemon/info.go +++ b/daemon/info.go @@ -66,7 +66,7 @@ func (daemon *Daemon) SystemInfo() *types.Info { daemon.fillDebugInfo(v) daemon.fillAPIInfo(v, &cfg.Config) // Retrieve platform specific info - daemon.fillPlatformInfo(v, sysInfo, &cfg.Config) + daemon.fillPlatformInfo(v, sysInfo, cfg) daemon.fillDriverInfo(v) daemon.fillPluginsInfo(v, &cfg.Config) daemon.fillSecurityOptions(v, sysInfo, &cfg.Config) @@ -117,7 +117,7 @@ func (daemon *Daemon) SystemVersion() types.Version { v.Platform.Name = dockerversion.PlatformName - daemon.fillPlatformVersion(&v, &cfg.Config) + daemon.fillPlatformVersion(&v, cfg) return v } diff --git a/daemon/info_unix.go b/daemon/info_unix.go index 22d313fb04..228c932a5d 100644 --- a/daemon/info_unix.go +++ b/daemon/info_unix.go @@ -9,6 +9,7 @@ import ( "path/filepath" "strings" + v2runcoptions "github.com/containerd/containerd/runtime/v2/runc/options" "github.com/docker/docker/api/types" containertypes "github.com/docker/docker/api/types/container" "github.com/docker/docker/daemon/config" @@ -19,8 +20,8 @@ import ( ) // fillPlatformInfo fills the platform related info. -func (daemon *Daemon) fillPlatformInfo(v *types.Info, sysInfo *sysinfo.SysInfo, cfg *config.Config) { - v.CgroupDriver = cgroupDriver(cfg) +func (daemon *Daemon) fillPlatformInfo(v *types.Info, sysInfo *sysinfo.SysInfo, cfg *configStore) { + v.CgroupDriver = cgroupDriver(&cfg.Config) v.CgroupVersion = "1" if sysInfo.CgroupUnified { v.CgroupVersion = "2" @@ -39,18 +40,21 @@ func (daemon *Daemon) fillPlatformInfo(v *types.Info, sysInfo *sysinfo.SysInfo, v.PidsLimit = sysInfo.PidsLimit } v.Runtimes = make(map[string]types.Runtime) - for n, r := range cfg.Runtimes { + for n, p := range stockRuntimes() { + v.Runtimes[n] = types.Runtime{Path: p} + } + for n, r := range cfg.Config.Runtimes { v.Runtimes[n] = types.Runtime{ Path: r.Path, Args: append([]string(nil), r.Args...), } } - v.DefaultRuntime = cfg.DefaultRuntime + v.DefaultRuntime = cfg.Runtimes.Default v.RuncCommit.ID = "N/A" v.ContainerdCommit.ID = "N/A" v.InitCommit.ID = "N/A" - if _, _, commit, err := parseDefaultRuntimeVersion(cfg); err != nil { + if _, _, commit, err := parseDefaultRuntimeVersion(&cfg.Runtimes); err != nil { logrus.Warnf(err.Error()) } else { v.RuncCommit.ID = commit @@ -166,7 +170,7 @@ func (daemon *Daemon) fillPlatformInfo(v *types.Info, sysInfo *sysinfo.SysInfo, } } -func (daemon *Daemon) fillPlatformVersion(v *types.Version, cfg *config.Config) { +func (daemon *Daemon) fillPlatformVersion(v *types.Version, cfg *configStore) { if rv, err := daemon.containerd.Version(context.Background()); err == nil { v.Components = append(v.Components, types.ComponentVersion{ Name: "containerd", @@ -177,11 +181,11 @@ func (daemon *Daemon) fillPlatformVersion(v *types.Version, cfg *config.Config) }) } - if _, ver, commit, err := parseDefaultRuntimeVersion(cfg); err != nil { + if _, ver, commit, err := parseDefaultRuntimeVersion(&cfg.Runtimes); err != nil { logrus.Warnf(err.Error()) } else { v.Components = append(v.Components, types.ComponentVersion{ - Name: cfg.DefaultRuntime, + Name: cfg.Runtimes.Default, Version: ver, Details: map[string]string{ "GitCommit": commit, @@ -331,19 +335,28 @@ func parseRuntimeVersion(v string) (runtime, version, commit string, err error) return runtime, version, commit, err } -func parseDefaultRuntimeVersion(cfg *config.Config) (runtime, version, commit string, err error) { - if rt, ok := cfg.Runtimes[cfg.DefaultRuntime]; ok { - rv, err := exec.Command(rt.Path, "--version").Output() - if err != nil { - return "", "", "", fmt.Errorf("failed to retrieve %s version: %w", rt.Path, err) - } - runtime, version, commit, err := parseRuntimeVersion(string(rv)) - if err != nil { - return "", "", "", fmt.Errorf("failed to parse %s version: %w", rt.Path, err) - } - return runtime, version, commit, err +func parseDefaultRuntimeVersion(rts *runtimes) (runtime, version, commit string, err error) { + shim, opts, err := rts.Get(rts.Default) + if err != nil { + return "", "", "", err } - return "", "", "", nil + shimopts, ok := opts.(*v2runcoptions.Options) + if !ok { + return "", "", "", fmt.Errorf("%s: retrieving version not supported", shim) + } + rt := shimopts.BinaryName + if rt == "" { + rt = defaultRuntimeName + } + rv, err := exec.Command(rt, "--version").Output() + if err != nil { + return "", "", "", fmt.Errorf("failed to retrieve %s version: %w", rt, err) + } + runtime, version, commit, err = parseRuntimeVersion(string(rv)) + if err != nil { + return "", "", "", fmt.Errorf("failed to parse %s version: %w", rt, err) + } + return runtime, version, commit, err } func cgroupNamespacesEnabled(sysInfo *sysinfo.SysInfo, cfg *config.Config) bool { diff --git a/daemon/info_windows.go b/daemon/info_windows.go index 613646aa48..bbb0172d35 100644 --- a/daemon/info_windows.go +++ b/daemon/info_windows.go @@ -7,10 +7,10 @@ import ( ) // fillPlatformInfo fills the platform related info. -func (daemon *Daemon) fillPlatformInfo(v *types.Info, sysInfo *sysinfo.SysInfo, cfg *config.Config) { +func (daemon *Daemon) fillPlatformInfo(v *types.Info, sysInfo *sysinfo.SysInfo, cfg *configStore) { } -func (daemon *Daemon) fillPlatformVersion(v *types.Version, cfg *config.Config) {} +func (daemon *Daemon) fillPlatformVersion(v *types.Version, cfg *configStore) {} func fillDriverWarnings(v *types.Info) { } diff --git a/daemon/reload_unix.go b/daemon/reload_unix.go index 8b24bbd63c..bfb970c71b 100644 --- a/daemon/reload_unix.go +++ b/daemon/reload_unix.go @@ -18,7 +18,6 @@ func (daemon *Daemon) reloadPlatform(txn *reloadTxn, newCfg *configStore, conf * if conf.IsValueSet("runtimes") { newCfg.Config.Runtimes = conf.Runtimes } - configureRuntimes(&newCfg.Config) var err error newCfg.Runtimes, err = setupRuntimes(&newCfg.Config) if err != nil { diff --git a/daemon/runtime_unix.go b/daemon/runtime_unix.go index 402f6eebb4..4d4be1ca91 100644 --- a/daemon/runtime_unix.go +++ b/daemon/runtime_unix.go @@ -16,7 +16,7 @@ import ( "github.com/containerd/containerd/plugin" v2runcoptions "github.com/containerd/containerd/runtime/v2/runc/options" - "github.com/docker/docker/api/types" + "github.com/containerd/containerd/runtime/v2/shim" "github.com/docker/docker/daemon/config" "github.com/docker/docker/errdefs" "github.com/docker/docker/libcontainerd/shimopts" @@ -29,6 +29,9 @@ import ( const ( defaultRuntimeName = "runc" + + // The runtime used to specify the containerd v2 runc shim + linuxV2RuntimeName = "io.containerd.runc.v2" ) type shimConfig struct { @@ -41,18 +44,15 @@ type shimConfig struct { } type runtimes struct { + Default string configured map[string]*shimConfig } -func configureRuntimes(conf *config.Config) { - if conf.DefaultRuntime == "" { - conf.DefaultRuntime = config.StockRuntimeName +func stockRuntimes() map[string]string { + return map[string]string{ + linuxV2RuntimeName: defaultRuntimeName, + config.StockRuntimeName: defaultRuntimeName, } - if conf.Runtimes == nil { - conf.Runtimes = make(map[string]types.Runtime) - } - conf.Runtimes[config.LinuxV2RuntimeName] = types.Runtime{Path: defaultRuntimeName} - conf.Runtimes[config.StockRuntimeName] = conf.Runtimes[config.LinuxV2RuntimeName] } func defaultV2ShimConfig(conf *config.Config, runtimePath string) *shimConfig { @@ -98,9 +98,27 @@ func initRuntimesDir(cfg *config.Config) error { } func setupRuntimes(cfg *config.Config) (runtimes, error) { + if _, ok := cfg.Runtimes[config.StockRuntimeName]; ok { + return runtimes{}, errors.Errorf("runtime name '%s' is reserved", config.StockRuntimeName) + } + newrt := runtimes{ + Default: cfg.DefaultRuntime, configured: make(map[string]*shimConfig), } + for name, path := range stockRuntimes() { + newrt.configured[name] = defaultV2ShimConfig(cfg, path) + } + + if newrt.Default != "" { + _, isStock := newrt.configured[newrt.Default] + _, isConfigured := cfg.Runtimes[newrt.Default] + if !isStock && !isConfigured && !isPermissibleC8dRuntimeName(newrt.Default) { + return runtimes{}, errors.Errorf("specified default runtime '%s' does not exist", newrt.Default) + } + } else { + newrt.Default = config.StockRuntimeName + } dir := runtimeScriptsDir(cfg) for name, rt := range cfg.Runtimes { @@ -180,7 +198,14 @@ func wrapRuntime(dir, name, binary string, args []string) (string, error) { return scriptPath, nil } +// Get returns the containerd runtime and options for name, suitable to pass +// into containerd.WithRuntime(). The runtime and options for the default +// runtime are returned when name is the empty string. func (r *runtimes) Get(name string) (string, interface{}, error) { + if name == "" { + name = r.Default + } + rt := r.configured[name] if rt != nil { if rt.PreflightCheck != nil { @@ -191,16 +216,54 @@ func (r *runtimes) Get(name string) (string, interface{}, error) { return rt.Shim, rt.Opts, nil } - if !config.IsPermissibleC8dRuntimeName(name) { + if !isPermissibleC8dRuntimeName(name) { return "", nil, errdefs.InvalidParameter(errors.Errorf("unknown or invalid runtime name: %s", name)) } return name, nil, nil } func (r *runtimes) Features(name string) *features.Features { + if name == "" { + name = r.Default + } + rt := r.configured[name] if rt != nil { return rt.Features } return nil } + +// isPermissibleC8dRuntimeName tests whether name is safe to pass into +// containerd as a runtime name, and whether the name is well-formed. +// It does not check if the runtime is installed. +// +// A runtime name containing slash characters is interpreted by containerd as +// the path to a runtime binary. If we allowed this, anyone with Engine API +// access could get containerd to execute an arbitrary binary as root. Although +// Engine API access is already equivalent to root on the host, the runtime name +// has not historically been a vector to run arbitrary code as root so users are +// not expecting it to become one. +// +// This restriction is not configurable. There are viable workarounds for +// legitimate use cases: administrators and runtime developers can make runtimes +// available for use with Docker by installing them onto PATH following the +// [binary naming convention] for containerd Runtime v2. +// +// [binary naming convention]: https://github.com/containerd/containerd/blob/main/runtime/v2/README.md#binary-naming +func isPermissibleC8dRuntimeName(name string) bool { + // containerd uses a rather permissive test to validate runtime names: + // + // - Any name for which filepath.IsAbs(name) is interpreted as the absolute + // path to a shim binary. We want to block this behaviour. + // - Any name which contains at least one '.' character and no '/' characters + // and does not begin with a '.' character is a valid runtime name. The shim + // binary name is derived from the final two components of the name and + // searched for on the PATH. The name "a.." is technically valid per + // containerd's implementation: it would resolve to a binary named + // "containerd-shim---". + // + // https://github.com/containerd/containerd/blob/11ded166c15f92450958078cd13c6d87131ec563/runtime/v2/manager.go#L297-L317 + // https://github.com/containerd/containerd/blob/11ded166c15f92450958078cd13c6d87131ec563/runtime/v2/shim/util.go#L83-L93 + return !filepath.IsAbs(name) && !strings.ContainsRune(name, '/') && shim.BinaryName(name) != "" +} diff --git a/daemon/runtime_unix_test.go b/daemon/runtime_unix_test.go index 582c77d7a5..e7db51c941 100644 --- a/daemon/runtime_unix_test.go +++ b/daemon/runtime_unix_test.go @@ -10,6 +10,7 @@ import ( "github.com/containerd/containerd/plugin" v2runcoptions "github.com/containerd/containerd/runtime/v2/runc/options" + "github.com/imdario/mergo" "gotest.tools/v3/assert" is "gotest.tools/v3/assert/cmp" @@ -18,81 +19,169 @@ import ( "github.com/docker/docker/errdefs" ) -func TestInitRuntimes_InvalidConfigs(t *testing.T) { +func TestSetupRuntimes(t *testing.T) { cases := []struct { name string - runtime types.Runtime + config *config.Config expectErr string }{ { - name: "Empty", + name: "Empty", + config: &config.Config{ + Runtimes: map[string]types.Runtime{ + "myruntime": {}, + }, + }, expectErr: "either a runtimeType or a path must be configured", }, { - name: "ArgsOnly", - runtime: types.Runtime{Args: []string{"foo", "bar"}}, + name: "ArgsOnly", + config: &config.Config{ + Runtimes: map[string]types.Runtime{ + "myruntime": {Args: []string{"foo", "bar"}}, + }, + }, expectErr: "either a runtimeType or a path must be configured", }, { - name: "OptionsOnly", - runtime: types.Runtime{Options: map[string]interface{}{"hello": "world"}}, + name: "OptionsOnly", + config: &config.Config{ + Runtimes: map[string]types.Runtime{ + "myruntime": {Options: map[string]interface{}{"hello": "world"}}, + }, + }, expectErr: "either a runtimeType or a path must be configured", }, { - name: "PathAndType", - runtime: types.Runtime{Path: "/bin/true", Type: "io.containerd.runsc.v1"}, + name: "PathAndType", + config: &config.Config{ + Runtimes: map[string]types.Runtime{ + "myruntime": {Path: "/bin/true", Type: "io.containerd.runsc.v1"}, + }, + }, expectErr: "cannot configure both", }, { - name: "PathAndOptions", - runtime: types.Runtime{Path: "/bin/true", Options: map[string]interface{}{"a": "b"}}, + name: "PathAndOptions", + config: &config.Config{ + Runtimes: map[string]types.Runtime{ + "myruntime": {Path: "/bin/true", Options: map[string]interface{}{"a": "b"}}, + }, + }, expectErr: "options cannot be used with a path runtime", }, { - name: "TypeAndArgs", - runtime: types.Runtime{Type: "io.containerd.runsc.v1", Args: []string{"--version"}}, + name: "TypeAndArgs", + config: &config.Config{ + Runtimes: map[string]types.Runtime{ + "myruntime": {Type: "io.containerd.runsc.v1", Args: []string{"--version"}}, + }, + }, expectErr: "args cannot be used with a runtimeType runtime", }, { name: "PathArgsOptions", - runtime: types.Runtime{ - Path: "/bin/true", - Args: []string{"--version"}, - Options: map[string]interface{}{"hmm": 3}, + config: &config.Config{ + Runtimes: map[string]types.Runtime{ + "myruntime": { + Path: "/bin/true", + Args: []string{"--version"}, + Options: map[string]interface{}{"hmm": 3}, + }, + }, }, expectErr: "options cannot be used with a path runtime", }, { name: "TypeOptionsArgs", - runtime: types.Runtime{ - Type: "io.containerd.kata.v2", - Options: map[string]interface{}{"a": "b"}, - Args: []string{"--help"}, + config: &config.Config{ + Runtimes: map[string]types.Runtime{ + "myruntime": { + Type: "io.containerd.kata.v2", + Options: map[string]interface{}{"a": "b"}, + Args: []string{"--help"}, + }, + }, }, expectErr: "args cannot be used with a runtimeType runtime", }, { name: "PathArgsTypeOptions", - runtime: types.Runtime{ - Path: "/bin/true", - Args: []string{"foo"}, - Type: "io.containerd.runsc.v1", - Options: map[string]interface{}{"a": "b"}, + config: &config.Config{ + Runtimes: map[string]types.Runtime{ + "myruntime": { + Path: "/bin/true", + Args: []string{"foo"}, + Type: "io.containerd.runsc.v1", + Options: map[string]interface{}{"a": "b"}, + }, + }, }, expectErr: "cannot configure both", }, + { + name: "CannotOverrideStockRuntime", + config: &config.Config{ + Runtimes: map[string]types.Runtime{ + config.StockRuntimeName: {}, + }, + }, + expectErr: `runtime name 'runc' is reserved`, + }, + { + name: "SetStockRuntimeAsDefault", + config: &config.Config{ + CommonConfig: config.CommonConfig{ + DefaultRuntime: config.StockRuntimeName, + }, + }, + }, + { + name: "SetLinuxRuntimeAsDefault", + config: &config.Config{ + CommonConfig: config.CommonConfig{ + DefaultRuntime: linuxV2RuntimeName, + }, + }, + }, + { + name: "CannotSetBogusRuntimeAsDefault", + config: &config.Config{ + CommonConfig: config.CommonConfig{ + DefaultRuntime: "notdefined", + }, + }, + expectErr: "specified default runtime 'notdefined' does not exist", + }, + { + name: "SetDefinedRuntimeAsDefault", + config: &config.Config{ + Runtimes: map[string]types.Runtime{ + "some-runtime": { + Path: "/usr/local/bin/file-not-found", + }, + }, + CommonConfig: config.CommonConfig{ + DefaultRuntime: "some-runtime", + }, + }, + }, } - - for _, tt := range cases { - t.Run(tt.name, func(t *testing.T) { + for _, tc := range cases { + tc := tc + t.Run(tc.name, func(t *testing.T) { cfg, err := config.New() assert.NilError(t, err) cfg.Root = t.TempDir() - cfg.Runtimes["myruntime"] = tt.runtime + assert.NilError(t, mergo.Merge(cfg, tc.config, mergo.WithOverride)) assert.Assert(t, initRuntimesDir(cfg)) _, err = setupRuntimes(cfg) - assert.Check(t, is.ErrorContains(err, tt.expectErr)) + if tc.expectErr == "" { + assert.NilError(t, err) + } else { + assert.ErrorContains(t, err, tc.expectErr) + } }) } } @@ -133,7 +222,6 @@ func TestGetRuntime(t *testing.T) { shimAliasName: shimAlias, configuredShimByPathName: configuredShimByPath, } - configureRuntimes(cfg) assert.NilError(t, initRuntimesDir(cfg)) runtimes, err := setupRuntimes(cfg) assert.NilError(t, err) @@ -179,6 +267,7 @@ func TestGetRuntime(t *testing.T) { { name: "EmptyString", runtime: "", + want: stockRuntime, }, { name: "PathToShim", diff --git a/daemon/start_unix.go b/daemon/start_unix.go index 8d54d2a353..fb59425a29 100644 --- a/daemon/start_unix.go +++ b/daemon/start_unix.go @@ -10,7 +10,7 @@ import ( func (daemon *Daemon) getLibcontainerdCreateOptions(daemonCfg *configStore, container *container.Container) (string, interface{}, error) { // Ensure a runtime has been assigned to this container if container.HostConfig.Runtime == "" { - container.HostConfig.Runtime = daemonCfg.DefaultRuntime + container.HostConfig.Runtime = daemonCfg.Runtimes.Default container.CheckpointTo(daemon.containersReplica) } diff --git a/integration-cli/docker_cli_daemon_test.go b/integration-cli/docker_cli_daemon_test.go index 6908f55739..de9d8e6c42 100644 --- a/integration-cli/docker_cli_daemon_test.go +++ b/integration-cli/docker_cli_daemon_test.go @@ -2275,7 +2275,7 @@ func (s *DockerDaemonSuite) TestRunWithRuntimeFromConfigFile(c *testing.T) { content, err := s.d.ReadLogFile() assert.NilError(c, err) - assert.Assert(c, is.Contains(string(content), `file configuration validation failed: runtime name 'runc' is reserved`)) + assert.Assert(c, is.Contains(string(content), `runtime name 'runc' is reserved`)) // Check that we can select a default runtime config = ` {