Fix deny encoded characters
Co-authored-by: Kevin Pollet <pollet.kevin@gmail.com>
This commit is contained in:
parent
8e6ce08f33
commit
23788e90cb
21 changed files with 435 additions and 303 deletions
|
|
@ -85,7 +85,6 @@ THIS FILE MUST NOT BE EDITED BY HAND
|
|||
| <a id="opt-entrypoints-name-forwardedheaders-insecure" href="#opt-entrypoints-name-forwardedheaders-insecure" title="#opt-entrypoints-name-forwardedheaders-insecure">entrypoints._name_.forwardedheaders.insecure</a> | Trust all forwarded headers. | false |
|
||||
| <a id="opt-entrypoints-name-forwardedheaders-trustedips" href="#opt-entrypoints-name-forwardedheaders-trustedips" title="#opt-entrypoints-name-forwardedheaders-trustedips">entrypoints._name_.forwardedheaders.trustedips</a> | Trust only forwarded headers from selected IPs. | |
|
||||
| <a id="opt-entrypoints-name-http" href="#opt-entrypoints-name-http" title="#opt-entrypoints-name-http">entrypoints._name_.http</a> | HTTP configuration. | |
|
||||
| <a id="opt-entrypoints-name-http-encodedcharacters" href="#opt-entrypoints-name-http-encodedcharacters" title="#opt-entrypoints-name-http-encodedcharacters">entrypoints._name_.http.encodedcharacters</a> | Defines which encoded characters are allowed in the request path. | |
|
||||
| <a id="opt-entrypoints-name-http-encodedcharacters-allowencodedbackslash" href="#opt-entrypoints-name-http-encodedcharacters-allowencodedbackslash" title="#opt-entrypoints-name-http-encodedcharacters-allowencodedbackslash">entrypoints._name_.http.encodedcharacters.allowencodedbackslash</a> | Defines whether requests with encoded back slash characters in the path are allowed. | false |
|
||||
| <a id="opt-entrypoints-name-http-encodedcharacters-allowencodedhash" href="#opt-entrypoints-name-http-encodedcharacters-allowencodedhash" title="#opt-entrypoints-name-http-encodedcharacters-allowencodedhash">entrypoints._name_.http.encodedcharacters.allowencodedhash</a> | Defines whether requests with encoded hash characters in the path are allowed. | false |
|
||||
| <a id="opt-entrypoints-name-http-encodedcharacters-allowencodednullcharacter" href="#opt-entrypoints-name-http-encodedcharacters-allowencodednullcharacter" title="#opt-entrypoints-name-http-encodedcharacters-allowencodednullcharacter">entrypoints._name_.http.encodedcharacters.allowencodednullcharacter</a> | Defines whether requests with encoded null characters in the path are allowed. | false |
|
||||
|
|
|
|||
34
integration/fixtures/simple_encoded_chars.toml
Normal file
34
integration/fixtures/simple_encoded_chars.toml
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
[global]
|
||||
checkNewVersion = false
|
||||
sendAnonymousUsage = false
|
||||
|
||||
[log]
|
||||
level = "DEBUG"
|
||||
|
||||
[entryPoints]
|
||||
[entryPoints.strict]
|
||||
address = ":8000"
|
||||
# Default: no encoded characters allowed
|
||||
|
||||
[entryPoints.permissive]
|
||||
address = ":8001"
|
||||
[entryPoints.permissive.http.encodedCharacters]
|
||||
allowEncodedSlash = true
|
||||
|
||||
[api]
|
||||
insecure = true
|
||||
|
||||
[providers.file]
|
||||
filename = "{{ .SelfFilename }}"
|
||||
|
||||
## dynamic configuration ##
|
||||
|
||||
[http.routers]
|
||||
[http.routers.sameRouter]
|
||||
service = "service1"
|
||||
rule = "Host(`test.localhost`)"
|
||||
|
||||
[http.services]
|
||||
[http.services.service1.loadBalancer]
|
||||
[[http.services.service1.loadBalancer.servers]]
|
||||
url = "{{ .Server1 }}"
|
||||
|
|
@ -2092,3 +2092,72 @@ func (s *SimpleSuite) TestSanitizePathSyntaxV2() {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestEncodedCharactersDifferentEntryPoints verifies that router handler caching does not interfere with
|
||||
// per-entry-point encoded characters configuration.
|
||||
// The same router should behave differently on different entry points.
|
||||
func (s *SimpleSuite) TestEncodedCharactersDifferentEntryPoints() {
|
||||
s.createComposeProject("base")
|
||||
|
||||
s.composeUp()
|
||||
defer s.composeDown()
|
||||
|
||||
whoami1URL := "http://" + net.JoinHostPort(s.getComposeServiceIP("whoami1"), "80")
|
||||
|
||||
file := s.adaptFile("fixtures/simple_encoded_chars.toml", struct {
|
||||
Server1 string
|
||||
}{whoami1URL})
|
||||
|
||||
s.traefikCmd(withConfigFile(file))
|
||||
|
||||
err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("Host(`test.localhost`)"))
|
||||
require.NoError(s.T(), err)
|
||||
|
||||
testCases := []struct {
|
||||
desc string
|
||||
request string
|
||||
target string
|
||||
expected int
|
||||
}{
|
||||
{
|
||||
desc: "Encoded slash should be REJECTED on strict entry point",
|
||||
request: "GET /path%2Fwith%2Fslash HTTP/1.1\r\nHost: test.localhost\r\n\r\n",
|
||||
target: "127.0.0.1:8000", // strict entry point
|
||||
expected: http.StatusBadRequest,
|
||||
},
|
||||
{
|
||||
desc: "Encoded slash should be ALLOWED on permissive entry point",
|
||||
request: "GET /path%2Fwith%2Fslash HTTP/1.1\r\nHost: test.localhost\r\n\r\n",
|
||||
target: "127.0.0.1:8001", // permissive entry point
|
||||
expected: http.StatusOK,
|
||||
},
|
||||
{
|
||||
desc: "Regular path should work on strict entry point",
|
||||
request: "GET /regular/path HTTP/1.1\r\nHost: test.localhost\r\n\r\n",
|
||||
target: "127.0.0.1:8000",
|
||||
expected: http.StatusOK,
|
||||
},
|
||||
{
|
||||
desc: "Regular path should work on permissive entry point",
|
||||
request: "GET /regular/path HTTP/1.1\r\nHost: test.localhost\r\n\r\n",
|
||||
target: "127.0.0.1:8001",
|
||||
expected: http.StatusOK,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
conn, err := net.Dial("tcp", test.target)
|
||||
require.NoError(s.T(), err)
|
||||
|
||||
_, err = conn.Write([]byte(test.request))
|
||||
require.NoError(s.T(), err)
|
||||
|
||||
resp, err := http.ReadResponse(bufio.NewReader(conn), nil)
|
||||
require.NoError(s.T(), err)
|
||||
|
||||
assert.Equalf(s.T(), test.expected, resp.StatusCode, "%s failed with %d instead of %d", test.desc, resp.StatusCode, test.expected)
|
||||
|
||||
err = conn.Close()
|
||||
require.NoError(s.T(), err)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -76,7 +76,7 @@ func TestHandler_SupportDump(t *testing.T) {
|
|||
assert.Contains(t, string(files["version.json"]), `"version":"dev"`)
|
||||
|
||||
// Verify static config contains entry points
|
||||
assert.Contains(t, string(files["static-config.json"]), `"entryPoints":{"web":{"address":"xxxx","http":{"encodedCharacters":{}}}`)
|
||||
assert.Contains(t, string(files["static-config.json"]), `"entryPoints":{"web":{"address":"xxxx","http":{}`)
|
||||
|
||||
// Verify runtime config contains services
|
||||
assert.Contains(t, string(files["runtime-config.json"]), `"services":`)
|
||||
|
|
|
|||
4
pkg/api/testdata/entrypoint-bar.json
vendored
4
pkg/api/testdata/entrypoint-bar.json
vendored
|
|
@ -1,7 +1,5 @@
|
|||
{
|
||||
"address": ":81",
|
||||
"http": {
|
||||
"encodedCharacters": {}
|
||||
},
|
||||
"http": {},
|
||||
"name": "bar"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
{
|
||||
"address": ":81",
|
||||
"http": {
|
||||
"encodedCharacters": {}
|
||||
},
|
||||
"http": {},
|
||||
"name": "foo / bar"
|
||||
}
|
||||
|
|
|
|||
20
pkg/api/testdata/entrypoints-many-lastpage.json
vendored
20
pkg/api/testdata/entrypoints-many-lastpage.json
vendored
|
|
@ -1,37 +1,27 @@
|
|||
[
|
||||
{
|
||||
"address": ":14",
|
||||
"http": {
|
||||
"encodedCharacters": {}
|
||||
},
|
||||
"http": {},
|
||||
"name": "ep14"
|
||||
},
|
||||
{
|
||||
"address": ":15",
|
||||
"http": {
|
||||
"encodedCharacters": {}
|
||||
},
|
||||
"http": {},
|
||||
"name": "ep15"
|
||||
},
|
||||
{
|
||||
"address": ":16",
|
||||
"http": {
|
||||
"encodedCharacters": {}
|
||||
},
|
||||
"http": {},
|
||||
"name": "ep16"
|
||||
},
|
||||
{
|
||||
"address": ":17",
|
||||
"http": {
|
||||
"encodedCharacters": {}
|
||||
},
|
||||
"http": {},
|
||||
"name": "ep17"
|
||||
},
|
||||
{
|
||||
"address": ":18",
|
||||
"http": {
|
||||
"encodedCharacters": {}
|
||||
},
|
||||
"http": {},
|
||||
"name": "ep18"
|
||||
}
|
||||
]
|
||||
|
|
|
|||
4
pkg/api/testdata/entrypoints-page2.json
vendored
4
pkg/api/testdata/entrypoints-page2.json
vendored
|
|
@ -1,9 +1,7 @@
|
|||
[
|
||||
{
|
||||
"address": ":82",
|
||||
"http": {
|
||||
"encodedCharacters": {}
|
||||
},
|
||||
"http": {},
|
||||
"name": "web2"
|
||||
}
|
||||
]
|
||||
|
|
|
|||
8
pkg/api/testdata/entrypoints.json
vendored
8
pkg/api/testdata/entrypoints.json
vendored
|
|
@ -8,9 +8,7 @@
|
|||
"192.168.1.4"
|
||||
]
|
||||
},
|
||||
"http": {
|
||||
"encodedCharacters": {}
|
||||
},
|
||||
"http": {},
|
||||
"name": "web",
|
||||
"proxyProtocol": {
|
||||
"insecure": true,
|
||||
|
|
@ -40,9 +38,7 @@
|
|||
"192.168.1.40"
|
||||
]
|
||||
},
|
||||
"http": {
|
||||
"encodedCharacters": {}
|
||||
},
|
||||
"http": {},
|
||||
"name": "websecure",
|
||||
"proxyProtocol": {
|
||||
"insecure": true,
|
||||
|
|
|
|||
|
|
@ -44,10 +44,11 @@ type HTTPConfiguration struct {
|
|||
|
||||
// Model holds model configuration.
|
||||
type Model struct {
|
||||
Middlewares []string `json:"middlewares,omitempty" toml:"middlewares,omitempty" yaml:"middlewares,omitempty" export:"true"`
|
||||
TLS *RouterTLSConfig `json:"tls,omitempty" toml:"tls,omitempty" yaml:"tls,omitempty" label:"allowEmpty" file:"allowEmpty" kv:"allowEmpty" export:"true"`
|
||||
Observability RouterObservabilityConfig `json:"observability,omitempty" toml:"observability,omitempty" yaml:"observability,omitempty" export:"true"`
|
||||
DefaultRuleSyntax string `json:"-" toml:"-" yaml:"-" label:"-" file:"-" kv:"-" export:"true"`
|
||||
Middlewares []string `json:"middlewares,omitempty" toml:"middlewares,omitempty" yaml:"middlewares,omitempty" export:"true"`
|
||||
TLS *RouterTLSConfig `json:"tls,omitempty" toml:"tls,omitempty" yaml:"tls,omitempty" label:"allowEmpty" file:"allowEmpty" kv:"allowEmpty" export:"true"`
|
||||
Observability RouterObservabilityConfig `json:"observability,omitempty" toml:"observability,omitempty" yaml:"observability,omitempty" export:"true"`
|
||||
DeniedEncodedPathCharacters *RouterDeniedEncodedPathCharacters `json:"-" toml:"-" yaml:"-" label:"-" file:"-" kv:"-" export:"true"`
|
||||
DefaultRuleSyntax string `json:"-" toml:"-" yaml:"-" label:"-" file:"-" kv:"-" export:"true"`
|
||||
}
|
||||
|
||||
// +k8s:deepcopy-gen=true
|
||||
|
|
@ -71,11 +72,58 @@ type Router struct {
|
|||
Rule string `json:"rule,omitempty" toml:"rule,omitempty" yaml:"rule,omitempty"`
|
||||
ParentRefs []string `json:"parentRefs,omitempty" toml:"parentRefs,omitempty" yaml:"parentRefs,omitempty" label:"-" export:"true"`
|
||||
// Deprecated: Please do not use this field and rewrite the router rules to use the v3 syntax.
|
||||
RuleSyntax string `json:"ruleSyntax,omitempty" toml:"ruleSyntax,omitempty" yaml:"ruleSyntax,omitempty" export:"true"`
|
||||
Priority int `json:"priority,omitempty" toml:"priority,omitempty,omitzero" yaml:"priority,omitempty" export:"true"`
|
||||
TLS *RouterTLSConfig `json:"tls,omitempty" toml:"tls,omitempty" yaml:"tls,omitempty" label:"allowEmpty" file:"allowEmpty" kv:"allowEmpty" export:"true"`
|
||||
Observability *RouterObservabilityConfig `json:"observability,omitempty" toml:"observability,omitempty" yaml:"observability,omitempty" export:"true"`
|
||||
DefaultRule bool `json:"-" toml:"-" yaml:"-" label:"-" file:"-"`
|
||||
RuleSyntax string `json:"ruleSyntax,omitempty" toml:"ruleSyntax,omitempty" yaml:"ruleSyntax,omitempty" export:"true"`
|
||||
Priority int `json:"priority,omitempty" toml:"priority,omitempty,omitzero" yaml:"priority,omitempty" export:"true"`
|
||||
TLS *RouterTLSConfig `json:"tls,omitempty" toml:"tls,omitempty" yaml:"tls,omitempty" label:"allowEmpty" file:"allowEmpty" kv:"allowEmpty" export:"true"`
|
||||
Observability *RouterObservabilityConfig `json:"observability,omitempty" toml:"observability,omitempty" yaml:"observability,omitempty" export:"true"`
|
||||
DefaultRule bool `json:"-" toml:"-" yaml:"-" label:"-" file:"-"`
|
||||
DeniedEncodedPathCharacters RouterDeniedEncodedPathCharacters `json:"-" toml:"-" yaml:"-" label:"-" file:"-"`
|
||||
}
|
||||
|
||||
// +k8s:deepcopy-gen=true
|
||||
|
||||
// RouterDeniedEncodedPathCharacters configures which encoded characters are allowed in the request path.
|
||||
type RouterDeniedEncodedPathCharacters struct {
|
||||
AllowEncodedSlash bool `description:"Defines whether requests with encoded slash characters in the path are allowed." json:"allowEncodedSlash,omitempty" toml:"allowEncodedSlash,omitempty" yaml:"allowEncodedSlash,omitempty" export:"true"`
|
||||
AllowEncodedBackSlash bool `description:"Defines whether requests with encoded back slash characters in the path are allowed." json:"allowEncodedBackSlash,omitempty" toml:"allowEncodedBackSlash,omitempty" yaml:"allowEncodedBackSlash,omitempty" export:"true"`
|
||||
AllowEncodedNullCharacter bool `description:"Defines whether requests with encoded null characters in the path are allowed." json:"allowEncodedNullCharacter,omitempty" toml:"allowEncodedNullCharacter,omitempty" yaml:"allowEncodedNullCharacter,omitempty" export:"true"`
|
||||
AllowEncodedSemicolon bool `description:"Defines whether requests with encoded semicolon characters in the path are allowed." json:"allowEncodedSemicolon,omitempty" toml:"allowEncodedSemicolon,omitempty" yaml:"allowEncodedSemicolon,omitempty" export:"true"`
|
||||
AllowEncodedPercent bool `description:"Defines whether requests with encoded percent characters in the path are allowed." json:"allowEncodedPercent,omitempty" toml:"allowEncodedPercent,omitempty" yaml:"allowEncodedPercent,omitempty" export:"true"`
|
||||
AllowEncodedQuestionMark bool `description:"Defines whether requests with encoded question mark characters in the path are allowed." json:"allowEncodedQuestionMark,omitempty" toml:"allowEncodedQuestionMark,omitempty" yaml:"allowEncodedQuestionMark,omitempty" export:"true"`
|
||||
AllowEncodedHash bool `description:"Defines whether requests with encoded hash characters in the path are allowed." json:"allowEncodedHash,omitempty" toml:"allowEncodedHash,omitempty" yaml:"allowEncodedHash,omitempty" export:"true"`
|
||||
}
|
||||
|
||||
// Map returns a map of unallowed encoded characters.
|
||||
func (r *RouterDeniedEncodedPathCharacters) Map() map[string]struct{} {
|
||||
characters := make(map[string]struct{})
|
||||
|
||||
if !r.AllowEncodedSlash {
|
||||
characters["%2F"] = struct{}{}
|
||||
characters["%2f"] = struct{}{}
|
||||
}
|
||||
if !r.AllowEncodedBackSlash {
|
||||
characters["%5C"] = struct{}{}
|
||||
characters["%5c"] = struct{}{}
|
||||
}
|
||||
if !r.AllowEncodedNullCharacter {
|
||||
characters["%00"] = struct{}{}
|
||||
}
|
||||
if !r.AllowEncodedSemicolon {
|
||||
characters["%3B"] = struct{}{}
|
||||
characters["%3b"] = struct{}{}
|
||||
}
|
||||
if !r.AllowEncodedPercent {
|
||||
characters["%25"] = struct{}{}
|
||||
}
|
||||
if !r.AllowEncodedQuestionMark {
|
||||
characters["%3F"] = struct{}{}
|
||||
characters["%3f"] = struct{}{}
|
||||
}
|
||||
if !r.AllowEncodedHash {
|
||||
characters["%23"] = struct{}{}
|
||||
}
|
||||
|
||||
return characters
|
||||
}
|
||||
|
||||
// +k8s:deepcopy-gen=true
|
||||
|
|
|
|||
165
pkg/config/dynamic/http_config_test.go
Normal file
165
pkg/config/dynamic/http_config_test.go
Normal file
|
|
@ -0,0 +1,165 @@
|
|||
package dynamic
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestEncodedCharactersMap(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
config RouterDeniedEncodedPathCharacters
|
||||
expected map[string]struct{}
|
||||
}{
|
||||
{
|
||||
name: "Handles empty configuration",
|
||||
expected: map[string]struct{}{
|
||||
"%2F": {},
|
||||
"%2f": {},
|
||||
"%5C": {},
|
||||
"%5c": {},
|
||||
"%00": {},
|
||||
"%3B": {},
|
||||
"%3b": {},
|
||||
"%25": {},
|
||||
"%3F": {},
|
||||
"%3f": {},
|
||||
"%23": {},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Exclude encoded slash when allowed",
|
||||
config: RouterDeniedEncodedPathCharacters{
|
||||
AllowEncodedSlash: true,
|
||||
},
|
||||
expected: map[string]struct{}{
|
||||
"%5C": {},
|
||||
"%5c": {},
|
||||
"%00": {},
|
||||
"%3B": {},
|
||||
"%3b": {},
|
||||
"%25": {},
|
||||
"%3F": {},
|
||||
"%3f": {},
|
||||
"%23": {},
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
name: "Exclude encoded backslash when allowed",
|
||||
config: RouterDeniedEncodedPathCharacters{
|
||||
AllowEncodedBackSlash: true,
|
||||
},
|
||||
expected: map[string]struct{}{
|
||||
"%2F": {},
|
||||
"%2f": {},
|
||||
"%00": {},
|
||||
"%3B": {},
|
||||
"%3b": {},
|
||||
"%25": {},
|
||||
"%3F": {},
|
||||
"%3f": {},
|
||||
"%23": {},
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
name: "Exclude encoded null character when allowed",
|
||||
config: RouterDeniedEncodedPathCharacters{
|
||||
AllowEncodedNullCharacter: true,
|
||||
},
|
||||
expected: map[string]struct{}{
|
||||
"%2F": {},
|
||||
"%2f": {},
|
||||
"%5C": {},
|
||||
"%5c": {},
|
||||
"%3B": {},
|
||||
"%3b": {},
|
||||
"%25": {},
|
||||
"%3F": {},
|
||||
"%3f": {},
|
||||
"%23": {},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Exclude encoded semicolon when allowed",
|
||||
config: RouterDeniedEncodedPathCharacters{
|
||||
AllowEncodedSemicolon: true,
|
||||
},
|
||||
expected: map[string]struct{}{
|
||||
"%2F": {},
|
||||
"%2f": {},
|
||||
"%5C": {},
|
||||
"%5c": {},
|
||||
"%00": {},
|
||||
"%25": {},
|
||||
"%3F": {},
|
||||
"%3f": {},
|
||||
"%23": {},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Exclude encoded percent when allowed",
|
||||
config: RouterDeniedEncodedPathCharacters{
|
||||
AllowEncodedPercent: true,
|
||||
},
|
||||
expected: map[string]struct{}{
|
||||
"%2F": {},
|
||||
"%2f": {},
|
||||
"%5C": {},
|
||||
"%5c": {},
|
||||
"%00": {},
|
||||
"%3B": {},
|
||||
"%3b": {},
|
||||
"%3F": {},
|
||||
"%3f": {},
|
||||
"%23": {},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Exclude encoded question mark when allowed",
|
||||
config: RouterDeniedEncodedPathCharacters{
|
||||
AllowEncodedQuestionMark: true,
|
||||
},
|
||||
expected: map[string]struct{}{
|
||||
"%2F": {},
|
||||
"%2f": {},
|
||||
"%5C": {},
|
||||
"%5c": {},
|
||||
"%00": {},
|
||||
"%3B": {},
|
||||
"%3b": {},
|
||||
"%25": {},
|
||||
"%23": {},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Exclude encoded hash when allowed",
|
||||
config: RouterDeniedEncodedPathCharacters{
|
||||
AllowEncodedHash: true,
|
||||
},
|
||||
expected: map[string]struct{}{
|
||||
"%2F": {},
|
||||
"%2f": {},
|
||||
"%5C": {},
|
||||
"%5c": {},
|
||||
"%00": {},
|
||||
"%3B": {},
|
||||
"%3b": {},
|
||||
"%25": {},
|
||||
"%3F": {},
|
||||
"%3f": {},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
result := test.config.Map()
|
||||
require.Equal(t, test.expected, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -1086,6 +1086,11 @@ func (in *Model) DeepCopyInto(out *Model) {
|
|||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
in.Observability.DeepCopyInto(&out.Observability)
|
||||
if in.DeniedEncodedPathCharacters != nil {
|
||||
in, out := &in.DeniedEncodedPathCharacters, &out.DeniedEncodedPathCharacters
|
||||
*out = new(RouterDeniedEncodedPathCharacters)
|
||||
**out = **in
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -1384,6 +1389,7 @@ func (in *Router) DeepCopyInto(out *Router) {
|
|||
*out = new(RouterObservabilityConfig)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
out.DeniedEncodedPathCharacters = in.DeniedEncodedPathCharacters
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -1397,6 +1403,22 @@ func (in *Router) DeepCopy() *Router {
|
|||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *RouterDeniedEncodedPathCharacters) DeepCopyInto(out *RouterDeniedEncodedPathCharacters) {
|
||||
*out = *in
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RouterDeniedEncodedPathCharacters.
|
||||
func (in *RouterDeniedEncodedPathCharacters) DeepCopy() *RouterDeniedEncodedPathCharacters {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(RouterDeniedEncodedPathCharacters)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *RouterObservabilityConfig) DeepCopyInto(out *RouterObservabilityConfig) {
|
||||
*out = *in
|
||||
|
|
|
|||
|
|
@ -65,13 +65,13 @@ func (ep *EntryPoint) SetDefaults() {
|
|||
|
||||
// HTTPConfig is the HTTP configuration of an entry point.
|
||||
type HTTPConfig struct {
|
||||
Redirections *Redirections `description:"Set of redirection" json:"redirections,omitempty" toml:"redirections,omitempty" yaml:"redirections,omitempty" export:"true"`
|
||||
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"`
|
||||
EncodedCharacters EncodedCharacters `description:"Defines which encoded characters are allowed in the request path." json:"encodedCharacters,omitempty" toml:"encodedCharacters,omitempty" yaml:"encodedCharacters,omitempty" export:"true"`
|
||||
EncodeQuerySemicolons bool `description:"Defines whether request query semicolons should be URLEncoded." json:"encodeQuerySemicolons,omitempty" toml:"encodeQuerySemicolons,omitempty" yaml:"encodeQuerySemicolons,omitempty" export:"true"`
|
||||
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"`
|
||||
Redirections *Redirections `description:"Set of redirection" json:"redirections,omitempty" toml:"redirections,omitempty" yaml:"redirections,omitempty" export:"true"`
|
||||
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"`
|
||||
EncodedCharacters *EncodedCharacters `description:"Defines which encoded characters are allowed in the request path." json:"encodedCharacters,omitempty" toml:"encodedCharacters,omitempty" yaml:"encodedCharacters,omitempty" export:"true"`
|
||||
EncodeQuerySemicolons bool `description:"Defines whether request query semicolons should be URLEncoded." json:"encodeQuerySemicolons,omitempty" toml:"encodeQuerySemicolons,omitempty" yaml:"encodeQuerySemicolons,omitempty" export:"true"`
|
||||
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.
|
||||
|
|
@ -92,39 +92,6 @@ type EncodedCharacters struct {
|
|||
AllowEncodedHash bool `description:"Defines whether requests with encoded hash characters in the path are allowed." json:"allowEncodedHash,omitempty" toml:"allowEncodedHash,omitempty" yaml:"allowEncodedHash,omitempty" export:"true"`
|
||||
}
|
||||
|
||||
// Map returns a map of unallowed encoded characters.
|
||||
func (h *EncodedCharacters) Map() map[string]struct{} {
|
||||
characters := make(map[string]struct{})
|
||||
|
||||
if !h.AllowEncodedSlash {
|
||||
characters["%2F"] = struct{}{}
|
||||
characters["%2f"] = struct{}{}
|
||||
}
|
||||
if !h.AllowEncodedBackSlash {
|
||||
characters["%5C"] = struct{}{}
|
||||
characters["%5c"] = struct{}{}
|
||||
}
|
||||
if !h.AllowEncodedNullCharacter {
|
||||
characters["%00"] = struct{}{}
|
||||
}
|
||||
if !h.AllowEncodedSemicolon {
|
||||
characters["%3B"] = struct{}{}
|
||||
characters["%3b"] = struct{}{}
|
||||
}
|
||||
if !h.AllowEncodedPercent {
|
||||
characters["%25"] = struct{}{}
|
||||
}
|
||||
if !h.AllowEncodedQuestionMark {
|
||||
characters["%3F"] = struct{}{}
|
||||
characters["%3f"] = struct{}{}
|
||||
}
|
||||
if !h.AllowEncodedHash {
|
||||
characters["%23"] = struct{}{}
|
||||
}
|
||||
|
||||
return characters
|
||||
}
|
||||
|
||||
// HTTP2Config is the HTTP2 configuration of an entry point.
|
||||
type HTTP2Config struct {
|
||||
MaxConcurrentStreams int32 `description:"Specifies the number of concurrent streams per connection that each client is allowed to initiate." json:"maxConcurrentStreams,omitempty" toml:"maxConcurrentStreams,omitempty" yaml:"maxConcurrentStreams,omitempty" export:"true"`
|
||||
|
|
|
|||
|
|
@ -65,161 +65,3 @@ func TestEntryPointProtocol(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestEncodedCharactersMap(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
config EncodedCharacters
|
||||
expected map[string]struct{}
|
||||
}{
|
||||
{
|
||||
name: "Handles empty configuration",
|
||||
expected: map[string]struct{}{
|
||||
"%2F": {},
|
||||
"%2f": {},
|
||||
"%5C": {},
|
||||
"%5c": {},
|
||||
"%00": {},
|
||||
"%3B": {},
|
||||
"%3b": {},
|
||||
"%25": {},
|
||||
"%3F": {},
|
||||
"%3f": {},
|
||||
"%23": {},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Exclude encoded slash when allowed",
|
||||
config: EncodedCharacters{
|
||||
AllowEncodedSlash: true,
|
||||
},
|
||||
expected: map[string]struct{}{
|
||||
"%5C": {},
|
||||
"%5c": {},
|
||||
"%00": {},
|
||||
"%3B": {},
|
||||
"%3b": {},
|
||||
"%25": {},
|
||||
"%3F": {},
|
||||
"%3f": {},
|
||||
"%23": {},
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
name: "Exclude encoded backslash when allowed",
|
||||
config: EncodedCharacters{
|
||||
AllowEncodedBackSlash: true,
|
||||
},
|
||||
expected: map[string]struct{}{
|
||||
"%2F": {},
|
||||
"%2f": {},
|
||||
"%00": {},
|
||||
"%3B": {},
|
||||
"%3b": {},
|
||||
"%25": {},
|
||||
"%3F": {},
|
||||
"%3f": {},
|
||||
"%23": {},
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
name: "Exclude encoded null character when allowed",
|
||||
config: EncodedCharacters{
|
||||
AllowEncodedNullCharacter: true,
|
||||
},
|
||||
expected: map[string]struct{}{
|
||||
"%2F": {},
|
||||
"%2f": {},
|
||||
"%5C": {},
|
||||
"%5c": {},
|
||||
"%3B": {},
|
||||
"%3b": {},
|
||||
"%25": {},
|
||||
"%3F": {},
|
||||
"%3f": {},
|
||||
"%23": {},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Exclude encoded semicolon when allowed",
|
||||
config: EncodedCharacters{
|
||||
AllowEncodedSemicolon: true,
|
||||
},
|
||||
expected: map[string]struct{}{
|
||||
"%2F": {},
|
||||
"%2f": {},
|
||||
"%5C": {},
|
||||
"%5c": {},
|
||||
"%00": {},
|
||||
"%25": {},
|
||||
"%3F": {},
|
||||
"%3f": {},
|
||||
"%23": {},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Exclude encoded percent when allowed",
|
||||
config: EncodedCharacters{
|
||||
AllowEncodedPercent: true,
|
||||
},
|
||||
expected: map[string]struct{}{
|
||||
"%2F": {},
|
||||
"%2f": {},
|
||||
"%5C": {},
|
||||
"%5c": {},
|
||||
"%00": {},
|
||||
"%3B": {},
|
||||
"%3b": {},
|
||||
"%3F": {},
|
||||
"%3f": {},
|
||||
"%23": {},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Exclude encoded question mark when allowed",
|
||||
config: EncodedCharacters{
|
||||
AllowEncodedQuestionMark: true,
|
||||
},
|
||||
expected: map[string]struct{}{
|
||||
"%2F": {},
|
||||
"%2f": {},
|
||||
"%5C": {},
|
||||
"%5c": {},
|
||||
"%00": {},
|
||||
"%3B": {},
|
||||
"%3b": {},
|
||||
"%25": {},
|
||||
"%23": {},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Exclude encoded hash when allowed",
|
||||
config: EncodedCharacters{
|
||||
AllowEncodedHash: true,
|
||||
},
|
||||
expected: map[string]struct{}{
|
||||
"%2F": {},
|
||||
"%2f": {},
|
||||
"%5C": {},
|
||||
"%5c": {},
|
||||
"%00": {},
|
||||
"%3B": {},
|
||||
"%3b": {},
|
||||
"%25": {},
|
||||
"%3F": {},
|
||||
"%3f": {},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
result := test.config.Map()
|
||||
require.Equal(t, test.expected, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -231,7 +231,7 @@ func (i *Provider) entryPointModels(cfg *dynamic.Configuration) {
|
|||
}
|
||||
}
|
||||
|
||||
if len(ep.HTTP.Middlewares) == 0 && ep.HTTP.TLS == nil && defaultRuleSyntax == "" && ep.Observability == nil {
|
||||
if len(ep.HTTP.Middlewares) == 0 && ep.HTTP.TLS == nil && defaultRuleSyntax == "" && ep.Observability == nil && ep.HTTP.EncodedCharacters == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
|
|
@ -240,6 +240,18 @@ func (i *Provider) entryPointModels(cfg *dynamic.Configuration) {
|
|||
Middlewares: ep.HTTP.Middlewares,
|
||||
}
|
||||
|
||||
if ep.HTTP.EncodedCharacters != nil {
|
||||
httpModel.DeniedEncodedPathCharacters = &dynamic.RouterDeniedEncodedPathCharacters{
|
||||
AllowEncodedSlash: ep.HTTP.EncodedCharacters.AllowEncodedSlash,
|
||||
AllowEncodedBackSlash: ep.HTTP.EncodedCharacters.AllowEncodedBackSlash,
|
||||
AllowEncodedPercent: ep.HTTP.EncodedCharacters.AllowEncodedPercent,
|
||||
AllowEncodedQuestionMark: ep.HTTP.EncodedCharacters.AllowEncodedQuestionMark,
|
||||
AllowEncodedSemicolon: ep.HTTP.EncodedCharacters.AllowEncodedSemicolon,
|
||||
AllowEncodedHash: ep.HTTP.EncodedCharacters.AllowEncodedHash,
|
||||
AllowEncodedNullCharacter: ep.HTTP.EncodedCharacters.AllowEncodedNullCharacter,
|
||||
}
|
||||
}
|
||||
|
||||
if ep.Observability != nil {
|
||||
httpModel.Observability = dynamic.RouterObservabilityConfig{
|
||||
AccessLogs: ep.Observability.AccessLogs,
|
||||
|
|
|
|||
|
|
@ -82,8 +82,7 @@
|
|||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"encodedCharacters": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -212,6 +212,12 @@ func applyModel(cfg dynamic.Configuration) dynamic.Configuration {
|
|||
|
||||
cp.Middlewares = append(m.Middlewares, cp.Middlewares...)
|
||||
|
||||
if m.DeniedEncodedPathCharacters != nil {
|
||||
// As the denied encoded path characters option is not configurable at the router level,
|
||||
// we can simply copy the whole structure to override the router's default config.
|
||||
cp.DeniedEncodedPathCharacters = *m.DeniedEncodedPathCharacters
|
||||
}
|
||||
|
||||
if cp.Observability == nil {
|
||||
cp.Observability = &dynamic.RouterObservabilityConfig{}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,14 +39,13 @@ type serviceManager interface {
|
|||
|
||||
// Manager A route/router manager.
|
||||
type Manager struct {
|
||||
routerHandlers map[string]http.Handler
|
||||
serviceManager serviceManager
|
||||
observabilityMgr *middleware.ObservabilityMgr
|
||||
middlewaresBuilder middlewareBuilder
|
||||
conf *runtime.Configuration
|
||||
tlsManager *tls.Manager
|
||||
parser httpmuxer.SyntaxParser
|
||||
deniedEncodedPathCharacters map[string]map[string]struct{}
|
||||
routerHandlers map[string]http.Handler
|
||||
serviceManager serviceManager
|
||||
observabilityMgr *middleware.ObservabilityMgr
|
||||
middlewaresBuilder middlewareBuilder
|
||||
conf *runtime.Configuration
|
||||
tlsManager *tls.Manager
|
||||
parser httpmuxer.SyntaxParser
|
||||
}
|
||||
|
||||
// NewManager creates a new Manager.
|
||||
|
|
@ -56,17 +55,15 @@ func NewManager(conf *runtime.Configuration,
|
|||
observabilityMgr *middleware.ObservabilityMgr,
|
||||
tlsManager *tls.Manager,
|
||||
parser httpmuxer.SyntaxParser,
|
||||
deniedEncodedPathCharacters map[string]map[string]struct{},
|
||||
) *Manager {
|
||||
return &Manager{
|
||||
routerHandlers: make(map[string]http.Handler),
|
||||
serviceManager: serviceManager,
|
||||
observabilityMgr: observabilityMgr,
|
||||
middlewaresBuilder: middlewaresBuilder,
|
||||
conf: conf,
|
||||
tlsManager: tlsManager,
|
||||
parser: parser,
|
||||
deniedEncodedPathCharacters: deniedEncodedPathCharacters,
|
||||
routerHandlers: make(map[string]http.Handler),
|
||||
serviceManager: serviceManager,
|
||||
observabilityMgr: observabilityMgr,
|
||||
middlewaresBuilder: middlewaresBuilder,
|
||||
conf: conf,
|
||||
tlsManager: tlsManager,
|
||||
parser: parser,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -282,7 +279,7 @@ func (m *Manager) buildHTTPHandler(ctx context.Context, router *runtime.RouterIn
|
|||
return denyFragment(next), nil
|
||||
})
|
||||
chain = chain.Append(func(next http.Handler) (http.Handler, error) {
|
||||
return denyEncodedPathCharacters(m.deniedEncodedPathCharacters[entryPointName], next), nil
|
||||
return denyEncodedPathCharacters(router.DeniedEncodedPathCharacters.Map(), next), nil
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@ import (
|
|||
ptypes "github.com/traefik/paerser/types"
|
||||
"github.com/traefik/traefik/v3/pkg/config/dynamic"
|
||||
"github.com/traefik/traefik/v3/pkg/config/runtime"
|
||||
"github.com/traefik/traefik/v3/pkg/config/static"
|
||||
"github.com/traefik/traefik/v3/pkg/middlewares/requestdecorator"
|
||||
httpmuxer "github.com/traefik/traefik/v3/pkg/muxer/http"
|
||||
"github.com/traefik/traefik/v3/pkg/server/middleware"
|
||||
|
|
@ -333,7 +332,7 @@ func TestRouterManager_Get(t *testing.T) {
|
|||
parser, err := httpmuxer.NewSyntaxParser()
|
||||
require.NoError(t, err)
|
||||
|
||||
routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, nil, tlsManager, parser, nil)
|
||||
routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, nil, tlsManager, parser)
|
||||
|
||||
handlers := routerManager.BuildHandlers(t.Context(), test.entryPoints, false)
|
||||
|
||||
|
|
@ -721,7 +720,7 @@ func TestRuntimeConfiguration(t *testing.T) {
|
|||
parser, err := httpmuxer.NewSyntaxParser()
|
||||
require.NoError(t, err)
|
||||
|
||||
routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, nil, tlsManager, parser, nil)
|
||||
routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, nil, tlsManager, parser)
|
||||
|
||||
_ = routerManager.BuildHandlers(t.Context(), entryPoints, false)
|
||||
_ = routerManager.BuildHandlers(t.Context(), entryPoints, true)
|
||||
|
|
@ -802,7 +801,7 @@ func TestProviderOnMiddlewares(t *testing.T) {
|
|||
parser, err := httpmuxer.NewSyntaxParser()
|
||||
require.NoError(t, err)
|
||||
|
||||
routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, nil, tlsManager, parser, nil)
|
||||
routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, nil, tlsManager, parser)
|
||||
|
||||
_ = routerManager.BuildHandlers(t.Context(), entryPoints, false)
|
||||
|
||||
|
|
@ -857,7 +856,7 @@ func BenchmarkRouterServe(b *testing.B) {
|
|||
parser, err := httpmuxer.NewSyntaxParser()
|
||||
require.NoError(b, err)
|
||||
|
||||
routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, nil, tlsManager, parser, nil)
|
||||
routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, nil, tlsManager, parser)
|
||||
|
||||
handlers := routerManager.BuildHandlers(b.Context(), entryPoints, false)
|
||||
|
||||
|
|
@ -1450,7 +1449,7 @@ func TestManager_buildChildRoutersMuxer(t *testing.T) {
|
|||
parser, err := httpmuxer.NewSyntaxParser()
|
||||
require.NoError(t, err)
|
||||
|
||||
manager := NewManager(conf, serviceManager, middlewareBuilder, nil, nil, parser, nil)
|
||||
manager := NewManager(conf, serviceManager, middlewareBuilder, nil, nil, parser)
|
||||
|
||||
// Compute multi-layer routing to populate ChildRefs
|
||||
manager.ParseRouterTree()
|
||||
|
|
@ -1641,7 +1640,7 @@ func TestManager_buildHTTPHandler_WithChildRouters(t *testing.T) {
|
|||
parser, err := httpmuxer.NewSyntaxParser()
|
||||
require.NoError(t, err)
|
||||
|
||||
manager := NewManager(conf, serviceManager, middlewareBuilder, nil, nil, parser, nil)
|
||||
manager := NewManager(conf, serviceManager, middlewareBuilder, nil, nil, parser)
|
||||
|
||||
// Run ParseRouterTree to validate configuration and populate ChildRefs/errors
|
||||
manager.ParseRouterTree()
|
||||
|
|
@ -1788,7 +1787,7 @@ func TestManager_BuildHandlers_WithChildRouters(t *testing.T) {
|
|||
parser, err := httpmuxer.NewSyntaxParser()
|
||||
require.NoError(t, err)
|
||||
|
||||
manager := NewManager(conf, serviceManager, middlewareBuilder, nil, nil, parser, nil)
|
||||
manager := NewManager(conf, serviceManager, middlewareBuilder, nil, nil, parser)
|
||||
|
||||
// Compute multi-layer routing to set up parent-child relationships
|
||||
manager.ParseRouterTree()
|
||||
|
|
@ -1819,11 +1818,10 @@ func TestManager_BuildHandlers_Deny(t *testing.T) {
|
|||
routers map[string]*dynamic.Router
|
||||
services map[string]*dynamic.Service
|
||||
requestPath string
|
||||
encodedCharacters static.EncodedCharacters
|
||||
expectedStatusCode int
|
||||
}{
|
||||
{
|
||||
desc: "parent router without child routers request with encoded slash",
|
||||
desc: "parent router without child routers, request with encoded slash",
|
||||
requestPath: "/foo%2F",
|
||||
routers: map[string]*dynamic.Router{
|
||||
"parent": {
|
||||
|
|
@ -1842,7 +1840,7 @@ func TestManager_BuildHandlers_Deny(t *testing.T) {
|
|||
expectedStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
{
|
||||
desc: "parent router with child routers request with encoded slash",
|
||||
desc: "parent router with child routers, request with encoded slash",
|
||||
requestPath: "/foo%2F",
|
||||
routers: map[string]*dynamic.Router{
|
||||
"parent": {
|
||||
|
|
@ -1865,13 +1863,16 @@ func TestManager_BuildHandlers_Deny(t *testing.T) {
|
|||
expectedStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
{
|
||||
desc: "parent router without child router allowing encoded slash",
|
||||
desc: "parent router allowing encoded slash without child router",
|
||||
requestPath: "/foo%2F",
|
||||
routers: map[string]*dynamic.Router{
|
||||
"parent": {
|
||||
EntryPoints: []string{"web"},
|
||||
Rule: "PathPrefix(`/`)",
|
||||
Service: "service",
|
||||
DeniedEncodedPathCharacters: dynamic.RouterDeniedEncodedPathCharacters{
|
||||
AllowEncodedSlash: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
services: map[string]*dynamic.Service{
|
||||
|
|
@ -1881,18 +1882,18 @@ func TestManager_BuildHandlers_Deny(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
encodedCharacters: static.EncodedCharacters{
|
||||
AllowEncodedSlash: true,
|
||||
},
|
||||
expectedStatusCode: http.StatusOK,
|
||||
},
|
||||
{
|
||||
desc: "parent router with child routers allowing encoded slash",
|
||||
desc: "parent router allowing encoded slash with child routers",
|
||||
requestPath: "/foo%2F",
|
||||
routers: map[string]*dynamic.Router{
|
||||
"parent": {
|
||||
EntryPoints: []string{"web"},
|
||||
Rule: "PathPrefix(`/`)",
|
||||
DeniedEncodedPathCharacters: dynamic.RouterDeniedEncodedPathCharacters{
|
||||
AllowEncodedSlash: true,
|
||||
},
|
||||
},
|
||||
"child1": {
|
||||
Rule: "PathPrefix(`/`)",
|
||||
|
|
@ -1907,13 +1908,10 @@ func TestManager_BuildHandlers_Deny(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
encodedCharacters: static.EncodedCharacters{
|
||||
AllowEncodedSlash: true,
|
||||
},
|
||||
expectedStatusCode: http.StatusOK,
|
||||
},
|
||||
{
|
||||
desc: "parent router without child routers request with fragment",
|
||||
desc: "parent router without child routers, request with fragment",
|
||||
requestPath: "/foo#",
|
||||
routers: map[string]*dynamic.Router{
|
||||
"parent": {
|
||||
|
|
@ -1932,7 +1930,7 @@ func TestManager_BuildHandlers_Deny(t *testing.T) {
|
|||
expectedStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
{
|
||||
desc: "parent router with child routers request with fragment",
|
||||
desc: "parent router with child routers, request with fragment",
|
||||
requestPath: "/foo#",
|
||||
routers: map[string]*dynamic.Router{
|
||||
"parent": {
|
||||
|
|
@ -1986,8 +1984,7 @@ func TestManager_BuildHandlers_Deny(t *testing.T) {
|
|||
parser, err := httpmuxer.NewSyntaxParser()
|
||||
require.NoError(t, err)
|
||||
|
||||
deniedEncodedPathCharacters := map[string]map[string]struct{}{"web": test.encodedCharacters.Map()}
|
||||
manager := NewManager(conf, serviceManager, middlewareBuilder, nil, nil, parser, deniedEncodedPathCharacters)
|
||||
manager := NewManager(conf, serviceManager, middlewareBuilder, nil, nil, parser)
|
||||
|
||||
// Compute multi-layer routing to set up parent-child relationships
|
||||
manager.ParseRouterTree()
|
||||
|
|
|
|||
|
|
@ -26,8 +26,7 @@ type RouterFactory struct {
|
|||
entryPointsTCP []string
|
||||
entryPointsUDP []string
|
||||
|
||||
allowACMEByPass map[string]bool
|
||||
deniedEncodedPathCharacters map[string]map[string]struct{}
|
||||
allowACMEByPass map[string]bool
|
||||
|
||||
managerFactory *service.ManagerFactory
|
||||
|
||||
|
|
@ -73,27 +72,21 @@ func NewRouterFactory(staticConfiguration static.Configuration, managerFactory *
|
|||
}
|
||||
}
|
||||
|
||||
deniedEncodedPathCharacters := map[string]map[string]struct{}{}
|
||||
for name, ep := range staticConfiguration.EntryPoints {
|
||||
deniedEncodedPathCharacters[name] = ep.HTTP.EncodedCharacters.Map()
|
||||
}
|
||||
|
||||
parser, err := httpmuxer.NewSyntaxParser()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("creating parser: %w", err)
|
||||
}
|
||||
|
||||
return &RouterFactory{
|
||||
entryPointsTCP: entryPointsTCP,
|
||||
entryPointsUDP: entryPointsUDP,
|
||||
managerFactory: managerFactory,
|
||||
observabilityMgr: observabilityMgr,
|
||||
tlsManager: tlsManager,
|
||||
pluginBuilder: pluginBuilder,
|
||||
dialerManager: dialerManager,
|
||||
allowACMEByPass: allowACMEByPass,
|
||||
deniedEncodedPathCharacters: deniedEncodedPathCharacters,
|
||||
parser: parser,
|
||||
entryPointsTCP: entryPointsTCP,
|
||||
entryPointsUDP: entryPointsUDP,
|
||||
managerFactory: managerFactory,
|
||||
observabilityMgr: observabilityMgr,
|
||||
tlsManager: tlsManager,
|
||||
pluginBuilder: pluginBuilder,
|
||||
dialerManager: dialerManager,
|
||||
allowACMEByPass: allowACMEByPass,
|
||||
parser: parser,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
|
@ -111,7 +104,7 @@ func (f *RouterFactory) CreateRouters(rtConf *runtime.Configuration) (map[string
|
|||
|
||||
middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager, f.pluginBuilder)
|
||||
|
||||
routerManager := router.NewManager(rtConf, serviceManager, middlewaresBuilder, f.observabilityMgr, f.tlsManager, f.parser, f.deniedEncodedPathCharacters)
|
||||
routerManager := router.NewManager(rtConf, serviceManager, middlewaresBuilder, f.observabilityMgr, f.tlsManager, f.parser)
|
||||
|
||||
routerManager.ParseRouterTree()
|
||||
|
||||
|
|
|
|||
|
|
@ -526,8 +526,10 @@ func TestPathOperations(t *testing.T) {
|
|||
configuration.SetDefaults()
|
||||
|
||||
// We need to allow some of the suspicious encoded characters to test the path operations in case they are authorized.
|
||||
configuration.HTTP.EncodedCharacters.AllowEncodedSlash = true
|
||||
configuration.HTTP.EncodedCharacters.AllowEncodedPercent = true
|
||||
configuration.HTTP.EncodedCharacters = &static.EncodedCharacters{
|
||||
AllowEncodedSlash: true,
|
||||
AllowEncodedPercent: true,
|
||||
}
|
||||
|
||||
// Create the HTTP server using newHTTPServer.
|
||||
server, err := newHTTPServer(t.Context(), ln, configuration, false, requestdecorator.New(nil))
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue