1
0
Fork 0

Merge branch v2.11 into v3.6

This commit is contained in:
romain 2026-01-14 11:28:12 +01:00
commit 8479d66d18
23 changed files with 266 additions and 224 deletions

View file

@ -2,29 +2,10 @@ package router
import (
"net/http"
"strings"
"github.com/rs/zerolog/log"
)
// denyFragment rejects the request if the URL path contains a fragment (hash character).
// When go receives an HTTP request, it assumes the absence of fragment URL.
// However, it is still possible to send a fragment in the request.
// In this case, Traefik will encode the '#' character, altering the request's intended meaning.
// To avoid this behavior, the following function rejects requests that include a fragment in the URL.
func denyFragment(h http.Handler) http.Handler {
return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
if strings.Contains(req.URL.RawPath, "#") {
log.Debug().Msgf("Rejecting request because it contains a fragment in the URL path: %s", req.URL.RawPath)
rw.WriteHeader(http.StatusBadRequest)
return
}
h.ServeHTTP(rw, req)
})
}
// denyEncodedPathCharacters reject the request if the escaped path contains encoded characters in the given list.
func denyEncodedPathCharacters(encodedCharacters map[string]struct{}, h http.Handler) http.Handler {
return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {

View file

@ -8,42 +8,6 @@ import (
"github.com/stretchr/testify/assert"
)
func Test_denyFragment(t *testing.T) {
tests := []struct {
name string
url string
wantStatus int
}{
{
name: "Rejects fragment character",
url: "http://example.com/#",
wantStatus: http.StatusBadRequest,
},
{
name: "Allows without fragment",
url: "http://example.com/",
wantStatus: http.StatusOK,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
t.Parallel()
handler := denyFragment(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
}))
req := httptest.NewRequest(http.MethodGet, test.url, nil)
res := httptest.NewRecorder()
handler.ServeHTTP(res, req)
assert.Equal(t, test.wantStatus, res.Code)
})
}
}
func Test_denyEncodedPathCharacters(t *testing.T) {
tests := []struct {
name string

View file

@ -274,10 +274,7 @@ func (m *Manager) buildHTTPHandler(ctx context.Context, router *runtime.RouterIn
// Here we are adding deny handlers for encoded path characters and fragment.
// Deny handler are only added for root routers, child routers are protected by their parent router deny handlers.
if len(router.ParentRefs) == 0 {
chain = chain.Append(func(next http.Handler) (http.Handler, error) {
return denyFragment(next), nil
})
if len(router.ParentRefs) == 0 && router.DeniedEncodedPathCharacters != nil {
chain = chain.Append(func(next http.Handler) (http.Handler, error) {
return denyEncodedPathCharacters(router.DeniedEncodedPathCharacters.Map(), next), nil
})

View file

@ -1837,7 +1837,7 @@ func TestManager_BuildHandlers_Deny(t *testing.T) {
},
},
},
expectedStatusCode: http.StatusBadRequest,
expectedStatusCode: http.StatusOK,
},
{
desc: "parent router with child routers, request with encoded slash",
@ -1860,18 +1860,18 @@ func TestManager_BuildHandlers_Deny(t *testing.T) {
},
},
},
expectedStatusCode: http.StatusBadRequest,
expectedStatusCode: http.StatusOK,
},
{
desc: "parent router allowing encoded slash without child router",
desc: "parent router disallowing 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,
DeniedEncodedPathCharacters: &dynamic.RouterDeniedEncodedPathCharacters{
AllowEncodedSlash: false,
},
},
},
@ -1882,17 +1882,17 @@ func TestManager_BuildHandlers_Deny(t *testing.T) {
},
},
},
expectedStatusCode: http.StatusOK,
expectedStatusCode: http.StatusBadRequest,
},
{
desc: "parent router allowing encoded slash with child routers",
desc: "parent router disallowing encoded slash with child routers",
requestPath: "/foo%2F",
routers: map[string]*dynamic.Router{
"parent": {
EntryPoints: []string{"web"},
Rule: "PathPrefix(`/`)",
DeniedEncodedPathCharacters: dynamic.RouterDeniedEncodedPathCharacters{
AllowEncodedSlash: true,
DeniedEncodedPathCharacters: &dynamic.RouterDeniedEncodedPathCharacters{
AllowEncodedSlash: false,
},
},
"child1": {
@ -1908,48 +1908,6 @@ func TestManager_BuildHandlers_Deny(t *testing.T) {
},
},
},
expectedStatusCode: http.StatusOK,
},
{
desc: "parent router without child routers, request with fragment",
requestPath: "/foo#",
routers: map[string]*dynamic.Router{
"parent": {
EntryPoints: []string{"web"},
Rule: "PathPrefix(`/`)",
Service: "service",
},
},
services: map[string]*dynamic.Service{
"service": {
LoadBalancer: &dynamic.ServersLoadBalancer{
Servers: []dynamic.Server{{URL: "http://localhost:8080"}},
},
},
},
expectedStatusCode: http.StatusBadRequest,
},
{
desc: "parent router with child routers, request with fragment",
requestPath: "/foo#",
routers: map[string]*dynamic.Router{
"parent": {
EntryPoints: []string{"web"},
Rule: "PathPrefix(`/`)",
},
"child1": {
Rule: "Path(`/v1`)",
Service: "child1-service",
ParentRefs: []string{"parent"},
},
},
services: map[string]*dynamic.Service{
"child1-service": {
LoadBalancer: &dynamic.ServersLoadBalancer{
Servers: []dynamic.Server{{URL: "http://localhost:8080"}},
},
},
},
expectedStatusCode: http.StatusBadRequest,
},
}