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