cmd/dockerd: normalize hosts when loading config

Previously, hosts were de-duplicated and normalized when starting
the API server (in `loadListeners()`), which meant that errors could
occur in that step (but not detected when using `dockerd --validate`),
as well as the list of hosts in the config not matching what would
actually be used (i.e., if duplicates were present).

This patch extracts the de-duplicating to a separate function, and
executes it as part of loading the daemon configuration, so that we
can fail early.

Moving this code also showed that some of this validation depended
on `newAPIServerConfig()` modifying the configuration (adding an
empty host if none was set) in order to have the parsing set a
default. This code was moved elsewhere, but a TODO comment added
as this logic is somewhat sketchy.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
This commit is contained in:
Sebastiaan van Stijn 2022-04-21 10:21:18 +02:00
parent 7b3463f2c5
commit 57c20c1b79
No known key found for this signature in database
GPG key ID: 76698F39D527CE8C

View file

@ -8,6 +8,7 @@ import (
"os"
"path/filepath"
"runtime"
"sort"
"strings"
"time"
@ -435,6 +436,10 @@ func loadDaemonCliConfig(opts *daemonOptions) (*config.Config, error) {
}
}
if err := normalizeHosts(conf); err != nil {
return nil, err
}
if err := config.Validate(conf); err != nil {
return nil, err
}
@ -469,6 +474,41 @@ func loadDaemonCliConfig(opts *daemonOptions) (*config.Config, error) {
return conf, nil
}
// normalizeHosts normalizes the configured config.Hosts and remove duplicates.
// It returns an error if it fails to parse a host.
func normalizeHosts(config *config.Config) error {
if len(config.Hosts) == 0 {
// if no hosts are configured, create a single entry slice, so that the
// default is used.
//
// TODO(thaJeztah) implement a cleaner way for this; this depends on a
// side-effect of how we parse empty/partial hosts.
config.Hosts = make([]string, 1)
}
hosts := make([]string, 0, len(config.Hosts))
seen := make(map[string]struct{}, len(config.Hosts))
useTLS := DefaultTLSValue
if config.TLS != nil {
useTLS = *config.TLS
}
for _, h := range config.Hosts {
host, err := dopts.ParseHost(useTLS, honorXDG, h)
if err != nil {
return err
}
if _, ok := seen[host]; ok {
continue
}
seen[host] = struct{}{}
hosts = append(hosts, host)
}
sort.Strings(hosts)
config.Hosts = hosts
return nil
}
func checkDeprecatedOptions(config *config.Config) error {
// Overlay networks with external k/v stores have been deprecated
if config.ClusterAdvertise != "" || len(config.ClusterOpts) > 0 || config.ClusterStore != "" {
@ -591,10 +631,6 @@ func newAPIServerConfig(cli *DaemonCli) (*apiserver.Config, error) {
serverConfig.TLSConfig = tlsConfig
}
if len(cli.Config.Hosts) == 0 {
cli.Config.Hosts = make([]string, 1)
}
return serverConfig, nil
}
@ -624,32 +660,19 @@ func checkTLSAuthOK(c *config.Config) bool {
}
func loadListeners(cli *DaemonCli, serverConfig *apiserver.Config) ([]string, error) {
var hosts []string
seen := make(map[string]struct{}, len(cli.Config.Hosts))
useTLS := DefaultTLSValue
if cli.Config.TLS != nil {
useTLS = *cli.Config.TLS
if len(cli.Config.Hosts) == 0 {
return nil, errors.New("no hosts configured")
}
var hosts []string
for i := 0; i < len(cli.Config.Hosts); i++ {
var err error
if cli.Config.Hosts[i], err = dopts.ParseHost(useTLS, honorXDG, cli.Config.Hosts[i]); err != nil {
return nil, errors.Wrapf(err, "error parsing -H %s", cli.Config.Hosts[i])
}
if _, ok := seen[cli.Config.Hosts[i]]; ok {
continue
}
seen[cli.Config.Hosts[i]] = struct{}{}
protoAddr := cli.Config.Hosts[i]
protoAddrParts := strings.SplitN(protoAddr, "://", 2)
protoAddrParts := strings.SplitN(cli.Config.Hosts[i], "://", 2)
if len(protoAddrParts) != 2 {
return nil, fmt.Errorf("bad format %s, expected PROTO://ADDR", protoAddr)
}
proto := protoAddrParts[0]
addr := protoAddrParts[1]
proto, addr := protoAddrParts[0], protoAddrParts[1]
// It's a bad idea to bind to TCP without tlsverify.
authEnabled := serverConfig.TLSConfig != nil && serverConfig.TLSConfig.ClientAuth == tls.RequireAndVerifyClientCert