From 70c45a6f9c27c722612bd4b2e0b672e531f46bf3 Mon Sep 17 00:00:00 2001 From: Michael Date: Fri, 20 Mar 2026 09:12:05 +0100 Subject: [PATCH 1/4] Bump google.golang.org/grpc to v1.79.3 --- go.mod | 14 +++++++------- go.sum | 31 ++++++++++++++++--------------- 2 files changed, 23 insertions(+), 22 deletions(-) diff --git a/go.mod b/go.mod index b991e7aef4..dbc0b329a2 100644 --- a/go.mod +++ b/go.mod @@ -78,7 +78,7 @@ require ( golang.org/x/text v0.34.0 golang.org/x/time v0.12.0 golang.org/x/tools v0.41.0 - google.golang.org/grpc v1.73.0 + google.golang.org/grpc v1.79.3 gopkg.in/DataDog/dd-trace-go.v1 v1.74.6 gopkg.in/yaml.v3 v3.0.1 k8s.io/api v0.32.3 @@ -93,7 +93,7 @@ require ( require ( cloud.google.com/go/auth v0.16.2 // indirect cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect - cloud.google.com/go/compute/metadata v0.7.0 // indirect + cloud.google.com/go/compute/metadata v0.9.0 // indirect dario.cat/mergo v1.0.1 // indirect github.com/AdamSLevy/jsonrpc2/v14 v14.1.0 // indirect github.com/Azure/azure-sdk-for-go v68.0.0+incompatible // indirect @@ -198,7 +198,7 @@ require ( github.com/go-acme/alidns-20150109/v4 v4.5.10 // indirect github.com/go-acme/tencentclouddnspod v1.0.1208 // indirect github.com/go-errors/errors v1.0.1 // indirect - github.com/go-jose/go-jose/v4 v4.1.1 // indirect + github.com/go-jose/go-jose/v4 v4.1.3 // indirect github.com/go-logfmt/logfmt v0.5.1 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect @@ -382,15 +382,15 @@ require ( go.uber.org/zap v1.27.0 // indirect golang.org/x/crypto v0.48.0 // indirect golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 // indirect - golang.org/x/oauth2 v0.30.0 // indirect + golang.org/x/oauth2 v0.34.0 // indirect golang.org/x/sync v0.19.0 // indirect golang.org/x/sys v0.41.0 // indirect golang.org/x/term v0.40.0 // indirect golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect google.golang.org/api v0.242.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20250707201910-8d1bb00bc6a7 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7 // indirect - google.golang.org/protobuf v1.36.6 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect + google.golang.org/protobuf v1.36.10 // indirect gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/ini.v1 v1.67.0 // indirect diff --git a/go.sum b/go.sum index 47660a01ed..c6a6fcd4c8 100644 --- a/go.sum +++ b/go.sum @@ -28,8 +28,8 @@ cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvf cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= -cloud.google.com/go/compute/metadata v0.7.0 h1:PBWF+iiAerVNe8UCHxdOt6eHLVc3ydFeOCw78U8ytSU= -cloud.google.com/go/compute/metadata v0.7.0/go.mod h1:j5MvL9PprKL39t166CoB1uVHfQMs4tFQZZcKwksXUjo= +cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs= +cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= @@ -504,8 +504,8 @@ github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-jose/go-jose/v4 v4.1.1 h1:JYhSgy4mXXzAdF3nUx3ygx347LRXJRrpgyU3adRmkAI= -github.com/go-jose/go-jose/v4 v4.1.1/go.mod h1:BdsZGqgdO3b6tTc6LSE56wcDbMMLuPsw5d4ZD5f94kA= +github.com/go-jose/go-jose/v4 v4.1.3 h1:CVLmWDhDVRa6Mi/IgCgaopNosCaHz7zrMeF9MlZRkrs= +github.com/go-jose/go-jose/v4 v4.1.3/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.12.0/go.mod h1:lHd+EkCZPIwYItmGDDRdhinkzX2A1sj+M9biaEaizzs= @@ -1781,8 +1781,8 @@ golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= -golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= +golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw= +golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -2043,8 +2043,9 @@ golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSm golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= gomodules.xyz/jsonpatch/v2 v2.2.0/go.mod h1:WXp+iVDkoLQqPudfQ9GBlwB2eZ5DKOnjQZCYdOS8GPY= gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= -gonum.org/v1/gonum v0.8.2 h1:CCXrcPKiGGotvnN6jfUsKk4rRqm7q09/YbKb5xCEvtM= gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0= +gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= +gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= @@ -2126,10 +2127,10 @@ google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxH google.golang.org/genproto v0.0.0-20210917145530-b395a37504d4/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= google.golang.org/genproto v0.0.0-20250505200425-f936aa4a68b2 h1:1tXaIXCracvtsRxSBsYDiSBN0cuJvM7QYW+MrpIRY78= google.golang.org/genproto v0.0.0-20250505200425-f936aa4a68b2/go.mod h1:49MsLSx0oWMOZqcpB3uL8ZOkAh1+TndpJ8ONoCBWiZk= -google.golang.org/genproto/googleapis/api v0.0.0-20250707201910-8d1bb00bc6a7 h1:FiusG7LWj+4byqhbvmB+Q93B/mOxJLN2DTozDuZm4EU= -google.golang.org/genproto/googleapis/api v0.0.0-20250707201910-8d1bb00bc6a7/go.mod h1:kXqgZtrWaf6qS3jZOCnCH7WYfrvFjkC51bM8fz3RsCA= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7 h1:pFyd6EwwL2TqFf8emdthzeX+gZE1ElRq3iM8pui4KBY= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= +google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 h1:fCvbg86sFXwdrl5LgVcTEvNC+2txB5mgROGmRL5mrls= +google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:+rXWjjaukWZun3mLfjmVnQi18E1AsFbDN9QdJ5YXLto= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 h1:gRkg/vSppuSQoDjxyiGfN4Upv/h/DQmIR10ZU8dh4Ww= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= @@ -2153,8 +2154,8 @@ google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAG google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= -google.golang.org/grpc v1.73.0 h1:VIWSmpI2MegBtTuFt5/JWy2oXxtjJ/e89Z70ImfD2ok= -google.golang.org/grpc v1.73.0/go.mod h1:50sbHOUqWoCQGI8V2HQLJM0B+LMlIUjNSZmow7EVBQc= +google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE= +google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -2169,8 +2170,8 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= -google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= +google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= +google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/DataDog/dd-trace-go.v1 v1.74.6 h1:VBxCK/WkaNjsM9Ygn57scwmiwMqF0gEbuE4C5c2TU5E= gopkg.in/DataDog/dd-trace-go.v1 v1.74.6/go.mod h1:jQL1vSDZhH+DJWUOYjkRQ+kU1HUXPvUK41gS1AvHOTE= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= From 51f6b0435f01e6d1cc2eecf2ebb04011e5f2ac70 Mon Sep 17 00:00:00 2001 From: Julien Salleyron Date: Fri, 20 Mar 2026 16:24:05 +0100 Subject: [PATCH 2/4] Prevent duplicate user headers in basic and digest auth middleware --- .golangci.yml | 5 +++++ pkg/middlewares/auth/basic_auth.go | 2 ++ pkg/middlewares/auth/basic_auth_test.go | 24 +++++++++++++++++++++ pkg/middlewares/auth/digest_auth.go | 2 ++ pkg/middlewares/auth/digest_auth_test.go | 27 ++++++++++++++++++++++++ 5 files changed, 60 insertions(+) diff --git a/.golangci.yml b/.golangci.yml index 7dd5763bea..02749c267d 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -323,6 +323,11 @@ linters: text: 'SA1019: cfg.(SSLRedirect|SSLTemporaryRedirect|SSLHost|SSLForceHost|FeaturePolicy) is deprecated' - path: (.+)\.go$ text: 'SA1019: c.Providers.(ConsulCatalog|Consul|Nomad).Namespace is deprecated' + - path: pkg/middlewares/auth/basic_auth_test.go + text: 'SA1008: keys in http.Header are canonicalized, "x-user" is not canonical; fix the constant or use http.CanonicalHeaderKey' + - path: pkg/middlewares/auth/digest_auth_test.go + text: 'SA1008: keys in http.Header are canonicalized, "x-user" is not canonical; fix the constant or use http.CanonicalHeaderKey' + paths: - pkg/provider/kubernetes/crd/generated/ diff --git a/pkg/middlewares/auth/basic_auth.go b/pkg/middlewares/auth/basic_auth.go index 944900ef1f..2dce9775c0 100644 --- a/pkg/middlewares/auth/basic_auth.go +++ b/pkg/middlewares/auth/basic_auth.go @@ -100,6 +100,8 @@ func (b *basicAuth) ServeHTTP(rw http.ResponseWriter, req *http.Request) { req.URL.User = url.User(user) if b.headerField != "" { + // TODO Deprecated we should add the header with canonical key. + req.Header.Del(b.headerField) req.Header[b.headerField] = []string{user} } diff --git a/pkg/middlewares/auth/basic_auth_test.go b/pkg/middlewares/auth/basic_auth_test.go index f3f98df73b..e70aa38d8a 100644 --- a/pkg/middlewares/auth/basic_auth_test.go +++ b/pkg/middlewares/auth/basic_auth_test.go @@ -103,6 +103,30 @@ func TestBasicAuthUserHeader(t *testing.T) { assert.Equal(t, "traefik\n", string(body)) } +func TestBasicAuthUserHeaderCanonical(t *testing.T) { + var nextCalled bool + next := http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + nextCalled = true + assert.Empty(t, req.Header.Get("X-User")) + assert.Equal(t, []string{"test"}, req.Header["x-user"]) + }) + auth := dynamic.BasicAuth{ + Users: []string{"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/"}, + HeaderField: "x-user", + } + m, err := NewBasic(t.Context(), next, auth, "test") + require.NoError(t, err) + + req := httptest.NewRequest(http.MethodGet, "http://localhost/", nil) + req.SetBasicAuth("test", "test") + req.Header.Set("X-User", "admin") + rw := httptest.NewRecorder() + m.ServeHTTP(rw, req) + + assert.Equal(t, http.StatusOK, rw.Result().StatusCode) + assert.True(t, nextCalled) +} + func TestBasicAuthHeaderRemoved(t *testing.T) { next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { assert.Empty(t, r.Header.Get(authorizationHeader)) diff --git a/pkg/middlewares/auth/digest_auth.go b/pkg/middlewares/auth/digest_auth.go index 454a972951..7ff7633ca4 100644 --- a/pkg/middlewares/auth/digest_auth.go +++ b/pkg/middlewares/auth/digest_auth.go @@ -98,6 +98,8 @@ func (d *digestAuth) ServeHTTP(rw http.ResponseWriter, req *http.Request) { } if d.headerField != "" { + // TODO Deprecated we should add the header with canonical key. + req.Header.Del(d.headerField) req.Header[d.headerField] = []string{username} } diff --git a/pkg/middlewares/auth/digest_auth_test.go b/pkg/middlewares/auth/digest_auth_test.go index 8987fe0998..ae922f305c 100644 --- a/pkg/middlewares/auth/digest_auth_test.go +++ b/pkg/middlewares/auth/digest_auth_test.go @@ -151,3 +151,30 @@ func TestDigestAuthUsersFromFile(t *testing.T) { }) } } + +func TestDigestAuthUserHeaderCanonical(t *testing.T) { + var nextCalled bool + next := http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + nextCalled = true + assert.Empty(t, req.Header.Get("X-User")) + assert.Equal(t, []string{"test"}, req.Header["x-user"]) + }) + auth := dynamic.DigestAuth{ + Users: []string{"test:traefik:a2688e031edb4be6a3797f3882655c05"}, + HeaderField: "x-user", + } + m, err := NewDigest(t.Context(), next, auth, "test") + require.NoError(t, err) + + srv := httptest.NewServer(m) + t.Cleanup(srv.Close) + + req := testhelpers.MustNewRequest(http.MethodGet, srv.URL, nil) + req.Header.Set("X-User", "admin") + digestReq := newDigestRequest("test", "test", http.DefaultClient) + res, err := digestReq.Do(req) + require.NoError(t, err) + + assert.Equal(t, http.StatusOK, res.StatusCode) + assert.True(t, nextCalled) +} From f19aaa769c80d2acb37de6cf8a4b16f366edf04f Mon Sep 17 00:00:00 2001 From: "Gina A." <70909035+gndz07@users.noreply.github.com> Date: Tue, 24 Mar 2026 17:06:05 +0100 Subject: [PATCH 3/4] Fix StripPrefix and StripPrefixRegex to slice the prefix using encoded prefix length Co-authored-by: Mathis Urien --- pkg/middlewares/stripprefix/strip_prefix.go | 33 ++++- .../stripprefix/strip_prefix_test.go | 11 ++ .../stripprefixregex/strip_prefix_regex.go | 18 ++- .../strip_prefix_regex_test.go | 122 +++++++++++++++--- 4 files changed, 160 insertions(+), 24 deletions(-) diff --git a/pkg/middlewares/stripprefix/strip_prefix.go b/pkg/middlewares/stripprefix/strip_prefix.go index 406b13ab24..53e54c0ece 100644 --- a/pkg/middlewares/stripprefix/strip_prefix.go +++ b/pkg/middlewares/stripprefix/strip_prefix.go @@ -44,9 +44,9 @@ func (s *stripPrefix) GetTracingInformation() (string, ext.SpanKindEnum) { func (s *stripPrefix) ServeHTTP(rw http.ResponseWriter, req *http.Request) { for _, prefix := range s.prefixes { if strings.HasPrefix(req.URL.Path, prefix) { - req.URL.Path = s.getPrefixStripped(req.URL.Path, prefix) + req.URL.Path = s.getPathStripped(req.URL.Path, prefix) if req.URL.RawPath != "" { - req.URL.RawPath = s.getPrefixStripped(req.URL.RawPath, prefix) + req.URL.RawPath = s.getRawPathStripped(req.URL.RawPath, prefix) } s.serveRequest(rw, req, strings.TrimSpace(prefix)) return @@ -61,7 +61,7 @@ func (s *stripPrefix) serveRequest(rw http.ResponseWriter, req *http.Request, pr s.next.ServeHTTP(rw, req) } -func (s *stripPrefix) getPrefixStripped(urlPath, prefix string) string { +func (s *stripPrefix) getPathStripped(urlPath, prefix string) string { if s.forceSlash { // Only for compatibility reason with the previous behavior, // but the previous behavior is wrong. @@ -72,6 +72,33 @@ func (s *stripPrefix) getPrefixStripped(urlPath, prefix string) string { return ensureLeadingSlash(strings.TrimPrefix(urlPath, prefix)) } +func (s *stripPrefix) getRawPathStripped(rawPath, prefix string) string { + if s.forceSlash { + // Only for compatibility reason with the previous behavior, + // but the previous behavior is wrong. + // This needs to be removed in the next breaking version. + return "/" + strings.TrimPrefix(rawPath[encodedPrefixLen(rawPath, prefix):], "/") + } + + return ensureLeadingSlash(rawPath[encodedPrefixLen(rawPath, prefix):]) +} + +// encodedPrefixLen returns the number of bytes in rawPath that correspond to +// the decoded prefix, advancing 3 bytes per %XX sequence and 1 byte otherwise. +func encodedPrefixLen(rawPath, decodedPrefix string) int { + decoded := 0 + i := 0 + for i < len(rawPath) && decoded < len(decodedPrefix) { + if rawPath[i] == '%' && i+2 < len(rawPath) { + i += 3 + } else { + i++ + } + decoded++ + } + return i +} + func ensureLeadingSlash(str string) string { if str == "" { return str diff --git a/pkg/middlewares/stripprefix/strip_prefix_test.go b/pkg/middlewares/stripprefix/strip_prefix_test.go index 8ed6ebe921..18d66c7145 100644 --- a/pkg/middlewares/stripprefix/strip_prefix_test.go +++ b/pkg/middlewares/stripprefix/strip_prefix_test.go @@ -174,6 +174,17 @@ func TestStripPrefix(t *testing.T) { expectedRawPath: "/a%2Fb", expectedHeader: "/stat", }, + { + desc: "encoded char in prefix segment of raw path", + config: dynamic.StripPrefix{ + Prefixes: []string{"/api/"}, + }, + path: "/ap%69/a%2Fb", + expectedStatusCode: http.StatusOK, + expectedPath: "/a/b", + expectedRawPath: "/a%2Fb", + expectedHeader: "/api/", + }, } for _, test := range testCases { diff --git a/pkg/middlewares/stripprefixregex/strip_prefix_regex.go b/pkg/middlewares/stripprefixregex/strip_prefix_regex.go index ee0722ad32..8a1997941f 100644 --- a/pkg/middlewares/stripprefixregex/strip_prefix_regex.go +++ b/pkg/middlewares/stripprefixregex/strip_prefix_regex.go @@ -62,7 +62,7 @@ func (s *stripPrefixRegex) ServeHTTP(rw http.ResponseWriter, req *http.Request) req.URL.Path = ensureLeadingSlash(strings.Replace(req.URL.Path, prefix, "", 1)) if req.URL.RawPath != "" { - req.URL.RawPath = ensureLeadingSlash(req.URL.RawPath[len(prefix):]) + req.URL.RawPath = ensureLeadingSlash(req.URL.RawPath[encodedPrefixLen(req.URL.RawPath, prefix):]) } req.RequestURI = req.URL.RequestURI() @@ -74,6 +74,22 @@ func (s *stripPrefixRegex) ServeHTTP(rw http.ResponseWriter, req *http.Request) s.next.ServeHTTP(rw, req) } +// encodedPrefixLen returns the number of bytes in rawPath that correspond to +// the decoded prefix, advancing 3 bytes per %XX sequence and 1 byte otherwise. +func encodedPrefixLen(rawPath, decodedPrefix string) int { + decoded := 0 + i := 0 + for i < len(rawPath) && decoded < len(decodedPrefix) { + if rawPath[i] == '%' && i+2 < len(rawPath) { + i += 3 + } else { + i++ + } + decoded++ + } + return i +} + func ensureLeadingSlash(str string) string { if str == "" { return str diff --git a/pkg/middlewares/stripprefixregex/strip_prefix_regex_test.go b/pkg/middlewares/stripprefixregex/strip_prefix_regex_test.go index 5a9b6d1156..c0caf73056 100644 --- a/pkg/middlewares/stripprefixregex/strip_prefix_regex_test.go +++ b/pkg/middlewares/stripprefixregex/strip_prefix_regex_test.go @@ -13,111 +13,204 @@ import ( ) func TestStripPrefixRegex(t *testing.T) { - testPrefixRegex := dynamic.StripPrefixRegex{ - Regex: []string{"/a/api/", "/b/([a-z0-9]+)/", "/c/[a-z0-9]+/[0-9]+/"}, - } - testCases := []struct { + desc string + config dynamic.StripPrefixRegex path string expectedStatusCode int expectedPath string expectedRawPath string + expectedRequestURI string expectedHeader string }{ { + desc: "/a/test", + config: dynamic.StripPrefixRegex{Regex: []string{"/a/api/"}}, path: "/a/test", expectedStatusCode: http.StatusOK, expectedPath: "/a/test", }, { + desc: "/a/test/", + config: dynamic.StripPrefixRegex{Regex: []string{"/a/api/"}}, path: "/a/test/", expectedStatusCode: http.StatusOK, expectedPath: "/a/test/", }, { + desc: "/a/api/", + config: dynamic.StripPrefixRegex{Regex: []string{"/a/api/"}}, path: "/a/api/", expectedStatusCode: http.StatusOK, + // ensureLeadingSlash do not add a slash when the path is empty. expectedPath: "", + expectedRequestURI: "/", expectedHeader: "/a/api/", }, { + desc: "/a/api/test", + config: dynamic.StripPrefixRegex{Regex: []string{"/a/api/"}}, path: "/a/api/test", expectedStatusCode: http.StatusOK, expectedPath: "/test", + expectedRequestURI: "/test", expectedHeader: "/a/api/", }, { + desc: "/a/api/test/", + config: dynamic.StripPrefixRegex{Regex: []string{"/a/api/"}}, path: "/a/api/test/", expectedStatusCode: http.StatusOK, expectedPath: "/test/", + expectedRequestURI: "/test/", expectedHeader: "/a/api/", }, { + desc: "/b/api/", + config: dynamic.StripPrefixRegex{Regex: []string{"/b/([a-z0-9]+)/"}}, path: "/b/api/", expectedStatusCode: http.StatusOK, expectedPath: "", + expectedRequestURI: "/", expectedHeader: "/b/api/", }, { + desc: "/b/api", + config: dynamic.StripPrefixRegex{Regex: []string{"/b/([a-z0-9]+)/"}}, path: "/b/api", expectedStatusCode: http.StatusOK, expectedPath: "/b/api", + // When the path do not match, the requestURI is not computed. + expectedRequestURI: "", }, { + desc: "/b/api/test1", + config: dynamic.StripPrefixRegex{Regex: []string{"/b/([a-z0-9]+)/"}}, path: "/b/api/test1", expectedStatusCode: http.StatusOK, expectedPath: "/test1", + expectedRequestURI: "/test1", expectedHeader: "/b/api/", }, { + desc: "/b/api2/test2", + config: dynamic.StripPrefixRegex{Regex: []string{"/b/([a-z0-9]+)/"}}, path: "/b/api2/test2", expectedStatusCode: http.StatusOK, expectedPath: "/test2", + expectedRequestURI: "/test2", expectedHeader: "/b/api2/", }, { + desc: "/c/api/123/", + config: dynamic.StripPrefixRegex{Regex: []string{"/c/[a-z0-9]+/[0-9]+/"}}, path: "/c/api/123/", expectedStatusCode: http.StatusOK, expectedPath: "", + expectedRequestURI: "/", expectedHeader: "/c/api/123/", }, { + desc: "/c/api/123", + config: dynamic.StripPrefixRegex{Regex: []string{"/c/[a-z0-9]+/[0-9]+/"}}, path: "/c/api/123", expectedStatusCode: http.StatusOK, expectedPath: "/c/api/123", + // When the path do not match, the requestURI is not computed. + expectedRequestURI: "", }, { + desc: "/c/api/123/test3", + config: dynamic.StripPrefixRegex{Regex: []string{"/c/[a-z0-9]+/[0-9]+/"}}, path: "/c/api/123/test3", expectedStatusCode: http.StatusOK, expectedPath: "/test3", + expectedRequestURI: "/test3", expectedHeader: "/c/api/123/", }, { + desc: "/c/api/abc/test4", + config: dynamic.StripPrefixRegex{Regex: []string{"/c/[a-z0-9]+/[0-9]+/"}}, path: "/c/api/abc/test4", expectedStatusCode: http.StatusOK, expectedPath: "/c/api/abc/test4", + // When the path do not match, the requestURI is not computed. + expectedRequestURI: "", }, { + desc: "/a/api/a2Fb", + config: dynamic.StripPrefixRegex{Regex: []string{"/a/api/"}}, path: "/a/api/a%2Fb", expectedStatusCode: http.StatusOK, expectedPath: "/a/b", expectedRawPath: "/a%2Fb", + expectedRequestURI: "/a%2Fb", expectedHeader: "/a/api/", }, + { + desc: "/b/ap69/test", + config: dynamic.StripPrefixRegex{Regex: []string{"/b/([a-z0-9]+)/"}}, + path: "/b/ap%69/test", + expectedStatusCode: http.StatusOK, + expectedPath: "/test", + expectedRawPath: "/test", + expectedRequestURI: "/test", + expectedHeader: "/b/api/", + }, + { + desc: "/b/ap69/a2Fb", + config: dynamic.StripPrefixRegex{Regex: []string{"/b/([a-z0-9]+)/"}}, + path: "/b/ap%69/a%2Fb", + expectedStatusCode: http.StatusOK, + expectedPath: "/a/b", + expectedRawPath: "/a%2Fb", + expectedRequestURI: "/a%2Fb", + expectedHeader: "/b/api/", + }, + { + desc: "/t2F/test/foo", + config: dynamic.StripPrefixRegex{Regex: []string{"/t /test"}}, + path: "/t%2F/test/foo", + expectedStatusCode: http.StatusOK, + expectedPath: "/t//test/foo", + expectedRawPath: "/t%2F/test/foo", + // When the path do not match, the requestURI is not computed. + expectedRequestURI: "", + }, + { + desc: "/t /test/a2Fb", + config: dynamic.StripPrefixRegex{Regex: []string{"/t /test"}}, + path: "/t /test/a%2Fb", + expectedStatusCode: http.StatusOK, + expectedPath: "/a/b", + expectedRawPath: "/a%2Fb", + expectedRequestURI: "/a%2Fb", + expectedHeader: "/t /test", + }, + { + desc: "/t20/test/a2Fb", + config: dynamic.StripPrefixRegex{Regex: []string{"/t /test"}}, + path: "/t%20/test/a%2Fb", + expectedStatusCode: http.StatusOK, + expectedPath: "/a/b", + expectedRawPath: "/a%2Fb", + expectedRequestURI: "/a%2Fb", + expectedHeader: "/t /test", + }, } for _, test := range testCases { - t.Run(test.path, func(t *testing.T) { + t.Run(test.desc, func(t *testing.T) { t.Parallel() - var actualPath, actualRawPath, actualHeader, requestURI string + var actualPath, actualRawPath, actualHeader, actualRequestURI string handlerPath := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { actualPath = r.URL.Path actualRawPath = r.URL.RawPath actualHeader = r.Header.Get(stripprefix.ForwardedPrefixHeader) - requestURI = r.RequestURI + actualRequestURI = r.RequestURI }) - handler, err := New(t.Context(), handlerPath, testPrefixRegex, "foo-strip-prefix-regex") + handler, err := New(t.Context(), handlerPath, test.config, "foo-strip-prefix-regex") require.NoError(t, err) req := testhelpers.MustNewRequest(http.MethodGet, "http://localhost"+test.path, nil) @@ -129,18 +222,7 @@ func TestStripPrefixRegex(t *testing.T) { assert.Equal(t, test.expectedPath, actualPath, "Unexpected path.") assert.Equal(t, test.expectedRawPath, actualRawPath, "Unexpected raw path.") assert.Equal(t, test.expectedHeader, actualHeader, "Unexpected '%s' header.", stripprefix.ForwardedPrefixHeader) - - if test.expectedPath != test.path { - expectedRequestURI := test.expectedPath - if test.expectedRawPath != "" { - // go HTTP uses the raw path when existent in the RequestURI - expectedRequestURI = test.expectedRawPath - } - if test.expectedPath == "" { - expectedRequestURI = "/" - } - assert.Equal(t, expectedRequestURI, requestURI, "Unexpected request URI.") - } + assert.Equal(t, test.expectedRequestURI, actualRequestURI, "Unexpected request uri.") }) } } From e4b2c648bf78b29a97a560571510b61eb7dc67b6 Mon Sep 17 00:00:00 2001 From: Kevin Pollet Date: Thu, 26 Mar 2026 09:48:04 +0100 Subject: [PATCH 4/4] Prepare release v2.11.42 --- CHANGELOG.md | 8 ++++++++ script/gcg/traefik-bugfix.toml | 6 +++--- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2468c476b3..ff7a2c6d70 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +## [v2.11.42](https://github.com/traefik/traefik/tree/v2.11.42) (2026-03-26) +[All Commits](https://github.com/traefik/traefik/compare/v2.11.41...v2.11.42) + +**Bug fixes:** +- **[grpc]** Bump google.golang.org/grpc to v1.79.3 ([#12845](https://github.com/traefik/traefik/pull/12845) @mmatur) +- **[middleware, authentication]** Prevent duplicate user headers in basic and digest auth middleware ([#12851](https://github.com/traefik/traefik/pull/12851) @juliens) +- **[middleware]** Fix StripPrefix and StripPrefixRegex to slice the prefix using encoded prefix length ([#12863](https://github.com/traefik/traefik/pull/12863) @gndz07) + ## [v2.11.41](https://github.com/traefik/traefik/tree/v2.11.41) (2026-03-18) [All Commits](https://github.com/traefik/traefik/compare/v2.11.40...v2.11.41) diff --git a/script/gcg/traefik-bugfix.toml b/script/gcg/traefik-bugfix.toml index c2a9cac68a..52d72c84be 100644 --- a/script/gcg/traefik-bugfix.toml +++ b/script/gcg/traefik-bugfix.toml @@ -4,11 +4,11 @@ RepositoryName = "traefik" OutputType = "file" FileName = "traefik_changelog.md" -# example new bugfix v2.11.41 +# example new bugfix v2.11.42 CurrentRef = "v2.11" -PreviousRef = "v2.11.40" +PreviousRef = "v2.11.41" BaseBranch = "v2.11" -FutureCurrentRefName = "v2.11.41" +FutureCurrentRefName = "v2.11.42" ThresholdPreviousRef = 10000 ThresholdCurrentRef = 10000