Cleanup Connection headers before passing the middleware chain
Co-authored-by: Romain <rtribotte@users.noreply.github.com>
This commit is contained in:
parent
0cf2032c15
commit
5841441005
15 changed files with 475 additions and 31 deletions
|
@ -111,6 +111,9 @@ Entry point address.
|
||||||
`--entrypoints.<name>.allowacmebypass`:
|
`--entrypoints.<name>.allowacmebypass`:
|
||||||
Enables handling of ACME TLS and HTTP challenges with custom routers. (Default: ```false```)
|
Enables handling of ACME TLS and HTTP challenges with custom routers. (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`:
|
`--entrypoints.<name>.forwardedheaders.insecure`:
|
||||||
Trust all forwarded headers. (Default: ```false```)
|
Trust all forwarded headers. (Default: ```false```)
|
||||||
|
|
||||||
|
|
|
@ -111,6 +111,9 @@ Entry point address.
|
||||||
`TRAEFIK_ENTRYPOINTS_<NAME>_ALLOWACMEBYPASS`:
|
`TRAEFIK_ENTRYPOINTS_<NAME>_ALLOWACMEBYPASS`:
|
||||||
Enables handling of ACME TLS and HTTP challenges with custom routers. (Default: ```false```)
|
Enables handling of ACME TLS and HTTP challenges with custom routers. (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`:
|
`TRAEFIK_ENTRYPOINTS_<NAME>_FORWARDEDHEADERS_INSECURE`:
|
||||||
Trust all forwarded headers. (Default: ```false```)
|
Trust all forwarded headers. (Default: ```false```)
|
||||||
|
|
||||||
|
|
|
@ -33,6 +33,7 @@
|
||||||
[entryPoints.EntryPoint0.forwardedHeaders]
|
[entryPoints.EntryPoint0.forwardedHeaders]
|
||||||
insecure = true
|
insecure = true
|
||||||
trustedIPs = ["foobar", "foobar"]
|
trustedIPs = ["foobar", "foobar"]
|
||||||
|
connection = ["foobar", "foobar"]
|
||||||
[entryPoints.EntryPoint0.http]
|
[entryPoints.EntryPoint0.http]
|
||||||
middlewares = ["foobar", "foobar"]
|
middlewares = ["foobar", "foobar"]
|
||||||
encodeQuerySemicolons = true
|
encodeQuerySemicolons = true
|
||||||
|
|
|
@ -37,6 +37,9 @@ entryPoints:
|
||||||
trustedIPs:
|
trustedIPs:
|
||||||
- foobar
|
- foobar
|
||||||
- foobar
|
- foobar
|
||||||
|
connection:
|
||||||
|
- foobar
|
||||||
|
- foobar
|
||||||
http:
|
http:
|
||||||
redirections:
|
redirections:
|
||||||
entryPoint:
|
entryPoint:
|
||||||
|
|
|
@ -422,6 +422,40 @@ You can configure Traefik to trust the forwarded headers information (`X-Forward
|
||||||
--entryPoints.web.forwardedHeaders.insecure
|
--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
|
### Transport
|
||||||
|
|
||||||
#### `respondingTimeouts`
|
#### `respondingTimeouts`
|
||||||
|
|
|
@ -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"
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -20,6 +21,11 @@ func TestHeadersSuite(t *testing.T) {
|
||||||
suite.Run(t, new(HeadersSuite))
|
suite.Run(t, new(HeadersSuite))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *HeadersSuite) TearDownTest() {
|
||||||
|
s.displayTraefikLogFile(traefikTestLogFile)
|
||||||
|
_ = os.Remove(traefikTestAccessLogFile)
|
||||||
|
}
|
||||||
|
|
||||||
func (s *HeadersSuite) TestSimpleConfiguration() {
|
func (s *HeadersSuite) TestSimpleConfiguration() {
|
||||||
s.traefikCmd(withConfigFile("fixtures/headers/basic.toml"))
|
s.traefikCmd(withConfigFile("fixtures/headers/basic.toml"))
|
||||||
|
|
||||||
|
@ -62,6 +68,53 @@ func (s *HeadersSuite) TestReverseProxyHeaderRemoved() {
|
||||||
require.NoError(s.T(), err)
|
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() {
|
func (s *HeadersSuite) TestCorsResponses() {
|
||||||
file := s.adaptFile("fixtures/headers/cors.toml", struct{}{})
|
file := s.adaptFile("fixtures/headers/cors.toml", struct{}{})
|
||||||
s.traefikCmd(withConfigFile(file))
|
s.traefikCmd(withConfigFile(file))
|
||||||
|
|
|
@ -110,6 +110,7 @@ type TLSConfig struct {
|
||||||
type ForwardedHeaders struct {
|
type ForwardedHeaders struct {
|
||||||
Insecure bool `description:"Trust all forwarded headers." json:"insecure,omitempty" toml:"insecure,omitempty" yaml:"insecure,omitempty" export:"true"`
|
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"`
|
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.
|
// ProxyProtocol contains Proxy-Protocol configuration.
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package connectionheader
|
package auth
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
|
@ -1,4 +1,4 @@
|
||||||
package connectionheader
|
package auth
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
|
@ -15,7 +15,6 @@ import (
|
||||||
"github.com/traefik/traefik/v2/pkg/config/dynamic"
|
"github.com/traefik/traefik/v2/pkg/config/dynamic"
|
||||||
"github.com/traefik/traefik/v2/pkg/log"
|
"github.com/traefik/traefik/v2/pkg/log"
|
||||||
"github.com/traefik/traefik/v2/pkg/middlewares"
|
"github.com/traefik/traefik/v2/pkg/middlewares"
|
||||||
"github.com/traefik/traefik/v2/pkg/middlewares/connectionheader"
|
|
||||||
"github.com/traefik/traefik/v2/pkg/tracing"
|
"github.com/traefik/traefik/v2/pkg/tracing"
|
||||||
"github.com/vulcand/oxy/v2/forward"
|
"github.com/vulcand/oxy/v2/forward"
|
||||||
"github.com/vulcand/oxy/v2/utils"
|
"github.com/vulcand/oxy/v2/utils"
|
||||||
|
@ -90,7 +89,7 @@ func NewForward(ctx context.Context, next http.Handler, config dynamic.ForwardAu
|
||||||
fa.authResponseHeadersRegex = re
|
fa.authResponseHeadersRegex = re
|
||||||
}
|
}
|
||||||
|
|
||||||
return connectionheader.Remover(fa), nil
|
return Remover(fa), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fa *forwardAuth) GetTracingInformation() (string, ext.SpanKindEnum) {
|
func (fa *forwardAuth) GetTracingInformation() (string, ext.SpanKindEnum) {
|
||||||
|
|
|
@ -3,10 +3,13 @@ package forwardedheaders
|
||||||
import (
|
import (
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/textproto"
|
||||||
"os"
|
"os"
|
||||||
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/traefik/traefik/v2/pkg/ip"
|
"github.com/traefik/traefik/v2/pkg/ip"
|
||||||
|
"golang.org/x/net/http/httpguts"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -42,19 +45,20 @@ var xHeaders = []string{
|
||||||
// Unless insecure is set,
|
// 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.
|
// it first removes all the existing values for those headers if the remote address is not one of the trusted ones.
|
||||||
type XForwarded struct {
|
type XForwarded struct {
|
||||||
insecure bool
|
insecure bool
|
||||||
trustedIps []string
|
trustedIPs []string
|
||||||
ipChecker *ip.Checker
|
connectionHeaders []string
|
||||||
next http.Handler
|
ipChecker *ip.Checker
|
||||||
hostname string
|
next http.Handler
|
||||||
|
hostname string
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewXForwarded creates a new XForwarded.
|
// 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
|
var ipChecker *ip.Checker
|
||||||
if len(trustedIps) > 0 {
|
if len(trustedIPs) > 0 {
|
||||||
var err error
|
var err error
|
||||||
ipChecker, err = ip.NewChecker(trustedIps)
|
ipChecker, err = ip.NewChecker(trustedIPs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -66,11 +70,12 @@ func NewXForwarded(insecure bool, trustedIps []string, next http.Handler) (*XFor
|
||||||
}
|
}
|
||||||
|
|
||||||
return &XForwarded{
|
return &XForwarded{
|
||||||
insecure: insecure,
|
insecure: insecure,
|
||||||
trustedIps: trustedIps,
|
trustedIPs: trustedIPs,
|
||||||
ipChecker: ipChecker,
|
connectionHeaders: connectionHeaders,
|
||||||
next: next,
|
ipChecker: ipChecker,
|
||||||
hostname: hostname,
|
next: next,
|
||||||
|
hostname: hostname,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -189,9 +194,53 @@ func (x *XForwarded) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
x.rewrite(r)
|
x.rewrite(r)
|
||||||
|
|
||||||
|
x.removeConnectionHeaders(r)
|
||||||
|
|
||||||
x.next.ServeHTTP(w, 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.
|
// unsafeHeader allows to manage Header values.
|
||||||
// Must be used only when the header name is already a canonical key.
|
// Must be used only when the header name is already a canonical key.
|
||||||
type unsafeHeader map[string][]string
|
type unsafeHeader map[string][]string
|
||||||
|
|
|
@ -12,15 +12,16 @@ import (
|
||||||
|
|
||||||
func TestServeHTTP(t *testing.T) {
|
func TestServeHTTP(t *testing.T) {
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
desc string
|
desc string
|
||||||
insecure bool
|
insecure bool
|
||||||
trustedIps []string
|
trustedIps []string
|
||||||
incomingHeaders map[string][]string
|
connectionHeaders []string
|
||||||
remoteAddr string
|
incomingHeaders map[string][]string
|
||||||
expectedHeaders map[string]string
|
remoteAddr string
|
||||||
tls bool
|
expectedHeaders map[string]string
|
||||||
websocket bool
|
tls bool
|
||||||
host string
|
websocket bool
|
||||||
|
host string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
desc: "all Empty",
|
desc: "all Empty",
|
||||||
|
@ -269,6 +270,196 @@ func TestServeHTTP(t *testing.T) {
|
||||||
xForwardedServer: "foo.com:8080",
|
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 {
|
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) {}))
|
http.HandlerFunc(func(_ http.ResponseWriter, _ *http.Request) {}))
|
||||||
require.NoError(t, err)
|
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)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -10,7 +10,6 @@ import (
|
||||||
"github.com/traefik/traefik/v2/pkg/config/dynamic"
|
"github.com/traefik/traefik/v2/pkg/config/dynamic"
|
||||||
"github.com/traefik/traefik/v2/pkg/log"
|
"github.com/traefik/traefik/v2/pkg/log"
|
||||||
"github.com/traefik/traefik/v2/pkg/middlewares"
|
"github.com/traefik/traefik/v2/pkg/middlewares"
|
||||||
"github.com/traefik/traefik/v2/pkg/middlewares/connectionheader"
|
|
||||||
"github.com/traefik/traefik/v2/pkg/tracing"
|
"github.com/traefik/traefik/v2/pkg/tracing"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -69,12 +68,11 @@ func New(ctx context.Context, next http.Handler, cfg dynamic.Headers, name strin
|
||||||
|
|
||||||
if hasCustomHeaders || hasCorsHeaders {
|
if hasCustomHeaders || hasCorsHeaders {
|
||||||
logger.Debugf("Setting up customHeaders/Cors from %v", cfg)
|
logger.Debugf("Setting up customHeaders/Cors from %v", cfg)
|
||||||
h, err := NewHeader(nextHandler, cfg)
|
var err error
|
||||||
|
handler, err = NewHeader(nextHandler, cfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
handler = connectionheader.Remover(h)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return &headers{
|
return &headers{
|
||||||
|
|
|
@ -565,6 +565,7 @@ func createHTTPServer(ctx context.Context, ln net.Listener, configuration *stati
|
||||||
handler, err = forwardedheaders.NewXForwarded(
|
handler, err = forwardedheaders.NewXForwarded(
|
||||||
configuration.ForwardedHeaders.Insecure,
|
configuration.ForwardedHeaders.Insecure,
|
||||||
configuration.ForwardedHeaders.TrustedIPs,
|
configuration.ForwardedHeaders.TrustedIPs,
|
||||||
|
configuration.ForwardedHeaders.Connection,
|
||||||
next)
|
next)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
Loading…
Reference in a new issue