From 3002cd2380d272a295efa174b3148cb5b7d632e2 Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Tue, 24 Sep 2024 15:57:33 +0200 Subject: [PATCH] traefik: backport fix for CVE-2024-45410 Signed-off-by: Sefa Eyeoglu --- pkgs/servers/traefik/default.nix | 5 + pkgs/servers/traefik/fix-CVE-2024-45410.patch | 781 ++++++++++++++++++ 2 files changed, 786 insertions(+) create mode 100644 pkgs/servers/traefik/fix-CVE-2024-45410.patch diff --git a/pkgs/servers/traefik/default.nix b/pkgs/servers/traefik/default.nix index 0e9c6812c6bc..03b3c23266fd 100644 --- a/pkgs/servers/traefik/default.nix +++ b/pkgs/servers/traefik/default.nix @@ -11,6 +11,11 @@ buildGoModule rec { stripRoot = false; }; + patches = [ + # Rebased onto v3.1.2 from https://github.com/traefik/traefik/pull/11077 + ./fix-CVE-2024-45410.patch + ]; + vendorHash = "sha256-xQPDlwu/mRdyvZW0qSCA9eko9pOQAMwh2vVJWzMnyfs="; subPackages = [ "cmd/traefik" ]; diff --git a/pkgs/servers/traefik/fix-CVE-2024-45410.patch b/pkgs/servers/traefik/fix-CVE-2024-45410.patch new file mode 100644 index 000000000000..ef0257927df9 --- /dev/null +++ b/pkgs/servers/traefik/fix-CVE-2024-45410.patch @@ -0,0 +1,781 @@ +From 21f0062a6d570f12ce51646cacdcb28178d8be6f Mon Sep 17 00:00:00 2001 +From: Sefa Eyeoglu +Date: Tue, 24 Sep 2024 15:52:25 +0200 +Subject: [PATCH] Backport "Cleanup Connection headers before passing the + middleware chain" to v3.1.2 + +See https://github.com/traefik/traefik/commit/584144100524277829f26219baaab29a53b8134f +See https://github.com/traefik/traefik/pull/11077 + +Fixes GHSA-62c8-mh53-4cqv for v3.1.2 + +Signed-off-by: Sefa Eyeoglu +--- + .../reference/static-configuration/cli-ref.md | 3 + + .../reference/static-configuration/env-ref.md | 3 + + .../reference/static-configuration/file.toml | 1 + + .../reference/static-configuration/file.yaml | 3 + + docs/content/routing/entrypoints.md | 34 +++ + .../connection_hop_by_hop_headers.toml | 37 +++ + integration/headers_test.go | 53 ++++ + pkg/config/static/entrypoints.go | 1 + + .../connectionheader.go | 2 +- + .../connectionheader_test.go | 2 +- + pkg/middlewares/auth/forward.go | 3 +- + .../forwardedheaders/forwarded_header.go | 75 ++++- + .../forwardedheaders/forwarded_header_test.go | 282 +++++++++++++++++- + pkg/middlewares/headers/headers.go | 6 +- + pkg/server/server_entrypoint_tcp.go | 1 + + 15 files changed, 475 insertions(+), 31 deletions(-) + create mode 100644 integration/fixtures/headers/connection_hop_by_hop_headers.toml + rename pkg/middlewares/{connectionheader => auth}/connectionheader.go (97%) + rename pkg/middlewares/{connectionheader => auth}/connectionheader_test.go (98%) + +diff --git a/docs/content/reference/static-configuration/cli-ref.md b/docs/content/reference/static-configuration/cli-ref.md +index cd245706d0..3a1489b951 100644 +--- a/docs/content/reference/static-configuration/cli-ref.md ++++ b/docs/content/reference/static-configuration/cli-ref.md +@@ -120,6 +120,9 @@ Entry point address. + `--entrypoints..asdefault`: + Adds this EntryPoint to the list of default EntryPoints to be used on routers that don't have any Entrypoint defined. (Default: ```false```) + ++`--entrypoints..forwardedheaders.connection`: ++List of Connection headers that are allowed to pass through the middleware chain before being removed. ++ + `--entrypoints..forwardedheaders.insecure`: + Trust all forwarded headers. (Default: ```false```) + +diff --git a/docs/content/reference/static-configuration/env-ref.md b/docs/content/reference/static-configuration/env-ref.md +index 02873474bb..38c71b75d0 100644 +--- a/docs/content/reference/static-configuration/env-ref.md ++++ b/docs/content/reference/static-configuration/env-ref.md +@@ -120,6 +120,9 @@ Entry point address. + `TRAEFIK_ENTRYPOINTS__ASDEFAULT`: + Adds this EntryPoint to the list of default EntryPoints to be used on routers that don't have any Entrypoint defined. (Default: ```false```) + ++`TRAEFIK_ENTRYPOINTS__FORWARDEDHEADERS_CONNECTION`: ++List of Connection headers that are allowed to pass through the middleware chain before being removed. ++ + `TRAEFIK_ENTRYPOINTS__FORWARDEDHEADERS_INSECURE`: + Trust all forwarded headers. (Default: ```false```) + +diff --git a/docs/content/reference/static-configuration/file.toml b/docs/content/reference/static-configuration/file.toml +index 8a033d2f29..ccd013069b 100644 +--- a/docs/content/reference/static-configuration/file.toml ++++ b/docs/content/reference/static-configuration/file.toml +@@ -48,6 +48,7 @@ + [entryPoints.EntryPoint0.forwardedHeaders] + insecure = true + trustedIPs = ["foobar", "foobar"] ++ connection = ["foobar", "foobar"] + [entryPoints.EntryPoint0.http] + middlewares = ["foobar", "foobar"] + encodeQuerySemicolons = true +diff --git a/docs/content/reference/static-configuration/file.yaml b/docs/content/reference/static-configuration/file.yaml +index d8a9b13624..de8ac21c85 100644 +--- a/docs/content/reference/static-configuration/file.yaml ++++ b/docs/content/reference/static-configuration/file.yaml +@@ -57,6 +57,9 @@ entryPoints: + trustedIPs: + - foobar + - foobar ++ connection: ++ - foobar ++ - foobar + http: + redirections: + entryPoint: +diff --git a/docs/content/routing/entrypoints.md b/docs/content/routing/entrypoints.md +index 04c730924c..53a707783a 100644 +--- a/docs/content/routing/entrypoints.md ++++ b/docs/content/routing/entrypoints.md +@@ -500,6 +500,40 @@ You can configure Traefik to trust the forwarded headers information (`X-Forward + --entryPoints.web.forwardedHeaders.insecure + ``` + ++??? info "`forwardedHeaders.connection`" ++ ++ As per RFC7230, Traefik respects the Connection options from the client request. ++ By doing so, it removes any header field(s) listed in the request Connection header and the Connection header field itself when empty. ++ The removal happens as soon as the request is handled by Traefik, ++ thus the removed headers are not available when the request passes through the middleware chain. ++ The `connection` option lists the Connection headers allowed to passthrough the middleware chain before their removal. ++ ++ ```yaml tab="File (YAML)" ++ ## Static configuration ++ entryPoints: ++ web: ++ address: ":80" ++ forwardedHeaders: ++ connection: ++ - foobar ++ ``` ++ ++ ```toml tab="File (TOML)" ++ ## Static configuration ++ [entryPoints] ++ [entryPoints.web] ++ address = ":80" ++ ++ [entryPoints.web.forwardedHeaders] ++ connection = ["foobar"] ++ ``` ++ ++ ```bash tab="CLI" ++ ## Static configuration ++ --entryPoints.web.address=:80 ++ --entryPoints.web.forwardedHeaders.connection=foobar ++ ``` ++ + ### Transport + + #### `respondingTimeouts` +diff --git a/integration/fixtures/headers/connection_hop_by_hop_headers.toml b/integration/fixtures/headers/connection_hop_by_hop_headers.toml +new file mode 100644 +index 0000000000..091e1995cc +--- /dev/null ++++ b/integration/fixtures/headers/connection_hop_by_hop_headers.toml +@@ -0,0 +1,37 @@ ++[global] ++ checkNewVersion = false ++ sendAnonymousUsage = false ++ ++[log] ++ level = "DEBUG" ++ ++# Limiting the Logs to Specific Fields ++[accessLog] ++ format = "json" ++ filePath = "access.log" ++ ++ [accessLog.fields.headers.names] ++ "Foo" = "keep" ++ "Bar" = "keep" ++ ++[entryPoints] ++ [entryPoints.web] ++ address = ":8000" ++ [entryPoints.web.forwardedHeaders] ++ insecure = true ++ connection = ["Foo"] ++ ++[providers.file] ++ filename = "{{ .SelfFilename }}" ++ ++## dynamic configuration ## ++ ++[http.routers] ++ [http.routers.router1] ++ rule = "Host(`test.localhost`)" ++ service = "service1" ++ ++[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 e47fe1d738..c3c1377cab 100644 +--- a/integration/headers_test.go ++++ b/integration/headers_test.go +@@ -4,6 +4,7 @@ import ( + "net" + "net/http" + "net/http/httptest" ++ "os" + "testing" + "time" + +@@ -20,6 +21,11 @@ func TestHeadersSuite(t *testing.T) { + suite.Run(t, new(HeadersSuite)) + } + ++func (s *HeadersSuite) TearDownTest() { ++ s.displayTraefikLogFile(traefikTestLogFile) ++ _ = os.Remove(traefikTestAccessLogFile) ++} ++ + func (s *HeadersSuite) TestSimpleConfiguration() { + s.traefikCmd(withConfigFile("fixtures/headers/basic.toml")) + +@@ -62,6 +68,53 @@ func (s *HeadersSuite) TestReverseProxyHeaderRemoved() { + require.NoError(s.T(), err) + } + ++func (s *HeadersSuite) TestConnectionHopByHop() { ++ file := s.adaptFile("fixtures/headers/connection_hop_by_hop_headers.toml", struct{}{}) ++ s.traefikCmd(withConfigFile(file)) ++ ++ handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { ++ _, found := r.Header["X-Forwarded-For"] ++ assert.True(s.T(), found) ++ xHost, found := r.Header["X-Forwarded-Host"] ++ assert.True(s.T(), found) ++ assert.Equal(s.T(), "localhost", xHost[0]) ++ ++ _, found = r.Header["Foo"] ++ assert.False(s.T(), found) ++ _, found = r.Header["Bar"] ++ assert.False(s.T(), found) ++ }) ++ ++ listener, err := net.Listen("tcp", "127.0.0.1:9000") ++ require.NoError(s.T(), err) ++ ++ 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) ++ require.NoError(s.T(), err) ++ req.Host = "test.localhost" ++ req.Header = http.Header{ ++ "Connection": {"Foo,Bar,X-Forwarded-For,X-Forwarded-Host"}, ++ "Foo": {"bar"}, ++ "Bar": {"foo"}, ++ "X-Forwarded-Host": {"localhost"}, ++ } ++ ++ err = try.Request(req, time.Second, try.StatusCodeIs(http.StatusOK)) ++ require.NoError(s.T(), err) ++ ++ accessLog, err := os.ReadFile(traefikTestAccessLogFile) ++ require.NoError(s.T(), err) ++ ++ assert.Contains(s.T(), string(accessLog), "\"request_Foo\":\"bar\"") ++ assert.NotContains(s.T(), string(accessLog), "\"request_Bar\":\"\"") ++} ++ + func (s *HeadersSuite) TestCorsResponses() { + file := s.adaptFile("fixtures/headers/cors.toml", struct{}{}) + s.traefikCmd(withConfigFile(file)) +diff --git a/pkg/config/static/entrypoints.go b/pkg/config/static/entrypoints.go +index 9b48ddce42..f9256f9fc0 100644 +--- a/pkg/config/static/entrypoints.go ++++ b/pkg/config/static/entrypoints.go +@@ -111,6 +111,7 @@ type TLSConfig struct { + type ForwardedHeaders struct { + Insecure bool `description:"Trust all forwarded headers." json:"insecure,omitempty" toml:"insecure,omitempty" yaml:"insecure,omitempty" export:"true"` + TrustedIPs []string `description:"Trust only forwarded headers from selected IPs." json:"trustedIPs,omitempty" toml:"trustedIPs,omitempty" yaml:"trustedIPs,omitempty"` ++ Connection []string `description:"List of Connection headers that are allowed to pass through the middleware chain before being removed." json:"connection,omitempty" toml:"connection,omitempty" yaml:"connection,omitempty"` + } + + // ProxyProtocol contains Proxy-Protocol configuration. +diff --git a/pkg/middlewares/connectionheader/connectionheader.go b/pkg/middlewares/auth/connectionheader.go +similarity index 97% +rename from pkg/middlewares/connectionheader/connectionheader.go +rename to pkg/middlewares/auth/connectionheader.go +index 12a994f17a..30d3ab87c5 100644 +--- a/pkg/middlewares/connectionheader/connectionheader.go ++++ b/pkg/middlewares/auth/connectionheader.go +@@ -1,4 +1,4 @@ +-package connectionheader ++package auth + + import ( + "net/http" +diff --git a/pkg/middlewares/connectionheader/connectionheader_test.go b/pkg/middlewares/auth/connectionheader_test.go +similarity index 98% +rename from pkg/middlewares/connectionheader/connectionheader_test.go +rename to pkg/middlewares/auth/connectionheader_test.go +index bd41d58d80..00d719ef04 100644 +--- a/pkg/middlewares/connectionheader/connectionheader_test.go ++++ b/pkg/middlewares/auth/connectionheader_test.go +@@ -1,4 +1,4 @@ +-package connectionheader ++package auth + + import ( + "net/http" +diff --git a/pkg/middlewares/auth/forward.go b/pkg/middlewares/auth/forward.go +index 26053a50e8..bd3f7c225c 100644 +--- a/pkg/middlewares/auth/forward.go ++++ b/pkg/middlewares/auth/forward.go +@@ -13,7 +13,6 @@ import ( + + "github.com/traefik/traefik/v3/pkg/config/dynamic" + "github.com/traefik/traefik/v3/pkg/middlewares" +- "github.com/traefik/traefik/v3/pkg/middlewares/connectionheader" + "github.com/traefik/traefik/v3/pkg/middlewares/observability" + "github.com/traefik/traefik/v3/pkg/tracing" + "github.com/traefik/traefik/v3/pkg/types" +@@ -121,7 +120,7 @@ func (fa *forwardAuth) GetTracingInformation() (string, string, trace.SpanKind) + func (fa *forwardAuth) ServeHTTP(rw http.ResponseWriter, req *http.Request) { + logger := middlewares.GetLogger(req.Context(), fa.name, typeNameForward) + +- req = connectionheader.Remove(req) ++ req = Remove(req) + + forwardReq, err := http.NewRequestWithContext(req.Context(), http.MethodGet, fa.address, nil) + if err != nil { +diff --git a/pkg/middlewares/forwardedheaders/forwarded_header.go b/pkg/middlewares/forwardedheaders/forwarded_header.go +index 3f4e32301a..f03bea9abc 100644 +--- a/pkg/middlewares/forwardedheaders/forwarded_header.go ++++ b/pkg/middlewares/forwardedheaders/forwarded_header.go +@@ -3,10 +3,13 @@ package forwardedheaders + import ( + "net" + "net/http" ++ "net/textproto" + "os" ++ "slices" + "strings" + + "github.com/traefik/traefik/v3/pkg/ip" ++ "golang.org/x/net/http/httpguts" + ) + + const ( +@@ -42,19 +45,20 @@ var xHeaders = []string{ + // Unless insecure is set, + // it first removes all the existing values for those headers if the remote address is not one of the trusted ones. + type XForwarded struct { +- insecure bool +- trustedIps []string +- ipChecker *ip.Checker +- next http.Handler +- hostname string ++ insecure bool ++ trustedIPs []string ++ connectionHeaders []string ++ ipChecker *ip.Checker ++ next http.Handler ++ hostname string + } + + // NewXForwarded creates a new XForwarded. +-func NewXForwarded(insecure bool, trustedIps []string, next http.Handler) (*XForwarded, error) { ++func NewXForwarded(insecure bool, trustedIPs []string, connectionHeaders []string, next http.Handler) (*XForwarded, error) { + var ipChecker *ip.Checker +- if len(trustedIps) > 0 { ++ if len(trustedIPs) > 0 { + var err error +- ipChecker, err = ip.NewChecker(trustedIps) ++ ipChecker, err = ip.NewChecker(trustedIPs) + if err != nil { + return nil, err + } +@@ -66,11 +70,12 @@ func NewXForwarded(insecure bool, trustedIps []string, next http.Handler) (*XFor + } + + return &XForwarded{ +- insecure: insecure, +- trustedIps: trustedIps, +- ipChecker: ipChecker, +- next: next, +- hostname: hostname, ++ insecure: insecure, ++ trustedIPs: trustedIPs, ++ connectionHeaders: connectionHeaders, ++ ipChecker: ipChecker, ++ next: next, ++ hostname: hostname, + }, nil + } + +@@ -189,9 +194,53 @@ func (x *XForwarded) ServeHTTP(w http.ResponseWriter, r *http.Request) { + + x.rewrite(r) + ++ x.removeConnectionHeaders(r) ++ + x.next.ServeHTTP(w, r) + } + ++func (x *XForwarded) removeConnectionHeaders(req *http.Request) { ++ var reqUpType string ++ if httpguts.HeaderValuesContainsToken(req.Header[connection], upgrade) { ++ reqUpType = unsafeHeader(req.Header).Get(upgrade) ++ } ++ ++ var connectionHopByHopHeaders []string ++ for _, f := range req.Header[connection] { ++ for _, sf := range strings.Split(f, ",") { ++ if sf = textproto.TrimString(sf); sf != "" { ++ // Connection header cannot dictate to remove X- headers managed by Traefik, ++ // as per rfc7230 https://datatracker.ietf.org/doc/html/rfc7230#section-6.1, ++ // A proxy or gateway MUST ... and then remove the Connection header field itself ++ // (or replace it with the intermediary's own connection options for the forwarded message). ++ if slices.Contains(xHeaders, sf) { ++ continue ++ } ++ ++ // Keep headers allowed through the middleware chain. ++ if slices.Contains(x.connectionHeaders, sf) { ++ connectionHopByHopHeaders = append(connectionHopByHopHeaders, sf) ++ continue ++ } ++ ++ // Apply Connection header option. ++ req.Header.Del(sf) ++ } ++ } ++ } ++ ++ if reqUpType != "" { ++ connectionHopByHopHeaders = append(connectionHopByHopHeaders, upgrade) ++ unsafeHeader(req.Header).Set(upgrade, reqUpType) ++ } ++ if len(connectionHopByHopHeaders) > 0 { ++ unsafeHeader(req.Header).Set(connection, strings.Join(connectionHopByHopHeaders, ",")) ++ return ++ } ++ ++ unsafeHeader(req.Header).Del(connection) ++} ++ + // unsafeHeader allows to manage Header values. + // Must be used only when the header name is already a canonical key. + type unsafeHeader map[string][]string +diff --git a/pkg/middlewares/forwardedheaders/forwarded_header_test.go b/pkg/middlewares/forwardedheaders/forwarded_header_test.go +index 8e1d109253..414fd50070 100644 +--- a/pkg/middlewares/forwardedheaders/forwarded_header_test.go ++++ b/pkg/middlewares/forwardedheaders/forwarded_header_test.go +@@ -12,15 +12,16 @@ import ( + + func TestServeHTTP(t *testing.T) { + testCases := []struct { +- desc string +- insecure bool +- trustedIps []string +- incomingHeaders map[string][]string +- remoteAddr string +- expectedHeaders map[string]string +- tls bool +- websocket bool +- host string ++ desc string ++ insecure bool ++ trustedIps []string ++ connectionHeaders []string ++ incomingHeaders map[string][]string ++ remoteAddr string ++ expectedHeaders map[string]string ++ tls bool ++ websocket bool ++ host string + }{ + { + desc: "all Empty", +@@ -269,6 +270,196 @@ func TestServeHTTP(t *testing.T) { + xForwardedServer: "foo.com:8080", + }, + }, ++ { ++ desc: "Untrusted: Connection header has no effect on X- forwarded headers", ++ insecure: false, ++ incomingHeaders: map[string][]string{ ++ connection: { ++ xForwardedProto, ++ xForwardedFor, ++ xForwardedURI, ++ xForwardedMethod, ++ xForwardedHost, ++ xForwardedPort, ++ xForwardedTLSClientCert, ++ xForwardedTLSClientCertInfo, ++ xRealIP, ++ }, ++ xForwardedProto: {"foo"}, ++ xForwardedFor: {"foo"}, ++ xForwardedURI: {"foo"}, ++ xForwardedMethod: {"foo"}, ++ xForwardedHost: {"foo"}, ++ xForwardedPort: {"foo"}, ++ xForwardedTLSClientCert: {"foo"}, ++ xForwardedTLSClientCertInfo: {"foo"}, ++ xRealIP: {"foo"}, ++ }, ++ expectedHeaders: map[string]string{ ++ xForwardedProto: "http", ++ xForwardedFor: "", ++ xForwardedURI: "", ++ xForwardedMethod: "", ++ xForwardedHost: "", ++ xForwardedPort: "80", ++ xForwardedTLSClientCert: "", ++ xForwardedTLSClientCertInfo: "", ++ xRealIP: "", ++ connection: "", ++ }, ++ }, ++ { ++ desc: "Trusted (insecure): Connection header has no effect on X- forwarded headers", ++ insecure: true, ++ incomingHeaders: map[string][]string{ ++ connection: { ++ xForwardedProto, ++ xForwardedFor, ++ xForwardedURI, ++ xForwardedMethod, ++ xForwardedHost, ++ xForwardedPort, ++ xForwardedTLSClientCert, ++ xForwardedTLSClientCertInfo, ++ xRealIP, ++ }, ++ xForwardedProto: {"foo"}, ++ xForwardedFor: {"foo"}, ++ xForwardedURI: {"foo"}, ++ xForwardedMethod: {"foo"}, ++ xForwardedHost: {"foo"}, ++ xForwardedPort: {"foo"}, ++ xForwardedTLSClientCert: {"foo"}, ++ xForwardedTLSClientCertInfo: {"foo"}, ++ xRealIP: {"foo"}, ++ }, ++ expectedHeaders: map[string]string{ ++ xForwardedProto: "foo", ++ xForwardedFor: "foo", ++ xForwardedURI: "foo", ++ xForwardedMethod: "foo", ++ xForwardedHost: "foo", ++ xForwardedPort: "foo", ++ xForwardedTLSClientCert: "foo", ++ xForwardedTLSClientCertInfo: "foo", ++ xRealIP: "foo", ++ connection: "", ++ }, ++ }, ++ { ++ desc: "Untrusted and Connection: Connection header has no effect on X- forwarded headers", ++ insecure: false, ++ connectionHeaders: []string{ ++ xForwardedProto, ++ xForwardedFor, ++ xForwardedURI, ++ xForwardedMethod, ++ xForwardedHost, ++ xForwardedPort, ++ xForwardedTLSClientCert, ++ xForwardedTLSClientCertInfo, ++ xRealIP, ++ }, ++ incomingHeaders: map[string][]string{ ++ connection: { ++ xForwardedProto, ++ xForwardedFor, ++ xForwardedURI, ++ xForwardedMethod, ++ xForwardedHost, ++ xForwardedPort, ++ xForwardedTLSClientCert, ++ xForwardedTLSClientCertInfo, ++ xRealIP, ++ }, ++ xForwardedProto: {"foo"}, ++ xForwardedFor: {"foo"}, ++ xForwardedURI: {"foo"}, ++ xForwardedMethod: {"foo"}, ++ xForwardedHost: {"foo"}, ++ xForwardedPort: {"foo"}, ++ xForwardedTLSClientCert: {"foo"}, ++ xForwardedTLSClientCertInfo: {"foo"}, ++ xRealIP: {"foo"}, ++ }, ++ expectedHeaders: map[string]string{ ++ xForwardedProto: "http", ++ xForwardedFor: "", ++ xForwardedURI: "", ++ xForwardedMethod: "", ++ xForwardedHost: "", ++ xForwardedPort: "80", ++ xForwardedTLSClientCert: "", ++ xForwardedTLSClientCertInfo: "", ++ xRealIP: "", ++ connection: "", ++ }, ++ }, ++ { ++ desc: "Trusted (insecure) and Connection: Connection header has no effect on X- forwarded headers", ++ insecure: true, ++ connectionHeaders: []string{ ++ xForwardedProto, ++ xForwardedFor, ++ xForwardedURI, ++ xForwardedMethod, ++ xForwardedHost, ++ xForwardedPort, ++ xForwardedTLSClientCert, ++ xForwardedTLSClientCertInfo, ++ xRealIP, ++ }, ++ incomingHeaders: map[string][]string{ ++ connection: { ++ xForwardedProto, ++ xForwardedFor, ++ xForwardedURI, ++ xForwardedMethod, ++ xForwardedHost, ++ xForwardedPort, ++ xForwardedTLSClientCert, ++ xForwardedTLSClientCertInfo, ++ xRealIP, ++ }, ++ xForwardedProto: {"foo"}, ++ xForwardedFor: {"foo"}, ++ xForwardedURI: {"foo"}, ++ xForwardedMethod: {"foo"}, ++ xForwardedHost: {"foo"}, ++ xForwardedPort: {"foo"}, ++ xForwardedTLSClientCert: {"foo"}, ++ xForwardedTLSClientCertInfo: {"foo"}, ++ xRealIP: {"foo"}, ++ }, ++ expectedHeaders: map[string]string{ ++ xForwardedProto: "foo", ++ xForwardedFor: "foo", ++ xForwardedURI: "foo", ++ xForwardedMethod: "foo", ++ xForwardedHost: "foo", ++ xForwardedPort: "foo", ++ xForwardedTLSClientCert: "foo", ++ xForwardedTLSClientCertInfo: "foo", ++ xRealIP: "foo", ++ connection: "", ++ }, ++ }, ++ { ++ desc: "Connection: one remove, and one passthrough header", ++ connectionHeaders: []string{ ++ "foo", ++ }, ++ incomingHeaders: map[string][]string{ ++ connection: { ++ "foo", ++ }, ++ "Foo": {"bar"}, ++ "Bar": {"foo"}, ++ }, ++ expectedHeaders: map[string]string{ ++ "Bar": "foo", ++ }, ++ }, + } + + for _, test := range testCases { +@@ -299,7 +490,7 @@ func TestServeHTTP(t *testing.T) { + } + } + +- m, err := NewXForwarded(test.insecure, test.trustedIps, ++ m, err := NewXForwarded(test.insecure, test.trustedIps, test.connectionHeaders, + http.HandlerFunc(func(_ http.ResponseWriter, _ *http.Request) {})) + require.NoError(t, err) + +@@ -382,3 +573,74 @@ func Test_isWebsocketRequest(t *testing.T) { + }) + } + } ++ ++func TestConnection(t *testing.T) { ++ testCases := []struct { ++ desc string ++ reqHeaders map[string]string ++ connectionHeaders []string ++ expected http.Header ++ }{ ++ { ++ desc: "simple remove", ++ reqHeaders: map[string]string{ ++ "Foo": "bar", ++ connection: "foo", ++ }, ++ expected: http.Header{}, ++ }, ++ { ++ desc: "remove and upgrade", ++ reqHeaders: map[string]string{ ++ upgrade: "test", ++ "Foo": "bar", ++ connection: "upgrade,foo", ++ }, ++ expected: http.Header{ ++ upgrade: []string{"test"}, ++ connection: []string{"Upgrade"}, ++ }, ++ }, ++ { ++ desc: "no remove", ++ reqHeaders: map[string]string{ ++ "Foo": "bar", ++ connection: "fii", ++ }, ++ expected: http.Header{ ++ "Foo": []string{"bar"}, ++ }, ++ }, ++ { ++ desc: "no remove because connection header pass through", ++ reqHeaders: map[string]string{ ++ "Foo": "bar", ++ connection: "Foo", ++ }, ++ connectionHeaders: []string{"Foo"}, ++ expected: http.Header{ ++ "Foo": []string{"bar"}, ++ connection: []string{"Foo"}, ++ }, ++ }, ++ } ++ ++ for _, test := range testCases { ++ t.Run(test.desc, func(t *testing.T) { ++ t.Parallel() ++ ++ forwarded, err := NewXForwarded(true, nil, test.connectionHeaders, nil) ++ require.NoError(t, err) ++ ++ req := httptest.NewRequest(http.MethodGet, "https://localhost", nil) ++ ++ for k, v := range test.reqHeaders { ++ req.Header.Set(k, v) ++ } ++ ++ forwarded.removeConnectionHeaders(req) ++ ++ assert.Equal(t, test.expected, req.Header) ++ }) ++ } ++} +diff --git a/pkg/middlewares/headers/headers.go b/pkg/middlewares/headers/headers.go +index e393aa1a65..861d1066de 100644 +--- a/pkg/middlewares/headers/headers.go ++++ b/pkg/middlewares/headers/headers.go +@@ -8,7 +8,6 @@ import ( + + "github.com/traefik/traefik/v3/pkg/config/dynamic" + "github.com/traefik/traefik/v3/pkg/middlewares" +- "github.com/traefik/traefik/v3/pkg/middlewares/connectionheader" + "go.opentelemetry.io/otel/trace" + ) + +@@ -46,12 +45,11 @@ func New(ctx context.Context, next http.Handler, cfg dynamic.Headers, name strin + + if hasCustomHeaders || hasCorsHeaders { + logger.Debug().Msgf("Setting up customHeaders/Cors from %v", cfg) +- h, err := NewHeader(nextHandler, cfg) ++ var err error ++ handler, err = NewHeader(nextHandler, cfg) + if err != nil { + return nil, err + } +- +- handler = connectionheader.Remover(h) + } + + return &headers{ +diff --git a/pkg/server/server_entrypoint_tcp.go b/pkg/server/server_entrypoint_tcp.go +index ca901c90f9..07350885a6 100644 +--- a/pkg/server/server_entrypoint_tcp.go ++++ b/pkg/server/server_entrypoint_tcp.go +@@ -607,6 +607,7 @@ func createHTTPServer(ctx context.Context, ln net.Listener, configuration *stati + handler, err = forwardedheaders.NewXForwarded( + configuration.ForwardedHeaders.Insecure, + configuration.ForwardedHeaders.TrustedIPs, ++ configuration.ForwardedHeaders.Connection, + next) + if err != nil { + return nil, err