diff --git a/tools/remotecommand/fallback_test.go b/tools/remotecommand/fallback_test.go index 0dcca2063..b82c7bedf 100644 --- a/tools/remotecommand/fallback_test.go +++ b/tools/remotecommand/fallback_test.go @@ -169,6 +169,67 @@ func TestFallbackClient_SPDYSecondarySucceeds(t *testing.T) { } } +func TestFallbackClient_PrimaryAndSecondaryFail(t *testing.T) { + // Create fake WebSocket server. Copy received STDIN data back onto STDOUT stream. + websocketServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + conns, err := webSocketServerStreams(req, w, streamOptionsFromRequest(req)) + if err != nil { + w.WriteHeader(http.StatusForbidden) + return + } + defer conns.conn.Close() + // Loopback the STDIN stream onto the STDOUT stream. + _, err = io.Copy(conns.stdoutStream, conns.stdinStream) + require.NoError(t, err) + })) + defer websocketServer.Close() + + // Now create the fallback client (executor), and point it to the "websocketServer". + // Must add STDIN and STDOUT query params for the client request. + websocketServer.URL = websocketServer.URL + "?" + "stdin=true" + "&" + "stdout=true" + websocketLocation, err := url.Parse(websocketServer.URL) + require.NoError(t, err) + websocketExecutor, err := NewWebSocketExecutor(&rest.Config{Host: websocketLocation.Host}, "GET", websocketServer.URL) + require.NoError(t, err) + spdyExecutor, err := NewSPDYExecutor(&rest.Config{Host: websocketLocation.Host}, "POST", websocketLocation) + require.NoError(t, err) + // Always fallback to spdyExecutor, but spdyExecutor fails against websocket server. + exec, err := NewFallbackExecutor(websocketExecutor, spdyExecutor, func(error) bool { return true }) + require.NoError(t, err) + // Update the websocket executor to request remote command v4, which is unsupported. + fallbackExec, ok := exec.(*FallbackExecutor) + assert.True(t, ok, "error casting executor as FallbackExecutor") + websocketExec, ok := fallbackExec.primary.(*wsStreamExecutor) + assert.True(t, ok, "error casting executor as websocket executor") + // Set the attempted subprotocol version to V4; websocket server only accepts V5. + websocketExec.protocols = []string{remotecommand.StreamProtocolV4Name} + + // Generate random data, and set it up to stream on STDIN. The data will be + // returned on the STDOUT buffer. + randomSize := 1024 * 1024 + randomData := make([]byte, randomSize) + if _, err := rand.Read(randomData); err != nil { + t.Errorf("unexpected error reading random data: %v", err) + } + var stdout bytes.Buffer + options := &StreamOptions{ + Stdin: bytes.NewReader(randomData), + Stdout: &stdout, + } + errorChan := make(chan error) + go func() { + errorChan <- exec.StreamWithContext(context.Background(), *options) + }() + + select { + case <-time.After(wait.ForeverTestTimeout): + t.Fatalf("expect stream to be closed after connection is closed.") + case err := <-errorChan: + // Ensure secondary executor returned an error. + require.Error(t, err) + } +} + // localhostCert was generated from crypto/tls/generate_cert.go with the following command: // // 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 @@ -340,64 +401,3 @@ func TestFallbackClient_WebSocketHTTPSProxyCausesSPDYFallback(t *testing.T) { t.Errorf("expected %d proxy call, got %d", e, a) } } - -func TestFallbackClient_PrimaryAndSecondaryFail(t *testing.T) { - // Create fake WebSocket server. Copy received STDIN data back onto STDOUT stream. - websocketServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - conns, err := webSocketServerStreams(req, w, streamOptionsFromRequest(req)) - if err != nil { - w.WriteHeader(http.StatusForbidden) - return - } - defer conns.conn.Close() - // Loopback the STDIN stream onto the STDOUT stream. - _, err = io.Copy(conns.stdoutStream, conns.stdinStream) - require.NoError(t, err) - })) - defer websocketServer.Close() - - // Now create the fallback client (executor), and point it to the "websocketServer". - // Must add STDIN and STDOUT query params for the client request. - websocketServer.URL = websocketServer.URL + "?" + "stdin=true" + "&" + "stdout=true" - websocketLocation, err := url.Parse(websocketServer.URL) - require.NoError(t, err) - websocketExecutor, err := NewWebSocketExecutor(&rest.Config{Host: websocketLocation.Host}, "GET", websocketServer.URL) - require.NoError(t, err) - spdyExecutor, err := NewSPDYExecutor(&rest.Config{Host: websocketLocation.Host}, "POST", websocketLocation) - require.NoError(t, err) - // Always fallback to spdyExecutor, but spdyExecutor fails against websocket server. - exec, err := NewFallbackExecutor(websocketExecutor, spdyExecutor, func(error) bool { return true }) - require.NoError(t, err) - // Update the websocket executor to request remote command v4, which is unsupported. - fallbackExec, ok := exec.(*FallbackExecutor) - assert.True(t, ok, "error casting executor as FallbackExecutor") - websocketExec, ok := fallbackExec.primary.(*wsStreamExecutor) - assert.True(t, ok, "error casting executor as websocket executor") - // Set the attempted subprotocol version to V4; websocket server only accepts V5. - websocketExec.protocols = []string{remotecommand.StreamProtocolV4Name} - - // Generate random data, and set it up to stream on STDIN. The data will be - // returned on the STDOUT buffer. - randomSize := 1024 * 1024 - randomData := make([]byte, randomSize) - if _, err := rand.Read(randomData); err != nil { - t.Errorf("unexpected error reading random data: %v", err) - } - var stdout bytes.Buffer - options := &StreamOptions{ - Stdin: bytes.NewReader(randomData), - Stdout: &stdout, - } - errorChan := make(chan error) - go func() { - errorChan <- exec.StreamWithContext(context.Background(), *options) - }() - - select { - case <-time.After(wait.ForeverTestTimeout): - t.Fatalf("expect stream to be closed after connection is closed.") - case err := <-errorChan: - // Ensure secondary executor returned an error. - require.Error(t, err) - } -} diff --git a/tools/remotecommand/websocket_test.go b/tools/remotecommand/websocket_test.go index e1b69c004..b70afcacb 100644 --- a/tools/remotecommand/websocket_test.go +++ b/tools/remotecommand/websocket_test.go @@ -815,42 +815,6 @@ func TestWebSocketClient_BadHandshake(t *testing.T) { } } -// See (https://github.com/kubernetes/kubernetes/issues/126134). -func TestWebSocketClient_HTTPSProxyErrorExpected(t *testing.T) { - urlStr := "http://127.0.0.1/never-used" + "?" + "stdin=true" + "&" + "stdout=true" - websocketLocation, err := url.Parse(urlStr) - if err != nil { - t.Fatalf("Unable to parse WebSocket server URL: %s", urlStr) - } - // proxy url with https scheme will trigger websocket dialing error. - httpsProxyFunc := func(req *http.Request) (*url.URL, error) { return url.Parse("https://127.0.0.1") } - exec, err := NewWebSocketExecutor(&rest.Config{Host: websocketLocation.Host, Proxy: httpsProxyFunc}, "GET", urlStr) - if err != nil { - t.Errorf("unexpected error creating websocket executor: %v", err) - } - var stdout bytes.Buffer - options := &StreamOptions{ - Stdout: &stdout, - } - errorChan := make(chan error) - go func() { - // Start the streaming on the WebSocket "exec" client. - errorChan <- exec.StreamWithContext(context.Background(), *options) - }() - - select { - case <-time.After(wait.ForeverTestTimeout): - t.Fatalf("expect stream to be closed after connection is closed.") - case err := <-errorChan: - if err == nil { - t.Errorf("expected error but received none") - } - if !httpstream.IsHTTPSProxyError(err) { - t.Errorf("expected https proxy error, got (%s)", err) - } - } -} - // TestWebSocketClient_HeartbeatTimeout tests the heartbeat by forcing a // timeout by setting the ping period greater than the deadline. func TestWebSocketClient_HeartbeatTimeout(t *testing.T) { @@ -1377,3 +1341,39 @@ func createWebSocketStreams(req *http.Request, w http.ResponseWriter, opts *opti return wsStreams, nil } + +// See (https://github.com/kubernetes/kubernetes/issues/126134). +func TestWebSocketClient_HTTPSProxyErrorExpected(t *testing.T) { + urlStr := "http://127.0.0.1/never-used" + "?" + "stdin=true" + "&" + "stdout=true" + websocketLocation, err := url.Parse(urlStr) + if err != nil { + t.Fatalf("Unable to parse WebSocket server URL: %s", urlStr) + } + // proxy url with https scheme will trigger websocket dialing error. + httpsProxyFunc := func(req *http.Request) (*url.URL, error) { return url.Parse("https://127.0.0.1") } + exec, err := NewWebSocketExecutor(&rest.Config{Host: websocketLocation.Host, Proxy: httpsProxyFunc}, "GET", urlStr) + if err != nil { + t.Errorf("unexpected error creating websocket executor: %v", err) + } + var stdout bytes.Buffer + options := &StreamOptions{ + Stdout: &stdout, + } + errorChan := make(chan error) + go func() { + // Start the streaming on the WebSocket "exec" client. + errorChan <- exec.StreamWithContext(context.Background(), *options) + }() + + select { + case <-time.After(wait.ForeverTestTimeout): + t.Fatalf("expect stream to be closed after connection is closed.") + case err := <-errorChan: + if err == nil { + t.Errorf("expected error but received none") + } + if !httpstream.IsHTTPSProxyError(err) { + t.Errorf("expected https proxy error, got (%s)", err) + } + } +}