1
0
Fork 0

Merge current v2.11 into v3.0

This commit is contained in:
mmatur 2024-02-08 14:15:45 +01:00
commit bc84fdd006
No known key found for this signature in database
GPG key ID: 2FFE42FC256CFF8E
143 changed files with 8736 additions and 6601 deletions

View file

@ -76,7 +76,7 @@ func New(staticConfig static.Configuration, runtimeConfig *runtime.Configuration
// createRouter creates API routes and router.
func (h Handler) createRouter() *mux.Router {
router := mux.NewRouter()
router := mux.NewRouter().UseEncodedPath()
if h.staticConfig.API.Debug {
DebugHandler{}.Append(router)

View file

@ -4,6 +4,7 @@ import (
"encoding/json"
"fmt"
"net/http"
"net/url"
"sort"
"strconv"
@ -49,7 +50,13 @@ func (h Handler) getEntryPoints(rw http.ResponseWriter, request *http.Request) {
}
func (h Handler) getEntryPoint(rw http.ResponseWriter, request *http.Request) {
entryPointID := mux.Vars(request)["entryPointID"]
scapedEntryPointID := mux.Vars(request)["entryPointID"]
entryPointID, err := url.PathUnescape(scapedEntryPointID)
if err != nil {
writeError(rw, fmt.Sprintf("unable to decode entryPointID %q: %s", scapedEntryPointID, err), http.StatusBadRequest)
return
}
rw.Header().Set("Content-Type", "application/json")
@ -64,7 +71,7 @@ func (h Handler) getEntryPoint(rw http.ResponseWriter, request *http.Request) {
Name: entryPointID,
}
err := json.NewEncoder(rw).Encode(result)
err = json.NewEncoder(rw).Encode(result)
if err != nil {
log.Ctx(request.Context()).Error().Err(err).Send()
writeError(rw, err.Error(), http.StatusInternalServerError)

View file

@ -6,6 +6,7 @@ import (
"io"
"net/http"
"net/http/httptest"
"net/url"
"os"
"strconv"
"testing"
@ -169,6 +170,21 @@ func TestHandler_EntryPoints(t *testing.T) {
jsonFile: "testdata/entrypoint-bar.json",
},
},
{
desc: "one entry point by id containing slash",
path: "/api/entrypoints/" + url.PathEscape("foo / bar"),
conf: static.Configuration{
Global: &static.Global{},
API: &static.API{},
EntryPoints: map[string]*static.EntryPoint{
"foo / bar": {Address: ":81"},
},
},
expected: expected{
statusCode: http.StatusOK,
jsonFile: "testdata/entrypoint-foo-slash-bar.json",
},
},
{
desc: "one entry point by id, that does not exist",
path: "/api/entrypoints/foo",

View file

@ -4,6 +4,7 @@ import (
"encoding/json"
"fmt"
"net/http"
"net/url"
"strconv"
"strings"
@ -97,7 +98,13 @@ func (h Handler) getRouters(rw http.ResponseWriter, request *http.Request) {
}
func (h Handler) getRouter(rw http.ResponseWriter, request *http.Request) {
routerID := mux.Vars(request)["routerID"]
scapedRouterID := mux.Vars(request)["routerID"]
routerID, err := url.PathUnescape(scapedRouterID)
if err != nil {
writeError(rw, fmt.Sprintf("unable to decode routerID %q: %s", scapedRouterID, err), http.StatusBadRequest)
return
}
rw.Header().Set("Content-Type", "application/json")
@ -109,7 +116,7 @@ func (h Handler) getRouter(rw http.ResponseWriter, request *http.Request) {
result := newRouterRepresentation(routerID, router)
err := json.NewEncoder(rw).Encode(result)
err = json.NewEncoder(rw).Encode(result)
if err != nil {
log.Ctx(request.Context()).Error().Err(err).Send()
writeError(rw, err.Error(), http.StatusInternalServerError)
@ -148,7 +155,13 @@ func (h Handler) getServices(rw http.ResponseWriter, request *http.Request) {
}
func (h Handler) getService(rw http.ResponseWriter, request *http.Request) {
serviceID := mux.Vars(request)["serviceID"]
scapedServiceID := mux.Vars(request)["serviceID"]
serviceID, err := url.PathUnescape(scapedServiceID)
if err != nil {
writeError(rw, fmt.Sprintf("unable to decode serviceID %q: %s", scapedServiceID, err), http.StatusBadRequest)
return
}
rw.Header().Add("Content-Type", "application/json")
@ -160,7 +173,7 @@ func (h Handler) getService(rw http.ResponseWriter, request *http.Request) {
result := newServiceRepresentation(serviceID, service)
err := json.NewEncoder(rw).Encode(result)
err = json.NewEncoder(rw).Encode(result)
if err != nil {
log.Ctx(request.Context()).Error().Err(err).Send()
writeError(rw, err.Error(), http.StatusInternalServerError)
@ -199,7 +212,13 @@ func (h Handler) getMiddlewares(rw http.ResponseWriter, request *http.Request) {
}
func (h Handler) getMiddleware(rw http.ResponseWriter, request *http.Request) {
middlewareID := mux.Vars(request)["middlewareID"]
scapedMiddlewareID := mux.Vars(request)["middlewareID"]
middlewareID, err := url.PathUnescape(scapedMiddlewareID)
if err != nil {
writeError(rw, fmt.Sprintf("unable to decode middlewareID %q: %s", scapedMiddlewareID, err), http.StatusBadRequest)
return
}
rw.Header().Set("Content-Type", "application/json")
@ -211,7 +230,7 @@ func (h Handler) getMiddleware(rw http.ResponseWriter, request *http.Request) {
result := newMiddlewareRepresentation(middlewareID, middleware)
err := json.NewEncoder(rw).Encode(result)
err = json.NewEncoder(rw).Encode(result)
if err != nil {
log.Ctx(request.Context()).Error().Err(err).Send()
writeError(rw, err.Error(), http.StatusInternalServerError)

View file

@ -7,6 +7,7 @@ import (
"io"
"net/http"
"net/http/httptest"
"net/url"
"os"
"strconv"
"testing"
@ -301,6 +302,27 @@ func TestHandler_HTTP(t *testing.T) {
jsonFile: "testdata/router-bar.json",
},
},
{
desc: "one router by id containing slash",
path: "/api/http/routers/" + url.PathEscape("foo / bar@myprovider"),
conf: runtime.Configuration{
Routers: map[string]*runtime.RouterInfo{
"foo / bar@myprovider": {
Router: &dynamic.Router{
EntryPoints: []string{"web"},
Service: "foo-service@myprovider",
Rule: "Host(`foo.bar`)",
Middlewares: []string{"auth", "addPrefixTest@anotherprovider"},
},
Status: "enabled",
},
},
},
expected: expected{
statusCode: http.StatusOK,
jsonFile: "testdata/router-foo-slash-bar.json",
},
},
{
desc: "one router by id, implicitly using default TLS options",
path: "/api/http/routers/baz@myprovider",
@ -661,6 +683,35 @@ func TestHandler_HTTP(t *testing.T) {
jsonFile: "testdata/service-bar.json",
},
},
{
desc: "one service by id containing slash",
path: "/api/http/services/" + url.PathEscape("foo / bar@myprovider"),
conf: runtime.Configuration{
Services: map[string]*runtime.ServiceInfo{
"foo / bar@myprovider": func() *runtime.ServiceInfo {
si := &runtime.ServiceInfo{
Service: &dynamic.Service{
LoadBalancer: &dynamic.ServersLoadBalancer{
PassHostHeader: Bool(true),
Servers: []dynamic.Server{
{
URL: "http://127.0.0.1",
},
},
},
},
UsedBy: []string{"foo@myprovider", "test@myprovider"},
}
si.UpdateServerStatus("http://127.0.0.1", "UP")
return si
}(),
},
},
expected: expected{
statusCode: http.StatusOK,
jsonFile: "testdata/service-foo-slash-bar.json",
},
},
{
desc: "one service by id, that does not exist",
path: "/api/http/services/nono@myprovider",
@ -897,6 +948,26 @@ func TestHandler_HTTP(t *testing.T) {
jsonFile: "testdata/middleware-auth.json",
},
},
{
desc: "one middleware by id containing slash",
path: "/api/http/middlewares/" + url.PathEscape("foo / bar@myprovider"),
conf: runtime.Configuration{
Middlewares: map[string]*runtime.MiddlewareInfo{
"foo / bar@myprovider": {
Middleware: &dynamic.Middleware{
AddPrefix: &dynamic.AddPrefix{
Prefix: "/titi",
},
},
UsedBy: []string{"test@myprovider"},
},
},
},
expected: expected{
statusCode: http.StatusOK,
jsonFile: "testdata/middleware-foo-slash-bar.json",
},
},
{
desc: "one middleware by id, that does not exist",
path: "/api/http/middlewares/foo@myprovider",

View file

@ -4,6 +4,7 @@ import (
"encoding/json"
"fmt"
"net/http"
"net/url"
"strconv"
"strings"
@ -90,7 +91,13 @@ func (h Handler) getTCPRouters(rw http.ResponseWriter, request *http.Request) {
}
func (h Handler) getTCPRouter(rw http.ResponseWriter, request *http.Request) {
routerID := mux.Vars(request)["routerID"]
scapedRouterID := mux.Vars(request)["routerID"]
routerID, err := url.PathUnescape(scapedRouterID)
if err != nil {
writeError(rw, fmt.Sprintf("unable to decode routerID %q: %s", scapedRouterID, err), http.StatusBadRequest)
return
}
rw.Header().Set("Content-Type", "application/json")
@ -102,7 +109,7 @@ func (h Handler) getTCPRouter(rw http.ResponseWriter, request *http.Request) {
result := newTCPRouterRepresentation(routerID, router)
err := json.NewEncoder(rw).Encode(result)
err = json.NewEncoder(rw).Encode(result)
if err != nil {
log.Ctx(request.Context()).Error().Err(err).Send()
writeError(rw, err.Error(), http.StatusInternalServerError)
@ -141,7 +148,13 @@ func (h Handler) getTCPServices(rw http.ResponseWriter, request *http.Request) {
}
func (h Handler) getTCPService(rw http.ResponseWriter, request *http.Request) {
serviceID := mux.Vars(request)["serviceID"]
scapedServiceID := mux.Vars(request)["serviceID"]
serviceID, err := url.PathUnescape(scapedServiceID)
if err != nil {
writeError(rw, fmt.Sprintf("unable to decode serviceID %q: %s", scapedServiceID, err), http.StatusBadRequest)
return
}
rw.Header().Set("Content-Type", "application/json")
@ -153,7 +166,7 @@ func (h Handler) getTCPService(rw http.ResponseWriter, request *http.Request) {
result := newTCPServiceRepresentation(serviceID, service)
err := json.NewEncoder(rw).Encode(result)
err = json.NewEncoder(rw).Encode(result)
if err != nil {
log.Ctx(request.Context()).Error().Err(err).Send()
writeError(rw, err.Error(), http.StatusInternalServerError)
@ -192,7 +205,13 @@ func (h Handler) getTCPMiddlewares(rw http.ResponseWriter, request *http.Request
}
func (h Handler) getTCPMiddleware(rw http.ResponseWriter, request *http.Request) {
middlewareID := mux.Vars(request)["middlewareID"]
scapedMiddlewareID := mux.Vars(request)["middlewareID"]
middlewareID, err := url.PathUnescape(scapedMiddlewareID)
if err != nil {
writeError(rw, fmt.Sprintf("unable to decode middlewareID %q: %s", scapedMiddlewareID, err), http.StatusBadRequest)
return
}
rw.Header().Set("Content-Type", "application/json")
@ -204,7 +223,7 @@ func (h Handler) getTCPMiddleware(rw http.ResponseWriter, request *http.Request)
result := newTCPMiddlewareRepresentation(middlewareID, middleware)
err := json.NewEncoder(rw).Encode(result)
err = json.NewEncoder(rw).Encode(result)
if err != nil {
log.Ctx(request.Context()).Error().Err(err).Send()
writeError(rw, err.Error(), http.StatusInternalServerError)

View file

@ -6,6 +6,7 @@ import (
"io"
"net/http"
"net/http/httptest"
"net/url"
"os"
"testing"
@ -295,6 +296,25 @@ func TestHandler_TCP(t *testing.T) {
jsonFile: "testdata/tcprouter-bar.json",
},
},
{
desc: "one TCP router by id containing slash",
path: "/api/tcp/routers/" + url.PathEscape("foo / bar@myprovider"),
conf: runtime.Configuration{
TCPRouters: map[string]*runtime.TCPRouterInfo{
"foo / bar@myprovider": {
TCPRouter: &dynamic.TCPRouter{
EntryPoints: []string{"web"},
Service: "foo-service@myprovider",
Rule: "Host(`foo.bar`)",
},
},
},
},
expected: expected{
statusCode: http.StatusOK,
jsonFile: "testdata/tcprouter-foo-slash-bar.json",
},
},
{
desc: "one TCP router by id, that does not exist",
path: "/api/tcp/routers/foo@myprovider",
@ -559,6 +579,30 @@ func TestHandler_TCP(t *testing.T) {
jsonFile: "testdata/tcpservice-bar.json",
},
},
{
desc: "one tcp service by id containing slash",
path: "/api/tcp/services/" + url.PathEscape("foo / bar@myprovider"),
conf: runtime.Configuration{
TCPServices: map[string]*runtime.TCPServiceInfo{
"foo / bar@myprovider": {
TCPService: &dynamic.TCPService{
LoadBalancer: &dynamic.TCPServersLoadBalancer{
Servers: []dynamic.TCPServer{
{
Address: "127.0.0.1:2345",
},
},
},
},
UsedBy: []string{"foo@myprovider", "test@myprovider"},
},
},
},
expected: expected{
statusCode: http.StatusOK,
jsonFile: "testdata/tcpservice-foo-slash-bar.json",
},
},
{
desc: "one tcp service by id, that does not exist",
path: "/api/tcp/services/nono@myprovider",
@ -780,6 +824,26 @@ func TestHandler_TCP(t *testing.T) {
jsonFile: "testdata/tcpmiddleware-ipallowlist.json",
},
},
{
desc: "one middleware by id containing slash",
path: "/api/tcp/middlewares/" + url.PathEscape("foo / bar@myprovider"),
conf: runtime.Configuration{
TCPMiddlewares: map[string]*runtime.TCPMiddlewareInfo{
"foo / bar@myprovider": {
TCPMiddleware: &dynamic.TCPMiddleware{
IPWhiteList: &dynamic.TCPIPWhiteList{
SourceRange: []string{"127.0.0.1/32"},
},
},
UsedBy: []string{"bar@myprovider", "test@myprovider"},
},
},
},
expected: expected{
statusCode: http.StatusOK,
jsonFile: "testdata/tcpmiddleware-foo-slash-bar.json",
},
},
{
desc: "one middleware by id, that does not exist",
path: "/api/tcp/middlewares/foo@myprovider",

View file

@ -4,6 +4,7 @@ import (
"encoding/json"
"fmt"
"net/http"
"net/url"
"strconv"
"strings"
@ -74,7 +75,13 @@ func (h Handler) getUDPRouters(rw http.ResponseWriter, request *http.Request) {
}
func (h Handler) getUDPRouter(rw http.ResponseWriter, request *http.Request) {
routerID := mux.Vars(request)["routerID"]
scapedRouterID := mux.Vars(request)["routerID"]
routerID, err := url.PathUnescape(scapedRouterID)
if err != nil {
writeError(rw, fmt.Sprintf("unable to decode routerID %q: %s", scapedRouterID, err), http.StatusBadRequest)
return
}
rw.Header().Set("Content-Type", "application/json")
@ -86,7 +93,7 @@ func (h Handler) getUDPRouter(rw http.ResponseWriter, request *http.Request) {
result := newUDPRouterRepresentation(routerID, router)
err := json.NewEncoder(rw).Encode(result)
err = json.NewEncoder(rw).Encode(result)
if err != nil {
log.Ctx(request.Context()).Error().Err(err).Send()
writeError(rw, err.Error(), http.StatusInternalServerError)
@ -125,7 +132,13 @@ func (h Handler) getUDPServices(rw http.ResponseWriter, request *http.Request) {
}
func (h Handler) getUDPService(rw http.ResponseWriter, request *http.Request) {
serviceID := mux.Vars(request)["serviceID"]
scapedServiceID := mux.Vars(request)["serviceID"]
serviceID, err := url.PathUnescape(scapedServiceID)
if err != nil {
writeError(rw, fmt.Sprintf("unable to decode serviceID %q: %s", scapedServiceID, err), http.StatusBadRequest)
return
}
rw.Header().Set("Content-Type", "application/json")
@ -137,7 +150,7 @@ func (h Handler) getUDPService(rw http.ResponseWriter, request *http.Request) {
result := newUDPServiceRepresentation(serviceID, service)
err := json.NewEncoder(rw).Encode(result)
err = json.NewEncoder(rw).Encode(result)
if err != nil {
log.Ctx(request.Context()).Error().Err(err).Send()
writeError(rw, err.Error(), http.StatusInternalServerError)

View file

@ -6,6 +6,7 @@ import (
"io"
"net/http"
"net/http/httptest"
"net/url"
"os"
"testing"
@ -224,6 +225,24 @@ func TestHandler_UDP(t *testing.T) {
jsonFile: "testdata/udprouter-bar.json",
},
},
{
desc: "one UDP router by id containing slash",
path: "/api/udp/routers/" + url.PathEscape("foo / bar@myprovider"),
conf: runtime.Configuration{
UDPRouters: map[string]*runtime.UDPRouterInfo{
"foo / bar@myprovider": {
UDPRouter: &dynamic.UDPRouter{
EntryPoints: []string{"web"},
Service: "foo-service@myprovider",
},
},
},
},
expected: expected{
statusCode: http.StatusOK,
jsonFile: "testdata/udprouter-foo-slash-bar.json",
},
},
{
desc: "one UDP router by id, that does not exist",
path: "/api/udp/routers/foo@myprovider",
@ -487,6 +506,30 @@ func TestHandler_UDP(t *testing.T) {
jsonFile: "testdata/udpservice-bar.json",
},
},
{
desc: "one udp service by id containing slash",
path: "/api/udp/services/" + url.PathEscape("foo / bar@myprovider"),
conf: runtime.Configuration{
UDPServices: map[string]*runtime.UDPServiceInfo{
"foo / bar@myprovider": {
UDPService: &dynamic.UDPService{
LoadBalancer: &dynamic.UDPServersLoadBalancer{
Servers: []dynamic.UDPServer{
{
Address: "127.0.0.1:2345",
},
},
},
},
UsedBy: []string{"foo@myprovider", "test@myprovider"},
},
},
},
expected: expected{
statusCode: http.StatusOK,
jsonFile: "testdata/udpservice-foo-slash-bar.json",
},
},
{
desc: "one udp service by id, that does not exist",
path: "/api/udp/services/nono@myprovider",

View file

@ -0,0 +1,5 @@
{
"address": ":81",
"http": {},
"name": "foo / bar"
}

View file

@ -0,0 +1,12 @@
{
"addPrefix": {
"prefix": "/titi"
},
"name": "foo / bar@myprovider",
"provider": "myprovider",
"status": "enabled",
"type": "addprefix",
"usedBy": [
"test@myprovider"
]
}

View file

@ -0,0 +1,17 @@
{
"entryPoints": [
"web"
],
"middlewares": [
"auth",
"addPrefixTest@anotherprovider"
],
"name": "foo / bar@myprovider",
"provider": "myprovider",
"rule": "Host(`foo.bar`)",
"service": "foo-service@myprovider",
"status": "enabled",
"using": [
"web"
]
}

View file

@ -0,0 +1,21 @@
{
"loadBalancer": {
"passHostHeader": true,
"servers": [
{
"url": "http://127.0.0.1"
}
]
},
"name": "foo / bar@myprovider",
"provider": "myprovider",
"serverStatus": {
"http://127.0.0.1": "UP"
},
"status": "enabled",
"type": "loadbalancer",
"usedBy": [
"foo@myprovider",
"test@myprovider"
]
}

View file

@ -0,0 +1,13 @@
{
"ipWhiteList": {
"sourceRange": ["127.0.0.1/32"]
},
"name": "foo / bar@myprovider",
"provider": "myprovider",
"status": "enabled",
"type": "ipwhitelist",
"usedBy": [
"bar@myprovider",
"test@myprovider"
]
}

View file

@ -0,0 +1,13 @@
{
"entryPoints": [
"web"
],
"name": "foo / bar@myprovider",
"provider": "myprovider",
"rule": "Host(`foo.bar`)",
"service": "foo-service@myprovider",
"status": "enabled",
"using": [
"web"
]
}

View file

@ -0,0 +1,17 @@
{
"loadBalancer": {
"servers": [
{
"address": "127.0.0.1:2345"
}
]
},
"name": "foo / bar@myprovider",
"provider": "myprovider",
"status": "enabled",
"type": "loadbalancer",
"usedBy": [
"foo@myprovider",
"test@myprovider"
]
}

View file

@ -0,0 +1,12 @@
{
"entryPoints": [
"web"
],
"name": "foo / bar@myprovider",
"provider": "myprovider",
"service": "foo-service@myprovider",
"status": "enabled",
"using": [
"web"
]
}

View file

@ -0,0 +1,17 @@
{
"loadBalancer": {
"servers": [
{
"address": "127.0.0.1:2345"
}
]
},
"name": "foo / bar@myprovider",
"provider": "myprovider",
"status": "enabled",
"type": "loadbalancer",
"usedBy": [
"foo@myprovider",
"test@myprovider"
]
}

View file

@ -20,8 +20,6 @@ import (
const collectorURL = "https://collect.traefik.io/yYaUej3P42cziRVzv6T5w2aYy9po2Mrn"
// Collected data.
//
//nolint:musttag // cannot be changed for historical reasons.
type data struct {
Version string
Codename string
@ -67,7 +65,7 @@ func createBody(staticConfiguration *static.Configuration) (*bytes.Buffer, error
}
buf := new(bytes.Buffer)
err = json.NewEncoder(buf).Encode(data)
err = json.NewEncoder(buf).Encode(data) //nolint:musttag // cannot be changed for historical reasons.
if err != nil {
return nil, err
}

View file

@ -2,6 +2,7 @@ package runtime
import (
"context"
"errors"
"fmt"
"slices"
"sort"
@ -43,7 +44,7 @@ func (c *Configuration) GetRoutersByEntryPoints(ctx context.Context, entryPoints
}
if entryPointsCount == 0 {
rt.AddError(fmt.Errorf("no valid entryPoint for this router"), true)
rt.AddError(errors.New("no valid entryPoint for this router"), true)
logger.Error().Msg("No valid entryPoint for this router")
}

View file

@ -2,6 +2,7 @@ package runtime
import (
"context"
"errors"
"fmt"
"slices"
@ -37,7 +38,7 @@ func (c *Configuration) GetTCPRoutersByEntryPoints(ctx context.Context, entryPoi
}
if entryPointsCount == 0 {
rt.AddError(fmt.Errorf("no valid entryPoint for this router"), true)
rt.AddError(errors.New("no valid entryPoint for this router"), true)
logger.Error().Msg("No valid entryPoint for this router")
}
}

View file

@ -2,6 +2,7 @@ package runtime
import (
"context"
"errors"
"fmt"
"slices"
@ -43,7 +44,7 @@ func (c *Configuration) GetUDPRoutersByEntryPoints(ctx context.Context, entryPoi
}
if entryPointsCount == 0 {
rt.AddError(fmt.Errorf("no valid entryPoint for this router"), true)
rt.AddError(errors.New("no valid entryPoint for this router"), true)
logger.Error().Msg("No valid entryPoint for this router")
}
}

View file

@ -27,6 +27,8 @@ import (
"github.com/traefik/traefik/v3/pkg/types"
)
const delta float64 = 1e-10
var (
logFileNameSuffix = "/traefik/logger/test.log"
testContent = "Hello, World"
@ -280,7 +282,7 @@ func assertFloat64(exp float64) func(t *testing.T, actual interface{}) {
return func(t *testing.T, actual interface{}) {
t.Helper()
assert.Equal(t, exp, actual)
assert.InDelta(t, exp, actual, delta)
}
}

View file

@ -2,7 +2,7 @@ package addprefix
import (
"context"
"fmt"
"errors"
"net/http"
"github.com/traefik/traefik/v3/pkg/config/dynamic"
@ -33,7 +33,7 @@ func New(ctx context.Context, next http.Handler, config dynamic.AddPrefix, name
name: name,
}
} else {
return nil, fmt.Errorf("prefix cannot be empty")
return nil, errors.New("prefix cannot be empty")
}
return result, nil

View file

@ -2,6 +2,7 @@ package brotli
import (
"bufio"
"errors"
"fmt"
"io"
"mime"
@ -34,11 +35,11 @@ type Config struct {
// NewWrapper returns a new Brotli compressing wrapper.
func NewWrapper(cfg Config) (func(http.Handler) http.HandlerFunc, error) {
if cfg.MinSize < 0 {
return nil, fmt.Errorf("minimum size must be greater than or equal to zero")
return nil, errors.New("minimum size must be greater than or equal to zero")
}
if len(cfg.ExcludedContentTypes) > 0 && len(cfg.IncludedContentTypes) > 0 {
return nil, fmt.Errorf("excludedContentTypes and includedContentTypes options are mutually exclusive")
return nil, errors.New("excludedContentTypes and includedContentTypes options are mutually exclusive")
}
var excludedContentTypes []parsedContentType

View file

@ -2,6 +2,7 @@ package compress
import (
"context"
"errors"
"fmt"
"mime"
"net/http"
@ -38,7 +39,7 @@ func New(ctx context.Context, next http.Handler, conf dynamic.Compress, name str
middlewares.GetLogger(ctx, name, typeName).Debug().Msg("Creating middleware")
if len(conf.ExcludedContentTypes) > 0 && len(conf.IncludedContentTypes) > 0 {
return nil, fmt.Errorf("excludedContentTypes and includedContentTypes options are mutually exclusive")
return nil, errors.New("excludedContentTypes and includedContentTypes options are mutually exclusive")
}
excludes := []string{"application/grpc"}

View file

@ -126,7 +126,7 @@ func hostSNIV2(tree *matchersTree, hosts ...string) error {
// hostSNIRegexpV2 checks if the SNI Host of the connection matches the matcher host regexp.
func hostSNIRegexpV2(tree *matchersTree, templates ...string) error {
if len(templates) == 0 {
return fmt.Errorf("empty value for \"HostSNIRegexp\" matcher is not allowed")
return errors.New("empty value for \"HostSNIRegexp\" matcher is not allowed")
}
var regexps []*regexp.Regexp

View file

@ -2,6 +2,7 @@ package plugins
import (
"context"
"errors"
"fmt"
"net/http"
"path/filepath"
@ -176,7 +177,7 @@ func getWasmPath(manifest *Manifest) (string, error) {
}
if !filepath.IsLocal(wasmPath) {
return "", fmt.Errorf("wasmPath must be a local path")
return "", errors.New("wasmPath must be a local path")
}
return wasmPath, nil

View file

@ -228,7 +228,7 @@ func (c *Client) Check(ctx context.Context, pName, pVersion, hash string) error
return nil
}
return fmt.Errorf("plugin integrity check failed")
return errors.New("plugin integrity check failed")
}
// Unzip unzip a plugin archive.

View file

@ -2,6 +2,7 @@ package http
import (
"context"
"errors"
"fmt"
"hash/fnv"
"io"
@ -44,11 +45,11 @@ func (p *Provider) SetDefaults() {
// Init the provider.
func (p *Provider) Init() error {
if p.Endpoint == "" {
return fmt.Errorf("non-empty endpoint is required")
return errors.New("non-empty endpoint is required")
}
if p.PollInterval <= 0 {
return fmt.Errorf("poll interval must be greater than 0")
return errors.New("poll interval must be greater than 0")
}
p.httpClient = &http.Client{

View file

@ -50,6 +50,7 @@ type sharedInformerFactory struct {
lock sync.Mutex
defaultResync time.Duration
customResync map[reflect.Type]time.Duration
transform cache.TransformFunc
informers map[reflect.Type]cache.SharedIndexInformer
// startedInformers is used for tracking which informers have been started.
@ -88,6 +89,14 @@ func WithNamespace(namespace string) SharedInformerOption {
}
}
// WithTransform sets a transform on all informers.
func WithTransform(transform cache.TransformFunc) SharedInformerOption {
return func(factory *sharedInformerFactory) *sharedInformerFactory {
factory.transform = transform
return factory
}
}
// NewSharedInformerFactory constructs a new instance of sharedInformerFactory for all namespaces.
func NewSharedInformerFactory(client versioned.Interface, defaultResync time.Duration) SharedInformerFactory {
return NewSharedInformerFactoryWithOptions(client, defaultResync)
@ -192,6 +201,7 @@ func (f *sharedInformerFactory) InformerFor(obj runtime.Object, newFunc internal
}
informer = newFunc(f.client, resyncPeriod)
informer.SetTransform(f.transform)
f.informers[informerType] = informer
return informer

View file

@ -717,7 +717,7 @@ func createForwardAuthMiddleware(k8sClient Client, namespace string, auth *traef
return nil, nil
}
if len(auth.Address) == 0 {
return nil, fmt.Errorf("forward authentication requires an address")
return nil, errors.New("forward authentication requires an address")
}
forwardAuth := &dynamic.ForwardAuth{
@ -812,7 +812,7 @@ func createBasicAuthMiddleware(client Client, namespace string, basicAuth *traef
}
if basicAuth.Secret == "" {
return nil, fmt.Errorf("auth secret must be set")
return nil, errors.New("auth secret must be set")
}
secret, ok, err := client.GetSecret(namespace, basicAuth.Secret)
@ -859,7 +859,7 @@ func createDigestAuthMiddleware(client Client, namespace string, digestAuth *tra
}
if digestAuth.Secret == "" {
return nil, fmt.Errorf("auth secret must be set")
return nil, errors.New("auth secret must be set")
}
secret, ok, err := client.GetSecret(namespace, digestAuth.Secret)

View file

@ -2,7 +2,7 @@ package ingress
import (
"context"
"fmt"
"errors"
"testing"
"time"
@ -39,9 +39,9 @@ func TestTranslateNotFoundError(t *testing.T) {
},
{
desc: "not a kubernetes not found error",
err: fmt.Errorf("bar error"),
err: errors.New("bar error"),
expectedExists: false,
expectedError: fmt.Errorf("bar error"),
expectedError: errors.New("bar error"),
},
}

View file

@ -2,7 +2,7 @@ package safe
import (
"context"
"fmt"
"errors"
"sync"
"testing"
"time"
@ -146,7 +146,7 @@ func TestOperationWithRecoverPanic(t *testing.T) {
func TestOperationWithRecoverError(t *testing.T) {
operation := func() error {
return fmt.Errorf("ERROR")
return errors.New("ERROR")
}
err := backoff.Retry(OperationWithRecover(operation), &backoff.StopBackOff{})
if err == nil {

View file

@ -2,7 +2,7 @@ package server
import (
"context"
"fmt"
"errors"
"strconv"
"sync"
"testing"
@ -30,7 +30,7 @@ func (p *mockProvider) Provide(configurationChan chan<- dynamic.Message, _ *safe
}
if len(p.messages) == 0 {
return fmt.Errorf("no messages available")
return errors.New("no messages available")
}
configurationChan <- p.messages[0]

View file

@ -173,9 +173,11 @@ func Test_Routing(t *testing.T) {
map[string]traefiktls.Store{},
map[string]traefiktls.Options{
"default": {
MinVersion: "VersionTLS10",
MaxVersion: "VersionTLS10",
},
"tls10": {
MinVersion: "VersionTLS10",
MaxVersion: "VersionTLS10",
},
"tls12": {

View file

@ -31,6 +31,7 @@ import (
"github.com/traefik/traefik/v3/pkg/safe"
"github.com/traefik/traefik/v3/pkg/server/router"
tcprouter "github.com/traefik/traefik/v3/pkg/server/router/tcp"
"github.com/traefik/traefik/v3/pkg/server/service"
"github.com/traefik/traefik/v3/pkg/tcp"
"github.com/traefik/traefik/v3/pkg/types"
"golang.org/x/net/http2"
@ -387,7 +388,7 @@ func writeCloser(conn net.Conn) (tcp.WriteCloser, error) {
case *proxyproto.Conn:
underlying, ok := typedConn.TCPConn()
if !ok {
return nil, fmt.Errorf("underlying connection is not a tcp connection")
return nil, errors.New("underlying connection is not a tcp connection")
}
return &writeCloserWrapper{writeCloser: underlying, Conn: typedConn}, nil
case *net.TCPConn:
@ -633,6 +634,16 @@ func createHTTPServer(ctx context.Context, ln net.Listener, configuration *stati
}
}
prevConnContext := serverHTTP.ConnContext
serverHTTP.ConnContext = func(ctx context.Context, c net.Conn) context.Context {
// This adds an empty struct in order to store a RoundTripper in the ConnContext in case of Kerberos or NTLM.
ctx = service.AddTransportOnContext(ctx)
if prevConnContext != nil {
return prevConnContext(ctx, c)
}
return ctx
}
// ConfigureServer configures HTTP/2 with the MaxConcurrentStreams option for the given server.
// Also keeping behavior the same as
// https://cs.opensource.google/go/go/+/refs/tags/go1.17.7:src/net/http/server.go;l=3262
@ -641,7 +652,6 @@ func createHTTPServer(ctx context.Context, ln net.Listener, configuration *stati
MaxConcurrentStreams: uint32(configuration.HTTP2.MaxConcurrentStreams),
NewWriteScheduler: func() http2.WriteScheduler { return http2.NewPriorityWriteScheduler(nil) },
})
if err != nil {
return nil, fmt.Errorf("configure HTTP/2 server: %w", err)
}

View file

@ -4,9 +4,9 @@ import (
"container/heap"
"context"
"errors"
"fmt"
"hash/fnv"
"net/http"
"strconv"
"sync"
"github.com/rs/zerolog/log"
@ -276,5 +276,5 @@ func hash(input string) string {
// We purposely ignore the error because the implementation always returns nil.
_, _ = hasher.Write([]byte(input))
return fmt.Sprintf("%x", hasher.Sum64())
return strconv.FormatUint(hasher.Sum64(), 16)
}

View file

@ -1,6 +1,7 @@
package service
import (
"context"
"crypto/tls"
"crypto/x509"
"errors"
@ -8,6 +9,7 @@ import (
"net"
"net/http"
"reflect"
"strings"
"sync"
"time"
@ -180,10 +182,71 @@ func (r *RoundTripperManager) createRoundTripper(cfg *dynamic.ServersTransport)
// Return directly HTTP/1.1 transport when HTTP/2 is disabled
if cfg.DisableHTTP2 {
return transport, nil
return &KerberosRoundTripper{
OriginalRoundTripper: transport,
new: func() http.RoundTripper {
return transport.Clone()
},
}, nil
}
return newSmartRoundTripper(transport, cfg.ForwardingTimeouts)
rt, err := newSmartRoundTripper(transport, cfg.ForwardingTimeouts)
if err != nil {
return nil, err
}
return &KerberosRoundTripper{
OriginalRoundTripper: rt,
new: func() http.RoundTripper {
return rt.Clone()
},
}, nil
}
type KerberosRoundTripper struct {
new func() http.RoundTripper
OriginalRoundTripper http.RoundTripper
}
type stickyRoundTripper struct {
RoundTripper http.RoundTripper
}
type transportKeyType string
var transportKey transportKeyType = "transport"
func AddTransportOnContext(ctx context.Context) context.Context {
return context.WithValue(ctx, transportKey, &stickyRoundTripper{})
}
func (k *KerberosRoundTripper) RoundTrip(request *http.Request) (*http.Response, error) {
value, ok := request.Context().Value(transportKey).(*stickyRoundTripper)
if !ok {
return k.OriginalRoundTripper.RoundTrip(request)
}
if value.RoundTripper != nil {
return value.RoundTripper.RoundTrip(request)
}
resp, err := k.OriginalRoundTripper.RoundTrip(request)
// If we found that we are authenticating with Kerberos (Negotiate) or NTLM.
// We put a dedicated roundTripper in the ConnContext.
// This will stick the next calls to the same connection with the backend.
if err == nil && containsNTLMorNegotiate(resp.Header.Values("WWW-Authenticate")) {
value.RoundTripper = k.new()
}
return resp, err
}
func containsNTLMorNegotiate(h []string) bool {
for _, s := range h {
if strings.HasPrefix(s, "NTLM") || strings.HasPrefix(s, "Negotiate") {
return true
}
}
return false
}
func createRootCACertPool(rootCAs []types.FileOrContent) *x509.CertPool {

View file

@ -1,6 +1,7 @@
package service
import (
"context"
"crypto/rand"
"crypto/rsa"
"crypto/tls"
@ -549,3 +550,80 @@ func (s *fakeSpiffeSource) GetX509BundleForTrustDomain(trustDomain spiffeid.Trus
func (s *fakeSpiffeSource) GetX509SVID() (*x509svid.SVID, error) {
return s.svid, nil
}
type roundTripperFn func(req *http.Request) (*http.Response, error)
func (r roundTripperFn) RoundTrip(request *http.Request) (*http.Response, error) {
return r(request)
}
func TestKerberosRoundTripper(t *testing.T) {
testCases := []struct {
desc string
originalRoundTripperHeaders map[string][]string
expectedStatusCode []int
expectedDedicatedCount int
expectedOriginalCount int
}{
{
desc: "without special header",
expectedStatusCode: []int{http.StatusUnauthorized, http.StatusUnauthorized, http.StatusUnauthorized},
expectedOriginalCount: 3,
},
{
desc: "with Negotiate (Kerberos)",
originalRoundTripperHeaders: map[string][]string{"Www-Authenticate": {"Negotiate"}},
expectedStatusCode: []int{http.StatusUnauthorized, http.StatusOK, http.StatusOK},
expectedOriginalCount: 1,
expectedDedicatedCount: 2,
},
{
desc: "with NTLM",
originalRoundTripperHeaders: map[string][]string{"Www-Authenticate": {"NTLM"}},
expectedStatusCode: []int{http.StatusUnauthorized, http.StatusOK, http.StatusOK},
expectedOriginalCount: 1,
expectedDedicatedCount: 2,
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
origCount := 0
dedicatedCount := 0
rt := KerberosRoundTripper{
new: func() http.RoundTripper {
return roundTripperFn(func(req *http.Request) (*http.Response, error) {
dedicatedCount++
return &http.Response{
StatusCode: http.StatusOK,
}, nil
})
},
OriginalRoundTripper: roundTripperFn(func(req *http.Request) (*http.Response, error) {
origCount++
return &http.Response{
StatusCode: http.StatusUnauthorized,
Header: test.originalRoundTripperHeaders,
}, nil
}),
}
ctx := AddTransportOnContext(context.Background())
for _, expected := range test.expectedStatusCode {
req, err := http.NewRequestWithContext(ctx, http.MethodGet, "http://127.0.0.1", http.NoBody)
require.NoError(t, err)
resp, err := rt.RoundTrip(req)
require.NoError(t, err)
require.Equal(t, expected, resp.StatusCode)
}
require.Equal(t, test.expectedOriginalCount, origCount)
require.Equal(t, test.expectedDedicatedCount, dedicatedCount)
})
}
}

View file

@ -11,7 +11,7 @@ import (
"golang.org/x/net/http2"
)
func newSmartRoundTripper(transport *http.Transport, forwardingTimeouts *dynamic.ForwardingTimeouts) (http.RoundTripper, error) {
func newSmartRoundTripper(transport *http.Transport, forwardingTimeouts *dynamic.ForwardingTimeouts) (*smartRoundTripper, error) {
transportHTTP1 := transport.Clone()
transportHTTP2, err := http2.ConfigureTransports(transport)
@ -53,6 +53,12 @@ type smartRoundTripper struct {
http *http.Transport
}
func (m *smartRoundTripper) Clone() http.RoundTripper {
h := m.http.Clone()
h2 := m.http2.Clone()
return &smartRoundTripper{http: h, http2: h2}
}
func (m *smartRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
// If we have a connection upgrade, we don't use HTTP/2
if httpguts.HeaderValuesContainsToken(req.Header["Connection"], "Upgrade") {

View file

@ -1,7 +1,7 @@
package tcp
import (
"fmt"
"errors"
)
// Constructor A constructor for a piece of TCP middleware.
@ -29,7 +29,7 @@ func NewChain(constructors ...Constructor) Chain {
// Then adds an handler at the end of the chain.
func (c Chain) Then(h Handler) (Handler, error) {
if h == nil {
return nil, fmt.Errorf("cannot add a nil handler to the chain")
return nil, errors.New("cannot add a nil handler to the chain")
}
for i := range c.constructors {

View file

@ -1,7 +1,7 @@
package tcp
import (
"fmt"
"errors"
"sync"
"github.com/rs/zerolog/log"
@ -91,7 +91,7 @@ func gcd(a, b int) int {
func (b *WRRLoadBalancer) next() (Handler, error) {
if len(b.servers) == 0 {
return nil, fmt.Errorf("no servers in the pool")
return nil, errors.New("no servers in the pool")
}
// The algo below may look messy, but is actually very simple
@ -101,7 +101,7 @@ func (b *WRRLoadBalancer) next() (Handler, error) {
// Maximum weight across all enabled servers
max := b.maxWeight()
if max == 0 {
return nil, fmt.Errorf("all servers have 0 weight")
return nil, errors.New("all servers have 0 weight")
}
// GCD across all enabled servers

View file

@ -334,10 +334,6 @@ func TestManager_Get_DefaultValues(t *testing.T) {
assert.Equal(t, uint16(tls.VersionTLS12), config.MinVersion)
assert.Equal(t, []string{"h2", "http/1.1", "acme-tls/1"}, config.NextProtos)
assert.Equal(t, []uint16{
tls.TLS_RSA_WITH_AES_128_CBC_SHA,
tls.TLS_RSA_WITH_AES_256_CBC_SHA,
tls.TLS_RSA_WITH_AES_128_GCM_SHA256,
tls.TLS_RSA_WITH_AES_256_GCM_SHA384,
tls.TLS_AES_128_GCM_SHA256,
tls.TLS_AES_256_GCM_SHA384,
tls.TLS_CHACHA20_POLY1305_SHA256,

View file

@ -1,7 +1,7 @@
package udp
import (
"fmt"
"errors"
"sync"
"github.com/rs/zerolog/log"
@ -91,7 +91,7 @@ func gcd(a, b int) int {
func (b *WRRLoadBalancer) next() (Handler, error) {
if len(b.servers) == 0 {
return nil, fmt.Errorf("no servers in the pool")
return nil, errors.New("no servers in the pool")
}
// The algorithm below may look messy,
@ -101,7 +101,7 @@ func (b *WRRLoadBalancer) next() (Handler, error) {
// Maximum weight across all enabled servers
max := b.maxWeight()
if max == 0 {
return nil, fmt.Errorf("all servers have 0 weight")
return nil, errors.New("all servers have 0 weight")
}
// GCD across all enabled servers