Skip to content

Commit dcf60e0

Browse files
authored
Merge pull request kubernetes#92941 from tallclair/invalid-redirect
Don't return proxied redirects to the client
2 parents 165a221 + 1f991f8 commit dcf60e0

File tree

4 files changed

+62
-9
lines changed

4 files changed

+62
-9
lines changed

staging/src/k8s.io/apimachinery/pkg/util/net/http.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -453,7 +453,7 @@ redirectLoop:
453453

454454
// Only follow redirects to the same host. Otherwise, propagate the redirect response back.
455455
if requireSameHostRedirects && location.Hostname() != originalLocation.Hostname() {
456-
break redirectLoop
456+
return nil, nil, fmt.Errorf("hostname mismatch: expected %s, found %s", originalLocation.Hostname(), location.Hostname())
457457
}
458458

459459
// Reset the connection.

staging/src/k8s.io/apimachinery/pkg/util/net/http_test.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -331,13 +331,13 @@ func TestConnectWithRedirects(t *testing.T) {
331331
redirects: []string{"/1", "/2", "/3", "/4", "/5", "/6", "/7", "/8", "/9", "/10"},
332332
expectError: true,
333333
}, {
334-
desc: "redirect to different host are prevented",
335-
redirects: []string{"http://example.com/foo"},
336-
expectedRedirects: 0,
334+
desc: "redirect to different host are prevented",
335+
redirects: []string{"http://example.com/foo"},
336+
expectError: true,
337337
}, {
338-
desc: "multiple redirect to different host forbidden",
339-
redirects: []string{"/1", "/2", "/3", "http://example.com/foo"},
340-
expectedRedirects: 3,
338+
desc: "multiple redirect to different host forbidden",
339+
redirects: []string{"/1", "/2", "/3", "http://example.com/foo"},
340+
expectError: true,
341341
}, {
342342
desc: "redirect to different port is allowed",
343343
redirects: []string{"http://HOST/foo"},

staging/src/k8s.io/apimachinery/pkg/util/proxy/upgradeaware.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,16 @@ func (h *UpgradeAwareHandler) tryUpgrade(w http.ResponseWriter, req *http.Reques
304304
rawResponse = headerBytes
305305
}
306306

307+
// If the backend did not upgrade the request, return an error to the client. If the response was
308+
// an error, the error is forwarded directly after the connection is hijacked. Otherwise, just
309+
// return a generic error here.
310+
if backendHTTPResponse.StatusCode != http.StatusSwitchingProtocols && backendHTTPResponse.StatusCode < 400 {
311+
err := fmt.Errorf("invalid upgrade response: status code %d", backendHTTPResponse.StatusCode)
312+
klog.Errorf("Proxy upgrade error: %v", err)
313+
h.Responder.Error(w, req, err)
314+
return true
315+
}
316+
307317
// Once the connection is hijacked, the ErrorResponder will no longer work, so
308318
// hijacking should be the last step in the upgrade.
309319
requestHijacker, ok := w.(http.Hijacker)

staging/src/k8s.io/apimachinery/pkg/util/proxy/upgradeaware_test.go

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -512,7 +512,7 @@ func (r *noErrorsAllowed) Error(w http.ResponseWriter, req *http.Request, err er
512512
r.t.Error(err)
513513
}
514514

515-
func TestProxyUpgradeErrorResponse(t *testing.T) {
515+
func TestProxyUpgradeConnectionErrorResponse(t *testing.T) {
516516
var (
517517
responder *fakeResponder
518518
expectedErr = errors.New("EXPECTED")
@@ -560,7 +560,7 @@ func TestProxyUpgradeErrorResponse(t *testing.T) {
560560

561561
func TestProxyUpgradeErrorResponseTerminates(t *testing.T) {
562562
for _, intercept := range []bool{true, false} {
563-
for _, code := range []int{200, 400, 500} {
563+
for _, code := range []int{400, 500} {
564564
t.Run(fmt.Sprintf("intercept=%v,code=%v", intercept, code), func(t *testing.T) {
565565
// Set up a backend server
566566
backend := http.NewServeMux()
@@ -620,6 +620,49 @@ func TestProxyUpgradeErrorResponseTerminates(t *testing.T) {
620620
}
621621
}
622622

623+
func TestProxyUpgradeErrorResponse(t *testing.T) {
624+
for _, intercept := range []bool{true, false} {
625+
for _, code := range []int{200, 300, 302, 307} {
626+
t.Run(fmt.Sprintf("intercept=%v,code=%v", intercept, code), func(t *testing.T) {
627+
// Set up a backend server
628+
backend := http.NewServeMux()
629+
backend.Handle("/hello", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
630+
http.Redirect(w, r, "https://example.com/there", code)
631+
}))
632+
backendServer := httptest.NewServer(backend)
633+
defer backendServer.Close()
634+
backendServerURL, _ := url.Parse(backendServer.URL)
635+
backendServerURL.Path = "/hello"
636+
637+
// Set up a proxy pointing to a specific path on the backend
638+
proxyHandler := NewUpgradeAwareHandler(backendServerURL, nil, false, false, &fakeResponder{t: t})
639+
proxyHandler.InterceptRedirects = intercept
640+
proxyHandler.RequireSameHostRedirects = true
641+
proxy := httptest.NewServer(proxyHandler)
642+
defer proxy.Close()
643+
proxyURL, _ := url.Parse(proxy.URL)
644+
645+
conn, err := net.Dial("tcp", proxyURL.Host)
646+
require.NoError(t, err)
647+
bufferedReader := bufio.NewReader(conn)
648+
649+
// Send upgrade request resulting in a non-101 response from the backend
650+
req, _ := http.NewRequest("GET", "/", nil)
651+
req.Header.Set(httpstream.HeaderConnection, httpstream.HeaderUpgrade)
652+
require.NoError(t, req.Write(conn))
653+
// Verify we get the correct response and full message body content
654+
resp, err := http.ReadResponse(bufferedReader, nil)
655+
require.NoError(t, err)
656+
assert.Equal(t, fakeStatusCode, resp.StatusCode)
657+
resp.Body.Close()
658+
659+
// clean up
660+
conn.Close()
661+
})
662+
}
663+
}
664+
}
665+
623666
func TestDefaultProxyTransport(t *testing.T) {
624667
tests := []struct {
625668
name,

0 commit comments

Comments
 (0)