Skip to content

Commit

Permalink
Preserve existing X-Forwarded-* headers
Browse files Browse the repository at this point in the history
For X-Forwarded-Host and X-Forwarded-Proto, we should only populate
these ourselves if they weren't present in the original request. If they
were present, we should preserve them.

Otherwise when running behind another proxy, we may lose information
that it has provided. For example, where a downstream proxy terminated
TLS and set `X-Forwarded-Proto` to `https`, and connects to us over
plain `http`, we would have wrongly reset `X-Forwarded-Proto` to `http`,
which will mislead the upstream.
  • Loading branch information
kevinmcconnell committed Jul 11, 2024
1 parent 0a8da81 commit 8d19698
Show file tree
Hide file tree
Showing 2 changed files with 32 additions and 1 deletion.
18 changes: 18 additions & 0 deletions internal/handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,24 @@ func TestHandlerXForwardedHeadersWhenProxying(t *testing.T) {
h.ServeHTTP(w, r)
}

func TestHandlerXForwardedHeadersRespectExistingHeaders(t *testing.T) {
upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, "1.2.3.4", r.Header.Get("X-Forwarded-For"))
assert.Equal(t, "other.example.com", r.Header.Get("X-Forwarded-Host"))
assert.Equal(t, "https", r.Header.Get("X-Forwarded-Proto"))
}))
defer upstream.Close()

h := NewHandler(handlerOptions(upstream.URL))

w := httptest.NewRecorder()
r := httptest.NewRequest("GET", "http://example.org", nil)
r.Header.Set("X-Forwarded-Proto", "https")
r.Header.Set("X-Forwarded-Host", "other.example.com")
r.RemoteAddr = "1.2.3.4:1234"
h.ServeHTTP(w, r)
}

// Helpers

func handlerOptions(targetUrl string) HandlerOptions {
Expand Down
15 changes: 14 additions & 1 deletion internal/proxy_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ func NewProxyHandler(targetUrl *url.URL, badGatewayPage string) http.Handler {
r.SetURL(targetUrl)
r.Out.Host = r.In.Host
r.Out.Header["X-Forwarded-For"] = r.In.Header["X-Forwarded-For"]
r.SetXForwarded()
setXForwarded(r)
},
ErrorHandler: ProxyErrorHandler(badGatewayPage),
Transport: createProxyTransport(),
Expand Down Expand Up @@ -47,6 +47,19 @@ func ProxyErrorHandler(badGatewayPage string) func(w http.ResponseWriter, r *htt
}
}

func setXForwarded(r *httputil.ProxyRequest) {
// Populate new headers by default
r.SetXForwarded()

// Preserve original headers if we had them
if r.In.Header.Get("X-Forwarded-Host") != "" {
r.Out.Header.Set("X-Forwarded-Host", r.In.Header.Get("X-Forwarded-Host"))
}
if r.In.Header.Get("X-Forwarded-Proto") != "" {
r.Out.Header.Set("X-Forwarded-Proto", r.In.Header.Get("X-Forwarded-Proto"))
}
}

func isRequestEntityTooLarge(err error) bool {
var maxBytesError *http.MaxBytesError
return errors.As(err, &maxBytesError)
Expand Down

0 comments on commit 8d19698

Please sign in to comment.