github.com/rish1988/moby@v25.0.2+incompatible/cmd/dockerd/daemon.go (about) 1 package main 2 3 import ( 4 "context" 5 "crypto/tls" 6 "fmt" 7 "net" 8 "net/http" 9 "os" 10 "path/filepath" 11 "runtime" 12 "sort" 13 "strings" 14 "sync" 15 "time" 16 17 containerddefaults "github.com/containerd/containerd/defaults" 18 "github.com/containerd/containerd/tracing" 19 "github.com/containerd/log" 20 "github.com/docker/docker/api" 21 apiserver "github.com/docker/docker/api/server" 22 buildbackend "github.com/docker/docker/api/server/backend/build" 23 "github.com/docker/docker/api/server/middleware" 24 "github.com/docker/docker/api/server/router" 25 "github.com/docker/docker/api/server/router/build" 26 checkpointrouter "github.com/docker/docker/api/server/router/checkpoint" 27 "github.com/docker/docker/api/server/router/container" 28 distributionrouter "github.com/docker/docker/api/server/router/distribution" 29 grpcrouter "github.com/docker/docker/api/server/router/grpc" 30 "github.com/docker/docker/api/server/router/image" 31 "github.com/docker/docker/api/server/router/network" 32 pluginrouter "github.com/docker/docker/api/server/router/plugin" 33 sessionrouter "github.com/docker/docker/api/server/router/session" 34 swarmrouter "github.com/docker/docker/api/server/router/swarm" 35 systemrouter "github.com/docker/docker/api/server/router/system" 36 "github.com/docker/docker/api/server/router/volume" 37 buildkit "github.com/docker/docker/builder/builder-next" 38 "github.com/docker/docker/builder/dockerfile" 39 "github.com/docker/docker/cli/debug" 40 "github.com/docker/docker/cmd/dockerd/trap" 41 "github.com/docker/docker/daemon" 42 "github.com/docker/docker/daemon/cluster" 43 "github.com/docker/docker/daemon/config" 44 "github.com/docker/docker/daemon/listeners" 45 "github.com/docker/docker/dockerversion" 46 "github.com/docker/docker/libcontainerd/supervisor" 47 dopts "github.com/docker/docker/opts" 48 "github.com/docker/docker/pkg/authorization" 49 "github.com/docker/docker/pkg/homedir" 50 "github.com/docker/docker/pkg/pidfile" 51 "github.com/docker/docker/pkg/plugingetter" 52 "github.com/docker/docker/pkg/rootless" 53 "github.com/docker/docker/pkg/sysinfo" 54 "github.com/docker/docker/pkg/system" 55 "github.com/docker/docker/plugin" 56 "github.com/docker/docker/runconfig" 57 "github.com/docker/go-connections/tlsconfig" 58 "github.com/moby/buildkit/session" 59 "github.com/moby/buildkit/util/tracing/detect" 60 swarmapi "github.com/moby/swarmkit/v2/api" 61 "github.com/pkg/errors" 62 "github.com/sirupsen/logrus" 63 "github.com/spf13/pflag" 64 "go.opentelemetry.io/otel" 65 "go.opentelemetry.io/otel/propagation" 66 "go.opentelemetry.io/otel/sdk/resource" 67 "tags.cncf.io/container-device-interface/pkg/cdi" 68 ) 69 70 // DaemonCli represents the daemon CLI. 71 type DaemonCli struct { 72 *config.Config 73 configFile *string 74 flags *pflag.FlagSet 75 76 d *daemon.Daemon 77 authzMiddleware *authorization.Middleware // authzMiddleware enables to dynamically reload the authorization plugins 78 79 stopOnce sync.Once 80 apiShutdown chan struct{} 81 } 82 83 // NewDaemonCli returns a daemon CLI 84 func NewDaemonCli() *DaemonCli { 85 return &DaemonCli{ 86 apiShutdown: make(chan struct{}), 87 } 88 } 89 90 func (cli *DaemonCli) start(opts *daemonOptions) (err error) { 91 ctx := context.TODO() 92 93 if cli.Config, err = loadDaemonCliConfig(opts); err != nil { 94 return err 95 } 96 97 tlsConfig, err := newAPIServerTLSConfig(cli.Config) 98 if err != nil { 99 return err 100 } 101 102 if opts.Validate { 103 // If config wasn't OK we wouldn't have made it this far. 104 _, _ = fmt.Fprintln(os.Stderr, "configuration OK") 105 return nil 106 } 107 108 configureProxyEnv(cli.Config) 109 configureDaemonLogs(cli.Config) 110 111 log.G(ctx).Info("Starting up") 112 113 cli.configFile = &opts.configFile 114 cli.flags = opts.flags 115 116 if cli.Config.Debug { 117 debug.Enable() 118 } 119 120 if cli.Config.Experimental { 121 log.G(ctx).Warn("Running experimental build") 122 } 123 124 if cli.Config.IsRootless() { 125 log.G(ctx).Warn("Running in rootless mode. This mode has feature limitations.") 126 } 127 if rootless.RunningWithRootlessKit() { 128 log.G(ctx).Info("Running with RootlessKit integration") 129 if !cli.Config.IsRootless() { 130 return fmt.Errorf("rootless mode needs to be enabled for running with RootlessKit") 131 } 132 } 133 134 // return human-friendly error before creating files 135 if runtime.GOOS == "linux" && os.Geteuid() != 0 { 136 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/") 137 } 138 139 if err := setDefaultUmask(); err != nil { 140 return err 141 } 142 143 // Create the daemon root before we create ANY other files (PID, or migrate keys) 144 // to ensure the appropriate ACL is set (particularly relevant on Windows) 145 if err := daemon.CreateDaemonRoot(cli.Config); err != nil { 146 return err 147 } 148 149 if err := system.MkdirAll(cli.Config.ExecRoot, 0o700); err != nil { 150 return err 151 } 152 153 potentiallyUnderRuntimeDir := []string{cli.Config.ExecRoot} 154 155 if cli.Pidfile != "" { 156 if err = system.MkdirAll(filepath.Dir(cli.Pidfile), 0o755); err != nil { 157 return errors.Wrap(err, "failed to create pidfile directory") 158 } 159 if err = pidfile.Write(cli.Pidfile, os.Getpid()); err != nil { 160 return errors.Wrapf(err, "failed to start daemon, ensure docker is not running or delete %s", cli.Pidfile) 161 } 162 potentiallyUnderRuntimeDir = append(potentiallyUnderRuntimeDir, cli.Pidfile) 163 defer func() { 164 if err := os.Remove(cli.Pidfile); err != nil { 165 log.G(ctx).Error(err) 166 } 167 }() 168 } 169 170 if cli.Config.IsRootless() { 171 // Set sticky bit if XDG_RUNTIME_DIR is set && the file is actually under XDG_RUNTIME_DIR 172 if _, err := homedir.StickRuntimeDirContents(potentiallyUnderRuntimeDir); err != nil { 173 // StickRuntimeDirContents returns nil error if XDG_RUNTIME_DIR is just unset 174 log.G(ctx).WithError(err).Warn("cannot set sticky bit on files under XDG_RUNTIME_DIR") 175 } 176 } 177 178 lss, hosts, err := loadListeners(cli.Config, tlsConfig) 179 if err != nil { 180 return errors.Wrap(err, "failed to load listeners") 181 } 182 183 ctx, cancel := context.WithCancel(context.Background()) 184 waitForContainerDShutdown, err := cli.initContainerd(ctx) 185 if waitForContainerDShutdown != nil { 186 defer waitForContainerDShutdown(10 * time.Second) 187 } 188 if err != nil { 189 cancel() 190 return err 191 } 192 defer cancel() 193 194 httpServer := &http.Server{ 195 ReadHeaderTimeout: 5 * time.Minute, // "G112: Potential Slowloris Attack (gosec)"; not a real concern for our use, so setting a long timeout. 196 } 197 apiShutdownCtx, apiShutdownCancel := context.WithCancel(context.Background()) 198 apiShutdownDone := make(chan struct{}) 199 trap.Trap(cli.stop) 200 go func() { 201 // Block until cli.stop() has been called. 202 // It may have already been called, and that's okay. 203 // Any httpServer.Serve() calls made after 204 // httpServer.Shutdown() will return immediately, 205 // which is what we want. 206 <-cli.apiShutdown 207 err := httpServer.Shutdown(apiShutdownCtx) 208 if err != nil { 209 log.G(ctx).WithError(err).Error("Error shutting down http server") 210 } 211 close(apiShutdownDone) 212 }() 213 defer func() { 214 select { 215 case <-cli.apiShutdown: 216 // cli.stop() has been called and the daemon has completed 217 // shutting down. Give the HTTP server a little more time to 218 // finish handling any outstanding requests if needed. 219 tmr := time.AfterFunc(5*time.Second, apiShutdownCancel) 220 defer tmr.Stop() 221 <-apiShutdownDone 222 default: 223 // cli.start() has returned without cli.stop() being called, 224 // e.g. because the daemon failed to start. 225 // Stop the HTTP server with no grace period. 226 if closeErr := httpServer.Close(); closeErr != nil { 227 log.G(ctx).WithError(closeErr).Error("Error closing http server") 228 } 229 } 230 }() 231 232 // Notify that the API is active, but before daemon is set up. 233 preNotifyReady() 234 235 const otelServiceNameEnv = "OTEL_SERVICE_NAME" 236 if _, ok := os.LookupEnv(otelServiceNameEnv); !ok { 237 os.Setenv(otelServiceNameEnv, filepath.Base(os.Args[0])) 238 } 239 240 setOTLPProtoDefault() 241 otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{})) 242 243 // Override BuildKit's default Resource so that it matches the semconv 244 // version that is used in our code. 245 detect.Resource = resource.Default() 246 detect.Recorder = detect.NewTraceRecorder() 247 248 tp, err := detect.TracerProvider() 249 if err != nil { 250 log.G(ctx).WithError(err).Warn("Failed to initialize tracing, skipping") 251 } else { 252 otel.SetTracerProvider(tp) 253 log.G(ctx).Logger.AddHook(tracing.NewLogrusHook()) 254 } 255 256 pluginStore := plugin.NewStore() 257 258 var apiServer apiserver.Server 259 cli.authzMiddleware = initMiddlewares(&apiServer, cli.Config, pluginStore) 260 261 d, err := daemon.NewDaemon(ctx, cli.Config, pluginStore, cli.authzMiddleware) 262 if err != nil { 263 return errors.Wrap(err, "failed to start daemon") 264 } 265 266 d.StoreHosts(hosts) 267 268 // validate after NewDaemon has restored enabled plugins. Don't change order. 269 if err := validateAuthzPlugins(cli.Config.AuthorizationPlugins, pluginStore); err != nil { 270 return errors.Wrap(err, "failed to validate authorization plugin") 271 } 272 273 // Note that CDI is not inherently linux-specific, there are some linux-specific assumptions / implementations in the code that 274 // 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. 275 // 276 // In order to lift this restriction the following would have to be addressed: 277 // - Support needs to be added to the cdi package for injecting Windows devices: https://tags.cncf.io/container-device-interface/issues/28 278 // - The DeviceRequests API must be extended to non-linux platforms. 279 if runtime.GOOS == "linux" && cli.Config.Features["cdi"] { 280 daemon.RegisterCDIDriver(cli.Config.CDISpecDirs...) 281 } 282 283 cli.d = d 284 285 if err := startMetricsServer(cli.Config.MetricsAddress); err != nil { 286 return errors.Wrap(err, "failed to start metrics server") 287 } 288 289 c, err := createAndStartCluster(cli, d) 290 if err != nil { 291 log.G(ctx).Fatalf("Error starting cluster component: %v", err) 292 } 293 294 // Restart all autostart containers which has a swarm endpoint 295 // and is not yet running now that we have successfully 296 // initialized the cluster. 297 d.RestartSwarmContainers() 298 299 log.G(ctx).Info("Daemon has completed initialization") 300 301 routerCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 302 defer cancel() 303 304 // Get a the current daemon config, because the daemon sets up config 305 // during initialization. We cannot user the cli.Config for that reason, 306 // as that only holds the config that was set by the user. 307 // 308 // FIXME(thaJeztah): better separate runtime and config data? 309 daemonCfg := d.Config() 310 routerOptions, err := newRouterOptions(routerCtx, &daemonCfg, d) 311 if err != nil { 312 return err 313 } 314 315 routerOptions.cluster = c 316 317 httpServer.Handler = apiServer.CreateMux(routerOptions.Build()...) 318 319 go d.ProcessClusterNotifications(ctx, c.GetWatchStream()) 320 321 cli.setupConfigReloadTrap() 322 323 // after the daemon is done setting up we can notify systemd api 324 notifyReady() 325 326 // Daemon is fully initialized. Start handling API traffic 327 // and wait for serve API to complete. 328 var ( 329 apiWG sync.WaitGroup 330 errAPI = make(chan error, 1) 331 ) 332 for _, ls := range lss { 333 apiWG.Add(1) 334 go func(ls net.Listener) { 335 defer apiWG.Done() 336 log.G(ctx).Infof("API listen on %s", ls.Addr()) 337 if err := httpServer.Serve(ls); err != http.ErrServerClosed { 338 log.G(ctx).WithFields(log.Fields{ 339 "error": err, 340 "listener": ls.Addr(), 341 }).Error("ServeAPI error") 342 343 select { 344 case errAPI <- err: 345 default: 346 } 347 } 348 }(ls) 349 } 350 apiWG.Wait() 351 close(errAPI) 352 353 c.Cleanup() 354 355 // notify systemd that we're shutting down 356 notifyStopping() 357 shutdownDaemon(ctx, d) 358 359 if err := routerOptions.buildkit.Close(); err != nil { 360 log.G(ctx).WithError(err).Error("Failed to close buildkit") 361 } 362 363 // Stop notification processing and any background processes 364 cancel() 365 366 if err, ok := <-errAPI; ok { 367 return errors.Wrap(err, "shutting down due to ServeAPI error") 368 } 369 370 detect.Shutdown(context.Background()) 371 372 log.G(ctx).Info("Daemon shutdown complete") 373 return nil 374 } 375 376 // The buildkit "detect" package uses grpc as the default proto, which is in conformance with the old spec. 377 // 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 378 // so that the detect package will use http/protobuf as a default. 379 // TODO: This can be removed after buildkit is updated to use http/protobuf as the default. 380 func setOTLPProtoDefault() { 381 const ( 382 tracesEnv = "OTEL_EXPORTER_OTLP_TRACES_PROTOCOL" 383 protoEnv = "OTEL_EXPORTER_OTLP_PROTOCOL" 384 ) 385 386 if os.Getenv(tracesEnv) == "" && os.Getenv(protoEnv) == "" { 387 os.Setenv(tracesEnv, "http/protobuf") 388 } 389 } 390 391 type routerOptions struct { 392 sessionManager *session.Manager 393 buildBackend *buildbackend.Backend 394 features func() map[string]bool 395 buildkit *buildkit.Builder 396 daemon *daemon.Daemon 397 cluster *cluster.Cluster 398 } 399 400 func newRouterOptions(ctx context.Context, config *config.Config, d *daemon.Daemon) (routerOptions, error) { 401 opts := routerOptions{} 402 sm, err := session.NewManager() 403 if err != nil { 404 return opts, errors.Wrap(err, "failed to create sessionmanager") 405 } 406 407 manager, err := dockerfile.NewBuildManager(d.BuilderBackend(), d.IdentityMapping()) 408 if err != nil { 409 return opts, err 410 } 411 cgroupParent := newCgroupParent(config) 412 ro := routerOptions{ 413 sessionManager: sm, 414 features: d.Features, 415 daemon: d, 416 } 417 418 bk, err := buildkit.New(ctx, buildkit.Opt{ 419 SessionManager: sm, 420 Root: filepath.Join(config.Root, "buildkit"), 421 EngineID: d.ID(), 422 Dist: d.DistributionServices(), 423 ImageTagger: d.ImageService(), 424 NetworkController: d.NetworkController(), 425 DefaultCgroupParent: cgroupParent, 426 RegistryHosts: d.RegistryHosts, 427 BuilderConfig: config.Builder, 428 Rootless: daemon.Rootless(config), 429 IdentityMapping: d.IdentityMapping(), 430 DNSConfig: config.DNSConfig, 431 ApparmorProfile: daemon.DefaultApparmorProfile(), 432 UseSnapshotter: d.UsesSnapshotter(), 433 Snapshotter: d.ImageService().StorageDriver(), 434 ContainerdAddress: config.ContainerdAddr, 435 ContainerdNamespace: config.ContainerdNamespace, 436 }) 437 if err != nil { 438 return opts, err 439 } 440 441 bb, err := buildbackend.NewBackend(d.ImageService(), manager, bk, d.EventsService) 442 if err != nil { 443 return opts, errors.Wrap(err, "failed to create buildmanager") 444 } 445 446 ro.buildBackend = bb 447 ro.buildkit = bk 448 449 return ro, nil 450 } 451 452 func (cli *DaemonCli) reloadConfig() { 453 ctx := context.TODO() 454 reload := func(c *config.Config) { 455 if err := validateAuthzPlugins(c.AuthorizationPlugins, cli.d.PluginStore); err != nil { 456 log.G(ctx).Fatalf("Error validating authorization plugin: %v", err) 457 return 458 } 459 460 if err := cli.d.Reload(c); err != nil { 461 log.G(ctx).Errorf("Error reconfiguring the daemon: %v", err) 462 return 463 } 464 465 // Apply our own configuration only after the daemon reload has succeeded. We 466 // don't want to partially apply the config if the daemon is unhappy with it. 467 468 cli.authzMiddleware.SetPlugins(c.AuthorizationPlugins) 469 470 if c.IsValueSet("debug") { 471 debugEnabled := debug.IsEnabled() 472 switch { 473 case debugEnabled && !c.Debug: // disable debug 474 debug.Disable() 475 case c.Debug && !debugEnabled: // enable debug 476 debug.Enable() 477 } 478 } 479 } 480 481 if err := config.Reload(*cli.configFile, cli.flags, reload); err != nil { 482 log.G(ctx).Error(err) 483 } 484 } 485 486 func (cli *DaemonCli) stop() { 487 // Signal that the API server should shut down as soon as possible. 488 // This construct is used rather than directly shutting down the HTTP 489 // server to avoid any issues if this method is called before the server 490 // has been instantiated in cli.start(). If this method is called first, 491 // the HTTP server will be shut down immediately upon instantiation. 492 cli.stopOnce.Do(func() { 493 close(cli.apiShutdown) 494 }) 495 } 496 497 // shutdownDaemon just wraps daemon.Shutdown() to handle a timeout in case 498 // d.Shutdown() is waiting too long to kill container or worst it's 499 // blocked there 500 func shutdownDaemon(ctx context.Context, d *daemon.Daemon) { 501 var cancel context.CancelFunc 502 if timeout := d.ShutdownTimeout(); timeout >= 0 { 503 ctx, cancel = context.WithTimeout(ctx, time.Duration(timeout)*time.Second) 504 } else { 505 ctx, cancel = context.WithCancel(ctx) 506 } 507 508 go func() { 509 defer cancel() 510 d.Shutdown(ctx) 511 }() 512 513 <-ctx.Done() 514 if errors.Is(ctx.Err(), context.DeadlineExceeded) { 515 log.G(ctx).Error("Force shutdown daemon") 516 } else { 517 log.G(ctx).Debug("Clean shutdown succeeded") 518 } 519 } 520 521 func loadDaemonCliConfig(opts *daemonOptions) (*config.Config, error) { 522 if !opts.flags.Parsed() { 523 return nil, errors.New(`cannot load CLI config before flags are parsed`) 524 } 525 opts.setDefaultOptions() 526 527 conf := opts.daemonConfig 528 flags := opts.flags 529 conf.Debug = opts.Debug 530 conf.Hosts = opts.Hosts 531 conf.LogLevel = opts.LogLevel 532 conf.LogFormat = log.OutputFormat(opts.LogFormat) 533 534 // The DOCKER_MIN_API_VERSION env-var allows overriding the minimum API 535 // version provided by the daemon within constraints of the minimum and 536 // maximum (current) supported API versions. 537 // 538 // API versions older than [config.defaultMinAPIVersion] are deprecated and 539 // to be removed in a future release. The "DOCKER_MIN_API_VERSION" env-var 540 // should only be used for exceptional cases. 541 if ver := os.Getenv("DOCKER_MIN_API_VERSION"); ver != "" { 542 if err := config.ValidateMinAPIVersion(ver); err != nil { 543 return nil, errors.Wrap(err, "invalid DOCKER_MIN_API_VERSION") 544 } 545 conf.MinAPIVersion = ver 546 } 547 548 if flags.Changed(FlagTLS) { 549 conf.TLS = &opts.TLS 550 } 551 if flags.Changed(FlagTLSVerify) { 552 conf.TLSVerify = &opts.TLSVerify 553 v := true 554 conf.TLS = &v 555 } 556 557 if opts.TLSOptions != nil { 558 conf.TLSOptions = config.TLSOptions{ 559 CAFile: opts.TLSOptions.CAFile, 560 CertFile: opts.TLSOptions.CertFile, 561 KeyFile: opts.TLSOptions.KeyFile, 562 } 563 } else { 564 conf.TLSOptions = config.TLSOptions{} 565 } 566 567 if opts.configFile != "" { 568 c, err := config.MergeDaemonConfigurations(conf, flags, opts.configFile) 569 if err != nil { 570 if flags.Changed("config-file") || !os.IsNotExist(err) { 571 return nil, errors.Wrapf(err, "unable to configure the Docker daemon with file %s", opts.configFile) 572 } 573 } 574 575 // the merged configuration can be nil if the config file didn't exist. 576 // leave the current configuration as it is if when that happens. 577 if c != nil { 578 conf = c 579 } 580 } 581 582 if err := normalizeHosts(conf); err != nil { 583 return nil, err 584 } 585 586 if err := config.Validate(conf); err != nil { 587 return nil, err 588 } 589 590 // Check if duplicate label-keys with different values are found 591 newLabels, err := config.GetConflictFreeLabels(conf.Labels) 592 if err != nil { 593 return nil, err 594 } 595 conf.Labels = newLabels 596 597 // Regardless of whether the user sets it to true or false, if they 598 // specify TLSVerify at all then we need to turn on TLS 599 if conf.IsValueSet(FlagTLSVerify) { 600 v := true 601 conf.TLS = &v 602 } 603 604 if conf.TLSVerify == nil && conf.TLS != nil { 605 conf.TLSVerify = conf.TLS 606 } 607 608 err = validateCPURealtimeOptions(conf) 609 if err != nil { 610 return nil, err 611 } 612 613 if conf.CDISpecDirs == nil { 614 // If the CDISpecDirs is not set at this stage, we set it to the default. 615 conf.CDISpecDirs = append([]string(nil), cdi.DefaultSpecDirs...) 616 } else if len(conf.CDISpecDirs) == 1 && conf.CDISpecDirs[0] == "" { 617 // If CDISpecDirs is set to an empty string, we clear it to ensure that CDI is disabled. 618 conf.CDISpecDirs = nil 619 } 620 if !conf.Features["cdi"] { 621 // If the CDI feature is not enabled, we clear the CDISpecDirs to ensure that CDI is disabled. 622 conf.CDISpecDirs = nil 623 } 624 625 if err := loadCLIPlatformConfig(conf); err != nil { 626 return nil, err 627 } 628 629 return conf, nil 630 } 631 632 // normalizeHosts normalizes the configured config.Hosts and remove duplicates. 633 // It returns an error if it fails to parse a host. 634 func normalizeHosts(config *config.Config) error { 635 if len(config.Hosts) == 0 { 636 // if no hosts are configured, create a single entry slice, so that the 637 // default is used. 638 // 639 // TODO(thaJeztah) implement a cleaner way for this; this depends on a 640 // side-effect of how we parse empty/partial hosts. 641 config.Hosts = make([]string, 1) 642 } 643 hosts := make([]string, 0, len(config.Hosts)) 644 seen := make(map[string]struct{}, len(config.Hosts)) 645 646 useTLS := DefaultTLSValue 647 if config.TLS != nil { 648 useTLS = *config.TLS 649 } 650 651 for _, h := range config.Hosts { 652 host, err := dopts.ParseHost(useTLS, honorXDG, h) 653 if err != nil { 654 return err 655 } 656 if _, ok := seen[host]; ok { 657 continue 658 } 659 seen[host] = struct{}{} 660 hosts = append(hosts, host) 661 } 662 sort.Strings(hosts) 663 config.Hosts = hosts 664 return nil 665 } 666 667 func (opts routerOptions) Build() []router.Router { 668 decoder := runconfig.ContainerDecoder{ 669 GetSysInfo: func() *sysinfo.SysInfo { 670 return opts.daemon.RawSysInfo() 671 }, 672 } 673 674 routers := []router.Router{ 675 // we need to add the checkpoint router before the container router or the DELETE gets masked 676 checkpointrouter.NewRouter(opts.daemon, decoder), 677 container.NewRouter(opts.daemon, decoder, opts.daemon.RawSysInfo().CgroupUnified), 678 image.NewRouter( 679 opts.daemon.ImageService(), 680 opts.daemon.RegistryService(), 681 opts.daemon.ReferenceStore, 682 opts.daemon.ImageService().DistributionServices().ImageStore, 683 opts.daemon.ImageService().DistributionServices().LayerStore, 684 ), 685 systemrouter.NewRouter(opts.daemon, opts.cluster, opts.buildkit, opts.daemon.Features), 686 volume.NewRouter(opts.daemon.VolumesService(), opts.cluster), 687 build.NewRouter(opts.buildBackend, opts.daemon), 688 sessionrouter.NewRouter(opts.sessionManager), 689 swarmrouter.NewRouter(opts.cluster), 690 pluginrouter.NewRouter(opts.daemon.PluginManager()), 691 distributionrouter.NewRouter(opts.daemon.ImageBackend()), 692 } 693 694 if opts.buildBackend != nil { 695 routers = append(routers, grpcrouter.NewRouter(opts.buildBackend)) 696 } 697 698 if opts.daemon.NetworkControllerEnabled() { 699 routers = append(routers, network.NewRouter(opts.daemon, opts.cluster)) 700 } 701 702 if opts.daemon.HasExperimental() { 703 for _, r := range routers { 704 for _, route := range r.Routes() { 705 if experimental, ok := route.(router.ExperimentalRoute); ok { 706 experimental.Enable() 707 } 708 } 709 } 710 } 711 712 return routers 713 } 714 715 func initMiddlewares(s *apiserver.Server, cfg *config.Config, pluginStore plugingetter.PluginGetter) *authorization.Middleware { 716 v := dockerversion.Version 717 718 exp := middleware.NewExperimentalMiddleware(cfg.Experimental) 719 s.UseMiddleware(exp) 720 721 vm := middleware.NewVersionMiddleware(v, api.DefaultVersion, cfg.MinAPIVersion) 722 s.UseMiddleware(vm) 723 724 if cfg.CorsHeaders != "" { 725 c := middleware.NewCORSMiddleware(cfg.CorsHeaders) 726 s.UseMiddleware(c) 727 } 728 729 authzMiddleware := authorization.NewMiddleware(cfg.AuthorizationPlugins, pluginStore) 730 s.UseMiddleware(authzMiddleware) 731 return authzMiddleware 732 } 733 734 func (cli *DaemonCli) getContainerdDaemonOpts() ([]supervisor.DaemonOpt, error) { 735 var opts []supervisor.DaemonOpt 736 if cli.Debug { 737 opts = append(opts, supervisor.WithLogLevel("debug")) 738 } else { 739 opts = append(opts, supervisor.WithLogLevel(cli.LogLevel)) 740 } 741 742 if logFormat := cli.Config.LogFormat; logFormat != "" { 743 opts = append(opts, supervisor.WithLogFormat(logFormat)) 744 } 745 746 if !cli.CriContainerd { 747 // CRI support in the managed daemon is currently opt-in. 748 // 749 // It's disabled by default, originally because it was listening on 750 // a TCP connection at 0.0.0.0:10010, which was considered a security 751 // risk, and could conflict with user's container ports. 752 // 753 // Current versions of containerd started now listen on localhost on 754 // an ephemeral port instead, but could still conflict with container 755 // ports, and running kubernetes using the static binaries is not a 756 // common scenario, so we (for now) continue disabling it by default. 757 // 758 // Also see https://github.com/containerd/containerd/issues/2483#issuecomment-407530608 759 opts = append(opts, supervisor.WithCRIDisabled()) 760 } 761 762 return opts, nil 763 } 764 765 func newAPIServerTLSConfig(config *config.Config) (*tls.Config, error) { 766 var tlsConfig *tls.Config 767 if config.TLS != nil && *config.TLS { 768 var ( 769 clientAuth tls.ClientAuthType 770 err error 771 ) 772 if config.TLSVerify == nil || *config.TLSVerify { 773 // server requires and verifies client's certificate 774 clientAuth = tls.RequireAndVerifyClientCert 775 } 776 tlsConfig, err = tlsconfig.Server(tlsconfig.Options{ 777 CAFile: config.TLSOptions.CAFile, 778 CertFile: config.TLSOptions.CertFile, 779 KeyFile: config.TLSOptions.KeyFile, 780 ExclusiveRootPools: true, 781 ClientAuth: clientAuth, 782 }) 783 if err != nil { 784 return nil, errors.Wrap(err, "invalid TLS configuration") 785 } 786 } 787 788 return tlsConfig, nil 789 } 790 791 // checkTLSAuthOK checks basically for an explicitly disabled TLS/TLSVerify 792 // Going forward we do not want to support a scenario where dockerd listens 793 // on TCP without either TLS client auth (or an explicit opt-in to disable it) 794 func checkTLSAuthOK(c *config.Config) bool { 795 if c.TLS == nil { 796 // Either TLS is enabled by default, in which case TLS verification should be enabled by default, or explicitly disabled 797 // Or TLS is disabled by default... in any of these cases, we can just take the default value as to how to proceed 798 return DefaultTLSValue 799 } 800 801 if !*c.TLS { 802 // TLS is explicitly disabled, which is supported 803 return true 804 } 805 806 if c.TLSVerify == nil { 807 // this actually shouldn't happen since we set TLSVerify on the config object anyway 808 // But in case it does get here, be cautious and assume this is not supported. 809 return false 810 } 811 812 // Either TLSVerify is explicitly enabled or disabled, both cases are supported 813 return true 814 } 815 816 func loadListeners(cfg *config.Config, tlsConfig *tls.Config) ([]net.Listener, []string, error) { 817 ctx := context.TODO() 818 819 if len(cfg.Hosts) == 0 { 820 return nil, nil, errors.New("no hosts configured") 821 } 822 var ( 823 hosts []string 824 lss []net.Listener 825 ) 826 827 for i := 0; i < len(cfg.Hosts); i++ { 828 protoAddr := cfg.Hosts[i] 829 proto, addr, ok := strings.Cut(protoAddr, "://") 830 if !ok { 831 return nil, nil, fmt.Errorf("bad format %s, expected PROTO://ADDR", protoAddr) 832 } 833 834 // It's a bad idea to bind to TCP without tlsverify. 835 authEnabled := tlsConfig != nil && tlsConfig.ClientAuth == tls.RequireAndVerifyClientCert 836 if proto == "tcp" && !authEnabled { 837 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.") 838 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!") 839 time.Sleep(time.Second) 840 841 // If TLSVerify is explicitly set to false we'll take that as "Please let me shoot myself in the foot" 842 // 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 843 if !checkTLSAuthOK(cfg) { 844 ipAddr, _, err := net.SplitHostPort(addr) 845 if err != nil { 846 return nil, nil, errors.Wrap(err, "error parsing tcp address") 847 } 848 849 // shortcut all this extra stuff for literal "localhost" 850 // -H supports specifying hostnames, since we want to bypass this on loopback interfaces we'll look it up here. 851 if ipAddr != "localhost" { 852 ip := net.ParseIP(ipAddr) 853 if ip == nil { 854 ipA, err := net.ResolveIPAddr("ip", ipAddr) 855 if err != nil { 856 log.G(ctx).WithError(err).WithField("host", ipAddr).Error("Error looking up specified host address") 857 } 858 if ipA != nil { 859 ip = ipA.IP 860 } 861 } 862 if ip == nil || !ip.IsLoopback() { 863 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") 864 log.G(ctx).WithField("host", protoAddr).Warn("Please consider generating tls certificates with client validation to prevent exposing unauthenticated root access to your network") 865 log.G(ctx).WithField("host", protoAddr).Warnf("You can override this by explicitly specifying '--%s=false' or '--%s=false'", FlagTLS, FlagTLSVerify) 866 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") 867 868 time.Sleep(15 * time.Second) 869 } 870 } 871 } 872 } 873 // If we're binding to a TCP port, make sure that a container doesn't try to use it. 874 if proto == "tcp" { 875 if err := allocateDaemonPort(addr); err != nil { 876 return nil, nil, err 877 } 878 } 879 ls, err := listeners.Init(proto, addr, cfg.SocketGroup, tlsConfig) 880 if err != nil { 881 return nil, nil, err 882 } 883 log.G(ctx).Debugf("Listener created for HTTP on %s (%s)", proto, addr) 884 hosts = append(hosts, addr) 885 lss = append(lss, ls...) 886 } 887 888 return lss, hosts, nil 889 } 890 891 func createAndStartCluster(cli *DaemonCli, d *daemon.Daemon) (*cluster.Cluster, error) { 892 name, _ := os.Hostname() 893 894 // Use a buffered channel to pass changes from store watch API to daemon 895 // A buffer allows store watch API and daemon processing to not wait for each other 896 watchStream := make(chan *swarmapi.WatchMessage, 32) 897 898 c, err := cluster.New(cluster.Config{ 899 Root: cli.Config.Root, 900 Name: name, 901 Backend: d, 902 VolumeBackend: d.VolumesService(), 903 ImageBackend: d.ImageBackend(), 904 PluginBackend: d.PluginManager(), 905 NetworkSubnetsProvider: d, 906 DefaultAdvertiseAddr: cli.Config.SwarmDefaultAdvertiseAddr, 907 RaftHeartbeatTick: cli.Config.SwarmRaftHeartbeatTick, 908 RaftElectionTick: cli.Config.SwarmRaftElectionTick, 909 RuntimeRoot: cli.getSwarmRunRoot(), 910 WatchStream: watchStream, 911 }) 912 if err != nil { 913 return nil, err 914 } 915 d.SetCluster(c) 916 err = c.Start() 917 918 return c, err 919 } 920 921 // validates that the plugins requested with the --authorization-plugin flag are valid AuthzDriver 922 // plugins present on the host and available to the daemon 923 func validateAuthzPlugins(requestedPlugins []string, pg plugingetter.PluginGetter) error { 924 for _, reqPlugin := range requestedPlugins { 925 if _, err := pg.Get(reqPlugin, authorization.AuthZApiImplements, plugingetter.Lookup); err != nil { 926 return err 927 } 928 } 929 return nil 930 } 931 932 func systemContainerdRunning(honorXDG bool) (string, bool, error) { 933 addr := containerddefaults.DefaultAddress 934 if honorXDG { 935 runtimeDir, err := homedir.GetRuntimeDir() 936 if err != nil { 937 return "", false, err 938 } 939 addr = filepath.Join(runtimeDir, "containerd", "containerd.sock") 940 } 941 _, err := os.Lstat(addr) 942 return addr, err == nil, nil 943 } 944 945 // configureDaemonLogs sets the logging level and formatting. It expects 946 // the passed configuration to already be validated, and ignores invalid options. 947 func configureDaemonLogs(conf *config.Config) { 948 switch conf.LogFormat { 949 case log.JSONFormat: 950 if err := log.SetFormat(log.JSONFormat); err != nil { 951 panic(err.Error()) 952 } 953 case log.TextFormat, "": 954 if err := log.SetFormat(log.TextFormat); err != nil { 955 panic(err.Error()) 956 } 957 if conf.RawLogs { 958 // FIXME(thaJeztah): this needs a better solution: containerd doesn't allow disabling colors, and this code is depending on internal knowledge of "log.SetFormat" 959 if l, ok := log.L.Logger.Formatter.(*logrus.TextFormatter); ok { 960 l.DisableColors = true 961 } 962 } 963 default: 964 panic("unsupported log format " + conf.LogFormat) 965 } 966 967 logLevel := conf.LogLevel 968 if logLevel == "" { 969 logLevel = "info" 970 } 971 if err := log.SetLevel(logLevel); err != nil { 972 log.G(context.TODO()).WithError(err).Warn("configure log level") 973 } 974 } 975 976 func configureProxyEnv(conf *config.Config) { 977 if p := conf.HTTPProxy; p != "" { 978 overrideProxyEnv("HTTP_PROXY", p) 979 overrideProxyEnv("http_proxy", p) 980 } 981 if p := conf.HTTPSProxy; p != "" { 982 overrideProxyEnv("HTTPS_PROXY", p) 983 overrideProxyEnv("https_proxy", p) 984 } 985 if p := conf.NoProxy; p != "" { 986 overrideProxyEnv("NO_PROXY", p) 987 overrideProxyEnv("no_proxy", p) 988 } 989 } 990 991 func overrideProxyEnv(name, val string) { 992 if oldVal := os.Getenv(name); oldVal != "" && oldVal != val { 993 log.G(context.TODO()).WithFields(log.Fields{ 994 "name": name, 995 "old-value": config.MaskCredentials(oldVal), 996 "new-value": config.MaskCredentials(val), 997 }).Warn("overriding existing proxy variable with value from configuration") 998 } 999 _ = os.Setenv(name, val) 1000 }