Browse code

Merge pull request #19911 from Microsoft/jstarks/npipe

Windows: Add support for named pipe protocol

Alexander Morozov authored on 2016/02/03 08:59:45
Showing 9 changed files
... ...
@@ -176,12 +176,7 @@ func getServerHost(hosts []string, tlsOptions *tlsconfig.Options) (host string,
176 176
 		return "", errors.New("Please specify only one -H")
177 177
 	}
178 178
 
179
-	defaultHost := opts.DefaultTCPHost
180
-	if tlsOptions != nil {
181
-		defaultHost = opts.DefaultTLSHost
182
-	}
183
-
184
-	host, err = opts.ParseHost(defaultHost, host)
179
+	host, err = opts.ParseHost(tlsOptions != nil, host)
185 180
 	return
186 181
 }
187 182
 
... ...
@@ -4,8 +4,11 @@ package server
4 4
 
5 5
 import (
6 6
 	"errors"
7
+	"fmt"
8
+	"github.com/Microsoft/go-winio"
7 9
 	"net"
8 10
 	"net/http"
11
+	"strings"
9 12
 )
10 13
 
11 14
 // NewServer sets up the required Server and does protocol specific checking.
... ...
@@ -21,8 +24,26 @@ func (s *Server) newServer(proto, addr string) ([]*HTTPServer, error) {
21 21
 		}
22 22
 		ls = append(ls, l)
23 23
 
24
+	case "npipe":
25
+		// allow Administrators and SYSTEM, plus whatever additional users or groups were specified
26
+		sddl := "D:P(A;;GA;;;BA)(A;;GA;;;SY)"
27
+		if s.cfg.SocketGroup != "" {
28
+			for _, g := range strings.Split(s.cfg.SocketGroup, ",") {
29
+				sid, err := winio.LookupSidByName(g)
30
+				if err != nil {
31
+					return nil, err
32
+				}
33
+				sddl += fmt.Sprintf("(A;;GRGW;;;%s)", sid)
34
+			}
35
+		}
36
+		l, err := winio.ListenPipe(addr, sddl)
37
+		if err != nil {
38
+			return nil, err
39
+		}
40
+		ls = append(ls, l)
41
+
24 42
 	default:
25
-		return nil, errors.New("Invalid protocol format. Windows only supports tcp.")
43
+		return nil, errors.New("Invalid protocol format. Windows only supports tcp and npipe.")
26 44
 	}
27 45
 
28 46
 	var res []*HTTPServer
