moby/daemon/info.go
Anca Iordache 427c7cc5f8
Add http(s) proxy properties to daemon configuration
This allows configuring the daemon's proxy server through the daemon.json con-
figuration file or command-line flags configuration file, in addition to the
existing option (through environment variables).

Configuring environment variables on Windows to configure a service is more
complicated than on Linux, and adding alternatives for this to the daemon con-
figuration makes the configuration more transparent and easier to use.

The configuration as set through command-line flags or through the daemon.json
configuration file takes precedence over env-vars in the daemon's environment,
which allows the daemon to use a different proxy. If both command-line flags
and a daemon.json configuration option is set, an error is produced when starting
the daemon.

Note that this configuration is not "live reloadable" due to Golang's use of
`sync.Once()` for proxy configuration, which means that changing the proxy
configuration requires a restart of the daemon (reload / SIGHUP will not update
the configuration.

With this patch:

    cat /etc/docker/daemon.json
    {
        "http-proxy": "http://proxytest.example.com:80",
        "https-proxy": "https://proxytest.example.com:443"
    }

    docker pull busybox
    Using default tag: latest
    Error response from daemon: Get "https://registry-1.docker.io/v2/": proxyconnect tcp: dial tcp: lookup proxytest.example.com on 127.0.0.11:53: no such host

    docker build .
    Sending build context to Docker daemon  89.28MB
    Step 1/3 : FROM golang:1.16-alpine AS base
    Get "https://registry-1.docker.io/v2/": proxyconnect tcp: dial tcp: lookup proxytest.example.com on 127.0.0.11:53: no such host

Integration tests were added to test the behavior:

- verify that the configuration through all means are used (env-var,
  command-line flags, damon.json), and used in the expected order of
  preference.
- verify that conflicting options produce an error.

Signed-off-by: Anca Iordache <anca.iordache@docker.com>
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2021-10-27 12:38:59 +02:00

305 lines
9.8 KiB
Go

package daemon // import "github.com/docker/docker/daemon"
import (
"fmt"
"os"
"runtime"
"strings"
"time"
"github.com/docker/docker/api"
"github.com/docker/docker/api/types"
"github.com/docker/docker/cli/debug"
"github.com/docker/docker/daemon/config"
"github.com/docker/docker/daemon/logger"
"github.com/docker/docker/dockerversion"
"github.com/docker/docker/pkg/fileutils"
"github.com/docker/docker/pkg/parsers/kernel"
"github.com/docker/docker/pkg/parsers/operatingsystem"
"github.com/docker/docker/pkg/platform"
"github.com/docker/docker/pkg/sysinfo"
"github.com/docker/docker/pkg/system"
"github.com/docker/docker/registry"
metrics "github.com/docker/go-metrics"
"github.com/opencontainers/selinux/go-selinux"
"github.com/sirupsen/logrus"
)
// SystemInfo returns information about the host server the daemon is running on.
func (daemon *Daemon) SystemInfo() *types.Info {
defer metrics.StartTimer(hostInfoFunctions.WithValues("system_info"))()
sysInfo := daemon.RawSysInfo()
cRunning, cPaused, cStopped := stateCtr.get()
v := &types.Info{
ID: daemon.ID,
Containers: cRunning + cPaused + cStopped,
ContainersRunning: cRunning,
ContainersPaused: cPaused,
ContainersStopped: cStopped,
Images: daemon.imageService.CountImages(),
IPv4Forwarding: !sysInfo.IPv4ForwardingDisabled,
BridgeNfIptables: !sysInfo.BridgeNFCallIPTablesDisabled,
BridgeNfIP6tables: !sysInfo.BridgeNFCallIP6TablesDisabled,
Debug: debug.IsEnabled(),
Name: hostName(),
NFd: fileutils.GetTotalUsedFds(),
NGoroutines: runtime.NumGoroutine(),
SystemTime: time.Now().Format(time.RFC3339Nano),
LoggingDriver: daemon.defaultLogConfig.Type,
NEventsListener: daemon.EventsService.SubscribersCount(),
KernelVersion: kernelVersion(),
OperatingSystem: operatingSystem(),
OSVersion: osVersion(),
IndexServerAddress: registry.IndexServer,
OSType: platform.OSType,
Architecture: platform.Architecture,
RegistryConfig: daemon.RegistryService.ServiceConfig(),
NCPU: sysinfo.NumCPU(),
MemTotal: memInfo().MemTotal,
GenericResources: daemon.genericResources,
DockerRootDir: daemon.configStore.Root,
Labels: daemon.configStore.Labels,
ExperimentalBuild: daemon.configStore.Experimental,
ServerVersion: dockerversion.Version,
HTTPProxy: config.MaskCredentials(getConfigOrEnv(daemon.configStore.HTTPProxy, "HTTP_PROXY", "http_proxy")),
HTTPSProxy: config.MaskCredentials(getConfigOrEnv(daemon.configStore.HTTPSProxy, "HTTPS_PROXY", "https_proxy")),
NoProxy: getConfigOrEnv(daemon.configStore.NoProxy, "NO_PROXY", "no_proxy"),
LiveRestoreEnabled: daemon.configStore.LiveRestoreEnabled,
Isolation: daemon.defaultIsolation,
}
daemon.fillClusterInfo(v)
daemon.fillAPIInfo(v)
// Retrieve platform specific info
daemon.fillPlatformInfo(v, sysInfo)
daemon.fillDriverInfo(v)
daemon.fillPluginsInfo(v)
daemon.fillSecurityOptions(v, sysInfo)
daemon.fillLicense(v)
daemon.fillDefaultAddressPools(v)
if v.DefaultRuntime == config.LinuxV1RuntimeName {
v.Warnings = append(v.Warnings, fmt.Sprintf("Configured default runtime %q is deprecated and will be removed in the next release.", config.LinuxV1RuntimeName))
}
return v
}
// SystemVersion returns version information about the daemon.
func (daemon *Daemon) SystemVersion() types.Version {
defer metrics.StartTimer(hostInfoFunctions.WithValues("system_version"))()
kernelVersion := kernelVersion()
v := types.Version{
Components: []types.ComponentVersion{
{
Name: "Engine",
Version: dockerversion.Version,
Details: map[string]string{
"GitCommit": dockerversion.GitCommit,
"ApiVersion": api.DefaultVersion,
"MinAPIVersion": api.MinVersion,
"GoVersion": runtime.Version(),
"Os": runtime.GOOS,
"Arch": runtime.GOARCH,
"BuildTime": dockerversion.BuildTime,
"KernelVersion": kernelVersion,
"Experimental": fmt.Sprintf("%t", daemon.configStore.Experimental),
},
},
},
// Populate deprecated fields for older clients
Version: dockerversion.Version,
GitCommit: dockerversion.GitCommit,
APIVersion: api.DefaultVersion,
MinAPIVersion: api.MinVersion,
GoVersion: runtime.Version(),
Os: runtime.GOOS,
Arch: runtime.GOARCH,
BuildTime: dockerversion.BuildTime,
KernelVersion: kernelVersion,
Experimental: daemon.configStore.Experimental,
}
v.Platform.Name = dockerversion.PlatformName
daemon.fillPlatformVersion(&v)
return v
}
func (daemon *Daemon) fillClusterInfo(v *types.Info) {
v.ClusterAdvertise = daemon.configStore.ClusterAdvertise
v.ClusterStore = daemon.configStore.ClusterStore
if v.ClusterAdvertise != "" || v.ClusterStore != "" {
v.Warnings = append(v.Warnings, `WARNING: node discovery and overlay networks with an external k/v store (cluster-advertise,
cluster-store, cluster-store-opt) are deprecated and will be removed in a future release.`)
}
}
func (daemon *Daemon) fillDriverInfo(v *types.Info) {
switch daemon.graphDriver {
case "aufs", "devicemapper", "overlay":
v.Warnings = append(v.Warnings, fmt.Sprintf("WARNING: the %s storage-driver is deprecated, and will be removed in a future release.", daemon.graphDriver))
}
v.Driver = daemon.graphDriver
v.DriverStatus = daemon.imageService.LayerStoreStatus()
fillDriverWarnings(v)
}
func (daemon *Daemon) fillPluginsInfo(v *types.Info) {
v.Plugins = types.PluginsInfo{
Volume: daemon.volumes.GetDriverList(),
Network: daemon.GetNetworkDriverList(),
// The authorization plugins are returned in the order they are
// used as they constitute a request/response modification chain.
Authorization: daemon.configStore.AuthorizationPlugins,
Log: logger.ListDrivers(),
}
}
func (daemon *Daemon) fillSecurityOptions(v *types.Info, sysInfo *sysinfo.SysInfo) {
var securityOptions []string
if sysInfo.AppArmor {
securityOptions = append(securityOptions, "name=apparmor")
}
if sysInfo.Seccomp && supportsSeccomp {
if daemon.seccompProfilePath != config.SeccompProfileDefault {
v.Warnings = append(v.Warnings, "WARNING: daemon is not using the default seccomp profile")
}
securityOptions = append(securityOptions, "name=seccomp,profile="+daemon.seccompProfilePath)
}
if selinux.GetEnabled() {
securityOptions = append(securityOptions, "name=selinux")
}
if rootIDs := daemon.idMapping.RootPair(); rootIDs.UID != 0 || rootIDs.GID != 0 {
securityOptions = append(securityOptions, "name=userns")
}
if daemon.Rootless() {
securityOptions = append(securityOptions, "name=rootless")
}
if daemon.cgroupNamespacesEnabled(sysInfo) {
securityOptions = append(securityOptions, "name=cgroupns")
}
v.SecurityOptions = securityOptions
}
func (daemon *Daemon) fillAPIInfo(v *types.Info) {
const warn string = `
Access to the remote API is equivalent to root access on the host. Refer
to the 'Docker daemon attack surface' section in the documentation for
more information: https://docs.docker.com/go/attack-surface/`
cfg := daemon.configStore
for _, host := range cfg.Hosts {
// cnf.Hosts is normalized during startup, so should always have a scheme/proto
h := strings.SplitN(host, "://", 2)
proto := h[0]
addr := h[1]
if proto != "tcp" {
continue
}
if cfg.TLS == nil || !*cfg.TLS {
v.Warnings = append(v.Warnings, fmt.Sprintf("WARNING: API is accessible on http://%s without encryption.%s", addr, warn))
continue
}
if cfg.TLSVerify == nil || !*cfg.TLSVerify {
v.Warnings = append(v.Warnings, fmt.Sprintf("WARNING: API is accessible on https://%s without TLS client verification.%s", addr, warn))
continue
}
}
}
func (daemon *Daemon) fillDefaultAddressPools(v *types.Info) {
for _, pool := range daemon.configStore.DefaultAddressPools.Value() {
v.DefaultAddressPools = append(v.DefaultAddressPools, types.NetworkAddressPool{
Base: pool.Base,
Size: pool.Size,
})
}
}
func hostName() string {
hostname := ""
if hn, err := os.Hostname(); err != nil {
logrus.Warnf("Could not get hostname: %v", err)
} else {
hostname = hn
}
return hostname
}
func kernelVersion() string {
var kernelVersion string
if kv, err := kernel.GetKernelVersion(); err != nil {
logrus.Warnf("Could not get kernel version: %v", err)
} else {
kernelVersion = kv.String()
}
return kernelVersion
}
func memInfo() *system.MemInfo {
memInfo, err := system.ReadMemInfo()
if err != nil {
logrus.Errorf("Could not read system memory info: %v", err)
memInfo = &system.MemInfo{}
}
return memInfo
}
func operatingSystem() (operatingSystem string) {
defer metrics.StartTimer(hostInfoFunctions.WithValues("operating_system"))()
if s, err := operatingsystem.GetOperatingSystem(); err != nil {
logrus.Warnf("Could not get operating system name: %v", err)
} else {
operatingSystem = s
}
// Don't do containerized check on Windows
if runtime.GOOS != "windows" {
if inContainer, err := operatingsystem.IsContainerized(); err != nil {
logrus.Errorf("Could not determine if daemon is containerized: %v", err)
operatingSystem += " (error determining if containerized)"
} else if inContainer {
operatingSystem += " (containerized)"
}
}
return operatingSystem
}
func osVersion() (version string) {
defer metrics.StartTimer(hostInfoFunctions.WithValues("os_version"))()
version, err := operatingsystem.GetOperatingSystemVersion()
if err != nil {
logrus.Warnf("Could not get operating system version: %v", err)
}
return version
}
func getEnvAny(names ...string) string {
for _, n := range names {
if val := os.Getenv(n); val != "" {
return val
}
}
return ""
}
func getConfigOrEnv(config string, env ...string) string {
if config != "" {
return config
}
return getEnvAny(env...)
}