diff --git a/integration/fixtures/headers/remove_reverseproxy_headers.toml b/integration/fixtures/headers/remove_reverseproxy_headers.toml new file mode 100644 index 000000000..48ce504cf --- /dev/null +++ b/integration/fixtures/headers/remove_reverseproxy_headers.toml @@ -0,0 +1,31 @@ +[global] + checkNewVersion = false + sendAnonymousUsage = false + +[log] + level = "DEBUG" + +[entryPoints] + [entryPoints.web] + address = ":8000" + +[providers.file] + filename = "{{ .SelfFilename }}" + +## dynamic configuration ## + +[http.routers] + [http.routers.router1] + rule = "Host(`test.localhost`)" + middlewares = ["remove"] + service = "service1" + +[http.middlewares] + [http.middlewares.remove.headers.customRequestHeaders] + X-Forwarded-For = "" + Foo = "" + +[http.services] + [http.services.service1.loadBalancer] + [[http.services.service1.loadBalancer.servers]] + url = "http://127.0.0.1:9000" diff --git a/integration/headers_test.go b/integration/headers_test.go index 6e4b7e9b9..049783d85 100644 --- a/integration/headers_test.go +++ b/integration/headers_test.go @@ -1,7 +1,9 @@ package integration import ( + "net" "net/http" + "net/http/httptest" "os" "time" @@ -25,6 +27,46 @@ func (s *HeadersSuite) TestSimpleConfiguration(c *check.C) { c.Assert(err, checker.IsNil) } +func (s *HeadersSuite) TestReverseProxyHeaderRemoved(c *check.C) { + file := s.adaptFile(c, "fixtures/headers/remove_reverseproxy_headers.toml", struct{}{}) + defer os.Remove(file) + cmd, display := s.traefikCmd(withConfigFile(file)) + defer display(c) + + err := cmd.Start() + c.Assert(err, checker.IsNil) + defer s.killCmd(cmd) + + handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + _, found := r.Header["X-Forwarded-Host"] + c.Assert(found, checker.True) + _, found = r.Header["Foo"] + c.Assert(found, checker.False) + _, found = r.Header["X-Forwarded-For"] + c.Assert(found, checker.False) + }) + + listener, err := net.Listen("tcp", "127.0.0.1:9000") + c.Assert(err, checker.IsNil) + + ts := &httptest.Server{ + Listener: listener, + Config: &http.Server{Handler: handler}, + } + ts.Start() + defer ts.Close() + + req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/", nil) + c.Assert(err, checker.IsNil) + req.Host = "test.localhost" + req.Header = http.Header{ + "Foo": {"bar"}, + } + + err = try.Request(req, 500*time.Millisecond, try.StatusCodeIs(http.StatusOK)) + c.Assert(err, checker.IsNil) +} + func (s *HeadersSuite) TestCorsResponses(c *check.C) { file := s.adaptFile(c, "fixtures/headers/cors.toml", struct{}{}) defer os.Remove(file) diff --git a/pkg/middlewares/headers/header.go b/pkg/middlewares/headers/header.go index 7c5a99af5..8e48f9f33 100644 --- a/pkg/middlewares/headers/header.go +++ b/pkg/middlewares/headers/header.go @@ -10,6 +10,7 @@ import ( "github.com/traefik/traefik/v2/pkg/config/dynamic" "github.com/traefik/traefik/v2/pkg/log" + "github.com/vulcand/oxy/v2/forward" ) // Header is a middleware that helps setup a few basic security features. @@ -70,6 +71,10 @@ func (s *Header) modifyCustomRequestHeaders(req *http.Request) { // Loop through Custom request headers for header, value := range s.headers.CustomRequestHeaders { switch { + // Handling https://github.com/golang/go/commit/ecdbffd4ec68b509998792f120868fec319de59b. + case value == "" && header == forward.XForwardedFor: + req.Header[header] = nil + case value == "": req.Header.Del(header) diff --git a/pkg/middlewares/headers/header_test.go b/pkg/middlewares/headers/header_test.go index fa21fe311..363b7e737 100644 --- a/pkg/middlewares/headers/header_test.go +++ b/pkg/middlewares/headers/header_test.go @@ -29,11 +29,14 @@ func TestNewHeader_customRequestHeader(t *testing.T) { desc: "delete a header", cfg: dynamic.Headers{ CustomRequestHeaders: map[string]string{ + "X-Forwarded-For": "", "X-Custom-Request-Header": "", "Foo": "", }, }, - expected: http.Header{}, + expected: http.Header{ + "X-Forwarded-For": nil, + }, }, { desc: "override a header",