diff --git a/pkg/apiserver/apiserver_test.go b/pkg/apiserver/apiserver_test.go index e2cae1467cde..1890d50bb9f2 100644 --- a/pkg/apiserver/apiserver_test.go +++ b/pkg/apiserver/apiserver_test.go @@ -332,6 +332,7 @@ type SimpleRESTStorage struct { // The id requested, and location to return for ResourceLocation requestedResourceLocationID string resourceLocation *url.URL + resourceLocationTransport http.RoundTripper expectedResourceNamespace string // If non-nil, called inside the WorkFunc when answering update, delete, create. @@ -477,7 +478,7 @@ func (storage *SimpleRESTStorage) ResourceLocation(ctx api.Context, id string) ( } // Make a copy so the internal URL never gets mutated locationCopy := *storage.resourceLocation - return &locationCopy, nil, nil + return &locationCopy, storage.resourceLocationTransport, nil } // Implement Connecter diff --git a/pkg/apiserver/proxy.go b/pkg/apiserver/proxy.go index 6a2899d58ed6..be33a05c588b 100644 --- a/pkg/apiserver/proxy.go +++ b/pkg/apiserver/proxy.go @@ -286,6 +286,11 @@ func dialURL(url *url.URL, transport http.RoundTripper) (net.Conn, error) { return nil, err } + // Return if we were configured to skip validation + if tlsConfig != nil && tlsConfig.InsecureSkipVerify { + return tlsConn, nil + } + // Verify host, _, _ := net.SplitHostPort(dialAddr) if err := tlsConn.VerifyHostname(host); err != nil { diff --git a/pkg/apiserver/proxy_test.go b/pkg/apiserver/proxy_test.go index b4a347dc29c7..ddbd6c063c97 100644 --- a/pkg/apiserver/proxy_test.go +++ b/pkg/apiserver/proxy_test.go @@ -18,6 +18,8 @@ package apiserver import ( "compress/gzip" + "crypto/tls" + "crypto/x509" "fmt" "io" "io/ioutil" @@ -125,43 +127,97 @@ func TestProxy(t *testing.T) { } func TestProxyUpgrade(t *testing.T) { - backendServer := httptest.NewServer(websocket.Handler(func(ws *websocket.Conn) { - defer ws.Close() - body := make([]byte, 5) - ws.Read(body) - ws.Write([]byte("hello " + string(body))) - })) - defer backendServer.Close() - - serverURL, _ := url.Parse(backendServer.URL) - simpleStorage := &SimpleRESTStorage{ - errors: map[string]error{}, - resourceLocation: serverURL, - expectedResourceNamespace: "myns", + + localhostPool := x509.NewCertPool() + if !localhostPool.AppendCertsFromPEM(localhostCert) { + t.Errorf("error setting up localhostCert pool") } - namespaceHandler := handleNamespaced(map[string]rest.Storage{"foo": simpleStorage}) + testcases := map[string]struct { + ServerFunc func(http.Handler) *httptest.Server + ProxyTransport http.RoundTripper + }{ + "http": { + ServerFunc: httptest.NewServer, + ProxyTransport: nil, + }, + "https (invalid hostname + InsecureSkipVerify)": { + ServerFunc: func(h http.Handler) *httptest.Server { + cert, err := tls.X509KeyPair(exampleCert, exampleKey) + if err != nil { + t.Errorf("https (invalid hostname): proxy_test: %v", err) + } + ts := httptest.NewUnstartedServer(h) + ts.TLS = &tls.Config{ + Certificates: []tls.Certificate{cert}, + } + ts.StartTLS() + return ts + }, + ProxyTransport: &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}}, + }, + "https (valid hostname + RootCAs)": { + ServerFunc: func(h http.Handler) *httptest.Server { + cert, err := tls.X509KeyPair(localhostCert, localhostKey) + if err != nil { + t.Errorf("https (valid hostname): proxy_test: %v", err) + } + ts := httptest.NewUnstartedServer(h) + ts.TLS = &tls.Config{ + Certificates: []tls.Certificate{cert}, + } + ts.StartTLS() + return ts + }, + ProxyTransport: &http.Transport{TLSClientConfig: &tls.Config{RootCAs: localhostPool}}, + }, + } - server := httptest.NewServer(namespaceHandler) - defer server.Close() + for k, tc := range testcases { - ws, err := websocket.Dial("ws://"+server.Listener.Addr().String()+"/api/version2/proxy/namespaces/myns/foo/123", "", "http://127.0.0.1/") - if err != nil { - t.Fatalf("websocket dial err: %s", err) - } - defer ws.Close() + backendServer := tc.ServerFunc(websocket.Handler(func(ws *websocket.Conn) { + defer ws.Close() + body := make([]byte, 5) + ws.Read(body) + ws.Write([]byte("hello " + string(body))) + })) + defer backendServer.Close() - if _, err := ws.Write([]byte("world")); err != nil { - t.Fatalf("write err: %s", err) - } + serverURL, _ := url.Parse(backendServer.URL) + simpleStorage := &SimpleRESTStorage{ + errors: map[string]error{}, + resourceLocation: serverURL, + resourceLocationTransport: tc.ProxyTransport, + expectedResourceNamespace: "myns", + } - response := make([]byte, 20) - n, err := ws.Read(response) - if err != nil { - t.Fatalf("read err: %s", err) - } - if e, a := "hello world", string(response[0:n]); e != a { - t.Fatalf("expected '%#v', got '%#v'", e, a) + namespaceHandler := handleNamespaced(map[string]rest.Storage{"foo": simpleStorage}) + + server := httptest.NewServer(namespaceHandler) + defer server.Close() + + ws, err := websocket.Dial("ws://"+server.Listener.Addr().String()+"/api/version2/proxy/namespaces/myns/foo/123", "", "http://127.0.0.1/") + if err != nil { + t.Errorf("%s: websocket dial err: %s", k, err) + continue + } + defer ws.Close() + + if _, err := ws.Write([]byte("world")); err != nil { + t.Errorf("%s: write err: %s", k, err) + continue + } + + response := make([]byte, 20) + n, err := ws.Read(response) + if err != nil { + t.Errorf("%s: read err: %s", k, err) + continue + } + if e, a := "hello world", string(response[0:n]); e != a { + t.Errorf("%s: expected '%#v', got '%#v'", k, e, a) + continue + } } } @@ -225,3 +281,50 @@ func TestRedirectOnMissingTrailingSlash(t *testing.T) { } } } + +// exampleCert was generated from crypto/tls/generate_cert.go with the following command: +// go run generate_cert.go --rsa-bits 512 --host example.com --ca --start-date "Jan 1 00:00:00 1970" --duration=1000000h +var exampleCert = []byte(`-----BEGIN CERTIFICATE----- +MIIBcjCCAR6gAwIBAgIQBOUTYowZaENkZi0faI9DgTALBgkqhkiG9w0BAQswEjEQ +MA4GA1UEChMHQWNtZSBDbzAgFw03MDAxMDEwMDAwMDBaGA8yMDg0MDEyOTE2MDAw +MFowEjEQMA4GA1UEChMHQWNtZSBDbzBcMA0GCSqGSIb3DQEBAQUAA0sAMEgCQQCZ +xfR3sgeHBraGFfF/24tTn4PRVAHOf2UOOxSQRs+aYjNqimFqf/SRIblQgeXdBJDR +gVK5F1Js2zwlehw0bHxRAgMBAAGjUDBOMA4GA1UdDwEB/wQEAwIApDATBgNVHSUE +DDAKBggrBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MBYGA1UdEQQPMA2CC2V4YW1w +bGUuY29tMAsGCSqGSIb3DQEBCwNBAI/mfBB8dm33IpUl+acSyWfL6gX5Wc0FFyVj +dKeesE1XBuPX1My/rzU6Oy/YwX7LOL4FaeNUS6bbL4axSLPKYSs= +-----END CERTIFICATE-----`) + +var exampleKey = []byte(`-----BEGIN RSA PRIVATE KEY----- +MIIBOgIBAAJBAJnF9HeyB4cGtoYV8X/bi1Ofg9FUAc5/ZQ47FJBGz5piM2qKYWp/ +9JEhuVCB5d0EkNGBUrkXUmzbPCV6HDRsfFECAwEAAQJBAJLH9yPuButniACTn5L5 +IJQw1mWQt6zBw9eCo41YWkA0866EgjC53aPZaRjXMp0uNJGdIsys2V5rCOOLWN2C +ODECIQDICHsi8QQQ9wpuJy8X5l8MAfxHL+DIqI84wQTeVM91FQIhAMTME8A18/7h +1Ad6drdnxAkuC0tX6Sx0LDozrmen+HFNAiAlcEDrt0RVkIcpOrg7tuhPLQf0oudl +Zvb3Xlj069awSQIgcT15E/43w2+RASifzVNhQ2MCTr1sSA8lL+xzK+REmnUCIBhQ +j4139pf8Re1J50zBxS/JlQfgDQi9sO9pYeiHIxNs +-----END RSA PRIVATE KEY-----`) + +// localhostCert was generated from crypto/tls/generate_cert.go with the following command: +// go run generate_cert.go --rsa-bits 512 --host 127.0.0.1,::1,example.com --ca --start-date "Jan 1 00:00:00 1970" --duration=1000000h +var localhostCert = []byte(`-----BEGIN CERTIFICATE----- +MIIBdzCCASOgAwIBAgIBADALBgkqhkiG9w0BAQUwEjEQMA4GA1UEChMHQWNtZSBD +bzAeFw03MDAxMDEwMDAwMDBaFw00OTEyMzEyMzU5NTlaMBIxEDAOBgNVBAoTB0Fj +bWUgQ28wWjALBgkqhkiG9w0BAQEDSwAwSAJBAN55NcYKZeInyTuhcCwFMhDHCmwa +IUSdtXdcbItRB/yfXGBhiex00IaLXQnSU+QZPRZWYqeTEbFSgihqi1PUDy8CAwEA +AaNoMGYwDgYDVR0PAQH/BAQDAgCkMBMGA1UdJQQMMAoGCCsGAQUFBwMBMA8GA1Ud +EwEB/wQFMAMBAf8wLgYDVR0RBCcwJYILZXhhbXBsZS5jb22HBH8AAAGHEAAAAAAA +AAAAAAAAAAAAAAEwCwYJKoZIhvcNAQEFA0EAAoQn/ytgqpiLcZu9XKbCJsJcvkgk +Se6AbGXgSlq+ZCEVo0qIwSgeBqmsJxUu7NCSOwVJLYNEBO2DtIxoYVk+MA== +-----END CERTIFICATE-----`) + +// localhostKey is the private key for localhostCert. +var localhostKey = []byte(`-----BEGIN RSA PRIVATE KEY----- +MIIBPAIBAAJBAN55NcYKZeInyTuhcCwFMhDHCmwaIUSdtXdcbItRB/yfXGBhiex0 +0IaLXQnSU+QZPRZWYqeTEbFSgihqi1PUDy8CAwEAAQJBAQdUx66rfh8sYsgfdcvV +NoafYpnEcB5s4m/vSVe6SU7dCK6eYec9f9wpT353ljhDUHq3EbmE4foNzJngh35d +AekCIQDhRQG5Li0Wj8TM4obOnnXUXf1jRv0UkzE9AHWLG5q3AwIhAPzSjpYUDjVW +MCUXgckTpKCuGwbJk7424Nb8bLzf3kllAiA5mUBgjfr/WtFSJdWcPQ4Zt9KTMNKD +EUO0ukpTwEIl6wIhAMbGqZK3zAAFdq8DD2jPx+UJXnh0rnOkZBzDtJ6/iN69AiEA +1Aq8MJgTaYsDQWyU/hDq5YkDJc9e9DSCvUIzqxQWMQE= +-----END RSA PRIVATE KEY-----`) diff --git a/pkg/registry/generic/rest/proxy.go b/pkg/registry/generic/rest/proxy.go index 85c8b827c998..45611931bd36 100644 --- a/pkg/registry/generic/rest/proxy.go +++ b/pkg/registry/generic/rest/proxy.go @@ -240,6 +240,11 @@ func (h *UpgradeAwareProxyHandler) dialURL() (net.Conn, error) { } } + // Return if we were configured to skip validation + if tlsConfig != nil && tlsConfig.InsecureSkipVerify { + return tlsConn, nil + } + // Verify host, _, _ := net.SplitHostPort(dialAddr) if err := tlsConn.VerifyHostname(host); err != nil { diff --git a/pkg/registry/generic/rest/proxy_test.go b/pkg/registry/generic/rest/proxy_test.go index 9f035958908f..bb2e8b561527 100644 --- a/pkg/registry/generic/rest/proxy_test.go +++ b/pkg/registry/generic/rest/proxy_test.go @@ -18,6 +18,8 @@ package rest import ( "bytes" + "crypto/tls" + "crypto/x509" "io" "io/ioutil" "net/http" @@ -259,38 +261,88 @@ func TestServeHTTP(t *testing.T) { } func TestProxyUpgrade(t *testing.T) { - backendServer := httptest.NewServer(websocket.Handler(func(ws *websocket.Conn) { - defer ws.Close() - body := make([]byte, 5) - ws.Read(body) - ws.Write([]byte("hello " + string(body))) - })) - defer backendServer.Close() - - serverURL, _ := url.Parse(backendServer.URL) - proxyHandler := &UpgradeAwareProxyHandler{ - Location: serverURL, - } - proxy := httptest.NewServer(proxyHandler) - defer proxy.Close() - ws, err := websocket.Dial("ws://"+proxy.Listener.Addr().String()+"/some/path", "", "http://127.0.0.1/") - if err != nil { - t.Fatalf("websocket dial err: %s", err) + localhostPool := x509.NewCertPool() + if !localhostPool.AppendCertsFromPEM(localhostCert) { + t.Errorf("error setting up localhostCert pool") } - defer ws.Close() - if _, err := ws.Write([]byte("world")); err != nil { - t.Fatalf("write err: %s", err) + testcases := map[string]struct { + ServerFunc func(http.Handler) *httptest.Server + ProxyTransport http.RoundTripper + }{ + "http": { + ServerFunc: httptest.NewServer, + ProxyTransport: nil, + }, + "https (invalid hostname + InsecureSkipVerify)": { + ServerFunc: func(h http.Handler) *httptest.Server { + cert, err := tls.X509KeyPair(exampleCert, exampleKey) + if err != nil { + t.Errorf("https (invalid hostname): proxy_test: %v", err) + } + ts := httptest.NewUnstartedServer(h) + ts.TLS = &tls.Config{ + Certificates: []tls.Certificate{cert}, + } + ts.StartTLS() + return ts + }, + ProxyTransport: &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}}, + }, + "https (valid hostname + RootCAs)": { + ServerFunc: func(h http.Handler) *httptest.Server { + cert, err := tls.X509KeyPair(localhostCert, localhostKey) + if err != nil { + t.Errorf("https (valid hostname): proxy_test: %v", err) + } + ts := httptest.NewUnstartedServer(h) + ts.TLS = &tls.Config{ + Certificates: []tls.Certificate{cert}, + } + ts.StartTLS() + return ts + }, + ProxyTransport: &http.Transport{TLSClientConfig: &tls.Config{RootCAs: localhostPool}}, + }, } - response := make([]byte, 20) - n, err := ws.Read(response) - if err != nil { - t.Fatalf("read err: %s", err) - } - if e, a := "hello world", string(response[0:n]); e != a { - t.Fatalf("expected '%#v', got '%#v'", e, a) + for k, tc := range testcases { + + backendServer := tc.ServerFunc(websocket.Handler(func(ws *websocket.Conn) { + defer ws.Close() + body := make([]byte, 5) + ws.Read(body) + ws.Write([]byte("hello " + string(body))) + })) + defer backendServer.Close() + + serverURL, _ := url.Parse(backendServer.URL) + proxyHandler := &UpgradeAwareProxyHandler{ + Location: serverURL, + Transport: tc.ProxyTransport, + } + proxy := httptest.NewServer(proxyHandler) + defer proxy.Close() + + ws, err := websocket.Dial("ws://"+proxy.Listener.Addr().String()+"/some/path", "", "http://127.0.0.1/") + if err != nil { + t.Fatalf("%s: websocket dial err: %s", k, err) + } + defer ws.Close() + + if _, err := ws.Write([]byte("world")); err != nil { + t.Fatalf("%s: write err: %s", k, err) + } + + response := make([]byte, 20) + n, err := ws.Read(response) + if err != nil { + t.Fatalf("%s: read err: %s", k, err) + } + if e, a := "hello world", string(response[0:n]); e != a { + t.Fatalf("%s: expected '%#v', got '%#v'", k, e, a) + } } } @@ -349,3 +401,50 @@ func TestDefaultProxyTransport(t *testing.T) { } } } + +// exampleCert was generated from crypto/tls/generate_cert.go with the following command: +// go run generate_cert.go --rsa-bits 512 --host example.com --ca --start-date "Jan 1 00:00:00 1970" --duration=1000000h +var exampleCert = []byte(`-----BEGIN CERTIFICATE----- +MIIBcjCCAR6gAwIBAgIQBOUTYowZaENkZi0faI9DgTALBgkqhkiG9w0BAQswEjEQ +MA4GA1UEChMHQWNtZSBDbzAgFw03MDAxMDEwMDAwMDBaGA8yMDg0MDEyOTE2MDAw +MFowEjEQMA4GA1UEChMHQWNtZSBDbzBcMA0GCSqGSIb3DQEBAQUAA0sAMEgCQQCZ +xfR3sgeHBraGFfF/24tTn4PRVAHOf2UOOxSQRs+aYjNqimFqf/SRIblQgeXdBJDR +gVK5F1Js2zwlehw0bHxRAgMBAAGjUDBOMA4GA1UdDwEB/wQEAwIApDATBgNVHSUE +DDAKBggrBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MBYGA1UdEQQPMA2CC2V4YW1w +bGUuY29tMAsGCSqGSIb3DQEBCwNBAI/mfBB8dm33IpUl+acSyWfL6gX5Wc0FFyVj +dKeesE1XBuPX1My/rzU6Oy/YwX7LOL4FaeNUS6bbL4axSLPKYSs= +-----END CERTIFICATE-----`) + +var exampleKey = []byte(`-----BEGIN RSA PRIVATE KEY----- +MIIBOgIBAAJBAJnF9HeyB4cGtoYV8X/bi1Ofg9FUAc5/ZQ47FJBGz5piM2qKYWp/ +9JEhuVCB5d0EkNGBUrkXUmzbPCV6HDRsfFECAwEAAQJBAJLH9yPuButniACTn5L5 +IJQw1mWQt6zBw9eCo41YWkA0866EgjC53aPZaRjXMp0uNJGdIsys2V5rCOOLWN2C +ODECIQDICHsi8QQQ9wpuJy8X5l8MAfxHL+DIqI84wQTeVM91FQIhAMTME8A18/7h +1Ad6drdnxAkuC0tX6Sx0LDozrmen+HFNAiAlcEDrt0RVkIcpOrg7tuhPLQf0oudl +Zvb3Xlj069awSQIgcT15E/43w2+RASifzVNhQ2MCTr1sSA8lL+xzK+REmnUCIBhQ +j4139pf8Re1J50zBxS/JlQfgDQi9sO9pYeiHIxNs +-----END RSA PRIVATE KEY-----`) + +// localhostCert was generated from crypto/tls/generate_cert.go with the following command: +// go run generate_cert.go --rsa-bits 512 --host 127.0.0.1,::1,example.com --ca --start-date "Jan 1 00:00:00 1970" --duration=1000000h +var localhostCert = []byte(`-----BEGIN CERTIFICATE----- +MIIBdzCCASOgAwIBAgIBADALBgkqhkiG9w0BAQUwEjEQMA4GA1UEChMHQWNtZSBD +bzAeFw03MDAxMDEwMDAwMDBaFw00OTEyMzEyMzU5NTlaMBIxEDAOBgNVBAoTB0Fj +bWUgQ28wWjALBgkqhkiG9w0BAQEDSwAwSAJBAN55NcYKZeInyTuhcCwFMhDHCmwa +IUSdtXdcbItRB/yfXGBhiex00IaLXQnSU+QZPRZWYqeTEbFSgihqi1PUDy8CAwEA +AaNoMGYwDgYDVR0PAQH/BAQDAgCkMBMGA1UdJQQMMAoGCCsGAQUFBwMBMA8GA1Ud +EwEB/wQFMAMBAf8wLgYDVR0RBCcwJYILZXhhbXBsZS5jb22HBH8AAAGHEAAAAAAA +AAAAAAAAAAAAAAEwCwYJKoZIhvcNAQEFA0EAAoQn/ytgqpiLcZu9XKbCJsJcvkgk +Se6AbGXgSlq+ZCEVo0qIwSgeBqmsJxUu7NCSOwVJLYNEBO2DtIxoYVk+MA== +-----END CERTIFICATE-----`) + +// localhostKey is the private key for localhostCert. +var localhostKey = []byte(`-----BEGIN RSA PRIVATE KEY----- +MIIBPAIBAAJBAN55NcYKZeInyTuhcCwFMhDHCmwaIUSdtXdcbItRB/yfXGBhiex0 +0IaLXQnSU+QZPRZWYqeTEbFSgihqi1PUDy8CAwEAAQJBAQdUx66rfh8sYsgfdcvV +NoafYpnEcB5s4m/vSVe6SU7dCK6eYec9f9wpT353ljhDUHq3EbmE4foNzJngh35d +AekCIQDhRQG5Li0Wj8TM4obOnnXUXf1jRv0UkzE9AHWLG5q3AwIhAPzSjpYUDjVW +MCUXgckTpKCuGwbJk7424Nb8bLzf3kllAiA5mUBgjfr/WtFSJdWcPQ4Zt9KTMNKD +EUO0ukpTwEIl6wIhAMbGqZK3zAAFdq8DD2jPx+UJXnh0rnOkZBzDtJ6/iN69AiEA +1Aq8MJgTaYsDQWyU/hDq5YkDJc9e9DSCvUIzqxQWMQE= +-----END RSA PRIVATE KEY-----`) diff --git a/pkg/util/httpstream/spdy/roundtripper.go b/pkg/util/httpstream/spdy/roundtripper.go index 573a83fd402a..b00678221cf8 100644 --- a/pkg/util/httpstream/spdy/roundtripper.go +++ b/pkg/util/httpstream/spdy/roundtripper.go @@ -87,6 +87,11 @@ func (s *SpdyRoundTripper) dial(req *http.Request) (net.Conn, error) { return nil, err } + // Return if we were configured to skip validation + if s.tlsConfig != nil && s.tlsConfig.InsecureSkipVerify { + return conn, nil + } + host, _, err := net.SplitHostPort(dialAddr) if err != nil { return nil, err diff --git a/pkg/util/httpstream/spdy/roundtripper_test.go b/pkg/util/httpstream/spdy/roundtripper_test.go index d7fd0efe5d97..babd23c9011b 100644 --- a/pkg/util/httpstream/spdy/roundtripper_test.go +++ b/pkg/util/httpstream/spdy/roundtripper_test.go @@ -17,64 +17,97 @@ limitations under the License. package spdy import ( - "bytes" - "crypto/rand" - "crypto/rsa" "crypto/tls" "crypto/x509" - "crypto/x509/pkix" - "encoding/pem" "io" - "math/big" - "net" "net/http" "net/http/httptest" "testing" - "time" "k8s.io/kubernetes/pkg/util/httpstream" ) func TestRoundTripAndNewConnection(t *testing.T) { - testCases := []struct { + + localhostPool := x509.NewCertPool() + if !localhostPool.AppendCertsFromPEM(localhostCert) { + t.Errorf("error setting up localhostCert pool") + } + + testCases := map[string]struct { + serverFunc func(http.Handler) *httptest.Server + clientTLS *tls.Config serverConnectionHeader string serverUpgradeHeader string - useTLS bool shouldError bool }{ - { + "no headers": { + serverFunc: httptest.NewServer, serverConnectionHeader: "", serverUpgradeHeader: "", shouldError: true, }, - { + "no upgrade header": { + serverFunc: httptest.NewServer, serverConnectionHeader: "Upgrade", serverUpgradeHeader: "", shouldError: true, }, - { + "no connection header": { + serverFunc: httptest.NewServer, serverConnectionHeader: "", serverUpgradeHeader: "SPDY/3.1", shouldError: true, }, - { + "http": { + serverFunc: httptest.NewServer, + serverConnectionHeader: "Upgrade", + serverUpgradeHeader: "SPDY/3.1", + shouldError: false, + }, + "https (invalid hostname + InsecureSkipVerify)": { + serverFunc: func(h http.Handler) *httptest.Server { + cert, err := tls.X509KeyPair(exampleCert, exampleKey) + if err != nil { + t.Errorf("https (invalid hostname): proxy_test: %v", err) + } + ts := httptest.NewUnstartedServer(h) + ts.TLS = &tls.Config{ + Certificates: []tls.Certificate{cert}, + } + ts.StartTLS() + return ts + }, + clientTLS: &tls.Config{InsecureSkipVerify: true}, serverConnectionHeader: "Upgrade", serverUpgradeHeader: "SPDY/3.1", shouldError: false, }, - { + "https (valid hostname + RootCAs)": { + serverFunc: func(h http.Handler) *httptest.Server { + cert, err := tls.X509KeyPair(localhostCert, localhostKey) + if err != nil { + t.Errorf("https (valid hostname): proxy_test: %v", err) + } + ts := httptest.NewUnstartedServer(h) + ts.TLS = &tls.Config{ + Certificates: []tls.Certificate{cert}, + } + ts.StartTLS() + return ts + }, + clientTLS: &tls.Config{RootCAs: localhostPool}, serverConnectionHeader: "Upgrade", serverUpgradeHeader: "SPDY/3.1", - useTLS: true, shouldError: false, }, } - for i, testCase := range testCases { - server := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + for k, testCase := range testCases { + server := testCase.serverFunc(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { if testCase.shouldError { if e, a := httpstream.HeaderUpgrade, req.Header.Get(httpstream.HeaderConnection); e != a { - t.Fatalf("%d: Expected connection=upgrade header, got '%s", i, a) + t.Fatalf("%s: Expected connection=upgrade header, got '%s", k, a) } w.Header().Set(httpstream.HeaderConnection, testCase.serverConnectionHeader) @@ -92,102 +125,32 @@ func TestRoundTripAndNewConnection(t *testing.T) { return nil }) if spdyConn == nil { - t.Fatalf("%d: unexpected nil spdyConn", i) + t.Fatalf("%s: unexpected nil spdyConn", k) } defer spdyConn.Close() stream := <-streamCh io.Copy(stream, stream) })) - - clientTLS := &tls.Config{} - - if testCase.useTLS { - privateKey, err := rsa.GenerateKey(rand.Reader, 2048) - if err != nil { - t.Fatalf("%d: error generating keypair: %s", i, err) - } - - notBefore := time.Now() - notAfter := notBefore.Add(1 * time.Hour) - - template := x509.Certificate{ - SerialNumber: big.NewInt(1), - Subject: pkix.Name{ - Organization: []string{"Localhost Co"}, - }, - NotBefore: notBefore, - NotAfter: notAfter, - KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, - ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, - BasicConstraintsValid: true, - IsCA: true, - } - - host := "127.0.0.1" - if ip := net.ParseIP(host); ip != nil { - template.IPAddresses = append(template.IPAddresses, ip) - } - template.DNSNames = append(template.DNSNames, host) - - derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &privateKey.PublicKey, privateKey) - if err != nil { - t.Fatalf("%d: error creating cert: %s", i, err) - } - - cert, err := x509.ParseCertificate(derBytes) - if err != nil { - t.Fatalf("%d: error parsing cert: %s", i, err) - } - - roots := x509.NewCertPool() - roots.AddCert(cert) - server.TLS = &tls.Config{ - RootCAs: roots, - } - clientTLS.RootCAs = roots - - certBuf := bytes.Buffer{} - err = pem.Encode(&certBuf, &pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw}) - if err != nil { - t.Fatalf("%d: error encoding cert: %s", i, err) - } - - keyBuf := bytes.Buffer{} - err = pem.Encode(&keyBuf, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(privateKey)}) - if err != nil { - t.Fatalf("%d: error encoding key: %s", i, err) - } - - tlsCert, err := tls.X509KeyPair(certBuf.Bytes(), keyBuf.Bytes()) - if err != nil { - t.Fatalf("%d: error calling tls.X509KeyPair: %s", i, err) - } - server.TLS.Certificates = []tls.Certificate{tlsCert} - clientTLS.Certificates = []tls.Certificate{tlsCert} - server.StartTLS() - } else { - server.Start() - } defer server.Close() req, err := http.NewRequest("GET", server.URL, nil) if err != nil { - t.Fatalf("%d: Error creating request: %s", i, err) + t.Fatalf("%s: Error creating request: %s", k, err) } - spdyTransport := NewRoundTripper(clientTLS) + spdyTransport := NewRoundTripper(testCase.clientTLS) client := &http.Client{Transport: spdyTransport} resp, err := client.Do(req) if err != nil { - t.Fatalf("%d: unexpected error from client.Do: %s", i, err) + t.Fatalf("%s: unexpected error from client.Do: %s", k, err) } conn, err := spdyTransport.NewConnection(resp) haveErr := err != nil if e, a := testCase.shouldError, haveErr; e != a { - t.Fatalf("%d: shouldError=%t, got %t: %v", i, e, a, err) + t.Fatalf("%s: shouldError=%t, got %t: %v", k, e, a, err) } if testCase.shouldError { continue @@ -195,32 +158,79 @@ func TestRoundTripAndNewConnection(t *testing.T) { defer conn.Close() if resp.StatusCode != http.StatusSwitchingProtocols { - t.Fatalf("%d: expected http 101 switching protocols, got %d", i, resp.StatusCode) + t.Fatalf("%s: expected http 101 switching protocols, got %d", k, resp.StatusCode) } stream, err := conn.CreateStream(http.Header{}) if err != nil { - t.Fatalf("%d: error creating client stream: %s", i, err) + t.Fatalf("%s: error creating client stream: %s", k, err) } n, err := stream.Write([]byte("hello")) if err != nil { - t.Fatalf("%d: error writing to stream: %s", i, err) + t.Fatalf("%s: error writing to stream: %s", k, err) } if n != 5 { - t.Fatalf("%d: Expected to write 5 bytes, but actually wrote %d", i, n) + t.Fatalf("%s: Expected to write 5 bytes, but actually wrote %d", k, n) } b := make([]byte, 5) n, err = stream.Read(b) if err != nil { - t.Fatalf("%d: error reading from stream: %s", i, err) + t.Fatalf("%s: error reading from stream: %s", k, err) } if n != 5 { - t.Fatalf("%d: Expected to read 5 bytes, but actually read %d", i, n) + t.Fatalf("%s: Expected to read 5 bytes, but actually read %d", k, n) } if e, a := "hello", string(b[0:n]); e != a { - t.Fatalf("%d: expected '%s', got '%s'", i, e, a) + t.Fatalf("%s: expected '%s', got '%s'", k, e, a) } } } + +// exampleCert was generated from crypto/tls/generate_cert.go with the following command: +// go run generate_cert.go --rsa-bits 512 --host example.com --ca --start-date "Jan 1 00:00:00 1970" --duration=1000000h +var exampleCert = []byte(`-----BEGIN CERTIFICATE----- +MIIBcjCCAR6gAwIBAgIQBOUTYowZaENkZi0faI9DgTALBgkqhkiG9w0BAQswEjEQ +MA4GA1UEChMHQWNtZSBDbzAgFw03MDAxMDEwMDAwMDBaGA8yMDg0MDEyOTE2MDAw +MFowEjEQMA4GA1UEChMHQWNtZSBDbzBcMA0GCSqGSIb3DQEBAQUAA0sAMEgCQQCZ +xfR3sgeHBraGFfF/24tTn4PRVAHOf2UOOxSQRs+aYjNqimFqf/SRIblQgeXdBJDR +gVK5F1Js2zwlehw0bHxRAgMBAAGjUDBOMA4GA1UdDwEB/wQEAwIApDATBgNVHSUE +DDAKBggrBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MBYGA1UdEQQPMA2CC2V4YW1w +bGUuY29tMAsGCSqGSIb3DQEBCwNBAI/mfBB8dm33IpUl+acSyWfL6gX5Wc0FFyVj +dKeesE1XBuPX1My/rzU6Oy/YwX7LOL4FaeNUS6bbL4axSLPKYSs= +-----END CERTIFICATE-----`) + +var exampleKey = []byte(`-----BEGIN RSA PRIVATE KEY----- +MIIBOgIBAAJBAJnF9HeyB4cGtoYV8X/bi1Ofg9FUAc5/ZQ47FJBGz5piM2qKYWp/ +9JEhuVCB5d0EkNGBUrkXUmzbPCV6HDRsfFECAwEAAQJBAJLH9yPuButniACTn5L5 +IJQw1mWQt6zBw9eCo41YWkA0866EgjC53aPZaRjXMp0uNJGdIsys2V5rCOOLWN2C +ODECIQDICHsi8QQQ9wpuJy8X5l8MAfxHL+DIqI84wQTeVM91FQIhAMTME8A18/7h +1Ad6drdnxAkuC0tX6Sx0LDozrmen+HFNAiAlcEDrt0RVkIcpOrg7tuhPLQf0oudl +Zvb3Xlj069awSQIgcT15E/43w2+RASifzVNhQ2MCTr1sSA8lL+xzK+REmnUCIBhQ +j4139pf8Re1J50zBxS/JlQfgDQi9sO9pYeiHIxNs +-----END RSA PRIVATE KEY-----`) + +// localhostCert was generated from crypto/tls/generate_cert.go with the following command: +// go run generate_cert.go --rsa-bits 512 --host 127.0.0.1,::1,example.com --ca --start-date "Jan 1 00:00:00 1970" --duration=1000000h +var localhostCert = []byte(`-----BEGIN CERTIFICATE----- +MIIBdzCCASOgAwIBAgIBADALBgkqhkiG9w0BAQUwEjEQMA4GA1UEChMHQWNtZSBD +bzAeFw03MDAxMDEwMDAwMDBaFw00OTEyMzEyMzU5NTlaMBIxEDAOBgNVBAoTB0Fj +bWUgQ28wWjALBgkqhkiG9w0BAQEDSwAwSAJBAN55NcYKZeInyTuhcCwFMhDHCmwa +IUSdtXdcbItRB/yfXGBhiex00IaLXQnSU+QZPRZWYqeTEbFSgihqi1PUDy8CAwEA +AaNoMGYwDgYDVR0PAQH/BAQDAgCkMBMGA1UdJQQMMAoGCCsGAQUFBwMBMA8GA1Ud +EwEB/wQFMAMBAf8wLgYDVR0RBCcwJYILZXhhbXBsZS5jb22HBH8AAAGHEAAAAAAA +AAAAAAAAAAAAAAEwCwYJKoZIhvcNAQEFA0EAAoQn/ytgqpiLcZu9XKbCJsJcvkgk +Se6AbGXgSlq+ZCEVo0qIwSgeBqmsJxUu7NCSOwVJLYNEBO2DtIxoYVk+MA== +-----END CERTIFICATE-----`) + +// localhostKey is the private key for localhostCert. +var localhostKey = []byte(`-----BEGIN RSA PRIVATE KEY----- +MIIBPAIBAAJBAN55NcYKZeInyTuhcCwFMhDHCmwaIUSdtXdcbItRB/yfXGBhiex0 +0IaLXQnSU+QZPRZWYqeTEbFSgihqi1PUDy8CAwEAAQJBAQdUx66rfh8sYsgfdcvV +NoafYpnEcB5s4m/vSVe6SU7dCK6eYec9f9wpT353ljhDUHq3EbmE4foNzJngh35d +AekCIQDhRQG5Li0Wj8TM4obOnnXUXf1jRv0UkzE9AHWLG5q3AwIhAPzSjpYUDjVW +MCUXgckTpKCuGwbJk7424Nb8bLzf3kllAiA5mUBgjfr/WtFSJdWcPQ4Zt9KTMNKD +EUO0ukpTwEIl6wIhAMbGqZK3zAAFdq8DD2jPx+UJXnh0rnOkZBzDtJ6/iN69AiEA +1Aq8MJgTaYsDQWyU/hDq5YkDJc9e9DSCvUIzqxQWMQE= +-----END RSA PRIVATE KEY-----`)