diff --git a/pkg/util/httpstream/spdy/roundtripper.go b/pkg/util/httpstream/spdy/roundtripper.go index 573a83fd402..824be7691b0 100644 --- a/pkg/util/httpstream/spdy/roundtripper.go +++ b/pkg/util/httpstream/spdy/roundtripper.go @@ -23,6 +23,8 @@ import ( "io/ioutil" "net" "net/http" + "net/http/httputil" + "net/url" "strings" "k8s.io/kubernetes/pkg/api" @@ -53,21 +55,82 @@ type SpdyRoundTripper struct { Dialer *net.Dialer } -// NewSpdyRoundTripper creates a new SpdyRoundTripper that will use +// NewRoundTripper creates a new SpdyRoundTripper that will use // the specified tlsConfig. func NewRoundTripper(tlsConfig *tls.Config) httpstream.UpgradeRoundTripper { return NewSpdyRoundTripper(tlsConfig) } +// NewSpdyRoundTripper creates a new SpdyRoundTripper that will use +// the specified tlsConfig. This function is mostly meant for unit tests. func NewSpdyRoundTripper(tlsConfig *tls.Config) *SpdyRoundTripper { return &SpdyRoundTripper{tlsConfig: tlsConfig} } -// dial dials the host specified by req, using TLS if appropriate. +// dial dials the host specified by req, using TLS if appropriate, optionally +// using a proxy server if one is configured via environment variables. func (s *SpdyRoundTripper) dial(req *http.Request) (net.Conn, error) { - dialAddr := netutil.CanonicalAddr(req.URL) + proxyURL, err := http.ProxyFromEnvironment(req) + if err != nil { + return nil, err + } - if req.URL.Scheme == "http" { + if proxyURL == nil { + return s.dialWithoutProxy(req.URL) + } + + // proxying logic adapted from http://blog.h6t.eu/post/74098062923/golang-websocket-with-http-proxy-support + proxyReq := http.Request{ + Method: "CONNECT", + URL: &url.URL{}, + Host: req.URL.Host, + } + + proxyDialConn, err := s.dialWithoutProxy(proxyURL) + if err != nil { + return nil, err + } + + proxyClientConn := httputil.NewProxyClientConn(proxyDialConn, nil) + _, err = proxyClientConn.Do(&proxyReq) + if err != nil && err != httputil.ErrPersistEOF { + return nil, err + } + + rwc, _ := proxyClientConn.Hijack() + + if req.URL.Scheme != "https" { + return rwc, nil + } + + host, _, err := net.SplitHostPort(req.URL.Host) + if err != nil { + return nil, err + } + + if len(s.tlsConfig.ServerName) == 0 { + s.tlsConfig.ServerName = host + } + + tlsConn := tls.Client(rwc, s.tlsConfig) + + // need to manually call Handshake() so we can call VerifyHostname() below + if err := tlsConn.Handshake(); err != nil { + return nil, err + } + + if err := tlsConn.VerifyHostname(host); err != nil { + return nil, err + } + + return tlsConn, nil +} + +// dialWithoutProxy dials the host specified by url, using TLS if appropriate. +func (s *SpdyRoundTripper) dialWithoutProxy(url *url.URL) (net.Conn, error) { + dialAddr := netutil.CanonicalAddr(url) + + if url.Scheme == "http" { if s.Dialer == nil { return net.Dial("tcp", dialAddr) } else {