Repository: traefik/traefik (language: go)

Issue:
Ability to remove X-Forwarded-* headers
### Welcome!

- [X] Yes, I've searched similar issues on [GitHub](https://github.com/traefik/traefik/issues) and didn't find any.
- [X] Yes, I've searched similar issues on the [Traefik community forum](https://community.traefik.io) and didn't find any.

### What did you expect to see?

Hello Traefik Team,

I'm opening this feature request to address a situation with the handling of `X-Forwarded-*` headers in Traefik. Currently, these headers are added after the middlewares have been processed, which makes it impossible for **custom middlewares** (plugins) to remove or alter these headers. While I understand that these headers are vital for proper functioning of many architectures, giving the users an option to control these headers can provide additional flexibility and can be crucial in certain scenarios.

### Use Cases:

1. In some scenarios, we might want to fully control the headers passed on to the downstream services, and these automatic headers may conflict with the control.

2. It might be a security concern for some users. While `X-Forwarded-*` headers are generally harmless and useful, there might be cases where users don't want to expose certain information about the request's path.

3. It provides more flexibility in terms of header management, which could be useful in complex architectures and during debugging.

### Proposed Solution:

Introduce a configuration option that enables users to choose whether to include these headers or not. This option can be true by default to maintain the current behavior, which is adding these headers.

Please note that this feature request is not about disabling these headers completely, but about giving users a choice. If a user doesn't specify anything, the default behavior should remain intact, that is, the headers should be added as they are now.

P.S. ChatGPT wrote this, did way better job than I would be able to. :pray: 


Discussion / hints:
I went as far as developing plugin for Traefik to be able to remove those enforced request headers, but it seems they are added after the middlewares are processed. I've found an issues that is somewhat 4 years old, but it seems that to this day it is impossible to disable those headers :/

I don't want to setup intermediate reverse proxy (such as Nginx) to strip those headers. Could you please take a look at this?
In case this is a regular, but _enforced_ middleware:
https://github.com/traefik/traefik/blob/master/pkg/middlewares/forwardedheaders/forwarded_header.go

What would work for me is that if you execute it before my specified middlewares - I would be able to remove those `X-Forwarded-*` headers. As of now I have no control of `X-Forwarded-*` headers because these are added _after_ my specified middlewares are executed.... :/

Looks like this is the code that adds middlewares:
https://github.com/traefik/traefik/blob/60bc47d00e9ac7584a4bd437f53fdef347915dea/pkg/server/middleware/tcp/middlewares.go#L32-L67

Would it be possible to prepend the `forwardedheaders` middleware _before_ all other middlewares, so it's processed first, then the rest of user-specified middlewares can modify what `forwardedheaders` has added?
Hello,

there is an existing option to handle this header: https://doc.traefik.io/traefik/routing/entrypoints/#forwarded-headers

By default, only the Traefik X-Forwarded headers are allowed, so there is no security issue here.

the X-Forwarded headers added by Traefik cannot be removed.

The forwardedheaders middleware doesn't add headers, it's a filter.
Hi @ldez,

I understand this, but I would like to call it exactly what it is - **forced headers**.

Would it be possible to change this?
> the X-Forwarded headers added by Traefik cannot be removed.

One of my mentioned solutions is to add these headers before specified middlewares are executed, so user can use middleware such as `headers` to remove them from requestHeaders?

With Caddy and Nginx I can get rid of `X-Forwarded-*` headers, but not Traefik. Could you please consider implementing this feature? I am afraid I am not good enough at Go to be able to modify code myself and submit pull request. :/ 

EDIT: Issue from 2019 requesting this exact thing: https://github.com/traefik/traefik/issues/5482
Hey @erkexzcx!

Thanks for your suggestion.

We are interested in this issue but are unsure about the use case and the traction it will receive, so we are going to leave the status as "kind/proposal" to give the community time to let us know that they would like this.


Hi @nmengin,
I think I just created an issue on the community forum describing just this: a use case where removing the X-Forwarded-* headers in the upstream request is the only solution to my problem (apart from building a complete proxy application myself).
I'm interested to see what your thoughts on this are.
https://community.traefik.io/t/can-not-remove-x-forwarded-headers-anymore/19474

Fix patch:
diff --git a/integration/fixtures/headers/remove_reverseproxy_headers.toml b/integration/fixtures/headers/remove_reverseproxy_headers.toml
new file mode 100644
index 0000000000..48ce504cf2
--- /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/pkg/middlewares/headers/header.go b/pkg/middlewares/headers/header.go
index 7c5a99af54..8e48f9f33f 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)
 


Test patch:
diff --git a/integration/headers_test.go b/integration/headers_test.go
index 6e4b7e9b9c..049783d85f 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_test.go b/pkg/middlewares/headers/header_test.go
index fa21fe311f..363b7e7378 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",