k8s.io/apimachinery@v0.29.2/pkg/util/httpstream/spdy/roundtripper_test.go (about) 1 /* 2 Copyright 2015 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package spdy 18 19 import ( 20 "context" 21 "crypto/tls" 22 "crypto/x509" 23 "io" 24 "net" 25 "net/http" 26 "net/http/httptest" 27 "net/url" 28 "reflect" 29 "strconv" 30 "strings" 31 "testing" 32 33 "github.com/armon/go-socks5" 34 "github.com/stretchr/testify/assert" 35 "github.com/stretchr/testify/require" 36 37 "k8s.io/apimachinery/pkg/util/httpstream" 38 utilnettesting "k8s.io/apimachinery/pkg/util/net/testing" 39 ) 40 41 type serverHandlerConfig struct { 42 shouldError bool 43 statusCode int 44 connectionHeader string 45 upgradeHeader string 46 } 47 48 func serverHandler(t *testing.T, config serverHandlerConfig) http.HandlerFunc { 49 return func(w http.ResponseWriter, req *http.Request) { 50 if config.shouldError { 51 if e, a := httpstream.HeaderUpgrade, req.Header.Get(httpstream.HeaderConnection); e != a { 52 t.Fatalf("expected connection=upgrade header, got '%s", a) 53 } 54 55 w.Header().Set(httpstream.HeaderConnection, config.connectionHeader) 56 w.Header().Set(httpstream.HeaderUpgrade, config.upgradeHeader) 57 w.WriteHeader(config.statusCode) 58 59 return 60 } 61 62 streamCh := make(chan httpstream.Stream) 63 64 responseUpgrader := NewResponseUpgrader() 65 spdyConn := responseUpgrader.UpgradeResponse(w, req, func(s httpstream.Stream, replySent <-chan struct{}) error { 66 streamCh <- s 67 return nil 68 }) 69 if spdyConn == nil { 70 t.Fatal("unexpected nil spdyConn") 71 } 72 defer spdyConn.Close() 73 74 stream := <-streamCh 75 io.Copy(stream, stream) 76 } 77 } 78 79 type serverFunc func(http.Handler) *httptest.Server 80 81 func httpsServerInvalidHostname(t *testing.T) serverFunc { 82 return func(h http.Handler) *httptest.Server { 83 cert, err := tls.X509KeyPair(exampleCert, exampleKey) 84 if err != nil { 85 t.Errorf("https (invalid hostname): proxy_test: %v", err) 86 } 87 ts := httptest.NewUnstartedServer(h) 88 ts.TLS = &tls.Config{ 89 Certificates: []tls.Certificate{cert}, 90 } 91 ts.StartTLS() 92 return ts 93 } 94 } 95 96 func httpsServerValidHostname(t *testing.T) serverFunc { 97 return func(h http.Handler) *httptest.Server { 98 cert, err := tls.X509KeyPair(localhostCert, localhostKey) 99 if err != nil { 100 t.Errorf("https (valid hostname): proxy_test: %v", err) 101 } 102 ts := httptest.NewUnstartedServer(h) 103 ts.TLS = &tls.Config{ 104 Certificates: []tls.Certificate{cert}, 105 } 106 ts.StartTLS() 107 return ts 108 } 109 } 110 111 func localhostCertPool(t *testing.T) *x509.CertPool { 112 localhostPool := x509.NewCertPool() 113 114 if !localhostPool.AppendCertsFromPEM(localhostCert) { 115 t.Errorf("error setting up localhostCert pool") 116 } 117 return localhostPool 118 } 119 120 // be sure to unset environment variable https_proxy (if exported) before testing, otherwise the testing will fail unexpectedly. 121 func TestRoundTripAndNewConnection(t *testing.T) { 122 localhostPool := localhostCertPool(t) 123 124 testCases := map[string]struct { 125 serverFunc func(http.Handler) *httptest.Server 126 proxyServerFunc func(http.Handler) *httptest.Server 127 proxyAuth *url.Userinfo 128 clientTLS *tls.Config 129 serverConnectionHeader string 130 serverUpgradeHeader string 131 serverStatusCode int 132 shouldError bool 133 }{ 134 "no headers": { 135 serverFunc: httptest.NewServer, 136 serverConnectionHeader: "", 137 serverUpgradeHeader: "", 138 serverStatusCode: http.StatusSwitchingProtocols, 139 shouldError: true, 140 }, 141 "no upgrade header": { 142 serverFunc: httptest.NewServer, 143 serverConnectionHeader: "Upgrade", 144 serverUpgradeHeader: "", 145 serverStatusCode: http.StatusSwitchingProtocols, 146 shouldError: true, 147 }, 148 "no connection header": { 149 serverFunc: httptest.NewServer, 150 serverConnectionHeader: "", 151 serverUpgradeHeader: "SPDY/3.1", 152 serverStatusCode: http.StatusSwitchingProtocols, 153 shouldError: true, 154 }, 155 "no switching protocol status code": { 156 serverFunc: httptest.NewServer, 157 serverConnectionHeader: "Upgrade", 158 serverUpgradeHeader: "SPDY/3.1", 159 serverStatusCode: http.StatusForbidden, 160 shouldError: true, 161 }, 162 "http": { 163 serverFunc: httptest.NewServer, 164 serverConnectionHeader: "Upgrade", 165 serverUpgradeHeader: "SPDY/3.1", 166 serverStatusCode: http.StatusSwitchingProtocols, 167 shouldError: false, 168 }, 169 "https (invalid hostname + InsecureSkipVerify)": { 170 serverFunc: httpsServerInvalidHostname(t), 171 clientTLS: &tls.Config{InsecureSkipVerify: true}, 172 serverConnectionHeader: "Upgrade", 173 serverUpgradeHeader: "SPDY/3.1", 174 serverStatusCode: http.StatusSwitchingProtocols, 175 shouldError: false, 176 }, 177 "https (invalid hostname + hostname verification)": { 178 serverFunc: httpsServerInvalidHostname(t), 179 clientTLS: &tls.Config{InsecureSkipVerify: false}, 180 serverConnectionHeader: "Upgrade", 181 serverUpgradeHeader: "SPDY/3.1", 182 serverStatusCode: http.StatusSwitchingProtocols, 183 shouldError: true, 184 }, 185 "https (valid hostname + RootCAs)": { 186 serverFunc: httpsServerValidHostname(t), 187 clientTLS: &tls.Config{RootCAs: localhostPool}, 188 serverConnectionHeader: "Upgrade", 189 serverUpgradeHeader: "SPDY/3.1", 190 serverStatusCode: http.StatusSwitchingProtocols, 191 shouldError: false, 192 }, 193 "proxied http->http": { 194 serverFunc: httptest.NewServer, 195 proxyServerFunc: httptest.NewServer, 196 serverConnectionHeader: "Upgrade", 197 serverUpgradeHeader: "SPDY/3.1", 198 serverStatusCode: http.StatusSwitchingProtocols, 199 shouldError: false, 200 }, 201 "proxied https (invalid hostname + InsecureSkipVerify) -> http": { 202 serverFunc: httptest.NewServer, 203 proxyServerFunc: httpsServerInvalidHostname(t), 204 clientTLS: &tls.Config{InsecureSkipVerify: true}, 205 serverConnectionHeader: "Upgrade", 206 serverUpgradeHeader: "SPDY/3.1", 207 serverStatusCode: http.StatusSwitchingProtocols, 208 shouldError: false, 209 }, 210 "proxied https with auth (invalid hostname + InsecureSkipVerify) -> http": { 211 serverFunc: httptest.NewServer, 212 proxyServerFunc: httpsServerInvalidHostname(t), 213 proxyAuth: url.UserPassword("proxyuser", "proxypasswd"), 214 clientTLS: &tls.Config{InsecureSkipVerify: true}, 215 serverConnectionHeader: "Upgrade", 216 serverUpgradeHeader: "SPDY/3.1", 217 serverStatusCode: http.StatusSwitchingProtocols, 218 shouldError: false, 219 }, 220 "proxied https (invalid hostname + hostname verification) -> http": { 221 serverFunc: httptest.NewServer, 222 proxyServerFunc: httpsServerInvalidHostname(t), 223 clientTLS: &tls.Config{InsecureSkipVerify: false}, 224 serverConnectionHeader: "Upgrade", 225 serverUpgradeHeader: "SPDY/3.1", 226 serverStatusCode: http.StatusSwitchingProtocols, 227 shouldError: true, // fails because the client doesn't trust the proxy 228 }, 229 "proxied https (valid hostname + RootCAs) -> http": { 230 serverFunc: httptest.NewServer, 231 proxyServerFunc: httpsServerValidHostname(t), 232 clientTLS: &tls.Config{RootCAs: localhostPool}, 233 serverConnectionHeader: "Upgrade", 234 serverUpgradeHeader: "SPDY/3.1", 235 serverStatusCode: http.StatusSwitchingProtocols, 236 shouldError: false, 237 }, 238 "proxied https with auth (valid hostname + RootCAs) -> http": { 239 serverFunc: httptest.NewServer, 240 proxyServerFunc: httpsServerValidHostname(t), 241 proxyAuth: url.UserPassword("proxyuser", "proxypasswd"), 242 clientTLS: &tls.Config{RootCAs: localhostPool}, 243 serverConnectionHeader: "Upgrade", 244 serverUpgradeHeader: "SPDY/3.1", 245 serverStatusCode: http.StatusSwitchingProtocols, 246 shouldError: false, 247 }, 248 "proxied https (invalid hostname + InsecureSkipVerify) -> https (invalid hostname)": { 249 serverFunc: httpsServerInvalidHostname(t), 250 proxyServerFunc: httpsServerInvalidHostname(t), 251 clientTLS: &tls.Config{InsecureSkipVerify: true}, 252 serverConnectionHeader: "Upgrade", 253 serverUpgradeHeader: "SPDY/3.1", 254 serverStatusCode: http.StatusSwitchingProtocols, 255 shouldError: false, // works because the test proxy ignores TLS errors 256 }, 257 "proxied https with auth (invalid hostname + InsecureSkipVerify) -> https (invalid hostname)": { 258 serverFunc: httpsServerInvalidHostname(t), 259 proxyServerFunc: httpsServerInvalidHostname(t), 260 proxyAuth: url.UserPassword("proxyuser", "proxypasswd"), 261 clientTLS: &tls.Config{InsecureSkipVerify: true}, 262 serverConnectionHeader: "Upgrade", 263 serverUpgradeHeader: "SPDY/3.1", 264 serverStatusCode: http.StatusSwitchingProtocols, 265 shouldError: false, // works because the test proxy ignores TLS errors 266 }, 267 "proxied https (invalid hostname + hostname verification) -> https (invalid hostname)": { 268 serverFunc: httpsServerInvalidHostname(t), 269 proxyServerFunc: httpsServerInvalidHostname(t), 270 clientTLS: &tls.Config{InsecureSkipVerify: false}, 271 serverConnectionHeader: "Upgrade", 272 serverUpgradeHeader: "SPDY/3.1", 273 serverStatusCode: http.StatusSwitchingProtocols, 274 shouldError: true, // fails because the client doesn't trust the proxy 275 }, 276 "proxied https (valid hostname + RootCAs) -> https (valid hostname + RootCAs)": { 277 serverFunc: httpsServerValidHostname(t), 278 proxyServerFunc: httpsServerValidHostname(t), 279 clientTLS: &tls.Config{RootCAs: localhostPool}, 280 serverConnectionHeader: "Upgrade", 281 serverUpgradeHeader: "SPDY/3.1", 282 serverStatusCode: http.StatusSwitchingProtocols, 283 shouldError: false, 284 }, 285 "proxied https with auth (valid hostname + RootCAs) -> https (valid hostname + RootCAs)": { 286 serverFunc: httpsServerValidHostname(t), 287 proxyServerFunc: httpsServerValidHostname(t), 288 proxyAuth: url.UserPassword("proxyuser", "proxypasswd"), 289 clientTLS: &tls.Config{RootCAs: localhostPool}, 290 serverConnectionHeader: "Upgrade", 291 serverUpgradeHeader: "SPDY/3.1", 292 serverStatusCode: http.StatusSwitchingProtocols, 293 shouldError: false, 294 }, 295 "proxied valid https, proxy auth with chars that percent escape -> valid https": { 296 serverFunc: httpsServerValidHostname(t), 297 proxyServerFunc: httpsServerValidHostname(t), 298 proxyAuth: url.UserPassword("proxy user", "proxypasswd%"), 299 clientTLS: &tls.Config{RootCAs: localhostPool}, 300 serverConnectionHeader: "Upgrade", 301 serverUpgradeHeader: "SPDY/3.1", 302 serverStatusCode: http.StatusSwitchingProtocols, 303 shouldError: false, 304 }, 305 } 306 307 for k, testCase := range testCases { 308 t.Run(k, func(t *testing.T) { 309 server := testCase.serverFunc(serverHandler( 310 t, serverHandlerConfig{ 311 shouldError: testCase.shouldError, 312 statusCode: testCase.serverStatusCode, 313 connectionHeader: testCase.serverConnectionHeader, 314 upgradeHeader: testCase.serverUpgradeHeader, 315 }, 316 )) 317 defer server.Close() 318 t.Logf("Server URL: %v", server.URL) 319 320 serverURL, err := url.Parse(server.URL) 321 if err != nil { 322 t.Fatalf("error creating request: %s", err) 323 } 324 req, err := http.NewRequest("GET", server.URL, nil) 325 if err != nil { 326 t.Fatalf("error creating request: %s", err) 327 } 328 329 spdyTransport, err := NewRoundTripper(testCase.clientTLS) 330 if err != nil { 331 t.Fatalf("error creating SpdyRoundTripper: %v", err) 332 } 333 334 var proxierCalled bool 335 var proxyCalledWithHost string 336 var proxyCalledWithAuth bool 337 var proxyCalledWithAuthHeader string 338 if testCase.proxyServerFunc != nil { 339 proxyHandler := utilnettesting.NewHTTPProxyHandler(t, func(req *http.Request) bool { 340 proxyCalledWithHost = req.Host 341 342 proxyAuthHeaderName := "Proxy-Authorization" 343 _, proxyCalledWithAuth = req.Header[proxyAuthHeaderName] 344 proxyCalledWithAuthHeader = req.Header.Get(proxyAuthHeaderName) 345 return true 346 }) 347 defer proxyHandler.Wait() 348 349 proxy := testCase.proxyServerFunc(proxyHandler) 350 defer proxy.Close() 351 352 t.Logf("Proxy URL: %v", proxy.URL) 353 354 spdyTransport.proxier = func(proxierReq *http.Request) (*url.URL, error) { 355 proxierCalled = true 356 proxyURL, err := url.Parse(proxy.URL) 357 if err != nil { 358 return nil, err 359 } 360 proxyURL.User = testCase.proxyAuth 361 return proxyURL, nil 362 } 363 } 364 365 client := &http.Client{Transport: spdyTransport} 366 367 resp, err := client.Do(req) 368 var conn httpstream.Connection 369 if err == nil { 370 conn, err = spdyTransport.NewConnection(resp) 371 } 372 haveErr := err != nil 373 if e, a := testCase.shouldError, haveErr; e != a { 374 t.Fatalf("shouldError=%t, got %t: %v", e, a, err) 375 } 376 if testCase.shouldError { 377 return 378 } 379 defer conn.Close() 380 381 if resp.StatusCode != http.StatusSwitchingProtocols { 382 t.Fatalf("expected http 101 switching protocols, got %d", resp.StatusCode) 383 } 384 385 stream, err := conn.CreateStream(http.Header{}) 386 if err != nil { 387 t.Fatalf("error creating client stream: %s", err) 388 } 389 390 n, err := stream.Write([]byte("hello")) 391 if err != nil { 392 t.Fatalf("error writing to stream: %s", err) 393 } 394 if n != 5 { 395 t.Fatalf("expected to write 5 bytes, but actually wrote %d", n) 396 } 397 398 b := make([]byte, 5) 399 n, err = stream.Read(b) 400 if err != nil { 401 t.Fatalf("error reading from stream: %s", err) 402 } 403 if n != 5 { 404 t.Fatalf("expected to read 5 bytes, but actually read %d", n) 405 } 406 if e, a := "hello", string(b[0:n]); e != a { 407 t.Fatalf("expected '%s', got '%s'", e, a) 408 } 409 410 if testCase.proxyServerFunc != nil { 411 if !proxierCalled { 412 t.Fatal("expected to use a proxy but proxier in SpdyRoundTripper wasn't called") 413 } 414 if proxyCalledWithHost != serverURL.Host { 415 t.Fatalf("expected to see a call to the proxy for backend %q, got %q", serverURL.Host, proxyCalledWithHost) 416 } 417 } 418 419 if testCase.proxyAuth != nil { 420 expectedUsername := testCase.proxyAuth.Username() 421 expectedPassword, _ := testCase.proxyAuth.Password() 422 username, password, ok := (&http.Request{Header: http.Header{"Authorization": []string{proxyCalledWithAuthHeader}}}).BasicAuth() 423 if !ok { 424 t.Fatalf("invalid proxy auth header %s", proxyCalledWithAuthHeader) 425 } 426 if username != expectedUsername || password != expectedPassword { 427 t.Fatalf("expected proxy auth \"%s:%s\", got \"%s:%s\"", expectedUsername, expectedPassword, username, password) 428 } 429 } else if proxyCalledWithAuth { 430 t.Fatalf("proxy authorization unexpected, got %q", proxyCalledWithAuthHeader) 431 } 432 }) 433 } 434 } 435 436 // Tests SpdyRoundTripper constructors 437 func TestRoundTripConstuctor(t *testing.T) { 438 testCases := map[string]struct { 439 tlsConfig *tls.Config 440 proxier func(req *http.Request) (*url.URL, error) 441 upgradeTransport http.RoundTripper 442 expectedTLSConfig *tls.Config 443 errMsg string 444 }{ 445 "Basic TLSConfig; no error": { 446 tlsConfig: &tls.Config{InsecureSkipVerify: true}, 447 expectedTLSConfig: &tls.Config{InsecureSkipVerify: true}, 448 upgradeTransport: nil, 449 }, 450 "Basic TLSConfig and Proxier: no error": { 451 tlsConfig: &tls.Config{InsecureSkipVerify: true}, 452 proxier: func(req *http.Request) (*url.URL, error) { return nil, nil }, 453 expectedTLSConfig: &tls.Config{InsecureSkipVerify: true}, 454 upgradeTransport: nil, 455 }, 456 "TLSConfig with UpgradeTransport: error": { 457 tlsConfig: &tls.Config{InsecureSkipVerify: true}, 458 upgradeTransport: &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}}, 459 expectedTLSConfig: &tls.Config{InsecureSkipVerify: true}, 460 errMsg: "SpdyRoundTripper: UpgradeTransport is mutually exclusive to TLSConfig or Proxier", 461 }, 462 "Proxier with UpgradeTransport: error": { 463 proxier: func(req *http.Request) (*url.URL, error) { return nil, nil }, 464 upgradeTransport: &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}}, 465 expectedTLSConfig: &tls.Config{InsecureSkipVerify: true}, 466 errMsg: "SpdyRoundTripper: UpgradeTransport is mutually exclusive to TLSConfig or Proxier", 467 }, 468 "Only UpgradeTransport: no error": { 469 upgradeTransport: &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}}, 470 expectedTLSConfig: &tls.Config{InsecureSkipVerify: true}, 471 }, 472 } 473 for name, testCase := range testCases { 474 t.Run(name, func(t *testing.T) { 475 spdyRoundTripper, err := NewRoundTripperWithConfig( 476 RoundTripperConfig{ 477 TLS: testCase.tlsConfig, 478 Proxier: testCase.proxier, 479 UpgradeTransport: testCase.upgradeTransport, 480 }, 481 ) 482 if testCase.errMsg != "" { 483 if err == nil { 484 t.Fatalf("expected error but received none") 485 } 486 if !strings.Contains(err.Error(), testCase.errMsg) { 487 t.Fatalf("expected error message (%s), got (%s)", err.Error(), testCase.errMsg) 488 } 489 } 490 if testCase.errMsg == "" { 491 if err != nil { 492 t.Fatalf("unexpected error received: %v", err) 493 } 494 actualTLSConfig := spdyRoundTripper.TLSClientConfig() 495 if !reflect.DeepEqual(testCase.expectedTLSConfig, actualTLSConfig) { 496 t.Errorf("expected TLSConfig (%v), got (%v)", 497 testCase.expectedTLSConfig, actualTLSConfig) 498 } 499 } 500 }) 501 } 502 } 503 504 type Interceptor struct { 505 Authorization socks5.AuthContext 506 proxyCalledWithHost *string 507 } 508 509 func (i *Interceptor) GetAuthContext() (int, map[string]string) { 510 return int(i.Authorization.Method), i.Authorization.Payload 511 } 512 513 func (i *Interceptor) Rewrite(ctx context.Context, req *socks5.Request) (context.Context, *socks5.AddrSpec) { 514 *i.proxyCalledWithHost = req.DestAddr.Address() 515 i.Authorization = socks5.AuthContext(*req.AuthContext) 516 return ctx, req.DestAddr 517 } 518 519 // be sure to unset environment variable https_proxy (if exported) before testing, otherwise the testing will fail unexpectedly. 520 func TestRoundTripSocks5AndNewConnection(t *testing.T) { 521 localhostPool := localhostCertPool(t) 522 523 socks5Server := func(creds *socks5.StaticCredentials, interceptor *Interceptor) *socks5.Server { 524 var conf *socks5.Config 525 if creds != nil { 526 authenticator := socks5.UserPassAuthenticator{Credentials: creds} 527 conf = &socks5.Config{ 528 AuthMethods: []socks5.Authenticator{authenticator}, 529 Rewriter: interceptor, 530 } 531 } else { 532 conf = &socks5.Config{Rewriter: interceptor} 533 } 534 535 ts, err := socks5.New(conf) 536 if err != nil { 537 t.Errorf("failed to create sock5 server: %v", err) 538 } 539 return ts 540 } 541 542 testCases := map[string]struct { 543 clientTLS *tls.Config 544 proxyAuth *url.Userinfo 545 serverConnectionHeader string 546 serverFunc serverFunc 547 serverStatusCode int 548 serverUpgradeHeader string 549 shouldError bool 550 }{ 551 "proxied without auth -> http": { 552 serverFunc: httptest.NewServer, 553 serverConnectionHeader: "Upgrade", 554 serverStatusCode: http.StatusSwitchingProtocols, 555 serverUpgradeHeader: "SPDY/3.1", 556 shouldError: false, 557 }, 558 "proxied with invalid auth -> http": { 559 serverFunc: httptest.NewServer, 560 proxyAuth: url.UserPassword("invalid", "auth"), 561 serverConnectionHeader: "Upgrade", 562 serverStatusCode: http.StatusSwitchingProtocols, 563 serverUpgradeHeader: "SPDY/3.1", 564 shouldError: true, 565 }, 566 "proxied with valid auth -> http": { 567 serverFunc: httptest.NewServer, 568 proxyAuth: url.UserPassword("proxyuser", "proxypasswd"), 569 serverConnectionHeader: "Upgrade", 570 serverStatusCode: http.StatusSwitchingProtocols, 571 serverUpgradeHeader: "SPDY/3.1", 572 shouldError: false, 573 }, 574 "proxied with valid auth -> https (invalid hostname + InsecureSkipVerify)": { 575 serverFunc: httpsServerInvalidHostname(t), 576 proxyAuth: url.UserPassword("proxyuser", "proxypasswd"), 577 clientTLS: &tls.Config{InsecureSkipVerify: true}, 578 serverConnectionHeader: "Upgrade", 579 serverUpgradeHeader: "SPDY/3.1", 580 serverStatusCode: http.StatusSwitchingProtocols, 581 shouldError: false, 582 }, 583 "proxied with valid auth -> https (invalid hostname + hostname verification)": { 584 serverFunc: httpsServerInvalidHostname(t), 585 proxyAuth: url.UserPassword("proxyuser", "proxypasswd"), 586 clientTLS: &tls.Config{InsecureSkipVerify: false}, 587 serverConnectionHeader: "Upgrade", 588 serverUpgradeHeader: "SPDY/3.1", 589 serverStatusCode: http.StatusSwitchingProtocols, 590 shouldError: true, 591 }, 592 "proxied with valid auth -> https (valid hostname + RootCAs)": { 593 serverFunc: httpsServerValidHostname(t), 594 proxyAuth: url.UserPassword("proxyuser", "proxypasswd"), 595 clientTLS: &tls.Config{RootCAs: localhostPool}, 596 serverConnectionHeader: "Upgrade", 597 serverUpgradeHeader: "SPDY/3.1", 598 serverStatusCode: http.StatusSwitchingProtocols, 599 shouldError: false, 600 }, 601 } 602 603 for name, testCase := range testCases { 604 t.Run(name, func(t *testing.T) { 605 server := testCase.serverFunc(serverHandler( 606 t, serverHandlerConfig{ 607 shouldError: testCase.shouldError, 608 statusCode: testCase.serverStatusCode, 609 connectionHeader: testCase.serverConnectionHeader, 610 upgradeHeader: testCase.serverUpgradeHeader, 611 }, 612 )) 613 defer server.Close() 614 615 req, err := http.NewRequest("GET", server.URL, nil) 616 if err != nil { 617 t.Fatalf("error creating request: %s", err) 618 } 619 620 spdyTransport, err := NewRoundTripper(testCase.clientTLS) 621 if err != nil { 622 t.Fatalf("error creating SpdyRoundTripper: %v", err) 623 } 624 var proxierCalled bool 625 var proxyCalledWithHost string 626 627 interceptor := &Interceptor{proxyCalledWithHost: &proxyCalledWithHost} 628 629 proxyHandler := socks5Server(nil, interceptor) 630 631 if testCase.proxyAuth != nil { 632 proxyHandler = socks5Server(&socks5.StaticCredentials{ 633 "proxyuser": "proxypasswd", // Socks5 server static credentials when client authentication is expected 634 }, interceptor) 635 } 636 637 closed := make(chan struct{}) 638 isClosed := func() bool { 639 select { 640 case <-closed: 641 return true 642 default: 643 return false 644 } 645 } 646 647 l, err := net.Listen("tcp", "127.0.0.1:0") 648 if err != nil { 649 t.Fatalf("socks5Server: proxy_test: Listen: %v", err) 650 } 651 defer l.Close() 652 653 go func(shoulderror bool) { 654 conn, err := l.Accept() 655 if err != nil { 656 if isClosed() { 657 return 658 } 659 660 t.Errorf("error accepting connection: %s", err) 661 } 662 663 if err := proxyHandler.ServeConn(conn); err != nil && !shoulderror { 664 // If the connection request is closed before the channel is closed 665 // the test will fail with a ServeConn error. Since the test only return 666 // early if expects shouldError=true, the channel is closed at the end of 667 // the test, just before all the deferred connections Close() are executed. 668 if isClosed() { 669 return 670 } 671 672 t.Errorf("ServeConn error: %s", err) 673 } 674 }(testCase.shouldError) 675 spdyTransport.proxier = func(proxierReq *http.Request) (*url.URL, error) { 676 proxierCalled = true 677 return &url.URL{ 678 Scheme: "socks5", 679 Host: net.JoinHostPort("127.0.0.1", strconv.Itoa(l.Addr().(*net.TCPAddr).Port)), 680 User: testCase.proxyAuth, 681 }, nil 682 } 683 684 client := &http.Client{Transport: spdyTransport} 685 686 resp, err := client.Do(req) 687 haveErr := err != nil 688 if e, a := testCase.shouldError, haveErr; e != a { 689 t.Fatalf("shouldError=%t, got %t: %v", e, a, err) 690 } 691 if testCase.shouldError { 692 return 693 } 694 695 conn, err := spdyTransport.NewConnection(resp) 696 haveErr = err != nil 697 if e, a := testCase.shouldError, haveErr; e != a { 698 t.Fatalf("shouldError=%t, got %t: %v", e, a, err) 699 } 700 if testCase.shouldError { 701 return 702 } 703 704 defer conn.Close() 705 706 if resp.StatusCode != http.StatusSwitchingProtocols { 707 t.Fatalf("expected http 101 switching protocols, got %d", resp.StatusCode) 708 } 709 710 stream, err := conn.CreateStream(http.Header{}) 711 if err != nil { 712 t.Fatalf("error creating client stream: %s", err) 713 } 714 715 n, err := stream.Write([]byte("hello")) 716 if err != nil { 717 t.Fatalf("error writing to stream: %s", err) 718 } 719 if n != 5 { 720 t.Fatalf("expected to write 5 bytes, but actually wrote %d", n) 721 } 722 723 b := make([]byte, 5) 724 n, err = stream.Read(b) 725 if err != nil { 726 t.Fatalf("error reading from stream: %s", err) 727 } 728 if n != 5 { 729 t.Fatalf("expected to read 5 bytes, but actually read %d", n) 730 } 731 if e, a := "hello", string(b[0:n]); e != a { 732 t.Fatalf("expected '%s', got '%s'", e, a) 733 } 734 735 if !proxierCalled { 736 t.Fatal("xpected to use a proxy but proxier in SpdyRoundTripper wasn't called") 737 } 738 739 serverURL, err := url.Parse(server.URL) 740 if err != nil { 741 t.Fatalf("error creating request: %s", err) 742 } 743 if proxyCalledWithHost != serverURL.Host { 744 t.Fatalf("expected to see a call to the proxy for backend %q, got %q", serverURL.Host, proxyCalledWithHost) 745 } 746 747 authMethod, authUser := interceptor.GetAuthContext() 748 749 if testCase.proxyAuth != nil { 750 expectedSocks5AuthMethod := 2 751 expectedSocks5AuthUser := "proxyuser" 752 753 if expectedSocks5AuthMethod != authMethod { 754 t.Fatalf("socks5 Proxy authorization unexpected, got %d, expected %d", authMethod, expectedSocks5AuthMethod) 755 } 756 757 if expectedSocks5AuthUser != authUser["Username"] { 758 t.Fatalf("socks5 Proxy authorization user unexpected, got %q, expected %q", authUser["Username"], expectedSocks5AuthUser) 759 } 760 } else { 761 if authMethod != 0 { 762 t.Fatalf("proxy authentication method unexpected, got %d", authMethod) 763 } 764 if len(authUser) != 0 { 765 t.Fatalf("unexpected proxy user: %v", authUser) 766 } 767 } 768 769 // The channel must be closed before any of the connections are closed 770 close(closed) 771 }) 772 } 773 } 774 775 func TestRoundTripPassesContextToDialer(t *testing.T) { 776 urls := []string{"http://127.0.0.1:1233/", "https://127.0.0.1:1233/"} 777 for _, u := range urls { 778 t.Run(u, func(t *testing.T) { 779 ctx, cancel := context.WithCancel(context.Background()) 780 cancel() 781 req, err := http.NewRequestWithContext(ctx, "GET", u, nil) 782 require.NoError(t, err) 783 spdyTransport, err := NewRoundTripper(&tls.Config{}) 784 if err != nil { 785 t.Fatalf("error creating SpdyRoundTripper: %v", err) 786 } 787 _, err = spdyTransport.Dial(req) 788 assert.EqualError(t, err, "dial tcp 127.0.0.1:1233: operation was canceled") 789 }) 790 } 791 } 792 793 // exampleCert was generated from crypto/tls/generate_cert.go with the following command: 794 // 795 // go run generate_cert.go --rsa-bits 2048 --host example.com --ca --start-date "Jan 1 00:00:00 1970" --duration=1000000h 796 var exampleCert = []byte(`-----BEGIN CERTIFICATE----- 797 MIIDADCCAeigAwIBAgIQVHG3Fn9SdWayyLOZKCW1vzANBgkqhkiG9w0BAQsFADAS 798 MRAwDgYDVQQKEwdBY21lIENvMCAXDTcwMDEwMTAwMDAwMFoYDzIwODQwMTI5MTYw 799 MDAwWjASMRAwDgYDVQQKEwdBY21lIENvMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A 800 MIIBCgKCAQEArTCu9fiIclNgDdWHphewM+JW55dCb5yYGlJgCBvwbOx547M9p+tn 801 zm9QOhsdZDHDZsG9tqnWxE2Nc1HpIJyOlfYsOoonpEoG/Ep6nnK91ngj0bn/JlNy 802 +i/bwU4r97MOukvnOIQez9/D9jAJaOX2+b8/d4lRz9BsqiwJyg+ynZ5tVVYj7aMi 803 vXnd6HOnJmtqutOtr3beucJnkd6XbwRkLUcAYATT+ZihOWRbTuKqhCg6zGkJOoUG 804 f8sX61JjoilxiURA//ftGVbdTCU3DrmGmardp5NNOHbumMYU8Vhmqgx1Bqxb+9he 805 7G42uW5YWYK/GqJzgVPjjlB2dOGj9KrEWQIDAQABo1AwTjAOBgNVHQ8BAf8EBAMC 806 AqQwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDwYDVR0TAQH/BAUwAwEB/zAWBgNVHREE 807 DzANggtleGFtcGxlLmNvbTANBgkqhkiG9w0BAQsFAAOCAQEAig4AIi9xWs1+pLES 808 eeGGdSDoclplFpcbXANnsYYFyLf+8pcWgVi2bOmb2gXMbHFkB07MA82wRJAUTaA+ 809 2iNXVQMhPCoA7J6ADUbww9doJX2S9HGyArhiV/MhHtE8txzMn2EKNLdhhk3N9rmV 810 x/qRbWAY1U2z4BpdrAR87Fe81Nlj7h45csW9K+eS+NgXipiNTIfEShKgCFM8EdxL 811 1WXg7r9AvYV3TNDPWTjLsm1rQzzZQ7Uvcf6deWiNodZd8MOT/BFLclDPTK6cF2Hr 812 UU4dq6G4kCwMSxWE4cM3HlZ4u1dyIt47VbkP0rtvkBCXx36y+NXYA5lzntchNFZP 813 uvEQdw== 814 -----END CERTIFICATE-----`) 815 816 var exampleKey = []byte(`-----BEGIN RSA PRIVATE KEY----- 817 MIIEpQIBAAKCAQEArTCu9fiIclNgDdWHphewM+JW55dCb5yYGlJgCBvwbOx547M9 818 p+tnzm9QOhsdZDHDZsG9tqnWxE2Nc1HpIJyOlfYsOoonpEoG/Ep6nnK91ngj0bn/ 819 JlNy+i/bwU4r97MOukvnOIQez9/D9jAJaOX2+b8/d4lRz9BsqiwJyg+ynZ5tVVYj 820 7aMivXnd6HOnJmtqutOtr3beucJnkd6XbwRkLUcAYATT+ZihOWRbTuKqhCg6zGkJ 821 OoUGf8sX61JjoilxiURA//ftGVbdTCU3DrmGmardp5NNOHbumMYU8Vhmqgx1Bqxb 822 +9he7G42uW5YWYK/GqJzgVPjjlB2dOGj9KrEWQIDAQABAoIBAQClt4CiYaaF5ltx 823 wVDjz6TNcJUBUs3CKE+uWAYFnF5Ii1nyU876Pxj8Aaz9fHZ6Kde0GkwiXY7gFOj1 824 YHo2tzcELSKS/SEDZcYbYFTGCjq13g1AH74R+SV6WZLn+5m8kPvVrM1ZWap188H5 825 bmuCkRDqVmIvShkbRW7EwhC35J9fiuW3majC/sjmsxtxyP6geWmu4f5/Ttqahcdb 826 osPZIgIIPzqAkNtkLTi7+meHYI9wlrGhL7XZTwnJ1Oc/Y67zzmbthLYB5YFSLUew 827 rXT58jtSjX4gbiQyheBSrWxW08QE4qYg6jJlAdffHhWv72hJW2MCXhuXp8gJs/Do 828 XLRHGwSBAoGBAMdNtsbe4yae/QeHUPGxNW0ipa0yoTF6i+VYoxvqiRMzDM3+3L8k 829 dgI1rr4330SivqDahMA/odWtM/9rVwJI2B2QhZLMHA0n9ytH007OO9TghgVB12nN 830 xosRYBpKdHXyyvV/MUZl7Jux6zKIzRDWOkF95VVYPcAaxJqd1E5/jJ6JAoGBAN51 831 QrebA1w/jfydeqQTz1sK01sbO4HYj4qGfo/JarVqGEkm1azeBBPPRnHz3jNKnCkM 832 S4PpqRDased3NIcViXlAgoqPqivZ8mQa/Rb146l7WaTErASHsZ023OGrxsr/Ed6N 833 P3GrmvxVJjebaFNaQ9sP80dLkpgeas0t2TY8iQNRAoGATOcnx8TpUVW3vNfx29DN 834 FLdxxkrq9/SZVn3FMlhlXAsuva3B799ZybB9JNjaRdmmRNsMrkHfaFvU3JHGmRMS 835 kRXa9LHdgRYSwZiNaLMbUyDvlce6HxFPswmZU4u3NGvi9KeHk+pwSgN1BaLTvdNr 836 1ymE/FF4QlAR3LdZ3JBK6kECgYEA0wW4/CJ31ZIURoW8SNjh4iMqy0nR8SJVR7q9 837 Y/hU2TKDRyEnoIwaohAFayNCrLUh3W5kVAXa8roB+OgDVAECH5sqOfZ+HorofD19 838 x8II7ESujLZj1whBXDkm3ovsT7QWZ17lyBZZNvQvBKDPHgKKS8udowv1S4fPGENd 839 wS07a4ECgYEAwLSbmMIVJme0jFjsp5d1wOGA2Qi2ZwGIAVlsbnJtygrU/hSBfnu8 840 VfyJSCgg3fPe7kChWKlfcOebVKSb68LKRsz1Lz1KdbY0HOJFp/cT4lKmDAlRY9gq 841 LB4rdf46lV0mUkvd2/oofIbTrzukjQSnyfLawb/2uJGV1IkTcZcn9CI= 842 -----END RSA PRIVATE KEY-----`) 843 844 // localhostCert was generated from crypto/tls/generate_cert.go with the following command: 845 // 846 // go run generate_cert.go --rsa-bits 2048 --host 127.0.0.1,::1,example.com --ca --start-date "Jan 1 00:00:00 1970" --duration=1000000h 847 var localhostCert = []byte(`-----BEGIN CERTIFICATE----- 848 MIIDGTCCAgGgAwIBAgIRALL5AZcefF4kkYV1SEG6YrMwDQYJKoZIhvcNAQELBQAw 849 EjEQMA4GA1UEChMHQWNtZSBDbzAgFw03MDAxMDEwMDAwMDBaGA8yMDg0MDEyOTE2 850 MDAwMFowEjEQMA4GA1UEChMHQWNtZSBDbzCCASIwDQYJKoZIhvcNAQEBBQADggEP 851 ADCCAQoCggEBALQ/FHcyVwdFHxARbbD2KBtDUT7Eni+8ioNdjtGcmtXqBv45EC1C 852 JOqqGJTroFGJ6Q9kQIZ9FqH5IJR2fOOJD9kOTueG4Vt1JY1rj1Kbpjefu8XleZ5L 853 SBwIWVnN/lEsEbuKmj7N2gLt5AH3zMZiBI1mg1u9Z5ZZHYbCiTpBrwsq6cTlvR9g 854 dyo1YkM5hRESCzsrL0aUByoo0qRMD8ZsgANJwgsiO0/M6idbxDwv1BnGwGmRYvOE 855 Hxpy3v0Jg7GJYrvnpnifJTs4nw91N5X9pXxR7FFzi/6HTYDWRljvTb0w6XciKYAz 856 bWZ0+cJr5F7wB7ovlbm7HrQIR7z7EIIu2d8CAwEAAaNoMGYwDgYDVR0PAQH/BAQD 857 AgKkMBMGA1UdJQQMMAoGCCsGAQUFBwMBMA8GA1UdEwEB/wQFMAMBAf8wLgYDVR0R 858 BCcwJYILZXhhbXBsZS5jb22HBH8AAAGHEAAAAAAAAAAAAAAAAAAAAAEwDQYJKoZI 859 hvcNAQELBQADggEBAFPPWopNEJtIA2VFAQcqN6uJK+JVFOnjGRoCrM6Xgzdm0wxY 860 XCGjsxY5dl+V7KzdGqu858rCaq5osEBqypBpYAnS9C38VyCDA1vPS1PsN8SYv48z 861 DyBwj+7R2qar0ADBhnhWxvYO9M72lN/wuCqFKYMeFSnJdQLv3AsrrHe9lYqOa36s 862 8wxSwVTFTYXBzljPEnSaaJMPqFD8JXaZK1ryJPkO5OsCNQNGtatNiWAf3DcmwHAT 863 MGYMzP0u4nw47aRz9shB8w+taPKHx2BVwE1m/yp3nHVioOjXqA1fwRQVGclCJSH1 864 D2iq3hWVHRENgjTjANBPICLo9AZ4JfN6PH19mnU= 865 -----END CERTIFICATE-----`) 866 867 // localhostKey is the private key for localhostCert. 868 var localhostKey = []byte(`-----BEGIN RSA PRIVATE KEY----- 869 MIIEogIBAAKCAQEAtD8UdzJXB0UfEBFtsPYoG0NRPsSeL7yKg12O0Zya1eoG/jkQ 870 LUIk6qoYlOugUYnpD2RAhn0WofkglHZ844kP2Q5O54bhW3UljWuPUpumN5+7xeV5 871 nktIHAhZWc3+USwRu4qaPs3aAu3kAffMxmIEjWaDW71nllkdhsKJOkGvCyrpxOW9 872 H2B3KjViQzmFERILOysvRpQHKijSpEwPxmyAA0nCCyI7T8zqJ1vEPC/UGcbAaZFi 873 84QfGnLe/QmDsYliu+emeJ8lOzifD3U3lf2lfFHsUXOL/odNgNZGWO9NvTDpdyIp 874 gDNtZnT5wmvkXvAHui+VubsetAhHvPsQgi7Z3wIDAQABAoIBAGmw93IxjYCQ0ncc 875 kSKMJNZfsdtJdaxuNRZ0nNNirhQzR2h403iGaZlEpmdkhzxozsWcto1l+gh+SdFk 876 bTUK4MUZM8FlgO2dEqkLYh5BcMT7ICMZvSfJ4v21E5eqR68XVUqQKoQbNvQyxFk3 877 EddeEGdNrkb0GDK8DKlBlzAW5ep4gjG85wSTjR+J+muUv3R0BgLBFSuQnIDM/IMB 878 LWqsja/QbtB7yppe7jL5u8UCFdZG8BBKT9fcvFIu5PRLO3MO0uOI7LTc8+W1Xm23 879 uv+j3SY0+v+6POjK0UlJFFi/wkSPTFIfrQO1qFBkTDQHhQ6q/7GnILYYOiGbIRg2 880 NNuP52ECgYEAzXEoy50wSYh8xfFaBuxbm3ruuG2W49jgop7ZfoFrPWwOQKAZS441 881 VIwV4+e5IcA6KkuYbtGSdTYqK1SMkgnUyD/VevwAqH5TJoEIGu0pDuKGwVuwqioZ 882 frCIAV5GllKyUJ55VZNbRr2vY2fCsWbaCSCHETn6C16DNuTCe5C0JBECgYEA4JqY 883 5GpNbMG8fOt4H7hU0Fbm2yd6SHJcQ3/9iimef7xG6ajxsYrIhg1ft+3IPHMjVI0+ 884 9brwHDnWg4bOOx/VO4VJBt6Dm/F33bndnZRkuIjfSNpLM51P+EnRdaFVHOJHwKqx 885 uF69kihifCAG7YATgCveeXImzBUSyZUz9UrETu8CgYARNBimdFNG1RcdvEg9rC0/ 886 p9u1tfecvNySwZqU7WF9kz7eSonTueTdX521qAHowaAdSpdJMGODTTXaywm6cPhQ 887 jIfj9JZZhbqQzt1O4+08Qdvm9TamCUB5S28YLjza+bHU7nBaqixKkDfPqzCyilpX 888 yVGGL8SwjwmN3zop/sQXAQKBgC0JMsESQ6YcDsRpnrOVjYQc+LtW5iEitTdfsaID 889 iGGKihmOI7B66IxgoCHMTws39wycKdSyADVYr5e97xpR3rrJlgQHmBIrz+Iow7Q2 890 LiAGaec8xjl6QK/DdXmFuQBKqyKJ14rljFODP4QuE9WJid94bGqjpf3j99ltznZP 891 4J8HAoGAJb4eb4lu4UGwifDzqfAPzLGCoi0fE1/hSx34lfuLcc1G+LEu9YDKoOVJ 892 9suOh0b5K/bfEy9KrVMBBriduvdaERSD8S3pkIQaitIz0B029AbE4FLFf9lKQpP2 893 KR8NJEkK99Vh/tew6jAMll70xFrE7aF8VLXJVE7w4sQzuvHxl9Q= 894 -----END RSA PRIVATE KEY----- 895 `)