From 124f772aff08d60156336324f8ab271741e9d380 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Dollevoet?= <30157857+lodener@users.noreply.github.com> Date: Fri, 24 Jul 2026 11:30:56 +0200 Subject: [PATCH] Restore Forwarded header in reverse proxy Rewrite mode The migration from ReverseProxy.Director to Rewrite in ebe6fedab (#573) restored X-Forwarded-For, X-Forwarded-Host, and X-Forwarded-Proto, but missed the RFC 7239 Forwarded header. Rewrite mode strips Forwarded, X-Forwarded-For, X-Forwarded-Host, and X-Forwarded-Proto from the outbound request before calling the Rewrite func. Director never stripped any of these, so client-set Forwarded headers now get dropped. Copy Forwarded from the inbound request verbatim, preserving multiple header values, to match the previous pass-through behavior. Add regression tests covering single value, multiple values, and the absent-header case. --- .../gorouter/proxy/proxy.go | 9 +++++++-- .../gorouter/proxy/proxy_test.go | 18 ++++++++++++++++++ 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/src/code.cloudfoundry.org/gorouter/proxy/proxy.go b/src/code.cloudfoundry.org/gorouter/proxy/proxy.go index 6bc0e5392..d30ff9f7c 100644 --- a/src/code.cloudfoundry.org/gorouter/proxy/proxy.go +++ b/src/code.cloudfoundry.org/gorouter/proxy/proxy.go @@ -142,11 +142,16 @@ func NewProxy( rproxy := &httputil.ReverseProxy{ Rewrite: func(r *httputil.ProxyRequest) { p.setupProxyRequest(r.Out) - // Rewrite mode strips X-Forwarded-* from r.Out before calling this - // function. Restore them to replicate the behavior Director had: + // Rewrite mode strips the Forwarded and X-Forwarded-* headers from + // r.Out before calling this function. Restore them to replicate the + // behavior Director had: + // - Forwarded: pass the client-supplied value(s) through verbatim. // - X-Forwarded-Proto: copy the value already set by the XForwardedProto middleware. // - X-Forwarded-Host: preserve whatever the client/middleware set. // - X-Forwarded-For: append the client IP from r.In.RemoteAddr. + if fwd := r.In.Header.Values("Forwarded"); len(fwd) > 0 { + r.Out.Header["Forwarded"] = append([]string(nil), fwd...) + } if proto := r.In.Header.Get("X-Forwarded-Proto"); proto != "" { r.Out.Header.Set("X-Forwarded-Proto", proto) } diff --git a/src/code.cloudfoundry.org/gorouter/proxy/proxy_test.go b/src/code.cloudfoundry.org/gorouter/proxy/proxy_test.go index b5a703da4..9f1c5762c 100644 --- a/src/code.cloudfoundry.org/gorouter/proxy/proxy_test.go +++ b/src/code.cloudfoundry.org/gorouter/proxy/proxy_test.go @@ -498,6 +498,24 @@ var _ = Describe("Proxy", func() { }) }) + Describe("Forwarded", func() { + It("passes a client-supplied Forwarded header through verbatim", func() { + req.Header.Set("Forwarded", "for=1.2.3.4;proto=https;host=example.com") + Expect(getProxiedHeaders(req).Get("Forwarded")).To(Equal("for=1.2.3.4;proto=https;host=example.com")) + }) + + It("preserves multiple Forwarded header values", func() { + req.Header.Add("Forwarded", "for=1.2.3.4") + req.Header.Add("Forwarded", "for=5.6.7.8") + Expect(getProxiedHeaders(req).Values("Forwarded")).To(Equal([]string{"for=1.2.3.4", "for=5.6.7.8"})) + }) + + It("does not add a Forwarded header when the client does not send one", func() { + _, ok := getProxiedHeaders(req)["Forwarded"] + Expect(ok).To(BeFalse()) + }) + }) + Describe("X-Forwarded-Host", func() { Context("for expect-100-continue requests", func() { It("preserves the X-Forwarded-Host header", func() {