1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000 |
- package main
- import (
- "context"
- "crypto/tls"
- "fmt"
- "net"
- "net/http"
- "os"
- "path/filepath"
- "runtime"
- "sort"
- "strings"
- "sync"
- "time"
- containerddefaults "github.com/containerd/containerd/defaults"
- "github.com/containerd/containerd/tracing"
- "github.com/containerd/log"
- "github.com/docker/docker/api"
- apiserver "github.com/docker/docker/api/server"
- buildbackend "github.com/docker/docker/api/server/backend/build"
- "github.com/docker/docker/api/server/middleware"
- "github.com/docker/docker/api/server/router"
- "github.com/docker/docker/api/server/router/build"
- checkpointrouter "github.com/docker/docker/api/server/router/checkpoint"
- "github.com/docker/docker/api/server/router/container"
- distributionrouter "github.com/docker/docker/api/server/router/distribution"
- grpcrouter "github.com/docker/docker/api/server/router/grpc"
- "github.com/docker/docker/api/server/router/image"
- "github.com/docker/docker/api/server/router/network"
- pluginrouter "github.com/docker/docker/api/server/router/plugin"
- sessionrouter "github.com/docker/docker/api/server/router/session"
- swarmrouter "github.com/docker/docker/api/server/router/swarm"
- systemrouter "github.com/docker/docker/api/server/router/system"
- "github.com/docker/docker/api/server/router/volume"
- buildkit "github.com/docker/docker/builder/builder-next"
- "github.com/docker/docker/builder/dockerfile"
- "github.com/docker/docker/cli/debug"
- "github.com/docker/docker/cmd/dockerd/trap"
- "github.com/docker/docker/daemon"
- "github.com/docker/docker/daemon/cluster"
- "github.com/docker/docker/daemon/config"
- "github.com/docker/docker/daemon/listeners"
- "github.com/docker/docker/dockerversion"
- "github.com/docker/docker/libcontainerd/supervisor"
- dopts "github.com/docker/docker/opts"
- "github.com/docker/docker/pkg/authorization"
- "github.com/docker/docker/pkg/homedir"
- "github.com/docker/docker/pkg/pidfile"
- "github.com/docker/docker/pkg/plugingetter"
- "github.com/docker/docker/pkg/rootless"
- "github.com/docker/docker/pkg/sysinfo"
- "github.com/docker/docker/pkg/system"
- "github.com/docker/docker/plugin"
- "github.com/docker/docker/runconfig"
- "github.com/docker/go-connections/tlsconfig"
- "github.com/moby/buildkit/session"
- "github.com/moby/buildkit/util/tracing/detect"
- swarmapi "github.com/moby/swarmkit/v2/api"
- "github.com/pkg/errors"
- "github.com/sirupsen/logrus"
- "github.com/spf13/pflag"
- "go.opentelemetry.io/otel"
- "go.opentelemetry.io/otel/propagation"
- "go.opentelemetry.io/otel/sdk/resource"
- "tags.cncf.io/container-device-interface/pkg/cdi"
- )
- // DaemonCli represents the daemon CLI.
- type DaemonCli struct {
- *config.Config
- configFile *string
- flags *pflag.FlagSet
- d *daemon.Daemon
- authzMiddleware *authorization.Middleware // authzMiddleware enables to dynamically reload the authorization plugins
- stopOnce sync.Once
- apiShutdown chan struct{}
- }
- // NewDaemonCli returns a daemon CLI
- func NewDaemonCli() *DaemonCli {
- return &DaemonCli{
- apiShutdown: make(chan struct{}),
- }
- }
- func (cli *DaemonCli) start(opts *daemonOptions) (err error) {
- ctx := context.TODO()
- if cli.Config, err = loadDaemonCliConfig(opts); err != nil {
- return err
- }
- tlsConfig, err := newAPIServerTLSConfig(cli.Config)
- if err != nil {
- return err
- }
- if opts.Validate {
- // If config wasn't OK we wouldn't have made it this far.
- _, _ = fmt.Fprintln(os.Stderr, "configuration OK")
- return nil
- }
- configureProxyEnv(cli.Config)
- configureDaemonLogs(cli.Config)
- log.G(ctx).Info("Starting up")
- cli.configFile = &opts.configFile
- cli.flags = opts.flags
- if cli.Config.Debug {
- debug.Enable()
- }
- if cli.Config.Experimental {
- log.G(ctx).Warn("Running experimental build")
- }
- if cli.Config.IsRootless() {
- log.G(ctx).Warn("Running in rootless mode. This mode has feature limitations.")
- }
- if rootless.RunningWithRootlessKit() {
- log.G(ctx).Info("Running with RootlessKit integration")
- if !cli.Config.IsRootless() {
- return fmt.Errorf("rootless mode needs to be enabled for running with RootlessKit")
- }
- }
- // return human-friendly error before creating files
- if runtime.GOOS == "linux" && os.Geteuid() != 0 {
- return fmt.Errorf("dockerd needs to be started with root privileges. To run dockerd in rootless mode as an unprivileged user, see https://docs.docker.com/go/rootless/")
- }
- if err := setDefaultUmask(); err != nil {
- return err
- }
- // Create the daemon root before we create ANY other files (PID, or migrate keys)
- // to ensure the appropriate ACL is set (particularly relevant on Windows)
- if err := daemon.CreateDaemonRoot(cli.Config); err != nil {
- return err
- }
- if err := system.MkdirAll(cli.Config.ExecRoot, 0o700); err != nil {
- return err
- }
- potentiallyUnderRuntimeDir := []string{cli.Config.ExecRoot}
- if cli.Pidfile != "" {
- if err = system.MkdirAll(filepath.Dir(cli.Pidfile), 0o755); err != nil {
- return errors.Wrap(err, "failed to create pidfile directory")
- }
- if err = pidfile.Write(cli.Pidfile, os.Getpid()); err != nil {
- return errors.Wrapf(err, "failed to start daemon, ensure docker is not running or delete %s", cli.Pidfile)
- }
- potentiallyUnderRuntimeDir = append(potentiallyUnderRuntimeDir, cli.Pidfile)
- defer func() {
- if err := os.Remove(cli.Pidfile); err != nil {
- log.G(ctx).Error(err)
- }
- }()
- }
- if cli.Config.IsRootless() {
- // Set sticky bit if XDG_RUNTIME_DIR is set && the file is actually under XDG_RUNTIME_DIR
- if _, err := homedir.StickRuntimeDirContents(potentiallyUnderRuntimeDir); err != nil {
- // StickRuntimeDirContents returns nil error if XDG_RUNTIME_DIR is just unset
- log.G(ctx).WithError(err).Warn("cannot set sticky bit on files under XDG_RUNTIME_DIR")
- }
- }
- lss, hosts, err := loadListeners(cli.Config, tlsConfig)
- if err != nil {
- return errors.Wrap(err, "failed to load listeners")
- }
- ctx, cancel := context.WithCancel(context.Background())
- waitForContainerDShutdown, err := cli.initContainerd(ctx)
- if waitForContainerDShutdown != nil {
- defer waitForContainerDShutdown(10 * time.Second)
- }
- if err != nil {
- cancel()
- return err
- }
- defer cancel()
- httpServer := &http.Server{
- ReadHeaderTimeout: 5 * time.Minute, // "G112: Potential Slowloris Attack (gosec)"; not a real concern for our use, so setting a long timeout.
- }
- apiShutdownCtx, apiShutdownCancel := context.WithCancel(context.Background())
- apiShutdownDone := make(chan struct{})
- trap.Trap(cli.stop)
- go func() {
- // Block until cli.stop() has been called.
- // It may have already been called, and that's okay.
- // Any httpServer.Serve() calls made after
- // httpServer.Shutdown() will return immediately,
- // which is what we want.
- <-cli.apiShutdown
- err := httpServer.Shutdown(apiShutdownCtx)
- if err != nil {
- log.G(ctx).WithError(err).Error("Error shutting down http server")
- }
- close(apiShutdownDone)
- }()
- defer func() {
- select {
- case <-cli.apiShutdown:
- // cli.stop() has been called and the daemon has completed
- // shutting down. Give the HTTP server a little more time to
- // finish handling any outstanding requests if needed.
- tmr := time.AfterFunc(5*time.Second, apiShutdownCancel)
- defer tmr.Stop()
- <-apiShutdownDone
- default:
- // cli.start() has returned without cli.stop() being called,
- // e.g. because the daemon failed to start.
- // Stop the HTTP server with no grace period.
- if closeErr := httpServer.Close(); closeErr != nil {
- log.G(ctx).WithError(closeErr).Error("Error closing http server")
- }
- }
- }()
- // Notify that the API is active, but before daemon is set up.
- preNotifyReady()
- const otelServiceNameEnv = "OTEL_SERVICE_NAME"
- if _, ok := os.LookupEnv(otelServiceNameEnv); !ok {
- os.Setenv(otelServiceNameEnv, filepath.Base(os.Args[0]))
- }
- setOTLPProtoDefault()
- otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{}))
- // Override BuildKit's default Resource so that it matches the semconv
- // version that is used in our code.
- detect.OverrideResource(resource.Default())
- detect.Recorder = detect.NewTraceRecorder()
- tp, err := detect.TracerProvider()
- if err != nil {
- log.G(ctx).WithError(err).Warn("Failed to initialize tracing, skipping")
- } else {
- otel.SetTracerProvider(tp)
- log.G(ctx).Logger.AddHook(tracing.NewLogrusHook())
- }
- pluginStore := plugin.NewStore()
- var apiServer apiserver.Server
- cli.authzMiddleware, err = initMiddlewares(&apiServer, cli.Config, pluginStore)
- if err != nil {
- return errors.Wrap(err, "failed to start API server")
- }
- d, err := daemon.NewDaemon(ctx, cli.Config, pluginStore, cli.authzMiddleware)
- if err != nil {
- return errors.Wrap(err, "failed to start daemon")
- }
- d.StoreHosts(hosts)
- // validate after NewDaemon has restored enabled plugins. Don't change order.
- if err := validateAuthzPlugins(cli.Config.AuthorizationPlugins, pluginStore); err != nil {
- return errors.Wrap(err, "failed to validate authorization plugin")
- }
- // Note that CDI is not inherently linux-specific, there are some linux-specific assumptions / implementations in the code that
- // queries the properties of device on the host as wel as performs the injection of device nodes and their access permissions into the OCI spec.
- //
- // In order to lift this restriction the following would have to be addressed:
- // - Support needs to be added to the cdi package for injecting Windows devices: https://tags.cncf.io/container-device-interface/issues/28
- // - The DeviceRequests API must be extended to non-linux platforms.
- if runtime.GOOS == "linux" && cli.Config.Features["cdi"] {
- daemon.RegisterCDIDriver(cli.Config.CDISpecDirs...)
- }
- cli.d = d
- if err := startMetricsServer(cli.Config.MetricsAddress); err != nil {
- return errors.Wrap(err, "failed to start metrics server")
- }
- c, err := createAndStartCluster(cli, d)
- if err != nil {
- log.G(ctx).Fatalf("Error starting cluster component: %v", err)
- }
- // Restart all autostart containers which has a swarm endpoint
- // and is not yet running now that we have successfully
- // initialized the cluster.
- d.RestartSwarmContainers()
- log.G(ctx).Info("Daemon has completed initialization")
- routerCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
- defer cancel()
- // Get a the current daemon config, because the daemon sets up config
- // during initialization. We cannot user the cli.Config for that reason,
- // as that only holds the config that was set by the user.
- //
- // FIXME(thaJeztah): better separate runtime and config data?
- daemonCfg := d.Config()
- routerOpts, err := newRouterOptions(routerCtx, &daemonCfg, d, c)
- if err != nil {
- return err
- }
- httpServer.Handler = apiServer.CreateMux(routerOpts.Build()...)
- go d.ProcessClusterNotifications(ctx, c.GetWatchStream())
- cli.setupConfigReloadTrap()
- // after the daemon is done setting up we can notify systemd api
- notifyReady()
- // Daemon is fully initialized. Start handling API traffic
- // and wait for serve API to complete.
- var (
- apiWG sync.WaitGroup
- errAPI = make(chan error, 1)
- )
- for _, ls := range lss {
- apiWG.Add(1)
- go func(ls net.Listener) {
- defer apiWG.Done()
- log.G(ctx).Infof("API listen on %s", ls.Addr())
- if err := httpServer.Serve(ls); err != http.ErrServerClosed {
- log.G(ctx).WithFields(log.Fields{
- "error": err,
- "listener": ls.Addr(),
- }).Error("ServeAPI error")
- select {
- case errAPI <- err:
- default:
- }
- }
- }(ls)
- }
- apiWG.Wait()
- close(errAPI)
- c.Cleanup()
- // notify systemd that we're shutting down
- notifyStopping()
- shutdownDaemon(ctx, d)
- if err := routerOpts.buildkit.Close(); err != nil {
- log.G(ctx).WithError(err).Error("Failed to close buildkit")
- }
- // Stop notification processing and any background processes
- cancel()
- if err, ok := <-errAPI; ok {
- return errors.Wrap(err, "shutting down due to ServeAPI error")
- }
- detect.Shutdown(context.Background())
- log.G(ctx).Info("Daemon shutdown complete")
- return nil
- }
- // The buildkit "detect" package uses grpc as the default proto, which is in conformance with the old spec.
- // For a little while now http/protobuf is the default spec, so this function sets the protocol to http/protobuf when the env var is unset
- // so that the detect package will use http/protobuf as a default.
- // TODO: This can be removed after buildkit is updated to use http/protobuf as the default.
- func setOTLPProtoDefault() {
- const (
- tracesEnv = "OTEL_EXPORTER_OTLP_TRACES_PROTOCOL"
- protoEnv = "OTEL_EXPORTER_OTLP_PROTOCOL"
- )
- if os.Getenv(tracesEnv) == "" && os.Getenv(protoEnv) == "" {
- os.Setenv(tracesEnv, "http/protobuf")
- }
- }
- type routerOptions struct {
- sessionManager *session.Manager
- buildBackend *buildbackend.Backend
- features func() map[string]bool
- buildkit *buildkit.Builder
- daemon *daemon.Daemon
- cluster *cluster.Cluster
- }
- func newRouterOptions(ctx context.Context, config *config.Config, d *daemon.Daemon, c *cluster.Cluster) (routerOptions, error) {
- sm, err := session.NewManager()
- if err != nil {
- return routerOptions{}, errors.Wrap(err, "failed to create sessionmanager")
- }
- manager, err := dockerfile.NewBuildManager(d.BuilderBackend(), d.IdentityMapping())
- if err != nil {
- return routerOptions{}, err
- }
- cgroupParent := newCgroupParent(config)
- bk, err := buildkit.New(ctx, buildkit.Opt{
- SessionManager: sm,
- Root: filepath.Join(config.Root, "buildkit"),
- EngineID: d.ID(),
- Dist: d.DistributionServices(),
- ImageTagger: d.ImageService(),
- NetworkController: d.NetworkController(),
- DefaultCgroupParent: cgroupParent,
- RegistryHosts: d.RegistryHosts,
- BuilderConfig: config.Builder,
- Rootless: daemon.Rootless(config),
- IdentityMapping: d.IdentityMapping(),
- DNSConfig: config.DNSConfig,
- ApparmorProfile: daemon.DefaultApparmorProfile(),
- UseSnapshotter: d.UsesSnapshotter(),
- Snapshotter: d.ImageService().StorageDriver(),
- ContainerdAddress: config.ContainerdAddr,
- ContainerdNamespace: config.ContainerdNamespace,
- })
- if err != nil {
- return routerOptions{}, err
- }
- bb, err := buildbackend.NewBackend(d.ImageService(), manager, bk, d.EventsService)
- if err != nil {
- return routerOptions{}, errors.Wrap(err, "failed to create buildmanager")
- }
- return routerOptions{
- sessionManager: sm,
- buildBackend: bb,
- features: d.Features,
- buildkit: bk,
- daemon: d,
- cluster: c,
- }, nil
- }
- func (cli *DaemonCli) reloadConfig() {
- ctx := context.TODO()
- reload := func(c *config.Config) {
- if err := validateAuthzPlugins(c.AuthorizationPlugins, cli.d.PluginStore); err != nil {
- log.G(ctx).Fatalf("Error validating authorization plugin: %v", err)
- return
- }
- if err := cli.d.Reload(c); err != nil {
- log.G(ctx).Errorf("Error reconfiguring the daemon: %v", err)
- return
- }
- // Apply our own configuration only after the daemon reload has succeeded. We
- // don't want to partially apply the config if the daemon is unhappy with it.
- cli.authzMiddleware.SetPlugins(c.AuthorizationPlugins)
- if c.IsValueSet("debug") {
- debugEnabled := debug.IsEnabled()
- switch {
- case debugEnabled && !c.Debug: // disable debug
- debug.Disable()
- case c.Debug && !debugEnabled: // enable debug
- debug.Enable()
- }
- }
- }
- if err := config.Reload(*cli.configFile, cli.flags, reload); err != nil {
- log.G(ctx).Error(err)
- }
- }
- func (cli *DaemonCli) stop() {
- // Signal that the API server should shut down as soon as possible.
- // This construct is used rather than directly shutting down the HTTP
- // server to avoid any issues if this method is called before the server
- // has been instantiated in cli.start(). If this method is called first,
- // the HTTP server will be shut down immediately upon instantiation.
- cli.stopOnce.Do(func() {
- close(cli.apiShutdown)
- })
- }
- // shutdownDaemon just wraps daemon.Shutdown() to handle a timeout in case
- // d.Shutdown() is waiting too long to kill container or worst it's
- // blocked there
- func shutdownDaemon(ctx context.Context, d *daemon.Daemon) {
- var cancel context.CancelFunc
- if timeout := d.ShutdownTimeout(); timeout >= 0 {
- ctx, cancel = context.WithTimeout(ctx, time.Duration(timeout)*time.Second)
- } else {
- ctx, cancel = context.WithCancel(ctx)
- }
- go func() {
- defer cancel()
- d.Shutdown(ctx)
- }()
- <-ctx.Done()
- if errors.Is(ctx.Err(), context.DeadlineExceeded) {
- log.G(ctx).Error("Force shutdown daemon")
- } else {
- log.G(ctx).Debug("Clean shutdown succeeded")
- }
- }
- func loadDaemonCliConfig(opts *daemonOptions) (*config.Config, error) {
- if !opts.flags.Parsed() {
- return nil, errors.New(`cannot load CLI config before flags are parsed`)
- }
- opts.setDefaultOptions()
- conf := opts.daemonConfig
- flags := opts.flags
- conf.Debug = opts.Debug
- conf.Hosts = opts.Hosts
- conf.LogLevel = opts.LogLevel
- conf.LogFormat = log.OutputFormat(opts.LogFormat)
- // The DOCKER_MIN_API_VERSION env-var allows overriding the minimum API
- // version provided by the daemon within constraints of the minimum and
- // maximum (current) supported API versions.
- //
- // API versions older than [config.defaultMinAPIVersion] are deprecated and
- // to be removed in a future release. The "DOCKER_MIN_API_VERSION" env-var
- // should only be used for exceptional cases.
- if ver := os.Getenv("DOCKER_MIN_API_VERSION"); ver != "" {
- if err := config.ValidateMinAPIVersion(ver); err != nil {
- return nil, errors.Wrap(err, "invalid DOCKER_MIN_API_VERSION")
- }
- conf.MinAPIVersion = ver
- }
- if flags.Changed(FlagTLS) {
- conf.TLS = &opts.TLS
- }
- if flags.Changed(FlagTLSVerify) {
- conf.TLSVerify = &opts.TLSVerify
- v := true
- conf.TLS = &v
- }
- if opts.TLSOptions != nil {
- conf.TLSOptions = config.TLSOptions{
- CAFile: opts.TLSOptions.CAFile,
- CertFile: opts.TLSOptions.CertFile,
- KeyFile: opts.TLSOptions.KeyFile,
- }
- } else {
- conf.TLSOptions = config.TLSOptions{}
- }
- if opts.configFile != "" {
- c, err := config.MergeDaemonConfigurations(conf, flags, opts.configFile)
- if err != nil {
- if flags.Changed("config-file") || !os.IsNotExist(err) {
- return nil, errors.Wrapf(err, "unable to configure the Docker daemon with file %s", opts.configFile)
- }
- }
- // the merged configuration can be nil if the config file didn't exist.
- // leave the current configuration as it is if when that happens.
- if c != nil {
- conf = c
- }
- }
- if err := normalizeHosts(conf); err != nil {
- return nil, err
- }
- if err := config.Validate(conf); err != nil {
- return nil, err
- }
- // Check if duplicate label-keys with different values are found
- newLabels, err := config.GetConflictFreeLabels(conf.Labels)
- if err != nil {
- return nil, err
- }
- conf.Labels = newLabels
- // Regardless of whether the user sets it to true or false, if they
- // specify TLSVerify at all then we need to turn on TLS
- if conf.IsValueSet(FlagTLSVerify) {
- v := true
- conf.TLS = &v
- }
- if conf.TLSVerify == nil && conf.TLS != nil {
- conf.TLSVerify = conf.TLS
- }
- err = validateCPURealtimeOptions(conf)
- if err != nil {
- return nil, err
- }
- if conf.CDISpecDirs == nil {
- // If the CDISpecDirs is not set at this stage, we set it to the default.
- conf.CDISpecDirs = append([]string(nil), cdi.DefaultSpecDirs...)
- } else if len(conf.CDISpecDirs) == 1 && conf.CDISpecDirs[0] == "" {
- // If CDISpecDirs is set to an empty string, we clear it to ensure that CDI is disabled.
- conf.CDISpecDirs = nil
- }
- if !conf.Features["cdi"] {
- // If the CDI feature is not enabled, we clear the CDISpecDirs to ensure that CDI is disabled.
- conf.CDISpecDirs = nil
- }
- if err := loadCLIPlatformConfig(conf); err != nil {
- return nil, err
- }
- 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 (opts routerOptions) Build() []router.Router {
- decoder := runconfig.ContainerDecoder{
- GetSysInfo: func() *sysinfo.SysInfo {
- return opts.daemon.RawSysInfo()
- },
- }
- routers := []router.Router{
- // we need to add the checkpoint router before the container router or the DELETE gets masked
- checkpointrouter.NewRouter(opts.daemon, decoder),
- container.NewRouter(opts.daemon, decoder, opts.daemon.RawSysInfo().CgroupUnified),
- image.NewRouter(
- opts.daemon.ImageService(),
- opts.daemon.RegistryService(),
- opts.daemon.ReferenceStore,
- opts.daemon.ImageService().DistributionServices().ImageStore,
- opts.daemon.ImageService().DistributionServices().LayerStore,
- ),
- systemrouter.NewRouter(opts.daemon, opts.cluster, opts.buildkit, opts.daemon.Features),
- volume.NewRouter(opts.daemon.VolumesService(), opts.cluster),
- build.NewRouter(opts.buildBackend, opts.daemon),
- sessionrouter.NewRouter(opts.sessionManager),
- swarmrouter.NewRouter(opts.cluster),
- pluginrouter.NewRouter(opts.daemon.PluginManager()),
- distributionrouter.NewRouter(opts.daemon.ImageBackend()),
- }
- if opts.buildBackend != nil {
- routers = append(routers, grpcrouter.NewRouter(opts.buildBackend))
- }
- if opts.daemon.NetworkControllerEnabled() {
- routers = append(routers, network.NewRouter(opts.daemon, opts.cluster))
- }
- if opts.daemon.HasExperimental() {
- for _, r := range routers {
- for _, route := range r.Routes() {
- if experimental, ok := route.(router.ExperimentalRoute); ok {
- experimental.Enable()
- }
- }
- }
- }
- return routers
- }
- func initMiddlewares(s *apiserver.Server, cfg *config.Config, pluginStore plugingetter.PluginGetter) (*authorization.Middleware, error) {
- exp := middleware.NewExperimentalMiddleware(cfg.Experimental)
- s.UseMiddleware(exp)
- vm, err := middleware.NewVersionMiddleware(dockerversion.Version, api.DefaultVersion, cfg.MinAPIVersion)
- if err != nil {
- return nil, err
- }
- s.UseMiddleware(*vm)
- if cfg.CorsHeaders != "" {
- c := middleware.NewCORSMiddleware(cfg.CorsHeaders)
- s.UseMiddleware(c)
- }
- authzMiddleware := authorization.NewMiddleware(cfg.AuthorizationPlugins, pluginStore)
- s.UseMiddleware(authzMiddleware)
- return authzMiddleware, nil
- }
- func (cli *DaemonCli) getContainerdDaemonOpts() ([]supervisor.DaemonOpt, error) {
- var opts []supervisor.DaemonOpt
- if cli.Debug {
- opts = append(opts, supervisor.WithLogLevel("debug"))
- } else {
- opts = append(opts, supervisor.WithLogLevel(cli.LogLevel))
- }
- if logFormat := cli.Config.LogFormat; logFormat != "" {
- opts = append(opts, supervisor.WithLogFormat(logFormat))
- }
- if !cli.CriContainerd {
- // CRI support in the managed daemon is currently opt-in.
- //
- // It's disabled by default, originally because it was listening on
- // a TCP connection at 0.0.0.0:10010, which was considered a security
- // risk, and could conflict with user's container ports.
- //
- // Current versions of containerd started now listen on localhost on
- // an ephemeral port instead, but could still conflict with container
- // ports, and running kubernetes using the static binaries is not a
- // common scenario, so we (for now) continue disabling it by default.
- //
- // Also see https://github.com/containerd/containerd/issues/2483#issuecomment-407530608
- opts = append(opts, supervisor.WithCRIDisabled())
- }
- return opts, nil
- }
- func newAPIServerTLSConfig(config *config.Config) (*tls.Config, error) {
- var tlsConfig *tls.Config
- if config.TLS != nil && *config.TLS {
- var (
- clientAuth tls.ClientAuthType
- err error
- )
- if config.TLSVerify == nil || *config.TLSVerify {
- // server requires and verifies client's certificate
- clientAuth = tls.RequireAndVerifyClientCert
- }
- tlsConfig, err = tlsconfig.Server(tlsconfig.Options{
- CAFile: config.TLSOptions.CAFile,
- CertFile: config.TLSOptions.CertFile,
- KeyFile: config.TLSOptions.KeyFile,
- ExclusiveRootPools: true,
- ClientAuth: clientAuth,
- })
- if err != nil {
- return nil, errors.Wrap(err, "invalid TLS configuration")
- }
- }
- return tlsConfig, nil
- }
- // checkTLSAuthOK checks basically for an explicitly disabled TLS/TLSVerify
- // Going forward we do not want to support a scenario where dockerd listens
- // on TCP without either TLS client auth (or an explicit opt-in to disable it)
- func checkTLSAuthOK(c *config.Config) bool {
- if c.TLS == nil {
- // Either TLS is enabled by default, in which case TLS verification should be enabled by default, or explicitly disabled
- // Or TLS is disabled by default... in any of these cases, we can just take the default value as to how to proceed
- return DefaultTLSValue
- }
- if !*c.TLS {
- // TLS is explicitly disabled, which is supported
- return true
- }
- if c.TLSVerify == nil {
- // this actually shouldn't happen since we set TLSVerify on the config object anyway
- // But in case it does get here, be cautious and assume this is not supported.
- return false
- }
- // Either TLSVerify is explicitly enabled or disabled, both cases are supported
- return true
- }
- func loadListeners(cfg *config.Config, tlsConfig *tls.Config) ([]net.Listener, []string, error) {
- ctx := context.TODO()
- if len(cfg.Hosts) == 0 {
- return nil, nil, errors.New("no hosts configured")
- }
- var (
- hosts []string
- lss []net.Listener
- )
- for i := 0; i < len(cfg.Hosts); i++ {
- protoAddr := cfg.Hosts[i]
- proto, addr, ok := strings.Cut(protoAddr, "://")
- if !ok {
- return nil, nil, fmt.Errorf("bad format %s, expected PROTO://ADDR", protoAddr)
- }
- // It's a bad idea to bind to TCP without tlsverify.
- authEnabled := tlsConfig != nil && tlsConfig.ClientAuth == tls.RequireAndVerifyClientCert
- if proto == "tcp" && !authEnabled {
- log.G(ctx).WithField("host", protoAddr).Warn("Binding to IP address without --tlsverify is insecure and gives root access on this machine to everyone who has access to your network.")
- log.G(ctx).WithField("host", protoAddr).Warn("Binding to an IP address, even on localhost, can also give access to scripts run in a browser. Be safe out there!")
- time.Sleep(time.Second)
- // If TLSVerify is explicitly set to false we'll take that as "Please let me shoot myself in the foot"
- // We do not want to continue to support a default mode where tls verification is disabled, so we do some extra warnings here and eventually remove support
- if !checkTLSAuthOK(cfg) {
- ipAddr, _, err := net.SplitHostPort(addr)
- if err != nil {
- return nil, nil, errors.Wrap(err, "error parsing tcp address")
- }
- // shortcut all this extra stuff for literal "localhost"
- // -H supports specifying hostnames, since we want to bypass this on loopback interfaces we'll look it up here.
- if ipAddr != "localhost" {
- ip := net.ParseIP(ipAddr)
- if ip == nil {
- ipA, err := net.ResolveIPAddr("ip", ipAddr)
- if err != nil {
- log.G(ctx).WithError(err).WithField("host", ipAddr).Error("Error looking up specified host address")
- }
- if ipA != nil {
- ip = ipA.IP
- }
- }
- if ip == nil || !ip.IsLoopback() {
- log.G(ctx).WithField("host", protoAddr).Warn("Binding to an IP address without --tlsverify is deprecated. Startup is intentionally being slowed down to show this message")
- log.G(ctx).WithField("host", protoAddr).Warn("Please consider generating tls certificates with client validation to prevent exposing unauthenticated root access to your network")
- log.G(ctx).WithField("host", protoAddr).Warnf("You can override this by explicitly specifying '--%s=false' or '--%s=false'", FlagTLS, FlagTLSVerify)
- log.G(ctx).WithField("host", protoAddr).Warnf("Support for listening on TCP without authentication or explicit intent to run without authentication will be removed in the next release")
- time.Sleep(15 * time.Second)
- }
- }
- }
- }
- // If we're binding to a TCP port, make sure that a container doesn't try to use it.
- if proto == "tcp" {
- if err := allocateDaemonPort(addr); err != nil {
- return nil, nil, err
- }
- }
- ls, err := listeners.Init(proto, addr, cfg.SocketGroup, tlsConfig)
- if err != nil {
- return nil, nil, err
- }
- log.G(ctx).Debugf("Listener created for HTTP on %s (%s)", proto, addr)
- hosts = append(hosts, addr)
- lss = append(lss, ls...)
- }
- return lss, hosts, nil
- }
- func createAndStartCluster(cli *DaemonCli, d *daemon.Daemon) (*cluster.Cluster, error) {
- name, _ := os.Hostname()
- // Use a buffered channel to pass changes from store watch API to daemon
- // A buffer allows store watch API and daemon processing to not wait for each other
- watchStream := make(chan *swarmapi.WatchMessage, 32)
- c, err := cluster.New(cluster.Config{
- Root: cli.Config.Root,
- Name: name,
- Backend: d,
- VolumeBackend: d.VolumesService(),
- ImageBackend: d.ImageBackend(),
- PluginBackend: d.PluginManager(),
- NetworkSubnetsProvider: d,
- DefaultAdvertiseAddr: cli.Config.SwarmDefaultAdvertiseAddr,
- RaftHeartbeatTick: cli.Config.SwarmRaftHeartbeatTick,
- RaftElectionTick: cli.Config.SwarmRaftElectionTick,
- RuntimeRoot: cli.getSwarmRunRoot(),
- WatchStream: watchStream,
- })
- if err != nil {
- return nil, err
- }
- d.SetCluster(c)
- err = c.Start()
- return c, err
- }
- // validates that the plugins requested with the --authorization-plugin flag are valid AuthzDriver
- // plugins present on the host and available to the daemon
- func validateAuthzPlugins(requestedPlugins []string, pg plugingetter.PluginGetter) error {
- for _, reqPlugin := range requestedPlugins {
- if _, err := pg.Get(reqPlugin, authorization.AuthZApiImplements, plugingetter.Lookup); err != nil {
- return err
- }
- }
- return nil
- }
- func systemContainerdRunning(honorXDG bool) (string, bool, error) {
- addr := containerddefaults.DefaultAddress
- if honorXDG {
- runtimeDir, err := homedir.GetRuntimeDir()
- if err != nil {
- return "", false, err
- }
- addr = filepath.Join(runtimeDir, "containerd", "containerd.sock")
- }
- _, err := os.Lstat(addr)
- return addr, err == nil, nil
- }
- // configureDaemonLogs sets the logging level and formatting. It expects
- // the passed configuration to already be validated, and ignores invalid options.
- func configureDaemonLogs(conf *config.Config) {
- switch conf.LogFormat {
- case log.JSONFormat:
- if err := log.SetFormat(log.JSONFormat); err != nil {
- panic(err.Error())
- }
- case log.TextFormat, "":
- if err := log.SetFormat(log.TextFormat); err != nil {
- panic(err.Error())
- }
- if conf.RawLogs {
- // FIXME(thaJeztah): this needs a better solution: containerd doesn't allow disabling colors, and this code is depending on internal knowledge of "log.SetFormat"
- if l, ok := log.L.Logger.Formatter.(*logrus.TextFormatter); ok {
- l.DisableColors = true
- }
- }
- default:
- panic("unsupported log format " + conf.LogFormat)
- }
- logLevel := conf.LogLevel
- if logLevel == "" {
- logLevel = "info"
- }
- if err := log.SetLevel(logLevel); err != nil {
- log.G(context.TODO()).WithError(err).Warn("configure log level")
- }
- }
- func configureProxyEnv(conf *config.Config) {
- if p := conf.HTTPProxy; p != "" {
- overrideProxyEnv("HTTP_PROXY", p)
- overrideProxyEnv("http_proxy", p)
- }
- if p := conf.HTTPSProxy; p != "" {
- overrideProxyEnv("HTTPS_PROXY", p)
- overrideProxyEnv("https_proxy", p)
- }
- if p := conf.NoProxy; p != "" {
- overrideProxyEnv("NO_PROXY", p)
- overrideProxyEnv("no_proxy", p)
- }
- }
- func overrideProxyEnv(name, val string) {
- if oldVal := os.Getenv(name); oldVal != "" && oldVal != val {
- log.G(context.TODO()).WithFields(log.Fields{
- "name": name,
- "old-value": config.MaskCredentials(oldVal),
- "new-value": config.MaskCredentials(val),
- }).Warn("overriding existing proxy variable with value from configuration")
- }
- _ = os.Setenv(name, val)
- }
|