config.go 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698
  1. package config // import "github.com/docker/docker/daemon/config"
  2. import (
  3. "bytes"
  4. "context"
  5. "encoding/json"
  6. "fmt"
  7. "net"
  8. "net/url"
  9. "os"
  10. "strings"
  11. "github.com/containerd/log"
  12. "github.com/docker/docker/api"
  13. "github.com/docker/docker/api/types/versions"
  14. "github.com/docker/docker/opts"
  15. "github.com/docker/docker/registry"
  16. "github.com/imdario/mergo"
  17. "github.com/pkg/errors"
  18. "github.com/spf13/pflag"
  19. "golang.org/x/text/encoding"
  20. "golang.org/x/text/encoding/unicode"
  21. "golang.org/x/text/transform"
  22. )
  23. const (
  24. // DefaultMaxConcurrentDownloads is the default value for
  25. // maximum number of downloads that
  26. // may take place at a time.
  27. DefaultMaxConcurrentDownloads = 3
  28. // DefaultMaxConcurrentUploads is the default value for
  29. // maximum number of uploads that
  30. // may take place at a time.
  31. DefaultMaxConcurrentUploads = 5
  32. // DefaultDownloadAttempts is the default value for
  33. // maximum number of attempts that
  34. // may take place at a time for each pull when the connection is lost.
  35. DefaultDownloadAttempts = 5
  36. // DefaultShmSize is the default value for container's shm size (64 MiB)
  37. DefaultShmSize int64 = 64 * 1024 * 1024
  38. // DefaultNetworkMtu is the default value for network MTU
  39. DefaultNetworkMtu = 1500
  40. // DisableNetworkBridge is the default value of the option to disable network bridge
  41. DisableNetworkBridge = "none"
  42. // DefaultShutdownTimeout is the default shutdown timeout (in seconds) for
  43. // the daemon for containers to stop when it is shutting down.
  44. DefaultShutdownTimeout = 15
  45. // DefaultInitBinary is the name of the default init binary
  46. DefaultInitBinary = "docker-init"
  47. // DefaultRuntimeBinary is the default runtime to be used by
  48. // containerd if none is specified
  49. DefaultRuntimeBinary = "runc"
  50. // DefaultContainersNamespace is the name of the default containerd namespace used for users containers.
  51. DefaultContainersNamespace = "moby"
  52. // DefaultPluginNamespace is the name of the default containerd namespace used for plugins.
  53. DefaultPluginNamespace = "plugins.moby"
  54. // defaultMinAPIVersion is the minimum API version supported by the API.
  55. // This version can be overridden through the "DOCKER_MIN_API_VERSION"
  56. // environment variable. The minimum allowed version is determined
  57. // by [minAPIVersion].
  58. defaultMinAPIVersion = "1.24"
  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. // flatOptions contains configuration keys
  66. // that MUST NOT be parsed as deep structures.
  67. // Use this to differentiate these options
  68. // with others like the ones in TLSOptions.
  69. var flatOptions = map[string]bool{
  70. "cluster-store-opts": true,
  71. "default-network-opts": true,
  72. "log-opts": true,
  73. "runtimes": true,
  74. "default-ulimits": true,
  75. "features": true,
  76. "builder": true,
  77. }
  78. // skipValidateOptions contains configuration keys
  79. // that will be skipped from findConfigurationConflicts
  80. // for unknown flag validation.
  81. var skipValidateOptions = map[string]bool{
  82. "features": true,
  83. "builder": true,
  84. // Corresponding flag has been removed because it was already unusable
  85. "deprecated-key-path": true,
  86. }
  87. // skipDuplicates contains configuration keys that
  88. // will be skipped when checking duplicated
  89. // configuration field defined in both daemon
  90. // config file and from dockerd cli flags.
  91. // This allows some configurations to be merged
  92. // during the parsing.
  93. var skipDuplicates = map[string]bool{
  94. "runtimes": true,
  95. }
  96. // LogConfig represents the default log configuration.
  97. // It includes json tags to deserialize configuration from a file
  98. // using the same names that the flags in the command line use.
  99. type LogConfig struct {
  100. Type string `json:"log-driver,omitempty"`
  101. Config map[string]string `json:"log-opts,omitempty"`
  102. }
  103. // commonBridgeConfig stores all the platform-common bridge driver specific
  104. // configuration.
  105. type commonBridgeConfig struct {
  106. Iface string `json:"bridge,omitempty"`
  107. FixedCIDR string `json:"fixed-cidr,omitempty"`
  108. }
  109. // NetworkConfig stores the daemon-wide networking configurations
  110. type NetworkConfig struct {
  111. // Default address pools for docker networks
  112. DefaultAddressPools opts.PoolsOpt `json:"default-address-pools,omitempty"`
  113. // NetworkControlPlaneMTU allows to specify the control plane MTU, this will allow to optimize the network use in some components
  114. NetworkControlPlaneMTU int `json:"network-control-plane-mtu,omitempty"`
  115. // Default options for newly created networks
  116. DefaultNetworkOpts map[string]map[string]string `json:"default-network-opts,omitempty"`
  117. }
  118. // TLSOptions defines TLS configuration for the daemon server.
  119. // It includes json tags to deserialize configuration from a file
  120. // using the same names that the flags in the command line use.
  121. type TLSOptions struct {
  122. CAFile string `json:"tlscacert,omitempty"`
  123. CertFile string `json:"tlscert,omitempty"`
  124. KeyFile string `json:"tlskey,omitempty"`
  125. }
  126. // DNSConfig defines the DNS configurations.
  127. type DNSConfig struct {
  128. DNS []net.IP `json:"dns,omitempty"`
  129. DNSOptions []string `json:"dns-opts,omitempty"`
  130. DNSSearch []string `json:"dns-search,omitempty"`
  131. HostGatewayIP net.IP `json:"host-gateway-ip,omitempty"`
  132. }
  133. // CommonConfig defines the configuration of a docker daemon which is
  134. // common across platforms.
  135. // It includes json tags to deserialize configuration from a file
  136. // using the same names that the flags in the command line use.
  137. type CommonConfig struct {
  138. AuthorizationPlugins []string `json:"authorization-plugins,omitempty"` // AuthorizationPlugins holds list of authorization plugins
  139. AutoRestart bool `json:"-"`
  140. DisableBridge bool `json:"-"`
  141. ExecOptions []string `json:"exec-opts,omitempty"`
  142. GraphDriver string `json:"storage-driver,omitempty"`
  143. GraphOptions []string `json:"storage-opts,omitempty"`
  144. Labels []string `json:"labels,omitempty"`
  145. NetworkDiagnosticPort int `json:"network-diagnostic-port,omitempty"`
  146. Pidfile string `json:"pidfile,omitempty"`
  147. RawLogs bool `json:"raw-logs,omitempty"`
  148. Root string `json:"data-root,omitempty"`
  149. ExecRoot string `json:"exec-root,omitempty"`
  150. SocketGroup string `json:"group,omitempty"`
  151. CorsHeaders string `json:"api-cors-header,omitempty"`
  152. // Proxies holds the proxies that are configured for the daemon.
  153. Proxies `json:"proxies"`
  154. // LiveRestoreEnabled determines whether we should keep containers
  155. // alive upon daemon shutdown/start
  156. LiveRestoreEnabled bool `json:"live-restore,omitempty"`
  157. // MaxConcurrentDownloads is the maximum number of downloads that
  158. // may take place at a time for each pull.
  159. MaxConcurrentDownloads int `json:"max-concurrent-downloads,omitempty"`
  160. // MaxConcurrentUploads is the maximum number of uploads that
  161. // may take place at a time for each push.
  162. MaxConcurrentUploads int `json:"max-concurrent-uploads,omitempty"`
  163. // MaxDownloadAttempts is the maximum number of attempts that
  164. // may take place at a time for each push.
  165. MaxDownloadAttempts int `json:"max-download-attempts,omitempty"`
  166. // ShutdownTimeout is the timeout value (in seconds) the daemon will wait for the container
  167. // to stop when daemon is being shutdown
  168. ShutdownTimeout int `json:"shutdown-timeout,omitempty"`
  169. Debug bool `json:"debug,omitempty"`
  170. Hosts []string `json:"hosts,omitempty"`
  171. LogLevel string `json:"log-level,omitempty"`
  172. LogFormat log.OutputFormat `json:"log-format,omitempty"`
  173. TLS *bool `json:"tls,omitempty"`
  174. TLSVerify *bool `json:"tlsverify,omitempty"`
  175. // Embedded structs that allow config
  176. // deserialization without the full struct.
  177. TLSOptions
  178. // SwarmDefaultAdvertiseAddr is the default host/IP or network interface
  179. // to use if a wildcard address is specified in the ListenAddr value
  180. // given to the /swarm/init endpoint and no advertise address is
  181. // specified.
  182. SwarmDefaultAdvertiseAddr string `json:"swarm-default-advertise-addr"`
  183. // SwarmRaftHeartbeatTick is the number of ticks in time for swarm mode raft quorum heartbeat
  184. // Typical value is 1
  185. SwarmRaftHeartbeatTick uint32 `json:"swarm-raft-heartbeat-tick"`
  186. // SwarmRaftElectionTick is the number of ticks to elapse before followers in the quorum can propose
  187. // a new round of leader election. Default, recommended value is at least 10X that of Heartbeat tick.
  188. // Higher values can make the quorum less sensitive to transient faults in the environment, but this also
  189. // means it takes longer for the managers to detect a down leader.
  190. SwarmRaftElectionTick uint32 `json:"swarm-raft-election-tick"`
  191. MetricsAddress string `json:"metrics-addr"`
  192. DNSConfig
  193. LogConfig
  194. BridgeConfig // BridgeConfig holds bridge network specific configuration.
  195. NetworkConfig
  196. registry.ServiceOptions
  197. // FIXME(vdemeester) This part is not that clear and is mainly dependent on cli flags
  198. // It should probably be handled outside this package.
  199. ValuesSet map[string]interface{} `json:"-"`
  200. Experimental bool `json:"experimental"` // Experimental indicates whether experimental features should be exposed or not
  201. // Exposed node Generic Resources
  202. // e.g: ["orange=red", "orange=green", "orange=blue", "apple=3"]
  203. NodeGenericResources []string `json:"node-generic-resources,omitempty"`
  204. // ContainerAddr is the address used to connect to containerd if we're
  205. // not starting it ourselves
  206. ContainerdAddr string `json:"containerd,omitempty"`
  207. // CriContainerd determines whether a supervised containerd instance
  208. // should be configured with the CRI plugin enabled. This allows using
  209. // Docker's containerd instance directly with a Kubernetes kubelet.
  210. CriContainerd bool `json:"cri-containerd,omitempty"`
  211. // Features contains a list of feature key value pairs indicating what features are enabled or disabled.
  212. // If a certain feature doesn't appear in this list then it's unset (i.e. neither true nor false).
  213. Features map[string]bool `json:"features,omitempty"`
  214. Builder BuilderConfig `json:"builder,omitempty"`
  215. ContainerdNamespace string `json:"containerd-namespace,omitempty"`
  216. ContainerdPluginNamespace string `json:"containerd-plugin-namespace,omitempty"`
  217. DefaultRuntime string `json:"default-runtime,omitempty"`
  218. // CDISpecDirs is a list of directories in which CDI specifications can be found.
  219. CDISpecDirs []string `json:"cdi-spec-dirs,omitempty"`
  220. // The minimum API version provided by the daemon. Defaults to [defaultMinAPIVersion].
  221. //
  222. // The DOCKER_MIN_API_VERSION allows overriding the minimum API version within
  223. // constraints of the minimum and maximum (current) supported API versions.
  224. //
  225. // API versions older than [defaultMinAPIVersion] are deprecated and
  226. // to be removed in a future release. The "DOCKER_MIN_API_VERSION" env
  227. // var should only be used for exceptional cases, and the MinAPIVersion
  228. // field is therefore not included in the JSON representation.
  229. MinAPIVersion string `json:"-"`
  230. }
  231. // Proxies holds the proxies that are configured for the daemon.
  232. type Proxies struct {
  233. HTTPProxy string `json:"http-proxy,omitempty"`
  234. HTTPSProxy string `json:"https-proxy,omitempty"`
  235. NoProxy string `json:"no-proxy,omitempty"`
  236. }
  237. // IsValueSet returns true if a configuration value
  238. // was explicitly set in the configuration file.
  239. func (conf *Config) IsValueSet(name string) bool {
  240. if conf.ValuesSet == nil {
  241. return false
  242. }
  243. _, ok := conf.ValuesSet[name]
  244. return ok
  245. }
  246. // New returns a new fully initialized Config struct with default values set.
  247. func New() (*Config, error) {
  248. // platform-agnostic default values for the Config.
  249. cfg := &Config{
  250. CommonConfig: CommonConfig{
  251. ShutdownTimeout: DefaultShutdownTimeout,
  252. LogConfig: LogConfig{
  253. Config: make(map[string]string),
  254. },
  255. MaxConcurrentDownloads: DefaultMaxConcurrentDownloads,
  256. MaxConcurrentUploads: DefaultMaxConcurrentUploads,
  257. MaxDownloadAttempts: DefaultDownloadAttempts,
  258. BridgeConfig: BridgeConfig{
  259. DefaultBridgeConfig: DefaultBridgeConfig{
  260. MTU: DefaultNetworkMtu,
  261. },
  262. },
  263. NetworkConfig: NetworkConfig{
  264. NetworkControlPlaneMTU: DefaultNetworkMtu,
  265. DefaultNetworkOpts: make(map[string]map[string]string),
  266. },
  267. ContainerdNamespace: DefaultContainersNamespace,
  268. ContainerdPluginNamespace: DefaultPluginNamespace,
  269. DefaultRuntime: StockRuntimeName,
  270. MinAPIVersion: defaultMinAPIVersion,
  271. },
  272. }
  273. if err := setPlatformDefaults(cfg); err != nil {
  274. return nil, err
  275. }
  276. return cfg, nil
  277. }
  278. // GetConflictFreeLabels validates Labels for conflict
  279. // In swarm the duplicates for labels are removed
  280. // so we only take same values here, no conflict values
  281. // If the key-value is the same we will only take the last label
  282. func GetConflictFreeLabels(labels []string) ([]string, error) {
  283. labelMap := map[string]string{}
  284. for _, label := range labels {
  285. key, val, ok := strings.Cut(label, "=")
  286. if ok {
  287. // If there is a conflict we will return an error
  288. if v, ok := labelMap[key]; ok && v != val {
  289. return nil, errors.Errorf("conflict labels for %s=%s and %s=%s", key, val, key, v)
  290. }
  291. labelMap[key] = val
  292. }
  293. }
  294. newLabels := []string{}
  295. for k, v := range labelMap {
  296. newLabels = append(newLabels, k+"="+v)
  297. }
  298. return newLabels, nil
  299. }
  300. // Reload reads the configuration in the host and reloads the daemon and server.
  301. func Reload(configFile string, flags *pflag.FlagSet, reload func(*Config)) error {
  302. log.G(context.TODO()).Infof("Got signal to reload configuration, reloading from: %s", configFile)
  303. newConfig, err := getConflictFreeConfiguration(configFile, flags)
  304. if err != nil {
  305. if flags.Changed("config-file") || !os.IsNotExist(err) {
  306. return errors.Wrapf(err, "unable to configure the Docker daemon with file %s", configFile)
  307. }
  308. newConfig, err = New()
  309. if err != nil {
  310. return err
  311. }
  312. }
  313. // Check if duplicate label-keys with different values are found
  314. newLabels, err := GetConflictFreeLabels(newConfig.Labels)
  315. if err != nil {
  316. return err
  317. }
  318. newConfig.Labels = newLabels
  319. // TODO(thaJeztah) This logic is problematic and needs a rewrite;
  320. // This is validating newConfig before the "reload()" callback is executed.
  321. // At this point, newConfig may be a partial configuration, to be merged
  322. // with the existing configuration in the "reload()" callback. Validating
  323. // this config before it's merged can result in incorrect validation errors.
  324. //
  325. // However, the current "reload()" callback we use is DaemonCli.reloadConfig(),
  326. // which includes a call to Daemon.Reload(), which both performs "merging"
  327. // and validation, as well as actually updating the daemon configuration.
  328. // Calling DaemonCli.reloadConfig() *before* validation, could thus lead to
  329. // a failure in that function (making the reload non-atomic).
  330. //
  331. // While *some* errors could always occur when applying/updating the config,
  332. // we should make it more atomic, and;
  333. //
  334. // 1. get (a copy of) the active configuration
  335. // 2. get the new configuration
  336. // 3. apply the (reloadable) options from the new configuration
  337. // 4. validate the merged results
  338. // 5. apply the new configuration.
  339. if err := Validate(newConfig); err != nil {
  340. return errors.Wrap(err, "file configuration validation failed")
  341. }
  342. reload(newConfig)
  343. return nil
  344. }
  345. // boolValue is an interface that boolean value flags implement
  346. // to tell the command line how to make -name equivalent to -name=true.
  347. type boolValue interface {
  348. IsBoolFlag() bool
  349. }
  350. // MergeDaemonConfigurations reads a configuration file,
  351. // loads the file configuration in an isolated structure,
  352. // and merges the configuration provided from flags on top
  353. // if there are no conflicts.
  354. func MergeDaemonConfigurations(flagsConfig *Config, flags *pflag.FlagSet, configFile string) (*Config, error) {
  355. fileConfig, err := getConflictFreeConfiguration(configFile, flags)
  356. if err != nil {
  357. return nil, err
  358. }
  359. // merge flags configuration on top of the file configuration
  360. if err := mergo.Merge(fileConfig, flagsConfig); err != nil {
  361. return nil, err
  362. }
  363. // validate the merged fileConfig and flagsConfig
  364. if err := Validate(fileConfig); err != nil {
  365. return nil, errors.Wrap(err, "merged configuration validation from file and command line flags failed")
  366. }
  367. return fileConfig, nil
  368. }
  369. // getConflictFreeConfiguration loads the configuration from a JSON file.
  370. // It compares that configuration with the one provided by the flags,
  371. // and returns an error if there are conflicts.
  372. func getConflictFreeConfiguration(configFile string, flags *pflag.FlagSet) (*Config, error) {
  373. b, err := os.ReadFile(configFile)
  374. if err != nil {
  375. return nil, err
  376. }
  377. // Decode the contents of the JSON file using a [byte order mark] if present, instead of assuming UTF-8 without BOM.
  378. // The BOM, if present, will be used to determine the encoding. If no BOM is present, we will assume the default
  379. // and preferred encoding for JSON as defined by [RFC 8259], UTF-8 without BOM.
  380. //
  381. // While JSON is normatively UTF-8 with no BOM, there are a couple of reasons to decode here:
  382. // * UTF-8 with BOM is something that new implementations should avoid producing; however, [RFC 8259 Section 8.1]
  383. // allows implementations to ignore the UTF-8 BOM when present for interoperability. Older versions of Notepad,
  384. // the only text editor available out of the box on Windows Server, writes UTF-8 with a BOM by default.
  385. // * The default encoding for [Windows PowerShell] is UTF-16 LE with BOM. While encodings in PowerShell can be a
  386. // bit idiosyncratic, BOMs are still generally written. There is no support for selecting UTF-8 without a BOM as
  387. // the encoding in Windows PowerShell, though some Cmdlets only write UTF-8 with no BOM. PowerShell Core
  388. // introduces `utf8NoBOM` and makes it the default, but PowerShell Core is unlikely to be the implementation for
  389. // a majority of Windows Server + PowerShell users.
  390. // * While [RFC 8259 Section 8.1] asserts that software that is not part of a closed ecosystem or that crosses a
  391. // network boundary should only support UTF-8, and should never write a BOM, it does acknowledge older versions
  392. // of the standard, such as [RFC 7159 Section 8.1]. In the interest of pragmatism and easing pain for Windows
  393. // users, we consider Windows tools such as Windows PowerShell and Notepad part of our ecosystem, and support
  394. // the two most common encodings: UTF-16 LE with BOM, and UTF-8 with BOM, in addition to the standard UTF-8
  395. // without BOM.
  396. //
  397. // [byte order mark]: https://www.unicode.org/faq/utf_bom.html#BOM
  398. // [RFC 8259]: https://www.rfc-editor.org/rfc/rfc8259
  399. // [RFC 8259 Section 8.1]: https://www.rfc-editor.org/rfc/rfc8259#section-8.1
  400. // [RFC 7159 Section 8.1]: https://www.rfc-editor.org/rfc/rfc7159#section-8.1
  401. // [Windows PowerShell]: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_character_encoding?view=powershell-5.1
  402. b, n, err := transform.Bytes(transform.Chain(unicode.BOMOverride(transform.Nop), encoding.UTF8Validator), b)
  403. if err != nil {
  404. return nil, errors.Wrapf(err, "failed to decode configuration JSON at offset %d", n)
  405. }
  406. // Trim whitespace so that an empty config can be detected for an early return.
  407. b = bytes.TrimSpace(b)
  408. var config Config
  409. if len(b) == 0 {
  410. return &config, nil // early return on empty config
  411. }
  412. if flags != nil {
  413. var jsonConfig map[string]interface{}
  414. if err := json.Unmarshal(b, &jsonConfig); err != nil {
  415. return nil, err
  416. }
  417. configSet := configValuesSet(jsonConfig)
  418. if err := findConfigurationConflicts(configSet, flags); err != nil {
  419. return nil, err
  420. }
  421. // Override flag values to make sure the values set in the config file with nullable values, like `false`,
  422. // are not overridden by default truthy values from the flags that were not explicitly set.
  423. // See https://github.com/docker/docker/issues/20289 for an example.
  424. //
  425. // TODO: Rewrite configuration logic to avoid same issue with other nullable values, like numbers.
  426. namedOptions := make(map[string]interface{})
  427. for key, value := range configSet {
  428. f := flags.Lookup(key)
  429. if f == nil { // ignore named flags that don't match
  430. namedOptions[key] = value
  431. continue
  432. }
  433. if _, ok := f.Value.(boolValue); ok {
  434. f.Value.Set(fmt.Sprintf("%v", value))
  435. }
  436. }
  437. if len(namedOptions) > 0 {
  438. // set also default for mergeVal flags that are boolValue at the same time.
  439. flags.VisitAll(func(f *pflag.Flag) {
  440. if opt, named := f.Value.(opts.NamedOption); named {
  441. v, set := namedOptions[opt.Name()]
  442. _, boolean := f.Value.(boolValue)
  443. if set && boolean {
  444. f.Value.Set(fmt.Sprintf("%v", v))
  445. }
  446. }
  447. })
  448. }
  449. config.ValuesSet = configSet
  450. }
  451. if err := json.Unmarshal(b, &config); err != nil {
  452. return nil, err
  453. }
  454. return &config, nil
  455. }
  456. // configValuesSet returns the configuration values explicitly set in the file.
  457. func configValuesSet(config map[string]interface{}) map[string]interface{} {
  458. flatten := make(map[string]interface{})
  459. for k, v := range config {
  460. if m, isMap := v.(map[string]interface{}); isMap && !flatOptions[k] {
  461. for km, vm := range m {
  462. flatten[km] = vm
  463. }
  464. continue
  465. }
  466. flatten[k] = v
  467. }
  468. return flatten
  469. }
  470. // findConfigurationConflicts iterates over the provided flags searching for
  471. // duplicated configurations and unknown keys. It returns an error with all the conflicts if
  472. // it finds any.
  473. func findConfigurationConflicts(config map[string]interface{}, flags *pflag.FlagSet) error {
  474. // 1. Search keys from the file that we don't recognize as flags.
  475. unknownKeys := make(map[string]interface{})
  476. for key, value := range config {
  477. if flag := flags.Lookup(key); flag == nil && !skipValidateOptions[key] {
  478. unknownKeys[key] = value
  479. }
  480. }
  481. // 2. Discard values that implement NamedOption.
  482. // Their configuration name differs from their flag name, like `labels` and `label`.
  483. if len(unknownKeys) > 0 {
  484. unknownNamedConflicts := func(f *pflag.Flag) {
  485. if namedOption, ok := f.Value.(opts.NamedOption); ok {
  486. delete(unknownKeys, namedOption.Name())
  487. }
  488. }
  489. flags.VisitAll(unknownNamedConflicts)
  490. }
  491. if len(unknownKeys) > 0 {
  492. var unknown []string
  493. for key := range unknownKeys {
  494. unknown = append(unknown, key)
  495. }
  496. return errors.Errorf("the following directives don't match any configuration option: %s", strings.Join(unknown, ", "))
  497. }
  498. var conflicts []string
  499. printConflict := func(name string, flagValue, fileValue interface{}) string {
  500. switch name {
  501. case "http-proxy", "https-proxy":
  502. flagValue = MaskCredentials(flagValue.(string))
  503. fileValue = MaskCredentials(fileValue.(string))
  504. }
  505. return fmt.Sprintf("%s: (from flag: %v, from file: %v)", name, flagValue, fileValue)
  506. }
  507. // 3. Search keys that are present as a flag and as a file option.
  508. duplicatedConflicts := func(f *pflag.Flag) {
  509. // search option name in the json configuration payload if the value is a named option
  510. if namedOption, ok := f.Value.(opts.NamedOption); ok {
  511. if optsValue, ok := config[namedOption.Name()]; ok && !skipDuplicates[namedOption.Name()] {
  512. conflicts = append(conflicts, printConflict(namedOption.Name(), f.Value.String(), optsValue))
  513. }
  514. } else {
  515. // search flag name in the json configuration payload
  516. for _, name := range []string{f.Name, f.Shorthand} {
  517. if value, ok := config[name]; ok && !skipDuplicates[name] {
  518. conflicts = append(conflicts, printConflict(name, f.Value.String(), value))
  519. break
  520. }
  521. }
  522. }
  523. }
  524. flags.Visit(duplicatedConflicts)
  525. if len(conflicts) > 0 {
  526. return errors.Errorf("the following directives are specified both as a flag and in the configuration file: %s", strings.Join(conflicts, ", "))
  527. }
  528. return nil
  529. }
  530. // ValidateMinAPIVersion verifies if the given API version is within the
  531. // range supported by the daemon. It is used to validate a custom minimum
  532. // API version set through DOCKER_MIN_API_VERSION.
  533. func ValidateMinAPIVersion(ver string) error {
  534. if ver == "" {
  535. return errors.New(`value is empty`)
  536. }
  537. if strings.EqualFold(ver[0:1], "v") {
  538. return errors.New(`API version must be provided without "v" prefix`)
  539. }
  540. if versions.LessThan(ver, minAPIVersion) {
  541. return errors.Errorf(`minimum supported API version is %s: %s`, minAPIVersion, ver)
  542. }
  543. if versions.GreaterThan(ver, api.DefaultVersion) {
  544. return errors.Errorf(`maximum supported API version is %s: %s`, api.DefaultVersion, ver)
  545. }
  546. return nil
  547. }
  548. // Validate validates some specific configs.
  549. // such as config.DNS, config.Labels, config.DNSSearch,
  550. // as well as config.MaxConcurrentDownloads, config.MaxConcurrentUploads and config.MaxDownloadAttempts.
  551. func Validate(config *Config) error {
  552. // validate log-level
  553. if config.LogLevel != "" {
  554. // FIXME(thaJeztah): find a better way for this; this depends on knowledge of containerd's log package internals.
  555. // Alternatively: try log.SetLevel(config.LogLevel), and restore the original level, but this also requires internal knowledge.
  556. switch strings.ToLower(config.LogLevel) {
  557. case "panic", "fatal", "error", "warn", "info", "debug", "trace":
  558. // These are valid. See [log.SetLevel] for a list of accepted levels.
  559. default:
  560. return errors.Errorf("invalid logging level: %s", config.LogLevel)
  561. }
  562. }
  563. // validate log-format
  564. if logFormat := config.LogFormat; logFormat != "" {
  565. switch logFormat {
  566. case log.TextFormat, log.JSONFormat:
  567. // These are valid
  568. default:
  569. return errors.Errorf("invalid log format: %s", logFormat)
  570. }
  571. }
  572. // validate DNSSearch
  573. for _, dnsSearch := range config.DNSSearch {
  574. if _, err := opts.ValidateDNSSearch(dnsSearch); err != nil {
  575. return err
  576. }
  577. }
  578. // validate Labels
  579. for _, label := range config.Labels {
  580. if _, err := opts.ValidateLabel(label); err != nil {
  581. return err
  582. }
  583. }
  584. // TODO(thaJeztah) Validations below should not accept "0" to be valid; see Validate() for a more in-depth description of this problem
  585. if config.MTU < 0 {
  586. return errors.Errorf("invalid default MTU: %d", config.MTU)
  587. }
  588. if config.MaxConcurrentDownloads < 0 {
  589. return errors.Errorf("invalid max concurrent downloads: %d", config.MaxConcurrentDownloads)
  590. }
  591. if config.MaxConcurrentUploads < 0 {
  592. return errors.Errorf("invalid max concurrent uploads: %d", config.MaxConcurrentUploads)
  593. }
  594. if config.MaxDownloadAttempts < 0 {
  595. return errors.Errorf("invalid max download attempts: %d", config.MaxDownloadAttempts)
  596. }
  597. if _, err := ParseGenericResources(config.NodeGenericResources); err != nil {
  598. return err
  599. }
  600. for _, h := range config.Hosts {
  601. if _, err := opts.ValidateHost(h); err != nil {
  602. return err
  603. }
  604. }
  605. // validate platform-specific settings
  606. return config.ValidatePlatformConfig()
  607. }
  608. // MaskCredentials masks credentials that are in an URL.
  609. func MaskCredentials(rawURL string) string {
  610. parsedURL, err := url.Parse(rawURL)
  611. if err != nil || parsedURL.User == nil {
  612. return rawURL
  613. }
  614. parsedURL.User = url.UserPassword("xxxxx", "xxxxx")
  615. return parsedURL.String()
  616. }