Skip to content

Commit

Permalink
Don't return proxied redirects to the client
Browse files Browse the repository at this point in the history
Kubernetes-commit: 469c439ea18cb6cc58e36357b9ead15d68122341
  • Loading branch information
tallclair authored and k8s-publishing-bot committed Jun 17, 2020
1 parent 443e908 commit b6e3ac5
Show file tree
Hide file tree
Showing 4 changed files with 62 additions and 9 deletions.
2 changes: 1 addition & 1 deletion pkg/util/net/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -446,7 +446,7 @@ redirectLoop:

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

// Reset the connection.
Expand Down
12 changes: 6 additions & 6 deletions pkg/util/net/http_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -330,13 +330,13 @@ func TestConnectWithRedirects(t *testing.T) {
redirects: []string{"/1", "/2", "/3", "/4", "/5", "/6", "/7", "/8", "/9", "/10"},
expectError: true,
}, {
desc: "redirect to different host are prevented",
redirects: []string{"http://example.com/foo"},
expectedRedirects: 0,
desc: "redirect to different host are prevented",
redirects: []string{"http://example.com/foo"},
expectError: true,
}, {
desc: "multiple redirect to different host forbidden",
redirects: []string{"/1", "/2", "/3", "http://example.com/foo"},
expectedRedirects: 3,
desc: "multiple redirect to different host forbidden",
redirects: []string{"/1", "/2", "/3", "http://example.com/foo"},
expectError: true,
}, {
desc: "redirect to different port is allowed",
redirects: []string{"http://HOST/foo"},
Expand Down
10 changes: 10 additions & 0 deletions pkg/util/proxy/upgradeaware.go
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,16 @@ func (h *UpgradeAwareHandler) tryUpgrade(w http.ResponseWriter, req *http.Reques
rawResponse = headerBytes
}

// If the backend did not upgrade the request, return an error to the client. If the response was
// an error, the error is forwarded directly after the connection is hijacked. Otherwise, just
// return a generic error here.
if backendHTTPResponse.StatusCode != http.StatusSwitchingProtocols && backendHTTPResponse.StatusCode < 400 {
err := fmt.Errorf("invalid upgrade response: status code %d", backendHTTPResponse.StatusCode)
klog.Errorf("Proxy upgrade error: %v", err)
h.Responder.Error(w, req, err)
return true
}

// Once the connection is hijacked, the ErrorResponder will no longer work, so
// hijacking should be the last step in the upgrade.
requestHijacker, ok := w.(http.Hijacker)
Expand Down
47 changes: 45 additions & 2 deletions pkg/util/proxy/upgradeaware_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -512,7 +512,7 @@ func (r *noErrorsAllowed) Error(w http.ResponseWriter, req *http.Request, err er
r.t.Error(err)
}

func TestProxyUpgradeErrorResponse(t *testing.T) {
func TestProxyUpgradeConnectionErrorResponse(t *testing.T) {
var (
responder *fakeResponder
expectedErr = errors.New("EXPECTED")
Expand Down Expand Up @@ -560,7 +560,7 @@ func TestProxyUpgradeErrorResponse(t *testing.T) {

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

func TestProxyUpgradeErrorResponse(t *testing.T) {
for _, intercept := range []bool{true, false} {
for _, code := range []int{200, 300, 302, 307} {
t.Run(fmt.Sprintf("intercept=%v,code=%v", intercept, code), func(t *testing.T) {
// Set up a backend server
backend := http.NewServeMux()
backend.Handle("/hello", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, "https://example.com/there", code)
}))
backendServer := httptest.NewServer(backend)
defer backendServer.Close()
backendServerURL, _ := url.Parse(backendServer.URL)
backendServerURL.Path = "/hello"

// Set up a proxy pointing to a specific path on the backend
proxyHandler := NewUpgradeAwareHandler(backendServerURL, nil, false, false, &fakeResponder{t: t})
proxyHandler.InterceptRedirects = intercept
proxyHandler.RequireSameHostRedirects = true
proxy := httptest.NewServer(proxyHandler)
defer proxy.Close()
proxyURL, _ := url.Parse(proxy.URL)

conn, err := net.Dial("tcp", proxyURL.Host)
require.NoError(t, err)
bufferedReader := bufio.NewReader(conn)

// Send upgrade request resulting in a non-101 response from the backend
req, _ := http.NewRequest("GET", "/", nil)
req.Header.Set(httpstream.HeaderConnection, httpstream.HeaderUpgrade)
require.NoError(t, req.Write(conn))
// Verify we get the correct response and full message body content
resp, err := http.ReadResponse(bufferedReader, nil)
require.NoError(t, err)
assert.Equal(t, fakeStatusCode, resp.StatusCode)
resp.Body.Close()

// clean up
conn.Close()
})
}
}
}

func TestDefaultProxyTransport(t *testing.T) {
tests := []struct {
name,
Expand Down

0 comments on commit b6e3ac5

Please sign in to comment.