From aa5f2b92d4846f58bb20caabfee66d6fdd3927b3 Mon Sep 17 00:00:00 2001 From: Sheddy Date: Fri, 16 May 2025 11:02:04 +0100 Subject: [PATCH 01/21] Fix broken link in documentation --- docs/content/security/content-length.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/content/security/content-length.md b/docs/content/security/content-length.md index e09c62b6d..fff161881 100644 --- a/docs/content/security/content-length.md +++ b/docs/content/security/content-length.md @@ -5,7 +5,7 @@ description: "Enforce strict Content‑Length validation in Traefik by streaming Traefik acts as a streaming proxy. By default, it checks each chunk of data against the `Content-Length` header as it passes it on to the backend or client. This live check blocks truncated or over‑long streams without holding the entire message. -If you need Traefik to read and verify the full body before any data moves on, add the [buffering middleware](../../reference/routing-configuration/http/middlewares/buffering.md): +If you need Traefik to read and verify the full body before any data moves on, add the [buffering middleware](../middlewares/http/buffering.md): ```yaml http: From 79fde2b6dd681511a07823ccc41f5273870832fe Mon Sep 17 00:00:00 2001 From: Patrick Evans <31580846+holysoles@users.noreply.github.com> Date: Mon, 19 May 2025 07:26:09 +0000 Subject: [PATCH 02/21] Do not warn network missing if connected to a container network --- pkg/provider/docker/config.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pkg/provider/docker/config.go b/pkg/provider/docker/config.go index 760aba1b2..b3bbe2411 100644 --- a/pkg/provider/docker/config.go +++ b/pkg/provider/docker/config.go @@ -340,7 +340,6 @@ func (p *DynConfBuilder) getIPAddress(ctx context.Context, container dockerData) } netNotFound = true - logger.Warn().Msgf("Could not find network named %q for container %q. Maybe you're missing the project's prefix in the label?", container.ExtraConf.Network, container.Name) } } @@ -382,6 +381,9 @@ func (p *DynConfBuilder) getIPAddress(ctx context.Context, container dockerData) return p.getIPAddress(ctx, containerParsed) } + if netNotFound { + logger.Warn().Msgf("Could not find network named %q for container %q. Maybe you're missing the project's prefix in the label?", container.ExtraConf.Network, container.Name) + } for _, network := range container.NetworkSettings.Networks { if netNotFound { logger.Warn().Msgf("Defaulting to first available network (%q) for container %q.", network, container.Name) From 3deea566ac303f3020225b016b2179d709a8e011 Mon Sep 17 00:00:00 2001 From: Landry Benguigui Date: Mon, 19 May 2025 09:52:05 +0200 Subject: [PATCH 03/21] Make P2C strategy thread-safe --- pkg/server/service/loadbalancer/p2c/p2c.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/pkg/server/service/loadbalancer/p2c/p2c.go b/pkg/server/service/loadbalancer/p2c/p2c.go index 994df35bc..d6d31413c 100644 --- a/pkg/server/service/loadbalancer/p2c/p2c.go +++ b/pkg/server/service/loadbalancer/p2c/p2c.go @@ -58,7 +58,8 @@ type Balancer struct { sticky *loadbalancer.Sticky - rand rnd + randMu sync.Mutex + rand rnd } // New creates a new power-of-two-random-choices load balancer. @@ -152,10 +153,13 @@ func (b *Balancer) nextServer() (*namedHandler, error) { if len(healthy) == 1 { return healthy[0], nil } - // In order to not get the same backend twice, we make the second call to s.rand.IntN one fewer + // In order to not get the same backend twice, we make the second call to s.rand.Intn one fewer // than the length of the slice. We then have to shift over the second index if it is equal or // greater than the first index, wrapping round if needed. + b.randMu.Lock() n1, n2 := b.rand.Intn(len(healthy)), b.rand.Intn(len(healthy)) + b.randMu.Unlock() + if n2 == n1 { n2 = (n2 + 1) % len(healthy) } From 06b02bcd95ad4c5dd4bfcbb8b9f54f5fee8770ce Mon Sep 17 00:00:00 2001 From: Kevin Pollet Date: Fri, 23 May 2025 10:44:40 +0200 Subject: [PATCH 04/21] Do not display RemoveHeader option when not defined Co-authored-by: Romain --- webui/src/components/_commons/PanelMiddlewares.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webui/src/components/_commons/PanelMiddlewares.vue b/webui/src/components/_commons/PanelMiddlewares.vue index c390df83e..aef902f7f 100644 --- a/webui/src/components/_commons/PanelMiddlewares.vue +++ b/webui/src/components/_commons/PanelMiddlewares.vue @@ -153,7 +153,7 @@ - +
From 76153acac6cb91848a0c00e7f9e0bd4c7287fe82 Mon Sep 17 00:00:00 2001 From: Romain Date: Fri, 23 May 2025 11:34:05 +0200 Subject: [PATCH 05/21] Fix CEL validation for RootCA in ServersTransport Co-authored-by: Kevin Pollet --- .../dynamic-configuration/kubernetes-crd-definition-v1.yml | 4 ++-- .../dynamic-configuration/traefik.io_serverstransports.yaml | 2 +- .../traefik.io_serverstransporttcps.yaml | 2 +- integration/fixtures/k8s/01-traefik-crd.yml | 4 ++-- .../kubernetes/crd/traefikio/v1alpha1/serverstransport.go | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml b/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml index 7b3ed1bec..4a471058f 100644 --- a/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml +++ b/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml @@ -2273,7 +2273,7 @@ spec: type: object x-kubernetes-validations: - message: RootCA cannot have both Secret and ConfigMap defined. - rule: has(self.secret) && has(self.configMap) + rule: '!has(self.secret) || !has(self.configMap)' type: array rootCAsSecrets: description: |- @@ -2418,7 +2418,7 @@ spec: type: object x-kubernetes-validations: - message: RootCA cannot have both Secret and ConfigMap defined. - rule: has(self.secret) && has(self.configMap) + rule: '!has(self.secret) || !has(self.configMap)' type: array rootCAsSecrets: description: |- diff --git a/docs/content/reference/dynamic-configuration/traefik.io_serverstransports.yaml b/docs/content/reference/dynamic-configuration/traefik.io_serverstransports.yaml index 38a27840a..99d820da2 100644 --- a/docs/content/reference/dynamic-configuration/traefik.io_serverstransports.yaml +++ b/docs/content/reference/dynamic-configuration/traefik.io_serverstransports.yaml @@ -134,7 +134,7 @@ spec: type: object x-kubernetes-validations: - message: RootCA cannot have both Secret and ConfigMap defined. - rule: has(self.secret) && has(self.configMap) + rule: '!has(self.secret) || !has(self.configMap)' type: array rootCAsSecrets: description: |- diff --git a/docs/content/reference/dynamic-configuration/traefik.io_serverstransporttcps.yaml b/docs/content/reference/dynamic-configuration/traefik.io_serverstransporttcps.yaml index d2ffffed6..35f5dab93 100644 --- a/docs/content/reference/dynamic-configuration/traefik.io_serverstransporttcps.yaml +++ b/docs/content/reference/dynamic-configuration/traefik.io_serverstransporttcps.yaml @@ -110,7 +110,7 @@ spec: type: object x-kubernetes-validations: - message: RootCA cannot have both Secret and ConfigMap defined. - rule: has(self.secret) && has(self.configMap) + rule: '!has(self.secret) || !has(self.configMap)' type: array rootCAsSecrets: description: |- diff --git a/integration/fixtures/k8s/01-traefik-crd.yml b/integration/fixtures/k8s/01-traefik-crd.yml index 7b3ed1bec..4a471058f 100644 --- a/integration/fixtures/k8s/01-traefik-crd.yml +++ b/integration/fixtures/k8s/01-traefik-crd.yml @@ -2273,7 +2273,7 @@ spec: type: object x-kubernetes-validations: - message: RootCA cannot have both Secret and ConfigMap defined. - rule: has(self.secret) && has(self.configMap) + rule: '!has(self.secret) || !has(self.configMap)' type: array rootCAsSecrets: description: |- @@ -2418,7 +2418,7 @@ spec: type: object x-kubernetes-validations: - message: RootCA cannot have both Secret and ConfigMap defined. - rule: has(self.secret) && has(self.configMap) + rule: '!has(self.secret) || !has(self.configMap)' type: array rootCAsSecrets: description: |- diff --git a/pkg/provider/kubernetes/crd/traefikio/v1alpha1/serverstransport.go b/pkg/provider/kubernetes/crd/traefikio/v1alpha1/serverstransport.go index 7f74c6397..5c7773895 100644 --- a/pkg/provider/kubernetes/crd/traefikio/v1alpha1/serverstransport.go +++ b/pkg/provider/kubernetes/crd/traefikio/v1alpha1/serverstransport.go @@ -81,7 +81,7 @@ type ForwardingTimeouts struct { // RootCA defines a reference to a Secret or a ConfigMap that holds a CA certificate. // If both a Secret and a ConfigMap reference are defined, the Secret reference takes precedence. -// +kubebuilder:validation:XValidation:rule="has(self.secret) && has(self.configMap)",message="RootCA cannot have both Secret and ConfigMap defined." +// +kubebuilder:validation:XValidation:rule="!has(self.secret) || !has(self.configMap)",message="RootCA cannot have both Secret and ConfigMap defined." type RootCA struct { // Secret defines the name of a Secret that holds a CA certificate. // The referenced Secret must contain a certificate under either a tls.ca or a ca.crt key. From b669981018476eb75666d201254a3a0bb868913f Mon Sep 17 00:00:00 2001 From: Romain Date: Fri, 23 May 2025 14:56:05 +0200 Subject: [PATCH 06/21] Fix panic for ingress with backend resource Co-authored-by: Kevin Pollet --- .../Ingress-with-backend-resource.yml | 18 ++++++++++++++ .../fixtures/Ingress-without-backend.yml | 15 ++++++++++++ pkg/provider/kubernetes/ingress/kubernetes.go | 20 +++++++++------- .../kubernetes/ingress/kubernetes_test.go | 24 +++++++++++++++++++ 4 files changed, 68 insertions(+), 9 deletions(-) create mode 100644 pkg/provider/kubernetes/ingress/fixtures/Ingress-with-backend-resource.yml create mode 100644 pkg/provider/kubernetes/ingress/fixtures/Ingress-without-backend.yml diff --git a/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-backend-resource.yml b/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-backend-resource.yml new file mode 100644 index 000000000..fa113eae8 --- /dev/null +++ b/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-backend-resource.yml @@ -0,0 +1,18 @@ +kind: Ingress +apiVersion: networking.k8s.io/v1 +metadata: + name: "" + namespace: testing + +spec: + rules: + - host: traefik.tchouk + http: + paths: + - path: /bar + backend: + resource: + kind: Service + name: service1 + pathType: Prefix + diff --git a/pkg/provider/kubernetes/ingress/fixtures/Ingress-without-backend.yml b/pkg/provider/kubernetes/ingress/fixtures/Ingress-without-backend.yml new file mode 100644 index 000000000..58a4cb4e6 --- /dev/null +++ b/pkg/provider/kubernetes/ingress/fixtures/Ingress-without-backend.yml @@ -0,0 +1,15 @@ +kind: Ingress +apiVersion: networking.k8s.io/v1 +metadata: + name: "" + namespace: testing + +spec: + rules: + - host: traefik.tchouk + http: + paths: + - path: /bar + backend: {} + pathType: Prefix + diff --git a/pkg/provider/kubernetes/ingress/kubernetes.go b/pkg/provider/kubernetes/ingress/kubernetes.go index f83b37d45..77184492b 100644 --- a/pkg/provider/kubernetes/ingress/kubernetes.go +++ b/pkg/provider/kubernetes/ingress/kubernetes.go @@ -300,6 +300,17 @@ func (p *Provider) loadConfigurationFromIngresses(ctx context.Context, client Cl } for _, pa := range rule.HTTP.Paths { + if pa.Backend.Resource != nil { + // https://kubernetes.io/docs/concepts/services-networking/ingress/#resource-backend + log.FromContext(ctxIngress).Error("Resource backends are not supported") + continue + } + + if pa.Backend.Service == nil { + log.FromContext(ctxIngress).Error("Missing service definition") + continue + } + service, err := p.loadService(client, ingress.Namespace, pa.Backend) if err != nil { log.FromContext(ctxIngress). @@ -516,15 +527,6 @@ func getTLSConfig(tlsConfigs map[string]*tls.CertAndStores) []*tls.CertAndStores } func (p *Provider) loadService(client Client, namespace string, backend netv1.IngressBackend) (*dynamic.Service, error) { - if backend.Resource != nil { - // https://kubernetes.io/docs/concepts/services-networking/ingress/#resource-backend - return nil, errors.New("resource backends are not supported") - } - - if backend.Service == nil { - return nil, errors.New("missing service definition") - } - service, exists, err := client.GetService(namespace, backend.Service.Name) if err != nil { return nil, err diff --git a/pkg/provider/kubernetes/ingress/kubernetes_test.go b/pkg/provider/kubernetes/ingress/kubernetes_test.go index e999e14b7..8780d83c7 100644 --- a/pkg/provider/kubernetes/ingress/kubernetes_test.go +++ b/pkg/provider/kubernetes/ingress/kubernetes_test.go @@ -469,6 +469,30 @@ func TestLoadConfigurationFromIngresses(t *testing.T) { }, }, }, + { + desc: "Ingress with backend resource", + allowEmptyServices: true, + expected: &dynamic.Configuration{ + TCP: &dynamic.TCPConfiguration{}, + HTTP: &dynamic.HTTPConfiguration{ + Middlewares: map[string]*dynamic.Middleware{}, + Routers: map[string]*dynamic.Router{}, + Services: map[string]*dynamic.Service{}, + }, + }, + }, + { + desc: "Ingress without backend", + allowEmptyServices: true, + expected: &dynamic.Configuration{ + TCP: &dynamic.TCPConfiguration{}, + HTTP: &dynamic.HTTPConfiguration{ + Middlewares: map[string]*dynamic.Middleware{}, + Routers: map[string]*dynamic.Router{}, + Services: map[string]*dynamic.Service{}, + }, + }, + }, { desc: "Ingress with one service without endpoint", expected: &dynamic.Configuration{ From 08d5dfee0164aa54dd44a467870042e18e8d3f00 Mon Sep 17 00:00:00 2001 From: Kevin Pollet Date: Fri, 23 May 2025 15:10:05 +0200 Subject: [PATCH 07/21] Normalize request path Co-authored-by: Romain --- docs/content/migration/v2.md | 29 +++ go.mod | 2 +- go.sum | 4 +- integration/simple_test.go | 12 + pkg/muxer/http/mux.go | 2 +- pkg/server/server_entrypoint_tcp.go | 186 +++++++++++++-- pkg/server/server_entrypoint_tcp_test.go | 286 +++++++++++++++++++++++ 7 files changed, 504 insertions(+), 17 deletions(-) diff --git a/docs/content/migration/v2.md b/docs/content/migration/v2.md index cfffaadfd..a2106a5cd 100644 --- a/docs/content/migration/v2.md +++ b/docs/content/migration/v2.md @@ -674,3 +674,32 @@ it can lead to unsafe routing when the `sanitizePath` option is set to `false`. Setting the `sanitizePath` option to `false` is not safe. Ensure every request is properly url encoded instead. + +## v2.11.25 + +### Request Path Normalization + +Since `v2.11.25`, the request path is now normalized by decoding unreserved characters in the request path, +and also uppercasing the percent-encoded characters. +This follows [RFC 3986 percent-encoding normalization](https://datatracker.ietf.org/doc/html/rfc3986#section-6.2.2.2), +and [RFC 3986 case normalization](https://datatracker.ietf.org/doc/html/rfc3986#section-6.2.2.1). + +The normalization happens before the request path is sanitized, +and cannot be disabled. +This notably helps with encoded dots characters (which are unreserved characters) to be sanitized properly. + +### Routing Path + +Since `v2.11.25`, the reserved characters [(as per RFC 3986)](https://datatracker.ietf.org/doc/html/rfc3986#section-2.2) are kept encoded in the request path when matching the router rules. +Those characters, when decoded, change the meaning of the request path for routing purposes, +and Traefik now keeps them encoded to avoid any ambiguity. + +### Request Path Matching Examples + +| Request Path | Router Rule | Traefik v2.11.24 | Traefik v2.11.25 | +|-------------------|------------------------|------------------|------------------| +| `/foo%2Fbar` | PathPrefix(`/foo/bar`) | Match | No match | +| `/foo/../bar` | PathPrefix(`/foo`) | No match | No match | +| `/foo/../bar` | PathPrefix(`/bar`) | Match | Match | +| `/foo/%2E%2E/bar` | PathPrefix(`/foo`) | Match | No match | +| `/foo/%2E%2E/bar` | PathPrefix(`/bar`) | No match | Match | diff --git a/go.mod b/go.mod index fb7d17462..970fd883e 100644 --- a/go.mod +++ b/go.mod @@ -391,7 +391,7 @@ require ( // Containous forks replace ( github.com/abbot/go-http-auth => github.com/containous/go-http-auth v0.4.1-0.20200324110947-a37a7636d23e - github.com/gorilla/mux => github.com/containous/mux v0.0.0-20220627093034-b2dd784e613f + github.com/gorilla/mux => github.com/containous/mux v0.0.0-20250523120546-41b6ec3aed59 github.com/mailgun/minheap => github.com/containous/minheap v0.0.0-20190809180810-6e71eb837595 ) diff --git a/go.sum b/go.sum index 632f28cbb..3a0bcab1d 100644 --- a/go.sum +++ b/go.sum @@ -298,8 +298,8 @@ github.com/containous/go-http-auth v0.4.1-0.20200324110947-a37a7636d23e h1:D+uTE github.com/containous/go-http-auth v0.4.1-0.20200324110947-a37a7636d23e/go.mod h1:s8kLgBQolDbsJOPVIGCEEv9zGAKUUf/685Gi0Qqg8z8= github.com/containous/minheap v0.0.0-20190809180810-6e71eb837595 h1:aPspFRO6b94To3gl4yTDOEtpjFwXI7V2W+z0JcNljQ4= github.com/containous/minheap v0.0.0-20190809180810-6e71eb837595/go.mod h1:+lHFbEasIiQVGzhVDVw/cn0ZaOzde2OwNncp1NhXV4c= -github.com/containous/mux v0.0.0-20220627093034-b2dd784e613f h1:1uEtynq2C0ljy3630jt7EAxg8jZY2gy6YHdGwdqEpWw= -github.com/containous/mux v0.0.0-20220627093034-b2dd784e613f/go.mod h1:z8WW7n06n8/1xF9Jl9WmuDeZuHAhfL+bwarNjsciwwg= +github.com/containous/mux v0.0.0-20250523120546-41b6ec3aed59 h1:lJUOWjGohYjLKEfAz2nyI/dpzfKNPQLi5GLH7aaOZkw= +github.com/containous/mux v0.0.0-20250523120546-41b6ec3aed59/go.mod h1:z8WW7n06n8/1xF9Jl9WmuDeZuHAhfL+bwarNjsciwwg= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= diff --git a/integration/simple_test.go b/integration/simple_test.go index b48e41081..316c78797 100644 --- a/integration/simple_test.go +++ b/integration/simple_test.go @@ -1429,6 +1429,18 @@ func (s *SimpleSuite) TestSanitizePath() { target: "127.0.0.1:8000", expected: http.StatusFound, }, + { + desc: "Implicit encoded dot dots call to the route with a middleware", + request: "GET /without/%2E%2E/with HTTP/1.1\r\nHost: other.localhost\r\n\r\n", + target: "127.0.0.1:8000", + expected: http.StatusFound, + }, + { + desc: "Implicit with encoded unreserved character call to the route with a middleware", + request: "GET /%77ith HTTP/1.1\r\nHost: other.localhost\r\n\r\n", + target: "127.0.0.1:8000", + expected: http.StatusFound, + }, { desc: "Explicit call to the route with a middleware, and disable path sanitization", request: "GET /with HTTP/1.1\r\nHost: other.localhost\r\n\r\n", diff --git a/pkg/muxer/http/mux.go b/pkg/muxer/http/mux.go index ca011b79c..db398216e 100644 --- a/pkg/muxer/http/mux.go +++ b/pkg/muxer/http/mux.go @@ -48,7 +48,7 @@ func NewMuxer() (*Muxer, error) { } return &Muxer{ - Router: mux.NewRouter().SkipClean(true), + Router: mux.NewRouter().UseRoutingPath().SkipClean(true), parser: parser, }, nil } diff --git a/pkg/server/server_entrypoint_tcp.go b/pkg/server/server_entrypoint_tcp.go index b3b42edb7..ea1f1975b 100644 --- a/pkg/server/server_entrypoint_tcp.go +++ b/pkg/server/server_entrypoint_tcp.go @@ -16,6 +16,7 @@ import ( "time" "github.com/containous/alice" + "github.com/gorilla/mux" "github.com/pires/go-proxyproto" "github.com/sirupsen/logrus" "github.com/traefik/traefik/v2/pkg/config/static" @@ -571,18 +572,6 @@ func createHTTPServer(ctx context.Context, ln net.Listener, configuration *stati return nil, err } - if configuration.HTTP.SanitizePath != nil && *configuration.HTTP.SanitizePath { - // sanitizePath is used to clean the URL path by removing /../, /./ and duplicate slash sequences, - // to make sure the path is interpreted by the backends as it is evaluated inside rule matchers. - handler = sanitizePath(handler) - } - - if configuration.HTTP.EncodeQuerySemicolons { - handler = encodeQuerySemicolons(handler) - } else { - handler = http.AllowQuerySemicolons(handler) - } - debugConnection := os.Getenv(debugConnectionEnv) != "" if debugConnection || (configuration.Transport != nil && (configuration.Transport.KeepAliveMaxTime > 0 || configuration.Transport.KeepAliveMaxRequests > 0)) { handler = newKeepAliveMiddleware(handler, configuration.Transport.KeepAliveMaxRequests, configuration.Transport.KeepAliveMaxTime) @@ -594,6 +583,22 @@ func createHTTPServer(ctx context.Context, ln net.Listener, configuration *stati }) } + if configuration.HTTP.EncodeQuerySemicolons { + handler = encodeQuerySemicolons(handler) + } else { + handler = http.AllowQuerySemicolons(handler) + } + + handler = routingPath(handler) + + // Note that the Path sanitization has to be done after the path normalization, + // hence the wrapping has to be done before the normalize path wrapping. + if configuration.HTTP.SanitizePath != nil && *configuration.HTTP.SanitizePath { + handler = sanitizePath(handler) + } + + handler = normalizePath(handler) + handler = denyFragment(handler) serverHTTP := &http.Server{ @@ -721,7 +726,7 @@ func denyFragment(h http.Handler) http.Handler { }) } -// sanitizePath removes the "..", "." and duplicate slash segments from the URL. +// sanitizePath removes the "..", "." and duplicate slash segments from the URL according to https://datatracker.ietf.org/doc/html/rfc3986#section-6.2.2.3. // It cleans the request URL Path and RawPath, and updates the request URI. func sanitizePath(h http.Handler) http.Handler { return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { @@ -737,3 +742,158 @@ func sanitizePath(h http.Handler) http.Handler { h.ServeHTTP(rw, r2) }) } + +// unreservedCharacters contains the mapping of the percent-encoded form to the ASCII form +// of the unreserved characters according to https://datatracker.ietf.org/doc/html/rfc3986#section-2.3. +var unreservedCharacters = map[string]rune{ + "%41": 'A', "%42": 'B', "%43": 'C', "%44": 'D', "%45": 'E', "%46": 'F', + "%47": 'G', "%48": 'H', "%49": 'I', "%4A": 'J', "%4B": 'K', "%4C": 'L', + "%4D": 'M', "%4E": 'N', "%4F": 'O', "%50": 'P', "%51": 'Q', "%52": 'R', + "%53": 'S', "%54": 'T', "%55": 'U', "%56": 'V', "%57": 'W', "%58": 'X', + "%59": 'Y', "%5A": 'Z', + + "%61": 'a', "%62": 'b', "%63": 'c', "%64": 'd', "%65": 'e', "%66": 'f', + "%67": 'g', "%68": 'h', "%69": 'i', "%6A": 'j', "%6B": 'k', "%6C": 'l', + "%6D": 'm', "%6E": 'n', "%6F": 'o', "%70": 'p', "%71": 'q', "%72": 'r', + "%73": 's', "%74": 't', "%75": 'u', "%76": 'v', "%77": 'w', "%78": 'x', + "%79": 'y', "%7A": 'z', + + "%30": '0', "%31": '1', "%32": '2', "%33": '3', "%34": '4', + "%35": '5', "%36": '6', "%37": '7', "%38": '8', "%39": '9', + + "%2D": '-', "%2E": '.', "%5F": '_', "%7E": '~', +} + +// normalizePath removes from the RawPath unreserved percent-encoded characters as they are equivalent to their non-encoded +// form according to https://datatracker.ietf.org/doc/html/rfc3986#section-2.3 and capitalizes percent-encoded characters +// according to https://datatracker.ietf.org/doc/html/rfc3986#section-6.2.2.1. +func normalizePath(h http.Handler) http.Handler { + return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + rawPath := req.URL.RawPath + + // When the RawPath is empty the encoded form of the Path is equivalent to the original request Path. + // Thus, the normalization is not needed as no unreserved characters were encoded and the encoded version + // of Path obtained with URL.EscapedPath contains only percent-encoded characters in upper case. + if rawPath == "" { + h.ServeHTTP(rw, req) + return + } + + var normalizedRawPathBuilder strings.Builder + for i := 0; i < len(rawPath); i++ { + if rawPath[i] != '%' { + normalizedRawPathBuilder.WriteString(string(rawPath[i])) + continue + } + + // This should never happen as the standard library will reject requests containing invalid percent-encodings. + // This discards URLs with a percent character at the end. + if i+2 >= len(rawPath) { + rw.WriteHeader(http.StatusBadRequest) + return + } + + encodedCharacter := strings.ToUpper(rawPath[i : i+3]) + if r, unreserved := unreservedCharacters[encodedCharacter]; unreserved { + normalizedRawPathBuilder.WriteRune(r) + } else { + normalizedRawPathBuilder.WriteString(encodedCharacter) + } + + i += 2 + } + + normalizedRawPath := normalizedRawPathBuilder.String() + + // We do not have to alter the request URL as the original RawPath is already normalized. + if normalizedRawPath == rawPath { + h.ServeHTTP(rw, req) + return + } + + r2 := new(http.Request) + *r2 = *req + + // Decoding unreserved characters only alter the RAW version of the URL, + // as unreserved percent-encoded characters are equivalent to their non encoded form. + r2.URL.RawPath = normalizedRawPath + + // Because the reverse proxy director is building query params from RequestURI it needs to be updated as well. + r2.RequestURI = r2.URL.RequestURI() + + h.ServeHTTP(rw, r2) + }) +} + +// reservedCharacters contains the mapping of the percent-encoded form to the ASCII form +// of the reserved characters according to https://datatracker.ietf.org/doc/html/rfc3986#section-2.2. +// By extension to https://datatracker.ietf.org/doc/html/rfc3986#section-2.1 the percent character is also considered a reserved character. +// Because decoding the percent character would change the meaning of the URL. +var reservedCharacters = map[string]rune{ + "%3A": ':', + "%2F": '/', + "%3F": '?', + "%23": '#', + "%5B": '[', + "%5D": ']', + "%40": '@', + "%21": '!', + "%24": '$', + "%26": '&', + "%27": '\'', + "%28": '(', + "%29": ')', + "%2A": '*', + "%2B": '+', + "%2C": ',', + "%3B": ';', + "%3D": '=', + "%25": '%', +} + +// routingPath decodes non-allowed characters in the EscapedPath and stores it in the context to be able to use it for routing. +// This allows using the decoded version of the non-allowed characters in the routing rules for a better UX. +// For example, the rule PathPrefix(`/foo bar`) will match the following request path `/foo%20bar`. +func routingPath(h http.Handler) http.Handler { + return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + escapedPath := req.URL.EscapedPath() + + var routingPathBuilder strings.Builder + for i := 0; i < len(escapedPath); i++ { + if escapedPath[i] != '%' { + routingPathBuilder.WriteString(string(escapedPath[i])) + continue + } + + // This should never happen as the standard library will reject requests containing invalid percent-encodings. + // This discards URLs with a percent character at the end. + if i+2 >= len(escapedPath) { + rw.WriteHeader(http.StatusBadRequest) + return + } + + encodedCharacter := escapedPath[i : i+3] + if _, reserved := reservedCharacters[encodedCharacter]; reserved { + routingPathBuilder.WriteString(encodedCharacter) + } else { + // This should never happen as the standard library will reject requests containing invalid percent-encodings. + decodedCharacter, err := url.PathUnescape(encodedCharacter) + if err != nil { + rw.WriteHeader(http.StatusBadRequest) + return + } + routingPathBuilder.WriteString(decodedCharacter) + } + + i += 2 + } + + h.ServeHTTP(rw, req.WithContext( + context.WithValue( + req.Context(), + mux.RoutingPathKey, + routingPathBuilder.String(), + ), + )) + }) +} diff --git a/pkg/server/server_entrypoint_tcp_test.go b/pkg/server/server_entrypoint_tcp_test.go index 98066a4ca..e007fc325 100644 --- a/pkg/server/server_entrypoint_tcp_test.go +++ b/pkg/server/server_entrypoint_tcp_test.go @@ -13,10 +13,12 @@ import ( "testing" "time" + "github.com/gorilla/mux" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ptypes "github.com/traefik/paerser/types" "github.com/traefik/traefik/v2/pkg/config/static" + "github.com/traefik/traefik/v2/pkg/middlewares/requestdecorator" tcprouter "github.com/traefik/traefik/v2/pkg/server/router/tcp" "github.com/traefik/traefik/v2/pkg/tcp" "golang.org/x/net/http2" @@ -424,3 +426,287 @@ func TestSanitizePath(t *testing.T) { }) } } + +func TestNormalizePath(t *testing.T) { + unreservedDecoded := "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~" + unreserved := []string{ + "%41", "%42", "%43", "%44", "%45", "%46", "%47", "%48", "%49", "%4A", "%4B", "%4C", "%4D", "%4E", "%4F", "%50", "%51", "%52", "%53", "%54", "%55", "%56", "%57", "%58", "%59", "%5A", + "%61", "%62", "%63", "%64", "%65", "%66", "%67", "%68", "%69", "%6A", "%6B", "%6C", "%6D", "%6E", "%6F", "%70", "%71", "%72", "%73", "%74", "%75", "%76", "%77", "%78", "%79", "%7A", + "%30", "%31", "%32", "%33", "%34", "%35", "%36", "%37", "%38", "%39", + "%2D", "%2E", "%5F", "%7E", + } + + reserved := []string{ + "%3A", "%2F", "%3F", "%23", "%5B", "%5D", "%40", "%21", "%24", "%26", "%27", "%28", "%29", "%2A", "%2B", "%2C", "%3B", "%3D", "%25", + } + reservedJoined := strings.Join(reserved, "") + + unallowedCharacter := "%0a" // line feed + unallowedCharacterUpperCased := "%0A" // line feed upper case + + var callCount int + handler := normalizePath(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + callCount++ + + wantRawPath := "/" + unreservedDecoded + reservedJoined + unallowedCharacterUpperCased + assert.Equal(t, wantRawPath, r.URL.RawPath) + })) + + req := httptest.NewRequest(http.MethodGet, "http://foo/"+strings.Join(unreserved, "")+reservedJoined+unallowedCharacter, http.NoBody) + res := httptest.NewRecorder() + + handler.ServeHTTP(res, req) + + assert.Equal(t, http.StatusOK, res.Code) + assert.Equal(t, 1, callCount) +} + +func TestNormalizePath_malformedPercentEncoding(t *testing.T) { + tests := []struct { + desc string + path string + wantErr bool + }{ + { + desc: "well formed path", + path: "/%20", + }, + { + desc: "percent sign at the end", + path: "/%", + wantErr: true, + }, + { + desc: "incomplete percent encoding at the end", + path: "/%f", + wantErr: true, + }, + } + + for _, test := range tests { + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + var callCount int + handler := normalizePath(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + callCount++ + })) + + req := httptest.NewRequest(http.MethodGet, "http://foo", http.NoBody) + req.URL.RawPath = test.path + + res := httptest.NewRecorder() + + handler.ServeHTTP(res, req) + + if test.wantErr { + assert.Equal(t, http.StatusBadRequest, res.Code) + assert.Equal(t, 0, callCount) + } else { + assert.Equal(t, http.StatusOK, res.Code) + assert.Equal(t, 1, callCount) + } + }) + } +} + +func TestRoutingPath(t *testing.T) { + tests := []struct { + desc string + path string + expRoutingPath string + expStatus int + }{ + { + desc: "unallowed percent-encoded character is decoded", + path: "/foo%20bar", + expRoutingPath: "/foo bar", + expStatus: http.StatusOK, + }, + { + desc: "reserved percent-encoded character is kept encoded", + path: "/foo%2Fbar", + expRoutingPath: "/foo%2Fbar", + expStatus: http.StatusOK, + }, + { + desc: "multiple mixed characters", + path: "/foo%20bar%2Fbaz%23qux", + expRoutingPath: "/foo bar%2Fbaz%23qux", + expStatus: http.StatusOK, + }, + } + + for _, test := range tests { + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + var gotRoute string + handler := routingPath(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + gotRoute, _ = r.Context().Value(mux.RoutingPathKey).(string) + w.WriteHeader(http.StatusOK) + })) + + req := httptest.NewRequest(http.MethodGet, "http://foo"+test.path, http.NoBody) + + res := httptest.NewRecorder() + + handler.ServeHTTP(res, req) + + assert.Equal(t, test.expStatus, res.Code) + assert.Equal(t, test.expRoutingPath, gotRoute) + }) + } +} + +// TestPathOperations tests the whole behavior of normalizePath, sanitizePath, and routingPath combined through the use of the createHTTPServer func. +// It aims to guarantee the server entrypoint handler is secure regarding a large variety of cases that could lead to path traversal attacks. +func TestPathOperations(t *testing.T) { + // Create a listener for the server. + ln, err := net.Listen("tcp", "127.0.0.1:0") + require.NoError(t, err) + t.Cleanup(func() { + _ = ln.Close() + }) + + // Define the server configuration. + configuration := &static.EntryPoint{} + configuration.SetDefaults() + + // Create the HTTP server using createHTTPServer. + server, err := createHTTPServer(context.Background(), ln, configuration, false, requestdecorator.New(nil)) + require.NoError(t, err) + + server.Switcher.UpdateHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Path", r.URL.Path) + w.Header().Set("RawPath", r.URL.EscapedPath()) + w.Header().Set("RoutingPath", r.Context().Value(mux.RoutingPathKey).(string)) + w.WriteHeader(http.StatusOK) + })) + + go func() { + // server is expected to return an error if the listener is closed. + _ = server.Server.Serve(ln) + }() + + client := http.Client{ + Transport: &http.Transport{ + ResponseHeaderTimeout: 1 * time.Second, + }, + } + + tests := []struct { + desc string + rawPath string + expectedPath string + expectedRaw string + expectedRoutingPath string + expectedStatus int + }{ + { + desc: "normalize and sanitize path", + rawPath: "/a/../b/%41%42%43//%2f/", + expectedPath: "/b/ABC///", + expectedRaw: "/b/ABC/%2F/", + expectedRoutingPath: "/b/ABC/%2F/", + expectedStatus: http.StatusOK, + }, + { + desc: "path with traversal attempt", + rawPath: "/../../b/", + expectedPath: "/b/", + expectedRaw: "/b/", + expectedRoutingPath: "/b/", + expectedStatus: http.StatusOK, + }, + { + desc: "path with multiple traversal attempts", + rawPath: "/a/../../b/../c/", + expectedPath: "/c/", + expectedRaw: "/c/", + expectedRoutingPath: "/c/", + expectedStatus: http.StatusOK, + }, + { + desc: "path with mixed traversal and valid segments", + rawPath: "/a/../b/./c/../d/", + expectedPath: "/b/d/", + expectedRaw: "/b/d/", + expectedRoutingPath: "/b/d/", + expectedStatus: http.StatusOK, + }, + { + desc: "path with trailing slash and traversal", + rawPath: "/a/b/../", + expectedPath: "/a/", + expectedRaw: "/a/", + expectedRoutingPath: "/a/", + expectedStatus: http.StatusOK, + }, + { + desc: "path with encoded traversal sequences", + rawPath: "/a/%2E%2E/%2E%2E/b/", + expectedPath: "/b/", + expectedRaw: "/b/", + expectedRoutingPath: "/b/", + expectedStatus: http.StatusOK, + }, + { + desc: "path with over-encoded traversal sequences", + rawPath: "/a/%252E%252E/%252E%252E/b/", + expectedPath: "/a/%2E%2E/%2E%2E/b/", + expectedRaw: "/a/%252E%252E/%252E%252E/b/", + expectedRoutingPath: "/a/%252E%252E/%252E%252E/b/", + expectedStatus: http.StatusOK, + }, + { + desc: "routing path with unallowed percent-encoded character", + rawPath: "/foo%20bar", + expectedPath: "/foo bar", + expectedRaw: "/foo%20bar", + expectedRoutingPath: "/foo bar", + expectedStatus: http.StatusOK, + }, + { + desc: "routing path with reserved percent-encoded character", + rawPath: "/foo%2Fbar", + expectedPath: "/foo/bar", + expectedRaw: "/foo%2Fbar", + expectedRoutingPath: "/foo%2Fbar", + expectedStatus: http.StatusOK, + }, + { + desc: "routing path with unallowed and reserved percent-encoded character", + rawPath: "/foo%20%2Fbar", + expectedPath: "/foo /bar", + expectedRaw: "/foo%20%2Fbar", + expectedRoutingPath: "/foo %2Fbar", + expectedStatus: http.StatusOK, + }, + { + desc: "path with traversal and encoded slash", + rawPath: "/a/..%2Fb/", + expectedPath: "/a/../b/", + expectedRaw: "/a/..%2Fb/", + expectedRoutingPath: "/a/..%2Fb/", + expectedStatus: http.StatusOK, + }, + } + + for _, test := range tests { + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + req, err := http.NewRequest(http.MethodGet, "http://"+ln.Addr().String()+test.rawPath, http.NoBody) + require.NoError(t, err) + + res, err := client.Do(req) + require.NoError(t, err) + + assert.Equal(t, test.expectedStatus, res.StatusCode) + assert.Equal(t, test.expectedPath, res.Header.Get("Path")) + assert.Equal(t, test.expectedRaw, res.Header.Get("RawPath")) + assert.Equal(t, test.expectedRoutingPath, res.Header.Get("RoutingPath")) + }) + } +} From ab3234e458c306a8a1c68f885b578a805e039daf Mon Sep 17 00:00:00 2001 From: aromeyer Date: Fri, 23 May 2025 15:38:04 +0200 Subject: [PATCH 08/21] Scope the rate limit counter key by source AND by middleware --- pkg/middlewares/ratelimiter/rate_limiter.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/pkg/middlewares/ratelimiter/rate_limiter.go b/pkg/middlewares/ratelimiter/rate_limiter.go index 5798fd87a..043974d47 100755 --- a/pkg/middlewares/ratelimiter/rate_limiter.go +++ b/pkg/middlewares/ratelimiter/rate_limiter.go @@ -146,7 +146,12 @@ func (rl *rateLimiter) ServeHTTP(rw http.ResponseWriter, req *http.Request) { logger.Info().Msgf("ignoring token bucket amount > 1: %d", amount) } - delay, err := rl.limiter.Allow(ctx, source) + // Each rate limiter has its own source space, + // ensuring independence between rate limiters, + // i.e., rate limit rules are only applied based on traffic + // where the rate limiter is active. + rlSource := fmt.Sprintf("%s:%s", rl.name, source) + delay, err := rl.limiter.Allow(ctx, rlSource) if err != nil { rl.logger.Error().Err(err).Msg("Could not insert/update bucket") observability.SetStatusErrorf(ctx, "Could not insert/update bucket") From 55e6d327bc56dbbe8a6b2a43332158fb9d3ebb7c Mon Sep 17 00:00:00 2001 From: Josh McKinney Date: Mon, 26 May 2025 06:50:04 -0700 Subject: [PATCH 09/21] acme.md: specify which file should be specified between restarts --- docs/content/https/acme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/content/https/acme.md b/docs/content/https/acme.md index d86c5d58a..ce613f964 100644 --- a/docs/content/https/acme.md +++ b/docs/content/https/acme.md @@ -13,7 +13,7 @@ You can configure Traefik to use an ACME provider (like Let's Encrypt) for autom !!! warning "Let's Encrypt and Rate Limiting" Note that Let's Encrypt API has [rate limiting](https://letsencrypt.org/docs/rate-limits). These last up to **one week**, and cannot be overridden. - When running Traefik in a container this file should be persisted across restarts. + When running Traefik in a container the `acme.json` file should be persisted across restarts. If Traefik requests new certificates each time it starts up, a crash-looping container can quickly reach Let's Encrypt's ratelimits. To configure where certificates are stored, please take a look at the [storage](#storage) configuration. From fa18c35a9a3e16b94dff8e86cf8c3c5243ca94f3 Mon Sep 17 00:00:00 2001 From: Julien Salleyron Date: Mon, 26 May 2025 17:12:08 +0200 Subject: [PATCH 10/21] Refactor new muxer to have only one parser instance --- cmd/traefik/traefik.go | 5 +- integration/simple_test.go | 8 +-- pkg/muxer/http/matcher.go | 2 +- pkg/muxer/http/matcher_test.go | 44 +++++++++---- pkg/muxer/http/matcher_v2.go | 2 +- pkg/muxer/http/matcher_v2_test.go | 40 +++++++++--- pkg/muxer/http/mux.go | 72 ++++----------------- pkg/muxer/http/mux_test.go | 12 +++- pkg/muxer/http/parser.go | 103 ++++++++++++++++++++++++++++++ pkg/server/router/router.go | 9 ++- pkg/server/router/router_test.go | 22 +++++-- pkg/server/routerfactory.go | 16 ++++- pkg/server/routerfactory_test.go | 10 ++- 13 files changed, 240 insertions(+), 105 deletions(-) create mode 100644 pkg/muxer/http/parser.go diff --git a/cmd/traefik/traefik.go b/cmd/traefik/traefik.go index 61f8e8217..77930754b 100644 --- a/cmd/traefik/traefik.go +++ b/cmd/traefik/traefik.go @@ -301,7 +301,10 @@ func setupServer(staticConfiguration *static.Configuration) (*server.Server, err // Router factory - routerFactory := server.NewRouterFactory(*staticConfiguration, managerFactory, tlsManager, observabilityMgr, pluginBuilder, dialerManager) + routerFactory, err := server.NewRouterFactory(*staticConfiguration, managerFactory, tlsManager, observabilityMgr, pluginBuilder, dialerManager) + if err != nil { + return nil, fmt.Errorf("creating router factory: %w", err) + } // Watcher diff --git a/integration/simple_test.go b/integration/simple_test.go index b2c3166c6..4a70bd79b 100644 --- a/integration/simple_test.go +++ b/integration/simple_test.go @@ -711,11 +711,11 @@ func (s *SimpleSuite) TestWithDefaultRuleSyntax() { require.NoError(s.T(), err) // router2 has an error because it uses the wrong rule syntax (v3 instead of v2) - err = try.GetRequest("http://127.0.0.1:8080/api/http/routers/router2@file", 1*time.Second, try.BodyContains("error while parsing rule QueryRegexp(`foo`, `bar`): unsupported function: QueryRegexp")) + err = try.GetRequest("http://127.0.0.1:8080/api/http/routers/router2@file", 1*time.Second, try.BodyContains("parsing rule QueryRegexp(`foo`, `bar`): unsupported function: QueryRegexp")) require.NoError(s.T(), err) // router3 has an error because it uses the wrong rule syntax (v2 instead of v3) - err = try.GetRequest("http://127.0.0.1:8080/api/http/routers/router3@file", 1*time.Second, try.BodyContains("error while adding rule PathPrefix: unexpected number of parameters; got 2, expected one of [1]")) + err = try.GetRequest("http://127.0.0.1:8080/api/http/routers/router3@file", 1*time.Second, try.BodyContains("adding rule PathPrefix: unexpected number of parameters; got 2, expected one of [1]")) require.NoError(s.T(), err) } @@ -741,11 +741,11 @@ func (s *SimpleSuite) TestWithoutDefaultRuleSyntax() { require.NoError(s.T(), err) // router2 has an error because it uses the wrong rule syntax (v3 instead of v2) - err = try.GetRequest("http://127.0.0.1:8080/api/http/routers/router2@file", 1*time.Second, try.BodyContains("error while adding rule PathPrefix: unexpected number of parameters; got 2, expected one of [1]")) + err = try.GetRequest("http://127.0.0.1:8080/api/http/routers/router2@file", 1*time.Second, try.BodyContains("adding rule PathPrefix: unexpected number of parameters; got 2, expected one of [1]")) require.NoError(s.T(), err) // router2 has an error because it uses the wrong rule syntax (v2 instead of v3) - err = try.GetRequest("http://127.0.0.1:8080/api/http/routers/router3@file", 1*time.Second, try.BodyContains("error while parsing rule QueryRegexp(`foo`, `bar`): unsupported function: QueryRegexp")) + err = try.GetRequest("http://127.0.0.1:8080/api/http/routers/router3@file", 1*time.Second, try.BodyContains("parsing rule QueryRegexp(`foo`, `bar`): unsupported function: QueryRegexp")) require.NoError(s.T(), err) } diff --git a/pkg/muxer/http/matcher.go b/pkg/muxer/http/matcher.go index 200f3fc5d..98ebc8553 100644 --- a/pkg/muxer/http/matcher.go +++ b/pkg/muxer/http/matcher.go @@ -13,7 +13,7 @@ import ( "github.com/traefik/traefik/v3/pkg/middlewares/requestdecorator" ) -var httpFuncs = map[string]func(*matchersTree, ...string) error{ +var httpFuncs = matcherBuilderFuncs{ "ClientIP": expectNParameters(clientIP, 1), "Method": expectNParameters(method, 1), "Host": expectNParameters(host, 1), diff --git a/pkg/muxer/http/matcher_test.go b/pkg/muxer/http/matcher_test.go index f3a8f262c..35550ecaf 100644 --- a/pkg/muxer/http/matcher_test.go +++ b/pkg/muxer/http/matcher_test.go @@ -69,9 +69,11 @@ func TestClientIPMatcher(t *testing.T) { t.Parallel() handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}) - muxer, err := NewMuxer() + parser, err := NewSyntaxParser() require.NoError(t, err) + muxer := NewMuxer(parser) + err = muxer.AddRoute(test.rule, "", 0, handler) if test.expectedError { require.Error(t, err) @@ -142,9 +144,11 @@ func TestMethodMatcher(t *testing.T) { t.Parallel() handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}) - muxer, err := NewMuxer() + parser, err := NewSyntaxParser() require.NoError(t, err) + muxer := NewMuxer(parser) + err = muxer.AddRoute(test.rule, "", 0, handler) if test.expectedError { require.Error(t, err) @@ -259,9 +263,11 @@ func TestHostMatcher(t *testing.T) { t.Parallel() handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}) - muxer, err := NewMuxer() + parser, err := NewSyntaxParser() require.NoError(t, err) + muxer := NewMuxer(parser) + err = muxer.AddRoute(test.rule, "", 0, handler) if test.expectedError { require.Error(t, err) @@ -358,9 +364,11 @@ func TestHostRegexpMatcher(t *testing.T) { t.Parallel() handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}) - muxer, err := NewMuxer() + parser, err := NewSyntaxParser() require.NoError(t, err) + muxer := NewMuxer(parser) + err = muxer.AddRoute(test.rule, "", 0, handler) if test.expectedError { require.Error(t, err) @@ -431,9 +439,11 @@ func TestPathMatcher(t *testing.T) { t.Parallel() handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}) - muxer, err := NewMuxer() + parser, err := NewSyntaxParser() require.NoError(t, err) + muxer := NewMuxer(parser) + err = muxer.AddRoute(test.rule, "", 0, handler) if test.expectedError { require.Error(t, err) @@ -523,9 +533,11 @@ func TestPathRegexpMatcher(t *testing.T) { t.Parallel() handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}) - muxer, err := NewMuxer() + parser, err := NewSyntaxParser() require.NoError(t, err) + muxer := NewMuxer(parser) + err = muxer.AddRoute(test.rule, "", 0, handler) if test.expectedError { require.Error(t, err) @@ -594,9 +606,11 @@ func TestPathPrefixMatcher(t *testing.T) { t.Parallel() handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}) - muxer, err := NewMuxer() + parser, err := NewSyntaxParser() require.NoError(t, err) + muxer := NewMuxer(parser) + err = muxer.AddRoute(test.rule, "", 0, handler) if test.expectedError { require.Error(t, err) @@ -680,9 +694,11 @@ func TestHeaderMatcher(t *testing.T) { t.Parallel() handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}) - muxer, err := NewMuxer() + parser, err := NewSyntaxParser() require.NoError(t, err) + muxer := NewMuxer(parser) + err = muxer.AddRoute(test.rule, "", 0, handler) if test.expectedError { require.Error(t, err) @@ -787,9 +803,11 @@ func TestHeaderRegexpMatcher(t *testing.T) { t.Parallel() handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}) - muxer, err := NewMuxer() + parser, err := NewSyntaxParser() require.NoError(t, err) + muxer := NewMuxer(parser) + err = muxer.AddRoute(test.rule, "", 0, handler) if test.expectedError { require.Error(t, err) @@ -875,9 +893,11 @@ func TestQueryMatcher(t *testing.T) { t.Parallel() handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}) - muxer, err := NewMuxer() + parser, err := NewSyntaxParser() require.NoError(t, err) + muxer := NewMuxer(parser) + err = muxer.AddRoute(test.rule, "", 0, handler) if test.expectedError { require.Error(t, err) @@ -988,9 +1008,11 @@ func TestQueryRegexpMatcher(t *testing.T) { t.Parallel() handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}) - muxer, err := NewMuxer() + parser, err := NewSyntaxParser() require.NoError(t, err) + muxer := NewMuxer(parser) + err = muxer.AddRoute(test.rule, "", 0, handler) if test.expectedError { require.Error(t, err) diff --git a/pkg/muxer/http/matcher_v2.go b/pkg/muxer/http/matcher_v2.go index d87b8e41d..b6635220f 100644 --- a/pkg/muxer/http/matcher_v2.go +++ b/pkg/muxer/http/matcher_v2.go @@ -11,7 +11,7 @@ import ( "github.com/traefik/traefik/v3/pkg/middlewares/requestdecorator" ) -var httpFuncsV2 = map[string]func(*matchersTree, ...string) error{ +var httpFuncsV2 = matcherBuilderFuncs{ "Host": hostV2, "HostHeader": hostV2, "HostRegexp": hostRegexpV2, diff --git a/pkg/muxer/http/matcher_v2_test.go b/pkg/muxer/http/matcher_v2_test.go index 6799fb7da..d05e57e75 100644 --- a/pkg/muxer/http/matcher_v2_test.go +++ b/pkg/muxer/http/matcher_v2_test.go @@ -73,9 +73,11 @@ func TestClientIPV2Matcher(t *testing.T) { t.Parallel() handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}) - muxer, err := NewMuxer() + parser, err := NewSyntaxParser() require.NoError(t, err) + muxer := NewMuxer(parser) + err = muxer.AddRoute(test.rule, "v2", 0, handler) if test.expectedError { require.Error(t, err) @@ -149,9 +151,11 @@ func TestMethodV2Matcher(t *testing.T) { t.Parallel() handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}) - muxer, err := NewMuxer() + parser, err := NewSyntaxParser() require.NoError(t, err) + muxer := NewMuxer(parser) + err = muxer.AddRoute(test.rule, "v2", 0, handler) if test.expectedError { require.Error(t, err) @@ -273,9 +277,11 @@ func TestHostV2Matcher(t *testing.T) { t.Parallel() handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}) - muxer, err := NewMuxer() + parser, err := NewSyntaxParser() require.NoError(t, err) + muxer := NewMuxer(parser) + err = muxer.AddRoute(test.rule, "v2", 0, handler) if test.expectedError { require.Error(t, err) @@ -375,9 +381,11 @@ func TestHostRegexpV2Matcher(t *testing.T) { t.Parallel() handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}) - muxer, err := NewMuxer() + parser, err := NewSyntaxParser() require.NoError(t, err) + muxer := NewMuxer(parser) + err = muxer.AddRoute(test.rule, "v2", 0, handler) if test.expectedError { require.Error(t, err) @@ -469,9 +477,11 @@ func TestPathV2Matcher(t *testing.T) { t.Parallel() handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}) - muxer, err := NewMuxer() + parser, err := NewSyntaxParser() require.NoError(t, err) + muxer := NewMuxer(parser) + err = muxer.AddRoute(test.rule, "v2", 0, handler) if test.expectedError { require.Error(t, err) @@ -561,9 +571,11 @@ func TestPathPrefixV2Matcher(t *testing.T) { t.Parallel() handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}) - muxer, err := NewMuxer() + parser, err := NewSyntaxParser() require.NoError(t, err) + muxer := NewMuxer(parser) + err = muxer.AddRoute(test.rule, "v2", 0, handler) if test.expectedError { require.Error(t, err) @@ -647,9 +659,11 @@ func TestHeadersMatcher(t *testing.T) { t.Parallel() handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}) - muxer, err := NewMuxer() + parser, err := NewSyntaxParser() require.NoError(t, err) + muxer := NewMuxer(parser) + err = muxer.AddRoute(test.rule, "v2", 0, handler) if test.expectedError { require.Error(t, err) @@ -754,9 +768,11 @@ func TestHeaderRegexpV2Matcher(t *testing.T) { t.Parallel() handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}) - muxer, err := NewMuxer() + parser, err := NewSyntaxParser() require.NoError(t, err) + muxer := NewMuxer(parser) + err = muxer.AddRoute(test.rule, "v2", 0, handler) if test.expectedError { require.Error(t, err) @@ -846,9 +862,11 @@ func TestHostRegexp(t *testing.T) { t.Parallel() handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}) - muxer, err := NewMuxer() + parser, err := NewSyntaxParser() require.NoError(t, err) + muxer := NewMuxer(parser) + err = muxer.AddRoute(test.hostExp, "v2", 0, handler) require.NoError(t, err) @@ -1513,9 +1531,11 @@ func Test_addRoute(t *testing.T) { t.Parallel() handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}) - muxer, err := NewMuxer() + parser, err := NewSyntaxParser() require.NoError(t, err) + muxer := NewMuxer(parser) + err = muxer.AddRoute(test.rule, "v2", 0, handler) if test.expectedError { require.Error(t, err) diff --git a/pkg/muxer/http/mux.go b/pkg/muxer/http/mux.go index 1d84a1433..2adad60bc 100644 --- a/pkg/muxer/http/mux.go +++ b/pkg/muxer/http/mux.go @@ -7,44 +7,27 @@ import ( "github.com/rs/zerolog/log" "github.com/traefik/traefik/v3/pkg/rules" - "github.com/vulcand/predicate" ) +type matcherBuilderFuncs map[string]matcherBuilderFunc + +type matcherBuilderFunc func(*matchersTree, ...string) error + +type MatcherFunc func(*http.Request) bool + // Muxer handles routing with rules. type Muxer struct { routes routes - parser predicate.Parser - parserV2 predicate.Parser + parser SyntaxParser defaultHandler http.Handler } // NewMuxer returns a new muxer instance. -func NewMuxer() (*Muxer, error) { - var matchers []string - for matcher := range httpFuncs { - matchers = append(matchers, matcher) - } - - parser, err := rules.NewParser(matchers) - if err != nil { - return nil, fmt.Errorf("error while creating parser: %w", err) - } - - var matchersV2 []string - for matcher := range httpFuncsV2 { - matchersV2 = append(matchersV2, matcher) - } - - parserV2, err := rules.NewParser(matchersV2) - if err != nil { - return nil, fmt.Errorf("error while creating v2 parser: %w", err) - } - +func NewMuxer(parser SyntaxParser) *Muxer { return &Muxer{ parser: parser, - parserV2: parserV2, defaultHandler: http.NotFoundHandler(), - }, nil + } } // ServeHTTP forwards the connection to the matching HTTP handler. @@ -73,36 +56,9 @@ func GetRulePriority(rule string) int { // AddRoute add a new route to the router. func (m *Muxer) AddRoute(rule string, syntax string, priority int, handler http.Handler) error { - var parse interface{} - var err error - var matcherFuncs map[string]func(*matchersTree, ...string) error - - switch syntax { - case "v2": - parse, err = m.parserV2.Parse(rule) - if err != nil { - return fmt.Errorf("error while parsing rule %s: %w", rule, err) - } - - matcherFuncs = httpFuncsV2 - default: - parse, err = m.parser.Parse(rule) - if err != nil { - return fmt.Errorf("error while parsing rule %s: %w", rule, err) - } - - matcherFuncs = httpFuncs - } - - buildTree, ok := parse.(rules.TreeBuilder) - if !ok { - return fmt.Errorf("error while parsing rule %s", rule) - } - - var matchers matchersTree - err = matchers.addRule(buildTree(), matcherFuncs) + matchers, err := m.parser.parse(syntax, rule) if err != nil { - return fmt.Errorf("error while adding rule %s: %w", rule, err) + return fmt.Errorf("error while parsing rule %s: %w", rule, err) } m.routes = append(m.routes, &route{ @@ -173,7 +129,7 @@ type matchersTree struct { // matcher is a matcher func used to match HTTP request properties. // If matcher is not nil, it means that this matcherTree is a leaf of the tree. // It is therefore mutually exclusive with left and right. - matcher func(*http.Request) bool + matcher MatcherFunc // operator to combine the evaluation of left and right leaves. operator string // Mutually exclusive with matcher. @@ -204,9 +160,7 @@ func (m *matchersTree) match(req *http.Request) bool { } } -type matcherFuncs map[string]func(*matchersTree, ...string) error - -func (m *matchersTree) addRule(rule *rules.Tree, funcs matcherFuncs) error { +func (m *matchersTree) addRule(rule *rules.Tree, funcs matcherBuilderFuncs) error { switch rule.Matcher { case "and", "or": m.operator = rule.Matcher diff --git a/pkg/muxer/http/mux_test.go b/pkg/muxer/http/mux_test.go index 710df72b7..d0b4a08bf 100644 --- a/pkg/muxer/http/mux_test.go +++ b/pkg/muxer/http/mux_test.go @@ -225,9 +225,11 @@ func TestMuxer(t *testing.T) { t.Run(test.desc, func(t *testing.T) { t.Parallel() - muxer, err := NewMuxer() + parser, err := NewSyntaxParser() require.NoError(t, err) + muxer := NewMuxer(parser) + handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}) err = muxer.AddRoute(test.rule, "", 0, handler) if test.expectedError { @@ -378,9 +380,11 @@ func Test_addRoutePriority(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { t.Parallel() - muxer, err := NewMuxer() + parser, err := NewSyntaxParser() require.NoError(t, err) + muxer := NewMuxer(parser) + for _, route := range test.cases { handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("X-From", route.xFrom) @@ -510,9 +514,11 @@ func TestEmptyHost(t *testing.T) { t.Parallel() handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}) - muxer, err := NewMuxer() + parser, err := NewSyntaxParser() require.NoError(t, err) + muxer := NewMuxer(parser) + err = muxer.AddRoute(test.rule, "", 0, handler) require.NoError(t, err) diff --git a/pkg/muxer/http/parser.go b/pkg/muxer/http/parser.go new file mode 100644 index 000000000..dc473b19e --- /dev/null +++ b/pkg/muxer/http/parser.go @@ -0,0 +1,103 @@ +package http + +import ( + "errors" + "fmt" + "maps" + "slices" + "strings" + + "github.com/traefik/traefik/v3/pkg/rules" + "github.com/vulcand/predicate" +) + +type SyntaxParser struct { + parsers map[string]*parser +} + +type Options func(map[string]matcherBuilderFuncs) + +func WithMatcher(syntax, matcherName string, builderFunc func(params ...string) (MatcherFunc, error)) Options { + return func(syntaxFuncs map[string]matcherBuilderFuncs) { + syntax = strings.ToLower(syntax) + + syntaxFuncs[syntax][matcherName] = func(tree *matchersTree, s ...string) error { + matcher, err := builderFunc(s...) + if err != nil { + return fmt.Errorf("building matcher: %w", err) + } + + tree.matcher = matcher + return nil + } + } +} + +func NewSyntaxParser(opts ...Options) (SyntaxParser, error) { + syntaxFuncs := map[string]matcherBuilderFuncs{ + "v2": httpFuncsV2, + "v3": httpFuncs, + } + + for _, opt := range opts { + opt(syntaxFuncs) + } + + parsers := map[string]*parser{} + for syntax, funcs := range syntaxFuncs { + var err error + parsers[syntax], err = newParser(funcs) + if err != nil { + return SyntaxParser{}, err + } + } + + return SyntaxParser{ + parsers: parsers, + }, nil +} + +func (s SyntaxParser) parse(syntax string, rule string) (matchersTree, error) { + parser, ok := s.parsers[syntax] + if !ok { + parser = s.parsers["v3"] + } + + return parser.parse(rule) +} + +func newParser(funcs matcherBuilderFuncs) (*parser, error) { + p, err := rules.NewParser(slices.Collect(maps.Keys(funcs))) + if err != nil { + return nil, err + } + + return &parser{ + parser: p, + matcherFuncs: funcs, + }, nil +} + +type parser struct { + parser predicate.Parser + matcherFuncs matcherBuilderFuncs +} + +func (p *parser) parse(rule string) (matchersTree, error) { + parse, err := p.parser.Parse(rule) + if err != nil { + return matchersTree{}, fmt.Errorf("parsing rule %s: %w", rule, err) + } + buildTree, ok := parse.(rules.TreeBuilder) + if !ok { + return matchersTree{}, errors.New("obtaining build tree") + } + + var matchers matchersTree + err = matchers.addRule(buildTree(), p.matcherFuncs) + if err != nil { + return matchersTree{}, fmt.Errorf("adding rule %s: %w", rule, err) + } + + return matchers, nil +} diff --git a/pkg/server/router/router.go b/pkg/server/router/router.go index fcab90a94..3abd239e5 100644 --- a/pkg/server/router/router.go +++ b/pkg/server/router/router.go @@ -42,10 +42,11 @@ type Manager struct { middlewaresBuilder middlewareBuilder conf *runtime.Configuration tlsManager *tls.Manager + parser httpmuxer.SyntaxParser } // NewManager creates a new Manager. -func NewManager(conf *runtime.Configuration, serviceManager serviceManager, middlewaresBuilder middlewareBuilder, observabilityMgr *middleware.ObservabilityMgr, tlsManager *tls.Manager) *Manager { +func NewManager(conf *runtime.Configuration, serviceManager serviceManager, middlewaresBuilder middlewareBuilder, observabilityMgr *middleware.ObservabilityMgr, tlsManager *tls.Manager, parser httpmuxer.SyntaxParser) *Manager { return &Manager{ routerHandlers: make(map[string]http.Handler), serviceManager: serviceManager, @@ -53,6 +54,7 @@ func NewManager(conf *runtime.Configuration, serviceManager serviceManager, midd middlewaresBuilder: middlewaresBuilder, conf: conf, tlsManager: tlsManager, + parser: parser, } } @@ -103,10 +105,7 @@ func (m *Manager) BuildHandlers(rootCtx context.Context, entryPoints []string, t } func (m *Manager) buildEntryPointHandler(ctx context.Context, entryPointName string, configs map[string]*runtime.RouterInfo) (http.Handler, error) { - muxer, err := httpmuxer.NewMuxer() - if err != nil { - return nil, err - } + muxer := httpmuxer.NewMuxer(m.parser) defaultHandler, err := m.observabilityMgr.BuildEPChain(ctx, entryPointName, "", nil).Then(http.NotFoundHandler()) if err != nil { diff --git a/pkg/server/router/router_test.go b/pkg/server/router/router_test.go index 35a58bfb8..b73f3df5a 100644 --- a/pkg/server/router/router_test.go +++ b/pkg/server/router/router_test.go @@ -13,10 +13,12 @@ import ( "time" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ptypes "github.com/traefik/paerser/types" "github.com/traefik/traefik/v3/pkg/config/dynamic" "github.com/traefik/traefik/v3/pkg/config/runtime" "github.com/traefik/traefik/v3/pkg/middlewares/requestdecorator" + httpmuxer "github.com/traefik/traefik/v3/pkg/muxer/http" "github.com/traefik/traefik/v3/pkg/server/middleware" "github.com/traefik/traefik/v3/pkg/server/service" "github.com/traefik/traefik/v3/pkg/testhelpers" @@ -326,7 +328,10 @@ func TestRouterManager_Get(t *testing.T) { middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager, nil) tlsManager := traefiktls.NewManager() - routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, nil, tlsManager) + parser, err := httpmuxer.NewSyntaxParser() + require.NoError(t, err) + + routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, nil, tlsManager, parser) handlers := routerManager.BuildHandlers(context.Background(), test.entryPoints, false) @@ -711,7 +716,10 @@ func TestRuntimeConfiguration(t *testing.T) { tlsManager := traefiktls.NewManager() tlsManager.UpdateConfigs(context.Background(), nil, test.tlsOptions, nil) - routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, nil, tlsManager) + parser, err := httpmuxer.NewSyntaxParser() + require.NoError(t, err) + + routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, nil, tlsManager, parser) _ = routerManager.BuildHandlers(context.Background(), entryPoints, false) _ = routerManager.BuildHandlers(context.Background(), entryPoints, true) @@ -789,7 +797,10 @@ func TestProviderOnMiddlewares(t *testing.T) { middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager, nil) tlsManager := traefiktls.NewManager() - routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, nil, tlsManager) + parser, err := httpmuxer.NewSyntaxParser() + require.NoError(t, err) + + routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, nil, tlsManager, parser) _ = routerManager.BuildHandlers(context.Background(), entryPoints, false) @@ -865,7 +876,10 @@ func BenchmarkRouterServe(b *testing.B) { middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager, nil) tlsManager := traefiktls.NewManager() - routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, nil, tlsManager) + parser, err := httpmuxer.NewSyntaxParser() + require.NoError(b, err) + + routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, nil, tlsManager, parser) handlers := routerManager.BuildHandlers(context.Background(), entryPoints, false) diff --git a/pkg/server/routerfactory.go b/pkg/server/routerfactory.go index b0700849c..f4e0a1102 100644 --- a/pkg/server/routerfactory.go +++ b/pkg/server/routerfactory.go @@ -2,10 +2,12 @@ package server import ( "context" + "fmt" "github.com/rs/zerolog/log" "github.com/traefik/traefik/v3/pkg/config/runtime" "github.com/traefik/traefik/v3/pkg/config/static" + httpmuxer "github.com/traefik/traefik/v3/pkg/muxer/http" "github.com/traefik/traefik/v3/pkg/server/middleware" tcpmiddleware "github.com/traefik/traefik/v3/pkg/server/middleware/tcp" "github.com/traefik/traefik/v3/pkg/server/router" @@ -35,12 +37,14 @@ type RouterFactory struct { dialerManager *tcp.DialerManager cancelPrevState func() + + parser httpmuxer.SyntaxParser } // NewRouterFactory creates a new RouterFactory. func NewRouterFactory(staticConfiguration static.Configuration, managerFactory *service.ManagerFactory, tlsManager *tls.Manager, observabilityMgr *middleware.ObservabilityMgr, pluginBuilder middleware.PluginsBuilder, dialerManager *tcp.DialerManager, -) *RouterFactory { +) (*RouterFactory, error) { handlesTLSChallenge := false for _, resolver := range staticConfiguration.CertificatesResolvers { if resolver.ACME != nil && resolver.ACME.TLSChallenge != nil { @@ -67,6 +71,11 @@ func NewRouterFactory(staticConfiguration static.Configuration, managerFactory * } } + parser, err := httpmuxer.NewSyntaxParser() + if err != nil { + return nil, fmt.Errorf("creating parser: %w", err) + } + return &RouterFactory{ entryPointsTCP: entryPointsTCP, entryPointsUDP: entryPointsUDP, @@ -76,7 +85,8 @@ func NewRouterFactory(staticConfiguration static.Configuration, managerFactory * pluginBuilder: pluginBuilder, dialerManager: dialerManager, allowACMEByPass: allowACMEByPass, - } + parser: parser, + }, nil } // CreateRouters creates new TCPRouters and UDPRouters. @@ -93,7 +103,7 @@ func (f *RouterFactory) CreateRouters(rtConf *runtime.Configuration) (map[string middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager, f.pluginBuilder) - routerManager := router.NewManager(rtConf, serviceManager, middlewaresBuilder, f.observabilityMgr, f.tlsManager) + routerManager := router.NewManager(rtConf, serviceManager, middlewaresBuilder, f.observabilityMgr, f.tlsManager, f.parser) handlersNonTLS := routerManager.BuildHandlers(ctx, f.entryPointsTCP, false) handlersTLS := routerManager.BuildHandlers(ctx, f.entryPointsTCP, true) diff --git a/pkg/server/routerfactory_test.go b/pkg/server/routerfactory_test.go index 2b3d6fe1f..648b24e8f 100644 --- a/pkg/server/routerfactory_test.go +++ b/pkg/server/routerfactory_test.go @@ -8,6 +8,7 @@ import ( "time" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "github.com/traefik/traefik/v3/pkg/config/dynamic" "github.com/traefik/traefik/v3/pkg/config/runtime" "github.com/traefik/traefik/v3/pkg/config/static" @@ -58,7 +59,8 @@ func TestReuseService(t *testing.T) { dialerManager := tcp.NewDialerManager(nil) dialerManager.Update(map[string]*dynamic.TCPServersTransport{"default@internal": {}}) - factory := NewRouterFactory(staticConfig, managerFactory, tlsManager, nil, nil, dialerManager) + factory, err := NewRouterFactory(staticConfig, managerFactory, tlsManager, nil, nil, dialerManager) + require.NoError(t, err) entryPointsHandlers, _ := factory.CreateRouters(runtime.NewConfig(dynamic.Configuration{HTTP: dynamicConfigs})) @@ -196,7 +198,8 @@ func TestServerResponseEmptyBackend(t *testing.T) { dialerManager := tcp.NewDialerManager(nil) dialerManager.Update(map[string]*dynamic.TCPServersTransport{"default@internal": {}}) observabiltyMgr := middleware.NewObservabilityMgr(staticConfig, nil, nil, nil, nil, nil) - factory := NewRouterFactory(staticConfig, managerFactory, tlsManager, observabiltyMgr, nil, dialerManager) + factory, err := NewRouterFactory(staticConfig, managerFactory, tlsManager, observabiltyMgr, nil, dialerManager) + require.NoError(t, err) entryPointsHandlers, _ := factory.CreateRouters(runtime.NewConfig(dynamic.Configuration{HTTP: test.config(testServer.URL)})) @@ -240,7 +243,8 @@ func TestInternalServices(t *testing.T) { dialerManager := tcp.NewDialerManager(nil) dialerManager.Update(map[string]*dynamic.TCPServersTransport{"default@internal": {}}) - factory := NewRouterFactory(staticConfig, managerFactory, tlsManager, nil, nil, dialerManager) + factory, err := NewRouterFactory(staticConfig, managerFactory, tlsManager, nil, nil, dialerManager) + require.NoError(t, err) entryPointsHandlers, _ := factory.CreateRouters(runtime.NewConfig(dynamic.Configuration{HTTP: dynamicConfigs})) From 23c7c78a1abf08ef2fc7aa60cfa7adc2c33e0d6e Mon Sep 17 00:00:00 2001 From: Romain Date: Tue, 27 May 2025 09:34:04 +0200 Subject: [PATCH 11/21] Add HTTP/2 maxConcurrentStream parameter test --- .../fixtures/https/max_concurrent_stream.toml | 28 ++++++++++++++ integration/https_test.go | 37 +++++++++++++++++++ 2 files changed, 65 insertions(+) create mode 100644 integration/fixtures/https/max_concurrent_stream.toml diff --git a/integration/fixtures/https/max_concurrent_stream.toml b/integration/fixtures/https/max_concurrent_stream.toml new file mode 100644 index 000000000..912a47265 --- /dev/null +++ b/integration/fixtures/https/max_concurrent_stream.toml @@ -0,0 +1,28 @@ +[global] + checkNewVersion = false + sendAnonymousUsage = false + +[log] + level = "DEBUG" + +[serversTransport] + insecureSkipVerify=true + +[entryPoints] + [entryPoints.web] + address = ":8000" + [entryPoints.web.http2] + maxConcurrentStreams = 42 + +[api] + insecure = true + +[providers.file] + filename = "{{ .SelfFilename }}" + +## dynamic configuration ## + +[tls.stores] + [tls.stores.default.defaultCertificate] + certFile = "resources/tls/local.cert" + keyFile = "resources/tls/local.key" diff --git a/integration/https_test.go b/integration/https_test.go index 9ced3ffb9..6e98e61d5 100644 --- a/integration/https_test.go +++ b/integration/https_test.go @@ -3,6 +3,7 @@ package integration import ( "bytes" "crypto/tls" + "crypto/x509" "fmt" "net" "net/http" @@ -18,6 +19,7 @@ import ( "github.com/traefik/traefik/v2/integration/try" "github.com/traefik/traefik/v2/pkg/config/dynamic" traefiktls "github.com/traefik/traefik/v2/pkg/tls" + "golang.org/x/net/http2" ) // HTTPSSuite tests suite. @@ -1173,3 +1175,38 @@ func (s *HTTPSSuite) TestWithInvalidTLSOption() { assert.Nil(s.T(), conn) } } + +func (s *SimpleSuite) TestMaxConcurrentStream() { + file := s.adaptFile("fixtures/https/max_concurrent_stream.toml", struct{}{}) + + s.traefikCmd(withConfigFile(file), "--log.level=DEBUG", "--accesslog") + + // Wait for traefik. + err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", time.Second, try.BodyContains("api@internal")) + require.NoError(s.T(), err) + + // Add client self-signed cert. + roots := x509.NewCertPool() + certContent, err := os.ReadFile("./resources/tls/local.cert") + require.NoError(s.T(), err) + + roots.AppendCertsFromPEM(certContent) + + // Open a connection to inspect SettingsFrame. + conn, err := tls.Dial("tcp", "127.0.0.1:8000", &tls.Config{ + RootCAs: roots, + NextProtos: []string{"h2"}, + }) + require.NoError(s.T(), err) + + framer := http2.NewFramer(nil, conn) + frame, err := framer.ReadFrame() + require.NoError(s.T(), err) + + fr, ok := frame.(*http2.SettingsFrame) + require.True(s.T(), ok) + + maxConcurrentStream, ok := fr.Value(http2.SettingMaxConcurrentStreams) + assert.True(s.T(), ok) + assert.Equal(s.T(), uint32(42), maxConcurrentStream) +} From a3745d1eb2d1973dbbcdd533d4528b76acdc590a Mon Sep 17 00:00:00 2001 From: Patrick Evans <31580846+holysoles@users.noreply.github.com> Date: Tue, 27 May 2025 07:46:04 +0000 Subject: [PATCH 12/21] Match encoded certificate to example data for tlspassthrough --- .../http/middlewares/passtlsclientcert.md | 105 +----------------- 1 file changed, 1 insertion(+), 104 deletions(-) diff --git a/docs/content/reference/routing-configuration/http/middlewares/passtlsclientcert.md b/docs/content/reference/routing-configuration/http/middlewares/passtlsclientcert.md index 1b568b79a..10b40bb01 100644 --- a/docs/content/reference/routing-configuration/http/middlewares/passtlsclientcert.md +++ b/docs/content/reference/routing-configuration/http/middlewares/passtlsclientcert.md @@ -202,109 +202,6 @@ spec: - `X-Forwarded-Tls-Client-Cert-Info` header value is a string that has been escaped in order to be a valid URL query. - These options only work accordingly to the MutualTLS configuration. i.e, only the certificates that match the `clientAuth.clientAuthType` policy are passed. -??? example "Example of a complete certificate and explaining each of the middleware options" - - ```txt - Certificate: - Data: - Version: 3 (0x2) - Serial Number: 1 (0x1) - Signature Algorithm: sha1WithRSAEncryption - Issuer: DC=org, DC=cheese, O=Cheese, O=Cheese 2, OU=Simple Signing Section, OU=Simple Signing Section 2, CN=Simple Signing CA, CN=Simple Signing CA 2, C=FR, C=US, L=TOULOUSE, L=LYON, ST=Signing State, ST=Signing State 2/emailAddress=simple@signing.com/emailAddress=simple2@signing.com - Validity - Not Before: Dec 6 11:10:16 2018 GMT - Not After : Dec 5 11:10:16 2020 GMT - Subject: DC=org, DC=cheese, O=Cheese, O=Cheese 2, OU=Simple Signing Section, OU=Simple Signing Section 2, CN=*.example.org, CN=*.example.com, C=FR, C=US, L=TOULOUSE, L=LYON, ST=Cheese org state, ST=Cheese com statemailAddress=cert@example.org/emailAddress=cert@sexample.com - Subject Public Key Info: - Public Key Algorithm: rsaEncryption - RSA Public-Key: (2048 bit) - Modulus: - 00:de:77:fa:8d:03:70:30:39:dd:51:1b:cc:60:db: - a9:5a:13:b1:af:fe:2c:c6:38:9b:88:0a:0f:8e:d9: - 1b:a1:1d:af:0d:66:e4:13:5b:bc:5d:36:92:d7:5e: - d0:fa:88:29:d3:78:e1:81:de:98:b2:a9:22:3f:bf: - 8a:af:12:92:63:d4:a9:c3:f2:e4:7e:d2:dc:a2:c5: - 39:1c:7a:eb:d7:12:70:63:2e:41:47:e0:f0:08:e8: - dc:be:09:01:ec:28:09:af:35:d7:79:9c:50:35:d1: - 6b:e5:87:7b:34:f6:d2:31:65:1d:18:42:69:6c:04: - 11:83:fe:44:ae:90:92:2d:0b:75:39:57:62:e6:17: - 2f:47:2b:c7:53:dd:10:2d:c9:e3:06:13:d2:b9:ba: - 63:2e:3c:7d:83:6b:d6:89:c9:cc:9d:4d:bf:9f:e8: - a3:7b:da:c8:99:2b:ba:66:d6:8e:f8:41:41:a0:c9: - d0:5e:c8:11:a4:55:4a:93:83:87:63:04:63:41:9c: - fb:68:04:67:c2:71:2f:f2:65:1d:02:5d:15:db:2c: - d9:04:69:85:c2:7d:0d:ea:3b:ac:85:f8:d4:8f:0f: - c5:70:b2:45:e1:ec:b2:54:0b:e9:f7:82:b4:9b:1b: - 2d:b9:25:d4:ab:ca:8f:5b:44:3e:15:dd:b8:7f:b7: - ee:f9 - Exponent: 65537 (0x10001) - X509v3 extensions: - X509v3 Key Usage: critical - Digital Signature, Key Encipherment - X509v3 Basic Constraints: - CA:FALSE - X509v3 Extended Key Usage: - TLS Web Server Authentication, TLS Web Client Authentication - X509v3 Subject Key Identifier: - 94:BA:73:78:A2:87:FB:58:28:28:CF:98:3B:C2:45:70:16:6E:29:2F - X509v3 Authority Key Identifier: - keyid:1E:52:A2:E8:54:D5:37:EB:D5:A8:1D:E4:C2:04:1D:37:E2:F7:70:03 - - X509v3 Subject Alternative Name: - DNS:*.example.org, DNS:*.example.net, DNS:*.example.com, IP Address:10.0.1.0, IP Address:10.0.1.2, email:test@example.org, email:test@example.net - Signature Algorithm: sha1WithRSAEncryption - 76:6b:05:b0:0e:34:11:b1:83:99:91:dc:ae:1b:e2:08:15:8b: - 16:b2:9b:27:1c:02:ac:b5:df:1b:d0:d0:75:a4:2b:2c:5c:65: - ed:99:ab:f7:cd:fe:38:3f:c3:9a:22:31:1b:ac:8c:1c:c2:f9: - 5d:d4:75:7a:2e:72:c7:85:a9:04:af:9f:2a:cc:d3:96:75:f0: - 8e:c7:c6:76:48:ac:45:a4:b9:02:1e:2f:c0:15:c4:07:08:92: - cb:27:50:67:a1:c8:05:c5:3a:b3:a6:48:be:eb:d5:59:ab:a2: - 1b:95:30:71:13:5b:0a:9a:73:3b:60:cc:10:d0:6a:c7:e5:d7: - 8b:2f:f9:2e:98:f2:ff:81:14:24:09:e3:4b:55:57:09:1a:22: - 74:f1:f6:40:13:31:43:89:71:0a:96:1a:05:82:1f:83:3a:87: - 9b:17:25:ef:5a:55:f2:2d:cd:0d:4d:e4:81:58:b6:e3:8d:09: - 62:9a:0c:bd:e4:e5:5c:f0:95:da:cb:c7:34:2c:34:5f:6d:fc: - 60:7b:12:5b:86:fd:df:21:89:3b:48:08:30:bf:67:ff:8c:e6: - 9b:53:cc:87:36:47:70:40:3b:d9:90:2a:d2:d2:82:c6:9c:f5: - d1:d8:e0:e6:fd:aa:2f:95:7e:39:ac:fc:4e:d4:ce:65:b3:ec: - c6:98:8a:31 - -----BEGIN CERTIFICATE----- - MIIGWjCCBUKgAwIBAgIBATANBgkqhkiG9w0BAQUFADCCAYQxEzARBgoJkiaJk/Is - ZAEZFgNvcmcxFjAUBgoJkiaJk/IsZAEZFgZjaGVlc2UxDzANBgNVBAoMBkNoZWVz - ZTERMA8GA1UECgwIQ2hlZXNlIDIxHzAdBgNVBAsMFlNpbXBsZSBTaWduaW5nIFNl - Y3Rpb24xITAfBgNVBAsMGFNpbXBsZSBTaWduaW5nIFNlY3Rpb24gMjEaMBgGA1UE - AwwRU2ltcGxlIFNpZ25pbmcgQ0ExHDAaBgNVBAMME1NpbXBsZSBTaWduaW5nIENB - IDIxCzAJBgNVBAYTAkZSMQswCQYDVQQGEwJVUzERMA8GA1UEBwwIVE9VTE9VU0Ux - DTALBgNVBAcMBExZT04xFjAUBgNVBAgMDVNpZ25pbmcgU3RhdGUxGDAWBgNVBAgM - D1NpZ25pbmcgU3RhdGUgMjEhMB8GCSqGSIb3DQEJARYSc2ltcGxlQHNpZ25pbmcu - Y29tMSIwIAYJKoZIhvcNAQkBFhNzaW1wbGUyQHNpZ25pbmcuY29tMB4XDTE4MTIw - NjExMTAxNloXDTIwMTIwNTExMTAxNlowggF2MRMwEQYKCZImiZPyLGQBGRYDb3Jn - MRYwFAYKCZImiZPyLGQBGRYGY2hlZXNlMQ8wDQYDVQQKDAZDaGVlc2UxETAPBgNV - BAoMCENoZWVzZSAyMR8wHQYDVQQLDBZTaW1wbGUgU2lnbmluZyBTZWN0aW9uMSEw - HwYDVQQLDBhTaW1wbGUgU2lnbmluZyBTZWN0aW9uIDIxFTATBgNVBAMMDCouY2hl - ZXNlLm9yZzEVMBMGA1UEAwwMKi5jaGVlc2UuY29tMQswCQYDVQQGEwJGUjELMAkG - A1UEBhMCVVMxETAPBgNVBAcMCFRPVUxPVVNFMQ0wCwYDVQQHDARMWU9OMRkwFwYD - VQQIDBBDaGVlc2Ugb3JnIHN0YXRlMRkwFwYDVQQIDBBDaGVlc2UgY29tIHN0YXRl - MR4wHAYJKoZIhvcNAQkBFg9jZXJ0QGNoZWVzZS5vcmcxHzAdBgkqhkiG9w0BCQEW - EGNlcnRAc2NoZWVzZS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB - AQDed/qNA3AwOd1RG8xg26laE7Gv/izGOJuICg+O2RuhHa8NZuQTW7xdNpLXXtD6 - iCnTeOGB3piyqSI/v4qvEpJj1KnD8uR+0tyixTkceuvXEnBjLkFH4PAI6Ny+CQHs - KAmvNdd5nFA10Wvlh3s09tIxZR0YQmlsBBGD/kSukJItC3U5V2LmFy9HK8dT3RAt - yeMGE9K5umMuPH2Da9aJycydTb+f6KN72siZK7pm1o74QUGgydBeyBGkVUqTg4dj - BGNBnPtoBGfCcS/yZR0CXRXbLNkEaYXCfQ3qO6yF+NSPD8VwskXh7LJUC+n3grSb - Gy25JdSryo9bRD4V3bh/t+75AgMBAAGjgeAwgd0wDgYDVR0PAQH/BAQDAgWgMAkG - A1UdEwQCMAAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMB0GA1UdDgQW - BBSUunN4oof7WCgoz5g7wkVwFm4pLzAfBgNVHSMEGDAWgBQeUqLoVNU369WoHeTC - BB034vdwAzBhBgNVHREEWjBYggwqLmNoZWVzZS5vcmeCDCouY2hlZXNlLm5ldIIM - Ki5jaGVlc2UuY29thwQKAAEAhwQKAAECgQ90ZXN0QGNoZWVzZS5vcmeBD3Rlc3RA - Y2hlZXNlLm5ldDANBgkqhkiG9w0BAQUFAAOCAQEAdmsFsA40EbGDmZHcrhviCBWL - FrKbJxwCrLXfG9DQdaQrLFxl7Zmr983+OD/DmiIxG6yMHML5XdR1ei5yx4WpBK+f - KszTlnXwjsfGdkisRaS5Ah4vwBXEBwiSyydQZ6HIBcU6s6ZIvuvVWauiG5UwcRNb - CppzO2DMENBqx+XXiy/5Lpjy/4EUJAnjS1VXCRoidPH2QBMxQ4lxCpYaBYIfgzqH - mxcl71pV8i3NDU3kgVi2440JYpoMveTlXPCV2svHNCw0X238YHsSW4b93yGJO0gI - -----END CERTIFICATE----- - ``` - ## Configuration Options | Field | Description | Default | Required | @@ -362,5 +259,5 @@ If there are more than one certificate, they are separated by a `,`. The following example shows such a concatenation, when all the available fields are selected: ```text -Subject="DC=org,DC=cheese,C=FR,C=US,ST=Cheese org state,ST=Cheese com state,L=TOULOUSE,L=LYON,O=Cheese,O=Cheese 2,CN=*.example.com";Issuer="DC=org,DC=cheese,C=FR,C=US,ST=Signing State,ST=Signing State 2,L=TOULOUSE,L=LYON,O=Cheese,O=Cheese 2,CN=Simple Signing CA 2";NB="1544094616";NA="1607166616";SAN="*.example.org,*.example.net,*.example.com,test@example.org,test@example.net,10.0.1.0,10.0.1.2" +Subject="DC=org,DC=cheese,C=FR,C=US,ST=Cheese org state,ST=Cheese com state,L=TOULOUSE,L=LYON,O=Cheese,O=Cheese 2,CN=*.example.com";Issuer="DC=org,DC=cheese,C=FR,C=US,ST=Signing State,ST=Signing State 2,L=TOULOUSE,L=LYON,O=Cheese,O=Cheese 2,CN=Simple Signing CA 2";NB="1747282426";NA="1778818426"SAN="*.example.org,*.example.net,*.example.com,test@example.org,test@example.net,10.0.1.0,10.0.1.2" ``` From 859f4e886860442207d7ebc3b047d0bad44ba461 Mon Sep 17 00:00:00 2001 From: Kevin Pollet Date: Tue, 27 May 2025 11:06:05 +0200 Subject: [PATCH 13/21] Use routing path in v3 matchers Co-authored-by: Romain --- docs/content/migration/v3.md | 29 +++ ...an_path.toml => simple_sanitize_path.toml} | 3 + integration/simple_test.go | 105 +++++++++- pkg/muxer/http/matcher.go | 9 +- pkg/muxer/http/matcher_v2.go | 4 +- pkg/muxer/http/mux.go | 95 +++++++++ pkg/muxer/http/mux_test.go | 40 ++++ pkg/server/server_entrypoint_tcp.go | 76 ------- pkg/server/server_entrypoint_tcp_test.go | 186 ++++++------------ 9 files changed, 338 insertions(+), 209 deletions(-) rename integration/fixtures/{simple_clean_path.toml => simple_sanitize_path.toml} (93%) diff --git a/docs/content/migration/v3.md b/docs/content/migration/v3.md index 48696d010..6581b5f39 100644 --- a/docs/content/migration/v3.md +++ b/docs/content/migration/v3.md @@ -290,3 +290,32 @@ and to help with the migration from v2 to v3. The `ruleSyntax` router's option was used to override the default rule syntax for a specific router. In preparation for the next major release, please remove any use of these two options and use the v3 syntax for writing the router's rules. + +## v3.4.1 + +### Request Path Normalization + +Since `v3.4.1`, the request path is now normalized by decoding unreserved characters in the request path, +and also uppercasing the percent-encoded characters. +This follows [RFC 3986 percent-encoding normalization](https://datatracker.ietf.org/doc/html/rfc3986#section-6.2.2.2), +and [RFC 3986 case normalization](https://datatracker.ietf.org/doc/html/rfc3986#section-6.2.2.1). + +The normalization happens before the request path is sanitized, +and cannot be disabled. +This notably helps with encoded dots characters (which are unreserved characters) to be sanitized properly. + +### Routing Path + +Since `v3.4.1`, the reserved characters [(as per RFC 3986)](https://datatracker.ietf.org/doc/html/rfc3986#section-2.2) are kept encoded in the request path when matching the router rules. +Those characters, when decoded, change the meaning of the request path for routing purposes, +and Traefik now keeps them encoded to avoid any ambiguity. + +### Request Path Matching Examples + +| Request Path | Router Rule | Traefik v3.4.0 | Traefik v3.4.1 | +|-------------------|------------------------|----------------|----------------| +| `/foo%2Fbar` | PathPrefix(`/foo/bar`) | Match | No match | +| `/foo/../bar` | PathPrefix(`/foo`) | No match | No match | +| `/foo/../bar` | PathPrefix(`/bar`) | Match | Match | +| `/foo/%2E%2E/bar` | PathPrefix(`/foo`) | Match | No match | +| `/foo/%2E%2E/bar` | PathPrefix(`/bar`) | No match | Match | diff --git a/integration/fixtures/simple_clean_path.toml b/integration/fixtures/simple_sanitize_path.toml similarity index 93% rename from integration/fixtures/simple_clean_path.toml rename to integration/fixtures/simple_sanitize_path.toml index b889b750b..bf97bc8ea 100644 --- a/integration/fixtures/simple_clean_path.toml +++ b/integration/fixtures/simple_sanitize_path.toml @@ -19,6 +19,9 @@ [providers.file] filename = "{{ .SelfFilename }}" +[core] + defaultRuleSyntax = "{{ .DefaultRuleSyntax }}" + # dynamic configuration [http.routers] [http.routers.without] diff --git a/integration/simple_test.go b/integration/simple_test.go index 4a70bd79b..791ee514c 100644 --- a/integration/simple_test.go +++ b/integration/simple_test.go @@ -1606,9 +1606,108 @@ func (s *SimpleSuite) TestSanitizePath() { whoami1URL := "http://" + net.JoinHostPort(s.getComposeServiceIP("whoami1"), "80") - file := s.adaptFile("fixtures/simple_clean_path.toml", struct { - Server1 string - }{whoami1URL}) + file := s.adaptFile("fixtures/simple_sanitize_path.toml", struct { + Server1 string + DefaultRuleSyntax string + }{whoami1URL, "v3"}) + + s.traefikCmd(withConfigFile(file)) + + err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("PathPrefix(`/with`)")) + require.NoError(s.T(), err) + + testCases := []struct { + desc string + request string + target string + body string + expected int + }{ + { + desc: "Explicit call to the route with a middleware", + request: "GET /with HTTP/1.1\r\nHost: other.localhost\r\n\r\n", + target: "127.0.0.1:8000", + expected: http.StatusFound, + }, + { + desc: "Explicit call to the route without a middleware", + request: "GET /without HTTP/1.1\r\nHost: other.localhost\r\n\r\n", + target: "127.0.0.1:8000", + expected: http.StatusOK, + body: "GET /without HTTP/1.1", + }, + { + desc: "Implicit call to the route with a middleware", + request: "GET /without/../with HTTP/1.1\r\nHost: other.localhost\r\n\r\n", + target: "127.0.0.1:8000", + expected: http.StatusFound, + }, + { + desc: "Implicit encoded dot dots call to the route with a middleware", + request: "GET /without/%2E%2E/with HTTP/1.1\r\nHost: other.localhost\r\n\r\n", + target: "127.0.0.1:8000", + expected: http.StatusFound, + }, + { + desc: "Implicit with encoded unreserved character call to the route with a middleware", + request: "GET /%77ith HTTP/1.1\r\nHost: other.localhost\r\n\r\n", + target: "127.0.0.1:8000", + expected: http.StatusFound, + }, + { + desc: "Explicit call to the route with a middleware, and disable path sanitization", + request: "GET /with HTTP/1.1\r\nHost: other.localhost\r\n\r\n", + target: "127.0.0.1:8001", + expected: http.StatusFound, + }, + { + desc: "Explicit call to the route without a middleware, and disable path sanitization", + request: "GET /without HTTP/1.1\r\nHost: other.localhost\r\n\r\n", + target: "127.0.0.1:8001", + expected: http.StatusOK, + body: "GET /without HTTP/1.1", + }, + { + desc: "Implicit call to the route with a middleware, and disable path sanitization", + request: "GET /without/../with HTTP/1.1\r\nHost: other.localhost\r\n\r\n", + target: "127.0.0.1:8001", + // The whoami is redirecting to /with, but the path is not sanitized. + expected: http.StatusMovedPermanently, + }, + } + + 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) + + if test.body != "" { + body, err := io.ReadAll(resp.Body) + require.NoError(s.T(), err) + assert.Contains(s.T(), string(body), test.body) + } + } +} + +func (s *SimpleSuite) TestSanitizePathSyntaxV2() { + s.createComposeProject("base") + + s.composeUp() + defer s.composeDown() + + whoami1URL := "http://" + net.JoinHostPort(s.getComposeServiceIP("whoami1"), "80") + + file := s.adaptFile("fixtures/simple_sanitize_path.toml", struct { + Server1 string + DefaultRuleSyntax string + }{whoami1URL, "v2"}) s.traefikCmd(withConfigFile(file)) diff --git a/pkg/muxer/http/matcher.go b/pkg/muxer/http/matcher.go index 98ebc8553..74f5d97f0 100644 --- a/pkg/muxer/http/matcher.go +++ b/pkg/muxer/http/matcher.go @@ -142,7 +142,8 @@ func path(tree *matchersTree, paths ...string) error { } tree.matcher = func(req *http.Request) bool { - return req.URL.Path == path + routingPath := getRoutingPath(req) + return routingPath != nil && *routingPath == path } return nil @@ -157,7 +158,8 @@ func pathRegexp(tree *matchersTree, paths ...string) error { } tree.matcher = func(req *http.Request) bool { - return re.MatchString(req.URL.Path) + routingPath := getRoutingPath(req) + return routingPath != nil && re.MatchString(*routingPath) } return nil @@ -171,7 +173,8 @@ func pathPrefix(tree *matchersTree, paths ...string) error { } tree.matcher = func(req *http.Request) bool { - return strings.HasPrefix(req.URL.Path, path) + routingPath := getRoutingPath(req) + return routingPath != nil && strings.HasPrefix(*routingPath, path) } return nil diff --git a/pkg/muxer/http/matcher_v2.go b/pkg/muxer/http/matcher_v2.go index b6635220f..36f426f48 100644 --- a/pkg/muxer/http/matcher_v2.go +++ b/pkg/muxer/http/matcher_v2.go @@ -28,7 +28,7 @@ func pathV2(tree *matchersTree, paths ...string) error { var routes []*mux.Route for _, path := range paths { - route := mux.NewRouter().NewRoute() + route := mux.NewRouter().UseRoutingPath().NewRoute() if err := route.Path(path).GetError(); err != nil { return err @@ -54,7 +54,7 @@ func pathPrefixV2(tree *matchersTree, paths ...string) error { var routes []*mux.Route for _, path := range paths { - route := mux.NewRouter().NewRoute() + route := mux.NewRouter().UseRoutingPath().NewRoute() if err := route.PathPrefix(path).GetError(); err != nil { return err diff --git a/pkg/muxer/http/mux.go b/pkg/muxer/http/mux.go index 2adad60bc..d3ab6a45d 100644 --- a/pkg/muxer/http/mux.go +++ b/pkg/muxer/http/mux.go @@ -1,10 +1,15 @@ package http import ( + "context" + "errors" "fmt" "net/http" + "net/url" "sort" + "strings" + "github.com/gorilla/mux" "github.com/rs/zerolog/log" "github.com/traefik/traefik/v3/pkg/rules" ) @@ -33,6 +38,16 @@ func NewMuxer(parser SyntaxParser) *Muxer { // ServeHTTP forwards the connection to the matching HTTP handler. // Serves 404 if no handler is found. func (m *Muxer) ServeHTTP(rw http.ResponseWriter, req *http.Request) { + logger := log.Ctx(req.Context()) + + var err error + req, err = withRoutingPath(req) + if err != nil { + logger.Debug().Err(err).Msg("Unable to add routing path to request context") + rw.WriteHeader(http.StatusBadRequest) + return + } + for _, route := range m.routes { if route.matchers.match(req) { route.handler.ServeHTTP(rw, req) @@ -72,6 +87,86 @@ func (m *Muxer) AddRoute(rule string, syntax string, priority int, handler http. return nil } +// reservedCharacters contains the mapping of the percent-encoded form to the ASCII form +// of the reserved characters according to https://datatracker.ietf.org/doc/html/rfc3986#section-2.2. +// By extension to https://datatracker.ietf.org/doc/html/rfc3986#section-2.1 the percent character is also considered a reserved character. +// Because decoding the percent character would change the meaning of the URL. +var reservedCharacters = map[string]rune{ + "%3A": ':', + "%2F": '/', + "%3F": '?', + "%23": '#', + "%5B": '[', + "%5D": ']', + "%40": '@', + "%21": '!', + "%24": '$', + "%26": '&', + "%27": '\'', + "%28": '(', + "%29": ')', + "%2A": '*', + "%2B": '+', + "%2C": ',', + "%3B": ';', + "%3D": '=', + "%25": '%', +} + +// getRoutingPath retrieves the routing path from the request context. +// It returns nil if the routing path is not set in the context. +func getRoutingPath(req *http.Request) *string { + routingPath := req.Context().Value(mux.RoutingPathKey) + if routingPath != nil { + rp := routingPath.(string) + return &rp + } + return nil +} + +// withRoutingPath decodes non-allowed characters in the EscapedPath and stores it in the request context to be able to use it for routing. +// This allows using the decoded version of the non-allowed characters in the routing rules for a better UX. +// For example, the rule PathPrefix(`/foo bar`) will match the following request path `/foo%20bar`. +func withRoutingPath(req *http.Request) (*http.Request, error) { + escapedPath := req.URL.EscapedPath() + + var routingPathBuilder strings.Builder + for i := 0; i < len(escapedPath); i++ { + if escapedPath[i] != '%' { + routingPathBuilder.WriteString(string(escapedPath[i])) + continue + } + + // This should never happen as the standard library will reject requests containing invalid percent-encodings. + // This discards URLs with a percent character at the end. + if i+2 >= len(escapedPath) { + return nil, errors.New("invalid percent-encoding at the end of the URL path") + } + + encodedCharacter := escapedPath[i : i+3] + if _, reserved := reservedCharacters[encodedCharacter]; reserved { + routingPathBuilder.WriteString(encodedCharacter) + } else { + // This should never happen as the standard library will reject requests containing invalid percent-encodings. + decodedCharacter, err := url.PathUnescape(encodedCharacter) + if err != nil { + return nil, errors.New("invalid percent-encoding in URL path") + } + routingPathBuilder.WriteString(decodedCharacter) + } + + i += 2 + } + + return req.WithContext( + context.WithValue( + req.Context(), + mux.RoutingPathKey, + routingPathBuilder.String(), + ), + ), nil +} + // ParseDomains extract domains from rule. func ParseDomains(rule string) ([]string, error) { var matchers []string diff --git a/pkg/muxer/http/mux_test.go b/pkg/muxer/http/mux_test.go index d0b4a08bf..36cc99221 100644 --- a/pkg/muxer/http/mux_test.go +++ b/pkg/muxer/http/mux_test.go @@ -557,3 +557,43 @@ func TestGetRulePriority(t *testing.T) { }) } } + +func TestRoutingPath(t *testing.T) { + tests := []struct { + desc string + path string + expectedRoutingPath string + }{ + { + desc: "unallowed percent-encoded character is decoded", + path: "/foo%20bar", + expectedRoutingPath: "/foo bar", + }, + { + desc: "reserved percent-encoded character is kept encoded", + path: "/foo%2Fbar", + expectedRoutingPath: "/foo%2Fbar", + }, + { + desc: "multiple mixed characters", + path: "/foo%20bar%2Fbaz%23qux", + expectedRoutingPath: "/foo bar%2Fbaz%23qux", + }, + } + + for _, test := range tests { + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + req := httptest.NewRequest(http.MethodGet, "http://foo"+test.path, http.NoBody) + + var err error + req, err = withRoutingPath(req) + require.NoError(t, err) + + gotRoutingPath := getRoutingPath(req) + assert.NotNil(t, gotRoutingPath) + assert.Equal(t, test.expectedRoutingPath, *gotRoutingPath) + }) + } +} diff --git a/pkg/server/server_entrypoint_tcp.go b/pkg/server/server_entrypoint_tcp.go index 5dcc64830..039d06659 100644 --- a/pkg/server/server_entrypoint_tcp.go +++ b/pkg/server/server_entrypoint_tcp.go @@ -17,7 +17,6 @@ import ( "github.com/containous/alice" gokitmetrics "github.com/go-kit/kit/metrics" - "github.com/gorilla/mux" "github.com/pires/go-proxyproto" "github.com/rs/zerolog" "github.com/rs/zerolog/log" @@ -630,8 +629,6 @@ func createHTTPServer(ctx context.Context, ln net.Listener, configuration *stati handler = http.AllowQuerySemicolons(handler) } - handler = routingPath(handler) - // Note that the Path sanitization has to be done after the path normalization, // hence the wrapping has to be done before the normalize path wrapping. if configuration.HTTP.SanitizePath != nil && *configuration.HTTP.SanitizePath { @@ -866,76 +863,3 @@ func normalizePath(h http.Handler) http.Handler { h.ServeHTTP(rw, r2) }) } - -// reservedCharacters contains the mapping of the percent-encoded form to the ASCII form -// of the reserved characters according to https://datatracker.ietf.org/doc/html/rfc3986#section-2.2. -// By extension to https://datatracker.ietf.org/doc/html/rfc3986#section-2.1 the percent character is also considered a reserved character. -// Because decoding the percent character would change the meaning of the URL. -var reservedCharacters = map[string]rune{ - "%3A": ':', - "%2F": '/', - "%3F": '?', - "%23": '#', - "%5B": '[', - "%5D": ']', - "%40": '@', - "%21": '!', - "%24": '$', - "%26": '&', - "%27": '\'', - "%28": '(', - "%29": ')', - "%2A": '*', - "%2B": '+', - "%2C": ',', - "%3B": ';', - "%3D": '=', - "%25": '%', -} - -// routingPath decodes non-allowed characters in the EscapedPath and stores it in the context to be able to use it for routing. -// This allows using the decoded version of the non-allowed characters in the routing rules for a better UX. -// For example, the rule PathPrefix(`/foo bar`) will match the following request path `/foo%20bar`. -func routingPath(h http.Handler) http.Handler { - return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { - escapedPath := req.URL.EscapedPath() - - var routingPathBuilder strings.Builder - for i := 0; i < len(escapedPath); i++ { - if escapedPath[i] != '%' { - routingPathBuilder.WriteString(string(escapedPath[i])) - continue - } - - // This should never happen as the standard library will reject requests containing invalid percent-encodings. - // This discards URLs with a percent character at the end. - if i+2 >= len(escapedPath) { - rw.WriteHeader(http.StatusBadRequest) - return - } - - encodedCharacter := escapedPath[i : i+3] - if _, reserved := reservedCharacters[encodedCharacter]; reserved { - routingPathBuilder.WriteString(encodedCharacter) - } else { - // This should never happen as the standard library will reject requests containing invalid percent-encodings. - decodedCharacter, err := url.PathUnescape(encodedCharacter) - if err != nil { - rw.WriteHeader(http.StatusBadRequest) - return - } - routingPathBuilder.WriteString(decodedCharacter) - } - - i += 2 - } - - h.ServeHTTP(rw, req.WithContext( - context.WithValue( - req.Context(), - mux.RoutingPathKey, - routingPathBuilder.String(), - ), - )) - }) -} diff --git a/pkg/server/server_entrypoint_tcp_test.go b/pkg/server/server_entrypoint_tcp_test.go index 6ce62639b..ecb0c06ae 100644 --- a/pkg/server/server_entrypoint_tcp_test.go +++ b/pkg/server/server_entrypoint_tcp_test.go @@ -13,7 +13,6 @@ import ( "testing" "time" - "github.com/gorilla/mux" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ptypes "github.com/traefik/paerser/types" @@ -510,56 +509,7 @@ func TestNormalizePath_malformedPercentEncoding(t *testing.T) { } } -func TestRoutingPath(t *testing.T) { - tests := []struct { - desc string - path string - expRoutingPath string - expStatus int - }{ - { - desc: "unallowed percent-encoded character is decoded", - path: "/foo%20bar", - expRoutingPath: "/foo bar", - expStatus: http.StatusOK, - }, - { - desc: "reserved percent-encoded character is kept encoded", - path: "/foo%2Fbar", - expRoutingPath: "/foo%2Fbar", - expStatus: http.StatusOK, - }, - { - desc: "multiple mixed characters", - path: "/foo%20bar%2Fbaz%23qux", - expRoutingPath: "/foo bar%2Fbaz%23qux", - expStatus: http.StatusOK, - }, - } - - for _, test := range tests { - t.Run(test.desc, func(t *testing.T) { - t.Parallel() - - var gotRoute string - handler := routingPath(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - gotRoute, _ = r.Context().Value(mux.RoutingPathKey).(string) - w.WriteHeader(http.StatusOK) - })) - - req := httptest.NewRequest(http.MethodGet, "http://foo"+test.path, http.NoBody) - - res := httptest.NewRecorder() - - handler.ServeHTTP(res, req) - - assert.Equal(t, test.expStatus, res.Code) - assert.Equal(t, test.expRoutingPath, gotRoute) - }) - } -} - -// TestPathOperations tests the whole behavior of normalizePath, sanitizePath, and routingPath combined through the use of the createHTTPServer func. +// TestPathOperations tests the whole behavior of normalizePath, and sanitizePath combined through the use of the createHTTPServer func. // It aims to guarantee the server entrypoint handler is secure regarding a large variety of cases that could lead to path traversal attacks. func TestPathOperations(t *testing.T) { // Create a listener for the server. @@ -580,7 +530,6 @@ func TestPathOperations(t *testing.T) { server.Switcher.UpdateHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Path", r.URL.Path) w.Header().Set("RawPath", r.URL.EscapedPath()) - w.Header().Set("RoutingPath", r.Context().Value(mux.RoutingPathKey).(string)) w.WriteHeader(http.StatusOK) })) @@ -596,100 +545,88 @@ func TestPathOperations(t *testing.T) { } tests := []struct { - desc string - rawPath string - expectedPath string - expectedRaw string - expectedRoutingPath string - expectedStatus int + desc string + rawPath string + expectedPath string + expectedRaw string + expectedStatus int }{ { - desc: "normalize and sanitize path", - rawPath: "/a/../b/%41%42%43//%2f/", - expectedPath: "/b/ABC///", - expectedRaw: "/b/ABC/%2F/", - expectedRoutingPath: "/b/ABC/%2F/", - expectedStatus: http.StatusOK, + desc: "normalize and sanitize path", + rawPath: "/a/../b/%41%42%43//%2f/", + expectedPath: "/b/ABC///", + expectedRaw: "/b/ABC/%2F/", + expectedStatus: http.StatusOK, }, { - desc: "path with traversal attempt", - rawPath: "/../../b/", - expectedPath: "/b/", - expectedRaw: "/b/", - expectedRoutingPath: "/b/", - expectedStatus: http.StatusOK, + desc: "path with traversal attempt", + rawPath: "/../../b/", + expectedPath: "/b/", + expectedRaw: "/b/", + expectedStatus: http.StatusOK, }, { - desc: "path with multiple traversal attempts", - rawPath: "/a/../../b/../c/", - expectedPath: "/c/", - expectedRaw: "/c/", - expectedRoutingPath: "/c/", - expectedStatus: http.StatusOK, + desc: "path with multiple traversal attempts", + rawPath: "/a/../../b/../c/", + expectedPath: "/c/", + expectedRaw: "/c/", + expectedStatus: http.StatusOK, }, { - desc: "path with mixed traversal and valid segments", - rawPath: "/a/../b/./c/../d/", - expectedPath: "/b/d/", - expectedRaw: "/b/d/", - expectedRoutingPath: "/b/d/", - expectedStatus: http.StatusOK, + desc: "path with mixed traversal and valid segments", + rawPath: "/a/../b/./c/../d/", + expectedPath: "/b/d/", + expectedRaw: "/b/d/", + expectedStatus: http.StatusOK, }, { - desc: "path with trailing slash and traversal", - rawPath: "/a/b/../", - expectedPath: "/a/", - expectedRaw: "/a/", - expectedRoutingPath: "/a/", - expectedStatus: http.StatusOK, + desc: "path with trailing slash and traversal", + rawPath: "/a/b/../", + expectedPath: "/a/", + expectedRaw: "/a/", + expectedStatus: http.StatusOK, }, { - desc: "path with encoded traversal sequences", - rawPath: "/a/%2E%2E/%2E%2E/b/", - expectedPath: "/b/", - expectedRaw: "/b/", - expectedRoutingPath: "/b/", - expectedStatus: http.StatusOK, + desc: "path with encoded traversal sequences", + rawPath: "/a/%2E%2E/%2E%2E/b/", + expectedPath: "/b/", + expectedRaw: "/b/", + expectedStatus: http.StatusOK, }, { - desc: "path with over-encoded traversal sequences", - rawPath: "/a/%252E%252E/%252E%252E/b/", - expectedPath: "/a/%2E%2E/%2E%2E/b/", - expectedRaw: "/a/%252E%252E/%252E%252E/b/", - expectedRoutingPath: "/a/%252E%252E/%252E%252E/b/", - expectedStatus: http.StatusOK, + desc: "path with over-encoded traversal sequences", + rawPath: "/a/%252E%252E/%252E%252E/b/", + expectedPath: "/a/%2E%2E/%2E%2E/b/", + expectedRaw: "/a/%252E%252E/%252E%252E/b/", + expectedStatus: http.StatusOK, }, { - desc: "routing path with unallowed percent-encoded character", - rawPath: "/foo%20bar", - expectedPath: "/foo bar", - expectedRaw: "/foo%20bar", - expectedRoutingPath: "/foo bar", - expectedStatus: http.StatusOK, + desc: "routing path with unallowed percent-encoded character", + rawPath: "/foo%20bar", + expectedPath: "/foo bar", + expectedRaw: "/foo%20bar", + expectedStatus: http.StatusOK, }, { - desc: "routing path with reserved percent-encoded character", - rawPath: "/foo%2Fbar", - expectedPath: "/foo/bar", - expectedRaw: "/foo%2Fbar", - expectedRoutingPath: "/foo%2Fbar", - expectedStatus: http.StatusOK, + desc: "routing path with reserved percent-encoded character", + rawPath: "/foo%2Fbar", + expectedPath: "/foo/bar", + expectedRaw: "/foo%2Fbar", + expectedStatus: http.StatusOK, }, { - desc: "routing path with unallowed and reserved percent-encoded character", - rawPath: "/foo%20%2Fbar", - expectedPath: "/foo /bar", - expectedRaw: "/foo%20%2Fbar", - expectedRoutingPath: "/foo %2Fbar", - expectedStatus: http.StatusOK, + desc: "routing path with unallowed and reserved percent-encoded character", + rawPath: "/foo%20%2Fbar", + expectedPath: "/foo /bar", + expectedRaw: "/foo%20%2Fbar", + expectedStatus: http.StatusOK, }, { - desc: "path with traversal and encoded slash", - rawPath: "/a/..%2Fb/", - expectedPath: "/a/../b/", - expectedRaw: "/a/..%2Fb/", - expectedRoutingPath: "/a/..%2Fb/", - expectedStatus: http.StatusOK, + desc: "path with traversal and encoded slash", + rawPath: "/a/..%2Fb/", + expectedPath: "/a/../b/", + expectedRaw: "/a/..%2Fb/", + expectedStatus: http.StatusOK, }, } @@ -706,7 +643,6 @@ func TestPathOperations(t *testing.T) { assert.Equal(t, test.expectedStatus, res.StatusCode) assert.Equal(t, test.expectedPath, res.Header.Get("Path")) assert.Equal(t, test.expectedRaw, res.Header.Get("RawPath")) - assert.Equal(t, test.expectedRoutingPath, res.Header.Get("RoutingPath")) }) } } From 5f35c888051a2d33998a5cac3bd945b11b555d93 Mon Sep 17 00:00:00 2001 From: Romain Date: Tue, 27 May 2025 12:10:05 +0200 Subject: [PATCH 14/21] Prepare release v2.11.25 --- CHANGELOG.md | 13 +++++++++++++ script/gcg/traefik-bugfix.toml | 6 +++--- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 80a19819d..7b9da61c0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,16 @@ +## [v2.11.25](https://github.com/traefik/traefik/tree/v2.11.25) (2025-05-27) +[All Commits](https://github.com/traefik/traefik/compare/v2.11.24...v2.11.25) + +**Bug fixes:** +- **[k8s/ingress]** Fix panic for ingress with backend resource ([#11777](https://github.com/traefik/traefik/pull/11777) by [rtribotte](https://github.com/rtribotte)) +- **[server]** Normalize request path ([#11768](https://github.com/traefik/traefik/pull/11768) by [kevinpollet](https://github.com/kevinpollet)) + +**Documentation:** +- **[middleware,k8s]** Add multi-tenant TLS guidance to the docs ([#11724](https://github.com/traefik/traefik/pull/11724) by [sheddy-traefik](https://github.com/sheddy-traefik)) +- **[service]** Add a note about how to disable connection reuse with backends ([#11716](https://github.com/traefik/traefik/pull/11716) by [rtribotte](https://github.com/rtribotte)) +- Fix broken link in documentation ([#11761](https://github.com/traefik/traefik/pull/11761) by [sheddy-traefik](https://github.com/sheddy-traefik)) +- Change version for path sanitization migration guide ([#11702](https://github.com/traefik/traefik/pull/11702) by [rtribotte](https://github.com/rtribotte)) + ## [v2.11.24](https://github.com/traefik/traefik/tree/v2.11.24) (2025-04-18) [All Commits](https://github.com/traefik/traefik/compare/v2.11.22...v2.11.24) diff --git a/script/gcg/traefik-bugfix.toml b/script/gcg/traefik-bugfix.toml index 798164f4f..63a5bddd3 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.24 +# example new bugfix v2.11.25 CurrentRef = "v2.11" -PreviousRef = "v2.11.23" +PreviousRef = "v2.11.24" BaseBranch = "v2.11" -FutureCurrentRefName = "v2.11.24" +FutureCurrentRefName = "v2.11.25" ThresholdPreviousRef = 10 ThresholdCurrentRef = 10 From 8b495b45a547f86b6c27fe5db637515b8645a244 Mon Sep 17 00:00:00 2001 From: Romain Date: Tue, 27 May 2025 14:32:04 +0200 Subject: [PATCH 15/21] Prepare release v3.4.1 --- CHANGELOG.md | 23 +++++++++++++++++++++++ script/gcg/traefik-bugfix.toml | 10 +++++----- 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 17f7b697d..4ce5dde11 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,26 @@ +## [v3.4.1](https://github.com/traefik/traefik/tree/v3.4.1) (2025-05-27) +[All Commits](https://github.com/traefik/traefik/compare/v3.4.0...v3.4.1) + +**Bug fixes:** +- **[docker]** Do not warn network missing if connected to a container network ([#11698](https://github.com/traefik/traefik/pull/11698) by [holysoles](https://github.com/holysoles)) +- **[k8s/crd]** Fix CEL validation for RootCA in ServersTransport ([#11775](https://github.com/traefik/traefik/pull/11775) by [rtribotte](https://github.com/rtribotte)) +- **[middleware]** Scope the rate limit counter key by source and by middleware ([#11753](https://github.com/traefik/traefik/pull/11753) by [aromeyer](https://github.com/aromeyer)) +- **[server]** Use routing path in v3 matchers ([#11790](https://github.com/traefik/traefik/pull/11790) by [kevinpollet](https://github.com/kevinpollet)) +- **[service]** Make P2C strategy thread-safe ([#11762](https://github.com/traefik/traefik/pull/11762) by [lbenguigui](https://github.com/lbenguigui)) +- **[webui]** Do not display RemoveHeader option when not defined ([#11782](https://github.com/traefik/traefik/pull/11782) by [kevinpollet](https://github.com/kevinpollet)) + +**Documentation:** +- **[acme]** Fix ambiguous wording in ACME page ([#11789](https://github.com/traefik/traefik/pull/11789) by [joshka](https://github.com/joshka)) +- **[k8s]** Fix incorrect case and missing rbac in documentation ([#11742](https://github.com/traefik/traefik/pull/11742) by [mmatur](https://github.com/mmatur)) +- **[middleware]** Match encoded certificate to example data for TLS passthrough ([#11759](https://github.com/traefik/traefik/pull/11759) by [holysoles](https://github.com/holysoles)) + +**Misc:** +- Merge branch v2.11 into v3.4 ([#11799](https://github.com/traefik/traefik/pull/11799) by [kevinpollet](https://github.com/kevinpollet)) +- Merge branch v2.11 into v3.4 ([#11796](https://github.com/traefik/traefik/pull/11796) by [kevinpollet](https://github.com/kevinpollet)) +- Merge branch v2.11 into v3.4 ([#11783](https://github.com/traefik/traefik/pull/11783) by [kevinpollet](https://github.com/kevinpollet)) +- Merge branch v2.11 into v3.4 ([#11757](https://github.com/traefik/traefik/pull/11757) by [mmatur](https://github.com/mmatur)) +- Merge v2.11 into v3.4 ([#11751](https://github.com/traefik/traefik/pull/11751) by [mmatur](https://github.com/mmatur)) + ## [v2.11.25](https://github.com/traefik/traefik/tree/v2.11.25) (2025-05-27) [All Commits](https://github.com/traefik/traefik/compare/v2.11.24...v2.11.25) diff --git a/script/gcg/traefik-bugfix.toml b/script/gcg/traefik-bugfix.toml index 18f1a7c29..5e4a617a5 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 v3.3.7 -CurrentRef = "v3.3" -PreviousRef = "v3.3.6" -BaseBranch = "v3.3" -FutureCurrentRefName = "v3.3.7" +# example new bugfix v3.4.1 +CurrentRef = "v3.4" +PreviousRef = "v3.4.0" +BaseBranch = "v3.4" +FutureCurrentRefName = "v3.4.1" ThresholdPreviousRef = 10 ThresholdCurrentRef = 10 From 6a54f1f66ce6a760a605c40d07d7ff2deec3898d Mon Sep 17 00:00:00 2001 From: Corey Date: Wed, 28 May 2025 04:46:04 -0500 Subject: [PATCH 16/21] Add WebSocket guide --- docs/content/user-guides/websocket.md | 355 ++++++++++++++++++++++++++ docs/mkdocs.yml | 1 + 2 files changed, 356 insertions(+) create mode 100644 docs/content/user-guides/websocket.md diff --git a/docs/content/user-guides/websocket.md b/docs/content/user-guides/websocket.md new file mode 100644 index 000000000..cb122d90f --- /dev/null +++ b/docs/content/user-guides/websocket.md @@ -0,0 +1,355 @@ +--- +title: "Traefik WebSocket Documentation" +description: "How to configure WebSocket and WebSocket Secure (WSS) connections with Traefik Proxy." +--- + +# WebSocket + +Configuring Traefik to handle WebSocket and WebSocket Secure (WSS) connections. +{: .subtitle } + +## Overview + +WebSocket is a communication protocol that provides full-duplex communication channels over a single TCP connection. +WebSocket Secure (WSS) is the encrypted version of WebSocket, using TLS/SSL encryption. + +Traefik supports WebSocket and WebSocket Secure (WSS) out of the box. This guide will walk through examples of how to configure Traefik for different WebSocket scenarios. + +## Basic WebSocket Configuration + +A basic WebSocket configuration only requires defining a router and a service that points to your WebSocket server. + +```yaml tab="Docker & Swarm" +labels: + - "traefik.http.routers.my-websocket.rule=Host(`ws.example.com`)" + - "traefik.http.routers.my-websocket.service=my-websocket-service" + - "traefik.http.services.my-websocket-service.loadbalancer.server.port=8000" +``` + +```yaml tab="Kubernetes" +apiVersion: traefik.io/v1alpha1 +kind: IngressRoute +metadata: + name: my-websocket-route +spec: + entryPoints: + - web + routes: + - match: Host(`ws.example.com`) + kind: Rule + services: + - name: my-websocket-service + port: 8000 +``` + +```yaml tab="File (YAML)" +http: + routers: + my-websocket: + rule: "Host(`ws.example.com`)" + service: my-websocket-service + + services: + my-websocket-service: + loadBalancer: + servers: + - url: "http://my-websocket-server:8000" +``` + +```toml tab="File (TOML)" +[http.routers] + [http.routers.my-websocket] + rule = "Host(`ws.example.com`)" + service = "my-websocket-service" + +[http.services] + [http.services.my-websocket-service] + [http.services.my-websocket-service.loadBalancer] + [[http.services.my-websocket-service.loadBalancer.servers]] + url = "http://my-websocket-server:8000" +``` + +## WebSocket Secure (WSS) Configuration + +WebSocket Secure (WSS) requires TLS configuration. +The client connects using the `wss://` protocol instead of `ws://`. + +```yaml tab="Docker & Swarm" +labels: + - "traefik.http.routers.my-websocket-secure.rule=Host(`wss.example.com`)" + - "traefik.http.routers.my-websocket-secure.service=my-websocket-service" + - "traefik.http.routers.my-websocket-secure.tls=true" + - "traefik.http.services.my-websocket-service.loadbalancer.server.port=8000" +``` + +```yaml tab="Kubernetes" +apiVersion: traefik.io/v1alpha1 +kind: IngressRoute +metadata: + name: my-websocket-secure-route +spec: + entryPoints: + - websecure + routes: + - match: Host(`wss.example.com`) + kind: Rule + services: + - name: my-websocket-service + port: 8000 + tls: {} +``` + +```yaml tab="File (YAML)" +http: + routers: + my-websocket-secure: + rule: "Host(`wss.example.com`)" + service: my-websocket-service + tls: {} + + services: + my-websocket-service: + loadBalancer: + servers: + - url: "http://my-websocket-server:8000" +``` + +```toml tab="File (TOML)" +[http.routers] + [http.routers.my-websocket-secure] + rule = "Host(`wss.example.com`)" + service = "my-websocket-service" + [http.routers.my-websocket-secure.tls] + +[http.services] + [http.services.my-websocket-service] + [http.services.my-websocket-service.loadBalancer] + [[http.services.my-websocket-service.loadBalancer.servers]] + url = "http://my-websocket-server:8000" +``` + +## SSL Termination for WebSockets + +In this scenario, clients connect to Traefik using WSS (encrypted), but Traefik connects to your backend server using WS (unencrypted). +This is called SSL termination. + +```yaml tab="Docker & Swarm" +labels: + - "traefik.http.routers.my-wss-termination.rule=Host(`wss.example.com`)" + - "traefik.http.routers.my-wss-termination.service=my-ws-service" + - "traefik.http.routers.my-wss-termination.tls=true" + - "traefik.http.services.my-ws-service.loadbalancer.server.port=8000" +``` + +```yaml tab="Kubernetes" +apiVersion: traefik.io/v1alpha1 +kind: IngressRoute +metadata: + name: my-wss-termination-route +spec: + entryPoints: + - websecure + routes: + - match: Host(`wss.example.com`) + kind: Rule + services: + - name: my-ws-service + port: 8000 + tls: {} +``` + +```yaml tab="File (YAML)" +http: + routers: + my-wss-termination: + rule: "Host(`wss.example.com`)" + service: my-ws-service + tls: {} + + services: + my-ws-service: + loadBalancer: + servers: + - url: "http://my-ws-server:8000" +``` + +```toml tab="File (TOML)" +[http.routers] + [http.routers.my-wss-termination] + rule = "Host(`wss.example.com`)" + service = "my-ws-service" + [http.routers.my-wss-termination.tls] + +[http.services] + [http.services.my-ws-service] + [http.services.my-ws-service.loadBalancer] + [[http.services.my-ws-service.loadBalancer.servers]] + url = "http://my-ws-server:8000" +``` + +## End-to-End WebSocket Secure (WSS) + +For end-to-end encryption, Traefik can be configured to connect to your backend using HTTPS. + +```yaml tab="Docker & Swarm" +labels: + - "traefik.http.routers.my-wss-e2e.rule=Host(`wss.example.com`)" + - "traefik.http.routers.my-wss-e2e.service=my-wss-service" + - "traefik.http.routers.my-wss-e2e.tls=true" + - "traefik.http.services.my-wss-service.loadbalancer.server.port=8443" + # If the backend uses a self-signed certificate + - "traefik.http.serversTransports.insecureTransport.insecureSkipVerify=true" + - "traefik.http.services.my-wss-service.loadBalancer.serversTransport=insecureTransport" +``` + +```yaml tab="Kubernetes" +apiVersion: traefik.io/v1alpha1 +kind: ServersTransport +metadata: + name: insecure-transport +spec: + insecureSkipVerify: true + +--- +apiVersion: traefik.io/v1alpha1 +kind: IngressRoute +metadata: + name: my-wss-e2e-route +spec: + entryPoints: + - websecure + routes: + - match: Host(`wss.example.com`) + kind: Rule + services: + - name: my-wss-service + port: 8443 + serversTransport: insecure-transport + tls: {} +``` + +```yaml tab="File (YAML)" +http: + serversTransports: + insecureTransport: + insecureSkipVerify: true + + routers: + my-wss-e2e: + rule: "Host(`wss.example.com`)" + service: my-wss-service + tls: {} + + services: + my-wss-service: + loadBalancer: + serversTransport: insecureTransport + servers: + - url: "https://my-wss-server:8443" +``` + +```toml tab="File (TOML)" +[http.serversTransports] + [http.serversTransports.insecureTransport] + insecureSkipVerify = true + +[http.routers] + [http.routers.my-wss-e2e] + rule = "Host(`wss.example.com`)" + service = "my-wss-service" + [http.routers.my-wss-e2e.tls] + +[http.services] + [http.services.my-wss-service] + [http.services.my-wss-service.loadBalancer] + serversTransport = "insecureTransport" + [[http.services.my-wss-service.loadBalancer.servers]] + url = "https://my-wss-server:8443" +``` + +## EntryPoints Configuration for WebSockets + +In your Traefik static configuration, you'll need to define entryPoints for both WS and WSS: + +```yaml tab="File (YAML)" +entryPoints: + web: + address: ":80" + websecure: + address: ":443" +``` + +```toml tab="File (TOML)" +[entryPoints] + [entryPoints.web] + address = ":80" + [entryPoints.websecure] + address = ":443" +``` + +## Testing WebSocket Connections + +You can test your WebSocket configuration using various tools: + +1. Browser Developer Tools: Most modern browsers include WebSocket debugging in their developer tools. +2. WebSocket client tools like [wscat](https://github.com/websockets/wscat) or online tools like [Piesocket's WebSocket Tester](https://www.piesocket.com/websocket-tester). + +Example wscat commands: + +```bash +# Test standard WebSocket +wscat -c ws://ws.example.com + +# Test WebSocket Secure +wscat -c wss://wss.example.com +``` + +## Common Issues and Solutions + +### Headers and Origin Checks + +Some WebSocket servers implement origin checking. Traefik passes the original headers to your backend, including the `Origin` header. + +If you need to manipulate headers for WebSocket connections, you can use Traefik's Headers middleware: + +```yaml tab="Docker & Swarm" +labels: + - "traefik.http.middlewares.my-headers.headers.customrequestheaders.Origin=https://allowed-origin.com" + - "traefik.http.routers.my-websocket.middlewares=my-headers" +``` + +```yaml tab="Kubernetes" +apiVersion: traefik.io/v1alpha1 +kind: Middleware +metadata: + name: my-headers +spec: + headers: + customRequestHeaders: + Origin: "https://allowed-origin.com" + +--- +apiVersion: traefik.io/v1alpha1 +kind: IngressRoute +metadata: + name: my-websocket-route +spec: + routes: + - match: Host(`ws.example.com`) + kind: Rule + middlewares: + - name: my-headers + services: + - name: my-websocket-service + port: 8000 +``` + +### Certificate Issues with WSS + +If you're experiencing certificate issues with WSS: + +1. Ensure your certificates are valid and not expired +2. For testing with self-signed certificates, configure your clients to accept them +3. When using Let's Encrypt, ensure your domain is properly configured + +For backends with self-signed certificates, use the `insecureSkipVerify` option in the ServersTransport configuration as shown in the examples above. diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index fe10e3740..d8d50cd10 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -171,6 +171,7 @@ nav: - 'Kubernetes and Let''s Encrypt': 'user-guides/crd-acme/index.md' - 'Kubernetes and cert-manager': 'user-guides/cert-manager.md' - 'gRPC Examples': 'user-guides/grpc.md' + - 'WebSocket Examples': 'user-guides/websocket.md' - 'Docker': - 'Basic Example': 'user-guides/docker-compose/basic-example/index.md' - 'HTTPS with Let''s Encrypt': From 0b4058dde0cfc522a453c8b7ef45e31a09ef60da Mon Sep 17 00:00:00 2001 From: Thomas Mauran <78204354+thomas-mauran@users.noreply.github.com> Date: Wed, 28 May 2025 17:16:08 +0200 Subject: [PATCH 17/21] Remove obsolete version field in compose files --- docs/content/getting-started/quick-start.md | 4 ---- docs/content/observability/access-logs.md | 2 -- docs/content/providers/docker.md | 3 --- docs/content/providers/swarm.md | 5 ----- .../observability/logs-and-accesslogs.md | 2 -- .../reference/install-configuration/providers/docker.md | 3 --- .../reference/install-configuration/providers/swarm.md | 5 ----- .../routing-configuration/dynamic-configuration-methods.md | 2 -- .../routing-configuration/other-providers/docker.md | 3 --- .../reference/routing-configuration/other-providers/swarm.md | 3 --- docs/content/routing/providers/docker.md | 3 --- docs/content/routing/providers/swarm.md | 3 --- .../user-guides/docker-compose/acme-dns/docker-compose.yml | 2 -- .../docker-compose/acme-dns/docker-compose_secrets.yml | 2 -- .../user-guides/docker-compose/acme-http/docker-compose.yml | 2 -- .../user-guides/docker-compose/acme-tls/docker-compose.yml | 2 -- .../docker-compose/basic-example/docker-compose.yml | 2 -- .../user-guides/docker-compose/basic-example/index.md | 2 -- integration/resources/compose/access_log.yml | 1 - integration/resources/compose/allowlist.yml | 1 - integration/resources/compose/base.yml | 1 - integration/resources/compose/consul.yml | 1 - integration/resources/compose/consul_catalog.yml | 1 - integration/resources/compose/docker.yml | 1 - integration/resources/compose/error_pages.yml | 1 - integration/resources/compose/etcd.yml | 1 - integration/resources/compose/file.yml | 1 - integration/resources/compose/healthcheck.yml | 1 - integration/resources/compose/hostresolver.yml | 1 - integration/resources/compose/k8s.yml | 1 - integration/resources/compose/minimal.yml | 1 - integration/resources/compose/pebble.yml | 1 - integration/resources/compose/proxy-protocol.yml | 1 - integration/resources/compose/ratelimit.yml | 1 - integration/resources/compose/redis.yml | 1 - integration/resources/compose/redis_sentinel.yml | 1 - integration/resources/compose/reqacceptgrace.yml | 1 - integration/resources/compose/rest.yml | 1 - integration/resources/compose/retry.yml | 1 - integration/resources/compose/stats.yml | 1 - integration/resources/compose/tailscale.yml | 1 - integration/resources/compose/tcp.yml | 1 - integration/resources/compose/timeout.yml | 1 - integration/resources/compose/tlsclientheaders.yml | 1 - integration/resources/compose/tracing.yml | 1 - integration/resources/compose/udp.yml | 1 - integration/resources/compose/whitelist.yml | 1 - integration/resources/compose/zookeeper.yml | 1 - 48 files changed, 80 deletions(-) diff --git a/docs/content/getting-started/quick-start.md b/docs/content/getting-started/quick-start.md index a06bba0b1..4553a534f 100644 --- a/docs/content/getting-started/quick-start.md +++ b/docs/content/getting-started/quick-start.md @@ -15,8 +15,6 @@ A Use Case Using Docker Create a `docker-compose.yml` file where you will define a `reverse-proxy` service that uses the official Traefik image: ```yaml -version: '3' - services: reverse-proxy: # The official v3 Traefik docker image @@ -50,8 +48,6 @@ Now that you have a Traefik instance up and running, you will deploy new service Edit your `docker-compose.yml` file and add the following at the end of your file. ```yaml -version: '3' - services: ... diff --git a/docs/content/observability/access-logs.md b/docs/content/observability/access-logs.md index 4a74b814b..b2177d2ae 100644 --- a/docs/content/observability/access-logs.md +++ b/docs/content/observability/access-logs.md @@ -288,8 +288,6 @@ It is possible to configure the Traefik to timestamp in a specific timezone by e Example utilizing Docker Compose: ```yaml -version: "3.7" - services: traefik: image: traefik:v3.4 diff --git a/docs/content/providers/docker.md b/docs/content/providers/docker.md index e164ae349..df77e5e01 100644 --- a/docs/content/providers/docker.md +++ b/docs/content/providers/docker.md @@ -40,7 +40,6 @@ This provider works with [Docker (standalone) Engine](https://docs.docker.com/en Attaching labels to containers (in your docker compose file) ```yaml - version: "3" services: my-container: # ... @@ -162,8 +161,6 @@ See the [Docker API Access](#docker-api-access) section for more information. The docker-compose file shares the docker sock with the Traefik container ```yaml - version: '3' - services: traefik: image: traefik:v3.4 # The official v3 Traefik docker image diff --git a/docs/content/providers/swarm.md b/docs/content/providers/swarm.md index c5c158ba8..d1db245df 100644 --- a/docs/content/providers/swarm.md +++ b/docs/content/providers/swarm.md @@ -53,7 +53,6 @@ This provider works with [Docker Swarm Mode](https://docs.docker.com/engine/swar then that service is automatically assigned to the router. ```yaml - version: "3" services: my-container: deploy: @@ -176,8 +175,6 @@ docker service create \ ``` ```yml tab="With Docker Compose" -version: '3' - services: traefik: # ... @@ -208,8 +205,6 @@ See the [Docker Swarm API Access](#docker-api-access) section for more informati The docker-compose file shares the docker sock with the Traefik container ```yaml - version: '3' - services: traefik: image: traefik:v3.4 # The official v3 Traefik docker image diff --git a/docs/content/reference/install-configuration/observability/logs-and-accesslogs.md b/docs/content/reference/install-configuration/observability/logs-and-accesslogs.md index d13226ad4..d610fae4d 100644 --- a/docs/content/reference/install-configuration/observability/logs-and-accesslogs.md +++ b/docs/content/reference/install-configuration/observability/logs-and-accesslogs.md @@ -199,8 +199,6 @@ It is possible to configure the Traefik to timestamp in a specific timezone by e Example utilizing Docker Compose: ```yaml -version: "3.7" - services: traefik: image: traefik:v3.4 diff --git a/docs/content/reference/install-configuration/providers/docker.md b/docs/content/reference/install-configuration/providers/docker.md index 74e250ae0..82463e8fc 100644 --- a/docs/content/reference/install-configuration/providers/docker.md +++ b/docs/content/reference/install-configuration/providers/docker.md @@ -29,7 +29,6 @@ providers: Attach labels to containers (in your Docker compose file) ```yaml -version: "3" services: my-container: # ... @@ -67,8 +66,6 @@ See the [Docker API Access](#docker-api-access) section for more information. The docker-compose file shares the docker sock with the Traefik container ```yaml - version: '3' - services: traefik: image: traefik:v3.1 # The official v3 Traefik docker image diff --git a/docs/content/reference/install-configuration/providers/swarm.md b/docs/content/reference/install-configuration/providers/swarm.md index becfdd577..92ac6a562 100644 --- a/docs/content/reference/install-configuration/providers/swarm.md +++ b/docs/content/reference/install-configuration/providers/swarm.md @@ -33,7 +33,6 @@ When there is only one service, and the router does not specify a service, then that service is automatically assigned to the router. ```yaml tab="Labels" -version: "3" services: my-container: deploy: @@ -73,8 +72,6 @@ See the [Docker Swarm API Access](#docker-api-access) section for more informati The docker-compose file shares the docker sock with the Traefik container ```yaml - version: '3' - services: traefik: image: traefik:v3.1 # The official v3 Traefik docker image @@ -405,8 +402,6 @@ docker service create \ ``` ```yml tab="With Docker Compose" -version: '3' - services: traefik: # ... diff --git a/docs/content/reference/routing-configuration/dynamic-configuration-methods.md b/docs/content/reference/routing-configuration/dynamic-configuration-methods.md index cb158d0b2..20b30222e 100644 --- a/docs/content/reference/routing-configuration/dynamic-configuration-methods.md +++ b/docs/content/reference/routing-configuration/dynamic-configuration-methods.md @@ -72,8 +72,6 @@ When using Docker or Amazon ECS, you can define routing configuration using cont When deploying a Docker container, you can specify labels to define routing rules and services: ```yaml - version: '3' - services: my-service: image: my-image diff --git a/docs/content/reference/routing-configuration/other-providers/docker.md b/docs/content/reference/routing-configuration/other-providers/docker.md index e17c9fc91..fb3cb0445 100644 --- a/docs/content/reference/routing-configuration/other-providers/docker.md +++ b/docs/content/reference/routing-configuration/other-providers/docker.md @@ -35,7 +35,6 @@ With Docker, Traefik can leverage labels attached to a container to generate rou Attaching labels to containers (in your docker compose file) ```yaml - version: "3" services: my-container: # ... @@ -48,7 +47,6 @@ With Docker, Traefik can leverage labels attached to a container to generate rou Forward requests for `http://example.com` to `http://:12345`: ```yaml - version: "3" services: my-container: # ... @@ -71,7 +69,6 @@ With Docker, Traefik can leverage labels attached to a container to generate rou In this example, requests are forwarded for `http://example-a.com` to `http://:8000` in addition to `http://example-b.com` forwarding to `http://:9000`: ```yaml - version: "3" services: my-container: # ... diff --git a/docs/content/reference/routing-configuration/other-providers/swarm.md b/docs/content/reference/routing-configuration/other-providers/swarm.md index 51b2371c0..824bb1470 100644 --- a/docs/content/reference/routing-configuration/other-providers/swarm.md +++ b/docs/content/reference/routing-configuration/other-providers/swarm.md @@ -48,7 +48,6 @@ With Docker Swarm, Traefik can leverage labels attached to a service to generate then that service is automatically assigned to the router. ```yaml - version: "3" services: my-container: deploy: @@ -67,7 +66,6 @@ With Docker Swarm, Traefik can leverage labels attached to a service to generate Forward requests for `http://example.com` to `http://:12345`: ```yaml - version: "3" services: my-container: # ... @@ -93,7 +91,6 @@ With Docker Swarm, Traefik can leverage labels attached to a service to generate In this example, requests are forwarded for `http://example-a.com` to `http://:8000` in addition to `http://example-b.com` forwarding to `http://:9000`: ```yaml - version: "3" services: my-container: # ... diff --git a/docs/content/routing/providers/docker.md b/docs/content/routing/providers/docker.md index aedbfe09f..29b180388 100644 --- a/docs/content/routing/providers/docker.md +++ b/docs/content/routing/providers/docker.md @@ -42,7 +42,6 @@ With Docker, Traefik can leverage labels attached to a container to generate rou Attaching labels to containers (in your docker compose file) ```yaml - version: "3" services: my-container: # ... @@ -55,7 +54,6 @@ With Docker, Traefik can leverage labels attached to a container to generate rou Forward requests for `http://example.com` to `http://:12345`: ```yaml - version: "3" services: my-container: # ... @@ -78,7 +76,6 @@ With Docker, Traefik can leverage labels attached to a container to generate rou In this example, requests are forwarded for `http://example-a.com` to `http://:8000` in addition to `http://example-b.com` forwarding to `http://:9000`: ```yaml - version: "3" services: my-container: # ... diff --git a/docs/content/routing/providers/swarm.md b/docs/content/routing/providers/swarm.md index bd9685ef6..915a57a6d 100644 --- a/docs/content/routing/providers/swarm.md +++ b/docs/content/routing/providers/swarm.md @@ -55,7 +55,6 @@ With Docker Swarm, Traefik can leverage labels attached to a service to generate then that service is automatically assigned to the router. ```yaml - version: "3" services: my-container: deploy: @@ -74,7 +73,6 @@ With Docker Swarm, Traefik can leverage labels attached to a service to generate Forward requests for `http://example.com` to `http://:12345`: ```yaml - version: "3" services: my-container: # ... @@ -100,7 +98,6 @@ With Docker Swarm, Traefik can leverage labels attached to a service to generate In this example, requests are forwarded for `http://example-a.com` to `http://:8000` in addition to `http://example-b.com` forwarding to `http://:9000`: ```yaml - version: "3" services: my-container: # ... diff --git a/docs/content/user-guides/docker-compose/acme-dns/docker-compose.yml b/docs/content/user-guides/docker-compose/acme-dns/docker-compose.yml index d276dc41e..b81bcd495 100644 --- a/docs/content/user-guides/docker-compose/acme-dns/docker-compose.yml +++ b/docs/content/user-guides/docker-compose/acme-dns/docker-compose.yml @@ -1,5 +1,3 @@ -version: "3.3" - services: traefik: diff --git a/docs/content/user-guides/docker-compose/acme-dns/docker-compose_secrets.yml b/docs/content/user-guides/docker-compose/acme-dns/docker-compose_secrets.yml index ce629d8f2..74d70f749 100644 --- a/docs/content/user-guides/docker-compose/acme-dns/docker-compose_secrets.yml +++ b/docs/content/user-guides/docker-compose/acme-dns/docker-compose_secrets.yml @@ -1,5 +1,3 @@ -version: "3.3" - secrets: ovh_endpoint: file: "./secrets/ovh_endpoint.secret" diff --git a/docs/content/user-guides/docker-compose/acme-http/docker-compose.yml b/docs/content/user-guides/docker-compose/acme-http/docker-compose.yml index 82c248a68..6f54ab79c 100644 --- a/docs/content/user-guides/docker-compose/acme-http/docker-compose.yml +++ b/docs/content/user-guides/docker-compose/acme-http/docker-compose.yml @@ -1,5 +1,3 @@ -version: "3.3" - services: traefik: diff --git a/docs/content/user-guides/docker-compose/acme-tls/docker-compose.yml b/docs/content/user-guides/docker-compose/acme-tls/docker-compose.yml index e3faaa3ce..6e2b3fa72 100644 --- a/docs/content/user-guides/docker-compose/acme-tls/docker-compose.yml +++ b/docs/content/user-guides/docker-compose/acme-tls/docker-compose.yml @@ -1,5 +1,3 @@ -version: "3.3" - services: traefik: diff --git a/docs/content/user-guides/docker-compose/basic-example/docker-compose.yml b/docs/content/user-guides/docker-compose/basic-example/docker-compose.yml index 3e9f68fa7..2491de7dc 100644 --- a/docs/content/user-guides/docker-compose/basic-example/docker-compose.yml +++ b/docs/content/user-guides/docker-compose/basic-example/docker-compose.yml @@ -1,5 +1,3 @@ -version: "3.3" - services: traefik: diff --git a/docs/content/user-guides/docker-compose/basic-example/index.md b/docs/content/user-guides/docker-compose/basic-example/index.md index f6d395b4a..35a5fe39f 100644 --- a/docs/content/user-guides/docker-compose/basic-example/index.md +++ b/docs/content/user-guides/docker-compose/basic-example/index.md @@ -23,8 +23,6 @@ Create a `docker-compose.yml` file with the following content: You can use a [pre-existing network](https://docs.docker.com/compose/networking/#use-a-pre-existing-network "Link to Docker Compose networking docs") too. ```yaml - version: "3.3" - networks: traefiknet: {} diff --git a/integration/resources/compose/access_log.yml b/integration/resources/compose/access_log.yml index c5be8f9e4..a5e4f5d44 100644 --- a/integration/resources/compose/access_log.yml +++ b/integration/resources/compose/access_log.yml @@ -1,4 +1,3 @@ -version: "3.8" services: server0: image: traefik/whoami diff --git a/integration/resources/compose/allowlist.yml b/integration/resources/compose/allowlist.yml index 0fd241322..f5cd10680 100644 --- a/integration/resources/compose/allowlist.yml +++ b/integration/resources/compose/allowlist.yml @@ -1,4 +1,3 @@ -version: "3.8" services: noOverrideAllowlist: image: traefik/whoami diff --git a/integration/resources/compose/base.yml b/integration/resources/compose/base.yml index 2d6380051..54a5f2f8e 100644 --- a/integration/resources/compose/base.yml +++ b/integration/resources/compose/base.yml @@ -1,4 +1,3 @@ -version: "3.8" services: whoami1: image: traefik/whoami diff --git a/integration/resources/compose/consul.yml b/integration/resources/compose/consul.yml index 041ba0b45..0d1f343ba 100644 --- a/integration/resources/compose/consul.yml +++ b/integration/resources/compose/consul.yml @@ -1,4 +1,3 @@ -version: "3.8" services: consul: image: consul:1.6 diff --git a/integration/resources/compose/consul_catalog.yml b/integration/resources/compose/consul_catalog.yml index 6a0fd279c..949333a39 100644 --- a/integration/resources/compose/consul_catalog.yml +++ b/integration/resources/compose/consul_catalog.yml @@ -1,4 +1,3 @@ -version: "3.8" services: consul: image: consul:1.6.2 diff --git a/integration/resources/compose/docker.yml b/integration/resources/compose/docker.yml index b16571a4b..7ee4492cf 100644 --- a/integration/resources/compose/docker.yml +++ b/integration/resources/compose/docker.yml @@ -1,4 +1,3 @@ -version: "3.8" services: simple: image: swarm:1.0.0 diff --git a/integration/resources/compose/error_pages.yml b/integration/resources/compose/error_pages.yml index 03cc13f80..3f2c40e6b 100644 --- a/integration/resources/compose/error_pages.yml +++ b/integration/resources/compose/error_pages.yml @@ -1,4 +1,3 @@ -version: "3.8" services: nginx1: image: nginx:1.25.3-alpine3.18 diff --git a/integration/resources/compose/etcd.yml b/integration/resources/compose/etcd.yml index 6a3b34fe9..02529171a 100644 --- a/integration/resources/compose/etcd.yml +++ b/integration/resources/compose/etcd.yml @@ -1,4 +1,3 @@ -version: "3.8" services: etcd: image: quay.io/coreos/etcd:v3.5.14 diff --git a/integration/resources/compose/file.yml b/integration/resources/compose/file.yml index 52e973e53..3e37129b2 100644 --- a/integration/resources/compose/file.yml +++ b/integration/resources/compose/file.yml @@ -1,4 +1,3 @@ -version: "3.8" services: whoami1: image: traefik/whoami diff --git a/integration/resources/compose/healthcheck.yml b/integration/resources/compose/healthcheck.yml index 9419f4bce..ee9be620f 100644 --- a/integration/resources/compose/healthcheck.yml +++ b/integration/resources/compose/healthcheck.yml @@ -1,4 +1,3 @@ -version: "3.8" services: whoami1: image: traefik/whoami diff --git a/integration/resources/compose/hostresolver.yml b/integration/resources/compose/hostresolver.yml index 680962006..424b832ce 100644 --- a/integration/resources/compose/hostresolver.yml +++ b/integration/resources/compose/hostresolver.yml @@ -1,4 +1,3 @@ -version: "3.8" services: server1: image: traefik/whoami diff --git a/integration/resources/compose/k8s.yml b/integration/resources/compose/k8s.yml index f14b7abdb..51e46b0be 100644 --- a/integration/resources/compose/k8s.yml +++ b/integration/resources/compose/k8s.yml @@ -1,4 +1,3 @@ -version: "3.8" services: server: image: rancher/k3s:v1.21.14-k3s1 diff --git a/integration/resources/compose/minimal.yml b/integration/resources/compose/minimal.yml index acb12804e..911b12d38 100644 --- a/integration/resources/compose/minimal.yml +++ b/integration/resources/compose/minimal.yml @@ -1,4 +1,3 @@ -version: "3.8" services: whoami1: image: traefik/whoami diff --git a/integration/resources/compose/pebble.yml b/integration/resources/compose/pebble.yml index f39dc1378..4e9eac58b 100644 --- a/integration/resources/compose/pebble.yml +++ b/integration/resources/compose/pebble.yml @@ -1,4 +1,3 @@ -version: "3.8" services: pebble: image: letsencrypt/pebble:v2.3.1 diff --git a/integration/resources/compose/proxy-protocol.yml b/integration/resources/compose/proxy-protocol.yml index 8fa69a9ba..3d8339330 100644 --- a/integration/resources/compose/proxy-protocol.yml +++ b/integration/resources/compose/proxy-protocol.yml @@ -1,4 +1,3 @@ -version: "3.8" services: whoami: image: traefik/whoami diff --git a/integration/resources/compose/ratelimit.yml b/integration/resources/compose/ratelimit.yml index 5d9d6ec1e..414aff839 100644 --- a/integration/resources/compose/ratelimit.yml +++ b/integration/resources/compose/ratelimit.yml @@ -1,4 +1,3 @@ -version: "3.8" services: whoami1: image: traefik/whoami diff --git a/integration/resources/compose/redis.yml b/integration/resources/compose/redis.yml index 09bbeacad..bc5e657b3 100644 --- a/integration/resources/compose/redis.yml +++ b/integration/resources/compose/redis.yml @@ -1,4 +1,3 @@ -version: "3.8" services: redis: image: redis:5.0 diff --git a/integration/resources/compose/redis_sentinel.yml b/integration/resources/compose/redis_sentinel.yml index 1737c2a1a..3f76aa12f 100644 --- a/integration/resources/compose/redis_sentinel.yml +++ b/integration/resources/compose/redis_sentinel.yml @@ -1,4 +1,3 @@ -version: "3.8" services: master: image: redis diff --git a/integration/resources/compose/reqacceptgrace.yml b/integration/resources/compose/reqacceptgrace.yml index 8fa69a9ba..3d8339330 100644 --- a/integration/resources/compose/reqacceptgrace.yml +++ b/integration/resources/compose/reqacceptgrace.yml @@ -1,4 +1,3 @@ -version: "3.8" services: whoami: image: traefik/whoami diff --git a/integration/resources/compose/rest.yml b/integration/resources/compose/rest.yml index 251568165..fd0dedb04 100644 --- a/integration/resources/compose/rest.yml +++ b/integration/resources/compose/rest.yml @@ -1,4 +1,3 @@ -version: "3.8" services: whoami1: image: traefik/whoami diff --git a/integration/resources/compose/retry.yml b/integration/resources/compose/retry.yml index 8fa69a9ba..3d8339330 100644 --- a/integration/resources/compose/retry.yml +++ b/integration/resources/compose/retry.yml @@ -1,4 +1,3 @@ -version: "3.8" services: whoami: image: traefik/whoami diff --git a/integration/resources/compose/stats.yml b/integration/resources/compose/stats.yml index 599fed311..206fa5187 100644 --- a/integration/resources/compose/stats.yml +++ b/integration/resources/compose/stats.yml @@ -1,4 +1,3 @@ -version: "3.8" services: whoami1: image: traefik/whoami diff --git a/integration/resources/compose/tailscale.yml b/integration/resources/compose/tailscale.yml index dbad56561..70e5d796c 100644 --- a/integration/resources/compose/tailscale.yml +++ b/integration/resources/compose/tailscale.yml @@ -1,4 +1,3 @@ -version: "3.8" services: tailscaled: hostname: traefik-tests-gw # This will become the tailscale device name diff --git a/integration/resources/compose/tcp.yml b/integration/resources/compose/tcp.yml index cd7fc0627..14e2d413c 100644 --- a/integration/resources/compose/tcp.yml +++ b/integration/resources/compose/tcp.yml @@ -1,4 +1,3 @@ -version: "3.8" services: whoami-a: image: traefik/whoamitcp diff --git a/integration/resources/compose/timeout.yml b/integration/resources/compose/timeout.yml index 6e415b9b0..de629c4db 100644 --- a/integration/resources/compose/timeout.yml +++ b/integration/resources/compose/timeout.yml @@ -1,4 +1,3 @@ -version: "3.8" services: timeoutEndpoint: image: yaman/timeout diff --git a/integration/resources/compose/tlsclientheaders.yml b/integration/resources/compose/tlsclientheaders.yml index ef16f5f36..ae03ecbae 100644 --- a/integration/resources/compose/tlsclientheaders.yml +++ b/integration/resources/compose/tlsclientheaders.yml @@ -1,4 +1,3 @@ -version: "3.8" services: whoami: image: traefik/whoami diff --git a/integration/resources/compose/tracing.yml b/integration/resources/compose/tracing.yml index 314dd66dc..8a2a6d514 100644 --- a/integration/resources/compose/tracing.yml +++ b/integration/resources/compose/tracing.yml @@ -1,4 +1,3 @@ -version: "3.8" services: tempo: hostname: tempo diff --git a/integration/resources/compose/udp.yml b/integration/resources/compose/udp.yml index ce2633199..0c001c829 100644 --- a/integration/resources/compose/udp.yml +++ b/integration/resources/compose/udp.yml @@ -1,4 +1,3 @@ -version: "3.8" services: whoami-a: image: traefik/whoamiudp:latest diff --git a/integration/resources/compose/whitelist.yml b/integration/resources/compose/whitelist.yml index 790ce52b7..ee08b935e 100644 --- a/integration/resources/compose/whitelist.yml +++ b/integration/resources/compose/whitelist.yml @@ -1,4 +1,3 @@ -version: "3.8" services: noOverrideWhitelist: image: traefik/whoami diff --git a/integration/resources/compose/zookeeper.yml b/integration/resources/compose/zookeeper.yml index 9861c1437..b086f24f3 100644 --- a/integration/resources/compose/zookeeper.yml +++ b/integration/resources/compose/zookeeper.yml @@ -1,4 +1,3 @@ -version: "3.8" services: zookeeper: image: zookeeper:3.5 From cd16321dd9c25bb47a2e9417b2a4a75959be63d0 Mon Sep 17 00:00:00 2001 From: Kevin Pollet Date: Mon, 2 Jun 2025 10:36:05 +0200 Subject: [PATCH 18/21] Bump to go1.24 Co-authored-by: Romain --- .github/workflows/build.yaml | 2 +- .github/workflows/experimental.yaml | 2 +- .github/workflows/release.yaml | 2 +- .github/workflows/test-integration.yaml | 2 +- .github/workflows/test-unit.yaml | 2 +- .github/workflows/validate.yaml | 2 +- go.mod | 2 +- pkg/api/handler_http_test.go | 5 ++- pkg/api/handler_tcp_test.go | 3 +- pkg/api/handler_udp_test.go | 3 +- pkg/config/runtime/runtime_http_test.go | 3 +- pkg/config/runtime/runtime_tcp_test.go | 3 +- pkg/config/runtime/runtime_udp_test.go | 3 +- pkg/healthcheck/healthcheck_test.go | 4 +-- pkg/log/log_test.go | 3 +- pkg/metrics/datadog_test.go | 5 ++- pkg/metrics/influxdb2_test.go | 3 +- pkg/metrics/influxdb_test.go | 5 ++- pkg/metrics/prometheus_test.go | 11 +++--- pkg/metrics/statsd_test.go | 5 ++- pkg/middlewares/accesslog/logger_test.go | 2 +- pkg/middlewares/addprefix/add_prefix_test.go | 5 ++- pkg/middlewares/auth/basic_auth_test.go | 15 ++++---- pkg/middlewares/auth/digest_auth_test.go | 7 ++-- pkg/middlewares/auth/forward_test.go | 15 ++++---- pkg/middlewares/buffering/buffering_test.go | 3 +- pkg/middlewares/compress/compress_test.go | 23 ++++++------- .../customerrors/custom_errors_test.go | 6 ++-- pkg/middlewares/headers/headers_test.go | 11 +++--- .../ipallowlist/ip_allowlist_test.go | 5 ++- .../ipwhitelist/ip_whitelist_test.go | 5 ++- .../pass_tls_client_cert_test.go | 5 ++- pkg/middlewares/pipelining/pipelining_test.go | 7 ++-- .../ratelimiter/rate_limiter_test.go | 5 ++- pkg/middlewares/recovery/recovery_test.go | 3 +- .../redirect/redirect_regex_test.go | 3 +- .../redirect/redirect_scheme_test.go | 3 +- .../replacepath/replace_path_test.go | 3 +- .../replace_path_regex_test.go | 3 +- .../requestdecorator/hostresolver_test.go | 3 +- pkg/middlewares/retry/retry_test.go | 17 +++++----- .../stripprefix/strip_prefix_test.go | 3 +- .../strip_prefix_regex_test.go | 3 +- .../tcp/inflightconn/inflight_conn_test.go | 3 +- .../tcp/ipallowlist/ip_allowlist_test.go | 4 +-- .../tcp/ipwhitelist/ip_whitelist_test.go | 4 +-- pkg/middlewares/tracing/entrypoint_test.go | 3 +- pkg/middlewares/tracing/forwarder_test.go | 3 +- pkg/provider/acme/local_store_test.go | 5 ++- pkg/provider/acme/provider_test.go | 11 +++--- pkg/provider/aggregator/aggregator_test.go | 3 +- pkg/provider/consulcatalog/config_test.go | 5 ++- pkg/provider/docker/config_test.go | 15 ++++---- pkg/provider/docker/swarm_test.go | 10 +++--- pkg/provider/ecs/config_test.go | 5 ++- pkg/provider/file/file_test.go | 9 +++-- pkg/provider/http/http_test.go | 5 ++- .../kubernetes/crd/kubernetes_test.go | 13 ++++--- .../kubernetes/gateway/kubernetes_test.go | 9 +++-- .../kubernetes/ingress/client_test.go | 9 +++-- .../kubernetes/ingress/kubernetes_test.go | 9 +++-- pkg/provider/kv/kv_test.go | 7 ++-- pkg/provider/marathon/config_test.go | 7 ++-- pkg/provider/nomad/config_test.go | 11 +++--- pkg/provider/nomad/nomad_test.go | 3 +- pkg/provider/rancher/config_test.go | 3 +- pkg/provider/traefik/internal_test.go | 3 +- pkg/safe/routine_test.go | 6 ++-- pkg/server/configurationwatcher_test.go | 26 +++++++------- pkg/server/middleware/middlewares_test.go | 9 +++-- pkg/server/provider/provider_test.go | 20 +++++------ pkg/server/router/router_test.go | 17 +++++----- pkg/server/router/tcp/manager_test.go | 9 +++-- pkg/server/router/tcp/router_test.go | 5 ++- pkg/server/router/udp/router_test.go | 3 +- pkg/server/server_entrypoint_tcp.go | 30 ++++++---------- .../server_entrypoint_tcp_http3_test.go | 13 ++++--- pkg/server/server_entrypoint_tcp_test.go | 34 ++++++++++--------- pkg/server/server_entrypoint_udp_test.go | 5 ++- .../loadbalancer/failover/failover_test.go | 15 ++++---- .../loadbalancer/mirror/mirror_test.go | 13 ++++--- .../service/loadbalancer/wrr/wrr_test.go | 18 +++++----- pkg/server/service/roundtripper_test.go | 3 +- pkg/server/service/service_test.go | 18 +++++----- pkg/server/service/tcp/service_test.go | 3 +- pkg/server/service/udp/service_test.go | 3 +- pkg/tls/tlsmanager_test.go | 9 +++-- pkg/types/tls_test.go | 3 +- 88 files changed, 284 insertions(+), 362 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 0122f29e6..5d392a685 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -10,7 +10,7 @@ on: - 'script/gcg/**' env: - GO_VERSION: '1.23' + GO_VERSION: '1.24' CGO_ENABLED: 0 jobs: diff --git a/.github/workflows/experimental.yaml b/.github/workflows/experimental.yaml index 76959bbd5..fd0b76e4c 100644 --- a/.github/workflows/experimental.yaml +++ b/.github/workflows/experimental.yaml @@ -7,7 +7,7 @@ on: - v* env: - GO_VERSION: '1.23' + GO_VERSION: '1.24' CGO_ENABLED: 0 jobs: diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 821d639d3..d167180fe 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -6,7 +6,7 @@ on: - 'v*.*.*' env: - GO_VERSION: '1.23' + GO_VERSION: '1.24' CGO_ENABLED: 0 VERSION: ${{ github.ref_name }} TRAEFIKER_EMAIL: "traefiker@traefik.io" diff --git a/.github/workflows/test-integration.yaml b/.github/workflows/test-integration.yaml index f8eac5dc6..806148268 100644 --- a/.github/workflows/test-integration.yaml +++ b/.github/workflows/test-integration.yaml @@ -10,7 +10,7 @@ on: - 'script/gcg/**' env: - GO_VERSION: '1.23' + GO_VERSION: '1.24' CGO_ENABLED: 0 jobs: diff --git a/.github/workflows/test-unit.yaml b/.github/workflows/test-unit.yaml index 7d4a0fa66..ef82911e2 100644 --- a/.github/workflows/test-unit.yaml +++ b/.github/workflows/test-unit.yaml @@ -10,7 +10,7 @@ on: - 'script/gcg/**' env: - GO_VERSION: '1.23' + GO_VERSION: '1.24' jobs: diff --git a/.github/workflows/validate.yaml b/.github/workflows/validate.yaml index 4ed5faec5..8f9b0f643 100644 --- a/.github/workflows/validate.yaml +++ b/.github/workflows/validate.yaml @@ -6,7 +6,7 @@ on: - '*' env: - GO_VERSION: '1.23' + GO_VERSION: '1.24' GOLANGCI_LINT_VERSION: v2.0.2 MISSPELL_VERSION: v0.6.0 diff --git a/go.mod b/go.mod index 970fd883e..923f17d85 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/traefik/traefik/v2 -go 1.23.0 +go 1.24.0 require ( github.com/BurntSushi/toml v1.5.0 diff --git a/pkg/api/handler_http_test.go b/pkg/api/handler_http_test.go index 7c1d7fb4c..0e5079f61 100644 --- a/pkg/api/handler_http_test.go +++ b/pkg/api/handler_http_test.go @@ -1,7 +1,6 @@ package api import ( - "context" "encoding/json" "fmt" "io" @@ -926,8 +925,8 @@ func TestHandler_HTTP(t *testing.T) { rtConf := &test.conf // To lazily initialize the Statuses. rtConf.PopulateUsedBy() - rtConf.GetRoutersByEntryPoints(context.Background(), []string{"web"}, false) - rtConf.GetRoutersByEntryPoints(context.Background(), []string{"web"}, true) + rtConf.GetRoutersByEntryPoints(t.Context(), []string{"web"}, false) + rtConf.GetRoutersByEntryPoints(t.Context(), []string{"web"}, true) handler := New(static.Configuration{API: &static.API{}, Global: &static.Global{}}, rtConf) server := httptest.NewServer(handler.createRouter()) diff --git a/pkg/api/handler_tcp_test.go b/pkg/api/handler_tcp_test.go index af79bbce8..7566e4dd5 100644 --- a/pkg/api/handler_tcp_test.go +++ b/pkg/api/handler_tcp_test.go @@ -1,7 +1,6 @@ package api import ( - "context" "encoding/json" "io" "net/http" @@ -797,7 +796,7 @@ func TestHandler_TCP(t *testing.T) { rtConf := &test.conf // To lazily initialize the Statuses. rtConf.PopulateUsedBy() - rtConf.GetTCPRoutersByEntryPoints(context.Background(), []string{"web"}) + rtConf.GetTCPRoutersByEntryPoints(t.Context(), []string{"web"}) handler := New(static.Configuration{API: &static.API{}, Global: &static.Global{}}, rtConf) server := httptest.NewServer(handler.createRouter()) diff --git a/pkg/api/handler_udp_test.go b/pkg/api/handler_udp_test.go index 9f230c466..04429fc95 100644 --- a/pkg/api/handler_udp_test.go +++ b/pkg/api/handler_udp_test.go @@ -1,7 +1,6 @@ package api import ( - "context" "encoding/json" "io" "net/http" @@ -536,7 +535,7 @@ func TestHandler_UDP(t *testing.T) { rtConf := &test.conf // To lazily initialize the Statuses. rtConf.PopulateUsedBy() - rtConf.GetUDPRoutersByEntryPoints(context.Background(), []string{"web"}) + rtConf.GetUDPRoutersByEntryPoints(t.Context(), []string{"web"}) handler := New(static.Configuration{API: &static.API{}, Global: &static.Global{}}, rtConf) server := httptest.NewServer(handler.createRouter()) diff --git a/pkg/config/runtime/runtime_http_test.go b/pkg/config/runtime/runtime_http_test.go index 7ca957472..4d9dbd630 100644 --- a/pkg/config/runtime/runtime_http_test.go +++ b/pkg/config/runtime/runtime_http_test.go @@ -1,7 +1,6 @@ package runtime import ( - "context" "testing" "github.com/stretchr/testify/assert" @@ -211,7 +210,7 @@ func TestGetRoutersByEntryPoints(t *testing.T) { t.Run(test.desc, func(t *testing.T) { t.Parallel() runtimeConfig := NewConfig(test.conf) - actual := runtimeConfig.GetRoutersByEntryPoints(context.Background(), test.entryPoints, false) + actual := runtimeConfig.GetRoutersByEntryPoints(t.Context(), test.entryPoints, false) assert.Equal(t, test.expected, actual) }) } diff --git a/pkg/config/runtime/runtime_tcp_test.go b/pkg/config/runtime/runtime_tcp_test.go index b30509325..fc67336b6 100644 --- a/pkg/config/runtime/runtime_tcp_test.go +++ b/pkg/config/runtime/runtime_tcp_test.go @@ -1,7 +1,6 @@ package runtime import ( - "context" "testing" "github.com/stretchr/testify/assert" @@ -211,7 +210,7 @@ func TestGetTCPRoutersByEntryPoints(t *testing.T) { t.Run(test.desc, func(t *testing.T) { t.Parallel() runtimeConfig := NewConfig(test.conf) - actual := runtimeConfig.GetTCPRoutersByEntryPoints(context.Background(), test.entryPoints) + actual := runtimeConfig.GetTCPRoutersByEntryPoints(t.Context(), test.entryPoints) assert.Equal(t, test.expected, actual) }) } diff --git a/pkg/config/runtime/runtime_udp_test.go b/pkg/config/runtime/runtime_udp_test.go index c43972674..d4bc0eaf4 100644 --- a/pkg/config/runtime/runtime_udp_test.go +++ b/pkg/config/runtime/runtime_udp_test.go @@ -1,7 +1,6 @@ package runtime import ( - "context" "testing" "github.com/stretchr/testify/assert" @@ -192,7 +191,7 @@ func TestGetUDPRoutersByEntryPoints(t *testing.T) { t.Run(test.desc, func(t *testing.T) { t.Parallel() runtimeConfig := NewConfig(test.conf) - actual := runtimeConfig.GetUDPRoutersByEntryPoints(context.Background(), test.entryPoints) + actual := runtimeConfig.GetUDPRoutersByEntryPoints(t.Context(), test.entryPoints) assert.Equal(t, test.expected, actual) }) } diff --git a/pkg/healthcheck/healthcheck_test.go b/pkg/healthcheck/healthcheck_test.go index 4fcebde3b..e90d05526 100644 --- a/pkg/healthcheck/healthcheck_test.go +++ b/pkg/healthcheck/healthcheck_test.go @@ -101,7 +101,7 @@ func TestSetBackendsConfiguration(t *testing.T) { // The context is passed to the health check and canonically canceled by // the test server once all expected requests have been received. - ctx, cancel := context.WithCancel(context.Background()) + ctx, cancel := context.WithCancel(t.Context()) defer cancel() ts := newTestServer(cancel, test.healthSequence) defer ts.Close() @@ -568,7 +568,7 @@ func TestNotFollowingRedirects(t *testing.T) { })) defer redirectTestServer.Close() - ctx, cancel := context.WithCancel(context.Background()) + ctx, cancel := context.WithCancel(t.Context()) defer cancel() server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { diff --git a/pkg/log/log_test.go b/pkg/log/log_test.go index 704f2a83b..083b7b547 100644 --- a/pkg/log/log_test.go +++ b/pkg/log/log_test.go @@ -2,7 +2,6 @@ package log import ( "bytes" - "context" "strings" "testing" @@ -42,7 +41,7 @@ func TestLog(t *testing.T) { var buffer bytes.Buffer SetOutput(&buffer) - ctx := context.Background() + ctx := t.Context() for key, value := range test.fields { ctx = With(ctx, Str(key, value)) diff --git a/pkg/metrics/datadog_test.go b/pkg/metrics/datadog_test.go index e4b13cb15..26649dc31 100644 --- a/pkg/metrics/datadog_test.go +++ b/pkg/metrics/datadog_test.go @@ -1,7 +1,6 @@ package metrics import ( - "context" "net/http" "strconv" "testing" @@ -17,7 +16,7 @@ func TestDatadog(t *testing.T) { // This is needed to make sure that UDP Listener listens for data a bit longer, otherwise it will quit after a millisecond udp.Timeout = 5 * time.Second - datadogRegistry := RegisterDatadog(context.Background(), &types.Datadog{Address: ":18125", PushInterval: ptypes.Duration(time.Second), AddEntryPointsLabels: true, AddRoutersLabels: true, AddServicesLabels: true}) + datadogRegistry := RegisterDatadog(t.Context(), &types.Datadog{Address: ":18125", PushInterval: ptypes.Duration(time.Second), AddEntryPointsLabels: true, AddRoutersLabels: true, AddServicesLabels: true}) defer StopDatadog() if !datadogRegistry.IsEpEnabled() || !datadogRegistry.IsRouterEnabled() || !datadogRegistry.IsSvcEnabled() { @@ -35,7 +34,7 @@ func TestDatadogWithPrefix(t *testing.T) { // This is needed to make sure that UDP Listener listens for data a bit longer, otherwise it will quit after a millisecond udp.Timeout = 5 * time.Second - datadogRegistry := RegisterDatadog(context.Background(), &types.Datadog{Prefix: "testPrefix", Address: ":18125", PushInterval: ptypes.Duration(time.Second), AddEntryPointsLabels: true, AddRoutersLabels: true, AddServicesLabels: true}) + datadogRegistry := RegisterDatadog(t.Context(), &types.Datadog{Prefix: "testPrefix", Address: ":18125", PushInterval: ptypes.Duration(time.Second), AddEntryPointsLabels: true, AddRoutersLabels: true, AddServicesLabels: true}) testDatadogRegistry(t, "testPrefix", datadogRegistry) } diff --git a/pkg/metrics/influxdb2_test.go b/pkg/metrics/influxdb2_test.go index de20bb4b3..7a81f23d4 100644 --- a/pkg/metrics/influxdb2_test.go +++ b/pkg/metrics/influxdb2_test.go @@ -1,7 +1,6 @@ package metrics import ( - "context" "fmt" "io" "net/http" @@ -27,7 +26,7 @@ func TestInfluxDB2(t *testing.T) { })) defer ts.Close() - influxDB2Registry := RegisterInfluxDB2(context.Background(), + influxDB2Registry := RegisterInfluxDB2(t.Context(), &types.InfluxDB2{ Address: ts.URL, Token: "test-token", diff --git a/pkg/metrics/influxdb_test.go b/pkg/metrics/influxdb_test.go index bcec37b3f..ec1679866 100644 --- a/pkg/metrics/influxdb_test.go +++ b/pkg/metrics/influxdb_test.go @@ -1,7 +1,6 @@ package metrics import ( - "context" "fmt" "io" "net/http" @@ -23,7 +22,7 @@ func TestInfluxDB(t *testing.T) { udp.Timeout = 5 * time.Second influxDBClient = nil - influxDBRegistry := RegisterInfluxDB(context.Background(), + influxDBRegistry := RegisterInfluxDB(t.Context(), &types.InfluxDB{ Address: ":8089", PushInterval: ptypes.Duration(time.Second), @@ -147,7 +146,7 @@ func TestInfluxDBHTTP(t *testing.T) { defer ts.Close() influxDBClient = nil - influxDBRegistry := RegisterInfluxDB(context.Background(), + influxDBRegistry := RegisterInfluxDB(t.Context(), &types.InfluxDB{ Address: ts.URL, Protocol: "http", diff --git a/pkg/metrics/prometheus_test.go b/pkg/metrics/prometheus_test.go index 7be5cbf64..953acfd62 100644 --- a/pkg/metrics/prometheus_test.go +++ b/pkg/metrics/prometheus_test.go @@ -1,7 +1,6 @@ package metrics import ( - "context" "fmt" "net/http" "strconv" @@ -70,7 +69,7 @@ func TestRegisterPromState(t *testing.T) { if test.initPromState { initStandardRegistry(prom) } - if registerPromState(context.Background()) { + if registerPromState(t.Context()) { actualNbRegistries++ } if test.unregisterPromState { @@ -91,7 +90,7 @@ func TestPrometheus(t *testing.T) { promRegistry = prometheus.NewRegistry() t.Cleanup(promState.reset) - prometheusRegistry := RegisterPrometheus(context.Background(), &types.Prometheus{ + prometheusRegistry := RegisterPrometheus(t.Context(), &types.Prometheus{ AddEntryPointsLabels: true, AddRoutersLabels: true, AddServicesLabels: true, @@ -443,7 +442,7 @@ func TestPrometheusMetricRemoval(t *testing.T) { promRegistry = prometheus.NewRegistry() t.Cleanup(promState.reset) - prometheusRegistry := RegisterPrometheus(context.Background(), &types.Prometheus{AddEntryPointsLabels: true, AddServicesLabels: true, AddRoutersLabels: true}) + prometheusRegistry := RegisterPrometheus(t.Context(), &types.Prometheus{AddEntryPointsLabels: true, AddServicesLabels: true, AddRoutersLabels: true}) defer promRegistry.Unregister(promState) conf1 := dynamic.Configuration{ @@ -534,7 +533,7 @@ func TestPrometheusMetricRemoveEndpointForRecoveredService(t *testing.T) { promRegistry = prometheus.NewRegistry() t.Cleanup(promState.reset) - prometheusRegistry := RegisterPrometheus(context.Background(), &types.Prometheus{AddServicesLabels: true}) + prometheusRegistry := RegisterPrometheus(t.Context(), &types.Prometheus{AddServicesLabels: true}) defer promRegistry.Unregister(promState) conf1 := dynamic.Configuration{ @@ -573,7 +572,7 @@ func TestPrometheusMetricRemoveEndpointForRecoveredService(t *testing.T) { func TestPrometheusRemovedMetricsReset(t *testing.T) { t.Cleanup(promState.reset) - prometheusRegistry := RegisterPrometheus(context.Background(), &types.Prometheus{AddEntryPointsLabels: true, AddServicesLabels: true}) + prometheusRegistry := RegisterPrometheus(t.Context(), &types.Prometheus{AddEntryPointsLabels: true, AddServicesLabels: true}) defer promRegistry.Unregister(promState) conf1 := dynamic.Configuration{ diff --git a/pkg/metrics/statsd_test.go b/pkg/metrics/statsd_test.go index 1031d5929..c60075ad8 100644 --- a/pkg/metrics/statsd_test.go +++ b/pkg/metrics/statsd_test.go @@ -1,7 +1,6 @@ package metrics import ( - "context" "net/http" "strconv" "testing" @@ -21,7 +20,7 @@ func TestStatsD(t *testing.T) { // This is needed to make sure that UDP Listener listens for data a bit longer, otherwise it will quit after a millisecond udp.Timeout = 5 * time.Second - statsdRegistry := RegisterStatsd(context.Background(), &types.Statsd{Address: ":18125", PushInterval: ptypes.Duration(time.Second), AddEntryPointsLabels: true, AddRoutersLabels: true, AddServicesLabels: true}) + statsdRegistry := RegisterStatsd(t.Context(), &types.Statsd{Address: ":18125", PushInterval: ptypes.Duration(time.Second), AddEntryPointsLabels: true, AddRoutersLabels: true, AddServicesLabels: true}) testRegistry(t, defaultMetricsPrefix, statsdRegistry) } @@ -35,7 +34,7 @@ func TestStatsDWithPrefix(t *testing.T) { // This is needed to make sure that UDP Listener listens for data a bit longer, otherwise it will quit after a millisecond udp.Timeout = 5 * time.Second - statsdRegistry := RegisterStatsd(context.Background(), &types.Statsd{Address: ":18125", PushInterval: ptypes.Duration(time.Second), AddEntryPointsLabels: true, AddRoutersLabels: true, AddServicesLabels: true, Prefix: "testPrefix"}) + statsdRegistry := RegisterStatsd(t.Context(), &types.Statsd{Address: ":18125", PushInterval: ptypes.Duration(time.Second), AddEntryPointsLabels: true, AddRoutersLabels: true, AddServicesLabels: true, Prefix: "testPrefix"}) testRegistry(t, "testPrefix", statsdRegistry) } diff --git a/pkg/middlewares/accesslog/logger_test.go b/pkg/middlewares/accesslog/logger_test.go index 5201070ef..634ed72c7 100644 --- a/pkg/middlewares/accesslog/logger_test.go +++ b/pkg/middlewares/accesslog/logger_test.go @@ -926,7 +926,7 @@ func doLoggingWithAbortedStream(t *testing.T, config *types.AccessLog) { require.NoError(t, err, "logger should create "+config.FilePath) } - reqContext, cancelRequest := context.WithCancel(context.Background()) + reqContext, cancelRequest := context.WithCancel(t.Context()) req := &http.Request{ Header: map[string][]string{ diff --git a/pkg/middlewares/addprefix/add_prefix_test.go b/pkg/middlewares/addprefix/add_prefix_test.go index e56a13d9f..89cf5bc20 100644 --- a/pkg/middlewares/addprefix/add_prefix_test.go +++ b/pkg/middlewares/addprefix/add_prefix_test.go @@ -1,7 +1,6 @@ package addprefix import ( - "context" "net/http" "testing" @@ -34,7 +33,7 @@ func TestNewAddPrefix(t *testing.T) { next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}) - _, err := New(context.Background(), next, test.prefix, "foo-add-prefix") + _, err := New(t.Context(), next, test.prefix, "foo-add-prefix") if test.expectsError { assert.Error(t, err) } else { @@ -87,7 +86,7 @@ func TestAddPrefix(t *testing.T) { req := testhelpers.MustNewRequest(http.MethodGet, "http://localhost"+test.path, nil) - handler, err := New(context.Background(), next, test.prefix, "foo-add-prefix") + handler, err := New(t.Context(), next, test.prefix, "foo-add-prefix") require.NoError(t, err) handler.ServeHTTP(nil, req) diff --git a/pkg/middlewares/auth/basic_auth_test.go b/pkg/middlewares/auth/basic_auth_test.go index 196bd3669..f3f98df73 100644 --- a/pkg/middlewares/auth/basic_auth_test.go +++ b/pkg/middlewares/auth/basic_auth_test.go @@ -1,7 +1,6 @@ package auth import ( - "context" "fmt" "io" "net/http" @@ -23,13 +22,13 @@ func TestBasicAuthFail(t *testing.T) { auth := dynamic.BasicAuth{ Users: []string{"test"}, } - _, err := NewBasic(context.Background(), next, auth, "authName") + _, err := NewBasic(t.Context(), next, auth, "authName") require.Error(t, err) auth2 := dynamic.BasicAuth{ Users: []string{"test:test"}, } - authMiddleware, err := NewBasic(context.Background(), next, auth2, "authTest") + authMiddleware, err := NewBasic(t.Context(), next, auth2, "authTest") require.NoError(t, err) ts := httptest.NewServer(authMiddleware) @@ -52,7 +51,7 @@ func TestBasicAuthSuccess(t *testing.T) { auth := dynamic.BasicAuth{ Users: []string{"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/"}, } - authMiddleware, err := NewBasic(context.Background(), next, auth, "authName") + authMiddleware, err := NewBasic(t.Context(), next, auth, "authName") require.NoError(t, err) ts := httptest.NewServer(authMiddleware) @@ -83,7 +82,7 @@ func TestBasicAuthUserHeader(t *testing.T) { Users: []string{"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/"}, HeaderField: "X-Webauth-User", } - middleware, err := NewBasic(context.Background(), next, auth, "authName") + middleware, err := NewBasic(t.Context(), next, auth, "authName") require.NoError(t, err) ts := httptest.NewServer(middleware) @@ -114,7 +113,7 @@ func TestBasicAuthHeaderRemoved(t *testing.T) { RemoveHeader: true, Users: []string{"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/"}, } - middleware, err := NewBasic(context.Background(), next, auth, "authName") + middleware, err := NewBasic(t.Context(), next, auth, "authName") require.NoError(t, err) ts := httptest.NewServer(middleware) @@ -145,7 +144,7 @@ func TestBasicAuthHeaderPresent(t *testing.T) { auth := dynamic.BasicAuth{ Users: []string{"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/"}, } - middleware, err := NewBasic(context.Background(), next, auth, "authName") + middleware, err := NewBasic(t.Context(), next, auth, "authName") require.NoError(t, err) ts := httptest.NewServer(middleware) @@ -231,7 +230,7 @@ func TestBasicAuthUsersFromFile(t *testing.T) { fmt.Fprintln(w, "traefik") }) - authenticator, err := NewBasic(context.Background(), next, authenticatorConfiguration, "authName") + authenticator, err := NewBasic(t.Context(), next, authenticatorConfiguration, "authName") require.NoError(t, err) ts := httptest.NewServer(authenticator) diff --git a/pkg/middlewares/auth/digest_auth_test.go b/pkg/middlewares/auth/digest_auth_test.go index 1494205cd..8987fe099 100644 --- a/pkg/middlewares/auth/digest_auth_test.go +++ b/pkg/middlewares/auth/digest_auth_test.go @@ -1,7 +1,6 @@ package auth import ( - "context" "fmt" "io" "net/http" @@ -23,7 +22,7 @@ func TestDigestAuthError(t *testing.T) { auth := dynamic.DigestAuth{ Users: []string{"test"}, } - _, err := NewDigest(context.Background(), next, auth, "authName") + _, err := NewDigest(t.Context(), next, auth, "authName") assert.Error(t, err) } @@ -35,7 +34,7 @@ func TestDigestAuthFail(t *testing.T) { auth := dynamic.DigestAuth{ Users: []string{"test:traefik:a2688e031edb4be6a3797f3882655c05"}, } - authMiddleware, err := NewDigest(context.Background(), next, auth, "authName") + authMiddleware, err := NewDigest(t.Context(), next, auth, "authName") require.NoError(t, err) assert.NotNil(t, authMiddleware, "this should not be nil") @@ -109,7 +108,7 @@ func TestDigestAuthUsersFromFile(t *testing.T) { fmt.Fprintln(w, "traefik") }) - authenticator, err := NewDigest(context.Background(), next, authenticatorConfiguration, "authName") + authenticator, err := NewDigest(t.Context(), next, authenticatorConfiguration, "authName") require.NoError(t, err) ts := httptest.NewServer(authenticator) diff --git a/pkg/middlewares/auth/forward_test.go b/pkg/middlewares/auth/forward_test.go index de834a802..3dbd4eece 100644 --- a/pkg/middlewares/auth/forward_test.go +++ b/pkg/middlewares/auth/forward_test.go @@ -1,7 +1,6 @@ package auth import ( - "context" "fmt" "io" "net/http" @@ -30,7 +29,7 @@ func TestForwardAuthFail(t *testing.T) { })) t.Cleanup(server.Close) - middleware, err := NewForward(context.Background(), next, dynamic.ForwardAuth{ + middleware, err := NewForward(t.Context(), next, dynamic.ForwardAuth{ Address: server.URL, }, "authTest") require.NoError(t, err) @@ -77,7 +76,7 @@ func TestForwardAuthSuccess(t *testing.T) { AuthResponseHeaders: []string{"X-Auth-User", "X-Auth-Group"}, AuthResponseHeadersRegex: "^Foo-", } - middleware, err := NewForward(context.Background(), next, auth, "authTest") + middleware, err := NewForward(t.Context(), next, auth, "authTest") require.NoError(t, err) ts := httptest.NewServer(middleware) @@ -110,7 +109,7 @@ func TestForwardAuthRedirect(t *testing.T) { auth := dynamic.ForwardAuth{Address: authTs.URL} - authMiddleware, err := NewForward(context.Background(), next, auth, "authTest") + authMiddleware, err := NewForward(t.Context(), next, auth, "authTest") require.NoError(t, err) ts := httptest.NewServer(authMiddleware) @@ -161,7 +160,7 @@ func TestForwardAuthRemoveHopByHopHeaders(t *testing.T) { auth := dynamic.ForwardAuth{Address: authTs.URL} - authMiddleware, err := NewForward(context.Background(), next, auth, "authTest") + authMiddleware, err := NewForward(t.Context(), next, auth, "authTest") require.NoError(t, err) ts := httptest.NewServer(authMiddleware) @@ -207,7 +206,7 @@ func TestForwardAuthFailResponseHeaders(t *testing.T) { auth := dynamic.ForwardAuth{ Address: authTs.URL, } - authMiddleware, err := NewForward(context.Background(), next, auth, "authTest") + authMiddleware, err := NewForward(t.Context(), next, auth, "authTest") require.NoError(t, err) ts := httptest.NewServer(authMiddleware) @@ -469,10 +468,10 @@ func TestForwardAuthUsesTracing(t *testing.T) { tr, _ := tracing.NewTracing("testApp", 100, &mockBackend{tracer}) - next, err := NewForward(context.Background(), next, auth, "authTest") + next, err := NewForward(t.Context(), next, auth, "authTest") require.NoError(t, err) - next = tracingMiddleware.NewEntryPoint(context.Background(), tr, "tracingTest", next) + next = tracingMiddleware.NewEntryPoint(t.Context(), tr, "tracingTest", next) ts := httptest.NewServer(next) t.Cleanup(ts.Close) diff --git a/pkg/middlewares/buffering/buffering_test.go b/pkg/middlewares/buffering/buffering_test.go index adf2c6daa..bd5bb018a 100644 --- a/pkg/middlewares/buffering/buffering_test.go +++ b/pkg/middlewares/buffering/buffering_test.go @@ -2,7 +2,6 @@ package buffering import ( "bytes" - "context" "crypto/rand" "math" "net/http" @@ -57,7 +56,7 @@ func TestBuffering(t *testing.T) { require.NoError(t, err) }) - buffMiddleware, err := New(context.Background(), next, test.config, "foo") + buffMiddleware, err := New(t.Context(), next, test.config, "foo") require.NoError(t, err) req := httptest.NewRequest(http.MethodPost, "http://localhost", bytes.NewBuffer(test.body)) diff --git a/pkg/middlewares/compress/compress_test.go b/pkg/middlewares/compress/compress_test.go index 13715682e..d413ecf7c 100644 --- a/pkg/middlewares/compress/compress_test.go +++ b/pkg/middlewares/compress/compress_test.go @@ -1,7 +1,6 @@ package compress import ( - "context" "io" "net/http" "net/http/httptest" @@ -34,7 +33,7 @@ func TestShouldCompressWhenNoContentEncodingHeader(t *testing.T) { _, err := rw.Write(baseBody) assert.NoError(t, err) }) - handler, err := New(context.Background(), next, dynamic.Compress{}, "testing") + handler, err := New(t.Context(), next, dynamic.Compress{}, "testing") require.NoError(t, err) rw := httptest.NewRecorder() @@ -61,7 +60,7 @@ func TestShouldNotCompressWhenContentEncodingHeader(t *testing.T) { http.Error(rw, err.Error(), http.StatusInternalServerError) } }) - handler, err := New(context.Background(), next, dynamic.Compress{}, "testing") + handler, err := New(t.Context(), next, dynamic.Compress{}, "testing") require.NoError(t, err) rw := httptest.NewRecorder() @@ -83,7 +82,7 @@ func TestShouldNotCompressWhenNoAcceptEncodingHeader(t *testing.T) { http.Error(rw, err.Error(), http.StatusInternalServerError) } }) - handler, err := New(context.Background(), next, dynamic.Compress{}, "testing") + handler, err := New(t.Context(), next, dynamic.Compress{}, "testing") require.NoError(t, err) rw := httptest.NewRecorder() @@ -144,7 +143,7 @@ func TestShouldNotCompressWhenSpecificContentType(t *testing.T) { } }) - handler, err := New(context.Background(), next, test.conf, "test") + handler, err := New(t.Context(), next, test.conf, "test") require.NoError(t, err) rw := httptest.NewRecorder() @@ -194,7 +193,7 @@ func TestIntegrationShouldNotCompress(t *testing.T) { for _, test := range testCases { t.Run(test.name, func(t *testing.T) { - compress, err := New(context.Background(), test.handler, dynamic.Compress{}, "testing") + compress, err := New(t.Context(), test.handler, dynamic.Compress{}, "testing") require.NoError(t, err) ts := httptest.NewServer(compress) @@ -229,7 +228,7 @@ func TestShouldWriteHeaderWhenFlush(t *testing.T) { http.Error(rw, err.Error(), http.StatusInternalServerError) } }) - handler, err := New(context.Background(), next, dynamic.Compress{}, "testing") + handler, err := New(t.Context(), next, dynamic.Compress{}, "testing") require.NoError(t, err) ts := httptest.NewServer(handler) @@ -280,7 +279,7 @@ func TestIntegrationShouldCompress(t *testing.T) { for _, test := range testCases { t.Run(test.name, func(t *testing.T) { - compress, err := New(context.Background(), test.handler, dynamic.Compress{}, "testing") + compress, err := New(t.Context(), test.handler, dynamic.Compress{}, "testing") require.NoError(t, err) ts := httptest.NewServer(compress) @@ -337,7 +336,7 @@ func TestMinResponseBodyBytes(t *testing.T) { } }) - handler, err := New(context.Background(), next, dynamic.Compress{MinResponseBodyBytes: test.minResponseBodyBytes}, "testing") + handler, err := New(t.Context(), next, dynamic.Compress{MinResponseBodyBytes: test.minResponseBodyBytes}, "testing") require.NoError(t, err) rw := httptest.NewRecorder() @@ -373,7 +372,7 @@ func Test1xxResponses(t *testing.T) { } }) - compress, err := New(context.Background(), next, dynamic.Compress{MinResponseBodyBytes: 1024}, "testing") + compress, err := New(t.Context(), next, dynamic.Compress{MinResponseBodyBytes: 1024}, "testing") require.NoError(t, err) server := httptest.NewServer(compress) @@ -417,7 +416,7 @@ func Test1xxResponses(t *testing.T) { return nil }, } - req, _ := http.NewRequestWithContext(httptrace.WithClientTrace(context.Background(), trace), http.MethodGet, server.URL, nil) + req, _ := http.NewRequestWithContext(httptrace.WithClientTrace(t.Context(), trace), http.MethodGet, server.URL, nil) req.Header.Add(acceptEncodingHeader, gzipValue) res, err := frontendClient.Do(req) @@ -478,7 +477,7 @@ func BenchmarkCompress(b *testing.B) { _, err := rw.Write(baseBody) assert.NoError(b, err) }) - handler, _ := New(context.Background(), next, dynamic.Compress{}, "testing") + handler, _ := New(b.Context(), next, dynamic.Compress{}, "testing") req, _ := http.NewRequest(http.MethodGet, "/whatever", nil) req.Header.Set("Accept-Encoding", "gzip") diff --git a/pkg/middlewares/customerrors/custom_errors_test.go b/pkg/middlewares/customerrors/custom_errors_test.go index 046ecb665..bc3afe638 100644 --- a/pkg/middlewares/customerrors/custom_errors_test.go +++ b/pkg/middlewares/customerrors/custom_errors_test.go @@ -170,7 +170,7 @@ func TestHandler(t *testing.T) { } _, _ = fmt.Fprintln(w, http.StatusText(test.backendCode)) }) - errorPageHandler, err := New(context.Background(), handler, *test.errorPage, serviceBuilderMock, "test") + errorPageHandler, err := New(t.Context(), handler, *test.errorPage, serviceBuilderMock, "test") require.NoError(t, err) req := testhelpers.MustNewRequest(http.MethodGet, "http://localhost/test?foo=bar&baz=buz", nil) @@ -205,7 +205,7 @@ func Test1xxResponses(t *testing.T) { config := dynamic.ErrorPage{Service: "error", Query: "/", Status: []string{"200"}} - errorPageHandler, err := New(context.Background(), next, config, serviceBuilderMock, "test") + errorPageHandler, err := New(t.Context(), next, config, serviceBuilderMock, "test") require.NoError(t, err) server := httptest.NewServer(errorPageHandler) @@ -249,7 +249,7 @@ func Test1xxResponses(t *testing.T) { return nil }, } - req, _ := http.NewRequestWithContext(httptrace.WithClientTrace(context.Background(), trace), http.MethodGet, server.URL, nil) + req, _ := http.NewRequestWithContext(httptrace.WithClientTrace(t.Context(), trace), http.MethodGet, server.URL, nil) res, err := frontendClient.Do(req) assert.NoError(t, err) diff --git a/pkg/middlewares/headers/headers_test.go b/pkg/middlewares/headers/headers_test.go index 41ec273a2..6248d1c4f 100644 --- a/pkg/middlewares/headers/headers_test.go +++ b/pkg/middlewares/headers/headers_test.go @@ -3,7 +3,6 @@ package headers // Middleware tests based on https://github.com/unrolled/secure import ( - "context" "io" "net/http" "net/http/httptest" @@ -20,7 +19,7 @@ import ( func TestNew_withoutOptions(t *testing.T) { next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) }) - mid, err := New(context.Background(), next, dynamic.Headers{}, "testing") + mid, err := New(t.Context(), next, dynamic.Headers{}, "testing") require.Errorf(t, err, "headers configuration not valid") assert.Nil(t, mid) @@ -55,7 +54,7 @@ func TestNew_allowedHosts(t *testing.T) { AllowedHosts: []string{"foo.com", "bar.com"}, } - mid, err := New(context.Background(), emptyHandler, cfg, "foo") + mid, err := New(t.Context(), emptyHandler, cfg, "foo") require.NoError(t, err) for _, test := range testCases { @@ -86,7 +85,7 @@ func TestNew_customHeaders(t *testing.T) { }, } - mid, err := New(context.Background(), next, cfg, "testing") + mid, err := New(t.Context(), next, cfg, "testing") require.NoError(t, err) req := httptest.NewRequest(http.MethodGet, "/foo", nil) @@ -134,7 +133,7 @@ func Test1xxResponses(t *testing.T) { }, } - mid, err := New(context.Background(), next, cfg, "testing") + mid, err := New(t.Context(), next, cfg, "testing") require.NoError(t, err) server := httptest.NewServer(mid) @@ -178,7 +177,7 @@ func Test1xxResponses(t *testing.T) { return nil }, } - req, _ := http.NewRequestWithContext(httptrace.WithClientTrace(context.Background(), trace), http.MethodGet, server.URL, nil) + req, _ := http.NewRequestWithContext(httptrace.WithClientTrace(t.Context(), trace), http.MethodGet, server.URL, nil) res, err := frontendClient.Do(req) assert.NoError(t, err) diff --git a/pkg/middlewares/ipallowlist/ip_allowlist_test.go b/pkg/middlewares/ipallowlist/ip_allowlist_test.go index 130c6f7cc..8c90e961c 100644 --- a/pkg/middlewares/ipallowlist/ip_allowlist_test.go +++ b/pkg/middlewares/ipallowlist/ip_allowlist_test.go @@ -1,7 +1,6 @@ package ipallowlist import ( - "context" "net/http" "net/http/httptest" "testing" @@ -37,7 +36,7 @@ func TestNewIPAllowLister(t *testing.T) { t.Parallel() next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}) - allowLister, err := New(context.Background(), next, test.allowList, "traefikTest") + allowLister, err := New(t.Context(), next, test.allowList, "traefikTest") if test.expectedError { assert.Error(t, err) @@ -79,7 +78,7 @@ func TestIPAllowLister_ServeHTTP(t *testing.T) { t.Parallel() next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}) - allowLister, err := New(context.Background(), next, test.allowList, "traefikTest") + allowLister, err := New(t.Context(), next, test.allowList, "traefikTest") require.NoError(t, err) recorder := httptest.NewRecorder() diff --git a/pkg/middlewares/ipwhitelist/ip_whitelist_test.go b/pkg/middlewares/ipwhitelist/ip_whitelist_test.go index 13b6e9e5e..fddeaf43b 100644 --- a/pkg/middlewares/ipwhitelist/ip_whitelist_test.go +++ b/pkg/middlewares/ipwhitelist/ip_whitelist_test.go @@ -1,7 +1,6 @@ package ipwhitelist import ( - "context" "net/http" "net/http/httptest" "testing" @@ -37,7 +36,7 @@ func TestNewIPWhiteLister(t *testing.T) { t.Parallel() next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}) - whiteLister, err := New(context.Background(), next, test.whiteList, "traefikTest") + whiteLister, err := New(t.Context(), next, test.whiteList, "traefikTest") if test.expectedError { assert.Error(t, err) @@ -79,7 +78,7 @@ func TestIPWhiteLister_ServeHTTP(t *testing.T) { t.Parallel() next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}) - whiteLister, err := New(context.Background(), next, test.whiteList, "traefikTest") + whiteLister, err := New(t.Context(), next, test.whiteList, "traefikTest") require.NoError(t, err) recorder := httptest.NewRecorder() diff --git a/pkg/middlewares/passtlsclientcert/pass_tls_client_cert_test.go b/pkg/middlewares/passtlsclientcert/pass_tls_client_cert_test.go index d0a4b7735..edaea4106 100644 --- a/pkg/middlewares/passtlsclientcert/pass_tls_client_cert_test.go +++ b/pkg/middlewares/passtlsclientcert/pass_tls_client_cert_test.go @@ -1,7 +1,6 @@ package passtlsclientcert import ( - "context" "crypto/tls" "crypto/x509" "encoding/pem" @@ -313,7 +312,7 @@ func TestPassTLSClientCert_PEM(t *testing.T) { t.Run(test.desc, func(t *testing.T) { t.Parallel() - tlsClientHeaders, err := New(context.Background(), next, test.config, "foo") + tlsClientHeaders, err := New(t.Context(), next, test.config, "foo") require.NoError(t, err) res := httptest.NewRecorder() @@ -535,7 +534,7 @@ func TestPassTLSClientCert_certInfo(t *testing.T) { t.Run(test.desc, func(t *testing.T) { t.Parallel() - tlsClientHeaders, err := New(context.Background(), next, test.config, "foo") + tlsClientHeaders, err := New(t.Context(), next, test.config, "foo") require.NoError(t, err) res := httptest.NewRecorder() diff --git a/pkg/middlewares/pipelining/pipelining_test.go b/pkg/middlewares/pipelining/pipelining_test.go index 9ad64f103..449101737 100644 --- a/pkg/middlewares/pipelining/pipelining_test.go +++ b/pkg/middlewares/pipelining/pipelining_test.go @@ -1,7 +1,6 @@ package pipelining import ( - "context" "io" "net/http" "net/http/httptest" @@ -62,7 +61,7 @@ func TestNew(t *testing.T) { assert.Equal(t, test.implementCloseNotifier, ok) w.WriteHeader(http.StatusOK) }) - handler := New(context.Background(), nextHandler, "pipe") + handler := New(t.Context(), nextHandler, "pipe") req := httptest.NewRequest(test.HTTPMethod, "http://localhost", nil) @@ -87,7 +86,7 @@ func Test1xxResponses(t *testing.T) { _, _ = w.Write([]byte("Hello")) }) - pipe := New(context.Background(), next, "pipe") + pipe := New(t.Context(), next, "pipe") server := httptest.NewServer(pipe) t.Cleanup(server.Close) @@ -130,7 +129,7 @@ func Test1xxResponses(t *testing.T) { return nil }, } - req, _ := http.NewRequestWithContext(httptrace.WithClientTrace(context.Background(), trace), http.MethodGet, server.URL, nil) + req, _ := http.NewRequestWithContext(httptrace.WithClientTrace(t.Context(), trace), http.MethodGet, server.URL, nil) res, err := frontendClient.Do(req) assert.NoError(t, err) diff --git a/pkg/middlewares/ratelimiter/rate_limiter_test.go b/pkg/middlewares/ratelimiter/rate_limiter_test.go index 50c51553d..0b8927bf1 100644 --- a/pkg/middlewares/ratelimiter/rate_limiter_test.go +++ b/pkg/middlewares/ratelimiter/rate_limiter_test.go @@ -1,7 +1,6 @@ package ratelimiter import ( - "context" "fmt" "net/http" "net/http/httptest" @@ -94,7 +93,7 @@ func TestNewRateLimiter(t *testing.T) { next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}) - h, err := New(context.Background(), next, test.config, "rate-limiter") + h, err := New(t.Context(), next, test.config, "rate-limiter") if test.expectedError != "" { assert.EqualError(t, err, test.expectedError) } else { @@ -258,7 +257,7 @@ func TestRateLimit(t *testing.T) { next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { reqCount++ }) - h, err := New(context.Background(), next, test.config, "rate-limiter") + h, err := New(t.Context(), next, test.config, "rate-limiter") require.NoError(t, err) loadPeriod := time.Duration(1e9 / test.incomingLoad) diff --git a/pkg/middlewares/recovery/recovery_test.go b/pkg/middlewares/recovery/recovery_test.go index 1929f0b54..d93cb77eb 100644 --- a/pkg/middlewares/recovery/recovery_test.go +++ b/pkg/middlewares/recovery/recovery_test.go @@ -1,7 +1,6 @@ package recovery import ( - "context" "errors" "io" "net/http" @@ -47,7 +46,7 @@ func TestRecoverHandler(t *testing.T) { } panic(test.panicErr) } - recovery, err := New(context.Background(), http.HandlerFunc(fn)) + recovery, err := New(t.Context(), http.HandlerFunc(fn)) require.NoError(t, err) server := httptest.NewServer(recovery) diff --git a/pkg/middlewares/redirect/redirect_regex_test.go b/pkg/middlewares/redirect/redirect_regex_test.go index 0c04d9ef8..820016324 100644 --- a/pkg/middlewares/redirect/redirect_regex_test.go +++ b/pkg/middlewares/redirect/redirect_regex_test.go @@ -1,7 +1,6 @@ package redirect import ( - "context" "crypto/tls" "net/http" "net/http/httptest" @@ -158,7 +157,7 @@ func TestRedirectRegexHandler(t *testing.T) { t.Parallel() next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}) - handler, err := NewRedirectRegex(context.Background(), next, test.config, "traefikTest") + handler, err := NewRedirectRegex(t.Context(), next, test.config, "traefikTest") if test.errorExpected { require.Error(t, err) diff --git a/pkg/middlewares/redirect/redirect_scheme_test.go b/pkg/middlewares/redirect/redirect_scheme_test.go index 7ff97bba4..eb9718930 100644 --- a/pkg/middlewares/redirect/redirect_scheme_test.go +++ b/pkg/middlewares/redirect/redirect_scheme_test.go @@ -1,7 +1,6 @@ package redirect import ( - "context" "crypto/tls" "net/http" "net/http/httptest" @@ -287,7 +286,7 @@ func TestRedirectSchemeHandler(t *testing.T) { t.Parallel() next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}) - handler, err := NewRedirectScheme(context.Background(), next, test.config, "traefikTest") + handler, err := NewRedirectScheme(t.Context(), next, test.config, "traefikTest") if test.errorExpected { require.Error(t, err) diff --git a/pkg/middlewares/replacepath/replace_path_test.go b/pkg/middlewares/replacepath/replace_path_test.go index 782733bc0..e2fdc3782 100644 --- a/pkg/middlewares/replacepath/replace_path_test.go +++ b/pkg/middlewares/replacepath/replace_path_test.go @@ -1,7 +1,6 @@ package replacepath import ( - "context" "net/http" "net/http/httptest" "testing" @@ -82,7 +81,7 @@ func TestReplacePath(t *testing.T) { requestURI = r.RequestURI }) - handler, err := New(context.Background(), next, test.config, "foo-replace-path") + handler, err := New(t.Context(), next, test.config, "foo-replace-path") require.NoError(t, err) server := httptest.NewServer(handler) diff --git a/pkg/middlewares/replacepathregex/replace_path_regex_test.go b/pkg/middlewares/replacepathregex/replace_path_regex_test.go index b7e637851..348be4be2 100644 --- a/pkg/middlewares/replacepathregex/replace_path_regex_test.go +++ b/pkg/middlewares/replacepathregex/replace_path_regex_test.go @@ -1,7 +1,6 @@ package replacepathregex import ( - "context" "net/http" "net/http/httptest" "testing" @@ -150,7 +149,7 @@ func TestReplacePathRegex(t *testing.T) { requestURI = r.RequestURI }) - handler, err := New(context.Background(), next, test.config, "foo-replace-path-regexp") + handler, err := New(t.Context(), next, test.config, "foo-replace-path-regexp") if test.expectsError { require.Error(t, err) return diff --git a/pkg/middlewares/requestdecorator/hostresolver_test.go b/pkg/middlewares/requestdecorator/hostresolver_test.go index f1c8c39a2..e00228576 100644 --- a/pkg/middlewares/requestdecorator/hostresolver_test.go +++ b/pkg/middlewares/requestdecorator/hostresolver_test.go @@ -1,7 +1,6 @@ package requestdecorator import ( - "context" "testing" "github.com/stretchr/testify/assert" @@ -43,7 +42,7 @@ func TestCNAMEFlatten(t *testing.T) { ResolvDepth: 5, } - flatH := hostResolver.CNAMEFlatten(context.Background(), test.domain) + flatH := hostResolver.CNAMEFlatten(t.Context(), test.domain) assert.Equal(t, test.expectedDomain, flatH) }) } diff --git a/pkg/middlewares/retry/retry_test.go b/pkg/middlewares/retry/retry_test.go index b7332fe1e..b8e7d0fd5 100644 --- a/pkg/middlewares/retry/retry_test.go +++ b/pkg/middlewares/retry/retry_test.go @@ -1,7 +1,6 @@ package retry import ( - "context" "fmt" "io" "net/http" @@ -129,7 +128,7 @@ func TestRetry(t *testing.T) { }) retryListener := &countingRetryListener{} - retry, err := New(context.Background(), next, test.config, retryListener, "traefikTest") + retry, err := New(t.Context(), next, test.config, retryListener, "traefikTest") require.NoError(t, err) recorder := httptest.NewRecorder() @@ -149,7 +148,7 @@ func TestRetryEmptyServerList(t *testing.T) { }) retryListener := &countingRetryListener{} - retry, err := New(context.Background(), next, dynamic.Retry{Attempts: 3}, retryListener, "traefikTest") + retry, err := New(t.Context(), next, dynamic.Retry{Attempts: 3}, retryListener, "traefikTest") require.NoError(t, err) recorder := httptest.NewRecorder() @@ -185,7 +184,7 @@ func TestMultipleRetriesShouldNotLooseHeaders(t *testing.T) { rw.WriteHeader(http.StatusNoContent) }) - retry, err := New(context.Background(), next, dynamic.Retry{Attempts: 3}, &countingRetryListener{}, "traefikTest") + retry, err := New(t.Context(), next, dynamic.Retry{Attempts: 3}, &countingRetryListener{}, "traefikTest") require.NoError(t, err) res := httptest.NewRecorder() @@ -219,7 +218,7 @@ func TestRetryShouldNotLooseHeadersOnWrite(t *testing.T) { require.NoError(t, err) }) - retry, err := New(context.Background(), next, dynamic.Retry{Attempts: 3}, &countingRetryListener{}, "traefikTest") + retry, err := New(t.Context(), next, dynamic.Retry{Attempts: 3}, &countingRetryListener{}, "traefikTest") require.NoError(t, err) res := httptest.NewRecorder() @@ -243,7 +242,7 @@ func TestRetryWithFlush(t *testing.T) { } }) - retry, err := New(context.Background(), next, dynamic.Retry{Attempts: 1}, &countingRetryListener{}, "traefikTest") + retry, err := New(t.Context(), next, dynamic.Retry{Attempts: 1}, &countingRetryListener{}, "traefikTest") require.NoError(t, err) responseRecorder := httptest.NewRecorder() @@ -312,7 +311,7 @@ func TestRetryWebsocket(t *testing.T) { }) retryListener := &countingRetryListener{} - retryH, err := New(context.Background(), next, dynamic.Retry{Attempts: test.maxRequestAttempts}, retryListener, "traefikTest") + retryH, err := New(t.Context(), next, dynamic.Retry{Attempts: test.maxRequestAttempts}, retryListener, "traefikTest") require.NoError(t, err) retryServer := httptest.NewServer(retryH) @@ -345,7 +344,7 @@ func Test1xxResponses(t *testing.T) { }) retryListener := &countingRetryListener{} - retry, err := New(context.Background(), next, dynamic.Retry{Attempts: 1}, retryListener, "traefikTest") + retry, err := New(t.Context(), next, dynamic.Retry{Attempts: 1}, retryListener, "traefikTest") require.NoError(t, err) server := httptest.NewServer(retry) @@ -389,7 +388,7 @@ func Test1xxResponses(t *testing.T) { return nil }, } - req, _ := http.NewRequestWithContext(httptrace.WithClientTrace(context.Background(), trace), http.MethodGet, server.URL, nil) + req, _ := http.NewRequestWithContext(httptrace.WithClientTrace(t.Context(), trace), http.MethodGet, server.URL, nil) res, err := frontendClient.Do(req) assert.NoError(t, err) diff --git a/pkg/middlewares/stripprefix/strip_prefix_test.go b/pkg/middlewares/stripprefix/strip_prefix_test.go index b592224e9..8ed6ebe92 100644 --- a/pkg/middlewares/stripprefix/strip_prefix_test.go +++ b/pkg/middlewares/stripprefix/strip_prefix_test.go @@ -1,7 +1,6 @@ package stripprefix import ( - "context" "net/http" "net/http/httptest" "testing" @@ -189,7 +188,7 @@ func TestStripPrefix(t *testing.T) { requestURI = r.RequestURI }) - handler, err := New(context.Background(), next, test.config, "foo-strip-prefix") + handler, err := New(t.Context(), next, test.config, "foo-strip-prefix") require.NoError(t, err) req := testhelpers.MustNewRequest(http.MethodGet, "http://localhost"+test.path, nil) diff --git a/pkg/middlewares/stripprefixregex/strip_prefix_regex_test.go b/pkg/middlewares/stripprefixregex/strip_prefix_regex_test.go index 18e831d35..5a9b6d115 100644 --- a/pkg/middlewares/stripprefixregex/strip_prefix_regex_test.go +++ b/pkg/middlewares/stripprefixregex/strip_prefix_regex_test.go @@ -1,7 +1,6 @@ package stripprefixregex import ( - "context" "net/http" "net/http/httptest" "testing" @@ -118,7 +117,7 @@ func TestStripPrefixRegex(t *testing.T) { actualHeader = r.Header.Get(stripprefix.ForwardedPrefixHeader) requestURI = r.RequestURI }) - handler, err := New(context.Background(), handlerPath, testPrefixRegex, "foo-strip-prefix-regex") + handler, err := New(t.Context(), handlerPath, testPrefixRegex, "foo-strip-prefix-regex") require.NoError(t, err) req := testhelpers.MustNewRequest(http.MethodGet, "http://localhost"+test.path, nil) diff --git a/pkg/middlewares/tcp/inflightconn/inflight_conn_test.go b/pkg/middlewares/tcp/inflightconn/inflight_conn_test.go index 666df4317..ceaf876f8 100644 --- a/pkg/middlewares/tcp/inflightconn/inflight_conn_test.go +++ b/pkg/middlewares/tcp/inflightconn/inflight_conn_test.go @@ -1,7 +1,6 @@ package tcpinflightconn import ( - "context" "net" "testing" "time" @@ -27,7 +26,7 @@ func TestInFlightConn_ServeTCP(t *testing.T) { finishCh <- struct{}{} }) - middleware, err := New(context.Background(), next, dynamic.TCPInFlightConn{Amount: 1}, "foo") + middleware, err := New(t.Context(), next, dynamic.TCPInFlightConn{Amount: 1}, "foo") require.NoError(t, err) // The first connection should succeed and wait. diff --git a/pkg/middlewares/tcp/ipallowlist/ip_allowlist_test.go b/pkg/middlewares/tcp/ipallowlist/ip_allowlist_test.go index d55a1fffa..b6ff41a1c 100644 --- a/pkg/middlewares/tcp/ipallowlist/ip_allowlist_test.go +++ b/pkg/middlewares/tcp/ipallowlist/ip_allowlist_test.go @@ -43,7 +43,7 @@ func TestNewIPAllowLister(t *testing.T) { t.Parallel() next := tcp.HandlerFunc(func(conn tcp.WriteCloser) {}) - allowLister, err := New(context.Background(), next, test.allowList, "traefikTest") + allowLister, err := New(t.Context(), next, test.allowList, "traefikTest") if test.expectedError { assert.Error(t, err) @@ -92,7 +92,7 @@ func TestIPAllowLister_ServeHTTP(t *testing.T) { require.NoError(t, err) }) - allowLister, err := New(context.Background(), next, test.allowList, "traefikTest") + allowLister, err := New(t.Context(), next, test.allowList, "traefikTest") require.NoError(t, err) server, client := net.Pipe() diff --git a/pkg/middlewares/tcp/ipwhitelist/ip_whitelist_test.go b/pkg/middlewares/tcp/ipwhitelist/ip_whitelist_test.go index e419f6bfe..40753edff 100644 --- a/pkg/middlewares/tcp/ipwhitelist/ip_whitelist_test.go +++ b/pkg/middlewares/tcp/ipwhitelist/ip_whitelist_test.go @@ -43,7 +43,7 @@ func TestNewIPWhiteLister(t *testing.T) { t.Parallel() next := tcp.HandlerFunc(func(conn tcp.WriteCloser) {}) - whiteLister, err := New(context.Background(), next, test.whiteList, "traefikTest") + whiteLister, err := New(t.Context(), next, test.whiteList, "traefikTest") if test.expectedError { assert.Error(t, err) @@ -92,7 +92,7 @@ func TestIPWhiteLister_ServeHTTP(t *testing.T) { require.NoError(t, err) }) - whiteLister, err := New(context.Background(), next, test.whiteList, "traefikTest") + whiteLister, err := New(t.Context(), next, test.whiteList, "traefikTest") require.NoError(t, err) server, client := net.Pipe() diff --git a/pkg/middlewares/tracing/entrypoint_test.go b/pkg/middlewares/tracing/entrypoint_test.go index 604c8c012..14ed69366 100644 --- a/pkg/middlewares/tracing/entrypoint_test.go +++ b/pkg/middlewares/tracing/entrypoint_test.go @@ -1,7 +1,6 @@ package tracing import ( - "context" "net/http" "net/http/httptest" "testing" @@ -79,7 +78,7 @@ func TestEntryPointMiddleware(t *testing.T) { assert.Equal(t, test.expected.OperationName, span.OpName) }) - handler := NewEntryPoint(context.Background(), newTracing, test.entryPoint, next) + handler := NewEntryPoint(t.Context(), newTracing, test.entryPoint, next) handler.ServeHTTP(rw, req) }) } diff --git a/pkg/middlewares/tracing/forwarder_test.go b/pkg/middlewares/tracing/forwarder_test.go index 6eab47432..af9182e17 100644 --- a/pkg/middlewares/tracing/forwarder_test.go +++ b/pkg/middlewares/tracing/forwarder_test.go @@ -1,7 +1,6 @@ package tracing import ( - "context" "net/http" "net/http/httptest" "testing" @@ -129,7 +128,7 @@ func TestNewForwarder(t *testing.T) { assert.Equal(t, test.expected.OperationName, span.OpName) }) - handler := NewForwarder(context.Background(), test.router, test.service, next) + handler := NewForwarder(t.Context(), test.router, test.service, next) handler.ServeHTTP(rw, req) }) } diff --git a/pkg/provider/acme/local_store_test.go b/pkg/provider/acme/local_store_test.go index 61d2cff6b..257cfa3b0 100644 --- a/pkg/provider/acme/local_store_test.go +++ b/pkg/provider/acme/local_store_test.go @@ -1,7 +1,6 @@ package acme import ( - "context" "fmt" "os" "path/filepath" @@ -47,7 +46,7 @@ func TestLocalStore_GetAccount(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { - s := NewLocalStore(test.filename, safe.NewPool(context.Background())) + s := NewLocalStore(test.filename, safe.NewPool(t.Context())) account, err := s.GetAccount("test") require.NoError(t, err) @@ -60,7 +59,7 @@ func TestLocalStore_GetAccount(t *testing.T) { func TestLocalStore_SaveAccount(t *testing.T) { acmeFile := filepath.Join(t.TempDir(), "acme.json") - s := NewLocalStore(acmeFile, safe.NewPool(context.Background())) + s := NewLocalStore(acmeFile, safe.NewPool(t.Context())) email := "some@email.com" diff --git a/pkg/provider/acme/provider_test.go b/pkg/provider/acme/provider_test.go index 73dc785ff..e34fb9bad 100644 --- a/pkg/provider/acme/provider_test.go +++ b/pkg/provider/acme/provider_test.go @@ -1,7 +1,6 @@ package acme import ( - "context" "crypto/tls" "testing" "time" @@ -181,7 +180,7 @@ func TestGetUncheckedCertificates(t *testing.T) { resolvingDomains: test.resolvingDomains, } - domains := acmeProvider.getUncheckedDomains(context.Background(), test.domains, "default") + domains := acmeProvider.getUncheckedDomains(t.Context(), test.domains, "default") assert.Len(t, domains, len(test.expectedDomains), "Unexpected domains.") }) } @@ -245,7 +244,7 @@ func TestProvider_sanitizeDomains(t *testing.T) { acmeProvider := Provider{Configuration: &Configuration{DNSChallenge: test.dnsChallenge}} - domains, err := acmeProvider.sanitizeDomains(context.Background(), test.domains) + domains, err := acmeProvider.sanitizeDomains(t.Context(), test.domains) if len(test.expectedErr) > 0 { assert.EqualError(t, err, test.expectedErr, "Unexpected error.") @@ -424,7 +423,7 @@ func TestDeleteUnnecessaryDomains(t *testing.T) { t.Run(test.desc, func(t *testing.T) { t.Parallel() - domains := deleteUnnecessaryDomains(context.Background(), test.domains) + domains := deleteUnnecessaryDomains(t.Context(), test.domains) assert.Equal(t, test.expectedDomains, domains, "unexpected domain") }) } @@ -497,7 +496,7 @@ func TestIsAccountMatchingCaServer(t *testing.T) { t.Run(test.desc, func(t *testing.T) { t.Parallel() - result := isAccountMatchingCaServer(context.Background(), test.accountURI, test.serverURI) + result := isAccountMatchingCaServer(t.Context(), test.accountURI, test.serverURI) assert.Equal(t, test.expected, result) }) @@ -574,7 +573,7 @@ func TestInitAccount(t *testing.T) { acmeProvider := Provider{account: test.account, Configuration: &Configuration{Email: test.email, KeyType: test.keyType}} - actualAccount, err := acmeProvider.initAccount(context.Background()) + actualAccount, err := acmeProvider.initAccount(t.Context()) assert.NoError(t, err, "Init account in error") assert.Equal(t, test.expectedAccount.Email, actualAccount.Email, "unexpected email account") assert.Equal(t, test.expectedAccount.KeyType, actualAccount.KeyType, "unexpected keyType account") diff --git a/pkg/provider/aggregator/aggregator_test.go b/pkg/provider/aggregator/aggregator_test.go index 4bcf2bd79..b7fbd1b4f 100644 --- a/pkg/provider/aggregator/aggregator_test.go +++ b/pkg/provider/aggregator/aggregator_test.go @@ -1,7 +1,6 @@ package aggregator import ( - "context" "testing" "time" @@ -24,7 +23,7 @@ func TestProviderAggregator_Provide(t *testing.T) { cfgCh := make(chan dynamic.Message) errCh := make(chan error) - pool := safe.NewPool(context.Background()) + pool := safe.NewPool(t.Context()) t.Cleanup(pool.Stop) diff --git a/pkg/provider/consulcatalog/config_test.go b/pkg/provider/consulcatalog/config_test.go index 22f830c48..478ad2332 100644 --- a/pkg/provider/consulcatalog/config_test.go +++ b/pkg/provider/consulcatalog/config_test.go @@ -1,7 +1,6 @@ package consulcatalog import ( - "context" "fmt" "testing" @@ -294,7 +293,7 @@ func TestDefaultRule(t *testing.T) { require.NoError(t, err) } - configuration := p.buildConfiguration(context.Background(), test.items, nil) + configuration := p.buildConfiguration(t.Context(), test.items, nil) assert.Equal(t, test.expected, configuration) }) @@ -3186,7 +3185,7 @@ func Test_buildConfiguration(t *testing.T) { test.items[i].Tags = tags } - configuration := p.buildConfiguration(context.Background(), test.items, &connectCert{ + configuration := p.buildConfiguration(t.Context(), test.items, &connectCert{ root: []string{"root"}, leaf: keyPair{ cert: "cert", diff --git a/pkg/provider/docker/config_test.go b/pkg/provider/docker/config_test.go index 03a5af050..b6b3dc538 100644 --- a/pkg/provider/docker/config_test.go +++ b/pkg/provider/docker/config_test.go @@ -1,7 +1,6 @@ package docker import ( - "context" "strconv" "testing" @@ -387,7 +386,7 @@ func TestDefaultRule(t *testing.T) { require.NoError(t, err) } - configuration := p.buildConfiguration(context.Background(), test.containers) + configuration := p.buildConfiguration(t.Context(), test.containers) assert.Equal(t, test.expected, configuration) }) @@ -3504,7 +3503,7 @@ func Test_buildConfiguration(t *testing.T) { require.NoError(t, err) } - configuration := p.buildConfiguration(context.Background(), test.containers) + configuration := p.buildConfiguration(t.Context(), test.containers) assert.Equal(t, test.expected, configuration) }) @@ -3676,7 +3675,7 @@ func TestDockerGetIPPort(t *testing.T) { UseBindPortIP: true, } - actualIP, actualPort, actualError := provider.getIPPort(context.Background(), dData, test.serverPort) + actualIP, actualPort, actualError := provider.getIPPort(t.Context(), dData, test.serverPort) if test.expected.error { require.Error(t, actualError) } else { @@ -3857,7 +3856,7 @@ func TestDockerGetIPAddress(t *testing.T) { dData.ExtraConf.Docker.Network = test.network } - actual := provider.getIPAddress(context.Background(), dData) + actual := provider.getIPAddress(t.Context(), dData) assert.Equal(t, test.expected, actual) }) } @@ -3917,10 +3916,10 @@ func TestSwarmGetIPAddress(t *testing.T) { SwarmMode: true, } - dData, err := provider.parseService(context.Background(), test.service, test.networks) + dData, err := provider.parseService(t.Context(), test.service, test.networks) require.NoError(t, err) - actual := provider.getIPAddress(context.Background(), dData) + actual := provider.getIPAddress(t.Context(), dData) assert.Equal(t, test.expected, actual) }) } @@ -3949,7 +3948,7 @@ func TestSwarmGetPort(t *testing.T) { p := Provider{} - dData, err := p.parseService(context.Background(), test.service, test.networks) + dData, err := p.parseService(t.Context(), test.service, test.networks) require.NoError(t, err) actual := getPort(dData, test.serverPort) diff --git a/pkg/provider/docker/swarm_test.go b/pkg/provider/docker/swarm_test.go index 05c9451f0..a70c9488e 100644 --- a/pkg/provider/docker/swarm_test.go +++ b/pkg/provider/docker/swarm_test.go @@ -83,11 +83,11 @@ func TestListTasks(t *testing.T) { t.Parallel() p := Provider{} - dockerData, err := p.parseService(context.Background(), test.service, test.networks) + dockerData, err := p.parseService(t.Context(), test.service, test.networks) require.NoError(t, err) dockerClient := &fakeTasksClient{tasks: test.tasks} - taskDockerData, _ := listTasks(context.Background(), dockerClient, test.service.ID, dockerData, test.networks, test.isGlobalSVC) + taskDockerData, _ := listTasks(t.Context(), dockerClient, test.service.ID, dockerData, test.networks, test.isGlobalSVC) if len(test.expectedTasks) != len(taskDockerData) { t.Errorf("expected tasks %v, got %v", test.expectedTasks, taskDockerData) @@ -277,7 +277,7 @@ func TestListServices(t *testing.T) { p := Provider{} - serviceDockerData, err := p.listServices(context.Background(), dockerClient) + serviceDockerData, err := p.listServices(t.Context(), dockerClient) assert.NoError(t, err) assert.Len(t, serviceDockerData, len(test.expectedServices)) @@ -395,11 +395,11 @@ func TestSwarmTaskParsing(t *testing.T) { p := Provider{} - dData, err := p.parseService(context.Background(), test.service, test.networks) + dData, err := p.parseService(t.Context(), test.service, test.networks) require.NoError(t, err) for _, task := range test.tasks { - taskDockerData := parseTasks(context.Background(), task, dData, test.networks, test.isGlobalSVC) + taskDockerData := parseTasks(t.Context(), task, dData, test.networks, test.isGlobalSVC) expected := test.expected[task.ID] assert.Equal(t, expected.Name, taskDockerData.Name) } diff --git a/pkg/provider/ecs/config_test.go b/pkg/provider/ecs/config_test.go index 6f72344cf..dcd867b04 100644 --- a/pkg/provider/ecs/config_test.go +++ b/pkg/provider/ecs/config_test.go @@ -1,7 +1,6 @@ package ecs import ( - "context" "testing" ec2types "github.com/aws/aws-sdk-go-v2/service/ec2/types" @@ -358,7 +357,7 @@ func TestDefaultRule(t *testing.T) { require.NoError(t, err) } - configuration := p.buildConfiguration(context.Background(), test.instances) + configuration := p.buildConfiguration(t.Context(), test.instances) assert.Equal(t, test.expected, configuration) }) @@ -3088,7 +3087,7 @@ func Test_buildConfiguration(t *testing.T) { require.NoError(t, err) } - configuration := p.buildConfiguration(context.Background(), test.containers) + configuration := p.buildConfiguration(t.Context(), test.containers) assert.Equal(t, test.expected, configuration) }) diff --git a/pkg/provider/file/file_test.go b/pkg/provider/file/file_test.go index cf40d7032..76bb0ff91 100644 --- a/pkg/provider/file/file_test.go +++ b/pkg/provider/file/file_test.go @@ -1,7 +1,6 @@ package file import ( - "context" "io" "os" "path/filepath" @@ -60,7 +59,7 @@ func TestTLSCertificateContent(t *testing.T) { require.NoError(t, err) provider := &Provider{} - configuration, err := provider.loadFileConfig(context.Background(), fileConfig.Name(), true) + configuration, err := provider.loadFileConfig(t.Context(), fileConfig.Name(), true) require.NoError(t, err) require.Equal(t, "CONTENT", configuration.TLS.Certificates[0].Certificate.CertFile.String()) @@ -81,7 +80,7 @@ func TestErrorWhenEmptyConfig(t *testing.T) { configChan := make(chan dynamic.Message) errorChan := make(chan struct{}) go func() { - err := provider.Provide(configChan, safe.NewPool(context.Background())) + err := provider.Provide(configChan, safe.NewPool(t.Context())) assert.Error(t, err) close(errorChan) }() @@ -105,7 +104,7 @@ func TestProvideWithoutWatch(t *testing.T) { provider.DebugLogGeneratedTemplate = true go func() { - err := provider.Provide(configChan, safe.NewPool(context.Background())) + err := provider.Provide(configChan, safe.NewPool(t.Context())) assert.NoError(t, err) }() @@ -135,7 +134,7 @@ func TestProvideWithWatch(t *testing.T) { configChan := make(chan dynamic.Message) go func() { - err := provider.Provide(configChan, safe.NewPool(context.Background())) + err := provider.Provide(configChan, safe.NewPool(t.Context())) assert.NoError(t, err) }() diff --git a/pkg/provider/http/http_test.go b/pkg/provider/http/http_test.go index d63a3cacc..cb7d8cdb5 100644 --- a/pkg/provider/http/http_test.go +++ b/pkg/provider/http/http_test.go @@ -1,7 +1,6 @@ package http import ( - "context" "fmt" "net/http" "net/http/httptest" @@ -212,7 +211,7 @@ func TestProvider_Provide(t *testing.T) { }, } - err = provider.Provide(configurationChan, safe.NewPool(context.Background())) + err = provider.Provide(configurationChan, safe.NewPool(t.Context())) require.NoError(t, err) timeout := time.After(time.Second) @@ -246,7 +245,7 @@ func TestProvider_ProvideConfigurationOnlyOnceIfUnchanged(t *testing.T) { configurationChan := make(chan dynamic.Message, 10) - err = provider.Provide(configurationChan, safe.NewPool(context.Background())) + err = provider.Provide(configurationChan, safe.NewPool(t.Context())) require.NoError(t, err) time.Sleep(time.Second) diff --git a/pkg/provider/kubernetes/crd/kubernetes_test.go b/pkg/provider/kubernetes/crd/kubernetes_test.go index ba9031b33..d00ca8a8e 100644 --- a/pkg/provider/kubernetes/crd/kubernetes_test.go +++ b/pkg/provider/kubernetes/crd/kubernetes_test.go @@ -1,7 +1,6 @@ package crd import ( - "context" "os" "path/filepath" "strings" @@ -1466,7 +1465,7 @@ func TestLoadIngressRouteTCPs(t *testing.T) { } clientMock := newClientMock(test.paths...) - conf := p.loadConfigurationFromCRD(context.Background(), clientMock) + conf := p.loadConfigurationFromCRD(t.Context(), clientMock) assert.Equal(t, test.expected, conf) }) } @@ -4241,7 +4240,7 @@ func TestLoadIngressRoutes(t *testing.T) { } clientMock := newClientMock(test.paths...) - conf := p.loadConfigurationFromCRD(context.Background(), clientMock) + conf := p.loadConfigurationFromCRD(t.Context(), clientMock) assert.Equal(t, test.expected, conf) }) } @@ -4721,7 +4720,7 @@ func TestLoadIngressRouteUDPs(t *testing.T) { } clientMock := newClientMock(test.paths...) - conf := p.loadConfigurationFromCRD(context.Background(), clientMock) + conf := p.loadConfigurationFromCRD(t.Context(), clientMock) assert.Equal(t, test.expected, conf) }) } @@ -6098,7 +6097,7 @@ func TestCrossNamespace(t *testing.T) { p := Provider{AllowCrossNamespace: test.allowCrossNamespace} - conf := p.loadConfigurationFromCRD(context.Background(), client) + conf := p.loadConfigurationFromCRD(t.Context(), client) assert.Equal(t, test.expected, conf) }) } @@ -6387,7 +6386,7 @@ func TestExternalNameService(t *testing.T) { p := Provider{AllowExternalNameServices: test.allowExternalNameService} - conf := p.loadConfigurationFromCRD(context.Background(), client) + conf := p.loadConfigurationFromCRD(t.Context(), client) assert.Equal(t, test.expected, conf) }) } @@ -6593,7 +6592,7 @@ func TestNativeLB(t *testing.T) { p := Provider{} - conf := p.loadConfigurationFromCRD(context.Background(), client) + conf := p.loadConfigurationFromCRD(t.Context(), client) assert.Equal(t, test.expected, conf) }) } diff --git a/pkg/provider/kubernetes/gateway/kubernetes_test.go b/pkg/provider/kubernetes/gateway/kubernetes_test.go index 1d51d26af..f7f0c7858 100644 --- a/pkg/provider/kubernetes/gateway/kubernetes_test.go +++ b/pkg/provider/kubernetes/gateway/kubernetes_test.go @@ -1,7 +1,6 @@ package gateway import ( - "context" "testing" "github.com/stretchr/testify/assert" @@ -1481,7 +1480,7 @@ func TestLoadHTTPRoutes(t *testing.T) { } p := Provider{EntryPoints: test.entryPoints} - conf := p.loadConfigurationFromGateway(context.Background(), newClientMock(test.paths...)) + conf := p.loadConfigurationFromGateway(t.Context(), newClientMock(test.paths...)) assert.Equal(t, test.expected, conf) }) } @@ -2215,7 +2214,7 @@ func TestLoadTCPRoutes(t *testing.T) { } p := Provider{EntryPoints: test.entryPoints} - conf := p.loadConfigurationFromGateway(context.Background(), newClientMock(test.paths...)) + conf := p.loadConfigurationFromGateway(t.Context(), newClientMock(test.paths...)) assert.Equal(t, test.expected, conf) }) } @@ -3307,7 +3306,7 @@ func TestLoadTLSRoutes(t *testing.T) { } p := Provider{EntryPoints: test.entryPoints} - conf := p.loadConfigurationFromGateway(context.Background(), newClientMock(test.paths...)) + conf := p.loadConfigurationFromGateway(t.Context(), newClientMock(test.paths...)) assert.Equal(t, test.expected, conf) }) } @@ -4258,7 +4257,7 @@ func TestLoadMixedRoutes(t *testing.T) { } p := Provider{EntryPoints: test.entryPoints} - conf := p.loadConfigurationFromGateway(context.Background(), newClientMock(test.paths...)) + conf := p.loadConfigurationFromGateway(t.Context(), newClientMock(test.paths...)) assert.Equal(t, test.expected, conf) }) } diff --git a/pkg/provider/kubernetes/ingress/client_test.go b/pkg/provider/kubernetes/ingress/client_test.go index 725ba3491..0f82d9847 100644 --- a/pkg/provider/kubernetes/ingress/client_test.go +++ b/pkg/provider/kubernetes/ingress/client_test.go @@ -1,7 +1,6 @@ package ingress import ( - "context" "errors" "testing" "time" @@ -243,7 +242,7 @@ func TestClientIgnoresEmptyEndpointUpdates(t *testing.T) { assert.Fail(t, "expected to receive event for endpoints") } - emptyEndpoint, err = kubeClient.CoreV1().Endpoints("test").Get(context.TODO(), "empty-endpoint", metav1.GetOptions{}) + emptyEndpoint, err = kubeClient.CoreV1().Endpoints("test").Get(t.Context(), "empty-endpoint", metav1.GetOptions{}) assert.NoError(t, err) // Update endpoint annotation and resource version (apparently not done by fake client itself) @@ -251,7 +250,7 @@ func TestClientIgnoresEmptyEndpointUpdates(t *testing.T) { // This reflects the behavior of kubernetes controllers which use endpoint annotations for leader election. emptyEndpoint.Annotations["test-annotation"] = "___" emptyEndpoint.ResourceVersion = "1245" - _, err = kubeClient.CoreV1().Endpoints("test").Update(context.TODO(), emptyEndpoint, metav1.UpdateOptions{}) + _, err = kubeClient.CoreV1().Endpoints("test").Update(t.Context(), emptyEndpoint, metav1.UpdateOptions{}) require.NoError(t, err) select { @@ -263,12 +262,12 @@ func TestClientIgnoresEmptyEndpointUpdates(t *testing.T) { case <-time.After(50 * time.Millisecond): } - filledEndpoint, err = kubeClient.CoreV1().Endpoints("test").Get(context.TODO(), "filled-endpoint", metav1.GetOptions{}) + filledEndpoint, err = kubeClient.CoreV1().Endpoints("test").Get(t.Context(), "filled-endpoint", metav1.GetOptions{}) assert.NoError(t, err) filledEndpoint.Subsets[0].Addresses[0].IP = "10.13.37.2" filledEndpoint.ResourceVersion = "1235" - _, err = kubeClient.CoreV1().Endpoints("test").Update(context.TODO(), filledEndpoint, metav1.UpdateOptions{}) + _, err = kubeClient.CoreV1().Endpoints("test").Update(t.Context(), filledEndpoint, metav1.UpdateOptions{}) require.NoError(t, err) select { diff --git a/pkg/provider/kubernetes/ingress/kubernetes_test.go b/pkg/provider/kubernetes/ingress/kubernetes_test.go index 8780d83c7..078fdbaa9 100644 --- a/pkg/provider/kubernetes/ingress/kubernetes_test.go +++ b/pkg/provider/kubernetes/ingress/kubernetes_test.go @@ -1,7 +1,6 @@ package ingress import ( - "context" "errors" "math" "os" @@ -1658,7 +1657,7 @@ func TestLoadConfigurationFromIngresses(t *testing.T) { clientMock := newClientMock(serverVersion, paths...) p := Provider{IngressClass: test.ingressClass, AllowEmptyServices: test.allowEmptyServices} - conf := p.loadConfigurationFromIngresses(context.Background(), clientMock) + conf := p.loadConfigurationFromIngresses(t.Context(), clientMock) assert.Equal(t, test.expected, conf) }) @@ -1804,7 +1803,7 @@ func TestLoadConfigurationFromIngressesWithExternalNameServices(t *testing.T) { p := Provider{IngressClass: test.ingressClass} p.AllowExternalNameServices = test.allowExternalNameServices - conf := p.loadConfigurationFromIngresses(context.Background(), clientMock) + conf := p.loadConfigurationFromIngresses(t.Context(), clientMock) assert.Equal(t, test.expected, conf) }) @@ -1881,7 +1880,7 @@ func TestLoadConfigurationFromIngressesWithNativeLB(t *testing.T) { clientMock := newClientMock(serverVersion, paths...) p := Provider{IngressClass: test.ingressClass} - conf := p.loadConfigurationFromIngresses(context.Background(), clientMock) + conf := p.loadConfigurationFromIngresses(t.Context(), clientMock) assert.Equal(t, test.expected, conf) }) @@ -2053,7 +2052,7 @@ func TestGetCertificates(t *testing.T) { t.Parallel() tlsConfigs := map[string]*tls.CertAndStores{} - err := getCertificates(context.Background(), test.ingress, test.client, tlsConfigs) + err := getCertificates(t.Context(), test.ingress, test.client, tlsConfigs) if test.errResult != "" { assert.EqualError(t, err, test.errResult) diff --git a/pkg/provider/kv/kv_test.go b/pkg/provider/kv/kv_test.go index 18693d485..3d3fdad2f 100644 --- a/pkg/provider/kv/kv_test.go +++ b/pkg/provider/kv/kv_test.go @@ -1,7 +1,6 @@ package kv import ( - "context" "errors" "testing" "time" @@ -285,7 +284,7 @@ func Test_buildConfiguration(t *testing.T) { "traefik/tls/certificates/1/stores/1": "foobar", })) - cfg, err := provider.buildConfiguration(context.Background()) + cfg, err := provider.buildConfiguration(t.Context()) require.NoError(t, err) expected := &dynamic.Configuration{ @@ -931,7 +930,7 @@ func Test_buildConfiguration_KV_error(t *testing.T) { }, } - cfg, err := provider.buildConfiguration(context.Background()) + cfg, err := provider.buildConfiguration(t.Context()) require.Error(t, err) assert.Nil(t, cfg) } @@ -950,7 +949,7 @@ func TestKvWatchTree(t *testing.T) { configChan := make(chan dynamic.Message) go func() { - err := provider.watchKv(context.Background(), configChan) + err := provider.watchKv(t.Context(), configChan) require.NoError(t, err) }() diff --git a/pkg/provider/marathon/config_test.go b/pkg/provider/marathon/config_test.go index f8923833c..4857aad8e 100644 --- a/pkg/provider/marathon/config_test.go +++ b/pkg/provider/marathon/config_test.go @@ -1,7 +1,6 @@ package marathon import ( - "context" "math" "testing" @@ -22,7 +21,7 @@ func TestGetConfigurationAPIErrors(t *testing.T) { marathonClient: fakeClient, } - actualConfig := p.getConfigurations(context.Background()) + actualConfig := p.getConfigurations(t.Context()) fakeClient.AssertExpectations(t) if actualConfig != nil { @@ -2051,7 +2050,7 @@ func TestBuildConfiguration(t *testing.T) { err := p.Init() require.NoError(t, err) - actualConfig := p.buildConfiguration(context.Background(), test.applications) + actualConfig := p.buildConfiguration(t.Context(), test.applications) assert.NotNil(t, actualConfig) assert.Equal(t, test.expected, actualConfig) @@ -2097,7 +2096,7 @@ func TestApplicationFilterEnabled(t *testing.T) { extraConf, err := provider.getConfiguration(app) require.NoError(t, err) - if provider.keepApplication(context.Background(), extraConf, stringValueMap(app.Labels)) != test.expected { + if provider.keepApplication(t.Context(), extraConf, stringValueMap(app.Labels)) != test.expected { t.Errorf("got unexpected filtering = %t", !test.expected) } }) diff --git a/pkg/provider/nomad/config_test.go b/pkg/provider/nomad/config_test.go index b18335d3c..ec3545db5 100644 --- a/pkg/provider/nomad/config_test.go +++ b/pkg/provider/nomad/config_test.go @@ -1,7 +1,6 @@ package nomad import ( - "context" "testing" "github.com/stretchr/testify/assert" @@ -229,8 +228,7 @@ func Test_defaultRule(t *testing.T) { err := p.Init() require.NoError(t, err) - ctx := context.TODO() - config := p.buildConfig(ctx, test.items) + config := p.buildConfig(t.Context(), test.items) require.Equal(t, test.expected, config) }) } @@ -2680,8 +2678,7 @@ func Test_buildConfig(t *testing.T) { err := p.Init() require.NoError(t, err) - ctx := context.TODO() - c := p.buildConfig(ctx, test.items) + c := p.buildConfig(t.Context(), test.items) require.Equal(t, test.expected, c) }) } @@ -2729,8 +2726,8 @@ func Test_keepItem(t *testing.T) { p := new(Provider) p.SetDefaults() p.Constraints = test.constraints - ctx := context.TODO() - result := p.keepItem(ctx, test.i) + + result := p.keepItem(t.Context(), test.i) require.Equal(t, test.exp, result) }) } diff --git a/pkg/provider/nomad/nomad_test.go b/pkg/provider/nomad/nomad_test.go index 745a85a23..e6a11c60c 100644 --- a/pkg/provider/nomad/nomad_test.go +++ b/pkg/provider/nomad/nomad_test.go @@ -1,7 +1,6 @@ package nomad import ( - "context" "net/http" "net/http/httptest" "strings" @@ -155,7 +154,7 @@ func Test_getNomadServiceData(t *testing.T) { require.NoError(t, err) // make the query for services - items, err := p.getNomadServiceData(context.TODO()) + items, err := p.getNomadServiceData(t.Context()) require.NoError(t, err) require.Len(t, items, 2) } diff --git a/pkg/provider/rancher/config_test.go b/pkg/provider/rancher/config_test.go index 80e9b2322..6dfe75e44 100644 --- a/pkg/provider/rancher/config_test.go +++ b/pkg/provider/rancher/config_test.go @@ -1,7 +1,6 @@ package rancher import ( - "context" "testing" "github.com/stretchr/testify/assert" @@ -1281,7 +1280,7 @@ func Test_buildConfiguration(t *testing.T) { require.NoError(t, err) } - configuration := p.buildConfiguration(context.Background(), test.containers) + configuration := p.buildConfiguration(t.Context(), test.containers) assert.Equal(t, test.expected, configuration) }) diff --git a/pkg/provider/traefik/internal_test.go b/pkg/provider/traefik/internal_test.go index 133273044..3dccf6843 100644 --- a/pkg/provider/traefik/internal_test.go +++ b/pkg/provider/traefik/internal_test.go @@ -1,7 +1,6 @@ package traefik import ( - "context" "encoding/json" "flag" "os" @@ -262,7 +261,7 @@ func Test_createConfiguration(t *testing.T) { provider := Provider{staticCfg: test.staticCfg} - cfg := provider.createConfiguration(context.Background()) + cfg := provider.createConfiguration(t.Context()) filename := filepath.Join("fixtures", test.desc) diff --git a/pkg/safe/routine_test.go b/pkg/safe/routine_test.go index 12875972c..3fa403e78 100644 --- a/pkg/safe/routine_test.go +++ b/pkg/safe/routine_test.go @@ -15,7 +15,7 @@ func TestNewPoolContext(t *testing.T) { testKey := testKeyType("test") - ctx := context.WithValue(context.Background(), testKey, "test") + ctx := context.WithValue(t.Context(), testKey, "test") p := NewPool(ctx) p.GoCtx(func(ctx context.Context) { @@ -66,7 +66,7 @@ func TestPoolWithCtx(t *testing.T) { t.Run(test.desc, func(t *testing.T) { // These subtests cannot be run in parallel, since the testRoutine // is shared across the subtests. - p := NewPool(context.Background()) + p := NewPool(t.Context()) timer := time.NewTimer(500 * time.Millisecond) defer timer.Stop() @@ -93,7 +93,7 @@ func TestPoolWithCtx(t *testing.T) { } func TestPoolCleanupWithGoPanicking(t *testing.T) { - p := NewPool(context.Background()) + p := NewPool(t.Context()) timer := time.NewTimer(500 * time.Millisecond) defer timer.Stop() diff --git a/pkg/server/configurationwatcher_test.go b/pkg/server/configurationwatcher_test.go index e7179734c..64019c191 100644 --- a/pkg/server/configurationwatcher_test.go +++ b/pkg/server/configurationwatcher_test.go @@ -57,7 +57,7 @@ func (p *mockProvider) Init() error { } func TestNewConfigurationWatcher(t *testing.T) { - routinesPool := safe.NewPool(context.Background()) + routinesPool := safe.NewPool(t.Context()) t.Cleanup(routinesPool.Stop) pvd := &mockProvider{ @@ -114,7 +114,7 @@ func TestNewConfigurationWatcher(t *testing.T) { } func TestWaitForRequiredProvider(t *testing.T) { - routinesPool := safe.NewPool(context.Background()) + routinesPool := safe.NewPool(t.Context()) pvdAggregator := &mockProvider{ wait: 5 * time.Millisecond, @@ -162,7 +162,7 @@ func TestWaitForRequiredProvider(t *testing.T) { } func TestIgnoreTransientConfiguration(t *testing.T) { - routinesPool := safe.NewPool(context.Background()) + routinesPool := safe.NewPool(t.Context()) config := &dynamic.Configuration{ HTTP: th.BuildConfiguration( @@ -298,7 +298,7 @@ func TestIgnoreTransientConfiguration(t *testing.T) { } func TestListenProvidersThrottleProviderConfigReload(t *testing.T) { - routinesPool := safe.NewPool(context.Background()) + routinesPool := safe.NewPool(t.Context()) pvd := &mockProvider{ wait: 10 * time.Millisecond, @@ -343,7 +343,7 @@ func TestListenProvidersThrottleProviderConfigReload(t *testing.T) { } func TestListenProvidersSkipsEmptyConfigs(t *testing.T) { - routinesPool := safe.NewPool(context.Background()) + routinesPool := safe.NewPool(t.Context()) pvd := &mockProvider{ messages: []dynamic.Message{{ProviderName: "mock"}}, @@ -364,7 +364,7 @@ func TestListenProvidersSkipsEmptyConfigs(t *testing.T) { } func TestListenProvidersSkipsSameConfigurationForProvider(t *testing.T) { - routinesPool := safe.NewPool(context.Background()) + routinesPool := safe.NewPool(t.Context()) message := dynamic.Message{ ProviderName: "mock", @@ -398,7 +398,7 @@ func TestListenProvidersSkipsSameConfigurationForProvider(t *testing.T) { } func TestListenProvidersDoesNotSkipFlappingConfiguration(t *testing.T) { - routinesPool := safe.NewPool(context.Background()) + routinesPool := safe.NewPool(t.Context()) configuration := &dynamic.Configuration{ HTTP: th.BuildConfiguration( @@ -466,7 +466,7 @@ func TestListenProvidersDoesNotSkipFlappingConfiguration(t *testing.T) { } func TestListenProvidersIgnoreSameConfig(t *testing.T) { - routinesPool := safe.NewPool(context.Background()) + routinesPool := safe.NewPool(t.Context()) configuration := &dynamic.Configuration{ HTTP: th.BuildConfiguration( @@ -557,7 +557,7 @@ func TestListenProvidersIgnoreSameConfig(t *testing.T) { } func TestApplyConfigUnderStress(t *testing.T) { - routinesPool := safe.NewPool(context.Background()) + routinesPool := safe.NewPool(t.Context()) watcher := NewConfigurationWatcher(routinesPool, &mockProvider{}, []string{}, "") @@ -600,7 +600,7 @@ func TestApplyConfigUnderStress(t *testing.T) { } func TestListenProvidersIgnoreIntermediateConfigs(t *testing.T) { - routinesPool := safe.NewPool(context.Background()) + routinesPool := safe.NewPool(t.Context()) configuration := &dynamic.Configuration{ HTTP: th.BuildConfiguration( @@ -691,7 +691,7 @@ func TestListenProvidersIgnoreIntermediateConfigs(t *testing.T) { } func TestListenProvidersPublishesConfigForEachProvider(t *testing.T) { - routinesPool := safe.NewPool(context.Background()) + routinesPool := safe.NewPool(t.Context()) configuration := &dynamic.Configuration{ HTTP: th.BuildConfiguration( @@ -756,7 +756,7 @@ func TestListenProvidersPublishesConfigForEachProvider(t *testing.T) { } func TestPublishConfigUpdatedByProvider(t *testing.T) { - routinesPool := safe.NewPool(context.Background()) + routinesPool := safe.NewPool(t.Context()) pvdConfiguration := dynamic.Configuration{ TCP: &dynamic.TCPConfiguration{ @@ -802,7 +802,7 @@ func TestPublishConfigUpdatedByProvider(t *testing.T) { } func TestPublishConfigUpdatedByConfigWatcherListener(t *testing.T) { - routinesPool := safe.NewPool(context.Background()) + routinesPool := safe.NewPool(t.Context()) pvd := &mockProvider{ wait: 10 * time.Millisecond, diff --git a/pkg/server/middleware/middlewares_test.go b/pkg/server/middleware/middlewares_test.go index 58b053053..ed4456b31 100644 --- a/pkg/server/middleware/middlewares_test.go +++ b/pkg/server/middleware/middlewares_test.go @@ -1,7 +1,6 @@ package middleware import ( - "context" "errors" "net/http" "net/http/httptest" @@ -20,7 +19,7 @@ func TestBuilder_BuildChainNilConfig(t *testing.T) { } middlewaresBuilder := NewBuilder(testConfig, nil, nil) - chain := middlewaresBuilder.BuildChain(context.Background(), []string{"empty"}) + chain := middlewaresBuilder.BuildChain(t.Context(), []string{"empty"}) _, err := chain.Then(nil) require.Error(t, err) } @@ -31,7 +30,7 @@ func TestBuilder_BuildChainNonExistentChain(t *testing.T) { } middlewaresBuilder := NewBuilder(testConfig, nil, nil) - chain := middlewaresBuilder.BuildChain(context.Background(), []string{"empty"}) + chain := middlewaresBuilder.BuildChain(t.Context(), []string{"empty"}) _, err := chain.Then(nil) require.Error(t, err) } @@ -259,7 +258,7 @@ func TestBuilder_BuildChainWithContext(t *testing.T) { t.Run(test.desc, func(t *testing.T) { t.Parallel() - ctx := context.Background() + ctx := t.Context() if len(test.contextProvider) > 0 { ctx = provider.AddInContext(ctx, "foobar@"+test.contextProvider) } @@ -366,7 +365,7 @@ func TestBuilder_buildConstructor(t *testing.T) { t.Run(test.desc, func(t *testing.T) { t.Parallel() - constructor, err := middlewaresBuilder.buildConstructor(context.Background(), test.middlewareID) + constructor, err := middlewaresBuilder.buildConstructor(t.Context(), test.middlewareID) require.NoError(t, err) middleware, err2 := constructor(http.HandlerFunc(func(_ http.ResponseWriter, _ *http.Request) {})) diff --git a/pkg/server/provider/provider_test.go b/pkg/server/provider/provider_test.go index 7686d1bb5..f4a0b27e3 100644 --- a/pkg/server/provider/provider_test.go +++ b/pkg/server/provider/provider_test.go @@ -16,31 +16,31 @@ func TestAddInContext(t *testing.T) { }{ { desc: "without provider information", - ctx: context.Background(), + ctx: t.Context(), name: "test", expected: "", }, { desc: "provider name embedded in element name", - ctx: context.Background(), + ctx: t.Context(), name: "test@foo", expected: "foo", }, { desc: "provider name in context", - ctx: context.WithValue(context.Background(), key, "foo"), + ctx: context.WithValue(t.Context(), key, "foo"), name: "test", expected: "foo", }, { desc: "provider name in context and different provider name embedded in element name", - ctx: context.WithValue(context.Background(), key, "foo"), + ctx: context.WithValue(t.Context(), key, "foo"), name: "test@fii", expected: "fii", }, { desc: "provider name in context and same provider name embedded in element name", - ctx: context.WithValue(context.Background(), key, "foo"), + ctx: context.WithValue(t.Context(), key, "foo"), name: "test@foo", expected: "foo", }, @@ -71,31 +71,31 @@ func TestGetQualifiedName(t *testing.T) { }{ { desc: "empty name", - ctx: context.Background(), + ctx: t.Context(), name: "", expected: "", }, { desc: "without provider", - ctx: context.Background(), + ctx: t.Context(), name: "test", expected: "test", }, { desc: "with explicit provider", - ctx: context.Background(), + ctx: t.Context(), name: "test@foo", expected: "test@foo", }, { desc: "with provider in context", - ctx: context.WithValue(context.Background(), key, "foo"), + ctx: context.WithValue(t.Context(), key, "foo"), name: "test", expected: "test@foo", }, { desc: "with provider in context and explicit name", - ctx: context.WithValue(context.Background(), key, "foo"), + ctx: context.WithValue(t.Context(), key, "foo"), name: "test@fii", expected: "test@fii", }, diff --git a/pkg/server/router/router_test.go b/pkg/server/router/router_test.go index 3f6c9bbe9..c1c5e3b8a 100644 --- a/pkg/server/router/router_test.go +++ b/pkg/server/router/router_test.go @@ -1,7 +1,6 @@ package router import ( - "context" "io" "math" "net/http" @@ -322,7 +321,7 @@ func TestRouterManager_Get(t *testing.T) { routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, chainBuilder, metrics.NewVoidRegistry(), tlsManager) - handlers := routerManager.BuildHandlers(context.Background(), test.entryPoints, false) + handlers := routerManager.BuildHandlers(t.Context(), test.entryPoints, false) w := httptest.NewRecorder() req := testhelpers.MustNewRequest(http.MethodGet, "http://foo.bar/", nil) @@ -429,7 +428,7 @@ func TestAccessLog(t *testing.T) { routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, chainBuilder, metrics.NewVoidRegistry(), tlsManager) - handlers := routerManager.BuildHandlers(context.Background(), test.entryPoints, false) + handlers := routerManager.BuildHandlers(t.Context(), test.entryPoints, false) w := httptest.NewRecorder() req := testhelpers.MustNewRequest(http.MethodGet, "http://foo.bar/", nil) @@ -813,12 +812,12 @@ func TestRuntimeConfiguration(t *testing.T) { middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager, nil) chainBuilder := middleware.NewChainBuilder(nil, nil, nil) tlsManager := tls.NewManager() - tlsManager.UpdateConfigs(context.Background(), nil, test.tlsOptions, nil) + tlsManager.UpdateConfigs(t.Context(), nil, test.tlsOptions, nil) routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, chainBuilder, metrics.NewVoidRegistry(), tlsManager) - _ = routerManager.BuildHandlers(context.Background(), entryPoints, false) - _ = routerManager.BuildHandlers(context.Background(), entryPoints, true) + _ = routerManager.BuildHandlers(t.Context(), entryPoints, false) + _ = routerManager.BuildHandlers(t.Context(), entryPoints, true) // even though rtConf was passed by argument to the manager builders above, // it's ok to use it as the result we check, because everything worth checking @@ -894,7 +893,7 @@ func TestProviderOnMiddlewares(t *testing.T) { routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, chainBuilder, metrics.NewVoidRegistry(), tlsManager) - _ = routerManager.BuildHandlers(context.Background(), entryPoints, false) + _ = routerManager.BuildHandlers(t.Context(), entryPoints, false) assert.Equal(t, []string{"chain@file", "m1@file"}, rtConf.Routers["router@file"].Middlewares) assert.Equal(t, []string{"m1@file", "m2@file", "m1@file"}, rtConf.Middlewares["chain@file"].Chain.Middlewares) @@ -963,7 +962,7 @@ func BenchmarkRouterServe(b *testing.B) { routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, chainBuilder, metrics.NewVoidRegistry(), tlsManager) - handlers := routerManager.BuildHandlers(context.Background(), entryPoints, false) + handlers := routerManager.BuildHandlers(b.Context(), entryPoints, false) w := httptest.NewRecorder() req := testhelpers.MustNewRequest(http.MethodGet, "http://foo.bar/", nil) @@ -1003,7 +1002,7 @@ func BenchmarkService(b *testing.B) { w := httptest.NewRecorder() req := testhelpers.MustNewRequest(http.MethodGet, "http://foo.bar/", nil) - handler, _ := serviceManager.BuildHTTP(context.Background(), "foo-service") + handler, _ := serviceManager.BuildHTTP(b.Context(), "foo-service") b.ReportAllocs() for range b.N { handler.ServeHTTP(w, req) diff --git a/pkg/server/router/tcp/manager_test.go b/pkg/server/router/tcp/manager_test.go index 2ccbcb24b..d005f1b0e 100644 --- a/pkg/server/router/tcp/manager_test.go +++ b/pkg/server/router/tcp/manager_test.go @@ -1,7 +1,6 @@ package tcp import ( - "context" "crypto/tls" "math" "net/http" @@ -347,7 +346,7 @@ func TestRuntimeConfiguration(t *testing.T) { serviceManager := tcp.NewManager(conf) tlsManager := traefiktls.NewManager() tlsManager.UpdateConfigs( - context.Background(), + t.Context(), map[string]traefiktls.Store{}, map[string]traefiktls.Options{ "default": { @@ -367,7 +366,7 @@ func TestRuntimeConfiguration(t *testing.T) { routerManager := NewManager(conf, serviceManager, middlewaresBuilder, nil, nil, tlsManager) - _ = routerManager.BuildHandlers(context.Background(), entryPoints) + _ = routerManager.BuildHandlers(t.Context(), entryPoints) // even though conf was passed by argument to the manager builders above, // it's ok to use it as the result we check, because everything worth checking @@ -658,7 +657,7 @@ func TestDomainFronting(t *testing.T) { serviceManager := tcp.NewManager(conf) tlsManager := traefiktls.NewManager() - tlsManager.UpdateConfigs(context.Background(), map[string]traefiktls.Store{}, test.tlsOptions, []*traefiktls.CertAndStores{}) + tlsManager.UpdateConfigs(t.Context(), map[string]traefiktls.Store{}, test.tlsOptions, []*traefiktls.CertAndStores{}) httpsHandler := map[string]http.Handler{ "web": http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {}), @@ -668,7 +667,7 @@ func TestDomainFronting(t *testing.T) { routerManager := NewManager(conf, serviceManager, middlewaresBuilder, nil, httpsHandler, tlsManager) - routers := routerManager.BuildHandlers(context.Background(), entryPoints) + routers := routerManager.BuildHandlers(t.Context(), entryPoints) router, ok := routers["web"] require.True(t, ok) diff --git a/pkg/server/router/tcp/router_test.go b/pkg/server/router/tcp/router_test.go index 2f997c7f1..55b4b4f11 100644 --- a/pkg/server/router/tcp/router_test.go +++ b/pkg/server/router/tcp/router_test.go @@ -2,7 +2,6 @@ package tcp import ( "bytes" - "context" "crypto/tls" "errors" "fmt" @@ -172,7 +171,7 @@ func Test_Routing(t *testing.T) { // Creates the tlsManager and defines the TLS 1.0 and 1.2 TLSOptions. tlsManager := traefiktls.NewManager() tlsManager.UpdateConfigs( - context.Background(), + t.Context(), map[string]traefiktls.Store{ tlsalpn01.ACMETLS1Protocol: {}, }, @@ -588,7 +587,7 @@ func Test_Routing(t *testing.T) { router(dynConf) } - router, err := manager.buildEntryPointHandler(context.Background(), dynConf.TCPRouters, dynConf.Routers, nil, nil) + router, err := manager.buildEntryPointHandler(t.Context(), dynConf.TCPRouters, dynConf.Routers, nil, nil) require.NoError(t, err) if test.allowACMETLSPassthrough { diff --git a/pkg/server/router/udp/router_test.go b/pkg/server/router/udp/router_test.go index a71662b48..5967e3fe2 100644 --- a/pkg/server/router/udp/router_test.go +++ b/pkg/server/router/udp/router_test.go @@ -1,7 +1,6 @@ package udp import ( - "context" "testing" "github.com/stretchr/testify/assert" @@ -118,7 +117,7 @@ func TestRuntimeConfiguration(t *testing.T) { serviceManager := udp.NewManager(conf) routerManager := NewManager(conf, serviceManager) - _ = routerManager.BuildHandlers(context.Background(), entryPoints) + _ = routerManager.BuildHandlers(t.Context(), entryPoints) // even though conf was passed by argument to the manager builders above, // it's ok to use it as the result we check, because everything worth checking diff --git a/pkg/server/server_entrypoint_tcp.go b/pkg/server/server_entrypoint_tcp.go index ea1f1975b..3257d6720 100644 --- a/pkg/server/server_entrypoint_tcp.go +++ b/pkg/server/server_entrypoint_tcp.go @@ -31,8 +31,6 @@ import ( "github.com/traefik/traefik/v2/pkg/server/service" "github.com/traefik/traefik/v2/pkg/tcp" "github.com/traefik/traefik/v2/pkg/types" - "golang.org/x/net/http2" - "golang.org/x/net/http2/h2c" ) var httpServerLogger = stdlog.New(log.WithoutContext().WriterLevel(logrus.DebugLevel), "", 0) @@ -577,11 +575,12 @@ func createHTTPServer(ctx context.Context, ln net.Listener, configuration *stati handler = newKeepAliveMiddleware(handler, configuration.Transport.KeepAliveMaxRequests, configuration.Transport.KeepAliveMaxTime) } - if withH2c { - handler = h2c.NewHandler(handler, &http2.Server{ - MaxConcurrentStreams: uint32(configuration.HTTP2.MaxConcurrentStreams), - }) - } + var protocols http.Protocols + protocols.SetHTTP1(true) + protocols.SetHTTP2(true) + + // With the addition of UnencryptedHTTP2 in http.Server#Protocols in go1.24 setting the h2c handler is not necessary anymore. + protocols.SetUnencryptedHTTP2(withH2c) if configuration.HTTP.EncodeQuerySemicolons { handler = encodeQuerySemicolons(handler) @@ -602,11 +601,15 @@ func createHTTPServer(ctx context.Context, ln net.Listener, configuration *stati handler = denyFragment(handler) serverHTTP := &http.Server{ + Protocols: &protocols, Handler: handler, ErrorLog: httpServerLogger, ReadTimeout: time.Duration(configuration.Transport.RespondingTimeouts.ReadTimeout), WriteTimeout: time.Duration(configuration.Transport.RespondingTimeouts.WriteTimeout), IdleTimeout: time.Duration(configuration.Transport.RespondingTimeouts.IdleTimeout), + HTTP2: &http.HTTP2Config{ + MaxConcurrentStreams: int(configuration.HTTP2.MaxConcurrentStreams), + }, } if debugConnection || (configuration.Transport != nil && (configuration.Transport.KeepAliveMaxTime > 0 || configuration.Transport.KeepAliveMaxRequests > 0)) { serverHTTP.ConnContext = func(ctx context.Context, c net.Conn) context.Context { @@ -640,19 +643,6 @@ func createHTTPServer(ctx context.Context, ln net.Listener, configuration *stati 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 - if !strings.Contains(os.Getenv("GODEBUG"), "http2server=0") { - err = http2.ConfigureServer(serverHTTP, &http2.Server{ - 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) - } - } - listener := newHTTPForwarder(ln) go func() { err := serverHTTP.Serve(listener) diff --git a/pkg/server/server_entrypoint_tcp_http3_test.go b/pkg/server/server_entrypoint_tcp_http3_test.go index 2dee92df2..bcaa1c028 100644 --- a/pkg/server/server_entrypoint_tcp_http3_test.go +++ b/pkg/server/server_entrypoint_tcp_http3_test.go @@ -2,7 +2,6 @@ package server import ( "bufio" - "context" "crypto/tls" "crypto/x509" "net/http" @@ -87,7 +86,7 @@ func TestHTTP3AdvertisedPort(t *testing.T) { epConfig := &static.EntryPointsTransport{} epConfig.SetDefaults() - entryPoint, err := NewTCPEntryPoint(context.Background(), &static.EntryPoint{ + entryPoint, err := NewTCPEntryPoint(t.Context(), &static.EntryPoint{ Address: "127.0.0.1:0", Transport: epConfig, ForwardedHeaders: &static.ForwardedHeaders{}, @@ -108,7 +107,7 @@ func TestHTTP3AdvertisedPort(t *testing.T) { rw.WriteHeader(http.StatusOK) }), nil) - ctx := context.Background() + ctx := t.Context() go entryPoint.Start(ctx) entryPoint.SwitchRouter(router) @@ -151,7 +150,7 @@ func TestHTTP30RTT(t *testing.T) { epConfig := &static.EntryPointsTransport{} epConfig.SetDefaults() - entryPoint, err := NewTCPEntryPoint(context.Background(), &static.EntryPoint{ + entryPoint, err := NewTCPEntryPoint(t.Context(), &static.EntryPoint{ Address: "127.0.0.1:8090", Transport: epConfig, ForwardedHeaders: &static.ForwardedHeaders{}, @@ -170,7 +169,7 @@ func TestHTTP30RTT(t *testing.T) { rw.WriteHeader(http.StatusOK) }), nil) - ctx := context.Background() + ctx := t.Context() go entryPoint.Start(ctx) entryPoint.SwitchRouter(router) @@ -193,7 +192,7 @@ func TestHTTP30RTT(t *testing.T) { tlsConf.ClientSessionCache = cache // This first DialAddrEarly connection is here to populate the cache. - earlyConnection, err := quic.DialAddrEarly(context.Background(), "127.0.0.1:8090", tlsConf, &quic.Config{}) + earlyConnection, err := quic.DialAddrEarly(t.Context(), "127.0.0.1:8090", tlsConf, &quic.Config{}) require.NoError(t, err) t.Cleanup(func() { @@ -207,7 +206,7 @@ func TestHTTP30RTT(t *testing.T) { // 0RTT is always false on the first connection. require.False(t, earlyConnection.ConnectionState().Used0RTT) - earlyConnection, err = quic.DialAddrEarly(context.Background(), "127.0.0.1:8090", tlsConf, &quic.Config{}) + earlyConnection, err = quic.DialAddrEarly(t.Context(), "127.0.0.1:8090", tlsConf, &quic.Config{}) require.NoError(t, err) <-earlyConnection.HandshakeComplete() diff --git a/pkg/server/server_entrypoint_tcp_test.go b/pkg/server/server_entrypoint_tcp_test.go index e007fc325..f09c9ba63 100644 --- a/pkg/server/server_entrypoint_tcp_test.go +++ b/pkg/server/server_entrypoint_tcp_test.go @@ -81,7 +81,7 @@ func testShutdown(t *testing.T, router *tcprouter.Router) { epConfig.RespondingTimeouts.ReadTimeout = ptypes.Duration(5 * time.Second) epConfig.RespondingTimeouts.WriteTimeout = ptypes.Duration(5 * time.Second) - entryPoint, err := NewTCPEntryPoint(context.Background(), &static.EntryPoint{ + entryPoint, err := NewTCPEntryPoint(t.Context(), &static.EntryPoint{ // We explicitly use an IPV4 address because on Alpine, with an IPV6 address // there seems to be shenanigans related to properly cleaning up file descriptors Address: "127.0.0.1:0", @@ -91,7 +91,7 @@ func testShutdown(t *testing.T, router *tcprouter.Router) { }, nil) require.NoError(t, err) - conn, err := startEntrypoint(entryPoint, router) + conn, err := startEntrypoint(t, entryPoint, router) require.NoError(t, err) t.Cleanup(func() { _ = conn.Close() }) @@ -114,7 +114,7 @@ func testShutdown(t *testing.T, router *tcprouter.Router) { _, err = reader.Peek(1) require.NoError(t, err) - go entryPoint.Shutdown(context.Background()) + go entryPoint.Shutdown(t.Context()) // Make sure that new connections are not permitted anymore. // Note that this should be true not only after Shutdown has returned, @@ -145,8 +145,10 @@ func testShutdown(t *testing.T, router *tcprouter.Router) { assert.Equal(t, http.StatusOK, resp.StatusCode) } -func startEntrypoint(entryPoint *TCPEntryPoint, router *tcprouter.Router) (net.Conn, error) { - go entryPoint.Start(context.Background()) +func startEntrypoint(t *testing.T, entryPoint *TCPEntryPoint, router *tcprouter.Router) (net.Conn, error) { + t.Helper() + + go entryPoint.Start(t.Context()) entryPoint.SwitchRouter(router) @@ -168,7 +170,7 @@ func TestReadTimeoutWithoutFirstByte(t *testing.T) { epConfig.SetDefaults() epConfig.RespondingTimeouts.ReadTimeout = ptypes.Duration(2 * time.Second) - entryPoint, err := NewTCPEntryPoint(context.Background(), &static.EntryPoint{ + entryPoint, err := NewTCPEntryPoint(t.Context(), &static.EntryPoint{ Address: ":0", Transport: epConfig, ForwardedHeaders: &static.ForwardedHeaders{}, @@ -183,7 +185,7 @@ func TestReadTimeoutWithoutFirstByte(t *testing.T) { rw.WriteHeader(http.StatusOK) })) - conn, err := startEntrypoint(entryPoint, router) + conn, err := startEntrypoint(t, entryPoint, router) require.NoError(t, err) errChan := make(chan error) @@ -207,7 +209,7 @@ func TestReadTimeoutWithFirstByte(t *testing.T) { epConfig.SetDefaults() epConfig.RespondingTimeouts.ReadTimeout = ptypes.Duration(2 * time.Second) - entryPoint, err := NewTCPEntryPoint(context.Background(), &static.EntryPoint{ + entryPoint, err := NewTCPEntryPoint(t.Context(), &static.EntryPoint{ Address: ":0", Transport: epConfig, ForwardedHeaders: &static.ForwardedHeaders{}, @@ -222,7 +224,7 @@ func TestReadTimeoutWithFirstByte(t *testing.T) { rw.WriteHeader(http.StatusOK) })) - conn, err := startEntrypoint(entryPoint, router) + conn, err := startEntrypoint(t, entryPoint, router) require.NoError(t, err) _, err = conn.Write([]byte("GET /some HTTP/1.1\r\n")) @@ -249,7 +251,7 @@ func TestKeepAliveMaxRequests(t *testing.T) { epConfig.SetDefaults() epConfig.KeepAliveMaxRequests = 3 - entryPoint, err := NewTCPEntryPoint(context.Background(), &static.EntryPoint{ + entryPoint, err := NewTCPEntryPoint(t.Context(), &static.EntryPoint{ Address: ":0", Transport: epConfig, ForwardedHeaders: &static.ForwardedHeaders{}, @@ -264,7 +266,7 @@ func TestKeepAliveMaxRequests(t *testing.T) { rw.WriteHeader(http.StatusOK) })) - conn, err := startEntrypoint(entryPoint, router) + conn, err := startEntrypoint(t, entryPoint, router) require.NoError(t, err) http.DefaultClient.Transport = &http.Transport{ @@ -297,7 +299,7 @@ func TestKeepAliveMaxTime(t *testing.T) { epConfig.SetDefaults() epConfig.KeepAliveMaxTime = ptypes.Duration(time.Millisecond) - entryPoint, err := NewTCPEntryPoint(context.Background(), &static.EntryPoint{ + entryPoint, err := NewTCPEntryPoint(t.Context(), &static.EntryPoint{ Address: ":0", Transport: epConfig, ForwardedHeaders: &static.ForwardedHeaders{}, @@ -312,7 +314,7 @@ func TestKeepAliveMaxTime(t *testing.T) { rw.WriteHeader(http.StatusOK) })) - conn, err := startEntrypoint(entryPoint, router) + conn, err := startEntrypoint(t, entryPoint, router) require.NoError(t, err) http.DefaultClient.Transport = &http.Transport{ @@ -341,7 +343,7 @@ func TestKeepAliveH2c(t *testing.T) { epConfig.SetDefaults() epConfig.KeepAliveMaxRequests = 1 - entryPoint, err := NewTCPEntryPoint(context.Background(), &static.EntryPoint{ + entryPoint, err := NewTCPEntryPoint(t.Context(), &static.EntryPoint{ Address: ":0", Transport: epConfig, ForwardedHeaders: &static.ForwardedHeaders{}, @@ -356,7 +358,7 @@ func TestKeepAliveH2c(t *testing.T) { rw.WriteHeader(http.StatusOK) })) - conn, err := startEntrypoint(entryPoint, router) + conn, err := startEntrypoint(t, entryPoint, router) require.NoError(t, err) http2Transport := &http2.Transport{ @@ -574,7 +576,7 @@ func TestPathOperations(t *testing.T) { configuration.SetDefaults() // Create the HTTP server using createHTTPServer. - server, err := createHTTPServer(context.Background(), ln, configuration, false, requestdecorator.New(nil)) + server, err := createHTTPServer(t.Context(), ln, configuration, false, requestdecorator.New(nil)) require.NoError(t, err) server.Switcher.UpdateHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { diff --git a/pkg/server/server_entrypoint_udp_test.go b/pkg/server/server_entrypoint_udp_test.go index 0396f434f..580d087cb 100644 --- a/pkg/server/server_entrypoint_udp_test.go +++ b/pkg/server/server_entrypoint_udp_test.go @@ -1,7 +1,6 @@ package server import ( - "context" "io" "net" "testing" @@ -27,7 +26,7 @@ func TestShutdownUDPConn(t *testing.T) { entryPoint, err := NewUDPEntryPoint(&ep) require.NoError(t, err) - go entryPoint.Start(context.Background()) + go entryPoint.Start(t.Context()) entryPoint.Switch(udp.HandlerFunc(func(conn *udp.Conn) { for { b := make([]byte, 1024*1024) @@ -56,7 +55,7 @@ func TestShutdownUDPConn(t *testing.T) { doneChan := make(chan struct{}) go func() { - entryPoint.Shutdown(context.Background()) + entryPoint.Shutdown(t.Context()) close(doneChan) }() diff --git a/pkg/server/service/loadbalancer/failover/failover_test.go b/pkg/server/service/loadbalancer/failover/failover_test.go index 9f8d38215..410557ee9 100644 --- a/pkg/server/service/loadbalancer/failover/failover_test.go +++ b/pkg/server/service/loadbalancer/failover/failover_test.go @@ -1,7 +1,6 @@ package failover import ( - "context" "net/http" "net/http/httptest" "testing" @@ -51,7 +50,7 @@ func TestFailover(t *testing.T) { assert.Equal(t, []int{200}, recorder.status) assert.True(t, status) - failover.SetHandlerStatus(context.Background(), false) + failover.SetHandlerStatus(t.Context(), false) recorder = &responseRecorder{ResponseRecorder: httptest.NewRecorder(), save: map[string]int{}} failover.ServeHTTP(recorder, httptest.NewRequest(http.MethodGet, "/", nil)) @@ -61,7 +60,7 @@ func TestFailover(t *testing.T) { assert.Equal(t, []int{200}, recorder.status) assert.True(t, status) - failover.SetFallbackHandlerStatus(context.Background(), false) + failover.SetFallbackHandlerStatus(t.Context(), false) recorder = &responseRecorder{ResponseRecorder: httptest.NewRecorder(), save: map[string]int{}} failover.ServeHTTP(recorder, httptest.NewRequest(http.MethodGet, "/", nil)) @@ -92,7 +91,7 @@ func TestFailoverDownThenUp(t *testing.T) { assert.Equal(t, 0, recorder.save["fallback"]) assert.Equal(t, []int{200}, recorder.status) - failover.SetHandlerStatus(context.Background(), false) + failover.SetHandlerStatus(t.Context(), false) recorder = &responseRecorder{ResponseRecorder: httptest.NewRecorder(), save: map[string]int{}} failover.ServeHTTP(recorder, httptest.NewRequest(http.MethodGet, "/", nil)) @@ -101,7 +100,7 @@ func TestFailoverDownThenUp(t *testing.T) { assert.Equal(t, 1, recorder.save["fallback"]) assert.Equal(t, []int{200}, recorder.status) - failover.SetHandlerStatus(context.Background(), true) + failover.SetHandlerStatus(t.Context(), true) recorder = &responseRecorder{ResponseRecorder: httptest.NewRecorder(), save: map[string]int{}} failover.ServeHTTP(recorder, httptest.NewRequest(http.MethodGet, "/", nil)) @@ -129,7 +128,7 @@ func TestFailoverPropagate(t *testing.T) { rw.WriteHeader(http.StatusOK) })) err := failover.RegisterStatusUpdater(func(up bool) { - topFailover.SetHandlerStatus(context.Background(), up) + topFailover.SetHandlerStatus(t.Context(), up) }) require.NoError(t, err) @@ -141,7 +140,7 @@ func TestFailoverPropagate(t *testing.T) { assert.Equal(t, 0, recorder.save["topFailover"]) assert.Equal(t, []int{200}, recorder.status) - failover.SetHandlerStatus(context.Background(), false) + failover.SetHandlerStatus(t.Context(), false) recorder = &responseRecorder{ResponseRecorder: httptest.NewRecorder(), save: map[string]int{}} topFailover.ServeHTTP(recorder, httptest.NewRequest(http.MethodGet, "/", nil)) @@ -151,7 +150,7 @@ func TestFailoverPropagate(t *testing.T) { assert.Equal(t, 0, recorder.save["topFailover"]) assert.Equal(t, []int{200}, recorder.status) - failover.SetFallbackHandlerStatus(context.Background(), false) + failover.SetFallbackHandlerStatus(t.Context(), false) recorder = &responseRecorder{ResponseRecorder: httptest.NewRecorder(), save: map[string]int{}} topFailover.ServeHTTP(recorder, httptest.NewRequest(http.MethodGet, "/", nil)) diff --git a/pkg/server/service/loadbalancer/mirror/mirror_test.go b/pkg/server/service/loadbalancer/mirror/mirror_test.go index b417fa13d..3cb59b2f2 100644 --- a/pkg/server/service/loadbalancer/mirror/mirror_test.go +++ b/pkg/server/service/loadbalancer/mirror/mirror_test.go @@ -2,7 +2,6 @@ package mirror import ( "bytes" - "context" "io" "net/http" "net/http/httptest" @@ -20,7 +19,7 @@ func TestMirroringOn100(t *testing.T) { handler := http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { rw.WriteHeader(http.StatusOK) }) - pool := safe.NewPool(context.Background()) + pool := safe.NewPool(t.Context()) mirror := New(handler, pool, defaultMaxBodySize, nil) err := mirror.AddMirror(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { atomic.AddInt32(&countMirror1, 1) @@ -49,7 +48,7 @@ func TestMirroringOn10(t *testing.T) { handler := http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { rw.WriteHeader(http.StatusOK) }) - pool := safe.NewPool(context.Background()) + pool := safe.NewPool(t.Context()) mirror := New(handler, pool, defaultMaxBodySize, nil) err := mirror.AddMirror(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { atomic.AddInt32(&countMirror1, 1) @@ -74,7 +73,7 @@ func TestMirroringOn10(t *testing.T) { } func TestInvalidPercent(t *testing.T) { - mirror := New(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {}), safe.NewPool(context.Background()), defaultMaxBodySize, nil) + mirror := New(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {}), safe.NewPool(t.Context()), defaultMaxBodySize, nil) err := mirror.AddMirror(nil, -1) assert.Error(t, err) @@ -92,7 +91,7 @@ func TestHijack(t *testing.T) { handler := http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { rw.WriteHeader(http.StatusOK) }) - pool := safe.NewPool(context.Background()) + pool := safe.NewPool(t.Context()) mirror := New(handler, pool, defaultMaxBodySize, nil) var mirrorRequest bool @@ -116,7 +115,7 @@ func TestFlush(t *testing.T) { handler := http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { rw.WriteHeader(http.StatusOK) }) - pool := safe.NewPool(context.Background()) + pool := safe.NewPool(t.Context()) mirror := New(handler, pool, defaultMaxBodySize, nil) var mirrorRequest bool @@ -144,7 +143,7 @@ func TestMirroringWithBody(t *testing.T) { body = []byte(`body`) ) - pool := safe.NewPool(context.Background()) + pool := safe.NewPool(t.Context()) handler := http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { assert.NotNil(t, r.Body) diff --git a/pkg/server/service/loadbalancer/wrr/wrr_test.go b/pkg/server/service/loadbalancer/wrr/wrr_test.go index 8ef17faeb..4a953128d 100644 --- a/pkg/server/service/loadbalancer/wrr/wrr_test.go +++ b/pkg/server/service/loadbalancer/wrr/wrr_test.go @@ -90,8 +90,8 @@ func TestBalancerNoServiceUp(t *testing.T) { rw.WriteHeader(http.StatusInternalServerError) }), pointer(1)) - balancer.SetStatus(context.WithValue(context.Background(), serviceName, "parent"), "first", false) - balancer.SetStatus(context.WithValue(context.Background(), serviceName, "parent"), "second", false) + balancer.SetStatus(context.WithValue(t.Context(), serviceName, "parent"), "first", false) + balancer.SetStatus(context.WithValue(t.Context(), serviceName, "parent"), "second", false) recorder := httptest.NewRecorder() balancer.ServeHTTP(recorder, httptest.NewRequest(http.MethodGet, "/", nil)) @@ -110,7 +110,7 @@ func TestBalancerOneServerDown(t *testing.T) { balancer.AddService("second", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { rw.WriteHeader(http.StatusInternalServerError) }), pointer(1)) - balancer.SetStatus(context.WithValue(context.Background(), serviceName, "parent"), "second", false) + balancer.SetStatus(context.WithValue(t.Context(), serviceName, "parent"), "second", false) recorder := &responseRecorder{ResponseRecorder: httptest.NewRecorder(), save: map[string]int{}} for range 3 { @@ -132,7 +132,7 @@ func TestBalancerDownThenUp(t *testing.T) { rw.Header().Set("server", "second") rw.WriteHeader(http.StatusOK) }), pointer(1)) - balancer.SetStatus(context.WithValue(context.Background(), serviceName, "parent"), "second", false) + balancer.SetStatus(context.WithValue(t.Context(), serviceName, "parent"), "second", false) recorder := &responseRecorder{ResponseRecorder: httptest.NewRecorder(), save: map[string]int{}} for range 3 { @@ -140,7 +140,7 @@ func TestBalancerDownThenUp(t *testing.T) { } assert.Equal(t, 3, recorder.save["first"]) - balancer.SetStatus(context.WithValue(context.Background(), serviceName, "parent"), "second", true) + balancer.SetStatus(context.WithValue(t.Context(), serviceName, "parent"), "second", true) recorder = &responseRecorder{ResponseRecorder: httptest.NewRecorder(), save: map[string]int{}} for range 2 { balancer.ServeHTTP(recorder, httptest.NewRequest(http.MethodGet, "/", nil)) @@ -174,13 +174,13 @@ func TestBalancerPropagate(t *testing.T) { topBalancer := New(nil, &dynamic.HealthCheck{}) topBalancer.AddService("balancer1", balancer1, pointer(1)) _ = balancer1.RegisterStatusUpdater(func(up bool) { - topBalancer.SetStatus(context.WithValue(context.Background(), serviceName, "top"), "balancer1", up) + topBalancer.SetStatus(context.WithValue(t.Context(), serviceName, "top"), "balancer1", up) // TODO(mpl): if test gets flaky, add channel or something here to signal that // propagation is done, and wait on it before sending request. }) topBalancer.AddService("balancer2", balancer2, pointer(1)) _ = balancer2.RegisterStatusUpdater(func(up bool) { - topBalancer.SetStatus(context.WithValue(context.Background(), serviceName, "top"), "balancer2", up) + topBalancer.SetStatus(context.WithValue(t.Context(), serviceName, "top"), "balancer2", up) }) recorder := &responseRecorder{ResponseRecorder: httptest.NewRecorder(), save: map[string]int{}} @@ -195,7 +195,7 @@ func TestBalancerPropagate(t *testing.T) { assert.Equal(t, wantStatus, recorder.status) // fourth gets downed, but balancer2 still up since third is still up. - balancer2.SetStatus(context.WithValue(context.Background(), serviceName, "top"), "fourth", false) + balancer2.SetStatus(context.WithValue(t.Context(), serviceName, "top"), "fourth", false) recorder = &responseRecorder{ResponseRecorder: httptest.NewRecorder(), save: map[string]int{}} for range 8 { topBalancer.ServeHTTP(recorder, httptest.NewRequest(http.MethodGet, "/", nil)) @@ -209,7 +209,7 @@ func TestBalancerPropagate(t *testing.T) { // third gets downed, and the propagation triggers balancer2 to be marked as // down as well for topBalancer. - balancer2.SetStatus(context.WithValue(context.Background(), serviceName, "top"), "third", false) + balancer2.SetStatus(context.WithValue(t.Context(), serviceName, "top"), "third", false) recorder = &responseRecorder{ResponseRecorder: httptest.NewRecorder(), save: map[string]int{}} for range 8 { topBalancer.ServeHTTP(recorder, httptest.NewRequest(http.MethodGet, "/", nil)) diff --git a/pkg/server/service/roundtripper_test.go b/pkg/server/service/roundtripper_test.go index 2c5a2584e..9a97e2c98 100644 --- a/pkg/server/service/roundtripper_test.go +++ b/pkg/server/service/roundtripper_test.go @@ -1,7 +1,6 @@ package service import ( - "context" "crypto/tls" "crypto/x509" "net" @@ -351,7 +350,7 @@ func TestKerberosRoundTripper(t *testing.T) { }), } - ctx := AddTransportOnContext(context.Background()) + ctx := AddTransportOnContext(t.Context()) for _, expected := range test.expectedStatusCode { req, err := http.NewRequestWithContext(ctx, http.MethodGet, "http://127.0.0.1", http.NoBody) require.NoError(t, err) diff --git a/pkg/server/service/service_test.go b/pkg/server/service/service_test.go index 7bee444a3..2067a800f 100644 --- a/pkg/server/service/service_test.go +++ b/pkg/server/service/service_test.go @@ -69,7 +69,7 @@ func TestGetLoadBalancer(t *testing.T) { t.Run(test.desc, func(t *testing.T) { t.Parallel() - handler, err := sm.getLoadBalancer(context.Background(), test.serviceName, test.service, test.fwd) + handler, err := sm.getLoadBalancer(t.Context(), test.serviceName, test.service, test.fwd) if test.expectError { require.Error(t, err) assert.Nil(t, handler) @@ -336,7 +336,7 @@ func TestGetLoadBalancerServiceHandler(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { - handler, err := sm.getLoadBalancerServiceHandler(context.Background(), test.serviceName, test.service) + handler, err := sm.getLoadBalancerServiceHandler(t.Context(), test.serviceName, test.service) assert.NoError(t, err) assert.NotNil(t, handler) @@ -414,7 +414,7 @@ func Test1xxResponses(t *testing.T) { }, }, } - handler, err := sm.getLoadBalancerServiceHandler(context.Background(), "foobar", config) + handler, err := sm.getLoadBalancerServiceHandler(t.Context(), "foobar", config) assert.NoError(t, err) frontend := httptest.NewServer(handler) @@ -458,7 +458,7 @@ func Test1xxResponses(t *testing.T) { return nil }, } - req, _ := http.NewRequestWithContext(httptrace.WithClientTrace(context.Background(), trace), http.MethodGet, frontend.URL, nil) + req, _ := http.NewRequestWithContext(httptrace.WithClientTrace(t.Context(), trace), http.MethodGet, frontend.URL, nil) res, err := frontendClient.Do(req) assert.NoError(t, err) @@ -506,15 +506,15 @@ func TestManager_ServiceBuilders(t *testing.T) { return nil, nil })) - h, err := manager.BuildHTTP(context.Background(), "test@internal") + h, err := manager.BuildHTTP(t.Context(), "test@internal") require.NoError(t, err) assert.Equal(t, internalHandler, h) - h, err = manager.BuildHTTP(context.Background(), "test@test") + h, err = manager.BuildHTTP(t.Context(), "test@test") require.NoError(t, err) assert.NotNil(t, h) - _, err = manager.BuildHTTP(context.Background(), "wrong@test") + _, err = manager.BuildHTTP(t.Context(), "wrong@test") assert.Error(t, err) } @@ -571,7 +571,7 @@ func TestManager_Build(t *testing.T) { }, }) - ctx := context.Background() + ctx := t.Context() if len(test.providerName) > 0 { ctx = provider.AddInContext(ctx, "foobar@"+test.providerName) } @@ -598,6 +598,6 @@ func TestMultipleTypeOnBuildHTTP(t *testing.T) { }, }) - _, err := manager.BuildHTTP(context.Background(), "test@file") + _, err := manager.BuildHTTP(t.Context(), "test@file") assert.Error(t, err, "cannot create service: multi-types service not supported, consider declaring two different pieces of service instead") } diff --git a/pkg/server/service/tcp/service_test.go b/pkg/server/service/tcp/service_test.go index 6587469cc..857a9568b 100644 --- a/pkg/server/service/tcp/service_test.go +++ b/pkg/server/service/tcp/service_test.go @@ -1,7 +1,6 @@ package tcp import ( - "context" "testing" "github.com/stretchr/testify/assert" @@ -181,7 +180,7 @@ func TestManager_BuildTCP(t *testing.T) { TCPServices: test.configs, }) - ctx := context.Background() + ctx := t.Context() if len(test.providerName) > 0 { ctx = provider.AddInContext(ctx, "foobar@"+test.providerName) } diff --git a/pkg/server/service/udp/service_test.go b/pkg/server/service/udp/service_test.go index a3ee3b15e..f3de425f3 100644 --- a/pkg/server/service/udp/service_test.go +++ b/pkg/server/service/udp/service_test.go @@ -1,7 +1,6 @@ package udp import ( - "context" "testing" "github.com/stretchr/testify/assert" @@ -181,7 +180,7 @@ func TestManager_BuildUDP(t *testing.T) { UDPServices: test.configs, }) - ctx := context.Background() + ctx := t.Context() if len(test.providerName) > 0 { ctx = provider.AddInContext(ctx, "foobar@"+test.providerName) } diff --git a/pkg/tls/tlsmanager_test.go b/pkg/tls/tlsmanager_test.go index c91e244a1..48490a365 100644 --- a/pkg/tls/tlsmanager_test.go +++ b/pkg/tls/tlsmanager_test.go @@ -1,7 +1,6 @@ package tls import ( - "context" "crypto/tls" "crypto/x509" "encoding/pem" @@ -77,7 +76,7 @@ func TestTLSInStore(t *testing.T) { }} tlsManager := NewManager() - tlsManager.UpdateConfigs(context.Background(), nil, nil, dynamicConfigs) + tlsManager.UpdateConfigs(t.Context(), nil, nil, dynamicConfigs) certs := tlsManager.GetStore("default").DynamicCerts.Get().(map[string]*tls.Certificate) if len(certs) == 0 { @@ -94,7 +93,7 @@ func TestTLSInvalidStore(t *testing.T) { }} tlsManager := NewManager() - tlsManager.UpdateConfigs(context.Background(), + tlsManager.UpdateConfigs(t.Context(), map[string]Store{ "default": { DefaultCertificate: &Certificate{ @@ -158,7 +157,7 @@ func TestManager_Get(t *testing.T) { } tlsManager := NewManager() - tlsManager.UpdateConfigs(context.Background(), nil, tlsConfigs, dynamicConfigs) + tlsManager.UpdateConfigs(t.Context(), nil, tlsConfigs, dynamicConfigs) for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { @@ -297,7 +296,7 @@ func TestClientAuth(t *testing.T) { } tlsManager := NewManager() - tlsManager.UpdateConfigs(context.Background(), nil, tlsConfigs, nil) + tlsManager.UpdateConfigs(t.Context(), nil, tlsConfigs, nil) for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { diff --git a/pkg/types/tls_test.go b/pkg/types/tls_test.go index b123aba53..615267644 100644 --- a/pkg/types/tls_test.go +++ b/pkg/types/tls_test.go @@ -1,7 +1,6 @@ package types import ( - "context" "testing" "github.com/stretchr/testify/assert" @@ -105,7 +104,7 @@ func TestClientTLS_CreateTLSConfig(t *testing.T) { for _, test := range tests { t.Run(test.desc, func(t *testing.T) { - tlsConfig, err := test.clientTLS.CreateTLSConfig(context.Background()) + tlsConfig, err := test.clientTLS.CreateTLSConfig(t.Context()) if test.wantErr { require.Error(t, err) return From 2fdee25bb34ca5df3e9e7914e4da1c3027cc1aec Mon Sep 17 00:00:00 2001 From: Romain Date: Mon, 2 Jun 2025 10:46:04 +0200 Subject: [PATCH 19/21] Attempt to fix TestProxyFromEnvironment test Co-authored-by: Kevin Pollet --- pkg/proxy/fast/proxy_test.go | 95 +++++++++++++----------------------- 1 file changed, 35 insertions(+), 60 deletions(-) diff --git a/pkg/proxy/fast/proxy_test.go b/pkg/proxy/fast/proxy_test.go index e26c67f63..70b1256aa 100644 --- a/pkg/proxy/fast/proxy_test.go +++ b/pkg/proxy/fast/proxy_test.go @@ -20,7 +20,6 @@ import ( "github.com/traefik/traefik/v3/pkg/config/dynamic" "github.com/traefik/traefik/v3/pkg/config/static" "github.com/traefik/traefik/v3/pkg/testhelpers" - "github.com/traefik/traefik/v3/pkg/tls/generate" ) const ( @@ -125,9 +124,17 @@ func TestProxyFromEnvironment(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { - backendURL, backendCert := newBackendServer(t, test.tls, http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { - _, _ = rw.Write([]byte("backend")) - })) + var backendServer *httptest.Server + if test.tls { + backendServer = httptest.NewTLSServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + _, _ = rw.Write([]byte("backendTLS")) + })) + } else { + backendServer = httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + _, _ = rw.Write([]byte("backend")) + })) + } + t.Cleanup(backendServer.Close) var proxyCalled bool proxyHandler := http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { @@ -155,8 +162,21 @@ func TestProxyFromEnvironment(t *testing.T) { connHj, _, err := hj.Hijack() require.NoError(t, err) - go func() { _, _ = io.Copy(connHj, conn) }() - _, _ = io.Copy(conn, connHj) + defer func() { + _ = connHj.Close() + _ = conn.Close() + }() + + errCh := make(chan error, 1) + go func() { + _, err = io.Copy(connHj, conn) + errCh <- err + }() + go func() { + _, err = io.Copy(conn, connHj) + errCh <- err + }() + <-errCh // Wait for one of the copy operations to finish }) var proxyURL string @@ -198,7 +218,7 @@ func TestProxyFromEnvironment(t *testing.T) { proxyURL = proxyServer.URL case proxyHTTPS: - proxyServer := httptest.NewServer(proxyHandler) + proxyServer := httptest.NewTLSServer(proxyHandler) t.Cleanup(proxyServer.Close) proxyURL = proxyServer.URL @@ -209,11 +229,8 @@ func TestProxyFromEnvironment(t *testing.T) { if proxyCert != nil { certPool.AddCert(proxyCert) } - if backendCert != nil { - cert, err := x509.ParseCertificate(backendCert.Certificate[0]) - require.NoError(t, err) - - certPool.AddCert(cert) + if backendServer.Certificate() != nil { + certPool.AddCert(backendServer.Certificate()) } builder := NewProxyBuilder(&transportManagerMock{tlsConfig: &tls.Config{RootCAs: certPool}}, static.FastProxyConfig{}) @@ -230,7 +247,7 @@ func TestProxyFromEnvironment(t *testing.T) { return u, nil } - reverseProxy, err := builder.Build("foo", testhelpers.MustParseURL(backendURL), false, false) + reverseProxy, err := builder.Build("foo", testhelpers.MustParseURL(backendServer.URL), false, false) require.NoError(t, err) reverseProxyServer := httptest.NewServer(reverseProxy) @@ -246,7 +263,11 @@ func TestProxyFromEnvironment(t *testing.T) { body, err := io.ReadAll(resp.Body) require.NoError(t, err) - assert.Equal(t, "backend", string(body)) + if test.tls { + assert.Equal(t, "backendTLS", string(body)) + } else { + assert.Equal(t, "backend", string(body)) + } assert.True(t, proxyCalled) }) } @@ -385,52 +406,6 @@ func TestTransferEncodingChunked(t *testing.T) { assert.Equal(t, "chunk 0\nchunk 1\nchunk 2\n", string(body)) } -func newCertificate(t *testing.T, domain string) *tls.Certificate { - t.Helper() - - certPEM, keyPEM, err := generate.KeyPair(domain, time.Time{}) - require.NoError(t, err) - - certificate, err := tls.X509KeyPair(certPEM, keyPEM) - require.NoError(t, err) - - return &certificate -} - -func newBackendServer(t *testing.T, isTLS bool, handler http.Handler) (string, *tls.Certificate) { - t.Helper() - - var ln net.Listener - var err error - var cert *tls.Certificate - - scheme := "http" - domain := "backend.localhost" - if isTLS { - scheme = "https" - - cert = newCertificate(t, domain) - - ln, err = tls.Listen("tcp", ":0", &tls.Config{Certificates: []tls.Certificate{*cert}}) - require.NoError(t, err) - } else { - ln, err = net.Listen("tcp", ":0") - require.NoError(t, err) - } - - srv := &http.Server{Handler: handler} - go func() { _ = srv.Serve(ln) }() - - t.Cleanup(func() { _ = srv.Close() }) - - _, port, err := net.SplitHostPort(ln.Addr().String()) - require.NoError(t, err) - - backendURL := fmt.Sprintf("%s://%s:%s", scheme, domain, port) - - return backendURL, cert -} - type transportManagerMock struct { tlsConfig *tls.Config } From f174014d968f69680e80fcda6439c7dc39ab8038 Mon Sep 17 00:00:00 2001 From: Michael Date: Mon, 2 Jun 2025 11:00:05 +0200 Subject: [PATCH 20/21] feat: parallelise unit tests --- .github/workflows/test-unit.yaml | 31 ++++++++++++++- internal/testsci/genmatrix.go | 66 ++++++++++++++++++++++++++++++++ 2 files changed, 96 insertions(+), 1 deletion(-) create mode 100644 internal/testsci/genmatrix.go diff --git a/.github/workflows/test-unit.yaml b/.github/workflows/test-unit.yaml index ef82911e2..33e9459df 100644 --- a/.github/workflows/test-unit.yaml +++ b/.github/workflows/test-unit.yaml @@ -14,8 +14,36 @@ env: jobs: + generate-packages: + name: List Go Packages + runs-on: ubuntu-latest + outputs: + matrix: ${{ steps.set-matrix.outputs.matrix }} + steps: + - name: Check out code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Set up Go ${{ env.GO_VERSION }} + uses: actions/setup-go@v5 + with: + go-version: ${{ env.GO_VERSION }} + check-latest: true + + - name: Generate matrix + id: set-matrix + run: | + matrix_output=$(go run ./internal/testsci/genmatrix.go) + echo "$matrix_output" + echo "$matrix_output" >> $GITHUB_OUTPUT + test-unit: runs-on: ubuntu-latest + needs: generate-packages + strategy: + matrix: + package: ${{ fromJson(needs.generate-packages.outputs.matrix) }} steps: - name: Check out code @@ -33,7 +61,8 @@ jobs: run: touch webui/static/index.html - name: Tests - run: make test-unit + run: | + go test -v -parallel 8 ${{ matrix.package.group }} test-ui-unit: runs-on: ubuntu-latest diff --git a/internal/testsci/genmatrix.go b/internal/testsci/genmatrix.go new file mode 100644 index 000000000..3c0f82775 --- /dev/null +++ b/internal/testsci/genmatrix.go @@ -0,0 +1,66 @@ +package main + +import ( + "encoding/json" + "fmt" + "os" + "strings" + + "github.com/traefik/traefik/v2/pkg/log" + "golang.org/x/tools/go/packages" +) + +const groupCount = 12 + +type group struct { + Group string `json:"group"` +} + +func main() { + logger := log.WithoutContext() + + cfg := &packages.Config{ + Mode: packages.NeedName, + Dir: ".", + } + + pkgs, err := packages.Load(cfg, "./cmd/...", "./pkg/...") + if err != nil { + logger.Fatalf("Loading packages: %v", err) + } + + var packageNames []string + for _, pkg := range pkgs { + if pkg.PkgPath != "" { + packageNames = append(packageNames, pkg.PkgPath) + } + } + + total := len(packageNames) + perGroup := (total + groupCount - 1) / groupCount + + fmt.Fprintf(os.Stderr, "Total packages: %d\n", total) + fmt.Fprintf(os.Stderr, "Packages per group: %d\n", perGroup) + + var matrix []group + for i := range groupCount { + start := i * perGroup + end := start + perGroup + if start >= total { + break + } + if end > total { + end = total + } + g := strings.Join(packageNames[start:end], " ") + matrix = append(matrix, group{Group: g}) + } + + jsonBytes, err := json.Marshal(matrix) + if err != nil { + logger.Fatalf("Failed to marshal matrix: %v", err) + } + + // Output for GitHub Actions + fmt.Printf("matrix=%s\n", string(jsonBytes)) +} From fe5c7fdc65884f7193811d7e12519df70b410be9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Stuchl=C3=ADk?= Date: Mon, 2 Jun 2025 16:22:04 +0200 Subject: [PATCH 21/21] Add a note to certificatesDuration --- docs/content/https/acme.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/content/https/acme.md b/docs/content/https/acme.md index ce613f964..8e52e6f1b 100644 --- a/docs/content/https/acme.md +++ b/docs/content/https/acme.md @@ -767,6 +767,8 @@ docker run -v "/my/host/acme:/etc/traefik/acme" traefik _Optional, Default=2160_ +`certificatesDuration` specifies the duration (in hours) of the certificates issued by the CA server. It is used to determine when to renew the certificate, but it **doesn't** define the duration of the certificates, that is up to the CA server. + `certificatesDuration` is used to calculate two durations: - `Renew Period`: the period before the end of the certificate duration, during which the certificate should be renewed.