diff --git a/api/client/cli.go b/api/client/cli.go index fd47fccfa4..35ca93bbc0 100644 --- a/api/client/cli.go +++ b/api/client/cli.go @@ -176,12 +176,7 @@ func getServerHost(hosts []string, tlsOptions *tlsconfig.Options) (host string, return "", errors.New("Please specify only one -H") } - defaultHost := opts.DefaultTCPHost - if tlsOptions != nil { - defaultHost = opts.DefaultTLSHost - } - - host, err = opts.ParseHost(defaultHost, host) + host, err = opts.ParseHost(tlsOptions != nil, host) return } diff --git a/api/server/server_windows.go b/api/server/server_windows.go index 826dd2e008..613d185522 100644 --- a/api/server/server_windows.go +++ b/api/server/server_windows.go @@ -4,8 +4,11 @@ package server import ( "errors" + "fmt" + "github.com/Microsoft/go-winio" "net" "net/http" + "strings" ) // NewServer sets up the required Server and does protocol specific checking. @@ -21,8 +24,26 @@ func (s *Server) newServer(proto, addr string) ([]*HTTPServer, error) { } ls = append(ls, l) + case "npipe": + // allow Administrators and SYSTEM, plus whatever additional users or groups were specified + sddl := "D:P(A;;GA;;;BA)(A;;GA;;;SY)" + if s.cfg.SocketGroup != "" { + for _, g := range strings.Split(s.cfg.SocketGroup, ",") { + sid, err := winio.LookupSidByName(g) + if err != nil { + return nil, err + } + sddl += fmt.Sprintf("(A;;GRGW;;;%s)", sid) + } + } + l, err := winio.ListenPipe(addr, sddl) + if err != nil { + return nil, err + } + ls = append(ls, l) + default: - return nil, errors.New("Invalid protocol format. Windows only supports tcp.") + return nil, errors.New("Invalid protocol format. Windows only supports tcp and npipe.") } var res []*HTTPServer diff --git a/daemon/config.go b/daemon/config.go index c6f232d657..fb46f26667 100644 --- a/daemon/config.go +++ b/daemon/config.go @@ -68,6 +68,7 @@ type CommonConfig struct { Pidfile string `json:"pidfile,omitempty"` RawLogs bool `json:"raw-logs,omitempty"` Root string `json:"graph,omitempty"` + SocketGroup string `json:"group,omitempty"` TrustKeyPath string `json:"-"` // ClusterStore is the storage backend used for the cluster information. It is used by both diff --git a/daemon/config_unix.go b/daemon/config_unix.go index daf236d6bb..044b5d1cea 100644 --- a/daemon/config_unix.go +++ b/daemon/config_unix.go @@ -29,7 +29,6 @@ type Config struct { EnableCors bool `json:"api-enable-cors,omitempty"` EnableSelinuxSupport bool `json:"selinux-enabled,omitempty"` RemappedRoot string `json:"userns-remap,omitempty"` - SocketGroup string `json:"group,omitempty"` CgroupParent string `json:"cgroup-parent,omitempty"` Ulimits map[string]*units.Ulimit `json:"default-ulimits,omitempty"` } diff --git a/daemon/config_windows.go b/daemon/config_windows.go index 9918d45d00..cad0c8719c 100644 --- a/daemon/config_windows.go +++ b/daemon/config_windows.go @@ -38,4 +38,5 @@ func (config *Config) InstallFlags(cmd *flag.FlagSet, usageFn func(string) strin // Then platform-specific install flags. cmd.StringVar(&config.bridgeConfig.VirtualSwitchName, []string{"b", "-bridge"}, "", "Attach containers to a virtual switch") + cmd.StringVar(&config.SocketGroup, []string{"G", "-group"}, "", usageFn("Users or groups that can access the named pipe")) } diff --git a/docker/daemon.go b/docker/daemon.go index 793fd56cc6..821b3f993b 100644 --- a/docker/daemon.go +++ b/docker/daemon.go @@ -200,11 +200,11 @@ func (cli *DaemonCli) CmdDaemon(args ...string) error { serverConfig := &apiserver.Config{ AuthorizationPluginNames: cli.Config.AuthorizationPlugins, Logging: true, + SocketGroup: cli.Config.SocketGroup, Version: dockerversion.Version, } serverConfig = setPlatformServerConfig(serverConfig, cli.Config) - defaultHost := opts.DefaultHost if cli.Config.TLS { tlsOptions := tlsconfig.Options{ CAFile: cli.Config.CommonTLSOptions.CAFile, @@ -221,7 +221,6 @@ func (cli *DaemonCli) CmdDaemon(args ...string) error { logrus.Fatal(err) } serverConfig.TLSConfig = tlsConfig - defaultHost = opts.DefaultTLSHost } if len(cli.Config.Hosts) == 0 { @@ -229,7 +228,7 @@ func (cli *DaemonCli) CmdDaemon(args ...string) error { } for i := 0; i < len(cli.Config.Hosts); i++ { var err error - if cli.Config.Hosts[i], err = opts.ParseHost(defaultHost, cli.Config.Hosts[i]); err != nil { + if cli.Config.Hosts[i], err = opts.ParseHost(cli.Config.TLS, cli.Config.Hosts[i]); err != nil { logrus.Fatalf("error parsing -H %s : %v", cli.Config.Hosts[i], err) } diff --git a/docker/daemon_unix.go b/docker/daemon_unix.go index eba0beef6d..a89bdc73bb 100644 --- a/docker/daemon_unix.go +++ b/docker/daemon_unix.go @@ -19,7 +19,6 @@ import ( const defaultDaemonConfigFile = "/etc/docker/daemon.json" func setPlatformServerConfig(serverConfig *apiserver.Config, daemonCfg *daemon.Config) *apiserver.Config { - serverConfig.SocketGroup = daemonCfg.SocketGroup serverConfig.EnableCors = daemonCfg.EnableCors serverConfig.CorsHeaders = daemonCfg.CorsHeaders diff --git a/opts/hosts.go b/opts/hosts.go index d1b6985415..ad16759236 100644 --- a/opts/hosts.go +++ b/opts/hosts.go @@ -4,16 +4,12 @@ import ( "fmt" "net" "net/url" - "runtime" "strconv" "strings" ) var ( // DefaultHTTPPort Default HTTP Port used if only the protocol is provided to -H flag e.g. docker daemon -H tcp:// - // TODO Windows. DefaultHTTPPort is only used on Windows if a -H parameter - // is not supplied. A better longer term solution would be to use a named - // pipe as the default on the Windows daemon. // These are the IANA registered port numbers for use with Docker // see http://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xhtml?search=docker DefaultHTTPPort = 2375 // Default HTTP Port @@ -26,13 +22,19 @@ var ( DefaultTCPHost = fmt.Sprintf("tcp://%s:%d", DefaultHTTPHost, DefaultHTTPPort) // DefaultTLSHost constant defines the default host string used by docker for TLS sockets DefaultTLSHost = fmt.Sprintf("tcp://%s:%d", DefaultHTTPHost, DefaultTLSHTTPPort) + // DefaultNamedPipe defines the default named pipe used by docker on Windows + DefaultNamedPipe = `//./pipe/docker_engine` ) // ValidateHost validates that the specified string is a valid host and returns it. func ValidateHost(val string) (string, error) { - _, err := parseDockerDaemonHost(DefaultTCPHost, DefaultTLSHost, DefaultUnixSocket, "", val) - if err != nil { - return val, err + host := strings.TrimSpace(val) + // The empty string means default and is not handled by parseDockerDaemonHost + if host != "" { + _, err := parseDockerDaemonHost(host) + if err != nil { + return val, err + } } // Note: unlike most flag validators, we don't return the mutated value here // 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) { } // ParseHost and set defaults for a Daemon host string -func ParseHost(defaultHost, val string) (string, error) { - host, err := parseDockerDaemonHost(DefaultTCPHost, DefaultTLSHost, DefaultUnixSocket, defaultHost, val) - if err != nil { - return val, err +func ParseHost(defaultToTLS bool, val string) (string, error) { + host := strings.TrimSpace(val) + if host == "" { + if defaultToTLS { + host = DefaultTLSHost + } else { + host = DefaultHost + } + } else { + var err error + host, err = parseDockerDaemonHost(host) + if err != nil { + return val, err + } } return host, nil } // parseDockerDaemonHost parses the specified address and returns an address that will be used as the host. -// Depending of the address specified, will use the defaultTCPAddr or defaultUnixAddr -// defaultUnixAddr must be a absolute file path (no `unix://` prefix) -// defaultTCPAddr must be the full `tcp://host:port` form -func parseDockerDaemonHost(defaultTCPAddr, defaultTLSHost, defaultUnixAddr, defaultAddr, addr string) (string, error) { - addr = strings.TrimSpace(addr) - if addr == "" { - if defaultAddr == defaultTLSHost { - return defaultTLSHost, nil - } - if runtime.GOOS != "windows" { - return fmt.Sprintf("unix://%s", defaultUnixAddr), nil - } - return defaultTCPAddr, nil - } +// Depending of the address specified, this may return one of the global Default* strings defined in hosts.go. +func parseDockerDaemonHost(addr string) (string, error) { addrParts := strings.Split(addr, "://") - if len(addrParts) == 1 { + if len(addrParts) == 1 && addrParts[0] != "" { addrParts = []string{"tcp", addrParts[0]} } switch addrParts[0] { case "tcp": - return parseTCPAddr(addrParts[1], defaultTCPAddr) + return parseTCPAddr(addrParts[1], DefaultTCPHost) case "unix": - return parseUnixAddr(addrParts[1], defaultUnixAddr) + return parseSimpleProtoAddr("unix", addrParts[1], DefaultUnixSocket) + case "npipe": + return parseSimpleProtoAddr("npipe", addrParts[1], DefaultNamedPipe) case "fd": return addr, nil default: @@ -80,19 +82,19 @@ func parseDockerDaemonHost(defaultTCPAddr, defaultTLSHost, defaultUnixAddr, defa } } -// parseUnixAddr parses and validates that the specified address is a valid UNIX -// socket address. It returns a formatted UNIX socket address, either using the -// address parsed from addr, or the contents of defaultAddr if addr is a blank -// string. -func parseUnixAddr(addr string, defaultAddr string) (string, error) { - addr = strings.TrimPrefix(addr, "unix://") +// parseSimpleProtoAddr parses and validates that the specified address is a valid +// socket address for simple protocols like unix and npipe. It returns a formatted +// socket address, either using the address parsed from addr, or the contents of +// defaultAddr if addr is a blank string. +func parseSimpleProtoAddr(proto, addr, defaultAddr string) (string, error) { + addr = strings.TrimPrefix(addr, proto+"://") if strings.Contains(addr, "://") { - return "", fmt.Errorf("Invalid proto, expected unix: %s", addr) + return "", fmt.Errorf("Invalid proto, expected %s: %s", proto, addr) } if addr == "" { addr = defaultAddr } - return fmt.Sprintf("unix://%s", addr), nil + return fmt.Sprintf("%s://%s", proto, addr), nil } // parseTCPAddr parses and validates that the specified address is a valid TCP diff --git a/opts/hosts_test.go b/opts/hosts_test.go index e497e28656..8856f95fed 100644 --- a/opts/hosts_test.go +++ b/opts/hosts_test.go @@ -1,7 +1,7 @@ package opts import ( - "runtime" + "fmt" "testing" ) @@ -15,51 +15,41 @@ func TestParseHost(t *testing.T) { "tcp://invalid": "Invalid bind address format: invalid", "tcp://invalid:port": "Invalid bind address format: invalid:port", } - const defaultHTTPHost = "tcp://127.0.0.1:2375" - var defaultHOST = "unix:///var/run/docker.sock" - - if runtime.GOOS == "windows" { - defaultHOST = defaultHTTPHost - } valid := map[string]string{ - "": defaultHOST, + "": DefaultHost, + " ": DefaultHost, + " ": DefaultHost, "fd://": "fd://", "fd://something": "fd://something", - "tcp://host:": "tcp://host:2375", - "tcp://": "tcp://localhost:2375", - "tcp://:2375": "tcp://localhost:2375", // default ip address - "tcp://:2376": "tcp://localhost:2376", // default ip address + "tcp://host:": fmt.Sprintf("tcp://host:%d", DefaultHTTPPort), + "tcp://": DefaultTCPHost, + "tcp://:2375": fmt.Sprintf("tcp://%s:2375", DefaultHTTPHost), + "tcp://:2376": fmt.Sprintf("tcp://%s:2376", DefaultHTTPHost), "tcp://0.0.0.0:8080": "tcp://0.0.0.0:8080", "tcp://192.168.0.0:12000": "tcp://192.168.0.0:12000", "tcp://192.168:8080": "tcp://192.168:8080", "tcp://0.0.0.0:1234567890": "tcp://0.0.0.0:1234567890", // yeah it's valid :P + " tcp://:7777/path ": fmt.Sprintf("tcp://%s:7777/path", DefaultHTTPHost), "tcp://docker.com:2375": "tcp://docker.com:2375", - "unix://": "unix:///var/run/docker.sock", // default unix:// value + "unix://": "unix://" + DefaultUnixSocket, "unix://path/to/socket": "unix://path/to/socket", + "npipe://": "npipe://" + DefaultNamedPipe, + "npipe:////./pipe/foo": "npipe:////./pipe/foo", } for value, errorMessage := range invalid { - if _, err := ParseHost(defaultHTTPHost, value); err == nil || err.Error() != errorMessage { - t.Fatalf("Expected an error for %v with [%v], got [%v]", value, errorMessage, err) + if _, err := ParseHost(false, value); err == nil || err.Error() != errorMessage { + t.Errorf("Expected an error for %v with [%v], got [%v]", value, errorMessage, err) } } for value, expected := range valid { - if actual, err := ParseHost(defaultHTTPHost, value); err != nil || actual != expected { - t.Fatalf("Expected for %v [%v], got [%v, %v]", value, expected, actual, err) + if actual, err := ParseHost(false, value); err != nil || actual != expected { + t.Errorf("Expected for %v [%v], got [%v, %v]", value, expected, actual, err) } } } func TestParseDockerDaemonHost(t *testing.T) { - var ( - defaultHTTPHost = "tcp://localhost:2375" - defaultHTTPSHost = "tcp://localhost:2376" - defaultUnix = "/var/run/docker.sock" - defaultHOST = "unix:///var/run/docker.sock" - ) - if runtime.GOOS == "windows" { - defaultHOST = defaultHTTPHost - } invalids := map[string]string{ "0.0.0.0": "Invalid bind address format: 0.0.0.0", "tcp:a.b.c.d": "Invalid bind address format: tcp:a.b.c.d", @@ -67,9 +57,11 @@ func TestParseDockerDaemonHost(t *testing.T) { "udp://127.0.0.1": "Invalid bind address format: udp://127.0.0.1", "udp://127.0.0.1:2375": "Invalid bind address format: udp://127.0.0.1:2375", "tcp://unix:///run/docker.sock": "Invalid bind address format: unix", - "tcp": "Invalid bind address format: tcp", - "unix": "Invalid bind address format: unix", - "fd": "Invalid bind address format: fd", + " tcp://:7777/path ": "Invalid bind address format: tcp://:7777/path ", + "tcp": "Invalid bind address format: tcp", + "unix": "Invalid bind address format: unix", + "fd": "Invalid bind address format: fd", + "": "Invalid bind address format: ", } valids := map[string]string{ "0.0.0.1:": "tcp://0.0.0.1:2375", @@ -79,17 +71,13 @@ func TestParseDockerDaemonHost(t *testing.T) { "[::1]:5555/path": "tcp://[::1]:5555/path", "[0:0:0:0:0:0:0:1]:": "tcp://[0:0:0:0:0:0:0:1]:2375", "[0:0:0:0:0:0:0:1]:5555/path": "tcp://[0:0:0:0:0:0:0:1]:5555/path", - ":6666": "tcp://localhost:6666", - ":6666/path": "tcp://localhost:6666/path", - "": defaultHOST, - " ": defaultHOST, - " ": defaultHOST, - "tcp://": defaultHTTPHost, - "tcp://:7777": "tcp://localhost:7777", - "tcp://:7777/path": "tcp://localhost:7777/path", - " tcp://:7777/path ": "tcp://localhost:7777/path", + ":6666": fmt.Sprintf("tcp://%s:6666", DefaultHTTPHost), + ":6666/path": fmt.Sprintf("tcp://%s:6666/path", DefaultHTTPHost), + "tcp://": DefaultTCPHost, + "tcp://:7777": fmt.Sprintf("tcp://%s:7777", DefaultHTTPHost), + "tcp://:7777/path": fmt.Sprintf("tcp://%s:7777/path", DefaultHTTPHost), "unix:///run/docker.sock": "unix:///run/docker.sock", - "unix://": "unix:///var/run/docker.sock", + "unix://": "unix://" + DefaultUnixSocket, "fd://": "fd://", "fd://something": "fd://something", "localhost:": "tcp://localhost:2375", @@ -97,12 +85,12 @@ func TestParseDockerDaemonHost(t *testing.T) { "localhost:5555/path": "tcp://localhost:5555/path", } for invalidAddr, expectedError := range invalids { - if addr, err := parseDockerDaemonHost(defaultHTTPHost, defaultHTTPSHost, defaultUnix, "", invalidAddr); err == nil || err.Error() != expectedError { + if addr, err := parseDockerDaemonHost(invalidAddr); err == nil || err.Error() != expectedError { t.Errorf("tcp %v address expected error %v return, got %s and addr %v", invalidAddr, expectedError, err, addr) } } for validAddr, expectedAddr := range valids { - if addr, err := parseDockerDaemonHost(defaultHTTPHost, defaultHTTPSHost, defaultUnix, "", validAddr); err != nil || addr != expectedAddr { + if addr, err := parseDockerDaemonHost(validAddr); err != nil || addr != expectedAddr { t.Errorf("%v -> expected %v, got (%v) addr (%v)", validAddr, expectedAddr, err, addr) } } @@ -152,13 +140,13 @@ func TestParseTCP(t *testing.T) { } func TestParseInvalidUnixAddrInvalid(t *testing.T) { - 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" { + 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" { t.Fatalf("Expected an error, got %v", err) } - 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" { + 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" { t.Fatalf("Expected an error, got %v", err) } - if v, err := parseUnixAddr("", "/var/run/docker.sock"); err != nil || v != "unix:///var/run/docker.sock" { + if v, err := parseSimpleProtoAddr("unix", "", "/var/run/docker.sock"); err != nil || v != "unix:///var/run/docker.sock" { t.Fatalf("Expected an %v, got %v", v, "unix:///var/run/docker.sock") } }