nixpkgs/pkgs/servers/traefik/fix-CVE-2024-45410.patch
Sefa Eyeoglu 3002cd2380
traefik: backport fix for CVE-2024-45410
Signed-off-by: Sefa Eyeoglu <contact@scrumplex.net>
2024-09-24 16:14:33 +02:00

782 lines
26 KiB
Diff

From 21f0062a6d570f12ce51646cacdcb28178d8be6f Mon Sep 17 00:00:00 2001
From: Sefa Eyeoglu <contact@scrumplex.net>
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 <contact@scrumplex.net>
---
.../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.<name>.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.<name>.forwardedheaders.connection`:
+List of Connection headers that are allowed to pass through the middleware chain before being removed.
+
`--entrypoints.<name>.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_<NAME>_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_<NAME>_FORWARDEDHEADERS_CONNECTION`:
+List of Connection headers that are allowed to pass through the middleware chain before being removed.
+
`TRAEFIK_ENTRYPOINTS_<NAME>_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