Merge branch v3.3 into v3.4
This commit is contained in:
commit
9c1902c62e
54 changed files with 1060 additions and 636 deletions
|
@ -68,11 +68,14 @@ type HTTPConfig struct {
|
|||
Middlewares []string `description:"Default middlewares for the routers linked to the entry point." json:"middlewares,omitempty" toml:"middlewares,omitempty" yaml:"middlewares,omitempty" export:"true"`
|
||||
TLS *TLSConfig `description:"Default TLS configuration for the routers linked to the entry point." json:"tls,omitempty" toml:"tls,omitempty" yaml:"tls,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
|
||||
EncodeQuerySemicolons bool `description:"Defines whether request query semicolons should be URLEncoded." json:"encodeQuerySemicolons,omitempty" toml:"encodeQuerySemicolons,omitempty" yaml:"encodeQuerySemicolons,omitempty"`
|
||||
SanitizePath *bool `description:"Defines whether to enable request path sanitization (removal of /./, /../ and multiple slash sequences)." json:"sanitizePath,omitempty" toml:"sanitizePath,omitempty" yaml:"sanitizePath,omitempty" export:"true"`
|
||||
MaxHeaderBytes int `description:"Maximum size of request headers in bytes." json:"maxHeaderBytes,omitempty" toml:"maxHeaderBytes,omitempty" yaml:"maxHeaderBytes,omitempty" export:"true"`
|
||||
}
|
||||
|
||||
// SetDefaults sets the default values.
|
||||
func (c *HTTPConfig) SetDefaults() {
|
||||
sanitizePath := true
|
||||
c.SanitizePath = &sanitizePath
|
||||
c.MaxHeaderBytes = http.DefaultMaxHeaderBytes
|
||||
}
|
||||
|
||||
|
|
|
@ -7,6 +7,8 @@ import (
|
|||
"github.com/traefik/traefik/v3/pkg/provider/acme"
|
||||
)
|
||||
|
||||
func pointer[T any](v T) *T { return &v }
|
||||
|
||||
func TestHasEntrypoint(t *testing.T) {
|
||||
tests := []struct {
|
||||
desc string
|
||||
|
@ -68,6 +70,7 @@ func TestConfiguration_SetEffectiveConfiguration(t *testing.T) {
|
|||
ProxyProtocol: nil,
|
||||
ForwardedHeaders: &ForwardedHeaders{},
|
||||
HTTP: HTTPConfig{
|
||||
SanitizePath: pointer(true),
|
||||
MaxHeaderBytes: 1048576,
|
||||
},
|
||||
HTTP2: &HTTP2Config{
|
||||
|
@ -113,6 +116,7 @@ func TestConfiguration_SetEffectiveConfiguration(t *testing.T) {
|
|||
ProxyProtocol: nil,
|
||||
ForwardedHeaders: &ForwardedHeaders{},
|
||||
HTTP: HTTPConfig{
|
||||
SanitizePath: pointer(true),
|
||||
MaxHeaderBytes: 1048576,
|
||||
},
|
||||
HTTP2: &HTTP2Config{
|
||||
|
@ -169,6 +173,7 @@ func TestConfiguration_SetEffectiveConfiguration(t *testing.T) {
|
|||
ProxyProtocol: nil,
|
||||
ForwardedHeaders: &ForwardedHeaders{},
|
||||
HTTP: HTTPConfig{
|
||||
SanitizePath: pointer(true),
|
||||
MaxHeaderBytes: 1048576,
|
||||
},
|
||||
HTTP2: &HTTP2Config{
|
||||
|
@ -229,6 +234,7 @@ func TestConfiguration_SetEffectiveConfiguration(t *testing.T) {
|
|||
ProxyProtocol: nil,
|
||||
ForwardedHeaders: &ForwardedHeaders{},
|
||||
HTTP: HTTPConfig{
|
||||
SanitizePath: pointer(true),
|
||||
MaxHeaderBytes: 1048576,
|
||||
},
|
||||
HTTP2: &HTTP2Config{
|
||||
|
|
|
@ -386,9 +386,10 @@ func (h *Handler) logTheRoundTrip(ctx context.Context, logDataTable *LogData) {
|
|||
func (h *Handler) redactHeaders(headers http.Header, fields logrus.Fields, prefix string) {
|
||||
for k := range headers {
|
||||
v := h.config.Fields.KeepHeader(k)
|
||||
if v == types.AccessLogKeep {
|
||||
switch v {
|
||||
case types.AccessLogKeep:
|
||||
fields[prefix+k] = strings.Join(headers.Values(k), ",")
|
||||
} else if v == types.AccessLogRedact {
|
||||
case types.AccessLogRedact:
|
||||
fields[prefix+k] = "REDACTED"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -346,7 +346,7 @@ func assertNotEmpty() func(t *testing.T, actual interface{}) {
|
|||
return func(t *testing.T, actual interface{}) {
|
||||
t.Helper()
|
||||
|
||||
assert.NotEqual(t, "", actual)
|
||||
assert.NotEmpty(t, actual)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -590,7 +590,7 @@ func TestLoggerJSON(t *testing.T) {
|
|||
err = json.Unmarshal(logData, &jsonData)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, len(test.expected), len(jsonData))
|
||||
assert.Len(t, jsonData, len(test.expected))
|
||||
|
||||
for field, assertion := range test.expected {
|
||||
assertion(t, jsonData[field])
|
||||
|
@ -649,7 +649,7 @@ func TestLogger_AbortedRequest(t *testing.T) {
|
|||
err = json.Unmarshal(logData, &jsonData)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, len(expected), len(jsonData))
|
||||
assert.Len(t, jsonData, len(expected))
|
||||
|
||||
for field, assertion := range expected {
|
||||
assertion(t, jsonData[field])
|
||||
|
@ -880,7 +880,7 @@ func assertValidLogData(t *testing.T, expected string, logData []byte) {
|
|||
|
||||
formatErrMessage := fmt.Sprintf("Expected:\t%q\nActual:\t%q", expected, string(logData))
|
||||
|
||||
require.Equal(t, len(resultExpected), len(result), formatErrMessage)
|
||||
require.Len(t, result, len(resultExpected), formatErrMessage)
|
||||
assert.Equal(t, resultExpected[ClientHost], result[ClientHost], formatErrMessage)
|
||||
assert.Equal(t, resultExpected[ClientUsername], result[ClientUsername], formatErrMessage)
|
||||
assert.Equal(t, resultExpected[RequestMethod], result[RequestMethod], formatErrMessage)
|
||||
|
|
|
@ -65,7 +65,7 @@ func TestParseAccessLog(t *testing.T) {
|
|||
|
||||
result, err := ParseAccessLog(test.value)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, len(test.expected), len(result))
|
||||
assert.Len(t, result, len(test.expected))
|
||||
for key, value := range test.expected {
|
||||
assert.Equal(t, value, result[key])
|
||||
}
|
||||
|
|
|
@ -342,7 +342,7 @@ func TestForwardAuthRemoveHopByHopHeaders(t *testing.T) {
|
|||
assert.Equal(t, http.StatusFound, res.StatusCode, "they should be equal")
|
||||
|
||||
for _, header := range forward.HopHeaders {
|
||||
assert.Equal(t, "", res.Header.Get(header), "hop-by-hop header '%s' mustn't be set", header)
|
||||
assert.Empty(t, res.Header.Get(header), "hop-by-hop header '%s' mustn't be set", header)
|
||||
}
|
||||
|
||||
location, err := res.Location()
|
||||
|
|
|
@ -176,7 +176,7 @@ func TestShouldNotCompressWhenContentEncodingHeader(t *testing.T) {
|
|||
assert.Equal(t, gzipName, rw.Header().Get(contentEncodingHeader))
|
||||
assert.Equal(t, acceptEncodingHeader, rw.Header().Get(varyHeader))
|
||||
|
||||
assert.EqualValues(t, rw.Body.Bytes(), fakeCompressedBody)
|
||||
assert.Equal(t, rw.Body.Bytes(), fakeCompressedBody)
|
||||
}
|
||||
|
||||
func TestShouldNotCompressWhenNoAcceptEncodingHeader(t *testing.T) {
|
||||
|
@ -197,7 +197,7 @@ func TestShouldNotCompressWhenNoAcceptEncodingHeader(t *testing.T) {
|
|||
|
||||
assert.Empty(t, rw.Header().Get(contentEncodingHeader))
|
||||
assert.Empty(t, rw.Header().Get(varyHeader))
|
||||
assert.EqualValues(t, rw.Body.Bytes(), fakeBody)
|
||||
assert.Equal(t, rw.Body.Bytes(), fakeBody)
|
||||
}
|
||||
|
||||
func TestEmptyAcceptEncoding(t *testing.T) {
|
||||
|
@ -219,7 +219,7 @@ func TestEmptyAcceptEncoding(t *testing.T) {
|
|||
|
||||
assert.Empty(t, rw.Header().Get(contentEncodingHeader))
|
||||
assert.Empty(t, rw.Header().Get(varyHeader))
|
||||
assert.EqualValues(t, rw.Body.Bytes(), fakeBody)
|
||||
assert.Equal(t, rw.Body.Bytes(), fakeBody)
|
||||
}
|
||||
|
||||
func TestShouldNotCompressWhenIdentityAcceptEncodingHeader(t *testing.T) {
|
||||
|
@ -246,7 +246,7 @@ func TestShouldNotCompressWhenIdentityAcceptEncodingHeader(t *testing.T) {
|
|||
|
||||
assert.Empty(t, rw.Header().Get(contentEncodingHeader))
|
||||
assert.Empty(t, rw.Header().Get(varyHeader))
|
||||
assert.EqualValues(t, rw.Body.Bytes(), fakeBody)
|
||||
assert.Equal(t, rw.Body.Bytes(), fakeBody)
|
||||
}
|
||||
|
||||
func TestShouldNotCompressWhenEmptyAcceptEncodingHeader(t *testing.T) {
|
||||
|
@ -273,7 +273,7 @@ func TestShouldNotCompressWhenEmptyAcceptEncodingHeader(t *testing.T) {
|
|||
|
||||
assert.Empty(t, rw.Header().Get(contentEncodingHeader))
|
||||
assert.Empty(t, rw.Header().Get(varyHeader))
|
||||
assert.EqualValues(t, rw.Body.Bytes(), fakeBody)
|
||||
assert.Equal(t, rw.Body.Bytes(), fakeBody)
|
||||
}
|
||||
|
||||
func TestShouldNotCompressHeadRequest(t *testing.T) {
|
||||
|
@ -295,7 +295,7 @@ func TestShouldNotCompressHeadRequest(t *testing.T) {
|
|||
|
||||
assert.Empty(t, rw.Header().Get(contentEncodingHeader))
|
||||
assert.Empty(t, rw.Header().Get(varyHeader))
|
||||
assert.EqualValues(t, rw.Body.Bytes(), fakeBody)
|
||||
assert.Equal(t, rw.Body.Bytes(), fakeBody)
|
||||
}
|
||||
|
||||
func TestShouldNotCompressWhenSpecificContentType(t *testing.T) {
|
||||
|
@ -385,7 +385,7 @@ func TestShouldNotCompressWhenSpecificContentType(t *testing.T) {
|
|||
|
||||
assert.Empty(t, rw.Header().Get(acceptEncodingHeader))
|
||||
assert.Empty(t, rw.Header().Get(contentEncodingHeader))
|
||||
assert.EqualValues(t, rw.Body.Bytes(), baseBody)
|
||||
assert.Equal(t, rw.Body.Bytes(), baseBody)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -431,7 +431,7 @@ func TestShouldCompressWhenSpecificContentType(t *testing.T) {
|
|||
|
||||
assert.Equal(t, gzipName, rw.Header().Get(contentEncodingHeader))
|
||||
assert.Equal(t, acceptEncodingHeader, rw.Header().Get(varyHeader))
|
||||
assert.NotEqualValues(t, rw.Body.Bytes(), baseBody)
|
||||
assert.NotEqual(t, rw.Body.Bytes(), baseBody)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -492,7 +492,7 @@ func TestIntegrationShouldNotCompress(t *testing.T) {
|
|||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
require.NoError(t, err)
|
||||
assert.EqualValues(t, fakeCompressedBody, body)
|
||||
assert.Equal(t, fakeCompressedBody, body)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -627,12 +627,12 @@ func TestMinResponseBodyBytes(t *testing.T) {
|
|||
|
||||
if test.expectedCompression {
|
||||
assert.Equal(t, gzipName, rw.Header().Get(contentEncodingHeader))
|
||||
assert.NotEqualValues(t, rw.Body.Bytes(), fakeBody)
|
||||
assert.NotEqual(t, rw.Body.Bytes(), fakeBody)
|
||||
return
|
||||
}
|
||||
|
||||
assert.Empty(t, rw.Header().Get(contentEncodingHeader))
|
||||
assert.EqualValues(t, rw.Body.Bytes(), fakeBody)
|
||||
assert.Equal(t, rw.Body.Bytes(), fakeBody)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -738,7 +738,7 @@ func Test1xxResponses(t *testing.T) {
|
|||
|
||||
assert.Equal(t, test.encoding, res.Header.Get(contentEncodingHeader))
|
||||
body, _ := io.ReadAll(res.Body)
|
||||
assert.NotEqualValues(t, body, fakeBody)
|
||||
assert.NotEqual(t, body, fakeBody)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -49,6 +49,7 @@ func NewHeader(next http.Handler, cfg dynamic.Headers) (*Header, error) {
|
|||
func (s *Header) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
// Handle Cors headers and preflight if configured.
|
||||
if isPreflight := s.processCorsHeaders(rw, req); isPreflight {
|
||||
rw.Header().Set("Content-Length", "0")
|
||||
rw.WriteHeader(http.StatusOK)
|
||||
return
|
||||
}
|
||||
|
|
|
@ -134,6 +134,7 @@ func TestNewHeader_CORSPreflights(t *testing.T) {
|
|||
"Origin": {"https://foo.bar.org"},
|
||||
},
|
||||
expected: map[string][]string{
|
||||
"Content-Length": {"0"},
|
||||
"Access-Control-Allow-Origin": {"https://foo.bar.org"},
|
||||
"Access-Control-Max-Age": {"600"},
|
||||
"Access-Control-Allow-Methods": {"GET,OPTIONS,PUT"},
|
||||
|
@ -152,6 +153,7 @@ func TestNewHeader_CORSPreflights(t *testing.T) {
|
|||
"Origin": {"https://foo.bar.org"},
|
||||
},
|
||||
expected: map[string][]string{
|
||||
"Content-Length": {"0"},
|
||||
"Access-Control-Allow-Origin": {"*"},
|
||||
"Access-Control-Max-Age": {"600"},
|
||||
"Access-Control-Allow-Methods": {"GET,OPTIONS,PUT"},
|
||||
|
@ -171,6 +173,7 @@ func TestNewHeader_CORSPreflights(t *testing.T) {
|
|||
"Origin": {"https://foo.bar.org"},
|
||||
},
|
||||
expected: map[string][]string{
|
||||
"Content-Length": {"0"},
|
||||
"Access-Control-Allow-Origin": {"*"},
|
||||
"Access-Control-Max-Age": {"600"},
|
||||
"Access-Control-Allow-Methods": {"GET,OPTIONS,PUT"},
|
||||
|
@ -191,6 +194,7 @@ func TestNewHeader_CORSPreflights(t *testing.T) {
|
|||
"Origin": {"https://foo.bar.org"},
|
||||
},
|
||||
expected: map[string][]string{
|
||||
"Content-Length": {"0"},
|
||||
"Access-Control-Allow-Origin": {"*"},
|
||||
"Access-Control-Max-Age": {"600"},
|
||||
"Access-Control-Allow-Methods": {"GET,OPTIONS,PUT"},
|
||||
|
@ -210,6 +214,7 @@ func TestNewHeader_CORSPreflights(t *testing.T) {
|
|||
"Origin": {"https://foo.bar.org"},
|
||||
},
|
||||
expected: map[string][]string{
|
||||
"Content-Length": {"0"},
|
||||
"Access-Control-Allow-Origin": {"*"},
|
||||
"Access-Control-Max-Age": {"600"},
|
||||
"Access-Control-Allow-Methods": {"GET,OPTIONS,PUT"},
|
||||
|
|
|
@ -226,11 +226,11 @@ func getIssuerDNInfo(ctx context.Context, options *IssuerDistinguishedNameOption
|
|||
|
||||
content := &strings.Builder{}
|
||||
|
||||
// Manage non standard attributes
|
||||
// Manage non-standard attributes
|
||||
for _, name := range cs.Names {
|
||||
// Domain Component - RFC 2247
|
||||
if options.DomainComponent && attributeTypeNames[name.Type.String()] == "DC" {
|
||||
content.WriteString(fmt.Sprintf("DC=%s%s", name.Value, subFieldSeparator))
|
||||
_, _ = fmt.Fprintf(content, "DC=%s%s", name.Value, subFieldSeparator)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -272,7 +272,7 @@ func getSubjectDNInfo(ctx context.Context, options *SubjectDistinguishedNameOpti
|
|||
for _, name := range cs.Names {
|
||||
// Domain Component - RFC 2247
|
||||
if options.DomainComponent && attributeTypeNames[name.Type.String()] == "DC" {
|
||||
content.WriteString(fmt.Sprintf("DC=%s%s", name.Value, subFieldSeparator))
|
||||
_, _ = fmt.Fprintf(content, "DC=%s%s", name.Value, subFieldSeparator)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -282,11 +282,7 @@ func TestInMemoryRateLimit(t *testing.T) {
|
|||
end := start.Add(test.loadDuration)
|
||||
ticker := time.NewTicker(loadPeriod)
|
||||
defer ticker.Stop()
|
||||
for {
|
||||
if time.Now().After(end) {
|
||||
break
|
||||
}
|
||||
|
||||
for !time.Now().After(end) {
|
||||
req := testhelpers.MustNewRequest(http.MethodGet, "http://localhost", nil)
|
||||
req.RemoteAddr = "127.0.0.1:1234"
|
||||
w := httptest.NewRecorder()
|
||||
|
@ -496,11 +492,7 @@ func TestRedisRateLimit(t *testing.T) {
|
|||
end := start.Add(test.loadDuration)
|
||||
ticker := time.NewTicker(loadPeriod)
|
||||
defer ticker.Stop()
|
||||
for {
|
||||
if time.Now().After(end) {
|
||||
break
|
||||
}
|
||||
|
||||
for !time.Now().After(end) {
|
||||
req := testhelpers.MustNewRequest(http.MethodGet, "http://localhost", nil)
|
||||
req.RemoteAddr = "127.0.0." + strconv.Itoa(randPort) + ":" + strconv.Itoa(randPort)
|
||||
w := httptest.NewRecorder()
|
||||
|
|
|
@ -453,7 +453,7 @@ func TestParseDomains(t *testing.T) {
|
|||
require.NoError(t, err, "%s: Error while parsing domain.", test.expression)
|
||||
}
|
||||
|
||||
assert.EqualValues(t, test.domain, domains, "%s: Error parsing domains from expression.", test.expression)
|
||||
assert.Equal(t, test.domain, domains, "%s: Error parsing domains from expression.", test.expression)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -570,7 +570,7 @@ func TestParseHostSNIV2(t *testing.T) {
|
|||
require.NoError(t, err, "%s: Error while parsing domain.", test.expression)
|
||||
}
|
||||
|
||||
assert.EqualValues(t, test.domain, domains, "%s: Error parsing domains from expression.", test.expression)
|
||||
assert.Equal(t, test.domain, domains, "%s: Error parsing domains from expression.", test.expression)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -382,7 +382,7 @@ func TestParseHostSNI(t *testing.T) {
|
|||
require.NoError(t, err, "%s: Error while parsing domain.", test.expression)
|
||||
}
|
||||
|
||||
assert.EqualValues(t, test.domain, domains, "%s: Error parsing domains from expression.", test.expression)
|
||||
assert.Equal(t, test.domain, domains, "%s: Error parsing domains from expression.", test.expression)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -182,7 +182,7 @@ func TestGetUncheckedCertificates(t *testing.T) {
|
|||
}
|
||||
|
||||
domains := acmeProvider.getUncheckedDomains(context.Background(), test.domains, "default")
|
||||
assert.Equal(t, len(test.expectedDomains), len(domains), "Unexpected domains.")
|
||||
assert.Len(t, domains, len(test.expectedDomains), "Unexpected domains.")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -250,7 +250,7 @@ func TestProvider_sanitizeDomains(t *testing.T) {
|
|||
if len(test.expectedErr) > 0 {
|
||||
assert.EqualError(t, err, test.expectedErr, "Unexpected error.")
|
||||
} else {
|
||||
assert.Equal(t, len(test.expectedDomains), len(domains), "Unexpected domains.")
|
||||
assert.Len(t, domains, len(test.expectedDomains), "Unexpected domains.")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -241,7 +241,7 @@ func TestSwarmProvider_listServices(t *testing.T) {
|
|||
serviceDockerData, err := p.listServices(context.Background(), dockerClient)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t, len(test.expectedServices), len(serviceDockerData))
|
||||
assert.Len(t, serviceDockerData, len(test.expectedServices))
|
||||
for i, serviceName := range test.expectedServices {
|
||||
if len(serviceDockerData) <= i {
|
||||
require.Fail(t, "index", "invalid index %d", i)
|
||||
|
|
|
@ -598,8 +598,8 @@ func (c configBuilder) nameAndService(ctx context.Context, parentNamespace strin
|
|||
return "", nil, fmt.Errorf("service %s/%s not in the parent resource namespace %s", service.Namespace, service.Name, parentNamespace)
|
||||
}
|
||||
|
||||
switch {
|
||||
case service.Kind == "" || service.Kind == "Service":
|
||||
switch service.Kind {
|
||||
case "", "Service":
|
||||
serversLB, err := c.buildServersLB(namespace, service)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
|
@ -608,8 +608,10 @@ func (c configBuilder) nameAndService(ctx context.Context, parentNamespace strin
|
|||
fullName := fullServiceName(svcCtx, namespace, service, service.Port)
|
||||
|
||||
return fullName, serversLB, nil
|
||||
case service.Kind == "TraefikService":
|
||||
|
||||
case "TraefikService":
|
||||
return fullServiceName(svcCtx, namespace, service, intstr.FromInt(0)), nil, nil
|
||||
|
||||
default:
|
||||
return "", nil, fmt.Errorf("unsupported service kind %s", service.Kind)
|
||||
}
|
||||
|
|
|
@ -352,7 +352,7 @@ func TestTransferEncodingChunked(t *testing.T) {
|
|||
require.True(t, ok)
|
||||
|
||||
for i := range 3 {
|
||||
_, err := rw.Write([]byte(fmt.Sprintf("chunk %d\n", i)))
|
||||
_, err := fmt.Fprintf(rw, "chunk %d\n", i)
|
||||
require.NoError(t, err)
|
||||
|
||||
flusher.Flush()
|
||||
|
|
|
@ -375,7 +375,7 @@ func Test_doOnStruct(t *testing.T) {
|
|||
err := doOnStruct(val, tagExport, test.redactByDefault)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.EqualValues(t, test.expected, test.base)
|
||||
assert.Equal(t, test.expected, test.base)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -610,7 +610,12 @@ func createHTTPServer(ctx context.Context, ln net.Listener, configuration *stati
|
|||
return nil, err
|
||||
}
|
||||
|
||||
handler = denyFragment(handler)
|
||||
if configuration.HTTP.SanitizePath != nil && *configuration.HTTP.SanitizePath {
|
||||
// sanitizePath is used to clean the URL path by removing /../, /./ and duplicate slash sequences,
|
||||
// to make sure the path is interpreted by the backends as it is evaluated inside rule matchers.
|
||||
handler = sanitizePath(handler)
|
||||
}
|
||||
|
||||
if configuration.HTTP.EncodeQuerySemicolons {
|
||||
handler = encodeQuerySemicolons(handler)
|
||||
} else {
|
||||
|
@ -630,6 +635,8 @@ func createHTTPServer(ctx context.Context, ln net.Listener, configuration *stati
|
|||
})
|
||||
}
|
||||
|
||||
handler = denyFragment(handler)
|
||||
|
||||
serverHTTP := &http.Server{
|
||||
Handler: handler,
|
||||
ErrorLog: stdlog.New(logs.NoLevel(log.Logger, zerolog.DebugLevel), "", 0),
|
||||
|
@ -755,3 +762,20 @@ func denyFragment(h http.Handler) http.Handler {
|
|||
h.ServeHTTP(rw, req)
|
||||
})
|
||||
}
|
||||
|
||||
// sanitizePath removes the "..", "." and duplicate slash segments from the URL.
|
||||
// It cleans the request URL Path and RawPath, and updates the request URI.
|
||||
func sanitizePath(h http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
r2 := new(http.Request)
|
||||
*r2 = *req
|
||||
|
||||
// Cleans the URL raw path and path.
|
||||
r2.URL = r2.URL.JoinPath()
|
||||
|
||||
// Because the reverse proxy director is building query params from requestURI it needs to be updated as well.
|
||||
r2.RequestURI = r2.URL.RequestURI()
|
||||
|
||||
h.ServeHTTP(rw, r2)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ import (
|
|||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
@ -382,3 +383,44 @@ func TestKeepAliveH2c(t *testing.T) {
|
|||
// to change.
|
||||
require.Contains(t, err.Error(), "use of closed network connection")
|
||||
}
|
||||
|
||||
func TestSanitizePath(t *testing.T) {
|
||||
tests := []struct {
|
||||
path string
|
||||
expected string
|
||||
}{
|
||||
{path: "/b", expected: "/b"},
|
||||
{path: "/b/", expected: "/b/"},
|
||||
{path: "/../../b/", expected: "/b/"},
|
||||
{path: "/../../b", expected: "/b"},
|
||||
{path: "/a/b/..", expected: "/a"},
|
||||
{path: "/a/b/../", expected: "/a/"},
|
||||
{path: "/a/../../b", expected: "/b"},
|
||||
{path: "/..///b///", expected: "/b/"},
|
||||
{path: "/a/../b", expected: "/b"},
|
||||
{path: "/a/./b", expected: "/a/b"},
|
||||
{path: "/a//b", expected: "/a/b"},
|
||||
{path: "/a/../../b", expected: "/b"},
|
||||
{path: "/a/../c/../b", expected: "/b"},
|
||||
{path: "/a/../../../c/../b", expected: "/b"},
|
||||
{path: "/a/../c/../../b", expected: "/b"},
|
||||
{path: "/a/..//c/.././b", expected: "/b"},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run("Testing case: "+test.path, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var callCount int
|
||||
clean := sanitizePath(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
callCount++
|
||||
assert.Equal(t, test.expected, r.URL.Path)
|
||||
}))
|
||||
|
||||
request := httptest.NewRequest(http.MethodGet, "http://foo"+test.path, http.NoBody)
|
||||
clean.ServeHTTP(httptest.NewRecorder(), request)
|
||||
|
||||
assert.Equal(t, 1, callCount)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -327,7 +327,7 @@ func TestGetLoadBalancerServiceHandler(t *testing.T) {
|
|||
assert.NotNil(t, handler)
|
||||
|
||||
req := testhelpers.MustNewRequest(http.MethodGet, "http://callme", nil)
|
||||
assert.Equal(t, "", req.Header.Get("User-Agent"))
|
||||
assert.Empty(t, req.Header.Get("User-Agent"))
|
||||
|
||||
if test.userAgent != "" {
|
||||
req.Header.Set("User-Agent", test.userAgent)
|
||||
|
|
|
@ -41,7 +41,7 @@ func TestDomain_ToStrArray(t *testing.T) {
|
|||
t.Parallel()
|
||||
|
||||
domains := test.domain.ToStrArray()
|
||||
assert.EqualValues(t, test.expected, domains)
|
||||
assert.Equal(t, test.expected, domains)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -131,11 +131,7 @@ func (l *Listener) Shutdown(graceTimeout time.Duration) error {
|
|||
}
|
||||
start := time.Now()
|
||||
end := start.Add(graceTimeout)
|
||||
for {
|
||||
if time.Now().After(end) {
|
||||
break
|
||||
}
|
||||
|
||||
for !time.Now().After(end) {
|
||||
l.mu.RLock()
|
||||
if len(l.conns) == 0 {
|
||||
l.mu.RUnlock()
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue