diff --git a/daemon/info.go b/daemon/info.go index cc9ad8ac61..9dcfb95f03 100644 --- a/daemon/info.go +++ b/daemon/info.go @@ -68,6 +68,7 @@ func (daemon *Daemon) SystemInfo() (*types.Info, error) { Isolation: daemon.defaultIsolation, } + daemon.fillAPIInfo(v) // Retrieve platform specific info daemon.fillPlatformInfo(v, sysInfo) daemon.fillDriverInfo(v) @@ -171,6 +172,32 @@ func (daemon *Daemon) fillSecurityOptions(v *types.Info, sysInfo *sysinfo.SysInf 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/engine/security/security/#docker-daemon-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 { + v.Warnings = append(v.Warnings, fmt.Sprintf("WARNING: API is accessible on http://%s without encryption.%s", addr, warn)) + continue + } + if !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 hostName() string { hostname := "" if hn, err := os.Hostname(); err != nil { diff --git a/integration/system/info_test.go b/integration/system/info_test.go index 2a05dfbb74..b8bdcf0049 100644 --- a/integration/system/info_test.go +++ b/integration/system/info_test.go @@ -5,6 +5,7 @@ import ( "fmt" "testing" + "github.com/docker/docker/internal/test/daemon" "github.com/docker/docker/internal/test/request" "gotest.tools/assert" is "gotest.tools/assert/cmp" @@ -40,3 +41,26 @@ func TestInfoAPI(t *testing.T) { assert.Check(t, is.Contains(out, linePrefix)) } } + +func TestInfoAPIWarnings(t *testing.T) { + d := daemon.New(t) + + client, err := d.NewClient() + assert.NilError(t, err) + + d.StartWithBusybox(t, "--iptables=false", "-H=0.0.0.0:23756", "-H=unix://"+d.Sock()) + defer d.Stop(t) + + info, err := client.Info(context.Background()) + assert.NilError(t, err) + + stringsToCheck := []string{ + "Access to the remote API is equivalent to root access", + "http://0.0.0.0:23756", + } + + out := fmt.Sprintf("%+v", info) + for _, linePrefix := range stringsToCheck { + assert.Check(t, is.Contains(out, linePrefix)) + } +}