github.com/moby/docker@v26.1.3+incompatible/daemon/info.go (about)

     1  // FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
     2  //go:build go1.19
     3  
     4  package daemon // import "github.com/docker/docker/daemon"
     5  
     6  import (
     7  	"context"
     8  	"fmt"
     9  	"os"
    10  	"runtime"
    11  	"strings"
    12  	"time"
    13  
    14  	"github.com/containerd/containerd/tracing"
    15  	"github.com/containerd/log"
    16  	"github.com/docker/docker/api"
    17  	"github.com/docker/docker/api/types"
    18  	"github.com/docker/docker/api/types/system"
    19  	"github.com/docker/docker/cli/debug"
    20  	"github.com/docker/docker/daemon/config"
    21  	"github.com/docker/docker/daemon/logger"
    22  	"github.com/docker/docker/dockerversion"
    23  	"github.com/docker/docker/pkg/fileutils"
    24  	"github.com/docker/docker/pkg/meminfo"
    25  	"github.com/docker/docker/pkg/parsers/kernel"
    26  	"github.com/docker/docker/pkg/parsers/operatingsystem"
    27  	"github.com/docker/docker/pkg/platform"
    28  	"github.com/docker/docker/pkg/sysinfo"
    29  	"github.com/docker/docker/registry"
    30  	metrics "github.com/docker/go-metrics"
    31  	"github.com/opencontainers/selinux/go-selinux"
    32  )
    33  
    34  func doWithTrace[T any](ctx context.Context, name string, f func() T) T {
    35  	_, span := tracing.StartSpan(ctx, name)
    36  	defer span.End()
    37  	return f()
    38  }
    39  
    40  // SystemInfo returns information about the host server the daemon is running on.
    41  //
    42  // The only error this should return is due to context cancellation/deadline.
    43  // Anything else should be logged and ignored because this is looking up
    44  // multiple things and is often used for debugging.
    45  // The only case valid early return is when the caller doesn't want the result anymore (ie context cancelled).
    46  func (daemon *Daemon) SystemInfo(ctx context.Context) (*system.Info, error) {
    47  	defer metrics.StartTimer(hostInfoFunctions.WithValues("system_info"))()
    48  
    49  	sysInfo := daemon.RawSysInfo()
    50  	cfg := daemon.config()
    51  
    52  	v := &system.Info{
    53  		ID:                 daemon.id,
    54  		Images:             daemon.imageService.CountImages(ctx),
    55  		IPv4Forwarding:     !sysInfo.IPv4ForwardingDisabled,
    56  		BridgeNfIptables:   !sysInfo.BridgeNFCallIPTablesDisabled,
    57  		BridgeNfIP6tables:  !sysInfo.BridgeNFCallIP6TablesDisabled,
    58  		Name:               hostName(ctx),
    59  		SystemTime:         time.Now().Format(time.RFC3339Nano),
    60  		LoggingDriver:      daemon.defaultLogConfig.Type,
    61  		KernelVersion:      kernelVersion(ctx),
    62  		OperatingSystem:    operatingSystem(ctx),
    63  		OSVersion:          osVersion(ctx),
    64  		IndexServerAddress: registry.IndexServer,
    65  		OSType:             runtime.GOOS,
    66  		Architecture:       platform.Architecture,
    67  		RegistryConfig:     doWithTrace(ctx, "registry.ServiceConfig", daemon.registryService.ServiceConfig),
    68  		NCPU:               doWithTrace(ctx, "sysinfo.NumCPU", sysinfo.NumCPU),
    69  		MemTotal:           memInfo(ctx).MemTotal,
    70  		GenericResources:   daemon.genericResources,
    71  		DockerRootDir:      cfg.Root,
    72  		Labels:             cfg.Labels,
    73  		ExperimentalBuild:  cfg.Experimental,
    74  		ServerVersion:      dockerversion.Version,
    75  		HTTPProxy:          config.MaskCredentials(getConfigOrEnv(cfg.HTTPProxy, "HTTP_PROXY", "http_proxy")),
    76  		HTTPSProxy:         config.MaskCredentials(getConfigOrEnv(cfg.HTTPSProxy, "HTTPS_PROXY", "https_proxy")),
    77  		NoProxy:            getConfigOrEnv(cfg.NoProxy, "NO_PROXY", "no_proxy"),
    78  		LiveRestoreEnabled: cfg.LiveRestoreEnabled,
    79  		Isolation:          daemon.defaultIsolation,
    80  		CDISpecDirs:        promoteNil(cfg.CDISpecDirs),
    81  	}
    82  
    83  	daemon.fillContainerStates(v)
    84  	daemon.fillDebugInfo(ctx, v)
    85  	daemon.fillAPIInfo(v, &cfg.Config)
    86  	// Retrieve platform specific info
    87  	if err := daemon.fillPlatformInfo(ctx, v, sysInfo, cfg); err != nil {
    88  		return nil, err
    89  	}
    90  	daemon.fillDriverInfo(v)
    91  	daemon.fillPluginsInfo(ctx, v, &cfg.Config)
    92  	daemon.fillSecurityOptions(v, sysInfo, &cfg.Config)
    93  	daemon.fillLicense(v)
    94  	daemon.fillDefaultAddressPools(ctx, v, &cfg.Config)
    95  
    96  	return v, nil
    97  }
    98  
    99  // SystemVersion returns version information about the daemon.
   100  //
   101  // The only error this should return is due to context cancellation/deadline.
   102  // Anything else should be logged and ignored because this is looking up
   103  // multiple things and is often used for debugging.
   104  // The only case valid early return is when the caller doesn't want the result anymore (ie context cancelled).
   105  func (daemon *Daemon) SystemVersion(ctx context.Context) (types.Version, error) {
   106  	defer metrics.StartTimer(hostInfoFunctions.WithValues("system_version"))()
   107  
   108  	kernelVersion := kernelVersion(ctx)
   109  	cfg := daemon.config()
   110  
   111  	v := types.Version{
   112  		Components: []types.ComponentVersion{
   113  			{
   114  				Name:    "Engine",
   115  				Version: dockerversion.Version,
   116  				Details: map[string]string{
   117  					"GitCommit":     dockerversion.GitCommit,
   118  					"ApiVersion":    api.DefaultVersion,
   119  					"MinAPIVersion": cfg.MinAPIVersion,
   120  					"GoVersion":     runtime.Version(),
   121  					"Os":            runtime.GOOS,
   122  					"Arch":          runtime.GOARCH,
   123  					"BuildTime":     dockerversion.BuildTime,
   124  					"KernelVersion": kernelVersion,
   125  					"Experimental":  fmt.Sprintf("%t", cfg.Experimental),
   126  				},
   127  			},
   128  		},
   129  
   130  		// Populate deprecated fields for older clients
   131  		Version:       dockerversion.Version,
   132  		GitCommit:     dockerversion.GitCommit,
   133  		APIVersion:    api.DefaultVersion,
   134  		MinAPIVersion: cfg.MinAPIVersion,
   135  		GoVersion:     runtime.Version(),
   136  		Os:            runtime.GOOS,
   137  		Arch:          runtime.GOARCH,
   138  		BuildTime:     dockerversion.BuildTime,
   139  		KernelVersion: kernelVersion,
   140  		Experimental:  cfg.Experimental,
   141  	}
   142  
   143  	v.Platform.Name = dockerversion.PlatformName
   144  
   145  	if err := daemon.fillPlatformVersion(ctx, &v, cfg); err != nil {
   146  		return v, err
   147  	}
   148  	return v, nil
   149  }
   150  
   151  func (daemon *Daemon) fillDriverInfo(v *system.Info) {
   152  	v.Driver = daemon.imageService.StorageDriver()
   153  	v.DriverStatus = daemon.imageService.LayerStoreStatus()
   154  
   155  	const warnMsg = `
   156  WARNING: The %s storage-driver is deprecated, and will be removed in a future release.
   157           Refer to the documentation for more information: https://docs.docker.com/go/storage-driver/`
   158  
   159  	switch v.Driver {
   160  	case "overlay":
   161  		v.Warnings = append(v.Warnings, fmt.Sprintf(warnMsg, v.Driver))
   162  	}
   163  
   164  	fillDriverWarnings(v)
   165  }
   166  
   167  func (daemon *Daemon) fillPluginsInfo(ctx context.Context, v *system.Info, cfg *config.Config) {
   168  	v.Plugins = system.PluginsInfo{
   169  		Volume:  daemon.volumes.GetDriverList(),
   170  		Network: daemon.GetNetworkDriverList(ctx),
   171  
   172  		// The authorization plugins are returned in the order they are
   173  		// used as they constitute a request/response modification chain.
   174  		Authorization: cfg.AuthorizationPlugins,
   175  		Log:           logger.ListDrivers(),
   176  	}
   177  }
   178  
   179  func (daemon *Daemon) fillSecurityOptions(v *system.Info, sysInfo *sysinfo.SysInfo, cfg *config.Config) {
   180  	var securityOptions []string
   181  	if sysInfo.AppArmor {
   182  		securityOptions = append(securityOptions, "name=apparmor")
   183  	}
   184  	if sysInfo.Seccomp && supportsSeccomp {
   185  		if daemon.seccompProfilePath != config.SeccompProfileDefault {
   186  			v.Warnings = append(v.Warnings, "WARNING: daemon is not using the default seccomp profile")
   187  		}
   188  		securityOptions = append(securityOptions, "name=seccomp,profile="+daemon.seccompProfilePath)
   189  	}
   190  	if selinux.GetEnabled() {
   191  		securityOptions = append(securityOptions, "name=selinux")
   192  	}
   193  	if rootIDs := daemon.idMapping.RootPair(); rootIDs.UID != 0 || rootIDs.GID != 0 {
   194  		securityOptions = append(securityOptions, "name=userns")
   195  	}
   196  	if Rootless(cfg) {
   197  		securityOptions = append(securityOptions, "name=rootless")
   198  	}
   199  	if cgroupNamespacesEnabled(sysInfo, cfg) {
   200  		securityOptions = append(securityOptions, "name=cgroupns")
   201  	}
   202  	if noNewPrivileges(cfg) {
   203  		securityOptions = append(securityOptions, "name=no-new-privileges")
   204  	}
   205  
   206  	v.SecurityOptions = securityOptions
   207  }
   208  
   209  func (daemon *Daemon) fillContainerStates(v *system.Info) {
   210  	cRunning, cPaused, cStopped := stateCtr.get()
   211  	v.Containers = cRunning + cPaused + cStopped
   212  	v.ContainersPaused = cPaused
   213  	v.ContainersRunning = cRunning
   214  	v.ContainersStopped = cStopped
   215  }
   216  
   217  // fillDebugInfo sets the current debugging state of the daemon, and additional
   218  // debugging information, such as the number of Go-routines, and file descriptors.
   219  //
   220  // Note that this currently always collects the information, but the CLI only
   221  // prints it if the daemon has debug enabled. We should consider to either make
   222  // this information optional (cli to request "with debugging information"), or
   223  // only collect it if the daemon has debug enabled. For the CLI code, see
   224  // https://github.com/docker/cli/blob/v20.10.12/cli/command/system/info.go#L239-L244
   225  func (daemon *Daemon) fillDebugInfo(ctx context.Context, v *system.Info) {
   226  	v.Debug = debug.IsEnabled()
   227  	v.NFd = fileutils.GetTotalUsedFds(ctx)
   228  	v.NGoroutines = runtime.NumGoroutine()
   229  	v.NEventsListener = daemon.EventsService.SubscribersCount()
   230  }
   231  
   232  func (daemon *Daemon) fillAPIInfo(v *system.Info, cfg *config.Config) {
   233  	const warn string = `
   234           Access to the remote API is equivalent to root access on the host. Refer
   235           to the 'Docker daemon attack surface' section in the documentation for
   236           more information: https://docs.docker.com/go/attack-surface/`
   237  
   238  	for _, host := range cfg.Hosts {
   239  		// cnf.Hosts is normalized during startup, so should always have a scheme/proto
   240  		proto, addr, _ := strings.Cut(host, "://")
   241  		if proto != "tcp" {
   242  			continue
   243  		}
   244  		const removal = "In future versions this will be a hard failure preventing the daemon from starting! Learn more at: https://docs.docker.com/go/api-security/"
   245  		if cfg.TLS == nil || !*cfg.TLS {
   246  			v.Warnings = append(v.Warnings, fmt.Sprintf("[DEPRECATION NOTICE]: API is accessible on http://%s without encryption.%s\n%s", addr, warn, removal))
   247  			continue
   248  		}
   249  		if cfg.TLSVerify == nil || !*cfg.TLSVerify {
   250  			v.Warnings = append(v.Warnings, fmt.Sprintf("[DEPRECATION NOTICE]: API is accessible on https://%s without TLS client verification.%s\n%s", addr, warn, removal))
   251  			continue
   252  		}
   253  	}
   254  }
   255  
   256  func (daemon *Daemon) fillDefaultAddressPools(ctx context.Context, v *system.Info, cfg *config.Config) {
   257  	_, span := tracing.StartSpan(ctx, "fillDefaultAddressPools")
   258  	defer span.End()
   259  	for _, pool := range cfg.DefaultAddressPools.Value() {
   260  		v.DefaultAddressPools = append(v.DefaultAddressPools, system.NetworkAddressPool{
   261  			Base: pool.Base,
   262  			Size: pool.Size,
   263  		})
   264  	}
   265  }
   266  
   267  func hostName(ctx context.Context) string {
   268  	ctx, span := tracing.StartSpan(ctx, "hostName")
   269  	defer span.End()
   270  	hostname := ""
   271  	if hn, err := os.Hostname(); err != nil {
   272  		log.G(ctx).Warnf("Could not get hostname: %v", err)
   273  	} else {
   274  		hostname = hn
   275  	}
   276  	return hostname
   277  }
   278  
   279  func kernelVersion(ctx context.Context) string {
   280  	ctx, span := tracing.StartSpan(ctx, "kernelVersion")
   281  	defer span.End()
   282  
   283  	var kernelVersion string
   284  	if kv, err := kernel.GetKernelVersion(); err != nil {
   285  		log.G(ctx).Warnf("Could not get kernel version: %v", err)
   286  	} else {
   287  		kernelVersion = kv.String()
   288  	}
   289  	return kernelVersion
   290  }
   291  
   292  func memInfo(ctx context.Context) *meminfo.Memory {
   293  	ctx, span := tracing.StartSpan(ctx, "memInfo")
   294  	defer span.End()
   295  
   296  	memInfo, err := meminfo.Read()
   297  	if err != nil {
   298  		log.G(ctx).Errorf("Could not read system memory info: %v", err)
   299  		memInfo = &meminfo.Memory{}
   300  	}
   301  	return memInfo
   302  }
   303  
   304  func operatingSystem(ctx context.Context) (operatingSystem string) {
   305  	ctx, span := tracing.StartSpan(ctx, "operatingSystem")
   306  	defer span.End()
   307  
   308  	defer metrics.StartTimer(hostInfoFunctions.WithValues("operating_system"))()
   309  
   310  	if s, err := operatingsystem.GetOperatingSystem(); err != nil {
   311  		log.G(ctx).Warnf("Could not get operating system name: %v", err)
   312  	} else {
   313  		operatingSystem = s
   314  	}
   315  	if inContainer, err := operatingsystem.IsContainerized(); err != nil {
   316  		log.G(ctx).Errorf("Could not determine if daemon is containerized: %v", err)
   317  		operatingSystem += " (error determining if containerized)"
   318  	} else if inContainer {
   319  		operatingSystem += " (containerized)"
   320  	}
   321  
   322  	return operatingSystem
   323  }
   324  
   325  func osVersion(ctx context.Context) (version string) {
   326  	ctx, span := tracing.StartSpan(ctx, "osVersion")
   327  	defer span.End()
   328  
   329  	defer metrics.StartTimer(hostInfoFunctions.WithValues("os_version"))()
   330  
   331  	version, err := operatingsystem.GetOperatingSystemVersion()
   332  	if err != nil {
   333  		log.G(ctx).Warnf("Could not get operating system version: %v", err)
   334  	}
   335  
   336  	return version
   337  }
   338  
   339  func getEnvAny(names ...string) string {
   340  	for _, n := range names {
   341  		if val := os.Getenv(n); val != "" {
   342  			return val
   343  		}
   344  	}
   345  	return ""
   346  }
   347  
   348  func getConfigOrEnv(config string, env ...string) string {
   349  	if config != "" {
   350  		return config
   351  	}
   352  	return getEnvAny(env...)
   353  }
   354  
   355  // promoteNil converts a nil slice to an empty slice.
   356  // A non-nil slice is returned as is.
   357  //
   358  // TODO: make generic again once we are a go module,
   359  // go.dev/issue/64759 is fixed, or we drop support for Go 1.21.
   360  func promoteNil(s []string) []string {
   361  	if s == nil {
   362  		return []string{}
   363  	}
   364  	return s
   365  }