Fix deny encoded characters
Co-authored-by: Kevin Pollet <pollet.kevin@gmail.com>
This commit is contained in:
parent
8ebab1b243
commit
90ce858347
21 changed files with 427 additions and 300 deletions
|
|
@ -123,9 +123,6 @@ Trust only forwarded headers from selected IPs.
|
||||||
`--entrypoints.<name>.http`:
|
`--entrypoints.<name>.http`:
|
||||||
HTTP configuration.
|
HTTP configuration.
|
||||||
|
|
||||||
`--entrypoints.<name>.http.encodedcharacters`:
|
|
||||||
Defines which encoded characters are allowed in the request path.
|
|
||||||
|
|
||||||
`--entrypoints.<name>.http.encodedcharacters.allowencodedbackslash`:
|
`--entrypoints.<name>.http.encodedcharacters.allowencodedbackslash`:
|
||||||
Defines whether requests with encoded back slash characters in the path are allowed. (Default: ```false```)
|
Defines whether requests with encoded back slash characters in the path are allowed. (Default: ```false```)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -132,9 +132,6 @@ HTTP/3 configuration. (Default: ```false```)
|
||||||
`TRAEFIK_ENTRYPOINTS_<NAME>_HTTP3_ADVERTISEDPORT`:
|
`TRAEFIK_ENTRYPOINTS_<NAME>_HTTP3_ADVERTISEDPORT`:
|
||||||
UDP port to advertise, on which HTTP/3 is available. (Default: ```0```)
|
UDP port to advertise, on which HTTP/3 is available. (Default: ```0```)
|
||||||
|
|
||||||
`TRAEFIK_ENTRYPOINTS_<NAME>_HTTP_ENCODEDCHARACTERS`:
|
|
||||||
Defines which encoded characters are allowed in the request path.
|
|
||||||
|
|
||||||
`TRAEFIK_ENTRYPOINTS_<NAME>_HTTP_ENCODEDCHARACTERS_ALLOWENCODEDBACKSLASH`:
|
`TRAEFIK_ENTRYPOINTS_<NAME>_HTTP_ENCODEDCHARACTERS_ALLOWENCODEDBACKSLASH`:
|
||||||
Defines whether requests with encoded back slash characters in the path are allowed. (Default: ```false```)
|
Defines whether requests with encoded back slash characters in the path are allowed. (Default: ```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 }}"
|
||||||
|
|
@ -1481,3 +1481,72 @@ func (s *SimpleSuite) TestSanitizePath() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
4
pkg/api/testdata/entrypoint-bar.json
vendored
4
pkg/api/testdata/entrypoint-bar.json
vendored
|
|
@ -1,7 +1,5 @@
|
||||||
{
|
{
|
||||||
"address": ":81",
|
"address": ":81",
|
||||||
"http": {
|
"http": {},
|
||||||
"encodedCharacters": {}
|
|
||||||
},
|
|
||||||
"name": "bar"
|
"name": "bar"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,5 @@
|
||||||
{
|
{
|
||||||
"address": ":81",
|
"address": ":81",
|
||||||
"http": {
|
"http": {},
|
||||||
"encodedCharacters": {}
|
|
||||||
},
|
|
||||||
"name": "foo / bar"
|
"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",
|
"address": ":14",
|
||||||
"http": {
|
"http": {},
|
||||||
"encodedCharacters": {}
|
|
||||||
},
|
|
||||||
"name": "ep14"
|
"name": "ep14"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"address": ":15",
|
"address": ":15",
|
||||||
"http": {
|
"http": {},
|
||||||
"encodedCharacters": {}
|
|
||||||
},
|
|
||||||
"name": "ep15"
|
"name": "ep15"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"address": ":16",
|
"address": ":16",
|
||||||
"http": {
|
"http": {},
|
||||||
"encodedCharacters": {}
|
|
||||||
},
|
|
||||||
"name": "ep16"
|
"name": "ep16"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"address": ":17",
|
"address": ":17",
|
||||||
"http": {
|
"http": {},
|
||||||
"encodedCharacters": {}
|
|
||||||
},
|
|
||||||
"name": "ep17"
|
"name": "ep17"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"address": ":18",
|
"address": ":18",
|
||||||
"http": {
|
"http": {},
|
||||||
"encodedCharacters": {}
|
|
||||||
},
|
|
||||||
"name": "ep18"
|
"name": "ep18"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
||||||
4
pkg/api/testdata/entrypoints-page2.json
vendored
4
pkg/api/testdata/entrypoints-page2.json
vendored
|
|
@ -1,9 +1,7 @@
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"address": ":82",
|
"address": ":82",
|
||||||
"http": {
|
"http": {},
|
||||||
"encodedCharacters": {}
|
|
||||||
},
|
|
||||||
"name": "web2"
|
"name": "web2"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
||||||
8
pkg/api/testdata/entrypoints.json
vendored
8
pkg/api/testdata/entrypoints.json
vendored
|
|
@ -8,9 +8,7 @@
|
||||||
"192.168.1.4"
|
"192.168.1.4"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"http": {
|
"http": {},
|
||||||
"encodedCharacters": {}
|
|
||||||
},
|
|
||||||
"name": "web",
|
"name": "web",
|
||||||
"proxyProtocol": {
|
"proxyProtocol": {
|
||||||
"insecure": true,
|
"insecure": true,
|
||||||
|
|
@ -40,9 +38,7 @@
|
||||||
"192.168.1.40"
|
"192.168.1.40"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"http": {
|
"http": {},
|
||||||
"encodedCharacters": {}
|
|
||||||
},
|
|
||||||
"name": "websecure",
|
"name": "websecure",
|
||||||
"proxyProtocol": {
|
"proxyProtocol": {
|
||||||
"insecure": true,
|
"insecure": true,
|
||||||
|
|
|
||||||
|
|
@ -24,8 +24,9 @@ type HTTPConfiguration struct {
|
||||||
|
|
||||||
// Model is a set of default router's values.
|
// Model is a set of default router's values.
|
||||||
type Model struct {
|
type Model struct {
|
||||||
Middlewares []string `json:"middlewares,omitempty" toml:"middlewares,omitempty" yaml:"middlewares,omitempty" 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"`
|
TLS *RouterTLSConfig `json:"tls,omitempty" toml:"tls,omitempty" yaml:"tls,omitempty" label:"allowEmpty" file:"allowEmpty" kv:"allowEmpty" export:"true"`
|
||||||
|
DeniedEncodedPathCharacters *RouterDeniedEncodedPathCharacters `json:"-" toml:"-" yaml:"-" label:"-" file:"-" kv:"-" export:"true"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// +k8s:deepcopy-gen=true
|
// +k8s:deepcopy-gen=true
|
||||||
|
|
@ -42,13 +43,60 @@ type Service struct {
|
||||||
|
|
||||||
// Router holds the router configuration.
|
// Router holds the router configuration.
|
||||||
type Router struct {
|
type Router struct {
|
||||||
EntryPoints []string `json:"entryPoints,omitempty" toml:"entryPoints,omitempty" yaml:"entryPoints,omitempty" export:"true"`
|
EntryPoints []string `json:"entryPoints,omitempty" toml:"entryPoints,omitempty" yaml:"entryPoints,omitempty" export:"true"`
|
||||||
Middlewares []string `json:"middlewares,omitempty" toml:"middlewares,omitempty" yaml:"middlewares,omitempty" export:"true"`
|
Middlewares []string `json:"middlewares,omitempty" toml:"middlewares,omitempty" yaml:"middlewares,omitempty" export:"true"`
|
||||||
Service string `json:"service,omitempty" toml:"service,omitempty" yaml:"service,omitempty" export:"true"`
|
Service string `json:"service,omitempty" toml:"service,omitempty" yaml:"service,omitempty" export:"true"`
|
||||||
Rule string `json:"rule,omitempty" toml:"rule,omitempty" yaml:"rule,omitempty"`
|
Rule string `json:"rule,omitempty" toml:"rule,omitempty" yaml:"rule,omitempty"`
|
||||||
Priority int `json:"priority,omitempty" toml:"priority,omitempty,omitzero" yaml:"priority,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"`
|
TLS *RouterTLSConfig `json:"tls,omitempty" toml:"tls,omitempty" yaml:"tls,omitempty" label:"allowEmpty" file:"allowEmpty" kv:"allowEmpty" export:"true"`
|
||||||
DefaultRule bool `json:"-" toml:"-" yaml:"-" label:"-" file:"-"`
|
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
|
// +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)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -845,6 +845,11 @@ func (in *Model) DeepCopyInto(out *Model) {
|
||||||
*out = new(RouterTLSConfig)
|
*out = new(RouterTLSConfig)
|
||||||
(*in).DeepCopyInto(*out)
|
(*in).DeepCopyInto(*out)
|
||||||
}
|
}
|
||||||
|
if in.DeniedEncodedPathCharacters != nil {
|
||||||
|
in, out := &in.DeniedEncodedPathCharacters, &out.DeniedEncodedPathCharacters
|
||||||
|
*out = new(RouterDeniedEncodedPathCharacters)
|
||||||
|
**out = **in
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1030,6 +1035,7 @@ func (in *Router) DeepCopyInto(out *Router) {
|
||||||
*out = new(RouterTLSConfig)
|
*out = new(RouterTLSConfig)
|
||||||
(*in).DeepCopyInto(*out)
|
(*in).DeepCopyInto(*out)
|
||||||
}
|
}
|
||||||
|
out.DeniedEncodedPathCharacters = in.DeniedEncodedPathCharacters
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1043,6 +1049,22 @@ func (in *Router) DeepCopy() *Router {
|
||||||
return out
|
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.
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
func (in *RouterTCPTLSConfig) DeepCopyInto(out *RouterTCPTLSConfig) {
|
func (in *RouterTCPTLSConfig) DeepCopyInto(out *RouterTCPTLSConfig) {
|
||||||
*out = *in
|
*out = *in
|
||||||
|
|
|
||||||
|
|
@ -59,12 +59,12 @@ func (ep *EntryPoint) SetDefaults() {
|
||||||
|
|
||||||
// HTTPConfig is the HTTP configuration of an entry point.
|
// HTTPConfig is the HTTP configuration of an entry point.
|
||||||
type HTTPConfig struct {
|
type HTTPConfig struct {
|
||||||
Redirections *Redirections `description:"Set of redirection" json:"redirections,omitempty" toml:"redirections,omitempty" yaml:"redirections,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"`
|
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"`
|
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"`
|
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"`
|
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"`
|
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"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetDefaults sets the default values.
|
// SetDefaults sets the default values.
|
||||||
|
|
@ -84,39 +84,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"`
|
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.
|
// HTTP2Config is the HTTP2 configuration of an entry point.
|
||||||
type HTTP2Config struct {
|
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"`
|
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)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -212,7 +212,7 @@ func (i *Provider) getEntryPointPort(name string, def *static.Redirections) (str
|
||||||
|
|
||||||
func (i *Provider) entryPointModels(cfg *dynamic.Configuration) {
|
func (i *Provider) entryPointModels(cfg *dynamic.Configuration) {
|
||||||
for name, ep := range i.staticCfg.EntryPoints {
|
for name, ep := range i.staticCfg.EntryPoints {
|
||||||
if len(ep.HTTP.Middlewares) == 0 && ep.HTTP.TLS == nil {
|
if len(ep.HTTP.Middlewares) == 0 && ep.HTTP.TLS == nil && ep.HTTP.EncodedCharacters == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -220,6 +220,18 @@ func (i *Provider) entryPointModels(cfg *dynamic.Configuration) {
|
||||||
Middlewares: ep.HTTP.Middlewares,
|
Middlewares: ep.HTTP.Middlewares,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ep.HTTP.EncodedCharacters != nil {
|
||||||
|
m.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.HTTP.TLS != nil {
|
if ep.HTTP.TLS != nil {
|
||||||
m.TLS = &dynamic.RouterTLSConfig{
|
m.TLS = &dynamic.RouterTLSConfig{
|
||||||
Options: ep.HTTP.TLS.Options,
|
Options: ep.HTTP.TLS.Options,
|
||||||
|
|
|
||||||
|
|
@ -70,8 +70,7 @@
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
}
|
||||||
"encodedCharacters": {}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -164,6 +164,12 @@ func applyModel(cfg dynamic.Configuration) dynamic.Configuration {
|
||||||
|
|
||||||
cp.Middlewares = append(m.Middlewares, cp.Middlewares...)
|
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
|
||||||
|
}
|
||||||
|
|
||||||
rtName := name
|
rtName := name
|
||||||
if len(eps) > 1 {
|
if len(eps) > 1 {
|
||||||
rtName = epName + "-" + name
|
rtName = epName + "-" + name
|
||||||
|
|
|
||||||
|
|
@ -36,14 +36,13 @@ type serviceManager interface {
|
||||||
|
|
||||||
// Manager A route/router manager.
|
// Manager A route/router manager.
|
||||||
type Manager struct {
|
type Manager struct {
|
||||||
routerHandlers map[string]http.Handler
|
routerHandlers map[string]http.Handler
|
||||||
serviceManager serviceManager
|
serviceManager serviceManager
|
||||||
metricsRegistry metrics.Registry
|
metricsRegistry metrics.Registry
|
||||||
middlewaresBuilder middlewareBuilder
|
middlewaresBuilder middlewareBuilder
|
||||||
chainBuilder *middleware.ChainBuilder
|
chainBuilder *middleware.ChainBuilder
|
||||||
conf *runtime.Configuration
|
conf *runtime.Configuration
|
||||||
tlsManager *tls.Manager
|
tlsManager *tls.Manager
|
||||||
deniedEncodedPathCharacters map[string]map[string]struct{}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewManager creates a new Manager.
|
// NewManager creates a new Manager.
|
||||||
|
|
@ -53,17 +52,15 @@ func NewManager(conf *runtime.Configuration,
|
||||||
chainBuilder *middleware.ChainBuilder,
|
chainBuilder *middleware.ChainBuilder,
|
||||||
metricsRegistry metrics.Registry,
|
metricsRegistry metrics.Registry,
|
||||||
tlsManager *tls.Manager,
|
tlsManager *tls.Manager,
|
||||||
deniedEncodedPathCharacters map[string]map[string]struct{},
|
|
||||||
) *Manager {
|
) *Manager {
|
||||||
return &Manager{
|
return &Manager{
|
||||||
routerHandlers: make(map[string]http.Handler),
|
routerHandlers: make(map[string]http.Handler),
|
||||||
serviceManager: serviceManager,
|
serviceManager: serviceManager,
|
||||||
metricsRegistry: metricsRegistry,
|
metricsRegistry: metricsRegistry,
|
||||||
middlewaresBuilder: middlewaresBuilder,
|
middlewaresBuilder: middlewaresBuilder,
|
||||||
chainBuilder: chainBuilder,
|
chainBuilder: chainBuilder,
|
||||||
conf: conf,
|
conf: conf,
|
||||||
tlsManager: tlsManager,
|
tlsManager: tlsManager,
|
||||||
deniedEncodedPathCharacters: deniedEncodedPathCharacters,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -82,7 +79,7 @@ func (m *Manager) BuildHandlers(rootCtx context.Context, entryPoints []string, t
|
||||||
for entryPointName, routers := range m.getHTTPRouters(rootCtx, entryPoints, tls) {
|
for entryPointName, routers := range m.getHTTPRouters(rootCtx, entryPoints, tls) {
|
||||||
ctx := log.With(rootCtx, log.Str(log.EntryPointName, entryPointName))
|
ctx := log.With(rootCtx, log.Str(log.EntryPointName, entryPointName))
|
||||||
|
|
||||||
handler, err := m.buildEntryPointHandler(ctx, entryPointName, routers)
|
handler, err := m.buildEntryPointHandler(ctx, routers)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.FromContext(ctx).Error(err)
|
log.FromContext(ctx).Error(err)
|
||||||
continue
|
continue
|
||||||
|
|
@ -118,7 +115,7 @@ func (m *Manager) BuildHandlers(rootCtx context.Context, entryPoints []string, t
|
||||||
return entryPointHandlers
|
return entryPointHandlers
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) buildEntryPointHandler(ctx context.Context, entryPointName string, configs map[string]*runtime.RouterInfo) (http.Handler, error) {
|
func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string]*runtime.RouterInfo) (http.Handler, error) {
|
||||||
muxer, err := httpmuxer.NewMuxer()
|
muxer, err := httpmuxer.NewMuxer()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
@ -135,7 +132,7 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, entryPointName str
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
handler, err := m.buildRouterHandler(ctxRouter, entryPointName, routerName, routerConfig)
|
handler, err := m.buildRouterHandler(ctxRouter, routerName, routerConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
routerConfig.AddError(err, true)
|
routerConfig.AddError(err, true)
|
||||||
logger.Error(err)
|
logger.Error(err)
|
||||||
|
|
@ -160,7 +157,7 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, entryPointName str
|
||||||
return chain.Then(muxer)
|
return chain.Then(muxer)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) buildRouterHandler(ctx context.Context, entryPointName, routerName string, routerConfig *runtime.RouterInfo) (http.Handler, error) {
|
func (m *Manager) buildRouterHandler(ctx context.Context, routerName string, routerConfig *runtime.RouterInfo) (http.Handler, error) {
|
||||||
if handler, ok := m.routerHandlers[routerName]; ok {
|
if handler, ok := m.routerHandlers[routerName]; ok {
|
||||||
return handler, nil
|
return handler, nil
|
||||||
}
|
}
|
||||||
|
|
@ -176,7 +173,7 @@ func (m *Manager) buildRouterHandler(ctx context.Context, entryPointName, router
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handler, err := m.buildHTTPHandler(ctx, routerConfig, entryPointName, routerName)
|
handler, err := m.buildHTTPHandler(ctx, routerConfig, routerName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -194,7 +191,7 @@ func (m *Manager) buildRouterHandler(ctx context.Context, entryPointName, router
|
||||||
return m.routerHandlers[routerName], nil
|
return m.routerHandlers[routerName], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) buildHTTPHandler(ctx context.Context, router *runtime.RouterInfo, entryPointName, routerName string) (http.Handler, error) {
|
func (m *Manager) buildHTTPHandler(ctx context.Context, router *runtime.RouterInfo, routerName string) (http.Handler, error) {
|
||||||
var qualifiedNames []string
|
var qualifiedNames []string
|
||||||
for _, name := range router.Middlewares {
|
for _, name := range router.Middlewares {
|
||||||
qualifiedNames = append(qualifiedNames, provider.GetQualifiedName(ctx, name))
|
qualifiedNames = append(qualifiedNames, provider.GetQualifiedName(ctx, name))
|
||||||
|
|
@ -232,7 +229,7 @@ func (m *Manager) buildHTTPHandler(ctx context.Context, router *runtime.RouterIn
|
||||||
return denyFragment(next), nil
|
return denyFragment(next), nil
|
||||||
})
|
})
|
||||||
chain = chain.Append(func(next http.Handler) (http.Handler, error) {
|
chain = chain.Append(func(next http.Handler) (http.Handler, error) {
|
||||||
return denyEncodedPathCharacters(m.deniedEncodedPathCharacters[entryPointName], next), nil
|
return denyEncodedPathCharacters(router.DeniedEncodedPathCharacters.Map(), next), nil
|
||||||
})
|
})
|
||||||
|
|
||||||
return chain.Extend(*mHandler).Append(tHandler).Then(sHandler)
|
return chain.Extend(*mHandler).Append(tHandler).Then(sHandler)
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,6 @@ import (
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"github.com/traefik/traefik/v2/pkg/config/dynamic"
|
"github.com/traefik/traefik/v2/pkg/config/dynamic"
|
||||||
"github.com/traefik/traefik/v2/pkg/config/runtime"
|
"github.com/traefik/traefik/v2/pkg/config/runtime"
|
||||||
"github.com/traefik/traefik/v2/pkg/config/static"
|
|
||||||
"github.com/traefik/traefik/v2/pkg/metrics"
|
"github.com/traefik/traefik/v2/pkg/metrics"
|
||||||
"github.com/traefik/traefik/v2/pkg/middlewares/accesslog"
|
"github.com/traefik/traefik/v2/pkg/middlewares/accesslog"
|
||||||
"github.com/traefik/traefik/v2/pkg/middlewares/capture"
|
"github.com/traefik/traefik/v2/pkg/middlewares/capture"
|
||||||
|
|
@ -320,7 +319,7 @@ func TestRouterManager_Get(t *testing.T) {
|
||||||
chainBuilder := middleware.NewChainBuilder(nil, nil, nil)
|
chainBuilder := middleware.NewChainBuilder(nil, nil, nil)
|
||||||
tlsManager := tls.NewManager()
|
tlsManager := tls.NewManager()
|
||||||
|
|
||||||
routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, chainBuilder, metrics.NewVoidRegistry(), tlsManager, nil)
|
routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, chainBuilder, metrics.NewVoidRegistry(), tlsManager)
|
||||||
|
|
||||||
handlers := routerManager.BuildHandlers(t.Context(), test.entryPoints, false)
|
handlers := routerManager.BuildHandlers(t.Context(), test.entryPoints, false)
|
||||||
|
|
||||||
|
|
@ -427,7 +426,7 @@ func TestAccessLog(t *testing.T) {
|
||||||
chainBuilder := middleware.NewChainBuilder(nil, nil, nil)
|
chainBuilder := middleware.NewChainBuilder(nil, nil, nil)
|
||||||
tlsManager := tls.NewManager()
|
tlsManager := tls.NewManager()
|
||||||
|
|
||||||
routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, chainBuilder, metrics.NewVoidRegistry(), tlsManager, nil)
|
routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, chainBuilder, metrics.NewVoidRegistry(), tlsManager)
|
||||||
|
|
||||||
handlers := routerManager.BuildHandlers(t.Context(), test.entryPoints, false)
|
handlers := routerManager.BuildHandlers(t.Context(), test.entryPoints, false)
|
||||||
|
|
||||||
|
|
@ -815,7 +814,7 @@ func TestRuntimeConfiguration(t *testing.T) {
|
||||||
tlsManager := tls.NewManager()
|
tlsManager := tls.NewManager()
|
||||||
tlsManager.UpdateConfigs(t.Context(), nil, test.tlsOptions, nil)
|
tlsManager.UpdateConfigs(t.Context(), nil, test.tlsOptions, nil)
|
||||||
|
|
||||||
routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, chainBuilder, metrics.NewVoidRegistry(), tlsManager, nil)
|
routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, chainBuilder, metrics.NewVoidRegistry(), tlsManager)
|
||||||
|
|
||||||
_ = routerManager.BuildHandlers(t.Context(), entryPoints, false)
|
_ = routerManager.BuildHandlers(t.Context(), entryPoints, false)
|
||||||
_ = routerManager.BuildHandlers(t.Context(), entryPoints, true)
|
_ = routerManager.BuildHandlers(t.Context(), entryPoints, true)
|
||||||
|
|
@ -892,7 +891,7 @@ func TestProviderOnMiddlewares(t *testing.T) {
|
||||||
chainBuilder := middleware.NewChainBuilder(nil, nil, nil)
|
chainBuilder := middleware.NewChainBuilder(nil, nil, nil)
|
||||||
tlsManager := tls.NewManager()
|
tlsManager := tls.NewManager()
|
||||||
|
|
||||||
routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, chainBuilder, metrics.NewVoidRegistry(), tlsManager, nil)
|
routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, chainBuilder, metrics.NewVoidRegistry(), tlsManager)
|
||||||
|
|
||||||
_ = routerManager.BuildHandlers(t.Context(), entryPoints, false)
|
_ = routerManager.BuildHandlers(t.Context(), entryPoints, false)
|
||||||
|
|
||||||
|
|
@ -908,7 +907,6 @@ func TestManager_BuildHandlers_Deny(t *testing.T) {
|
||||||
routers map[string]*dynamic.Router
|
routers map[string]*dynamic.Router
|
||||||
services map[string]*dynamic.Service
|
services map[string]*dynamic.Service
|
||||||
requestPath string
|
requestPath string
|
||||||
encodedCharacters static.EncodedCharacters
|
|
||||||
expectedStatusCode int
|
expectedStatusCode int
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
|
|
@ -938,6 +936,9 @@ func TestManager_BuildHandlers_Deny(t *testing.T) {
|
||||||
EntryPoints: []string{"web"},
|
EntryPoints: []string{"web"},
|
||||||
Rule: "PathPrefix(`/`)",
|
Rule: "PathPrefix(`/`)",
|
||||||
Service: "service",
|
Service: "service",
|
||||||
|
DeniedEncodedPathCharacters: dynamic.RouterDeniedEncodedPathCharacters{
|
||||||
|
AllowEncodedSlash: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
services: map[string]*dynamic.Service{
|
services: map[string]*dynamic.Service{
|
||||||
|
|
@ -947,9 +948,6 @@ func TestManager_BuildHandlers_Deny(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
encodedCharacters: static.EncodedCharacters{
|
|
||||||
AllowEncodedSlash: true,
|
|
||||||
},
|
|
||||||
expectedStatusCode: http.StatusBadGateway,
|
expectedStatusCode: http.StatusBadGateway,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -996,7 +994,6 @@ func TestManager_BuildHandlers_Deny(t *testing.T) {
|
||||||
Services: runtimeServices,
|
Services: runtimeServices,
|
||||||
}
|
}
|
||||||
|
|
||||||
deniedEncodedPathCharacters := map[string]map[string]struct{}{"web": test.encodedCharacters.Map()}
|
|
||||||
roundTripperManager := service.NewRoundTripperManager()
|
roundTripperManager := service.NewRoundTripperManager()
|
||||||
roundTripperManager.Update(map[string]*dynamic.ServersTransport{"default@internal": {}})
|
roundTripperManager.Update(map[string]*dynamic.ServersTransport{"default@internal": {}})
|
||||||
serviceManager := service.NewManager(conf.Services, nil, nil, roundTripperManager)
|
serviceManager := service.NewManager(conf.Services, nil, nil, roundTripperManager)
|
||||||
|
|
@ -1004,7 +1001,7 @@ func TestManager_BuildHandlers_Deny(t *testing.T) {
|
||||||
chainBuilder := middleware.NewChainBuilder(nil, nil, nil)
|
chainBuilder := middleware.NewChainBuilder(nil, nil, nil)
|
||||||
tlsManager := tls.NewManager()
|
tlsManager := tls.NewManager()
|
||||||
|
|
||||||
routerManager := NewManager(conf, serviceManager, middlewaresBuilder, chainBuilder, metrics.NewVoidRegistry(), tlsManager, deniedEncodedPathCharacters)
|
routerManager := NewManager(conf, serviceManager, middlewaresBuilder, chainBuilder, metrics.NewVoidRegistry(), tlsManager)
|
||||||
|
|
||||||
// Build handlers
|
// Build handlers
|
||||||
ctx := t.Context()
|
ctx := t.Context()
|
||||||
|
|
@ -1079,7 +1076,7 @@ func BenchmarkRouterServe(b *testing.B) {
|
||||||
chainBuilder := middleware.NewChainBuilder(nil, nil, nil)
|
chainBuilder := middleware.NewChainBuilder(nil, nil, nil)
|
||||||
tlsManager := tls.NewManager()
|
tlsManager := tls.NewManager()
|
||||||
|
|
||||||
routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, chainBuilder, metrics.NewVoidRegistry(), tlsManager, nil)
|
routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, chainBuilder, metrics.NewVoidRegistry(), tlsManager)
|
||||||
|
|
||||||
handlers := routerManager.BuildHandlers(b.Context(), entryPoints, false)
|
handlers := routerManager.BuildHandlers(b.Context(), entryPoints, false)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -24,8 +24,7 @@ type RouterFactory struct {
|
||||||
entryPointsTCP []string
|
entryPointsTCP []string
|
||||||
entryPointsUDP []string
|
entryPointsUDP []string
|
||||||
|
|
||||||
allowACMEByPass map[string]bool
|
allowACMEByPass map[string]bool
|
||||||
deniedEncodedPathCharacters map[string]map[string]struct{}
|
|
||||||
|
|
||||||
managerFactory *service.ManagerFactory
|
managerFactory *service.ManagerFactory
|
||||||
|
|
||||||
|
|
@ -66,21 +65,15 @@ func NewRouterFactory(staticConfiguration static.Configuration, managerFactory *
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
deniedEncodedPathCharacters := map[string]map[string]struct{}{}
|
|
||||||
for name, ep := range staticConfiguration.EntryPoints {
|
|
||||||
deniedEncodedPathCharacters[name] = ep.HTTP.EncodedCharacters.Map()
|
|
||||||
}
|
|
||||||
|
|
||||||
return &RouterFactory{
|
return &RouterFactory{
|
||||||
entryPointsTCP: entryPointsTCP,
|
entryPointsTCP: entryPointsTCP,
|
||||||
entryPointsUDP: entryPointsUDP,
|
entryPointsUDP: entryPointsUDP,
|
||||||
managerFactory: managerFactory,
|
managerFactory: managerFactory,
|
||||||
metricsRegistry: metricsRegistry,
|
metricsRegistry: metricsRegistry,
|
||||||
tlsManager: tlsManager,
|
tlsManager: tlsManager,
|
||||||
chainBuilder: chainBuilder,
|
chainBuilder: chainBuilder,
|
||||||
pluginBuilder: pluginBuilder,
|
pluginBuilder: pluginBuilder,
|
||||||
allowACMEByPass: allowACMEByPass,
|
allowACMEByPass: allowACMEByPass,
|
||||||
deniedEncodedPathCharacters: deniedEncodedPathCharacters,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -93,7 +86,7 @@ func (f *RouterFactory) CreateRouters(rtConf *runtime.Configuration) (map[string
|
||||||
|
|
||||||
middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager, f.pluginBuilder)
|
middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager, f.pluginBuilder)
|
||||||
|
|
||||||
routerManager := router.NewManager(rtConf, serviceManager, middlewaresBuilder, f.chainBuilder, f.metricsRegistry, f.tlsManager, f.deniedEncodedPathCharacters)
|
routerManager := router.NewManager(rtConf, serviceManager, middlewaresBuilder, f.chainBuilder, f.metricsRegistry, f.tlsManager)
|
||||||
|
|
||||||
handlersNonTLS := routerManager.BuildHandlers(ctx, f.entryPointsTCP, false)
|
handlersNonTLS := routerManager.BuildHandlers(ctx, f.entryPointsTCP, false)
|
||||||
handlersTLS := routerManager.BuildHandlers(ctx, f.entryPointsTCP, true)
|
handlersTLS := routerManager.BuildHandlers(ctx, f.entryPointsTCP, true)
|
||||||
|
|
|
||||||
|
|
@ -576,8 +576,10 @@ func TestPathOperations(t *testing.T) {
|
||||||
configuration.SetDefaults()
|
configuration.SetDefaults()
|
||||||
|
|
||||||
// We need to allow some of the suspicious encoded characters to test the path operations in case they are authorized.
|
// 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 = &static.EncodedCharacters{
|
||||||
configuration.HTTP.EncodedCharacters.AllowEncodedPercent = true
|
AllowEncodedSlash: true,
|
||||||
|
AllowEncodedPercent: true,
|
||||||
|
}
|
||||||
|
|
||||||
// Create the HTTP server using createHTTPServer.
|
// Create the HTTP server using createHTTPServer.
|
||||||
server, err := createHTTPServer(t.Context(), ln, configuration, false, requestdecorator.New(nil))
|
server, err := createHTTPServer(t.Context(), ln, configuration, false, requestdecorator.New(nil))
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue