diff --git a/cmd/skopeo/copy.go b/cmd/skopeo/copy.go index 8939ca67..2baa0ba4 100644 --- a/cmd/skopeo/copy.go +++ b/cmd/skopeo/copy.go @@ -13,12 +13,12 @@ import ( // contextsFromGlobalOptions returns source and destionation types.SystemContext depending on c. func contextsFromGlobalOptions(c *cli.Context) (*types.SystemContext, *types.SystemContext, error) { - sourceCtx, err := contextFromGlobalOptions(c, "src-creds") + sourceCtx, err := contextFromGlobalOptions(c, "src-") if err != nil { return nil, nil, err } - destinationCtx, err := contextFromGlobalOptions(c, "dest-creds") + destinationCtx, err := contextFromGlobalOptions(c, "dest-") if err != nil { return nil, nil, err } @@ -87,5 +87,23 @@ var copyCmd = cli.Command{ Value: "", Usage: "Use `USERNAME[:PASSWORD]` for accessing the destination registry", }, + cli.StringFlag{ + Name: "src-cert-dir", + Value: "", + Usage: "use certificates at `PATH` (*.crt, *.cert, *.key) to connect to the source registry", + }, + cli.BoolTFlag{ + Name: "src-tls-verify", + Usage: "require HTTPS and verify certificates when talking to the docker source registry (defaults to true)", + }, + cli.StringFlag{ + Name: "dest-cert-dir", + Value: "", + Usage: "use certificates at `PATH` (*.crt, *.cert, *.key) to connect to the destination registry", + }, + cli.BoolTFlag{ + Name: "dest-tls-verify", + Usage: "require HTTPS and verify certificates when talking to the docker destination registry (defaults to true)", + }, }, } diff --git a/cmd/skopeo/delete.go b/cmd/skopeo/delete.go index cafe30f4..935365fc 100644 --- a/cmd/skopeo/delete.go +++ b/cmd/skopeo/delete.go @@ -18,7 +18,7 @@ func deleteHandler(context *cli.Context) error { return fmt.Errorf("Invalid source name %s: %v", context.Args()[0], err) } - ctx, err := contextFromGlobalOptions(context, "creds") + ctx, err := contextFromGlobalOptions(context, "") if err != nil { return err } @@ -39,5 +39,14 @@ var deleteCmd = cli.Command{ Value: "", Usage: "Use `USERNAME[:PASSWORD]` for accessing the registry", }, + cli.StringFlag{ + Name: "cert-dir", + Value: "", + Usage: "use certificates at `PATH` (*.crt, *.cert, *.key) to connect to the registry", + }, + cli.BoolTFlag{ + Name: "tls-verify", + Usage: "require HTTPS and verify certificates when talking to docker registries (defaults to true)", + }, }, } diff --git a/cmd/skopeo/inspect.go b/cmd/skopeo/inspect.go index 82f86c01..29adaedb 100644 --- a/cmd/skopeo/inspect.go +++ b/cmd/skopeo/inspect.go @@ -30,6 +30,15 @@ var inspectCmd = cli.Command{ Usage: "Inspect image IMAGE-NAME", ArgsUsage: "IMAGE-NAME", Flags: []cli.Flag{ + cli.StringFlag{ + Name: "cert-path", + Value: "", + Usage: "use certificates at `PATH` (*.crt, *.cert, *.key) to connect to the registry", + }, + cli.BoolTFlag{ + Name: "tls-verify", + Usage: "require HTTPS and verify certificates when talking to docker registries (defaults to true)", + }, cli.BoolFlag{ Name: "raw", Usage: "output raw manifest", diff --git a/cmd/skopeo/layers.go b/cmd/skopeo/layers.go index 07cd093c..af535404 100644 --- a/cmd/skopeo/layers.go +++ b/cmd/skopeo/layers.go @@ -19,6 +19,7 @@ var layersCmd = cli.Command{ Name: "layers", Usage: "Get layers of IMAGE-NAME", ArgsUsage: "IMAGE-NAME [LAYER...]", + Hidden: true, Action: func(c *cli.Context) error { fmt.Fprintln(os.Stderr, `DEPRECATED: skopeo layers is deprecated in favor of skopeo copy`) if c.NArg() == 0 { diff --git a/cmd/skopeo/main.go b/cmd/skopeo/main.go index 4953c818..56d39c16 100644 --- a/cmd/skopeo/main.go +++ b/cmd/skopeo/main.go @@ -30,14 +30,10 @@ func createApp() *cli.App { Name: "debug", Usage: "enable debug output", }, - cli.StringFlag{ - Name: "cert-path", - Value: "", - Usage: "use certificates at `PATH` (cert.pem, key.pem) to connect to the registry", - }, cli.BoolTFlag{ - Name: "tls-verify", - Usage: "require HTTPS and verify certificates when talking to docker registries (defaults to true)", + Name: "tls-verify", + Usage: "require HTTPS and verify certificates when talking to docker registries (defaults to true)", + Hidden: true, }, cli.StringFlag{ Name: "policy", @@ -54,6 +50,9 @@ func createApp() *cli.App { if c.GlobalBool("debug") { logrus.SetLevel(logrus.DebugLevel) } + if c.GlobalIsSet("tls-verify") { + logrus.Warn("'--tls-verify' is deprecated, please set this on the specific subcommand") + } return nil } app.Commands = []cli.Command{ diff --git a/cmd/skopeo/utils.go b/cmd/skopeo/utils.go index e349402b..d17efb01 100644 --- a/cmd/skopeo/utils.go +++ b/cmd/skopeo/utils.go @@ -9,15 +9,20 @@ import ( "github.com/urfave/cli" ) -func contextFromGlobalOptions(c *cli.Context, credsFlag string) (*types.SystemContext, error) { +func contextFromGlobalOptions(c *cli.Context, flagPrefix string) (*types.SystemContext, error) { ctx := &types.SystemContext{ - RegistriesDirPath: c.GlobalString("registries.d"), - DockerCertPath: c.GlobalString("cert-path"), + RegistriesDirPath: c.GlobalString("registries.d"), + DockerCertPath: c.String(flagPrefix + "cert-dir"), + // DEPRECATED: keep this here for backward compatibility, but override + // them if per subcommand flags are provided (see below). DockerInsecureSkipTLSVerify: !c.GlobalBoolT("tls-verify"), } - if c.IsSet(credsFlag) { + if c.IsSet(flagPrefix + "tls-verify") { + ctx.DockerInsecureSkipTLSVerify = !c.BoolT(flagPrefix + "tls-verify") + } + if c.IsSet(flagPrefix + "creds") { var err error - ctx.DockerAuthConfig, err = getDockerAuth(c.String(credsFlag)) + ctx.DockerAuthConfig, err = getDockerAuth(c.String(flagPrefix + "creds")) if err != nil { return nil, err } @@ -58,7 +63,7 @@ func parseImage(c *cli.Context) (types.Image, error) { if err != nil { return nil, err } - ctx, err := contextFromGlobalOptions(c, "creds") + ctx, err := contextFromGlobalOptions(c, "") if err != nil { return nil, err } @@ -73,7 +78,7 @@ func parseImageSource(c *cli.Context, name string, requestedManifestMIMETypes [] if err != nil { return nil, err } - ctx, err := contextFromGlobalOptions(c, "creds") + ctx, err := contextFromGlobalOptions(c, "") if err != nil { return nil, err } diff --git a/completions/bash/skopeo b/completions/bash/skopeo index fe3b81d0..ef662c2e 100644 --- a/completions/bash/skopeo +++ b/completions/bash/skopeo @@ -23,7 +23,11 @@ _skopeo_copy() { local options_with_args=" --sign-by --src-creds --screds + --src-cert-path + --src-tls-verify --dest-creds --dcreds + --dest-cert-path + --dest-tls-verify " local boolean_options=" --remove-signatures @@ -34,9 +38,11 @@ _skopeo_copy() { _skopeo_inspect() { local options_with_args=" --creds + --cert-path " local boolean_options=" --raw + --tls-verify " _complete_ "$options_with_args" "$boolean_options" } @@ -68,30 +74,33 @@ _skopeo_manifest_digest() { _skopeo_delete() { local options_with_args=" - --creds + --creds + --cert-path " local boolean_options=" + --tls-verify " _complete_ "$options_with_args" "$boolean_options" } _skopeo_layers() { local options_with_args=" + --creds + --cert-path " local boolean_options=" + --tls-verify " _complete_ "$options_with_args" "$boolean_options" } _skopeo_skopeo() { local options_with_args=" - --cert-path --policy --registries.d " local boolean_options=" --debug - --tls-verify --version -v --help -h " diff --git a/docs/skopeo.1.md b/docs/skopeo.1.md index f049485a..4c6f03d9 100644 --- a/docs/skopeo.1.md +++ b/docs/skopeo.1.md @@ -37,14 +37,10 @@ Most commands refer to container images, using a _transport_`:`_details_ format. **--debug** enable debug output - **--cert-path** _path_ Use certificates at _path_ (cert.pem, key.pem) to connect to the registry - **--policy** _path-to-policy_ Path to a policy.json file to use for verifying signatures and deciding whether an image is trusted, overriding the default trust policy file. **--registries.d** _dir_ use registry configuration files in _dir_ (e.g. for docker signature storage), overriding the default path. - **--tls-verify** _bool-value_ Require HTTPS and verify certificates when talking to docker registries (defaults to true) - **--help**|**-h** Show help **--version**|**-v** print the version number @@ -70,6 +66,14 @@ Uses the system's trust policy to validate images, rejects images not trusted by **--dest-creds** _username[:password]_ for accessing the destination registry + **--src-cert-dir** _path_ Use certificates at _path_ (*.crt, *.cert, *.key) to connect to the source registry + + **--src-tls-verify** _bool-value_ Require HTTPS and verify certificates when talking to docker source registry (defaults to true) + + **--dest-cert-dir** _path_ Use certificates at _path_ (*.crt, *.cert, *.key) to connect to the destination registry + + **--dest-tls-verify** _bool-value_ Require HTTPS and verify certificates when talking to docker destination registry (defaults to true) + Existing signatures, if any, are preserved as well. ## skopeo delete @@ -83,6 +87,10 @@ $ docker exec -it registry bin/registry garbage-collect /etc/docker/registry/con **--creds** _username[:password]_ for accessing the registry + **--cert-dir** _path_ Use certificates at _path_ (*.crt, *.cert, *.key) to connect to the registry + + **--tls-verify** _bool-value_ Require HTTPS and verify certificates when talking to docker registries (defaults to true) + Additionally, the registry must allow deletions by setting `REGISTRY_STORAGE_DELETE_ENABLED=true` for the registry daemon. ## skopeo inspect @@ -96,12 +104,9 @@ Return low-level information about _image-name_ in a registry **--creds** _username[:password]_ for accessing the registry -## skopeo layers -**skopeo layers** _image-name_ + **--cert-dir** _path_ Use certificates at _path_ (*.crt, *.cert, *.key) to connect to the registry -Get image layers of _image-name_ - - _image-name_ name of the image to retrieve layers + **--tls-verify** _bool-value_ Require HTTPS and verify certificates when talking to docker registries (defaults to true) ## skopeo manifest-digest **skopeo manifest-digest** _manifest-file_ diff --git a/hack/vendor.sh b/hack/vendor.sh index d3feacce..b2f3dc8f 100755 --- a/hack/vendor.sh +++ b/hack/vendor.sh @@ -16,7 +16,7 @@ clone git github.com/pmezard/go-difflib master # docker deps from https://github.com/docker/docker/blob/v1.11.2/hack/vendor.sh clone git github.com/docker/docker v1.12.1 clone git github.com/docker/engine-api 4eca04ae18f4f93f40196a17b9aa6e11262a7269 -clone git github.com/docker/go-connections v0.2.0 +clone git github.com/docker/go-connections 4ccf312bf1d35e5dbda654e57a9be4c3f3cd0366 clone git github.com/vbatts/tar-split v0.9.11 clone git github.com/gorilla/context 14f550f51a clone git github.com/gorilla/mux e444e69cbd diff --git a/vendor/github.com/containers/image/docker/docker_client.go b/vendor/github.com/containers/image/docker/docker_client.go index 3cef69c0..48231d69 100644 --- a/vendor/github.com/containers/image/docker/docker_client.go +++ b/vendor/github.com/containers/image/docker/docker_client.go @@ -87,6 +87,64 @@ func newTransport() *http.Transport { return tr } +func setupCertificates(dir string, tlsc *tls.Config) error { + if dir == "" { + return nil + } + fs, err := ioutil.ReadDir(dir) + if err != nil && !os.IsNotExist(err) { + return err + } + + for _, f := range fs { + fullPath := filepath.Join(dir, f.Name()) + if strings.HasSuffix(f.Name(), ".crt") { + systemPool, err := tlsconfig.SystemCertPool() + if err != nil { + return fmt.Errorf("unable to get system cert pool: %v", err) + } + tlsc.RootCAs = systemPool + logrus.Debugf("crt: %s", fullPath) + data, err := ioutil.ReadFile(fullPath) + if err != nil { + return err + } + tlsc.RootCAs.AppendCertsFromPEM(data) + } + if strings.HasSuffix(f.Name(), ".cert") { + certName := f.Name() + keyName := certName[:len(certName)-5] + ".key" + logrus.Debugf("cert: %s", fullPath) + if !hasFile(fs, keyName) { + return fmt.Errorf("missing key %s for client certificate %s. Note that CA certificates should use the extension .crt", keyName, certName) + } + cert, err := tls.LoadX509KeyPair(filepath.Join(dir, certName), filepath.Join(dir, keyName)) + if err != nil { + return err + } + tlsc.Certificates = append(tlsc.Certificates, cert) + } + if strings.HasSuffix(f.Name(), ".key") { + keyName := f.Name() + certName := keyName[:len(keyName)-4] + ".cert" + logrus.Debugf("key: %s", fullPath) + if !hasFile(fs, certName) { + return fmt.Errorf("missing client certificate %s for key %s", certName, keyName) + } + } + } + return nil +} + +func hasFile(files []os.FileInfo, name string) bool { + for _, f := range files { + if f.Name() == name { + return true + } + } + return false +} + // newDockerClient returns a new dockerClient instance for refHostname (a host a specified in the Docker image reference, not canonicalized to dockerRegistry) // “write” specifies whether the client will be used for "write" access (in particular passed to lookaside.go:toplevelFromSection) func newDockerClient(ctx *types.SystemContext, ref dockerReference, write bool) (*dockerClient, error) { @@ -102,13 +160,10 @@ func newDockerClient(ctx *types.SystemContext, ref dockerReference, write bool) if ctx != nil && (ctx.DockerCertPath != "" || ctx.DockerInsecureSkipTLSVerify) { tlsc := &tls.Config{} - if ctx.DockerCertPath != "" { - cert, err := tls.LoadX509KeyPair(filepath.Join(ctx.DockerCertPath, "cert.pem"), filepath.Join(ctx.DockerCertPath, "key.pem")) - if err != nil { - return nil, fmt.Errorf("Error loading x509 key pair: %s", err) - } - tlsc.Certificates = append(tlsc.Certificates, cert) + if err := setupCertificates(ctx.DockerCertPath, tlsc); err != nil { + return nil, err } + tlsc.InsecureSkipVerify = ctx.DockerInsecureSkipTLSVerify tr.TLSClientConfig = tlsc } diff --git a/vendor/github.com/containers/image/types/types.go b/vendor/github.com/containers/image/types/types.go index 18e0e236..9fd1de5e 100644 --- a/vendor/github.com/containers/image/types/types.go +++ b/vendor/github.com/containers/image/types/types.go @@ -270,8 +270,11 @@ type SystemContext struct { RegistriesDirPath string // === docker.Transport overrides === - DockerCertPath string // If not "", a directory containing "cert.pem" and "key.pem" used when talking to a Docker Registry - DockerInsecureSkipTLSVerify bool // Allow contacting docker registries over HTTP, or HTTPS with failed TLS verification. Note that this does not affect other TLS connections. + // If not "", a directory containing a CA certificate (ending with ".crt"), + // a client certificate (ending with ".cert") and a client ceritificate key + // (ending with ".key") used when talking to a Docker Registry. + DockerCertPath string + DockerInsecureSkipTLSVerify bool // Allow contacting docker registries over HTTP, or HTTPS with failed TLS verification. Note that this does not affect other TLS connections. // if nil, the library tries to parse ~/.docker/config.json to retrieve credentials DockerAuthConfig *DockerAuthConfig // if not "", an User-Agent header is added to each request when contacting a registry. diff --git a/vendor/github.com/docker/go-connections/nat/nat.go b/vendor/github.com/docker/go-connections/nat/nat.go index 3d469165..4d5f5ae6 100644 --- a/vendor/github.com/docker/go-connections/nat/nat.go +++ b/vendor/github.com/docker/go-connections/nat/nat.go @@ -85,14 +85,10 @@ func (p Port) Port() string { // Int returns the port number of a Port as an int func (p Port) Int() int { portStr := p.Port() - if len(portStr) == 0 { - return 0 - } - // We don't need to check for an error because we're going to // assume that any error would have been found, and reported, in NewPort() - port, _ := strconv.ParseUint(portStr, 10, 16) - return int(port) + port, _ := ParsePort(portStr) + return port } // Range returns the start/end port numbers of a Port range as ints @@ -132,92 +128,115 @@ func ParsePortSpecs(ports []string) (map[Port]struct{}, map[Port][]PortBinding, exposedPorts = make(map[Port]struct{}, len(ports)) bindings = make(map[Port][]PortBinding) ) - for _, rawPort := range ports { - proto := "tcp" - - if i := strings.LastIndex(rawPort, "/"); i != -1 { - proto = rawPort[i+1:] - rawPort = rawPort[:i] - } - if !strings.Contains(rawPort, ":") { - rawPort = fmt.Sprintf("::%s", rawPort) - } else if len(strings.Split(rawPort, ":")) == 2 { - rawPort = fmt.Sprintf(":%s", rawPort) - } - - parts, err := PartParser(portSpecTemplate, rawPort) + portMappings, err := ParsePortSpec(rawPort) if err != nil { return nil, nil, err } - var ( - containerPort = parts["containerPort"] - rawIP = parts["ip"] - hostPort = parts["hostPort"] - ) - - if rawIP != "" && net.ParseIP(rawIP) == nil { - return nil, nil, fmt.Errorf("Invalid ip address: %s", rawIP) - } - if containerPort == "" { - return nil, nil, fmt.Errorf("No port specified: %s", rawPort) - } - - startPort, endPort, err := ParsePortRange(containerPort) - if err != nil { - return nil, nil, fmt.Errorf("Invalid containerPort: %s", containerPort) - } - - var startHostPort, endHostPort uint64 = 0, 0 - if len(hostPort) > 0 { - startHostPort, endHostPort, err = ParsePortRange(hostPort) - if err != nil { - return nil, nil, fmt.Errorf("Invalid hostPort: %s", hostPort) - } - } - - if hostPort != "" && (endPort-startPort) != (endHostPort-startHostPort) { - // Allow host port range iff containerPort is not a range. - // In this case, use the host port range as the dynamic - // host port range to allocate into. - if endPort != startPort { - return nil, nil, fmt.Errorf("Invalid ranges specified for container and host Ports: %s and %s", containerPort, hostPort) - } - } - - if !validateProto(strings.ToLower(proto)) { - return nil, nil, fmt.Errorf("Invalid proto: %s", proto) - } - - for i := uint64(0); i <= (endPort - startPort); i++ { - containerPort = strconv.FormatUint(startPort+i, 10) - if len(hostPort) > 0 { - hostPort = strconv.FormatUint(startHostPort+i, 10) - } - // Set hostPort to a range only if there is a single container port - // and a dynamic host port. - if startPort == endPort && startHostPort != endHostPort { - hostPort = fmt.Sprintf("%s-%s", hostPort, strconv.FormatUint(endHostPort, 10)) - } - port, err := NewPort(strings.ToLower(proto), containerPort) - if err != nil { - return nil, nil, err - } + for _, portMapping := range portMappings { + port := portMapping.Port if _, exists := exposedPorts[port]; !exists { exposedPorts[port] = struct{}{} } - - binding := PortBinding{ - HostIP: rawIP, - HostPort: hostPort, - } bslice, exists := bindings[port] if !exists { bslice = []PortBinding{} } - bindings[port] = append(bslice, binding) + bindings[port] = append(bslice, portMapping.Binding) } } return exposedPorts, bindings, nil } + +// PortMapping is a data object mapping a Port to a PortBinding +type PortMapping struct { + Port Port + Binding PortBinding +} + +func splitParts(rawport string) (string, string, string) { + parts := strings.Split(rawport, ":") + n := len(parts) + containerport := parts[n-1] + + switch n { + case 1: + return "", "", containerport + case 2: + return "", parts[0], containerport + case 3: + return parts[0], parts[1], containerport + default: + return strings.Join(parts[:n-2], ":"), parts[n-2], containerport + } +} + +// ParsePortSpec parses a port specification string into a slice of PortMappings +func ParsePortSpec(rawPort string) ([]PortMapping, error) { + var proto string + rawIP, hostPort, containerPort := splitParts(rawPort) + proto, containerPort = SplitProtoPort(containerPort) + + // Strip [] from IPV6 addresses + ip, _, err := net.SplitHostPort(rawIP + ":") + if err != nil { + return nil, fmt.Errorf("Invalid ip address %v: %s", rawIP, err) + } + if ip != "" && net.ParseIP(ip) == nil { + return nil, fmt.Errorf("Invalid ip address: %s", ip) + } + if containerPort == "" { + return nil, fmt.Errorf("No port specified: %s", rawPort) + } + + startPort, endPort, err := ParsePortRange(containerPort) + if err != nil { + return nil, fmt.Errorf("Invalid containerPort: %s", containerPort) + } + + var startHostPort, endHostPort uint64 = 0, 0 + if len(hostPort) > 0 { + startHostPort, endHostPort, err = ParsePortRange(hostPort) + if err != nil { + return nil, fmt.Errorf("Invalid hostPort: %s", hostPort) + } + } + + if hostPort != "" && (endPort-startPort) != (endHostPort-startHostPort) { + // Allow host port range iff containerPort is not a range. + // In this case, use the host port range as the dynamic + // host port range to allocate into. + if endPort != startPort { + return nil, fmt.Errorf("Invalid ranges specified for container and host Ports: %s and %s", containerPort, hostPort) + } + } + + if !validateProto(strings.ToLower(proto)) { + return nil, fmt.Errorf("Invalid proto: %s", proto) + } + + ports := []PortMapping{} + for i := uint64(0); i <= (endPort - startPort); i++ { + containerPort = strconv.FormatUint(startPort+i, 10) + if len(hostPort) > 0 { + hostPort = strconv.FormatUint(startHostPort+i, 10) + } + // Set hostPort to a range only if there is a single container port + // and a dynamic host port. + if startPort == endPort && startHostPort != endHostPort { + hostPort = fmt.Sprintf("%s-%s", hostPort, strconv.FormatUint(endHostPort, 10)) + } + port, err := NewPort(strings.ToLower(proto), containerPort) + if err != nil { + return nil, err + } + + binding := PortBinding{ + HostIP: ip, + HostPort: hostPort, + } + ports = append(ports, PortMapping{Port: port, Binding: binding}) + } + return ports, nil +} diff --git a/vendor/github.com/docker/go-connections/nat/parse.go b/vendor/github.com/docker/go-connections/nat/parse.go index 87205020..892adf8c 100644 --- a/vendor/github.com/docker/go-connections/nat/parse.go +++ b/vendor/github.com/docker/go-connections/nat/parse.go @@ -8,6 +8,7 @@ import ( // PartParser parses and validates the specified string (data) using the specified template // e.g. ip:public:private -> 192.168.0.1:80:8000 +// DEPRECATED: do not use, this function may be removed in a future version func PartParser(template, data string) (map[string]string, error) { // ip:public:private var ( diff --git a/vendor/github.com/docker/go-connections/sockets/inmem_socket.go b/vendor/github.com/docker/go-connections/sockets/inmem_socket.go index 3395e402..99846ffd 100644 --- a/vendor/github.com/docker/go-connections/sockets/inmem_socket.go +++ b/vendor/github.com/docker/go-connections/sockets/inmem_socket.go @@ -79,11 +79,3 @@ func (a dummyAddr) Network() string { func (a dummyAddr) String() string { return string(a) } - -// timeoutError is used when there is a timeout with a connection -// this implements the net.Error interface -type timeoutError struct{} - -func (e *timeoutError) Error() string { return "i/o timeout" } -func (e *timeoutError) Timeout() bool { return true } -func (e *timeoutError) Temporary() bool { return true } diff --git a/vendor/github.com/docker/go-connections/sockets/sockets.go b/vendor/github.com/docker/go-connections/sockets/sockets.go index 1739cecf..a1d7beb4 100644 --- a/vendor/github.com/docker/go-connections/sockets/sockets.go +++ b/vendor/github.com/docker/go-connections/sockets/sockets.go @@ -2,6 +2,7 @@ package sockets import ( + "errors" "net" "net/http" "time" @@ -10,6 +11,9 @@ import ( // Why 32? See https://github.com/docker/docker/pull/8035. const defaultTimeout = 32 * time.Second +// ErrProtocolNotAvailable is returned when a given transport protocol is not provided by the operating system. +var ErrProtocolNotAvailable = errors.New("protocol not available") + // ConfigureTransport configures the specified Transport according to the // specified proto and addr. // If the proto is unix (using a unix socket to communicate) or npipe the @@ -17,17 +21,9 @@ const defaultTimeout = 32 * time.Second func ConfigureTransport(tr *http.Transport, proto, addr string) error { switch proto { case "unix": - // No need for compression in local communications. - tr.DisableCompression = true - tr.Dial = func(_, _ string) (net.Conn, error) { - return net.DialTimeout(proto, addr, defaultTimeout) - } + return configureUnixTransport(tr, proto, addr) case "npipe": - // No need for compression in local communications. - tr.DisableCompression = true - tr.Dial = func(_, _ string) (net.Conn, error) { - return DialPipe(addr, defaultTimeout) - } + return configureNpipeTransport(tr, proto, addr) default: tr.Proxy = http.ProxyFromEnvironment dialer, err := DialerFromEnvironment(&net.Dialer{ diff --git a/vendor/github.com/docker/go-connections/sockets/sockets_unix.go b/vendor/github.com/docker/go-connections/sockets/sockets_unix.go index b255ac9a..386cf0db 100644 --- a/vendor/github.com/docker/go-connections/sockets/sockets_unix.go +++ b/vendor/github.com/docker/go-connections/sockets/sockets_unix.go @@ -3,11 +3,31 @@ package sockets import ( + "fmt" "net" + "net/http" "syscall" "time" ) +const maxUnixSocketPathSize = len(syscall.RawSockaddrUnix{}.Path) + +func configureUnixTransport(tr *http.Transport, proto, addr string) error { + if len(addr) > maxUnixSocketPathSize { + return fmt.Errorf("Unix socket path %q is too long", addr) + } + // No need for compression in local communications. + tr.DisableCompression = true + tr.Dial = func(_, _ string) (net.Conn, error) { + return net.DialTimeout(proto, addr, defaultTimeout) + } + return nil +} + +func configureNpipeTransport(tr *http.Transport, proto, addr string) error { + return ErrProtocolNotAvailable +} + // DialPipe connects to a Windows named pipe. // This is not supported on other OSes. func DialPipe(_ string, _ time.Duration) (net.Conn, error) { diff --git a/vendor/github.com/docker/go-connections/sockets/sockets_windows.go b/vendor/github.com/docker/go-connections/sockets/sockets_windows.go index 1f3540b2..5c21644e 100644 --- a/vendor/github.com/docker/go-connections/sockets/sockets_windows.go +++ b/vendor/github.com/docker/go-connections/sockets/sockets_windows.go @@ -2,11 +2,25 @@ package sockets import ( "net" + "net/http" "time" "github.com/Microsoft/go-winio" ) +func configureUnixTransport(tr *http.Transport, proto, addr string) error { + return ErrProtocolNotAvailable +} + +func configureNpipeTransport(tr *http.Transport, proto, addr string) error { + // No need for compression in local communications. + tr.DisableCompression = true + tr.Dial = func(_, _ string) (net.Conn, error) { + return DialPipe(addr, defaultTimeout) + } + return nil +} + // DialPipe connects to a Windows named pipe. func DialPipe(addr string, timeout time.Duration) (net.Conn, error) { return winio.DialPipe(addr, &timeout) diff --git a/vendor/github.com/docker/go-connections/sockets/tcp_socket.go b/vendor/github.com/docker/go-connections/sockets/tcp_socket.go index 8a82727d..53cbb6c7 100644 --- a/vendor/github.com/docker/go-connections/sockets/tcp_socket.go +++ b/vendor/github.com/docker/go-connections/sockets/tcp_socket.go @@ -7,7 +7,7 @@ import ( ) // NewTCPSocket creates a TCP socket listener with the specified address and -// and the specified tls configuration. If TLSConfig is set, will encapsulate the +// the specified tls configuration. If TLSConfig is set, will encapsulate the // TCP listener inside a TLS one. func NewTCPSocket(addr string, tlsConfig *tls.Config) (net.Listener, error) { l, err := net.Listen("tcp", addr) diff --git a/vendor/github.com/docker/go-connections/tlsconfig/certpool_go17.go b/vendor/github.com/docker/go-connections/tlsconfig/certpool_go17.go new file mode 100644 index 00000000..352d342a --- /dev/null +++ b/vendor/github.com/docker/go-connections/tlsconfig/certpool_go17.go @@ -0,0 +1,21 @@ +// +build go1.7 + +package tlsconfig + +import ( + "crypto/x509" + "runtime" + + "github.com/Sirupsen/logrus" +) + +// SystemCertPool returns a copy of the system cert pool, +// returns an error if failed to load or empty pool on windows. +func SystemCertPool() (*x509.CertPool, error) { + certpool, err := x509.SystemCertPool() + if err != nil && runtime.GOOS == "windows" { + logrus.Warnf("Unable to use system certificate pool: %v", err) + return x509.NewCertPool(), nil + } + return certpool, err +} diff --git a/vendor/github.com/docker/go-connections/tlsconfig/certpool_other.go b/vendor/github.com/docker/go-connections/tlsconfig/certpool_other.go new file mode 100644 index 00000000..262c95e8 --- /dev/null +++ b/vendor/github.com/docker/go-connections/tlsconfig/certpool_other.go @@ -0,0 +1,16 @@ +// +build !go1.7 + +package tlsconfig + +import ( + "crypto/x509" + + "github.com/Sirupsen/logrus" +) + +// SystemCertPool returns an new empty cert pool, +// accessing system cert pool is supported in go 1.7 +func SystemCertPool() (*x509.CertPool, error) { + logrus.Warn("Unable to use system certificate pool: requires building with go 1.7 or later") + return x509.NewCertPool(), nil +} diff --git a/vendor/github.com/docker/go-connections/tlsconfig/config.go b/vendor/github.com/docker/go-connections/tlsconfig/config.go index 9378c358..8bbffcfd 100644 --- a/vendor/github.com/docker/go-connections/tlsconfig/config.go +++ b/vendor/github.com/docker/go-connections/tlsconfig/config.go @@ -46,44 +46,46 @@ var acceptedCBCCiphers = []uint16{ // known weak algorithms removed. var DefaultServerAcceptedCiphers = append(clientCipherSuites, acceptedCBCCiphers...) -// ServerDefault is a secure-enough TLS configuration for the server TLS configuration. -var ServerDefault = tls.Config{ - // Avoid fallback to SSL protocols < TLS1.0 - MinVersion: tls.VersionTLS10, - PreferServerCipherSuites: true, - CipherSuites: DefaultServerAcceptedCiphers, +// ServerDefault returns a secure-enough TLS configuration for the server TLS configuration. +func ServerDefault() *tls.Config { + return &tls.Config{ + // Avoid fallback to SSL protocols < TLS1.0 + MinVersion: tls.VersionTLS10, + PreferServerCipherSuites: true, + CipherSuites: DefaultServerAcceptedCiphers, + } } -// ClientDefault is a secure-enough TLS configuration for the client TLS configuration. -var ClientDefault = tls.Config{ - // Prefer TLS1.2 as the client minimum - MinVersion: tls.VersionTLS12, - CipherSuites: clientCipherSuites, +// ClientDefault returns a secure-enough TLS configuration for the client TLS configuration. +func ClientDefault() *tls.Config { + return &tls.Config{ + // Prefer TLS1.2 as the client minimum + MinVersion: tls.VersionTLS12, + CipherSuites: clientCipherSuites, + } } // certPool returns an X.509 certificate pool from `caFile`, the certificate file. func certPool(caFile string) (*x509.CertPool, error) { // If we should verify the server, we need to load a trusted ca - certPool := x509.NewCertPool() + certPool, err := SystemCertPool() + if err != nil { + return nil, fmt.Errorf("failed to read system certificates: %v", err) + } pem, err := ioutil.ReadFile(caFile) if err != nil { - return nil, fmt.Errorf("Could not read CA certificate %q: %v", caFile, err) + return nil, fmt.Errorf("could not read CA certificate %q: %v", caFile, err) } if !certPool.AppendCertsFromPEM(pem) { return nil, fmt.Errorf("failed to append certificates from PEM file: %q", caFile) } - s := certPool.Subjects() - subjects := make([]string, len(s)) - for i, subject := range s { - subjects[i] = string(subject) - } - logrus.Debugf("Trusting certs with subjects: %v", subjects) + logrus.Debugf("Trusting %d certs", len(certPool.Subjects())) return certPool, nil } // Client returns a TLS configuration meant to be used by a client. func Client(options Options) (*tls.Config, error) { - tlsConfig := ClientDefault + tlsConfig := ClientDefault() tlsConfig.InsecureSkipVerify = options.InsecureSkipVerify if !options.InsecureSkipVerify && options.CAFile != "" { CAs, err := certPool(options.CAFile) @@ -101,12 +103,12 @@ func Client(options Options) (*tls.Config, error) { tlsConfig.Certificates = []tls.Certificate{tlsCert} } - return &tlsConfig, nil + return tlsConfig, nil } // Server returns a TLS configuration meant to be used by a server. func Server(options Options) (*tls.Config, error) { - tlsConfig := ServerDefault + tlsConfig := ServerDefault() tlsConfig.ClientAuth = options.ClientAuth tlsCert, err := tls.LoadX509KeyPair(options.CertFile, options.KeyFile) if err != nil { @@ -123,5 +125,5 @@ func Server(options Options) (*tls.Config, error) { } tlsConfig.ClientCAs = CAs } - return &tlsConfig, nil + return tlsConfig, nil }