... ...
@@ -68,6 +68,7 @@ type CommonConfig struct {
68 68
 	Pidfile              string              `json:"pidfile,omitempty"`
69 69
 	RawLogs              bool                `json:"raw-logs,omitempty"`
70 70
 	Root                 string              `json:"graph,omitempty"`
71
+	SocketGroup          string              `json:"group,omitempty"`
71 72
 	TrustKeyPath         string              `json:"-"`
72 73
 
73 74
 	// ClusterStore is the storage backend used for the cluster information. It is used by both
... ...
@@ -29,7 +29,6 @@ type Config struct {
29 29
 	EnableCors           bool                     `json:"api-enable-cors,omitempty"`
30 30
 	EnableSelinuxSupport bool                     `json:"selinux-enabled,omitempty"`
31 31
 	RemappedRoot         string                   `json:"userns-remap,omitempty"`
32
-	SocketGroup          string                   `json:"group,omitempty"`
33 32
 	CgroupParent         string                   `json:"cgroup-parent,omitempty"`
34 33
 	Ulimits              map[string]*units.Ulimit `json:"default-ulimits,omitempty"`
35 34
 }
... ...
@@ -38,4 +38,5 @@ func (config *Config) InstallFlags(cmd *flag.FlagSet, usageFn func(string) strin
38 38
 
39 39
 	// Then platform-specific install flags.
40 40
 	cmd.StringVar(&config.bridgeConfig.VirtualSwitchName, []string{"b", "-bridge"}, "", "Attach containers to a virtual switch")
41
+	cmd.StringVar(&config.SocketGroup, []string{"G", "-group"}, "", usageFn("Users or groups that can access the named pipe"))
41 42
 }
... ...
@@ -200,11 +200,11 @@ func (cli *DaemonCli) CmdDaemon(args ...string) error {
200 200
 	serverConfig := &apiserver.Config{
201 201
 		AuthorizationPluginNames: cli.Config.AuthorizationPlugins,
202 202
 		Logging:                  true,
203
+		SocketGroup:              cli.Config.SocketGroup,
203 204
 		Version:                  dockerversion.Version,
204 205
 	}
205 206
 	serverConfig = setPlatformServerConfig(serverConfig, cli.Config)
206 207
 
207
-	defaultHost := opts.DefaultHost
208 208
 	if cli.Config.TLS {
209 209
 		tlsOptions := tlsconfig.Options{
210 210
 			CAFile:   cli.Config.CommonTLSOptions.CAFile,
... ...
@@ -221,7 +221,6 @@ func (cli *DaemonCli) CmdDaemon(args ...string) error {
221 221
 			logrus.Fatal(err)
222 222
 		}
223 223
 		serverConfig.TLSConfig = tlsConfig
224
-		defaultHost = opts.DefaultTLSHost
225 224
 	}
226 225
 
227 226
 	if len(cli.Config.Hosts) == 0 {
... ...
@@ -229,7 +228,7 @@ func (cli *DaemonCli) CmdDaemon(args ...string) error {
229 229
 	}
230 230
 	for i := 0; i < len(cli.Config.Hosts); i++ {
231 231
 		var err error
232
-		if cli.Config.Hosts[i], err = opts.ParseHost(defaultHost, cli.Config.Hosts[i]); err != nil {
232
+		if cli.Config.Hosts[i], err = opts.ParseHost(cli.Config.TLS, cli.Config.Hosts[i]); err != nil {
233 233
 			logrus.Fatalf("error parsing -H %s : %v", cli.Config.Hosts[i], err)
234 234
 		}
235 235
 
... ...
@@ -19,7 +19,6 @@ import (
19 19
 const defaultDaemonConfigFile = "/etc/docker/daemon.json"
20 20
 
21 21
 func setPlatformServerConfig(serverConfig *apiserver.Config, daemonCfg *daemon.Config) *apiserver.Config {
22
-	serverConfig.SocketGroup = daemonCfg.SocketGroup
23 22
 	serverConfig.EnableCors = daemonCfg.EnableCors
24 23
 	serverConfig.CorsHeaders = daemonCfg.CorsHeaders
25 24
 
... ...
@@ -4,16 +4,12 @@ import (
4 4
 	"fmt"
5 5
 	"net"
6 6
 	"net/url"
7
-	"runtime"
8 7
 	"strconv"
9 8
 	"strings"
10 9
 )
11 10
 
12 11
 var (
13 12
 	// DefaultHTTPPort Default HTTP Port used if only the protocol is provided to -H flag e.g. docker daemon -H tcp://
14
-	// TODO Windows. DefaultHTTPPort is only used on Windows if a -H parameter
15
-	// is not supplied. A better longer term solution would be to use a named
16
-	// pipe as the default on the Windows daemon.
17 13
 	// These are the IANA registered port numbers for use with Docker
18 14
 	// see http://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xhtml?search=docker
19 15
 	DefaultHTTPPort = 2375 // Default HTTP Port
... ...
@@ -26,13 +22,19 @@ var (
26 26
 	DefaultTCPHost = fmt.Sprintf("tcp://%s:%d", DefaultHTTPHost, DefaultHTTPPort)
27 27
 	// DefaultTLSHost constant defines the default host string used by docker for TLS sockets
28 28
 	DefaultTLSHost = fmt.Sprintf("tcp://%s:%d", DefaultHTTPHost, DefaultTLSHTTPPort)
29
+	// DefaultNamedPipe defines the default named pipe used by docker on Windows
30
+	DefaultNamedPipe = `//./pipe/docker_engine`
29 31
 )
30 32
 
31 33
 // ValidateHost validates that the specified string is a valid host and returns it.
32 34
 func ValidateHost(val string) (string, error) {
33
-	_, err := parseDockerDaemonHost(DefaultTCPHost, DefaultTLSHost, DefaultUnixSocket, "", val)
34
-	if err != nil {
35
-		return val, err
35
+	host := strings.TrimSpace(val)
36
+	// The empty string means default and is not handled by parseDockerDaemonHost
37
+	if host != "" {
38
+		_, err := parseDockerDaemonHost(host)
39
+		if err != nil {
40
+			return val, err
41
+		}
36 42
 	}
37 43
 	// Note: unlike most flag validators, we don't return the mutated value here
38 44
 	//       we need to know what the user entered later (using ParseHost) to adjust for tls
... ...
@@ -40,39 +42,39 @@ func ValidateHost(val string) (string, error) {
40 40
 }
41 41
 
42 42
 // ParseHost and set defaults for a Daemon host string
43
-func ParseHost(defaultHost, val string) (string, error) {
44
-	host, err := parseDockerDaemonHost(DefaultTCPHost, DefaultTLSHost, DefaultUnixSocket, defaultHost, val)
45
-	if err != nil {
46
-		return val, err
43
+func ParseHost(defaultToTLS bool, val string) (string, error) {
44
+	host := strings.TrimSpace(val)
45
+	if host == "" {
46
+		if defaultToTLS {
47
+			host = DefaultTLSHost
48
+		} else {
49
+			host = DefaultHost
50
+		}
51
+	} else {
52
+		var err error
53
+		host, err = parseDockerDaemonHost(host)
54
+		if err != nil {
55
+			return val, err
56
+		}
47 57
 	}
48 58
 	return host, nil
49 59
 }
50 60
 
51 61
 // parseDockerDaemonHost parses the specified address and returns an address that will be used as the host.
52
-// Depending of the address specified, will use the defaultTCPAddr or defaultUnixAddr
53
-// defaultUnixAddr must be a absolute file path (no `unix://` prefix)
54
-// defaultTCPAddr must be the full `tcp://host:port` form
55
-func parseDockerDaemonHost(defaultTCPAddr, defaultTLSHost, defaultUnixAddr, defaultAddr, addr string) (string, error) {
56
-	addr = strings.TrimSpace(addr)
57
-	if addr == "" {
58
-		if defaultAddr == defaultTLSHost {
59
-			return defaultTLSHost, nil
60
-		}
61
-		if runtime.GOOS != "windows" {
62
-			return fmt.Sprintf("unix://%s", defaultUnixAddr), nil
63
-		}
64
-		return defaultTCPAddr, nil
65
-	}
62
+// Depending of the address specified, this may return one of the global Default* strings defined in hosts.go.
63
+func parseDockerDaemonHost(addr string) (string, error) {
66 64
 	addrParts := strings.Split(addr, "://")
67
-	if len(addrParts) == 1 {
65
+	if len(addrParts) == 1 && addrParts[0] != "" {
68 66
 		addrParts = []string{"tcp", addrParts[0]}
69 67
 	}
70 68
 
71 69
 	switch addrParts[0] {
72 70
 	case "tcp":
73
-		return parseTCPAddr(addrParts[1], defaultTCPAddr)
71
+		return parseTCPAddr(addrParts[1], DefaultTCPHost)
74 72
 	case "unix":
75
-		return parseUnixAddr(addrParts[1], defaultUnixAddr)
73
+		return parseSimpleProtoAddr("unix", addrParts[1], DefaultUnixSocket)
74
+	case "npipe":
75
+		return parseSimpleProtoAddr("npipe", addrParts[1], DefaultNamedPipe)
76 76
 	case "fd":
77 77
 		return addr, nil
78 78
 	default:
... ...
@@ -80,19 +82,19 @@ func parseDockerDaemonHost(defaultTCPAddr, defaultTLSHost, defaultUnixAddr, defa
80 80
 	}
81 81
 }
82 82
 
83
-// parseUnixAddr parses and validates that the specified address is a valid UNIX
84
-// socket address. It returns a formatted UNIX socket address, either using the
85
-// address parsed from addr, or the contents of defaultAddr if addr is a blank
86
-// string.
87
-func parseUnixAddr(addr string, defaultAddr string) (string, error) {
88
-	addr = strings.TrimPrefix(addr, "unix://")
83
+// parseSimpleProtoAddr parses and validates that the specified address is a valid
84
+// socket address for simple protocols like unix and npipe. It returns a formatted
85
+// socket address, either using the address parsed from addr, or the contents of
86
+// defaultAddr if addr is a blank string.
87
+func parseSimpleProtoAddr(proto, addr, defaultAddr string) (string, error) {
88
+	addr = strings.TrimPrefix(addr, proto+"://")
89 89
 	if strings.Contains(addr, "://") {
90
-		return "", fmt.Errorf("Invalid proto, expected unix: %s", addr)
90
+		return "", fmt.Errorf("Invalid proto, expected %s: %s", proto, addr)
91 91
 	}
92 92
 	if addr == "" {
93 93
 		addr = defaultAddr
94 94
 	}
95
-	return fmt.Sprintf("unix://%s", addr), nil
95
+	return fmt.Sprintf("%s://%s", proto, addr), nil
96 96
 }
97 97
 
98 98
 // parseTCPAddr parses and validates that the specified address is a valid TCP
... ...
@@ -1,7 +1,7 @@
1 1
 package opts
2 2
 
3 3
 import (
4
-	"runtime"
4
+	"fmt"
5 5
 	"testing"
6 6
 )
7 7
 
... ...
@@ -15,51 +15,41 @@ func TestParseHost(t *testing.T) {
15 15
 		"tcp://invalid":      "Invalid bind address format: invalid",
16 16
 		"tcp://invalid:port": "Invalid bind address format: invalid:port",
17 17
 	}
18
-	const defaultHTTPHost = "tcp://127.0.0.1:2375"
19
-	var defaultHOST = "unix:///var/run/docker.sock"
20
-
21
-	if runtime.GOOS == "windows" {
22
-		defaultHOST = defaultHTTPHost
23
-	}
24 18
 	valid := map[string]string{
25
-		"":                         defaultHOST,
19
+		"":                         DefaultHost,
20
+		" ":                        DefaultHost,
21
+		"  ":                       DefaultHost,
26 22
 		"fd://":                    "fd://",
27 23
 		"fd://something":           "fd://something",
28
-		"tcp://host:":              "tcp://host:2375",
29
-		"tcp://":                   "tcp://localhost:2375",
30
-		"tcp://:2375":              "tcp://localhost:2375", // default ip address
31
-		"tcp://:2376":              "tcp://localhost:2376", // default ip address
24
+		"tcp://host:":              fmt.Sprintf("tcp://host:%d", DefaultHTTPPort),
25
+		"tcp://":                   DefaultTCPHost,
26
+		"tcp://:2375":              fmt.Sprintf("tcp://%s:2375", DefaultHTTPHost),
27
+		"tcp://:2376":              fmt.Sprintf("tcp://%s:2376", DefaultHTTPHost),
32 28
 		"tcp://0.0.0.0:8080":       "tcp://0.0.0.0:8080",
33 29
 		"tcp://192.168.0.0:12000":  "tcp://192.168.0.0:12000",
34 30
 		"tcp://192.168:8080":       "tcp://192.168:8080",
35 31
 		"tcp://0.0.0.0:1234567890": "tcp://0.0.0.0:1234567890", // yeah it's valid :P
32
+		" tcp://:7777/path ":       fmt.Sprintf("tcp://%s:7777/path", DefaultHTTPHost),
36 33
 		"tcp://docker.com:2375":    "tcp://docker.com:2375",
37
-		"unix://":                  "unix:///var/run/docker.sock", // default unix:// value
34
+		"unix://":                  "unix://" + DefaultUnixSocket,
38 35
 		"unix://path/to/socket":    "unix://path/to/socket",
36
+		"npipe://":                 "npipe://" + DefaultNamedPipe,
37
+		"npipe:////./pipe/foo":     "npipe:////./pipe/foo",
39 38
 	}
40 39
 
41 40
 	for value, errorMessage := range invalid {
42
-		if _, err := ParseHost(defaultHTTPHost, value); err == nil || err.Error() != errorMessage {
43
-			t.Fatalf("Expected an error for %v with [%v], got [%v]", value, errorMessage, err)
41
+		if _, err := ParseHost(false, value); err == nil || err.Error() != errorMessage {
42
+			t.Errorf("Expected an error for %v with [%v], got [%v]", value, errorMessage, err)
44 43
 		}
45 44
 	}
46 45
 	for value, expected := range valid {
47
-		if actual, err := ParseHost(defaultHTTPHost, value); err != nil || actual != expected {
48
-			t.Fatalf("Expected for %v [%v], got [%v, %v]", value, expected, actual, err)
46
+		if actual, err := ParseHost(false, value); err != nil || actual != expected {
47
+			t.Errorf("Expected for %v [%v], got [%v, %v]", value, expected, actual, err)
49 48
 		}
50 49
 	}
51 50
 }
52 51
 
53 52
 func TestParseDockerDaemonHost(t *testing.T) {
54
-	var (
55
-		defaultHTTPHost  = "tcp://localhost:2375"
56
-		defaultHTTPSHost = "tcp://localhost:2376"
57
-		defaultUnix      = "/var/run/docker.sock"
58
-		defaultHOST      = "unix:///var/run/docker.sock"
59
-	)
60
-	if runtime.GOOS == "windows" {
61
-		defaultHOST = defaultHTTPHost
62
-	}
63 53
 	invalids := map[string]string{
64 54
 		"0.0.0.0":                       "Invalid bind address format: 0.0.0.0",
65 55
 		"tcp:a.b.c.d":                   "Invalid bind address format: tcp:a.b.c.d",
... ...
@@ -67,9 +57,11 @@ func TestParseDockerDaemonHost(t *testing.T) {
67 67
 		"udp://127.0.0.1":               "Invalid bind address format: udp://127.0.0.1",
68 68
 		"udp://127.0.0.1:2375":          "Invalid bind address format: udp://127.0.0.1:2375",
69 69
 		"tcp://unix:///run/docker.sock": "Invalid bind address format: unix",
70
-		"tcp":  "Invalid bind address format: tcp",
71
-		"unix": "Invalid bind address format: unix",
72
-		"fd":   "Invalid bind address format: fd",
70
+		" tcp://:7777/path ":            "Invalid bind address format:  tcp://:7777/path ",
71
+		"tcp":                           "Invalid bind address format: tcp",
72
+		"unix":                          "Invalid bind address format: unix",
73
+		"fd":                            "Invalid bind address format: fd",
74
+		"":                              "Invalid bind address format: ",
73 75
 	}
74 76
 	valids := map[string]string{
75 77
 		"0.0.0.1:":                    "tcp://0.0.0.1:2375",
... ...
@@ -79,17 +71,13 @@ func TestParseDockerDaemonHost(t *testing.T) {
79 79
 		"[::1]:5555/path":             "tcp://[::1]:5555/path",
80 80
 		"[0:0:0:0:0:0:0:1]:":          "tcp://[0:0:0:0:0:0:0:1]:2375",
81 81
 		"[0:0:0:0:0:0:0:1]:5555/path": "tcp://[0:0:0:0:0:0:0:1]:5555/path",
82
-		":6666":                   "tcp://localhost:6666",
83
-		":6666/path":              "tcp://localhost:6666/path",
84
-		"":                        defaultHOST,
85
-		" ":                       defaultHOST,
86
-		"  ":                      defaultHOST,
87
-		"tcp://":                  defaultHTTPHost,
88
-		"tcp://:7777":             "tcp://localhost:7777",
89
-		"tcp://:7777/path":        "tcp://localhost:7777/path",
90
-		" tcp://:7777/path ":      "tcp://localhost:7777/path",
82
+		":6666":                   fmt.Sprintf("tcp://%s:6666", DefaultHTTPHost),
83
+		":6666/path":              fmt.Sprintf("tcp://%s:6666/path", DefaultHTTPHost),
84
+		"tcp://":                  DefaultTCPHost,
85
+		"tcp://:7777":             fmt.Sprintf("tcp://%s:7777", DefaultHTTPHost),
86
+		"tcp://:7777/path":        fmt.Sprintf("tcp://%s:7777/path", DefaultHTTPHost),
91 87
 		"unix:///run/docker.sock": "unix:///run/docker.sock",
92
-		"unix://":                 "unix:///var/run/docker.sock",
88
+		"unix://":                 "unix://" + DefaultUnixSocket,
93 89
 		"fd://":                   "fd://",
94 90
 		"fd://something":          "fd://something",
95 91
 		"localhost:":              "tcp://localhost:2375",
... ...
@@ -97,12 +85,12 @@ func TestParseDockerDaemonHost(t *testing.T) {
97 97
 		"localhost:5555/path":     "tcp://localhost:5555/path",
98 98
 	}
99 99
 	for invalidAddr, expectedError := range invalids {
100
-		if addr, err := parseDockerDaemonHost(defaultHTTPHost, defaultHTTPSHost, defaultUnix, "", invalidAddr); err == nil || err.Error() != expectedError {
100
+		if addr, err := parseDockerDaemonHost(invalidAddr); err == nil || err.Error() != expectedError {
101 101
 			t.Errorf("tcp %v address expected error %v return, got %s and addr %v", invalidAddr, expectedError, err, addr)
102 102
 		}
103 103
 	}
104 104
 	for validAddr, expectedAddr := range valids {
105
-		if addr, err := parseDockerDaemonHost(defaultHTTPHost, defaultHTTPSHost, defaultUnix, "", validAddr); err != nil || addr != expectedAddr {
105
+		if addr, err := parseDockerDaemonHost(validAddr); err != nil || addr != expectedAddr {
106 106
 			t.Errorf("%v -> expected %v, got (%v) addr (%v)", validAddr, expectedAddr, err, addr)
107 107
 		}
108 108
 	}
... ...
@@ -152,13 +140,13 @@ func TestParseTCP(t *testing.T) {
152 152
 }
153 153
 
154 154
 func TestParseInvalidUnixAddrInvalid(t *testing.T) {
155
-	if _, err := parseUnixAddr("tcp://127.0.0.1", "unix:///var/run/docker.sock"); err == nil || err.Error() != "Invalid proto, expected unix: tcp://127.0.0.1" {
155
+	if _, err := parseSimpleProtoAddr("unix", "tcp://127.0.0.1", "unix:///var/run/docker.sock"); err == nil || err.Error() != "Invalid proto, expected unix: tcp://127.0.0.1" {
156 156
 		t.Fatalf("Expected an error, got %v", err)
157 157
 	}
158
-	if _, err := parseUnixAddr("unix://tcp://127.0.0.1", "/var/run/docker.sock"); err == nil || err.Error() != "Invalid proto, expected unix: tcp://127.0.0.1" {
158
+	if _, err := parseSimpleProtoAddr("unix", "unix://tcp://127.0.0.1", "/var/run/docker.sock"); err == nil || err.Error() != "Invalid proto, expected unix: tcp://127.0.0.1" {
159 159
 		t.Fatalf("Expected an error, got %v", err)
160 160
 	}
161
-	if v, err := parseUnixAddr("", "/var/run/docker.sock"); err != nil || v != "unix:///var/run/docker.sock" {
161
+	if v, err := parseSimpleProtoAddr("unix", "", "/var/run/docker.sock"); err != nil || v != "unix:///var/run/docker.sock" {
162 162
 		t.Fatalf("Expected an %v, got %v", v, "unix:///var/run/docker.sock")
163 163
 	}
164 164
 }