Explorar el Código

Fix client-side HTTP hijacking over TLS

Properly CloseWrite() the client socket once done with stdin when using
TLS connection (this used to rely on an erroneous type assertion).

Fixes #8658.
Fixes #8642.

Signed-off-by: Arnaud Porterie <arnaud.porterie@docker.com>
Signed-off-by: Michael Crosby <crosby.michael@gmail.com>
Arnaud Porterie hace 10 años
padre
commit
e98e56bb1e
Se han modificado 1 ficheros con 98 adiciones y 7 borrados
  1. 98 7
      api/client/hijack.go

+ 98 - 7
api/client/hijack.go

@@ -2,6 +2,7 @@ package client
 
 
 import (
 import (
 	"crypto/tls"
 	"crypto/tls"
+	"errors"
 	"fmt"
 	"fmt"
 	"io"
 	"io"
 	"net"
 	"net"
@@ -10,6 +11,7 @@ import (
 	"os"
 	"os"
 	"runtime"
 	"runtime"
 	"strings"
 	"strings"
+	"time"
 
 
 	log "github.com/Sirupsen/logrus"
 	log "github.com/Sirupsen/logrus"
 	"github.com/docker/docker/api"
 	"github.com/docker/docker/api"
@@ -19,9 +21,99 @@ import (
 	"github.com/docker/docker/pkg/term"
 	"github.com/docker/docker/pkg/term"
 )
 )
 
 
+type tlsClientCon struct {
+	*tls.Conn
+	rawConn net.Conn
+}
+
+func (c *tlsClientCon) CloseWrite() error {
+	// Go standard tls.Conn doesn't provide the CloseWrite() method so we do it
+	// on its underlying connection.
+	if cwc, ok := c.rawConn.(interface {
+		CloseWrite() error
+	}); ok {
+		return cwc.CloseWrite()
+	}
+	return nil
+}
+
+func tlsDial(network, addr string, config *tls.Config) (net.Conn, error) {
+	return tlsDialWithDialer(new(net.Dialer), network, addr, config)
+}
+
+// We need to copy Go's implementation of tls.Dial (pkg/cryptor/tls/tls.go) in
+// order to return our custom tlsClientCon struct which holds both the tls.Conn
+// object _and_ its underlying raw connection. The rationale for this is that
+// we need to be able to close the write end of the connection when attaching,
+// which tls.Conn does not provide.
+func tlsDialWithDialer(dialer *net.Dialer, network, addr string, config *tls.Config) (net.Conn, error) {
+	// We want the Timeout and Deadline values from dialer to cover the
+	// whole process: TCP connection and TLS handshake. This means that we
+	// also need to start our own timers now.
+	timeout := dialer.Timeout
+
+	if !dialer.Deadline.IsZero() {
+		deadlineTimeout := dialer.Deadline.Sub(time.Now())
+		if timeout == 0 || deadlineTimeout < timeout {
+			timeout = deadlineTimeout
+		}
+	}
+
+	var errChannel chan error
+
+	if timeout != 0 {
+		errChannel = make(chan error, 2)
+		time.AfterFunc(timeout, func() {
+			errChannel <- errors.New("")
+		})
+	}
+
+	rawConn, err := dialer.Dial(network, addr)
+	if err != nil {
+		return nil, err
+	}
+
+	colonPos := strings.LastIndex(addr, ":")
+	if colonPos == -1 {
+		colonPos = len(addr)
+	}
+	hostname := addr[:colonPos]
+
+	// If no ServerName is set, infer the ServerName
+	// from the hostname we're connecting to.
+	if config.ServerName == "" {
+		// Make a copy to avoid polluting argument or default.
+		c := *config
+		c.ServerName = hostname
+		config = &c
+	}
+
+	conn := tls.Client(rawConn, config)
+
+	if timeout == 0 {
+		err = conn.Handshake()
+	} else {
+		go func() {
+			errChannel <- conn.Handshake()
+		}()
+
+		err = <-errChannel
+	}
+
+	if err != nil {
+		rawConn.Close()
+		return nil, err
+	}
+
+	// This is Docker difference with standard's crypto/tls package: returned a
+	// wrapper which holds both the TLS and raw connections.
+	return &tlsClientCon{conn, rawConn}, nil
+}
+
 func (cli *DockerCli) dial() (net.Conn, error) {
 func (cli *DockerCli) dial() (net.Conn, error) {
 	if cli.tlsConfig != nil && cli.proto != "unix" {
 	if cli.tlsConfig != nil && cli.proto != "unix" {
-		return tls.Dial(cli.proto, cli.addr, cli.tlsConfig)
+		// Notice this isn't Go standard's tls.Dial function
+		return tlsDial(cli.proto, cli.addr, cli.tlsConfig)
 	}
 	}
 	return net.Dial(cli.proto, cli.addr)
 	return net.Dial(cli.proto, cli.addr)
 }
 }
@@ -109,12 +201,11 @@ func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in io.Rea
 			io.Copy(rwc, in)
 			io.Copy(rwc, in)
 			log.Debugf("[hijack] End of stdin")
 			log.Debugf("[hijack] End of stdin")
 		}
 		}
-		if tcpc, ok := rwc.(*net.TCPConn); ok {
-			if err := tcpc.CloseWrite(); err != nil {
-				log.Debugf("Couldn't send EOF: %s", err)
-			}
-		} else if unixc, ok := rwc.(*net.UnixConn); ok {
-			if err := unixc.CloseWrite(); err != nil {
+
+		if conn, ok := rwc.(interface {
+			CloseWrite() error
+		}); ok {
+			if err := conn.CloseWrite(); err != nil {
 				log.Debugf("Couldn't send EOF: %s", err)
 				log.Debugf("Couldn't send EOF: %s", err)
 			}
 			}
 		}
 		}