From 5bffa5b8582764ac0baeb463a70533fc4ec5acd6 Mon Sep 17 00:00:00 2001 From: Fabiano Franz Date: Wed, 17 Feb 2016 17:29:25 -0200 Subject: [PATCH] SPDY roundtripper support to proxy with Basic auth --- pkg/util/httpstream/spdy/roundtripper.go | 16 +++++ pkg/util/httpstream/spdy/roundtripper_test.go | 65 ++++++++++++++++++- 2 files changed, 80 insertions(+), 1 deletion(-) diff --git a/pkg/util/httpstream/spdy/roundtripper.go b/pkg/util/httpstream/spdy/roundtripper.go index aab46ead3ff..ca7e9370ecf 100644 --- a/pkg/util/httpstream/spdy/roundtripper.go +++ b/pkg/util/httpstream/spdy/roundtripper.go @@ -19,6 +19,7 @@ package spdy import ( "bufio" "crypto/tls" + "encoding/base64" "fmt" "io/ioutil" "net" @@ -97,6 +98,11 @@ func (s *SpdyRoundTripper) dial(req *http.Request) (net.Conn, error) { Host: targetHost, } + if pa := s.proxyAuth(proxyURL); pa != "" { + proxyReq.Header = http.Header{} + proxyReq.Header.Set("Proxy-Authorization", pa) + } + proxyDialConn, err := s.dialWithoutProxy(proxyURL) if err != nil { return nil, err @@ -183,6 +189,16 @@ func (s *SpdyRoundTripper) dialWithoutProxy(url *url.URL) (net.Conn, error) { return conn, nil } +// proxyAuth returns, for a given proxy URL, the value to be used for the Proxy-Authorization header +func (s *SpdyRoundTripper) proxyAuth(proxyURL *url.URL) string { + if proxyURL == nil || proxyURL.User == nil { + return "" + } + credentials := proxyURL.User.String() + encodedAuth := base64.StdEncoding.EncodeToString([]byte(credentials)) + return fmt.Sprintf("Basic %s", encodedAuth) +} + // RoundTrip executes the Request and upgrades it. After a successful upgrade, // clients may call SpdyRoundTripper.Connection() to retrieve the upgraded // connection. diff --git a/pkg/util/httpstream/spdy/roundtripper_test.go b/pkg/util/httpstream/spdy/roundtripper_test.go index 1ce07c3689b..1aa3a0ee8cf 100644 --- a/pkg/util/httpstream/spdy/roundtripper_test.go +++ b/pkg/util/httpstream/spdy/roundtripper_test.go @@ -19,6 +19,7 @@ package spdy import ( "crypto/tls" "crypto/x509" + "encoding/base64" "io" "net/http" "net/http/httptest" @@ -64,6 +65,7 @@ func TestRoundTripAndNewConnection(t *testing.T) { testCases := map[string]struct { serverFunc func(http.Handler) *httptest.Server proxyServerFunc func(http.Handler) *httptest.Server + proxyAuth *url.Userinfo clientTLS *tls.Config serverConnectionHeader string serverUpgradeHeader string @@ -146,6 +148,16 @@ func TestRoundTripAndNewConnection(t *testing.T) { serverStatusCode: http.StatusSwitchingProtocols, shouldError: false, }, + "proxied https with auth (invalid hostname + InsecureSkipVerify) -> http": { + serverFunc: httptest.NewServer, + proxyServerFunc: httpsServerInvalidHostname, + proxyAuth: url.UserPassword("proxyuser", "proxypasswd"), + clientTLS: &tls.Config{InsecureSkipVerify: true}, + serverConnectionHeader: "Upgrade", + serverUpgradeHeader: "SPDY/3.1", + serverStatusCode: http.StatusSwitchingProtocols, + shouldError: false, + }, "proxied https (invalid hostname + hostname verification) -> http": { serverFunc: httptest.NewServer, proxyServerFunc: httpsServerInvalidHostname, @@ -164,6 +176,16 @@ func TestRoundTripAndNewConnection(t *testing.T) { serverStatusCode: http.StatusSwitchingProtocols, shouldError: false, }, + "proxied https with auth (valid hostname + RootCAs) -> http": { + serverFunc: httptest.NewServer, + proxyServerFunc: httpsServerValidHostname, + proxyAuth: url.UserPassword("proxyuser", "proxypasswd"), + clientTLS: &tls.Config{RootCAs: localhostPool}, + serverConnectionHeader: "Upgrade", + serverUpgradeHeader: "SPDY/3.1", + serverStatusCode: http.StatusSwitchingProtocols, + shouldError: false, + }, "proxied https (invalid hostname + InsecureSkipVerify) -> https (invalid hostname)": { serverFunc: httpsServerInvalidHostname, proxyServerFunc: httpsServerInvalidHostname, @@ -173,6 +195,16 @@ func TestRoundTripAndNewConnection(t *testing.T) { serverStatusCode: http.StatusSwitchingProtocols, shouldError: false, // works because the test proxy ignores TLS errors }, + "proxied https with auth (invalid hostname + InsecureSkipVerify) -> https (invalid hostname)": { + serverFunc: httpsServerInvalidHostname, + proxyServerFunc: httpsServerInvalidHostname, + proxyAuth: url.UserPassword("proxyuser", "proxypasswd"), + clientTLS: &tls.Config{InsecureSkipVerify: true}, + serverConnectionHeader: "Upgrade", + serverUpgradeHeader: "SPDY/3.1", + serverStatusCode: http.StatusSwitchingProtocols, + shouldError: false, // works because the test proxy ignores TLS errors + }, "proxied https (invalid hostname + hostname verification) -> https (invalid hostname)": { serverFunc: httpsServerInvalidHostname, proxyServerFunc: httpsServerInvalidHostname, @@ -191,6 +223,16 @@ func TestRoundTripAndNewConnection(t *testing.T) { serverStatusCode: http.StatusSwitchingProtocols, shouldError: false, }, + "proxied https with auth (valid hostname + RootCAs) -> https (valid hostname + RootCAs)": { + serverFunc: httpsServerValidHostname, + proxyServerFunc: httpsServerValidHostname, + proxyAuth: url.UserPassword("proxyuser", "proxypasswd"), + clientTLS: &tls.Config{RootCAs: localhostPool}, + serverConnectionHeader: "Upgrade", + serverUpgradeHeader: "SPDY/3.1", + serverStatusCode: http.StatusSwitchingProtocols, + shouldError: false, + }, } for k, testCase := range testCases { @@ -238,20 +280,29 @@ func TestRoundTripAndNewConnection(t *testing.T) { var proxierCalled bool var proxyCalledWithHost string + var proxyCalledWithAuth bool + var proxyCalledWithAuthHeader string if testCase.proxyServerFunc != nil { proxyHandler := goproxy.NewProxyHttpServer() + proxyHandler.OnRequest().HandleConnectFunc(func(host string, ctx *goproxy.ProxyCtx) (*goproxy.ConnectAction, string) { proxyCalledWithHost = host + + proxyAuthHeaderName := "Proxy-Authorization" + _, proxyCalledWithAuth = ctx.Req.Header[proxyAuthHeaderName] + proxyCalledWithAuthHeader = ctx.Req.Header.Get(proxyAuthHeaderName) return goproxy.OkConnect, host }) + proxy := testCase.proxyServerFunc(proxyHandler) spdyTransport.proxier = func(proxierReq *http.Request) (*url.URL, error) { + proxierCalled = true proxyURL, err := url.Parse(proxy.URL) if err != nil { return nil, err } - proxierCalled = true + proxyURL.User = testCase.proxyAuth return proxyURL, nil } // TODO: Uncomment when fix #19254 @@ -311,6 +362,18 @@ func TestRoundTripAndNewConnection(t *testing.T) { t.Fatalf("%s: Expected to see a call to the proxy for backend %q, got %q", k, serverURL.Host, proxyCalledWithHost) } } + + var expectedProxyAuth string + if testCase.proxyAuth != nil { + encodedCredentials := base64.StdEncoding.EncodeToString([]byte(testCase.proxyAuth.String())) + expectedProxyAuth = "Basic " + encodedCredentials + } + if len(expectedProxyAuth) == 0 && proxyCalledWithAuth { + t.Fatalf("%s: Proxy authorization unexpected, got %q", k, proxyCalledWithAuthHeader) + } + if proxyCalledWithAuthHeader != expectedProxyAuth { + t.Fatalf("%s: Expected to see a call to the proxy with credentials %q, got %q", k, testCase.proxyAuth, proxyCalledWithAuthHeader) + } } }