From 46c1600ada93d131618a76d0f530233fc81ae944 Mon Sep 17 00:00:00 2001 From: Kevin Pollet Date: Mon, 27 Sep 2021 17:40:13 +0200 Subject: [PATCH] fix: forward request Host to errors middleware service Co-authored-by: Romain --- docs/content/middlewares/http/errorpages.md | 6 +++ docs/content/migration/v2.md | 8 ++++ pkg/middlewares/customerrors/custom_errors.go | 7 +-- .../customerrors/custom_errors_test.go | 48 +++++++++++-------- 4 files changed, 45 insertions(+), 24 deletions(-) diff --git a/docs/content/middlewares/http/errorpages.md b/docs/content/middlewares/http/errorpages.md index 0e825dd73..b1751ca33 100644 --- a/docs/content/middlewares/http/errorpages.md +++ b/docs/content/middlewares/http/errorpages.md @@ -8,6 +8,7 @@ It Has Never Been Easier to Say That Something Went Wrong The ErrorPage middleware returns a custom page in lieu of the default, according to configured ranges of HTTP Status codes. !!! important + The error page itself is _not_ hosted by Traefik. ## Configuration Examples @@ -112,6 +113,11 @@ The service that will serve the new requested error page. In Kubernetes, you need to reference a Kubernetes Service instead of a Traefik service. +!!! info "Host Header" + + By default, the client `Host` header value is forwarded to the configured error [service](#service). + To forward the `Host` value corresponding to the configured error service URL, the [passHostHeader](../../../routing/services/#pass-host-header) option must be set to `false`. + ### `query` The URL for the error page (hosted by `service`). You can use the `{status}` variable in the `query` option in order to insert the status code in the URL. diff --git a/docs/content/migration/v2.md b/docs/content/migration/v2.md index 778abc3d6..77b715abb 100644 --- a/docs/content/migration/v2.md +++ b/docs/content/migration/v2.md @@ -415,3 +415,11 @@ For more advanced use cases, you can use either the [RedirectScheme middleware]( Following up on the deprecation started [previously](#x509-commonname-deprecation), as the `x509ignoreCN=0` value for the `GODEBUG` is [deprecated in Go 1.17](https://tip.golang.org/doc/go1.17#crypto/x509), the legacy behavior related to the CommonName field can not be enabled at all anymore. + +## v2.5.3 to v2.5.4 + +### Errors middleware + +In `v2.5.4`, when the errors service is configured with the [`PassHostHeader`](../routing/services/index.md#pass-host-header) option to `true` (default), +the forwarded Host header value is now set to the client request Host value and not `0.0.0.0`. +Check out the [Errors middleware](../middlewares/http/errorpages.md#service) documentation for more details. diff --git a/pkg/middlewares/customerrors/custom_errors.go b/pkg/middlewares/customerrors/custom_errors.go index 0af6e6681..a2c53935f 100644 --- a/pkg/middlewares/customerrors/custom_errors.go +++ b/pkg/middlewares/customerrors/custom_errors.go @@ -27,10 +27,7 @@ var ( _ middlewares.Stateful = &codeCatcherWithCloseNotify{} ) -const ( - typeName = "customError" - backendURL = "http://0.0.0.0" -) +const typeName = "customError" type serviceBuilder interface { BuildHTTP(ctx context.Context, serviceName string) (http.Handler, error) @@ -104,7 +101,7 @@ func (c *customErrors) ServeHTTP(rw http.ResponseWriter, req *http.Request) { query = strings.ReplaceAll(query, "{status}", strconv.Itoa(code)) } - pageReq, err := newRequest(backendURL + query) + pageReq, err := newRequest("http://" + req.Host + query) if err != nil { logger.Error(err) rw.WriteHeader(code) diff --git a/pkg/middlewares/customerrors/custom_errors_test.go b/pkg/middlewares/customerrors/custom_errors_test.go index 025851871..1e263d686 100644 --- a/pkg/middlewares/customerrors/custom_errors_test.go +++ b/pkg/middlewares/customerrors/custom_errors_test.go @@ -26,7 +26,7 @@ func TestHandler(t *testing.T) { errorPage: &dynamic.ErrorPage{Service: "error", Query: "/test", Status: []string{"500-501", "503-599"}}, backendCode: http.StatusOK, backendErrorHandler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - fmt.Fprintln(w, "My error page.") + _, _ = fmt.Fprintln(w, "My error page.") }), validate: func(t *testing.T, recorder *httptest.ResponseRecorder) { t.Helper() @@ -39,7 +39,7 @@ func TestHandler(t *testing.T) { errorPage: &dynamic.ErrorPage{Service: "error", Query: "/test", Status: []string{"500-501", "503-599"}}, backendCode: http.StatusPartialContent, backendErrorHandler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - fmt.Fprintln(w, "My error page.") + _, _ = fmt.Fprintln(w, "My error page.") }), validate: func(t *testing.T, recorder *httptest.ResponseRecorder) { t.Helper() @@ -52,7 +52,7 @@ func TestHandler(t *testing.T) { errorPage: &dynamic.ErrorPage{Service: "error", Query: "/test", Status: []string{"500-501", "503-599"}}, backendCode: http.StatusNotModified, backendErrorHandler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - fmt.Fprintln(w, "whatever, should not be called") + _, _ = fmt.Fprintln(w, "whatever, should not be called") }), validate: func(t *testing.T, recorder *httptest.ResponseRecorder) { t.Helper() @@ -65,13 +65,12 @@ func TestHandler(t *testing.T) { errorPage: &dynamic.ErrorPage{Service: "error", Query: "/test", Status: []string{"500-501", "503-599"}}, backendCode: http.StatusInternalServerError, backendErrorHandler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - fmt.Fprintln(w, "My error page.") + _, _ = fmt.Fprintln(w, "My error page.") }), validate: func(t *testing.T, recorder *httptest.ResponseRecorder) { t.Helper() assert.Equal(t, http.StatusInternalServerError, recorder.Code, "HTTP status") assert.Contains(t, recorder.Body.String(), "My error page.") - assert.NotContains(t, recorder.Body.String(), "oops", "Should not return the oops page") }, }, { @@ -79,13 +78,12 @@ func TestHandler(t *testing.T) { errorPage: &dynamic.ErrorPage{Service: "error", Query: "/test", Status: []string{"500-501", "503-599"}}, backendCode: http.StatusBadGateway, backendErrorHandler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - fmt.Fprintln(w, "My error page.") + _, _ = fmt.Fprintln(w, "My error page.") }), validate: func(t *testing.T, recorder *httptest.ResponseRecorder) { t.Helper() assert.Equal(t, http.StatusBadGateway, recorder.Code, "HTTP status") assert.Contains(t, recorder.Body.String(), http.StatusText(http.StatusBadGateway)) - assert.NotContains(t, recorder.Body.String(), "Test Server", "Should return the oops page since we have not configured the 502 code") }, }, { @@ -93,35 +91,46 @@ func TestHandler(t *testing.T) { errorPage: &dynamic.ErrorPage{Service: "error", Query: "/{status}", Status: []string{"503-503"}}, backendCode: http.StatusServiceUnavailable, backendErrorHandler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if r.RequestURI == "/503" { - fmt.Fprintln(w, "My 503 page.") - } else { - fmt.Fprintln(w, "Failed") + if r.RequestURI != "/503" { + return } + + _, _ = fmt.Fprintln(w, "My 503 page.") }), validate: func(t *testing.T, recorder *httptest.ResponseRecorder) { t.Helper() assert.Equal(t, http.StatusServiceUnavailable, recorder.Code, "HTTP status") assert.Contains(t, recorder.Body.String(), "My 503 page.") - assert.NotContains(t, recorder.Body.String(), "oops", "Should not return the oops page") }, }, { - desc: "Single code", + desc: "single code and query replacement", errorPage: &dynamic.ErrorPage{Service: "error", Query: "/{status}", Status: []string{"503"}}, backendCode: http.StatusServiceUnavailable, backendErrorHandler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if r.RequestURI == "/503" { - fmt.Fprintln(w, "My 503 page.") - } else { - fmt.Fprintln(w, "Failed") + if r.RequestURI != "/503" { + return } + + _, _ = fmt.Fprintln(w, "My 503 page.") }), validate: func(t *testing.T, recorder *httptest.ResponseRecorder) { t.Helper() assert.Equal(t, http.StatusServiceUnavailable, recorder.Code, "HTTP status") assert.Contains(t, recorder.Body.String(), "My 503 page.") - assert.NotContains(t, recorder.Body.String(), "oops", "Should not return the oops page") + }, + }, + { + desc: "forward request host header", + errorPage: &dynamic.ErrorPage{Service: "error", Query: "/test", Status: []string{"503"}}, + backendCode: http.StatusServiceUnavailable, + backendErrorHandler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + _, _ = fmt.Fprintln(w, r.Host) + }), + validate: func(t *testing.T, recorder *httptest.ResponseRecorder) { + t.Helper() + assert.Equal(t, http.StatusServiceUnavailable, recorder.Code, "HTTP status") + assert.Contains(t, recorder.Body.String(), "localhost") }, }, } @@ -135,10 +144,11 @@ func TestHandler(t *testing.T) { handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(test.backendCode) + if test.backendCode == http.StatusNotModified { return } - fmt.Fprintln(w, http.StatusText(test.backendCode)) + _, _ = fmt.Fprintln(w, http.StatusText(test.backendCode)) }) errorPageHandler, err := New(context.Background(), handler, *test.errorPage, serviceBuilderMock, "test") require.NoError(t, err)