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