Преглед на файлове

Add warning for --dns flag set to localhost addresses.

We should warn users who use the `--dns` command line option to point
DNS to a localhost address, either IPv4 or IPv6.  Unless they have
specifically set up the container as a DNS server or are using
--net=host (which is why this should be allowed, but warned on because
those are pretty unique cases) a localhost address as a resolver will
not reach what they might expect (e.g. expecting it will hit localhost
on the Docker daemon/host).

Added a test for the message, and fixed up tests to separate stdout and
stderr that were using `--dns=127.0.0.1` to test the options.

Docker-DCO-1.1-Signed-off-by: Phil Estes <estesp@linux.vnet.ibm.com> (github: estesp)
Phil Estes преди 10 години
родител
ревизия
afa92a9af0
променени са 3 файла, в които са добавени 35 реда и са изтрити 8 реда
  1. 13 0
      api/client/commands.go
  2. 8 3
      integration-cli/docker_cli_run_test.go
  3. 14 5
      pkg/networkfs/resolvconf/resolvconf.go

+ 13 - 0
api/client/commands.go

@@ -37,6 +37,7 @@ import (
 	"github.com/docker/docker/pkg/fileutils"
 	"github.com/docker/docker/pkg/homedir"
 	flag "github.com/docker/docker/pkg/mflag"
+	"github.com/docker/docker/pkg/networkfs/resolvconf"
 	"github.com/docker/docker/pkg/parsers"
 	"github.com/docker/docker/pkg/parsers/filters"
 	"github.com/docker/docker/pkg/promise"
@@ -2263,6 +2264,18 @@ func (cli *DockerCli) CmdRun(args ...string) error {
 	if err != nil {
 		utils.ReportError(cmd, err.Error(), true)
 	}
+
+	if len(hostConfig.Dns) > 0 {
+		// check the DNS settings passed via --dns against
+		// localhost regexp to warn if they are trying to
+		// set a DNS to a localhost address
+		for _, dnsIP := range hostConfig.Dns {
+			if resolvconf.IsLocalhost(dnsIP) {
+				fmt.Fprintf(cli.err, "WARNING: Localhost DNS setting (--dns=%s) may fail in containers.\n", dnsIP)
+				break
+			}
+		}
+	}
 	if config.Image == "" {
 		cmd.Usage()
 		return nil

+ 8 - 3
integration-cli/docker_cli_run_test.go

@@ -1463,11 +1463,16 @@ func TestRunDnsOptions(t *testing.T) {
 
 	cmd := exec.Command(dockerBinary, "run", "--dns=127.0.0.1", "--dns-search=mydomain", "busybox", "cat", "/etc/resolv.conf")
 
-	out, _, err := runCommandWithOutput(cmd)
+	out, stderr, _, err := runCommandWithStdoutStderr(cmd)
 	if err != nil {
 		t.Fatal(err, out)
 	}
 
+	// The client will get a warning on stderr when setting DNS to a localhost address; verify this:
+	if !strings.Contains(stderr, "Localhost DNS setting") {
+		t.Fatalf("Expected warning on stderr about localhost resolver, but got %q", stderr)
+	}
+
 	actual := strings.Replace(strings.Trim(out, "\r\n"), "\n", " ", -1)
 	if actual != "nameserver 127.0.0.1 search mydomain" {
 		t.Fatalf("expected 'nameserver 127.0.0.1 search mydomain', but says: %q", actual)
@@ -1475,7 +1480,7 @@ func TestRunDnsOptions(t *testing.T) {
 
 	cmd = exec.Command(dockerBinary, "run", "--dns=127.0.0.1", "--dns-search=.", "busybox", "cat", "/etc/resolv.conf")
 
-	out, _, err = runCommandWithOutput(cmd)
+	out, _, _, err = runCommandWithStdoutStderr(cmd)
 	if err != nil {
 		t.Fatal(err, out)
 	}
@@ -1502,7 +1507,7 @@ func TestRunDnsOptionsBasedOnHostResolvConf(t *testing.T) {
 
 	var out string
 	cmd := exec.Command(dockerBinary, "run", "--dns=127.0.0.1", "busybox", "cat", "/etc/resolv.conf")
-	if out, _, err = runCommandWithOutput(cmd); err != nil {
+	if out, _, _, err = runCommandWithStdoutStderr(cmd); err != nil {
 		t.Fatal(err, out)
 	}
 

+ 14 - 5
pkg/networkfs/resolvconf/resolvconf.go

@@ -23,11 +23,13 @@ var (
 	// For readability and sufficiency for Docker purposes this seemed more reasonable than a
 	// 1000+ character regexp with exact and complete IPv6 validation
 	ipv6Address = `([0-9A-Fa-f]{0,4}:){2,7}([0-9A-Fa-f]{0,4})`
+	ipLocalhost = `((127\.([0-9]{1,3}.){2}[0-9]{1,3})|(::1))`
 
-	localhostRegexp = regexp.MustCompile(`(?m)^nameserver\s+((127\.([0-9]{1,3}.){2}[0-9]{1,3})|(::1))\s*\n*`)
-	nsIPv6Regexp    = regexp.MustCompile(`(?m)^nameserver\s+` + ipv6Address + `\s*\n*`)
-	nsRegexp        = regexp.MustCompile(`^\s*nameserver\s*((` + ipv4Address + `)|(` + ipv6Address + `))\s*$`)
-	searchRegexp    = regexp.MustCompile(`^\s*search\s*(([^\s]+\s*)*)$`)
+	localhostIPRegexp = regexp.MustCompile(ipLocalhost)
+	localhostNSRegexp = regexp.MustCompile(`(?m)^nameserver\s+` + ipLocalhost + `\s*\n*`)
+	nsIPv6Regexp      = regexp.MustCompile(`(?m)^nameserver\s+` + ipv6Address + `\s*\n*`)
+	nsRegexp          = regexp.MustCompile(`^\s*nameserver\s*((` + ipv4Address + `)|(` + ipv6Address + `))\s*$`)
+	searchRegexp      = regexp.MustCompile(`^\s*search\s*(([^\s]+\s*)*)$`)
 )
 
 var lastModified struct {
@@ -87,7 +89,7 @@ func GetLastModified() ([]byte, string) {
 // It also returns a boolean to notify the caller if changes were made at all
 func FilterResolvDns(resolvConf []byte, ipv6Enabled bool) ([]byte, bool) {
 	changed := false
-	cleanedResolvConf := localhostRegexp.ReplaceAll(resolvConf, []byte{})
+	cleanedResolvConf := localhostNSRegexp.ReplaceAll(resolvConf, []byte{})
 	// if IPv6 is not enabled, also clean out any IPv6 address nameserver
 	if !ipv6Enabled {
 		cleanedResolvConf = nsIPv6Regexp.ReplaceAll(cleanedResolvConf, []byte{})
@@ -124,6 +126,13 @@ func getLines(input []byte, commentMarker []byte) [][]byte {
 	return output
 }
 
+// returns true if the IP string matches the localhost IP regular expression.
+// Used for determining if nameserver settings are being passed which are
+// localhost addresses
+func IsLocalhost(ip string) bool {
+	return localhostIPRegexp.MatchString(ip)
+}
+
 // GetNameservers returns nameservers (if any) listed in /etc/resolv.conf
 func GetNameservers(resolvConf []byte) []string {
 	nameservers := []string{}