Merge pull request #37684 from thaJeztah/add_remote_api_warning

Add warning if REST API is accessible through an insecure connection
diff --git a/daemon/info.go b/daemon/info.go
index cc9ad8a..9dcfb95 100644
--- a/daemon/info.go
+++ b/daemon/info.go
@@ -68,6 +68,7 @@
 		Isolation:          daemon.defaultIsolation,
 	}
 
+	daemon.fillAPIInfo(v)
 	// Retrieve platform specific info
 	daemon.fillPlatformInfo(v, sysInfo)
 	daemon.fillDriverInfo(v)
@@ -171,6 +172,32 @@
 	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 2a05dfb..b8bdcf0 100644
--- a/integration/system/info_test.go
+++ b/integration/system/info_test.go
@@ -5,6 +5,7 @@
 	"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 @@
 		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))
+	}
+}