github.com/rumpl/bof@v23.0.0-rc.2+incompatible/daemon/config/config.go (about) 1 package config // import "github.com/docker/docker/daemon/config" 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "fmt" 7 "net" 8 "net/url" 9 "os" 10 "path/filepath" 11 "strings" 12 "sync" 13 14 "github.com/containerd/containerd/runtime/v2/shim" 15 "github.com/docker/docker/opts" 16 "github.com/docker/docker/pkg/authorization" 17 "github.com/docker/docker/registry" 18 "github.com/imdario/mergo" 19 "github.com/pkg/errors" 20 "github.com/sirupsen/logrus" 21 "github.com/spf13/pflag" 22 ) 23 24 const ( 25 // DefaultMaxConcurrentDownloads is the default value for 26 // maximum number of downloads that 27 // may take place at a time. 28 DefaultMaxConcurrentDownloads = 3 29 // DefaultMaxConcurrentUploads is the default value for 30 // maximum number of uploads that 31 // may take place at a time. 32 DefaultMaxConcurrentUploads = 5 33 // DefaultDownloadAttempts is the default value for 34 // maximum number of attempts that 35 // may take place at a time for each pull when the connection is lost. 36 DefaultDownloadAttempts = 5 37 // DefaultShmSize is the default value for container's shm size (64 MiB) 38 DefaultShmSize int64 = 64 * 1024 * 1024 39 // DefaultNetworkMtu is the default value for network MTU 40 DefaultNetworkMtu = 1500 41 // DisableNetworkBridge is the default value of the option to disable network bridge 42 DisableNetworkBridge = "none" 43 // DefaultShutdownTimeout is the default shutdown timeout (in seconds) for 44 // the daemon for containers to stop when it is shutting down. 45 DefaultShutdownTimeout = 15 46 // DefaultInitBinary is the name of the default init binary 47 DefaultInitBinary = "docker-init" 48 // DefaultRuntimeBinary is the default runtime to be used by 49 // containerd if none is specified 50 DefaultRuntimeBinary = "runc" 51 // DefaultContainersNamespace is the name of the default containerd namespace used for users containers. 52 DefaultContainersNamespace = "moby" 53 // DefaultPluginNamespace is the name of the default containerd namespace used for plugins. 54 DefaultPluginNamespace = "plugins.moby" 55 56 // LinuxV2RuntimeName is the runtime used to specify the containerd v2 runc shim 57 LinuxV2RuntimeName = "io.containerd.runc.v2" 58 59 // SeccompProfileDefault is the built-in default seccomp profile. 60 SeccompProfileDefault = "builtin" 61 // SeccompProfileUnconfined is a special profile name for seccomp to use an 62 // "unconfined" seccomp profile. 63 SeccompProfileUnconfined = "unconfined" 64 ) 65 66 var builtinRuntimes = map[string]bool{ 67 StockRuntimeName: true, 68 LinuxV2RuntimeName: true, 69 } 70 71 // flatOptions contains configuration keys 72 // that MUST NOT be parsed as deep structures. 73 // Use this to differentiate these options 74 // with others like the ones in CommonTLSOptions. 75 var flatOptions = map[string]bool{ 76 "cluster-store-opts": true, 77 "log-opts": true, 78 "runtimes": true, 79 "default-ulimits": true, 80 "features": true, 81 "builder": true, 82 } 83 84 // skipValidateOptions contains configuration keys 85 // that will be skipped from findConfigurationConflicts 86 // for unknown flag validation. 87 var skipValidateOptions = map[string]bool{ 88 "features": true, 89 "builder": true, 90 // Corresponding flag has been removed because it was already unusable 91 "deprecated-key-path": true, 92 } 93 94 // skipDuplicates contains configuration keys that 95 // will be skipped when checking duplicated 96 // configuration field defined in both daemon 97 // config file and from dockerd cli flags. 98 // This allows some configurations to be merged 99 // during the parsing. 100 var skipDuplicates = map[string]bool{ 101 "runtimes": true, 102 } 103 104 // LogConfig represents the default log configuration. 105 // It includes json tags to deserialize configuration from a file 106 // using the same names that the flags in the command line use. 107 type LogConfig struct { 108 Type string `json:"log-driver,omitempty"` 109 Config map[string]string `json:"log-opts,omitempty"` 110 } 111 112 // commonBridgeConfig stores all the platform-common bridge driver specific 113 // configuration. 114 type commonBridgeConfig struct { 115 Iface string `json:"bridge,omitempty"` 116 FixedCIDR string `json:"fixed-cidr,omitempty"` 117 } 118 119 // NetworkConfig stores the daemon-wide networking configurations 120 type NetworkConfig struct { 121 // Default address pools for docker networks 122 DefaultAddressPools opts.PoolsOpt `json:"default-address-pools,omitempty"` 123 // NetworkControlPlaneMTU allows to specify the control plane MTU, this will allow to optimize the network use in some components 124 NetworkControlPlaneMTU int `json:"network-control-plane-mtu,omitempty"` 125 } 126 127 // CommonTLSOptions defines TLS configuration for the daemon server. 128 // It includes json tags to deserialize configuration from a file 129 // using the same names that the flags in the command line use. 130 type CommonTLSOptions struct { 131 CAFile string `json:"tlscacert,omitempty"` 132 CertFile string `json:"tlscert,omitempty"` 133 KeyFile string `json:"tlskey,omitempty"` 134 } 135 136 // DNSConfig defines the DNS configurations. 137 type DNSConfig struct { 138 DNS []string `json:"dns,omitempty"` 139 DNSOptions []string `json:"dns-opts,omitempty"` 140 DNSSearch []string `json:"dns-search,omitempty"` 141 HostGatewayIP net.IP `json:"host-gateway-ip,omitempty"` 142 } 143 144 // CommonConfig defines the configuration of a docker daemon which is 145 // common across platforms. 146 // It includes json tags to deserialize configuration from a file 147 // using the same names that the flags in the command line use. 148 type CommonConfig struct { 149 AuthzMiddleware *authorization.Middleware `json:"-"` 150 AuthorizationPlugins []string `json:"authorization-plugins,omitempty"` // AuthorizationPlugins holds list of authorization plugins 151 AutoRestart bool `json:"-"` 152 Context map[string][]string `json:"-"` 153 DisableBridge bool `json:"-"` 154 ExecOptions []string `json:"exec-opts,omitempty"` 155 GraphDriver string `json:"storage-driver,omitempty"` 156 GraphOptions []string `json:"storage-opts,omitempty"` 157 Labels []string `json:"labels,omitempty"` 158 Mtu int `json:"mtu,omitempty"` 159 NetworkDiagnosticPort int `json:"network-diagnostic-port,omitempty"` 160 Pidfile string `json:"pidfile,omitempty"` 161 RawLogs bool `json:"raw-logs,omitempty"` 162 RootDeprecated string `json:"graph,omitempty"` // Deprecated: use Root instead. TODO(thaJeztah): remove in next release. 163 Root string `json:"data-root,omitempty"` 164 ExecRoot string `json:"exec-root,omitempty"` 165 SocketGroup string `json:"group,omitempty"` 166 CorsHeaders string `json:"api-cors-header,omitempty"` 167 168 // Proxies holds the proxies that are configured for the daemon. 169 Proxies `json:"proxies"` 170 171 // TrustKeyPath is used to generate the daemon ID and for signing schema 1 manifests 172 // when pushing to a registry which does not support schema 2. This field is marked as 173 // deprecated because schema 1 manifests are deprecated in favor of schema 2 and the 174 // daemon ID will use a dedicated identifier not shared with exported signatures. 175 TrustKeyPath string `json:"deprecated-key-path,omitempty"` 176 177 // LiveRestoreEnabled determines whether we should keep containers 178 // alive upon daemon shutdown/start 179 LiveRestoreEnabled bool `json:"live-restore,omitempty"` 180 181 // ClusterStore is the storage backend used for the cluster information. It is used by both 182 // multihost networking (to store networks and endpoints information) and by the node discovery 183 // mechanism. 184 // Deprecated: host-discovery and overlay networks with external k/v stores are deprecated 185 ClusterStore string `json:"cluster-store,omitempty"` 186 187 // ClusterOpts is used to pass options to the discovery package for tuning libkv settings, such 188 // as TLS configuration settings. 189 // Deprecated: host-discovery and overlay networks with external k/v stores are deprecated 190 ClusterOpts map[string]string `json:"cluster-store-opts,omitempty"` 191 192 // ClusterAdvertise is the network endpoint that the Engine advertises for the purpose of node 193 // discovery. This should be a 'host:port' combination on which that daemon instance is 194 // reachable by other hosts. 195 // Deprecated: host-discovery and overlay networks with external k/v stores are deprecated 196 ClusterAdvertise string `json:"cluster-advertise,omitempty"` 197 198 // MaxConcurrentDownloads is the maximum number of downloads that 199 // may take place at a time for each pull. 200 MaxConcurrentDownloads int `json:"max-concurrent-downloads,omitempty"` 201 202 // MaxConcurrentUploads is the maximum number of uploads that 203 // may take place at a time for each push. 204 MaxConcurrentUploads int `json:"max-concurrent-uploads,omitempty"` 205 206 // MaxDownloadAttempts is the maximum number of attempts that 207 // may take place at a time for each push. 208 MaxDownloadAttempts int `json:"max-download-attempts,omitempty"` 209 210 // ShutdownTimeout is the timeout value (in seconds) the daemon will wait for the container 211 // to stop when daemon is being shutdown 212 ShutdownTimeout int `json:"shutdown-timeout,omitempty"` 213 214 Debug bool `json:"debug,omitempty"` 215 Hosts []string `json:"hosts,omitempty"` 216 LogLevel string `json:"log-level,omitempty"` 217 TLS *bool `json:"tls,omitempty"` 218 TLSVerify *bool `json:"tlsverify,omitempty"` 219 220 // Embedded structs that allow config 221 // deserialization without the full struct. 222 CommonTLSOptions 223 224 // SwarmDefaultAdvertiseAddr is the default host/IP or network interface 225 // to use if a wildcard address is specified in the ListenAddr value 226 // given to the /swarm/init endpoint and no advertise address is 227 // specified. 228 SwarmDefaultAdvertiseAddr string `json:"swarm-default-advertise-addr"` 229 230 // SwarmRaftHeartbeatTick is the number of ticks in time for swarm mode raft quorum heartbeat 231 // Typical value is 1 232 SwarmRaftHeartbeatTick uint32 `json:"swarm-raft-heartbeat-tick"` 233 234 // SwarmRaftElectionTick is the number of ticks to elapse before followers in the quorum can propose 235 // a new round of leader election. Default, recommended value is at least 10X that of Heartbeat tick. 236 // Higher values can make the quorum less sensitive to transient faults in the environment, but this also 237 // means it takes longer for the managers to detect a down leader. 238 SwarmRaftElectionTick uint32 `json:"swarm-raft-election-tick"` 239 240 MetricsAddress string `json:"metrics-addr"` 241 242 DNSConfig 243 LogConfig 244 BridgeConfig // BridgeConfig holds bridge network specific configuration. 245 NetworkConfig 246 registry.ServiceOptions 247 248 sync.Mutex 249 // FIXME(vdemeester) This part is not that clear and is mainly dependent on cli flags 250 // It should probably be handled outside this package. 251 ValuesSet map[string]interface{} `json:"-"` 252 253 Experimental bool `json:"experimental"` // Experimental indicates whether experimental features should be exposed or not 254 255 // Exposed node Generic Resources 256 // e.g: ["orange=red", "orange=green", "orange=blue", "apple=3"] 257 NodeGenericResources []string `json:"node-generic-resources,omitempty"` 258 259 // ContainerAddr is the address used to connect to containerd if we're 260 // not starting it ourselves 261 ContainerdAddr string `json:"containerd,omitempty"` 262 263 // CriContainerd determines whether a supervised containerd instance 264 // should be configured with the CRI plugin enabled. This allows using 265 // Docker's containerd instance directly with a Kubernetes kubelet. 266 CriContainerd bool `json:"cri-containerd,omitempty"` 267 268 // Features contains a list of feature key value pairs indicating what features are enabled or disabled. 269 // If a certain feature doesn't appear in this list then it's unset (i.e. neither true nor false). 270 Features map[string]bool `json:"features,omitempty"` 271 272 Builder BuilderConfig `json:"builder,omitempty"` 273 274 ContainerdNamespace string `json:"containerd-namespace,omitempty"` 275 ContainerdPluginNamespace string `json:"containerd-plugin-namespace,omitempty"` 276 277 DefaultRuntime string `json:"default-runtime,omitempty"` 278 } 279 280 // Proxies holds the proxies that are configured for the daemon. 281 type Proxies struct { 282 HTTPProxy string `json:"http-proxy,omitempty"` 283 HTTPSProxy string `json:"https-proxy,omitempty"` 284 NoProxy string `json:"no-proxy,omitempty"` 285 } 286 287 // IsValueSet returns true if a configuration value 288 // was explicitly set in the configuration file. 289 func (conf *Config) IsValueSet(name string) bool { 290 if conf.ValuesSet == nil { 291 return false 292 } 293 _, ok := conf.ValuesSet[name] 294 return ok 295 } 296 297 // New returns a new fully initialized Config struct with default values set. 298 func New() (*Config, error) { 299 // platform-agnostic default values for the Config. 300 cfg := &Config{ 301 CommonConfig: CommonConfig{ 302 ShutdownTimeout: DefaultShutdownTimeout, 303 LogConfig: LogConfig{ 304 Config: make(map[string]string), 305 }, 306 MaxConcurrentDownloads: DefaultMaxConcurrentDownloads, 307 MaxConcurrentUploads: DefaultMaxConcurrentUploads, 308 MaxDownloadAttempts: DefaultDownloadAttempts, 309 Mtu: DefaultNetworkMtu, 310 NetworkConfig: NetworkConfig{ 311 NetworkControlPlaneMTU: DefaultNetworkMtu, 312 }, 313 ContainerdNamespace: DefaultContainersNamespace, 314 ContainerdPluginNamespace: DefaultPluginNamespace, 315 DefaultRuntime: StockRuntimeName, 316 }, 317 } 318 319 if err := setPlatformDefaults(cfg); err != nil { 320 return nil, err 321 } 322 323 return cfg, nil 324 } 325 326 // GetConflictFreeLabels validates Labels for conflict 327 // In swarm the duplicates for labels are removed 328 // so we only take same values here, no conflict values 329 // If the key-value is the same we will only take the last label 330 func GetConflictFreeLabels(labels []string) ([]string, error) { 331 labelMap := map[string]string{} 332 for _, label := range labels { 333 key, val, ok := strings.Cut(label, "=") 334 if ok { 335 // If there is a conflict we will return an error 336 if v, ok := labelMap[key]; ok && v != val { 337 return nil, errors.Errorf("conflict labels for %s=%s and %s=%s", key, val, key, v) 338 } 339 labelMap[key] = val 340 } 341 } 342 343 newLabels := []string{} 344 for k, v := range labelMap { 345 newLabels = append(newLabels, k+"="+v) 346 } 347 return newLabels, nil 348 } 349 350 // Reload reads the configuration in the host and reloads the daemon and server. 351 func Reload(configFile string, flags *pflag.FlagSet, reload func(*Config)) error { 352 logrus.Infof("Got signal to reload configuration, reloading from: %s", configFile) 353 newConfig, err := getConflictFreeConfiguration(configFile, flags) 354 if err != nil { 355 if flags.Changed("config-file") || !os.IsNotExist(err) { 356 return errors.Wrapf(err, "unable to configure the Docker daemon with file %s", configFile) 357 } 358 newConfig, err = New() 359 if err != nil { 360 return err 361 } 362 } 363 364 // Check if duplicate label-keys with different values are found 365 newLabels, err := GetConflictFreeLabels(newConfig.Labels) 366 if err != nil { 367 return err 368 } 369 newConfig.Labels = newLabels 370 371 // TODO(thaJeztah) This logic is problematic and needs a rewrite; 372 // This is validating newConfig before the "reload()" callback is executed. 373 // At this point, newConfig may be a partial configuration, to be merged 374 // with the existing configuration in the "reload()" callback. Validating 375 // this config before it's merged can result in incorrect validation errors. 376 // 377 // However, the current "reload()" callback we use is DaemonCli.reloadConfig(), 378 // which includes a call to Daemon.Reload(), which both performs "merging" 379 // and validation, as well as actually updating the daemon configuration. 380 // Calling DaemonCli.reloadConfig() *before* validation, could thus lead to 381 // a failure in that function (making the reload non-atomic). 382 // 383 // While *some* errors could always occur when applying/updating the config, 384 // we should make it more atomic, and; 385 // 386 // 1. get (a copy of) the active configuration 387 // 2. get the new configuration 388 // 3. apply the (reloadable) options from the new configuration 389 // 4. validate the merged results 390 // 5. apply the new configuration. 391 if err := Validate(newConfig); err != nil { 392 return errors.Wrap(err, "file configuration validation failed") 393 } 394 395 reload(newConfig) 396 return nil 397 } 398 399 // boolValue is an interface that boolean value flags implement 400 // to tell the command line how to make -name equivalent to -name=true. 401 type boolValue interface { 402 IsBoolFlag() bool 403 } 404 405 // MergeDaemonConfigurations reads a configuration file, 406 // loads the file configuration in an isolated structure, 407 // and merges the configuration provided from flags on top 408 // if there are no conflicts. 409 func MergeDaemonConfigurations(flagsConfig *Config, flags *pflag.FlagSet, configFile string) (*Config, error) { 410 fileConfig, err := getConflictFreeConfiguration(configFile, flags) 411 if err != nil { 412 return nil, err 413 } 414 415 // merge flags configuration on top of the file configuration 416 if err := mergo.Merge(fileConfig, flagsConfig); err != nil { 417 return nil, err 418 } 419 420 // validate the merged fileConfig and flagsConfig 421 if err := Validate(fileConfig); err != nil { 422 return nil, errors.Wrap(err, "merged configuration validation from file and command line flags failed") 423 } 424 425 return fileConfig, nil 426 } 427 428 // getConflictFreeConfiguration loads the configuration from a JSON file. 429 // It compares that configuration with the one provided by the flags, 430 // and returns an error if there are conflicts. 431 func getConflictFreeConfiguration(configFile string, flags *pflag.FlagSet) (*Config, error) { 432 b, err := os.ReadFile(configFile) 433 if err != nil { 434 return nil, err 435 } 436 437 // Strip the UTF-8 BOM if present ([RFC 8259] allows JSON implementations to optionally strip the BOM for 438 // interoperability; do so here as Notepad on older versions of Windows Server insists on a BOM). 439 // [RFC 8259]: https://tools.ietf.org/html/rfc8259#section-8.1 440 b = bytes.TrimPrefix(b, []byte("\xef\xbb\xbf")) 441 // Trim whitespace so that an empty config can be detected for an early return. 442 b = bytes.TrimSpace(b) 443 444 var config Config 445 if len(b) == 0 { 446 return &config, nil // early return on empty config 447 } 448 449 if flags != nil { 450 var jsonConfig map[string]interface{} 451 if err := json.Unmarshal(b, &jsonConfig); err != nil { 452 return nil, err 453 } 454 455 configSet := configValuesSet(jsonConfig) 456 457 if err := findConfigurationConflicts(configSet, flags); err != nil { 458 return nil, err 459 } 460 461 // Override flag values to make sure the values set in the config file with nullable values, like `false`, 462 // are not overridden by default truthy values from the flags that were not explicitly set. 463 // See https://github.com/docker/docker/issues/20289 for an example. 464 // 465 // TODO: Rewrite configuration logic to avoid same issue with other nullable values, like numbers. 466 namedOptions := make(map[string]interface{}) 467 for key, value := range configSet { 468 f := flags.Lookup(key) 469 if f == nil { // ignore named flags that don't match 470 namedOptions[key] = value 471 continue 472 } 473 474 if _, ok := f.Value.(boolValue); ok { 475 f.Value.Set(fmt.Sprintf("%v", value)) 476 } 477 } 478 if len(namedOptions) > 0 { 479 // set also default for mergeVal flags that are boolValue at the same time. 480 flags.VisitAll(func(f *pflag.Flag) { 481 if opt, named := f.Value.(opts.NamedOption); named { 482 v, set := namedOptions[opt.Name()] 483 _, boolean := f.Value.(boolValue) 484 if set && boolean { 485 f.Value.Set(fmt.Sprintf("%v", v)) 486 } 487 } 488 }) 489 } 490 491 config.ValuesSet = configSet 492 } 493 494 if err := json.Unmarshal(b, &config); err != nil { 495 return nil, err 496 } 497 498 return &config, nil 499 } 500 501 // configValuesSet returns the configuration values explicitly set in the file. 502 func configValuesSet(config map[string]interface{}) map[string]interface{} { 503 flatten := make(map[string]interface{}) 504 for k, v := range config { 505 if m, isMap := v.(map[string]interface{}); isMap && !flatOptions[k] { 506 for km, vm := range m { 507 flatten[km] = vm 508 } 509 continue 510 } 511 512 flatten[k] = v 513 } 514 return flatten 515 } 516 517 // findConfigurationConflicts iterates over the provided flags searching for 518 // duplicated configurations and unknown keys. It returns an error with all the conflicts if 519 // it finds any. 520 func findConfigurationConflicts(config map[string]interface{}, flags *pflag.FlagSet) error { 521 // 1. Search keys from the file that we don't recognize as flags. 522 unknownKeys := make(map[string]interface{}) 523 for key, value := range config { 524 if flag := flags.Lookup(key); flag == nil && !skipValidateOptions[key] { 525 unknownKeys[key] = value 526 } 527 } 528 529 // 2. Discard values that implement NamedOption. 530 // Their configuration name differs from their flag name, like `labels` and `label`. 531 if len(unknownKeys) > 0 { 532 unknownNamedConflicts := func(f *pflag.Flag) { 533 if namedOption, ok := f.Value.(opts.NamedOption); ok { 534 delete(unknownKeys, namedOption.Name()) 535 } 536 } 537 flags.VisitAll(unknownNamedConflicts) 538 } 539 540 if len(unknownKeys) > 0 { 541 var unknown []string 542 for key := range unknownKeys { 543 unknown = append(unknown, key) 544 } 545 return errors.Errorf("the following directives don't match any configuration option: %s", strings.Join(unknown, ", ")) 546 } 547 548 var conflicts []string 549 printConflict := func(name string, flagValue, fileValue interface{}) string { 550 switch name { 551 case "http-proxy", "https-proxy": 552 flagValue = MaskCredentials(flagValue.(string)) 553 fileValue = MaskCredentials(fileValue.(string)) 554 } 555 return fmt.Sprintf("%s: (from flag: %v, from file: %v)", name, flagValue, fileValue) 556 } 557 558 // 3. Search keys that are present as a flag and as a file option. 559 duplicatedConflicts := func(f *pflag.Flag) { 560 // search option name in the json configuration payload if the value is a named option 561 if namedOption, ok := f.Value.(opts.NamedOption); ok { 562 if optsValue, ok := config[namedOption.Name()]; ok && !skipDuplicates[namedOption.Name()] { 563 conflicts = append(conflicts, printConflict(namedOption.Name(), f.Value.String(), optsValue)) 564 } 565 } else { 566 // search flag name in the json configuration payload 567 for _, name := range []string{f.Name, f.Shorthand} { 568 if value, ok := config[name]; ok && !skipDuplicates[name] { 569 conflicts = append(conflicts, printConflict(name, f.Value.String(), value)) 570 break 571 } 572 } 573 } 574 } 575 576 flags.Visit(duplicatedConflicts) 577 578 if len(conflicts) > 0 { 579 return errors.Errorf("the following directives are specified both as a flag and in the configuration file: %s", strings.Join(conflicts, ", ")) 580 } 581 return nil 582 } 583 584 // Validate validates some specific configs. 585 // such as config.DNS, config.Labels, config.DNSSearch, 586 // as well as config.MaxConcurrentDownloads, config.MaxConcurrentUploads and config.MaxDownloadAttempts. 587 func Validate(config *Config) error { 588 //nolint:staticcheck // TODO(thaJeztah): remove in next release. 589 if config.RootDeprecated != "" { 590 return errors.New(`the "graph" config file option is deprecated; use "data-root" instead`) 591 } 592 593 // validate log-level 594 if config.LogLevel != "" { 595 if _, err := logrus.ParseLevel(config.LogLevel); err != nil { 596 return errors.Errorf("invalid logging level: %s", config.LogLevel) 597 } 598 } 599 600 // validate DNS 601 for _, dns := range config.DNS { 602 if _, err := opts.ValidateIPAddress(dns); err != nil { 603 return err 604 } 605 } 606 607 // validate DNSSearch 608 for _, dnsSearch := range config.DNSSearch { 609 if _, err := opts.ValidateDNSSearch(dnsSearch); err != nil { 610 return err 611 } 612 } 613 614 // validate Labels 615 for _, label := range config.Labels { 616 if _, err := opts.ValidateLabel(label); err != nil { 617 return err 618 } 619 } 620 621 // TODO(thaJeztah) Validations below should not accept "0" to be valid; see Validate() for a more in-depth description of this problem 622 if config.Mtu < 0 { 623 return errors.Errorf("invalid default MTU: %d", config.Mtu) 624 } 625 if config.MaxConcurrentDownloads < 0 { 626 return errors.Errorf("invalid max concurrent downloads: %d", config.MaxConcurrentDownloads) 627 } 628 if config.MaxConcurrentUploads < 0 { 629 return errors.Errorf("invalid max concurrent uploads: %d", config.MaxConcurrentUploads) 630 } 631 if config.MaxDownloadAttempts < 0 { 632 return errors.Errorf("invalid max download attempts: %d", config.MaxDownloadAttempts) 633 } 634 635 // validate that "default" runtime is not reset 636 if runtimes := config.GetAllRuntimes(); len(runtimes) > 0 { 637 if _, ok := runtimes[StockRuntimeName]; ok { 638 return errors.Errorf("runtime name '%s' is reserved", StockRuntimeName) 639 } 640 } 641 642 if _, err := ParseGenericResources(config.NodeGenericResources); err != nil { 643 return err 644 } 645 646 if defaultRuntime := config.GetDefaultRuntimeName(); defaultRuntime != "" { 647 if !builtinRuntimes[defaultRuntime] { 648 runtimes := config.GetAllRuntimes() 649 if _, ok := runtimes[defaultRuntime]; !ok && !IsPermissibleC8dRuntimeName(defaultRuntime) { 650 return errors.Errorf("specified default runtime '%s' does not exist", defaultRuntime) 651 } 652 } 653 } 654 655 for _, h := range config.Hosts { 656 if _, err := opts.ValidateHost(h); err != nil { 657 return err 658 } 659 } 660 661 // validate platform-specific settings 662 return config.ValidatePlatformConfig() 663 } 664 665 // GetDefaultRuntimeName returns the current default runtime 666 func (conf *Config) GetDefaultRuntimeName() string { 667 conf.Lock() 668 rt := conf.DefaultRuntime 669 conf.Unlock() 670 671 return rt 672 } 673 674 // MaskCredentials masks credentials that are in an URL. 675 func MaskCredentials(rawURL string) string { 676 parsedURL, err := url.Parse(rawURL) 677 if err != nil || parsedURL.User == nil { 678 return rawURL 679 } 680 parsedURL.User = url.UserPassword("xxxxx", "xxxxx") 681 return parsedURL.String() 682 } 683 684 // IsPermissibleC8dRuntimeName tests whether name is safe to pass into 685 // containerd as a runtime name, and whether the name is well-formed. 686 // It does not check if the runtime is installed. 687 // 688 // A runtime name containing slash characters is interpreted by containerd as 689 // the path to a runtime binary. If we allowed this, anyone with Engine API 690 // access could get containerd to execute an arbitrary binary as root. Although 691 // Engine API access is already equivalent to root on the host, the runtime name 692 // has not historically been a vector to run arbitrary code as root so users are 693 // not expecting it to become one. 694 // 695 // This restriction is not configurable. There are viable workarounds for 696 // legitimate use cases: administrators and runtime developers can make runtimes 697 // available for use with Docker by installing them onto PATH following the 698 // [binary naming convention] for containerd Runtime v2. 699 // 700 // [binary naming convention]: https://github.com/containerd/containerd/blob/main/runtime/v2/README.md#binary-naming 701 func IsPermissibleC8dRuntimeName(name string) bool { 702 // containerd uses a rather permissive test to validate runtime names: 703 // 704 // - Any name for which filepath.IsAbs(name) is interpreted as the absolute 705 // path to a shim binary. We want to block this behaviour. 706 // - Any name which contains at least one '.' character and no '/' characters 707 // and does not begin with a '.' character is a valid runtime name. The shim 708 // binary name is derived from the final two components of the name and 709 // searched for on the PATH. The name "a.." is technically valid per 710 // containerd's implementation: it would resolve to a binary named 711 // "containerd-shim---". 712 // 713 // https://github.com/containerd/containerd/blob/11ded166c15f92450958078cd13c6d87131ec563/runtime/v2/manager.go#L297-L317 714 // https://github.com/containerd/containerd/blob/11ded166c15f92450958078cd13c6d87131ec563/runtime/v2/shim/util.go#L83-L93 715 return !filepath.IsAbs(name) && !strings.ContainsRune(name, '/') && shim.BinaryName(name) != "" 716 }