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 03a94ec91..e541db6b9 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 b7bbb91c3..6dce764d7 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 b7104431e..b63c2278e 100644 --- a/.github/workflows/test-unit.yaml +++ b/.github/workflows/test-unit.yaml @@ -10,11 +10,39 @@ on: - 'script/gcg/**' env: - GO_VERSION: '1.23' + GO_VERSION: '1.24' 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 @@ -34,7 +62,8 @@ jobs: 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/.github/workflows/validate.yaml b/.github/workflows/validate.yaml index 4b5ec44de..a43b005b2 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/CHANGELOG.md b/CHANGELOG.md index d3516b689..4ce5dde11 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,39 @@ +## [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) + +**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)) + ## [v3.4.0](https://github.com/traefik/traefik/tree/v3.4.0) (2025-05-05) [All Commits](https://github.com/traefik/traefik/compare/v3.3.0-rc1...v3.4.0) 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/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/https/acme.md b/docs/content/https/acme.md index 764b4b30c..143243255 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. @@ -795,6 +795,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. diff --git a/docs/content/migration/v2.md b/docs/content/migration/v2.md index 02924b976..d0a879781 100644 --- a/docs/content/migration/v2.md +++ b/docs/content/migration/v2.md @@ -677,3 +677,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/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/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/dynamic-configuration/kubernetes-crd-definition-v1.yml b/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml index fbf625766..aadc87f5e 100644 --- a/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml +++ b/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml @@ -2291,7 +2291,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: |- @@ -2436,7 +2436,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/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/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" ``` diff --git a/docs/content/reference/routing-configuration/other-providers/docker.md b/docs/content/reference/routing-configuration/other-providers/docker.md index f6e07620c..ac41f3a3a 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 59f21aed9..a50b6e4cf 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 d1fac0b27..ff05bb3e0 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 13ef3ffaa..1fbbcf7ca 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/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: 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/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': diff --git a/go.mod b/go.mod index ec971dd9c..b91189865 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/traefik/traefik/v3 -go 1.23.0 +go 1.24.0 require ( github.com/BurntSushi/toml v1.5.0 @@ -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 c1bc34b23..aeff1bfa0 100644 --- a/go.sum +++ b/go.sum @@ -253,8 +253,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-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= diff --git a/integration/consul_test.go b/integration/consul_test.go index 076fd4560..31908556e 100644 --- a/integration/consul_test.go +++ b/integration/consul_test.go @@ -2,7 +2,6 @@ package integration import ( "bytes" - "context" "encoding/json" "errors" "fmt" @@ -43,7 +42,7 @@ func (s *ConsulSuite) SetupSuite() { s.consulURL = fmt.Sprintf("http://%s", consulAddr) kv, err := valkeyrie.NewStore( - context.Background(), + s.T().Context(), consul.StoreName, []string{consulAddr}, &consul.Config{ @@ -63,7 +62,7 @@ func (s *ConsulSuite) TearDownSuite() { } func (s *ConsulSuite) TearDownTest() { - err := s.kvClient.DeleteTree(context.Background(), "traefik") + err := s.kvClient.DeleteTree(s.T().Context(), "traefik") if err != nil && !errors.Is(err, store.ErrKeyNotFound) { require.ErrorIs(s.T(), err, store.ErrKeyNotFound) } @@ -118,7 +117,7 @@ func (s *ConsulSuite) TestSimpleConfiguration() { } for k, v := range data { - err := s.kvClient.Put(context.Background(), k, []byte(v), nil) + err := s.kvClient.Put(s.T().Context(), k, []byte(v), nil) require.NoError(s.T(), err) } @@ -178,7 +177,7 @@ func (s *ConsulSuite) TestDeleteRootKey() { file := s.adaptFile("fixtures/consul/simple.toml", struct{ ConsulAddress string }{s.consulURL}) - ctx := context.Background() + ctx := s.T().Context() svcaddr := net.JoinHostPort(s.getComposeServiceIP("whoami"), "80") data := map[string]string{ diff --git a/integration/etcd_test.go b/integration/etcd_test.go index 01132ba88..067c4d61c 100644 --- a/integration/etcd_test.go +++ b/integration/etcd_test.go @@ -2,7 +2,6 @@ package integration import ( "bytes" - "context" "encoding/json" "net" "net/http" @@ -41,7 +40,7 @@ func (s *EtcdSuite) SetupSuite() { var err error s.etcdAddr = net.JoinHostPort(s.getComposeServiceIP("etcd"), "2379") s.kvClient, err = valkeyrie.NewStore( - context.Background(), + s.T().Context(), etcdv3.StoreName, []string{s.etcdAddr}, &etcdv3.Config{ @@ -108,7 +107,7 @@ func (s *EtcdSuite) TestSimpleConfiguration() { } for k, v := range data { - err := s.kvClient.Put(context.Background(), k, []byte(v), nil) + err := s.kvClient.Put(s.T().Context(), k, []byte(v), nil) require.NoError(s.T(), err) } 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/fixtures/k8s/01-traefik-crd.yml b/integration/fixtures/k8s/01-traefik-crd.yml index fbf625766..aadc87f5e 100644 --- a/integration/fixtures/k8s/01-traefik-crd.yml +++ b/integration/fixtures/k8s/01-traefik-crd.yml @@ -2291,7 +2291,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: |- @@ -2436,7 +2436,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/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/grpc_test.go b/integration/grpc_test.go index 856b1980d..a624310f2 100644 --- a/integration/grpc_test.go +++ b/integration/grpc_test.go @@ -108,7 +108,9 @@ func getHelloClientGRPCh2c() (helloworld.GreeterClient, func() error, error) { return helloworld.NewGreeterClient(conn), conn.Close, nil } -func callHelloClientGRPC(name string, secure bool) (string, error) { +func callHelloClientGRPC(t *testing.T, name string, secure bool) (string, error) { + t.Helper() + var client helloworld.GreeterClient var closer func() error var err error @@ -123,24 +125,26 @@ func callHelloClientGRPC(name string, secure bool) (string, error) { if err != nil { return "", err } - r, err := client.SayHello(context.Background(), &helloworld.HelloRequest{Name: name}) + r, err := client.SayHello(t.Context(), &helloworld.HelloRequest{Name: name}) if err != nil { return "", err } return r.GetMessage(), nil } -func callStreamExampleClientGRPC() (helloworld.Greeter_StreamExampleClient, func() error, error) { +func callStreamExampleClientGRPC(t *testing.T) (helloworld.Greeter_StreamExampleClient, func() error, error) { + t.Helper() + client, closer, err := getHelloClientGRPC() if err != nil { return nil, closer, err } - t, err := client.StreamExample(context.Background(), &helloworld.StreamExampleRequest{}) + s, err := client.StreamExample(t.Context(), &helloworld.StreamExampleRequest{}) if err != nil { return nil, closer, err } - return t, closer, nil + return s, closer, nil } func (s *GRPCSuite) TestGRPC() { @@ -172,7 +176,7 @@ func (s *GRPCSuite) TestGRPC() { var response string err = try.Do(1*time.Second, func() error { - response, err = callHelloClientGRPC("World", true) + response, err = callHelloClientGRPC(s.T(), "World", true) return err }) assert.NoError(s.T(), err) @@ -204,7 +208,7 @@ func (s *GRPCSuite) TestGRPCh2c() { var response string err = try.Do(1*time.Second, func() error { - response, err = callHelloClientGRPC("World", false) + response, err = callHelloClientGRPC(s.T(), "World", false) return err }) assert.NoError(s.T(), err) @@ -240,7 +244,7 @@ func (s *GRPCSuite) TestGRPCh2cTermination() { var response string err = try.Do(1*time.Second, func() error { - response, err = callHelloClientGRPC("World", true) + response, err = callHelloClientGRPC(s.T(), "World", true) return err }) assert.NoError(s.T(), err) @@ -276,7 +280,7 @@ func (s *GRPCSuite) TestGRPCInsecure() { var response string err = try.Do(1*time.Second, func() error { - response, err = callHelloClientGRPC("World", true) + response, err = callHelloClientGRPC(s.T(), "World", true) return err }) assert.NoError(s.T(), err) @@ -314,7 +318,7 @@ func (s *GRPCSuite) TestGRPCBuffer() { err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("Host(`127.0.0.1`)")) assert.NoError(s.T(), err) var client helloworld.Greeter_StreamExampleClient - client, closer, err := callStreamExampleClientGRPC() + client, closer, err := callStreamExampleClientGRPC(s.T()) defer func() { _ = closer() }() assert.NoError(s.T(), err) @@ -367,7 +371,7 @@ func (s *GRPCSuite) TestGRPCBufferWithFlushInterval() { assert.NoError(s.T(), err) var client helloworld.Greeter_StreamExampleClient - client, closer, err := callStreamExampleClientGRPC() + client, closer, err := callStreamExampleClientGRPC(s.T()) defer func() { _ = closer() stopStreamExample <- true @@ -422,7 +426,7 @@ func (s *GRPCSuite) TestGRPCWithRetry() { var response string err = try.Do(1*time.Second, func() error { - response, err = callHelloClientGRPC("World", true) + response, err = callHelloClientGRPC(s.T(), "World", true) return err }) assert.NoError(s.T(), err) diff --git a/integration/https_test.go b/integration/https_test.go index 816818143..68dd1ed27 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" @@ -19,6 +20,7 @@ import ( "github.com/traefik/traefik/v3/pkg/config/dynamic" traefiktls "github.com/traefik/traefik/v3/pkg/tls" "github.com/traefik/traefik/v3/pkg/types" + "golang.org/x/net/http2" ) // HTTPSSuite tests suite. @@ -1174,3 +1176,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) +} diff --git a/integration/integration_test.go b/integration/integration_test.go index 039f1aae4..bb0fc2bfd 100644 --- a/integration/integration_test.go +++ b/integration/integration_test.go @@ -106,7 +106,7 @@ func (s *BaseSuite) displayTraefikLogFile(path string) { } func (s *BaseSuite) SetupSuite() { - if isDockerDesktop(context.Background(), s.T()) { + if isDockerDesktop(s.T()) { _, err := os.Stat(tailscaleSecretFilePath) require.NoError(s.T(), err, "Tailscale need to be configured when running integration tests with Docker Desktop: (https://doc.traefik.io/traefik/v2.11/contributing/building-testing/#testing)") } @@ -116,7 +116,6 @@ func (s *BaseSuite) SetupSuite() { // TODO // stdlog.SetOutput(log.Logger) - ctx := context.Background() // Create docker network // docker network create traefik-test-network --driver bridge --subnet 172.31.42.0/24 var opts []network.NetworkCustomizer @@ -129,18 +128,18 @@ func (s *BaseSuite) SetupSuite() { }, }, })) - dockerNetwork, err := network.New(ctx, opts...) + dockerNetwork, err := network.New(s.T().Context(), opts...) require.NoError(s.T(), err) s.network = dockerNetwork s.hostIP = "172.31.42.1" - if isDockerDesktop(ctx, s.T()) { - s.hostIP = getDockerDesktopHostIP(ctx, s.T()) + if isDockerDesktop(s.T()) { + s.hostIP = getDockerDesktopHostIP(s.T()) s.setupVPN(tailscaleSecretFilePath) } } -func getDockerDesktopHostIP(ctx context.Context, t *testing.T) string { +func getDockerDesktopHostIP(t *testing.T) string { t.Helper() req := testcontainers.ContainerRequest{ @@ -151,13 +150,13 @@ func getDockerDesktopHostIP(ctx context.Context, t *testing.T) string { Cmd: []string{"getent", "hosts", "host.docker.internal"}, } - con, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ + con, err := testcontainers.GenericContainer(t.Context(), testcontainers.GenericContainerRequest{ ContainerRequest: req, Started: true, }) require.NoError(t, err) - closer, err := con.Logs(ctx) + closer, err := con.Logs(t.Context()) require.NoError(t, err) all, err := io.ReadAll(closer) @@ -170,15 +169,15 @@ func getDockerDesktopHostIP(ctx context.Context, t *testing.T) string { return matches[0] } -func isDockerDesktop(ctx context.Context, t *testing.T) bool { +func isDockerDesktop(t *testing.T) bool { t.Helper() - cli, err := testcontainers.NewDockerClientWithOpts(ctx) + cli, err := testcontainers.NewDockerClientWithOpts(t.Context()) if err != nil { t.Fatalf("failed to create docker client: %s", err) } - info, err := cli.Info(ctx) + info, err := cli.Info(t.Context()) if err != nil { t.Fatalf("failed to get docker info: %s", err) } @@ -191,7 +190,7 @@ func (s *BaseSuite) TearDownSuite() { err := try.Do(5*time.Second, func() error { if s.network != nil { - err := s.network.Remove(context.Background()) + err := s.network.Remove(s.T().Context()) if err != nil { return err } @@ -218,7 +217,7 @@ func (s *BaseSuite) createComposeProject(name string) { s.containers = map[string]testcontainers.Container{} } - ctx := context.Background() + ctx := s.T().Context() for id, containerConfig := range composeConfigData.Services { var mounts []mount.Mount @@ -273,7 +272,7 @@ func (s *BaseSuite) createContainer(ctx context.Context, containerConfig compose if containerConfig.CapAdd != nil { config.CapAdd = containerConfig.CapAdd } - if !isDockerDesktop(ctx, s.T()) { + if !isDockerDesktop(s.T()) { config.ExtraHosts = append(config.ExtraHosts, "host.docker.internal:"+s.hostIP) } config.Mounts = mounts @@ -292,7 +291,7 @@ func (s *BaseSuite) createContainer(ctx context.Context, containerConfig compose func (s *BaseSuite) composeUp(services ...string) { for name, con := range s.containers { if len(services) == 0 || slices.Contains(services, name) { - err := con.Start(context.Background()) + err := con.Start(s.T().Context()) require.NoError(s.T(), err) } } @@ -303,7 +302,7 @@ func (s *BaseSuite) composeStop(services ...string) { for name, con := range s.containers { if len(services) == 0 || slices.Contains(services, name) { timeout := 10 * time.Second - err := con.Stop(context.Background(), &timeout) + err := con.Stop(s.T().Context(), &timeout) require.NoError(s.T(), err) } } @@ -312,7 +311,7 @@ func (s *BaseSuite) composeStop(services ...string) { // composeDown stops all compose project services and removes the corresponding containers. func (s *BaseSuite) composeDown() { for _, c := range s.containers { - err := c.Terminate(context.Background()) + err := c.Terminate(s.T().Context()) require.NoError(s.T(), err) } s.containers = map[string]testcontainers.Container{} @@ -383,7 +382,7 @@ func (s *BaseSuite) displayLogK3S() { func (s *BaseSuite) displayLogCompose() { for name, ctn := range s.containers { - readCloser, err := ctn.Logs(context.Background()) + readCloser, err := ctn.Logs(s.T().Context()) require.NoError(s.T(), err) for { b := make([]byte, 1024) @@ -451,7 +450,7 @@ func (s *BaseSuite) getComposeServiceIP(name string) string { if !ok { return "" } - ip, err := container.ContainerIP(context.Background()) + ip, err := container.ContainerIP(s.T().Context()) if err != nil { return "" } @@ -501,7 +500,7 @@ func (s *BaseSuite) setupVPN(keyFile string) { func (s *BaseSuite) composeExec(service string, args ...string) string { require.Contains(s.T(), s.containers, service) - _, reader, err := s.containers[service].Exec(context.Background(), args) + _, reader, err := s.containers[service].Exec(s.T().Context(), args) require.NoError(s.T(), err) content, err := io.ReadAll(reader) diff --git a/integration/k8s_conformance_test.go b/integration/k8s_conformance_test.go index 959443be6..1684c951a 100644 --- a/integration/k8s_conformance_test.go +++ b/integration/k8s_conformance_test.go @@ -1,7 +1,6 @@ package integration import ( - "context" "fmt" "io" "io/fs" @@ -74,7 +73,7 @@ func (s *K8sConformanceSuite) SetupSuite() { s.T().Fatal(err) } - ctx := context.Background() + ctx := s.T().Context() // Ensure image is available locally. images, err := provider.ListImages(ctx) @@ -146,7 +145,7 @@ func (s *K8sConformanceSuite) SetupSuite() { } func (s *K8sConformanceSuite) TearDownSuite() { - ctx := context.Background() + ctx := s.T().Context() if s.T().Failed() || *showLog { k3sLogs, err := s.k3sContainer.Logs(ctx) @@ -173,7 +172,7 @@ func (s *K8sConformanceSuite) TearDownSuite() { func (s *K8sConformanceSuite) TestK8sGatewayAPIConformance() { // Wait for traefik to start - k3sContainerIP, err := s.k3sContainer.ContainerIP(context.Background()) + k3sContainerIP, err := s.k3sContainer.ContainerIP(s.T().Context()) require.NoError(s.T(), err) err = try.GetRequest("http://"+k3sContainerIP+":9000/api/entrypoints", 10*time.Second, try.BodyContains(`"name":"web"`)) diff --git a/integration/redis_sentinel_test.go b/integration/redis_sentinel_test.go index e229ceb60..542a63728 100644 --- a/integration/redis_sentinel_test.go +++ b/integration/redis_sentinel_test.go @@ -2,7 +2,6 @@ package integration import ( "bytes" - "context" "encoding/json" "fmt" "net" @@ -51,7 +50,7 @@ func (s *RedisSentinelSuite) SetupSuite() { net.JoinHostPort(s.getComposeServiceIP("sentinel3"), "26379"), } kv, err := valkeyrie.NewStore( - context.Background(), + s.T().Context(), redis.StoreName, s.redisEndpoints, &redis.Config{ @@ -157,7 +156,7 @@ func (s *RedisSentinelSuite) TestSentinelConfiguration() { } for k, v := range data { - err := s.kvClient.Put(context.Background(), k, []byte(v), nil) + err := s.kvClient.Put(s.T().Context(), k, []byte(v), nil) require.NoError(s.T(), err) } diff --git a/integration/redis_test.go b/integration/redis_test.go index 6685b7645..e1ed10899 100644 --- a/integration/redis_test.go +++ b/integration/redis_test.go @@ -2,7 +2,6 @@ package integration import ( "bytes" - "context" "encoding/json" "net" "net/http" @@ -43,7 +42,7 @@ func (s *RedisSuite) SetupSuite() { s.redisEndpoints = append(s.redisEndpoints, net.JoinHostPort(s.getComposeServiceIP("redis"), "6379")) kv, err := valkeyrie.NewStore( - context.Background(), + s.T().Context(), redis.StoreName, s.redisEndpoints, &redis.Config{}, @@ -112,7 +111,7 @@ func (s *RedisSuite) TestSimpleConfiguration() { } for k, v := range data { - err := s.kvClient.Put(context.Background(), k, []byte(v), nil) + err := s.kvClient.Put(s.T().Context(), k, []byte(v), nil) require.NoError(s.T(), err) } 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 diff --git a/integration/simple_test.go b/integration/simple_test.go index 7208b6599..791ee514c 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) } @@ -1606,9 +1606,10 @@ 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)) @@ -1641,6 +1642,116 @@ 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", + 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)) + + 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", diff --git a/integration/zk_test.go b/integration/zk_test.go index f971ca79c..8955c530e 100644 --- a/integration/zk_test.go +++ b/integration/zk_test.go @@ -2,7 +2,6 @@ package integration import ( "bytes" - "context" "encoding/json" "net" "net/http" @@ -43,7 +42,7 @@ func (s *ZookeeperSuite) SetupSuite() { var err error s.kvClient, err = valkeyrie.NewStore( - context.Background(), + s.T().Context(), zookeeper.StoreName, []string{s.zookeeperAddr}, &zookeeper.Config{ @@ -110,7 +109,7 @@ func (s *ZookeeperSuite) TestSimpleConfiguration() { } for k, v := range data { - err := s.kvClient.Put(context.Background(), k, []byte(v), nil) + err := s.kvClient.Put(s.T().Context(), k, []byte(v), nil) require.NoError(s.T(), err) } diff --git a/internal/testsci/genmatrix.go b/internal/testsci/genmatrix.go new file mode 100644 index 000000000..01ebdabcd --- /dev/null +++ b/internal/testsci/genmatrix.go @@ -0,0 +1,64 @@ +package main + +import ( + "encoding/json" + "fmt" + "os" + "strings" + + "github.com/rs/zerolog/log" + "golang.org/x/tools/go/packages" +) + +const groupCount = 12 + +type group struct { + Group string `json:"group"` +} + +func main() { + cfg := &packages.Config{ + Mode: packages.NeedName, + Dir: ".", + } + + pkgs, err := packages.Load(cfg, "./cmd/...", "./pkg/...") + if err != nil { + log.Fatal().Err(err).Msg("Loading packages") + } + + 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 { + log.Fatal().Err(err).Msg("Failed to marshal matrix") + } + + // Output for GitHub Actions + fmt.Printf("matrix=%s\n", string(jsonBytes)) +} diff --git a/pkg/api/handler_http_test.go b/pkg/api/handler_http_test.go index 715a58676..e34b1bfe2 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" @@ -1004,8 +1003,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 e08897880..387c39050 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" @@ -880,7 +879,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 dd3067314..bd8625dfe 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" @@ -570,7 +569,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 57938b616..ad3f44618 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 c4726ac72..644e57384 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 5531bb934..67bcb8602 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 a8df9bcdf..056a62a49 100644 --- a/pkg/healthcheck/healthcheck_test.go +++ b/pkg/healthcheck/healthcheck_test.go @@ -66,7 +66,7 @@ func TestNewServiceHealthChecker_durations(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { - healthChecker := NewServiceHealthChecker(context.Background(), nil, test.config, nil, nil, http.DefaultTransport, nil, "") + healthChecker := NewServiceHealthChecker(t.Context(), nil, test.config, nil, nil, http.DefaultTransport, nil, "") assert.Equal(t, test.expInterval, healthChecker.interval) assert.Equal(t, test.expTimeout, healthChecker.timeout) }) @@ -251,7 +251,7 @@ func TestServiceHealthChecker_newRequest(t *testing.T) { shc := ServiceHealthChecker{config: &test.config} u := testhelpers.MustParseURL(test.targetURL) - req, err := shc.newRequest(context.Background(), u) + req, err := shc.newRequest(t.Context(), u) if test.expError { require.Error(t, err) @@ -276,7 +276,7 @@ func TestServiceHealthChecker_checkHealthHTTP_NotFollowingRedirects(t *testing.T })) defer redirectTestServer.Close() - ctx, cancel := context.WithTimeout(context.Background(), time.Duration(dynamic.DefaultHealthCheckTimeout)) + ctx, cancel := context.WithTimeout(t.Context(), time.Duration(dynamic.DefaultHealthCheckTimeout)) defer cancel() server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { @@ -411,7 +411,7 @@ func TestServiceHealthChecker_Launch(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()) t.Cleanup(cancel) targetURL, timeout := test.server.Start(t, cancel) @@ -461,7 +461,7 @@ func TestServiceHealthChecker_Launch(t *testing.T) { func TestDifferentIntervals(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()) t.Cleanup(cancel) healthyServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { diff --git a/pkg/logs/otel_test.go b/pkg/logs/otel_test.go index 60c68cc09..c55137cf1 100644 --- a/pkg/logs/otel_test.go +++ b/pkg/logs/otel_test.go @@ -2,7 +2,6 @@ package logs import ( "compress/gzip" - "context" "encoding/json" "io" "net/http" @@ -175,7 +174,7 @@ func TestLog(t *testing.T) { logger, err := SetupOTelLogger(logger, config) require.NoError(t, err) - ctx := trace.ContextWithSpanContext(context.Background(), trace.NewSpanContext(trace.SpanContextConfig{ + ctx := trace.ContextWithSpanContext(t.Context(), trace.NewSpanContext(trace.SpanContextConfig{ TraceID: trace.TraceID{0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8}, SpanID: trace.SpanID{0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8}, })) diff --git a/pkg/metrics/datadog_test.go b/pkg/metrics/datadog_test.go index 0fc5bd758..b647505af 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" @@ -20,7 +19,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}) if !datadogRegistry.IsEpEnabled() || !datadogRegistry.IsRouterEnabled() || !datadogRegistry.IsSvcEnabled() { t.Errorf("DatadogRegistry should return true for IsEnabled(), IsRouterEnabled() and 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 e75141ff4..63c7e62b3 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" @@ -26,7 +25,7 @@ func TestInfluxDB2(t *testing.T) { _, _ = fmt.Fprintln(w, "ok") })) - influxDB2Registry := RegisterInfluxDB2(context.Background(), + influxDB2Registry := RegisterInfluxDB2(t.Context(), &types.InfluxDB2{ Address: ts.URL, Token: "test-token", diff --git a/pkg/metrics/otel_test.go b/pkg/metrics/otel_test.go index d9af5a090..b110a1f3f 100644 --- a/pkg/metrics/otel_test.go +++ b/pkg/metrics/otel_test.go @@ -2,7 +2,6 @@ package metrics import ( "compress/gzip" - "context" "encoding/json" "fmt" "io" @@ -338,7 +337,7 @@ func TestOpenTelemetry(t *testing.T) { wantServiceName = test.serviceName } - registry := RegisterOpenTelemetry(context.Background(), &cfg) + registry := RegisterOpenTelemetry(t.Context(), &cfg) require.NotNil(t, registry) if !registry.IsEpEnabled() || !registry.IsRouterEnabled() || !registry.IsSvcEnabled() { diff --git a/pkg/metrics/prometheus_test.go b/pkg/metrics/prometheus_test.go index 91e5a56fe..a93a14d7d 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, @@ -405,7 +404,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{ @@ -496,7 +495,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{ @@ -535,7 +534,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 174081db4..b912a7a6a 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 6d6a8188f..4daf1a5a5 100644 --- a/pkg/middlewares/accesslog/logger_test.go +++ b/pkg/middlewares/accesslog/logger_test.go @@ -97,7 +97,7 @@ func TestOTelAccessLog(t *testing.T) { Path: testPath, }, } - ctx := trace.ContextWithSpanContext(context.Background(), trace.NewSpanContext(trace.SpanContextConfig{ + ctx := trace.ContextWithSpanContext(t.Context(), trace.NewSpanContext(trace.SpanContextConfig{ TraceID: trace.TraceID{0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8}, SpanID: trace.SpanID{0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8}, })) @@ -1055,7 +1055,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 2bcc2ad7f..37caf08e2 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 6a59f0111..b81a22c03 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" @@ -25,13 +24,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) @@ -54,7 +53,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) @@ -85,7 +84,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) @@ -116,7 +115,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) @@ -147,7 +146,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) @@ -177,7 +176,7 @@ func TestBasicAuthConcurrentHashOnce(t *testing.T) { Users: []string{"test:$2a$04$.8sTYfcxbSplCtoxt5TdJOgpBYkarKtZYsYfYxQ1edbYRuO1DNi0e"}, } - authMiddleware, err := NewBasic(context.Background(), next, auth, "authName") + authMiddleware, err := NewBasic(t.Context(), next, auth, "authName") require.NoError(t, err) hashCount := 0 @@ -277,7 +276,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 ae472e3d0..b0f485b38 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 82ccb7346..aa4503381 100644 --- a/pkg/middlewares/auth/forward_test.go +++ b/pkg/middlewares/auth/forward_test.go @@ -37,7 +37,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) @@ -90,7 +90,7 @@ func TestForwardAuthSuccess(t *testing.T) { AuthResponseHeadersRegex: "^Foo-", AddAuthCookiesToResponse: []string{"authCookie"}, } - middleware, err := NewForward(context.Background(), next, auth, "authTest") + middleware, err := NewForward(t.Context(), next, auth, "authTest") require.NoError(t, err) ts := httptest.NewServer(middleware) @@ -135,7 +135,7 @@ func TestForwardAuthForwardBody(t *testing.T) { maxBodySize := int64(len(data)) auth := dynamic.ForwardAuth{Address: server.URL, ForwardBody: true, MaxBodySize: &maxBodySize} - middleware, err := NewForward(context.Background(), next, auth, "authTest") + middleware, err := NewForward(t.Context(), next, auth, "authTest") require.NoError(t, err) ts := httptest.NewServer(middleware) @@ -170,7 +170,7 @@ func TestForwardAuthForwardBodyEmptyBody(t *testing.T) { auth := dynamic.ForwardAuth{Address: server.URL, ForwardBody: true} - middleware, err := NewForward(context.Background(), next, auth, "authTest") + middleware, err := NewForward(t.Context(), next, auth, "authTest") require.NoError(t, err) ts := httptest.NewServer(middleware) @@ -208,7 +208,7 @@ func TestForwardAuthForwardBodySizeLimit(t *testing.T) { maxBodySize := int64(len(data)) - 1 auth := dynamic.ForwardAuth{Address: server.URL, ForwardBody: true, MaxBodySize: &maxBodySize} - middleware, err := NewForward(context.Background(), next, auth, "authTest") + middleware, err := NewForward(t.Context(), next, auth, "authTest") require.NoError(t, err) ts := httptest.NewServer(middleware) @@ -245,7 +245,7 @@ func TestForwardAuthNotForwardBody(t *testing.T) { auth := dynamic.ForwardAuth{Address: server.URL} - middleware, err := NewForward(context.Background(), next, auth, "authTest") + middleware, err := NewForward(t.Context(), next, auth, "authTest") require.NoError(t, err) ts := httptest.NewServer(middleware) @@ -273,7 +273,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) @@ -324,7 +324,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) @@ -370,7 +370,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) @@ -682,7 +682,7 @@ func TestForwardAuthTracing(t *testing.T) { Address: server.URL, AuthRequestHeaders: []string{"X-Foo"}, } - next, err := NewForward(context.Background(), next, auth, "authTest") + next, err := NewForward(t.Context(), next, auth, "authTest") require.NoError(t, err) req := httptest.NewRequest(http.MethodGet, "http://www.test.com/search?q=Opentelemetry", nil) @@ -725,7 +725,7 @@ func TestForwardAuthPreserveLocationHeader(t *testing.T) { Address: server.URL, PreserveLocationHeader: true, } - middleware, err := NewForward(context.Background(), next, auth, "authTest") + middleware, err := NewForward(t.Context(), next, auth, "authTest") require.NoError(t, err) ts := httptest.NewServer(middleware) @@ -779,7 +779,7 @@ func TestForwardAuthPreserveRequestMethod(t *testing.T) { PreserveRequestMethod: test.preserveRequestMethod, } - middleware, err := NewForward(context.Background(), next, auth, "authTest") + middleware, err := NewForward(t.Context(), next, auth, "authTest") require.NoError(t, err) ts := httptest.NewServer(middleware) diff --git a/pkg/middlewares/buffering/buffering_test.go b/pkg/middlewares/buffering/buffering_test.go index de6f50170..b070a180f 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/acceptencoding_test.go b/pkg/middlewares/compress/acceptencoding_test.go index d3059af6e..6cacbb951 100644 --- a/pkg/middlewares/compress/acceptencoding_test.go +++ b/pkg/middlewares/compress/acceptencoding_test.go @@ -1,7 +1,6 @@ package compress import ( - "context" "testing" "github.com/stretchr/testify/assert" @@ -153,7 +152,7 @@ func Test_getCompressionEncoding(t *testing.T) { DefaultEncoding: test.defaultEncoding, } - h, err := New(context.Background(), nil, conf, "test") + h, err := New(t.Context(), nil, conf, "test") require.NoError(t, err) c, ok := h.(*compress) diff --git a/pkg/middlewares/compress/compress_test.go b/pkg/middlewares/compress/compress_test.go index 3c53c4c30..9754de238 100644 --- a/pkg/middlewares/compress/compress_test.go +++ b/pkg/middlewares/compress/compress_test.go @@ -2,7 +2,6 @@ package compress import ( "compress/gzip" - "context" "io" "net/http" "net/http/httptest" @@ -116,7 +115,7 @@ func TestNegotiation(t *testing.T) { MinResponseBodyBytes: 1, Encodings: defaultSupportedEncodings, } - handler, err := New(context.Background(), next, cfg, "testing") + handler, err := New(t.Context(), next, cfg, "testing") require.NoError(t, err) rw := httptest.NewRecorder() @@ -137,7 +136,7 @@ func TestShouldCompressWhenNoContentEncodingHeader(t *testing.T) { _, err := rw.Write(baseBody) assert.NoError(t, err) }) - handler, err := New(context.Background(), next, dynamic.Compress{Encodings: defaultSupportedEncodings}, "testing") + handler, err := New(t.Context(), next, dynamic.Compress{Encodings: defaultSupportedEncodings}, "testing") require.NoError(t, err) rw := httptest.NewRecorder() @@ -167,7 +166,7 @@ func TestShouldNotCompressWhenContentEncodingHeader(t *testing.T) { http.Error(rw, err.Error(), http.StatusInternalServerError) } }) - handler, err := New(context.Background(), next, dynamic.Compress{Encodings: defaultSupportedEncodings}, "testing") + handler, err := New(t.Context(), next, dynamic.Compress{Encodings: defaultSupportedEncodings}, "testing") require.NoError(t, err) rw := httptest.NewRecorder() @@ -189,7 +188,7 @@ func TestShouldNotCompressWhenNoAcceptEncodingHeader(t *testing.T) { http.Error(rw, err.Error(), http.StatusInternalServerError) } }) - handler, err := New(context.Background(), next, dynamic.Compress{Encodings: defaultSupportedEncodings}, "testing") + handler, err := New(t.Context(), next, dynamic.Compress{Encodings: defaultSupportedEncodings}, "testing") require.NoError(t, err) rw := httptest.NewRecorder() @@ -211,7 +210,7 @@ func TestEmptyAcceptEncoding(t *testing.T) { http.Error(rw, err.Error(), http.StatusInternalServerError) } }) - handler, err := New(context.Background(), next, dynamic.Compress{Encodings: defaultSupportedEncodings}, "testing") + handler, err := New(t.Context(), next, dynamic.Compress{Encodings: defaultSupportedEncodings}, "testing") require.NoError(t, err) rw := httptest.NewRecorder() @@ -238,7 +237,7 @@ func TestShouldNotCompressWhenIdentityAcceptEncodingHeader(t *testing.T) { http.Error(rw, err.Error(), http.StatusInternalServerError) } }) - handler, err := New(context.Background(), next, dynamic.Compress{Encodings: defaultSupportedEncodings}, "testing") + handler, err := New(t.Context(), next, dynamic.Compress{Encodings: defaultSupportedEncodings}, "testing") require.NoError(t, err) rw := httptest.NewRecorder() @@ -265,7 +264,7 @@ func TestShouldNotCompressWhenEmptyAcceptEncodingHeader(t *testing.T) { http.Error(rw, err.Error(), http.StatusInternalServerError) } }) - handler, err := New(context.Background(), next, dynamic.Compress{Encodings: defaultSupportedEncodings}, "testing") + handler, err := New(t.Context(), next, dynamic.Compress{Encodings: defaultSupportedEncodings}, "testing") require.NoError(t, err) rw := httptest.NewRecorder() @@ -287,7 +286,7 @@ func TestShouldNotCompressHeadRequest(t *testing.T) { http.Error(rw, err.Error(), http.StatusInternalServerError) } }) - handler, err := New(context.Background(), next, dynamic.Compress{Encodings: defaultSupportedEncodings}, "testing") + handler, err := New(t.Context(), next, dynamic.Compress{Encodings: defaultSupportedEncodings}, "testing") require.NoError(t, err) rw := httptest.NewRecorder() @@ -377,7 +376,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() @@ -423,7 +422,7 @@ func TestShouldCompressWhenSpecificContentType(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() @@ -473,7 +472,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{Encodings: defaultSupportedEncodings}, "testing") + compress, err := New(t.Context(), test.handler, dynamic.Compress{Encodings: defaultSupportedEncodings}, "testing") require.NoError(t, err) ts := httptest.NewServer(compress) @@ -508,7 +507,7 @@ func TestShouldWriteHeaderWhenFlush(t *testing.T) { http.Error(rw, err.Error(), http.StatusInternalServerError) } }) - handler, err := New(context.Background(), next, dynamic.Compress{Encodings: defaultSupportedEncodings}, "testing") + handler, err := New(t.Context(), next, dynamic.Compress{Encodings: defaultSupportedEncodings}, "testing") require.NoError(t, err) ts := httptest.NewServer(handler) @@ -559,7 +558,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{Encodings: defaultSupportedEncodings}, "testing") + compress, err := New(t.Context(), test.handler, dynamic.Compress{Encodings: defaultSupportedEncodings}, "testing") require.NoError(t, err) ts := httptest.NewServer(compress) @@ -619,7 +618,7 @@ func TestMinResponseBodyBytes(t *testing.T) { MinResponseBodyBytes: test.minResponseBodyBytes, Encodings: defaultSupportedEncodings, } - handler, err := New(context.Background(), next, cfg, "testing") + handler, err := New(t.Context(), next, cfg, "testing") require.NoError(t, err) rw := httptest.NewRecorder() @@ -679,7 +678,7 @@ func Test1xxResponses(t *testing.T) { MinResponseBodyBytes: 1024, Encodings: defaultSupportedEncodings, } - compress, err := New(context.Background(), next, cfg, "testing") + compress, err := New(t.Context(), next, cfg, "testing") require.NoError(t, err) server := httptest.NewServer(compress) @@ -723,7 +722,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, test.encoding) res, err := frontendClient.Do(req) @@ -779,7 +778,7 @@ func runCompressionBenchmark(b *testing.B, algorithm string) { _, 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", algorithm) diff --git a/pkg/middlewares/contenttype/content_type_test.go b/pkg/middlewares/contenttype/content_type_test.go index cfe8ba502..89e3a4866 100644 --- a/pkg/middlewares/contenttype/content_type_test.go +++ b/pkg/middlewares/contenttype/content_type_test.go @@ -1,7 +1,6 @@ package contenttype import ( - "context" "net/http" "net/http/httptest" "testing" @@ -60,7 +59,7 @@ func TestAutoDetection(t *testing.T) { if test.autoDetect { var err error - next, err = New(context.Background(), next, dynamic.ContentType{}, "foo-content-type") + next, err = New(t.Context(), next, dynamic.ContentType{}, "foo-content-type") require.NoError(t, err) } diff --git a/pkg/middlewares/customerrors/custom_errors_test.go b/pkg/middlewares/customerrors/custom_errors_test.go index 37c13e53d..6cf745da8 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/gatewayapi/headermodifier/request_header_modifier_test.go b/pkg/middlewares/gatewayapi/headermodifier/request_header_modifier_test.go index a9c19980f..36ebbd9db 100644 --- a/pkg/middlewares/gatewayapi/headermodifier/request_header_modifier_test.go +++ b/pkg/middlewares/gatewayapi/headermodifier/request_header_modifier_test.go @@ -1,7 +1,6 @@ package headermodifier import ( - "context" "net/http" "net/http/httptest" "testing" @@ -103,7 +102,7 @@ func TestRequestHeaderModifier(t *testing.T) { gotHeaders = r.Header }) - handler := NewRequestHeaderModifier(context.Background(), next, test.config, "foo-request-header-modifier") + handler := NewRequestHeaderModifier(t.Context(), next, test.config, "foo-request-header-modifier") req := testhelpers.MustNewRequest(http.MethodGet, "http://localhost", nil) for h, v := range test.requestHeaders { diff --git a/pkg/middlewares/gatewayapi/headermodifier/response_header_modifier_test.go b/pkg/middlewares/gatewayapi/headermodifier/response_header_modifier_test.go index ceea62ca6..47a0f3c82 100644 --- a/pkg/middlewares/gatewayapi/headermodifier/response_header_modifier_test.go +++ b/pkg/middlewares/gatewayapi/headermodifier/response_header_modifier_test.go @@ -1,7 +1,6 @@ package headermodifier import ( - "context" "net/http" "net/http/httptest" "testing" @@ -104,7 +103,7 @@ func TestResponseHeaderModifier(t *testing.T) { rw.WriteHeader(http.StatusOK) }) - handler := NewResponseHeaderModifier(context.Background(), next, test.config, "foo-response-header-modifier") + handler := NewResponseHeaderModifier(t.Context(), next, test.config, "foo-response-header-modifier") req := testhelpers.MustNewRequest(http.MethodGet, "http://localhost", nil) resp := httptest.NewRecorder() diff --git a/pkg/middlewares/gatewayapi/redirect/request_redirect_test.go b/pkg/middlewares/gatewayapi/redirect/request_redirect_test.go index 68eb19bd3..530027adb 100644 --- a/pkg/middlewares/gatewayapi/redirect/request_redirect_test.go +++ b/pkg/middlewares/gatewayapi/redirect/request_redirect_test.go @@ -1,7 +1,6 @@ package redirect import ( - "context" "net/http" "net/http/httptest" "testing" @@ -185,7 +184,7 @@ func TestRequestRedirectHandler(t *testing.T) { next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}) - handler, err := NewRequestRedirect(context.Background(), next, test.config, "traefikTest") + handler, err := NewRequestRedirect(t.Context(), next, test.config, "traefikTest") if test.wantErr { require.Error(t, err) require.Nil(t, handler) diff --git a/pkg/middlewares/gatewayapi/urlrewrite/url_rewrite_test.go b/pkg/middlewares/gatewayapi/urlrewrite/url_rewrite_test.go index 6b2b9edab..07b085c65 100644 --- a/pkg/middlewares/gatewayapi/urlrewrite/url_rewrite_test.go +++ b/pkg/middlewares/gatewayapi/urlrewrite/url_rewrite_test.go @@ -1,7 +1,6 @@ package urlrewrite import ( - "context" "net/http" "net/http/httptest" "testing" @@ -113,7 +112,7 @@ func TestURLRewriteHandler(t *testing.T) { next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}) - handler := NewURLRewrite(context.Background(), next, test.config, "traefikTest") + handler := NewURLRewrite(t.Context(), next, test.config, "traefikTest") recorder := httptest.NewRecorder() req := httptest.NewRequest(http.MethodGet, test.url, nil) diff --git a/pkg/middlewares/headers/headers_test.go b/pkg/middlewares/headers/headers_test.go index 194975e36..6ce7383cf 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) @@ -135,7 +134,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) @@ -179,7 +178,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 3dddcc353..fac72dd1c 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" @@ -45,7 +44,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) @@ -105,7 +104,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 9cf88ef32..ce2158fb9 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/observability/entrypoint_test.go b/pkg/middlewares/observability/entrypoint_test.go index d39d3b842..bbcd57f98 100644 --- a/pkg/middlewares/observability/entrypoint_test.go +++ b/pkg/middlewares/observability/entrypoint_test.go @@ -1,7 +1,6 @@ package observability import ( - "context" "net/http" "net/http/httptest" "testing" @@ -68,7 +67,7 @@ func TestEntryPointMiddleware_tracing(t *testing.T) { tracer := &mockTracer{} - handler := newEntryPoint(context.Background(), tracing.NewTracer(tracer, []string{"X-Foo"}, []string{"X-Bar"}, []string{"q"}), test.entryPoint, next) + handler := newEntryPoint(t.Context(), tracing.NewTracer(tracer, []string{"X-Foo"}, []string{"X-Bar"}, []string{"q"}), test.entryPoint, next) handler.ServeHTTP(rw, req) for _, span := range tracer.spans { diff --git a/pkg/middlewares/observability/router_test.go b/pkg/middlewares/observability/router_test.go index 23bb46ab9..0ee0835cb 100644 --- a/pkg/middlewares/observability/router_test.go +++ b/pkg/middlewares/observability/router_test.go @@ -1,7 +1,6 @@ package observability import ( - "context" "net/http" "net/http/httptest" "testing" @@ -74,7 +73,7 @@ func TestNewRouter(t *testing.T) { rw.WriteHeader(http.StatusNotFound) }) - handler := newRouter(context.Background(), test.router, test.routerRule, test.service, next) + handler := newRouter(t.Context(), test.router, test.routerRule, test.service, next) handler.ServeHTTP(rw, req) for i, span := range tracer.spans { diff --git a/pkg/middlewares/observability/semconv_test.go b/pkg/middlewares/observability/semconv_test.go index 08846c4d7..40d3faf62 100644 --- a/pkg/middlewares/observability/semconv_test.go +++ b/pkg/middlewares/observability/semconv_test.go @@ -1,7 +1,6 @@ package observability import ( - "context" "net/http" "net/http/httptest" "testing" @@ -65,7 +64,7 @@ func TestSemConvServerMetrics(t *testing.T) { // force the meter provider with manual reader to collect metrics for the test. metrics.SetMeterProvider(meterProvider) - semConvMetricRegistry, err := metrics.NewSemConvMetricRegistry(context.Background(), &cfg) + semConvMetricRegistry, err := metrics.NewSemConvMetricRegistry(t.Context(), &cfg) require.NoError(t, err) require.NotNil(t, semConvMetricRegistry) @@ -79,7 +78,7 @@ func TestSemConvServerMetrics(t *testing.T) { rw.WriteHeader(test.statusCode) }) - handler := newServerMetricsSemConv(context.Background(), semConvMetricRegistry, next) + handler := newServerMetricsSemConv(t.Context(), semConvMetricRegistry, next) handler, err = capture.Wrap(handler) require.NoError(t, err) @@ -87,7 +86,7 @@ func TestSemConvServerMetrics(t *testing.T) { handler.ServeHTTP(rw, req) got := metricdata.ResourceMetrics{} - err = rdr.Collect(context.Background(), &got) + err = rdr.Collect(t.Context(), &got) require.NoError(t, err) require.Len(t, got.ScopeMetrics, 1) diff --git a/pkg/middlewares/observability/service_test.go b/pkg/middlewares/observability/service_test.go index db411e718..623ed214c 100644 --- a/pkg/middlewares/observability/service_test.go +++ b/pkg/middlewares/observability/service_test.go @@ -1,7 +1,6 @@ package observability import ( - "context" "net/http" "net/http/httptest" "testing" @@ -68,7 +67,7 @@ func TestNewService(t *testing.T) { rw.WriteHeader(http.StatusNotFound) }) - handler := NewService(context.Background(), test.service, next) + handler := NewService(t.Context(), test.service, next) handler.ServeHTTP(rw, req) for i, span := range tracer.spans { diff --git a/pkg/middlewares/passtlsclientcert/pass_tls_client_cert_test.go b/pkg/middlewares/passtlsclientcert/pass_tls_client_cert_test.go index 20b4f087c..7165e9dd3 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/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") diff --git a/pkg/middlewares/ratelimiter/rate_limiter_test.go b/pkg/middlewares/ratelimiter/rate_limiter_test.go index a741be4e4..a724c6fe6 100644 --- a/pkg/middlewares/ratelimiter/rate_limiter_test.go +++ b/pkg/middlewares/ratelimiter/rate_limiter_test.go @@ -110,7 +110,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 { @@ -274,7 +274,7 @@ func TestInMemoryRateLimit(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) @@ -477,7 +477,7 @@ func TestRedisRateLimit(t *testing.T) { test.config.Redis = &dynamic.Redis{ Endpoints: []string{"localhost:6379"}, } - h, err := New(context.Background(), next, test.config, "rate-limiter") + h, err := New(t.Context(), next, test.config, "rate-limiter") require.NoError(t, err) l := h.(*rateLimiter) 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 4239c1ae0..78d4fabd0 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 258bf6c04..aa90bddbc 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 5998579d1..9f4923aea 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 065b53d80..a1be75fcd 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 6dca141c6..a4575b817 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 83310ff8b..50cee8e28 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" @@ -148,7 +147,7 @@ func TestStripPrefix(t *testing.T) { pointer := func(v bool) *bool { return &v } test.config.ForceSlash = pointer(false) - 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 9f2c02548..7b7e3092e 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 28fca80df..d2f9dc141 100644 --- a/pkg/middlewares/tcp/inflightconn/inflight_conn_test.go +++ b/pkg/middlewares/tcp/inflightconn/inflight_conn_test.go @@ -1,7 +1,6 @@ package inflightconn 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 5c918e8cf..9e58e6a3a 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 f0bf631fb..2fb439714 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/muxer/http/matcher.go b/pkg/muxer/http/matcher.go index 200f3fc5d..74f5d97f0 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), @@ -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_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..36f426f48 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, @@ -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/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..d3ab6a45d 100644 --- a/pkg/muxer/http/mux.go +++ b/pkg/muxer/http/mux.go @@ -1,55 +1,53 @@ 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" - "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. // 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) @@ -73,36 +71,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{ @@ -116,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 @@ -173,7 +224,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 +255,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..36cc99221 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) @@ -551,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/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/plugins/middlewarewasm_test.go b/pkg/plugins/middlewarewasm_test.go index 38fee4bbe..faa33942f 100644 --- a/pkg/plugins/middlewarewasm_test.go +++ b/pkg/plugins/middlewarewasm_test.go @@ -1,7 +1,6 @@ package plugins import ( - "context" "net/http" "net/http/httptest" "os" @@ -21,7 +20,7 @@ func TestSettingsWithoutSocket(t *testing.T) { zerolog.SetGlobalLevel(zerolog.DebugLevel) - ctx := log.Logger.WithContext(context.Background()) + ctx := log.Logger.WithContext(t.Context()) t.Setenv("PLUGIN_TEST", "MY-TEST") t.Setenv("PLUGIN_TEST_B", "MY-TEST_B") diff --git a/pkg/provider/acme/local_store_test.go b/pkg/provider/acme/local_store_test.go index a3c05fc58..15d592b9a 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 020411bd2..477164e3a 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 4683a2480..895dc6539 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 227dd4838..763179d20 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" "time" @@ -323,7 +322,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) }) @@ -3602,7 +3601,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", @@ -4120,7 +4119,7 @@ func TestFilterHealthStatuses(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) }) 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) diff --git a/pkg/provider/docker/config_test.go b/pkg/provider/docker/config_test.go index c203fc18a..8f389edbf 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" "time" @@ -421,7 +420,7 @@ func TestDynConfBuilder_DefaultRule(t *testing.T) { require.NoError(t, err) } - configuration := builder.build(context.Background(), test.containers) + configuration := builder.build(t.Context(), test.containers) assert.Equal(t, test.expected, configuration) }) @@ -3929,7 +3928,7 @@ func TestDynConfBuilder_build(t *testing.T) { require.NoError(t, err) } - configuration := builder.build(context.Background(), test.containers) + configuration := builder.build(t.Context(), test.containers) assert.Equal(t, test.expected, configuration) }) @@ -4101,7 +4100,7 @@ func TestDynConfBuilder_getIPPort_docker(t *testing.T) { UseBindPortIP: true, }, nil, false) - actualIP, actualPort, actualError := builder.getIPPort(context.Background(), dData, test.serverPort) + actualIP, actualPort, actualError := builder.getIPPort(t.Context(), dData, test.serverPort) if test.expected.error { require.Error(t, actualError) } else { @@ -4219,7 +4218,7 @@ func TestDynConfBuilder_getIPAddress_docker(t *testing.T) { builder := NewDynConfBuilder(conf, nil, false) - actual := builder.getIPAddress(context.Background(), dData) + actual := builder.getIPAddress(t.Context(), dData) assert.Equal(t, test.expected, actual) }) } @@ -4278,11 +4277,11 @@ func TestDynConfBuilder_getIPAddress_swarm(t *testing.T) { var p SwarmProvider require.NoError(t, p.Init()) - dData, err := p.parseService(context.Background(), test.service, test.networks) + dData, err := p.parseService(t.Context(), test.service, test.networks) require.NoError(t, err) builder := NewDynConfBuilder(p.Shared, nil, false) - actual := builder.getIPAddress(context.Background(), dData) + actual := builder.getIPAddress(t.Context(), dData) assert.Equal(t, test.expected, actual) }) } diff --git a/pkg/provider/docker/pswarm_test.go b/pkg/provider/docker/pswarm_test.go index 7d6421037..0f9c72d5f 100644 --- a/pkg/provider/docker/pswarm_test.go +++ b/pkg/provider/docker/pswarm_test.go @@ -1,7 +1,6 @@ package docker import ( - "context" "strconv" "testing" "time" @@ -68,11 +67,11 @@ func TestListTasks(t *testing.T) { var p SwarmProvider require.NoError(t, p.Init()) - 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) @@ -238,7 +237,7 @@ func TestSwarmProvider_listServices(t *testing.T) { var p SwarmProvider require.NoError(t, p.Init()) - 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)) @@ -357,11 +356,11 @@ func TestSwarmProvider_parseService_task(t *testing.T) { var p SwarmProvider require.NoError(t, p.Init()) - 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/docker/shared_test.go b/pkg/provider/docker/shared_test.go index 90022c067..d6c51afd8 100644 --- a/pkg/provider/docker/shared_test.go +++ b/pkg/provider/docker/shared_test.go @@ -1,7 +1,6 @@ package docker import ( - "context" "strconv" "testing" @@ -101,7 +100,7 @@ func Test_getPort_swarm(t *testing.T) { var p SwarmProvider require.NoError(t, p.Init()) - 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/ecs/config_test.go b/pkg/provider/ecs/config_test.go index 8a0b41bab..756168120 100644 --- a/pkg/provider/ecs/config_test.go +++ b/pkg/provider/ecs/config_test.go @@ -1,7 +1,6 @@ package ecs import ( - "context" "testing" "time" @@ -390,7 +389,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) }) @@ -3491,7 +3490,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 3cb4a0490..3aebe1908 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" @@ -67,7 +66,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()) @@ -92,7 +91,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) }() @@ -116,7 +115,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) }() @@ -146,7 +145,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 c9f932536..eae98d979 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" @@ -235,7 +234,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) @@ -269,7 +268,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 239d3cdae..b2381998c 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" @@ -1677,7 +1676,7 @@ func TestLoadIngressRouteTCPs(t *testing.T) { AllowEmptyServices: test.allowEmptyServices, } - conf := p.loadConfigurationFromCRD(context.Background(), client) + conf := p.loadConfigurationFromCRD(t.Context(), client) assert.Equal(t, test.expected, conf) }) } @@ -5313,7 +5312,7 @@ func TestLoadIngressRoutes(t *testing.T) { AllowEmptyServices: test.allowEmptyServices, } - conf := p.loadConfigurationFromCRD(context.Background(), client) + conf := p.loadConfigurationFromCRD(t.Context(), client) assert.Equal(t, test.expected, conf) }) } @@ -5389,7 +5388,7 @@ func TestLoadIngressRoutes_multipleEndpointAddresses(t *testing.T) { } p := Provider{} - conf := p.loadConfigurationFromCRD(context.Background(), client) + conf := p.loadConfigurationFromCRD(t.Context(), client) service, ok := conf.HTTP.Services["default-test-route-6b204d94623b3df4370c"] require.True(t, ok) @@ -5904,7 +5903,7 @@ func TestLoadIngressRouteUDPs(t *testing.T) { AllowEmptyServices: test.allowEmptyServices, } - conf := p.loadConfigurationFromCRD(context.Background(), client) + conf := p.loadConfigurationFromCRD(t.Context(), client) assert.Equal(t, test.expected, conf) }) } @@ -7402,7 +7401,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) }) } @@ -7672,7 +7671,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) }) } @@ -7854,7 +7853,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) }) } @@ -8122,7 +8121,7 @@ func TestNodePortLB(t *testing.T) { DisableClusterScopeResources: test.disableClusterScope, } - conf := p.loadConfigurationFromCRD(context.Background(), client) + conf := p.loadConfigurationFromCRD(t.Context(), client) assert.Equal(t, test.expected, conf) }) } @@ -8634,7 +8633,7 @@ func TestGlobalNativeLB(t *testing.T) { p := Provider{NativeLBByDefault: test.NativeLBByDefault} - conf := p.loadConfigurationFromCRD(context.Background(), client) + conf := p.loadConfigurationFromCRD(t.Context(), client) assert.Equal(t, test.expected, conf) }) } 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. diff --git a/pkg/provider/kubernetes/gateway/kubernetes_test.go b/pkg/provider/kubernetes/gateway/kubernetes_test.go index 70000cfd2..89101ca45 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" "errors" "net/http" "os" @@ -75,14 +74,14 @@ func TestGatewayClassLabelSelector(t *testing.T) { client: client, } - _ = p.loadConfigurationFromGateways(context.Background()) + _ = p.loadConfigurationFromGateways(t.Context()) - gw, err := gwClient.GatewayV1().Gateways("default").Get(context.Background(), "traefik-external", metav1.GetOptions{}) + gw, err := gwClient.GatewayV1().Gateways("default").Get(t.Context(), "traefik-external", metav1.GetOptions{}) require.NoError(t, err) assert.Empty(t, gw.Status.Addresses) - gw, err = gwClient.GatewayV1().Gateways("default").Get(context.Background(), "traefik-internal", metav1.GetOptions{}) + gw, err = gwClient.GatewayV1().Gateways("default").Get(t.Context(), "traefik-internal", metav1.GetOptions{}) require.NoError(t, err) require.Len(t, gw.Status.Addresses, 1) require.NotNil(t, gw.Status.Addresses[0].Type) @@ -2556,7 +2555,7 @@ func TestLoadHTTPRoutes(t *testing.T) { client: client, } - conf := p.loadConfigurationFromGateways(context.Background()) + conf := p.loadConfigurationFromGateways(t.Context()) assert.Equal(t, test.expected, conf) }) } @@ -2983,7 +2982,7 @@ func TestLoadHTTPRoutes_backendExtensionRef(t *testing.T) { p.RegisterBackendFuncs(group, kind, backendFunc) } } - conf := p.loadConfigurationFromGateways(context.Background()) + conf := p.loadConfigurationFromGateways(t.Context()) assert.Equal(t, test.expected, conf) }) } @@ -3269,7 +3268,7 @@ func TestLoadHTTPRoutes_filterExtensionRef(t *testing.T) { p.RegisterFilterFuncs(group, kind, filterFunc) } } - conf := p.loadConfigurationFromGateways(context.Background()) + conf := p.loadConfigurationFromGateways(t.Context()) assert.Equal(t, test.expected, conf) }) } @@ -3561,7 +3560,7 @@ func TestLoadGRPCRoutes_filterExtensionRef(t *testing.T) { p.RegisterFilterFuncs(group, kind, filterFunc) } } - conf := p.loadConfigurationFromGateways(context.Background()) + conf := p.loadConfigurationFromGateways(t.Context()) assert.Equal(t, test.expected, conf) }) } @@ -4463,7 +4462,7 @@ func TestLoadTCPRoutes(t *testing.T) { client: client, } - conf := p.loadConfigurationFromGateways(context.Background()) + conf := p.loadConfigurationFromGateways(t.Context()) assert.Equal(t, test.expected, conf) }) } @@ -5742,7 +5741,7 @@ func TestLoadTLSRoutes(t *testing.T) { client: client, } - conf := p.loadConfigurationFromGateways(context.Background()) + conf := p.loadConfigurationFromGateways(t.Context()) assert.Equal(t, test.expected, conf) }) } @@ -6798,7 +6797,7 @@ func TestLoadMixedRoutes(t *testing.T) { client: client, } - conf := p.loadConfigurationFromGateways(context.Background()) + conf := p.loadConfigurationFromGateways(t.Context()) assert.Equal(t, test.expected, conf) }) } @@ -7134,7 +7133,7 @@ func TestLoadRoutesWithReferenceGrants(t *testing.T) { client: client, } - conf := p.loadConfigurationFromGateways(context.Background()) + conf := p.loadConfigurationFromGateways(t.Context()) assert.Equal(t, test.expected, conf) }) } @@ -8186,7 +8185,7 @@ func newGatewaySimpleClientSet(t *testing.T, objects ...runtime.Object) *gatefak continue } - _, err := client.GatewayV1().Gateways(gateway.Namespace).Create(context.Background(), gateway, metav1.CreateOptions{}) + _, err := client.GatewayV1().Gateways(gateway.Namespace).Create(t.Context(), gateway, metav1.CreateOptions{}) require.NoError(t, err) } diff --git a/pkg/provider/kubernetes/ingress/client_test.go b/pkg/provider/kubernetes/ingress/client_test.go index 24240f277..9014265bd 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" @@ -249,7 +248,7 @@ func TestClientIgnoresEmptyEndpointSliceUpdates(t *testing.T) { assert.Fail(t, "expected to receive event for endpointslices") } - emptyEndpointSlice, err = kubeClient.DiscoveryV1().EndpointSlices("test").Get(context.TODO(), "empty-endpointslice", metav1.GetOptions{}) + emptyEndpointSlice, err = kubeClient.DiscoveryV1().EndpointSlices("test").Get(t.Context(), "empty-endpointslice", metav1.GetOptions{}) assert.NoError(t, err) // Update endpoint annotation and resource version (apparently not done by fake client itself) @@ -257,7 +256,7 @@ func TestClientIgnoresEmptyEndpointSliceUpdates(t *testing.T) { // This reflects the behavior of kubernetes controllers which use endpoint annotations for leader election. emptyEndpointSlice.Annotations["test-annotation"] = "___" emptyEndpointSlice.ResourceVersion = "1245" - _, err = kubeClient.DiscoveryV1().EndpointSlices("test").Update(context.TODO(), emptyEndpointSlice, metav1.UpdateOptions{}) + _, err = kubeClient.DiscoveryV1().EndpointSlices("test").Update(t.Context(), emptyEndpointSlice, metav1.UpdateOptions{}) require.NoError(t, err) select { @@ -269,12 +268,12 @@ func TestClientIgnoresEmptyEndpointSliceUpdates(t *testing.T) { case <-time.After(50 * time.Millisecond): } - filledEndpointSlice, err = kubeClient.DiscoveryV1().EndpointSlices("test").Get(context.TODO(), "filled-endpointslice", metav1.GetOptions{}) + filledEndpointSlice, err = kubeClient.DiscoveryV1().EndpointSlices("test").Get(t.Context(), "filled-endpointslice", metav1.GetOptions{}) assert.NoError(t, err) filledEndpointSlice.Endpoints[0].Addresses[0] = "10.13.37.2" filledEndpointSlice.ResourceVersion = "1235" - _, err = kubeClient.DiscoveryV1().EndpointSlices("test").Update(context.TODO(), filledEndpointSlice, metav1.UpdateOptions{}) + _, err = kubeClient.DiscoveryV1().EndpointSlices("test").Update(t.Context(), filledEndpointSlice, metav1.UpdateOptions{}) require.NoError(t, err) select { @@ -296,7 +295,7 @@ func TestClientIgnoresEmptyEndpointSliceUpdates(t *testing.T) { newPortNumber := int32(42) filledEndpointSlice.Ports[0].Port = &newPortNumber filledEndpointSlice.ResourceVersion = "1236" - _, err = kubeClient.DiscoveryV1().EndpointSlices("test").Update(context.TODO(), filledEndpointSlice, metav1.UpdateOptions{}) + _, err = kubeClient.DiscoveryV1().EndpointSlices("test").Update(t.Context(), filledEndpointSlice, metav1.UpdateOptions{}) require.NoError(t, err) select { 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 e8416fb8f..072e08da1 100644 --- a/pkg/provider/kubernetes/ingress/kubernetes.go +++ b/pkg/provider/kubernetes/ingress/kubernetes.go @@ -316,6 +316,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 + logger.Error().Msg("Resource backends are not supported") + continue + } + + if pa.Backend.Service == nil { + logger.Error().Msg("Missing service definition") + continue + } + service, err := p.loadService(client, ingress.Namespace, pa.Backend) if err != nil { logger.Error(). @@ -493,15 +504,6 @@ func (p *Provider) shouldProcessIngress(ingress *netv1.Ingress, ingressClasses [ } 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 79f130391..b67ed4058 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" "fmt" "math" @@ -527,6 +526,28 @@ func TestLoadConfigurationFromIngresses(t *testing.T) { }, }, }, + { + desc: "Ingress with backend resource", + allowEmptyServices: true, + expected: &dynamic.Configuration{ + 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{ + 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{ @@ -1675,7 +1696,7 @@ func TestLoadConfigurationFromIngresses(t *testing.T) { DefaultRuleSyntax: test.defaultRuleSyntax, StrictPrefixMatching: test.strictPrefixMatching, } - conf := p.loadConfigurationFromIngresses(context.Background(), clientMock) + conf := p.loadConfigurationFromIngresses(t.Context(), clientMock) assert.Equal(t, test.expected, conf) }) @@ -1801,7 +1822,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) }) @@ -1851,7 +1872,7 @@ func TestLoadConfigurationFromIngressesWithNativeLB(t *testing.T) { clientMock := newClientMock(generateTestFilename(test.desc)) p := Provider{IngressClass: test.ingressClass} - conf := p.loadConfigurationFromIngresses(context.Background(), clientMock) + conf := p.loadConfigurationFromIngresses(t.Context(), clientMock) assert.Equal(t, test.expected, conf) }) @@ -1912,7 +1933,7 @@ func TestLoadConfigurationFromIngressesWithNodePortLB(t *testing.T) { clientMock := newClientMock(generateTestFilename(test.desc)) p := Provider{DisableClusterScopeResources: test.clusterScopeDisabled} - conf := p.loadConfigurationFromIngresses(context.Background(), clientMock) + conf := p.loadConfigurationFromIngresses(t.Context(), clientMock) assert.Equal(t, test.expected, conf) }) @@ -2084,7 +2105,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) @@ -2170,7 +2191,7 @@ func TestLoadConfigurationFromIngressesWithNativeLBByDefault(t *testing.T) { IngressClass: test.ingressClass, NativeLBByDefault: true, } - conf := p.loadConfigurationFromIngresses(context.Background(), clientMock) + conf := p.loadConfigurationFromIngresses(t.Context(), clientMock) assert.Equal(t, test.expected, conf) }) @@ -2270,9 +2291,9 @@ func TestIngressEndpointPublishedService(t *testing.T) { PublishedService: "default/published-service", }, } - p.loadConfigurationFromIngresses(context.Background(), client) + p.loadConfigurationFromIngresses(t.Context(), client) - ingress, err := kubeClient.NetworkingV1().Ingresses(metav1.NamespaceDefault).Get(context.Background(), "foo", metav1.GetOptions{}) + ingress, err := kubeClient.NetworkingV1().Ingresses(metav1.NamespaceDefault).Get(t.Context(), "foo", metav1.GetOptions{}) require.NoError(t, err) assert.Equal(t, test.expected, ingress.Status.LoadBalancer.Ingress) @@ -2380,9 +2401,11 @@ func TestStrictPrefixMatchingRule(t *testing.T) { t.Parallel() handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}) - muxer, err := traefikhttp.NewMuxer() + parser, err := traefikhttp.NewSyntaxParser() require.NoError(t, err) + muxer := traefikhttp.NewMuxer(parser) + rule := buildStrictPrefixMatchingRule(tt.path) err = muxer.AddRoute(rule, "", 0, handler) require.NoError(t, err) diff --git a/pkg/provider/kv/kv_test.go b/pkg/provider/kv/kv_test.go index 08ff286ef..c8dc84bbc 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" @@ -298,7 +297,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{ @@ -958,7 +957,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) } @@ -977,7 +976,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/nomad/config_test.go b/pkg/provider/nomad/config_test.go index e74ccddb5..efeabe66e 100644 --- a/pkg/provider/nomad/config_test.go +++ b/pkg/provider/nomad/config_test.go @@ -1,7 +1,6 @@ package nomad import ( - "context" "testing" "time" @@ -251,8 +250,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) }) } @@ -3077,8 +3075,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) }) } @@ -3246,8 +3243,7 @@ func Test_buildConfigAllowEmptyServicesTrue(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) }) } @@ -3379,8 +3375,7 @@ func Test_buildConfigAllowEmptyServicesFalseDefault(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) }) } @@ -3428,8 +3423,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 2794fab0c..d5442d50f 100644 --- a/pkg/provider/nomad/nomad_test.go +++ b/pkg/provider/nomad/nomad_test.go @@ -1,7 +1,6 @@ package nomad import ( - "context" "fmt" "net/http" "net/http/httptest" @@ -170,7 +169,7 @@ func Test_getNomadServiceDataWithEmptyServices_GroupService_Scaling1(t *testing. require.NoError(t, err) // make the query for services - items, err := p.getNomadServiceDataWithEmptyServices(context.TODO()) + items, err := p.getNomadServiceDataWithEmptyServices(t.Context()) require.NoError(t, err) require.Len(t, items, 1) } @@ -200,7 +199,7 @@ func Test_getNomadServiceDataWithEmptyServices_GroupService_Scaling0(t *testing. require.NoError(t, err) // make the query for services - items, err := p.getNomadServiceDataWithEmptyServices(context.TODO()) + items, err := p.getNomadServiceDataWithEmptyServices(t.Context()) require.NoError(t, err) require.Len(t, items, 1) } @@ -230,7 +229,7 @@ func Test_getNomadServiceDataWithEmptyServices_GroupService_ScalingDisabled(t *t require.NoError(t, err) // make the query for services - items, err := p.getNomadServiceDataWithEmptyServices(context.TODO()) + items, err := p.getNomadServiceDataWithEmptyServices(t.Context()) require.NoError(t, err) require.Len(t, items, 1) } @@ -260,7 +259,7 @@ func Test_getNomadServiceDataWithEmptyServices_GroupService_ScalingDisabled_Stop require.NoError(t, err) // make the query for services - items, err := p.getNomadServiceDataWithEmptyServices(context.TODO()) + items, err := p.getNomadServiceDataWithEmptyServices(t.Context()) require.NoError(t, err) // Should not be listed as job is stopped @@ -294,7 +293,7 @@ func Test_getNomadServiceDataWithEmptyServices_GroupTaskService_Scaling1(t *test require.NoError(t, err) // make the query for services - items, err := p.getNomadServiceDataWithEmptyServices(context.TODO()) + items, err := p.getNomadServiceDataWithEmptyServices(t.Context()) require.NoError(t, err) require.Len(t, items, 2) } @@ -326,7 +325,7 @@ func Test_getNomadServiceDataWithEmptyServices_GroupTaskService_Scaling0(t *test require.NoError(t, err) // make the query for services - items, err := p.getNomadServiceDataWithEmptyServices(context.TODO()) + items, err := p.getNomadServiceDataWithEmptyServices(t.Context()) require.NoError(t, err) require.Len(t, items, 2) } @@ -356,7 +355,7 @@ func Test_getNomadServiceDataWithEmptyServices_TCP(t *testing.T) { require.NoError(t, err) // make the query for services - items, err := p.getNomadServiceDataWithEmptyServices(context.TODO()) + items, err := p.getNomadServiceDataWithEmptyServices(t.Context()) require.NoError(t, err) require.Len(t, items, 1) } @@ -386,7 +385,7 @@ func Test_getNomadServiceDataWithEmptyServices_UDP(t *testing.T) { require.NoError(t, err) // make the query for services - items, err := p.getNomadServiceDataWithEmptyServices(context.TODO()) + items, err := p.getNomadServiceDataWithEmptyServices(t.Context()) require.NoError(t, err) require.Len(t, items, 1) } @@ -416,7 +415,7 @@ func Test_getNomadServiceDataWithEmptyServices_ScalingEnabled_Stopped(t *testing require.NoError(t, err) // make the query for services - items, err := p.getNomadServiceDataWithEmptyServices(context.TODO()) + items, err := p.getNomadServiceDataWithEmptyServices(t.Context()) require.NoError(t, err) // Should not be listed as job is stopped @@ -465,7 +464,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/tailscale/provider_test.go b/pkg/provider/tailscale/provider_test.go index 2e6e1b962..279299b3c 100644 --- a/pkg/provider/tailscale/provider_test.go +++ b/pkg/provider/tailscale/provider_test.go @@ -1,7 +1,6 @@ package tailscale import ( - "context" "testing" "github.com/stretchr/testify/assert" @@ -125,7 +124,7 @@ func TestProvider_findDomains(t *testing.T) { p := Provider{ResolverName: "foo"} - got := p.findDomains(context.TODO(), test.config) + got := p.findDomains(t.Context(), test.config) assert.Equal(t, test.want, got) }) } @@ -230,7 +229,7 @@ func Test_sanitizeDomains(t *testing.T) { t.Run(test.desc, func(t *testing.T) { t.Parallel() - got := sanitizeDomains(context.TODO(), test.domains) + got := sanitizeDomains(t.Context(), test.domains) assert.Equal(t, test.want, got) }) } diff --git a/pkg/provider/traefik/internal_test.go b/pkg/provider/traefik/internal_test.go index 3ff89a727..8ec8a7668 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" @@ -269,7 +268,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/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 } diff --git a/pkg/proxy/httputil/observability_test.go b/pkg/proxy/httputil/observability_test.go index ea0721e07..67d585a8f 100644 --- a/pkg/proxy/httputil/observability_test.go +++ b/pkg/proxy/httputil/observability_test.go @@ -1,7 +1,6 @@ package httputil import ( - "context" "net/http" "net/http/httptest" "testing" @@ -69,7 +68,7 @@ func TestObservabilityRoundTripper_metrics(t *testing.T) { // force the meter provider with manual reader to collect metrics for the test. metrics.SetMeterProvider(meterProvider) - semConvMetricRegistry, err := metrics.NewSemConvMetricRegistry(context.Background(), &cfg) + semConvMetricRegistry, err := metrics.NewSemConvMetricRegistry(t.Context(), &cfg) require.NoError(t, err) require.NotNil(t, semConvMetricRegistry) @@ -83,7 +82,7 @@ func TestObservabilityRoundTripper_metrics(t *testing.T) { require.NoError(t, err) got := metricdata.ResourceMetrics{} - err = rdr.Collect(context.Background(), &got) + err = rdr.Collect(t.Context(), &got) require.NoError(t, err) require.Len(t, got.ScopeMetrics, 1) 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 7baf233d0..99a196008 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{ @@ -117,7 +117,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, @@ -165,7 +165,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( @@ -305,7 +305,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, @@ -350,7 +350,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"}}, @@ -371,7 +371,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", @@ -405,7 +405,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( @@ -475,7 +475,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( @@ -568,7 +568,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{}, "") @@ -611,7 +611,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( @@ -704,7 +704,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( @@ -771,7 +771,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{ @@ -817,7 +817,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 89ea08963..92cebc050 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.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..9191c78d3 100644 --- a/pkg/server/router/router_test.go +++ b/pkg/server/router/router_test.go @@ -1,7 +1,6 @@ package router import ( - "context" "crypto/tls" "io" "math" @@ -13,10 +12,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,9 +327,12 @@ 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) - handlers := routerManager.BuildHandlers(context.Background(), test.entryPoints, false) + routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, nil, tlsManager, parser) + + handlers := routerManager.BuildHandlers(t.Context(), test.entryPoints, false) w := httptest.NewRecorder() req := testhelpers.MustNewRequest(http.MethodGet, "http://foo.bar/", nil) @@ -709,12 +713,15 @@ func TestRuntimeConfiguration(t *testing.T) { serviceManager := service.NewManager(rtConf.Services, nil, nil, transportManager, proxyBuilderMock{}) middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager, nil) tlsManager := traefiktls.NewManager() - tlsManager.UpdateConfigs(context.Background(), nil, test.tlsOptions, nil) + tlsManager.UpdateConfigs(t.Context(), nil, test.tlsOptions, nil) - routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, nil, tlsManager) + parser, err := httpmuxer.NewSyntaxParser() + require.NoError(t, err) - _ = routerManager.BuildHandlers(context.Background(), entryPoints, false) - _ = routerManager.BuildHandlers(context.Background(), entryPoints, true) + routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, nil, tlsManager, parser) + + _ = 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 @@ -789,9 +796,12 @@ 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.BuildHandlers(context.Background(), entryPoints, false) + routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, nil, tlsManager, parser) + + _ = 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) @@ -865,9 +875,12 @@ 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) - handlers := routerManager.BuildHandlers(context.Background(), entryPoints, false) + routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, nil, tlsManager, parser) + + handlers := routerManager.BuildHandlers(b.Context(), entryPoints, false) w := httptest.NewRecorder() req := testhelpers.MustNewRequest(http.MethodGet, "http://foo.bar/", nil) @@ -907,7 +920,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 97cec0444..02d49bde9 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" @@ -350,7 +349,7 @@ func TestRuntimeConfiguration(t *testing.T) { serviceManager := tcp.NewManager(conf, dialerManager) tlsManager := traefiktls.NewManager() tlsManager.UpdateConfigs( - context.Background(), + t.Context(), map[string]traefiktls.Store{}, map[string]traefiktls.Options{ "default": { @@ -370,7 +369,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 @@ -661,7 +660,7 @@ func TestDomainFronting(t *testing.T) { serviceManager := tcp.NewManager(conf, tcp2.NewDialerManager(nil)) 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) {}), @@ -671,7 +670,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 16f633443..b4e4e3edb 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" @@ -175,7 +174,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: {}, }, @@ -606,7 +605,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 eaa99e174..9dfcaacab 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/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})) diff --git a/pkg/server/server_entrypoint_listenconfig_other_test.go b/pkg/server/server_entrypoint_listenconfig_other_test.go index b143db1c6..45fa5ec47 100644 --- a/pkg/server/server_entrypoint_listenconfig_other_test.go +++ b/pkg/server/server_entrypoint_listenconfig_other_test.go @@ -16,12 +16,12 @@ func TestNewListenConfig(t *testing.T) { require.Nil(t, listenConfig.Control) require.Zero(t, listenConfig.KeepAlive) - l1, err := listenConfig.Listen(context.Background(), "tcp", ep.Address) + l1, err := listenConfig.Listen(t.Context(), "tcp", ep.Address) require.NoError(t, err) require.NotNil(t, l1) defer l1.Close() - l2, err := listenConfig.Listen(context.Background(), "tcp", l1.Addr().String()) + l2, err := listenConfig.Listen(t.Context(), "tcp", l1.Addr().String()) require.Error(t, err) require.ErrorContains(t, err, "address already in use") require.Nil(t, l2) @@ -31,12 +31,12 @@ func TestNewListenConfig(t *testing.T) { require.Nil(t, listenConfig.Control) require.Zero(t, listenConfig.KeepAlive) - l3, err := listenConfig.Listen(context.Background(), "tcp", ep.Address) + l3, err := listenConfig.Listen(t.Context(), "tcp", ep.Address) require.NoError(t, err) require.NotNil(t, l3) defer l3.Close() - l4, err := listenConfig.Listen(context.Background(), "tcp", l3.Addr().String()) + l4, err := listenConfig.Listen(t.Context(), "tcp", l3.Addr().String()) require.Error(t, err) require.ErrorContains(t, err, "address already in use") require.Nil(t, l4) diff --git a/pkg/server/server_entrypoint_listenconfig_unix_test.go b/pkg/server/server_entrypoint_listenconfig_unix_test.go index a5f7dda0a..98aab2fea 100644 --- a/pkg/server/server_entrypoint_listenconfig_unix_test.go +++ b/pkg/server/server_entrypoint_listenconfig_unix_test.go @@ -3,7 +3,6 @@ package server import ( - "context" "net" "testing" @@ -17,12 +16,12 @@ func TestNewListenConfig(t *testing.T) { require.Nil(t, listenConfig.Control) require.Zero(t, listenConfig.KeepAlive) - l1, err := listenConfig.Listen(context.Background(), "tcp", ep.Address) + l1, err := listenConfig.Listen(t.Context(), "tcp", ep.Address) require.NoError(t, err) require.NotNil(t, l1) defer l1.Close() - l2, err := listenConfig.Listen(context.Background(), "tcp", l1.Addr().String()) + l2, err := listenConfig.Listen(t.Context(), "tcp", l1.Addr().String()) require.Error(t, err) require.ErrorContains(t, err, "address already in use") require.Nil(t, l2) @@ -32,24 +31,24 @@ func TestNewListenConfig(t *testing.T) { require.NotNil(t, listenConfig.Control) require.Zero(t, listenConfig.KeepAlive) - l3, err := listenConfig.Listen(context.Background(), "tcp", ep.Address) + l3, err := listenConfig.Listen(t.Context(), "tcp", ep.Address) require.NoError(t, err) require.NotNil(t, l3) defer l3.Close() - l4, err := listenConfig.Listen(context.Background(), "tcp", l3.Addr().String()) + l4, err := listenConfig.Listen(t.Context(), "tcp", l3.Addr().String()) require.NoError(t, err) require.NotNil(t, l4) defer l4.Close() _, l3Port, err := net.SplitHostPort(l3.Addr().String()) require.NoError(t, err) - l5, err := listenConfig.Listen(context.Background(), "tcp", "127.0.0.1:"+l3Port) + l5, err := listenConfig.Listen(t.Context(), "tcp", "127.0.0.1:"+l3Port) require.NoError(t, err) require.NotNil(t, l5) defer l5.Close() - l6, err := listenConfig.Listen(context.Background(), "tcp", l1.Addr().String()) + l6, err := listenConfig.Listen(t.Context(), "tcp", l1.Addr().String()) require.Error(t, err) require.ErrorContains(t, err, "address already in use") require.Nil(t, l6) diff --git a/pkg/server/server_entrypoint_tcp.go b/pkg/server/server_entrypoint_tcp.go index 6c1406624..ea8ea6e48 100644 --- a/pkg/server/server_entrypoint_tcp.go +++ b/pkg/server/server_entrypoint_tcp.go @@ -34,8 +34,6 @@ import ( "github.com/traefik/traefik/v3/pkg/server/service" "github.com/traefik/traefik/v3/pkg/tcp" "github.com/traefik/traefik/v3/pkg/types" - "golang.org/x/net/http2" - "golang.org/x/net/http2/h2c" ) type key string @@ -610,40 +608,47 @@ 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) + 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) } + 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) + + handler = contenttype.DisableAutoDetection(handler) + if configuration.HTTP.EncodeQuerySemicolons { handler = encodeQuerySemicolons(handler) } else { handler = http.AllowQuerySemicolons(handler) } - handler = contenttype.DisableAutoDetection(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) + // 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) } - if withH2c { - handler = h2c.NewHandler(handler, &http2.Server{ - MaxConcurrentStreams: uint32(configuration.HTTP2.MaxConcurrentStreams), - }) - } + handler = normalizePath(handler) handler = denyFragment(handler) serverHTTP := &http.Server{ + Protocols: &protocols, Handler: handler, ErrorLog: stdlog.New(logs.NoLevel(log.Logger, zerolog.DebugLevel), "", 0), ReadTimeout: time.Duration(configuration.Transport.RespondingTimeouts.ReadTimeout), WriteTimeout: time.Duration(configuration.Transport.RespondingTimeouts.WriteTimeout), IdleTimeout: time.Duration(configuration.Transport.RespondingTimeouts.IdleTimeout), MaxHeaderBytes: configuration.HTTP.MaxHeaderBytes, + 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 { @@ -677,19 +682,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) @@ -763,7 +755,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) { @@ -779,3 +771,85 @@ 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) + }) +} diff --git a/pkg/server/server_entrypoint_tcp_http3_test.go b/pkg/server/server_entrypoint_tcp_http3_test.go index cc6b18082..746821958 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(), "foo", &static.EntryPoint{ + entryPoint, err := NewTCPEntryPoint(t.Context(), "foo", &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(), "foo", &static.EntryPoint{ + entryPoint, err := NewTCPEntryPoint(t.Context(), "foo", &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 9b54bce74..392f7c0a3 100644 --- a/pkg/server/server_entrypoint_tcp_test.go +++ b/pkg/server/server_entrypoint_tcp_test.go @@ -17,6 +17,7 @@ import ( "github.com/stretchr/testify/require" ptypes "github.com/traefik/paerser/types" "github.com/traefik/traefik/v3/pkg/config/static" + "github.com/traefik/traefik/v3/pkg/middlewares/requestdecorator" tcprouter "github.com/traefik/traefik/v3/pkg/server/router/tcp" "github.com/traefik/traefik/v3/pkg/tcp" "golang.org/x/net/http2" @@ -79,7 +80,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", @@ -89,7 +90,7 @@ func testShutdown(t *testing.T, router *tcprouter.Router) { }, nil, 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() }) @@ -112,7 +113,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, @@ -143,8 +144,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) @@ -166,7 +169,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{}, @@ -181,7 +184,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) @@ -205,7 +208,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{}, @@ -220,7 +223,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")) @@ -247,7 +250,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{}, @@ -262,7 +265,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{ @@ -295,7 +298,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{}, @@ -310,7 +313,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{ @@ -339,7 +342,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{}, @@ -354,7 +357,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{ @@ -424,3 +427,224 @@ 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) + } + }) + } +} + +// 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. + 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(t.Context(), 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.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 + expectedStatus int + }{ + { + 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/", + 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/", + 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/", + 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", + 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", + expectedStatus: http.StatusOK, + }, + { + desc: "path with traversal and encoded slash", + rawPath: "/a/..%2Fb/", + expectedPath: "/a/../b/", + expectedRaw: "/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")) + }) + } +} diff --git a/pkg/server/server_entrypoint_udp_test.go b/pkg/server/server_entrypoint_udp_test.go index dea497938..6deed527e 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 a2f7f89f0..3d357017e 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 94a4c62a0..515bafdb7 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, true, 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, true, 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()), true, defaultMaxBodySize, nil) + mirror := New(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {}), safe.NewPool(t.Context()), true, 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, true, 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, true, 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) @@ -186,7 +185,7 @@ func TestMirroringWithIgnoredBody(t *testing.T) { emptyBody = []byte(``) ) - 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/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) } diff --git a/pkg/server/service/loadbalancer/p2c/p2c_test.go b/pkg/server/service/loadbalancer/p2c/p2c_test.go index b89420de2..bce6cc456 100644 --- a/pkg/server/service/loadbalancer/p2c/p2c_test.go +++ b/pkg/server/service/loadbalancer/p2c/p2c_test.go @@ -1,7 +1,6 @@ package p2c import ( - "context" "net/http" "net/http/httptest" "strconv" @@ -207,7 +206,7 @@ func TestBalancerPropagate(t *testing.T) { assert.Equal(t, http.StatusOK, recorder.Code) // two gets downed, but balancer still up since first is still up. - balancer.SetStatus(context.Background(), "second", false) + balancer.SetStatus(t.Context(), "second", false) assert.Equal(t, 0, calls) recorder = httptest.NewRecorder() @@ -216,7 +215,7 @@ func TestBalancerPropagate(t *testing.T) { assert.Equal(t, "first", recorder.Header().Get("server")) // first gets downed, balancer is down. - balancer.SetStatus(context.Background(), "first", false) + balancer.SetStatus(t.Context(), "first", false) assert.Equal(t, 1, calls) recorder = httptest.NewRecorder() @@ -224,7 +223,7 @@ func TestBalancerPropagate(t *testing.T) { assert.Equal(t, http.StatusServiceUnavailable, recorder.Code) // two gets up, balancer up. - balancer.SetStatus(context.Background(), "second", true) + balancer.SetStatus(t.Context(), "second", true) assert.Equal(t, 2, calls) recorder = httptest.NewRecorder() diff --git a/pkg/server/service/loadbalancer/wrr/wrr_test.go b/pkg/server/service/loadbalancer/wrr/wrr_test.go index 7e9c3bc04..5260623be 100644 --- a/pkg/server/service/loadbalancer/wrr/wrr_test.go +++ b/pkg/server/service/loadbalancer/wrr/wrr_test.go @@ -76,8 +76,8 @@ func TestBalancerNoServiceUp(t *testing.T) { rw.WriteHeader(http.StatusInternalServerError) }), pointer(1), false) - 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)) @@ -96,7 +96,7 @@ func TestBalancerOneServerDown(t *testing.T) { balancer.Add("second", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { rw.WriteHeader(http.StatusInternalServerError) }), pointer(1), false) - 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 { @@ -118,7 +118,7 @@ func TestBalancerDownThenUp(t *testing.T) { rw.Header().Set("server", "second") rw.WriteHeader(http.StatusOK) }), pointer(1), false) - 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 { @@ -126,7 +126,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)) @@ -160,13 +160,13 @@ func TestBalancerPropagate(t *testing.T) { topBalancer := New(nil, true) topBalancer.Add("balancer1", balancer1, pointer(1), false) _ = 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.Add("balancer2", balancer2, pointer(1), false) _ = 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{}} @@ -181,7 +181,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)) @@ -195,7 +195,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/service_test.go b/pkg/server/service/service_test.go index 1a31a9e75..7d00a69fb 100644 --- a/pkg/server/service/service_test.go +++ b/pkg/server/service/service_test.go @@ -74,7 +74,7 @@ func TestGetLoadBalancer(t *testing.T) { t.Parallel() serviceInfo := &runtime.ServiceInfo{Service: &dynamic.Service{LoadBalancer: test.service}} - handler, err := sm.getLoadBalancerServiceHandler(context.Background(), test.serviceName, serviceInfo) + handler, err := sm.getLoadBalancerServiceHandler(t.Context(), test.serviceName, serviceInfo) if test.expectError { require.Error(t, err) assert.Nil(t, handler) @@ -321,7 +321,7 @@ func TestGetLoadBalancerServiceHandler(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { serviceInfo := &runtime.ServiceInfo{Service: &dynamic.Service{LoadBalancer: test.service}} - handler, err := sm.getLoadBalancerServiceHandler(context.Background(), test.serviceName, serviceInfo) + handler, err := sm.getLoadBalancerServiceHandler(t.Context(), test.serviceName, serviceInfo) assert.NoError(t, err) assert.NotNil(t, handler) @@ -402,7 +402,7 @@ func Test1xxResponses(t *testing.T) { }, } - handler, err := sm.getLoadBalancerServiceHandler(context.Background(), "foobar", info) + handler, err := sm.getLoadBalancerServiceHandler(t.Context(), "foobar", info) assert.NoError(t, err) frontend := httptest.NewServer(handler) @@ -446,7 +446,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) @@ -496,15 +496,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) } @@ -563,7 +563,7 @@ func TestManager_Build(t *testing.T) { manager := NewManager(test.configs, nil, nil, &transportManagerMock{}, nil) - ctx := context.Background() + ctx := t.Context() if len(test.providerName) > 0 { ctx = provider.AddInContext(ctx, "foobar@"+test.providerName) } @@ -586,7 +586,7 @@ func TestMultipleTypeOnBuildHTTP(t *testing.T) { manager := NewManager(services, nil, nil, &transportManagerMock{}, nil) - _, 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 6f06d296c..025ab7520 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" @@ -249,7 +248,7 @@ func TestManager_BuildTCP(t *testing.T) { TCPServices: test.configs, }, dialerManager) - ctx := context.Background() + ctx := t.Context() if len(test.providerName) > 0 { ctx = provider.AddInContext(ctx, "foobar@"+test.providerName) } diff --git a/pkg/server/service/transport_test.go b/pkg/server/service/transport_test.go index d27f293f2..0fd3a8ab8 100644 --- a/pkg/server/service/transport_test.go +++ b/pkg/server/service/transport_test.go @@ -1,7 +1,6 @@ package service import ( - "context" "crypto/rand" "crypto/rsa" "crypto/tls" @@ -607,7 +606,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/udp/service_test.go b/pkg/server/service/udp/service_test.go index ec1794bc1..3a744c6e6 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 f6df9c3c8..f91726b4a 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" @@ -78,7 +77,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 { @@ -95,7 +94,7 @@ func TestTLSInvalidStore(t *testing.T) { }} tlsManager := NewManager() - tlsManager.UpdateConfigs(context.Background(), + tlsManager.UpdateConfigs(t.Context(), map[string]Store{ "default": { DefaultCertificate: &Certificate{ @@ -159,7 +158,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) { @@ -298,7 +297,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/tracing/tracing_test.go b/pkg/tracing/tracing_test.go index b4d92dca8..9507b528c 100644 --- a/pkg/tracing/tracing_test.go +++ b/pkg/tracing/tracing_test.go @@ -2,7 +2,6 @@ package tracing import ( "compress/gzip" - "context" "encoding/json" "io" "net/http" @@ -409,7 +408,7 @@ func TestTracerProvider(t *testing.T) { } closer.Close() - _, span := tracer.Start(context.Background(), "test") + _, span := tracer.Start(t.Context(), "test") defer span.End() span.TracerProvider().Tracer("github.com/traefik/traefik") 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 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