mirror of
https://github.com/NixOS/nixpkgs.git
synced 2024-11-23 23:43:30 +00:00
3002cd2380
Signed-off-by: Sefa Eyeoglu <contact@scrumplex.net>
782 lines
26 KiB
Diff
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
|