diff --git a/CHANGELOG.md b/CHANGELOG.md index 7aa3cf06f..d5c4d8d02 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,41 @@ +## [v3.3.5](https://github.com/traefik/traefik/tree/v3.3.5) (2025-03-31) +[All Commits](https://github.com/traefik/traefik/compare/v3.3.4...v3.3.5) + +**Bug fixes:** +- **[k8s/gatewayapi]** Set scheme to https with BackendTLSPolicy ([#11586](https://github.com/traefik/traefik/pull/11586) by [rtribotte](https://github.com/rtribotte)) +- **[middleware]** Revert compress middleware algorithms priority to v2 behavior ([#11641](https://github.com/traefik/traefik/pull/11641) by [rtribotte](https://github.com/rtribotte)) +- **[middleware]** Do not abort request when response content-type is malformed ([#11628](https://github.com/traefik/traefik/pull/11628) by [kevinpollet](https://github.com/kevinpollet)) +- **[middleware]** Compress data on flush when compression is not started ([#11583](https://github.com/traefik/traefik/pull/11583) by [kevinpollet](https://github.com/kevinpollet)) + +**Documentation:** +- **[middleware]** Add back forwarded headers section in FAQ ([#11606](https://github.com/traefik/traefik/pull/11606) by [kevinpollet](https://github.com/kevinpollet)) +- New Routing Reference Documentation ([#11330](https://github.com/traefik/traefik/pull/11330) by [sheddy-traefik](https://github.com/sheddy-traefik)) + +**Misc:** +- Merge branch v2.11 into v3.3 ([#11644](https://github.com/traefik/traefik/pull/11644) by [kevinpollet](https://github.com/kevinpollet)) +- Merge branch v2.11 into v3.3 ([#11594](https://github.com/traefik/traefik/pull/11594) by [rtribotte](https://github.com/rtribotte)) + +## [v2.11.22](https://github.com/traefik/traefik/tree/v2.11.22) (2025-03-31) +[All Commits](https://github.com/traefik/traefik/compare/v2.11.21...v2.11.22) + +**Bug fixes:** +- **[ecs,logs]** Bump AWS SDK to v2 ([#11359](https://github.com/traefik/traefik/pull/11359) by [Juneezee](https://github.com/Juneezee)) +- **[logs,tls]** Error level log for configuration-related TLS errors with backends ([#11611](https://github.com/traefik/traefik/pull/11611) by [rtribotte](https://github.com/rtribotte)) +- **[rules]** Allow underscore character in HostSNI matcher ([#11557](https://github.com/traefik/traefik/pull/11557) by [rohitlohar45](https://github.com/rohitlohar45)) +- **[server]** Bump github.com/vulcand/oxy/v2 to v2.0.3 ([#11649](https://github.com/traefik/traefik/pull/11649) by [adamvduke](https://github.com/adamvduke)) +- **[server]** Bump golang.org/x/net to v0.37.0 ([#11632](https://github.com/traefik/traefik/pull/11632) by [kevinpollet](https://github.com/kevinpollet)) +- **[webui]** Change boolean module properties default value to undefined ([#11639](https://github.com/traefik/traefik/pull/11639) by [rtribotte](https://github.com/rtribotte)) +- Bump github.com/golang-jwt/jwt to v4.5.2 and v5.2.2 ([#11634](https://github.com/traefik/traefik/pull/11634) by [kevinpollet](https://github.com/kevinpollet)) +- Bump github.com/redis/go-redis/v9 to v9.6.3 ([#11633](https://github.com/traefik/traefik/pull/11633) by [kevinpollet](https://github.com/kevinpollet)) +- Bump golang.org/x/net to v0.36.0 ([#11608](https://github.com/traefik/traefik/pull/11608) by [kevinpollet](https://github.com/kevinpollet)) +- Bump github.com/go-jose/go-jose/v4 to v4.0.5 ([#11571](https://github.com/traefik/traefik/pull/11571) by [kevinpollet](https://github.com/kevinpollet)) + +**Documentation:** +- **[accesslogs]** Remove documentation for OriginStatusLine and DownstreamStatusLine accessLogs fields ([#11599](https://github.com/traefik/traefik/pull/11599) by [rtribotte](https://github.com/rtribotte)) +- **[middleware]** Clarifies that retry middleware uses TCP, not HTTP status codes ([#11603](https://github.com/traefik/traefik/pull/11603) by [geraldcroes](https://github.com/geraldcroes)) +- **[redis]** Add tip for dynamic configuration updates of Redis ([#11577](https://github.com/traefik/traefik/pull/11577) by [Alanxtl](https://github.com/Alanxtl)) +- Add Security Support ([#11610](https://github.com/traefik/traefik/pull/11610) by [nmengin](https://github.com/nmengin)) + ## [v3.3.4](https://github.com/traefik/traefik/tree/v3.3.4) (2025-02-25) [All Commits](https://github.com/traefik/traefik/compare/v3.3.3...v3.3.4) diff --git a/cmd/traefik/traefik.go b/cmd/traefik/traefik.go index bf001eaa3..61f8e8217 100644 --- a/cmd/traefik/traefik.go +++ b/cmd/traefik/traefik.go @@ -26,7 +26,6 @@ import ( "github.com/traefik/traefik/v3/cmd" "github.com/traefik/traefik/v3/cmd/healthcheck" cmdVersion "github.com/traefik/traefik/v3/cmd/version" - _ "github.com/traefik/traefik/v3/init" tcli "github.com/traefik/traefik/v3/pkg/cli" "github.com/traefik/traefik/v3/pkg/collector" "github.com/traefik/traefik/v3/pkg/config/dynamic" diff --git a/docs/check.Dockerfile b/docs/check.Dockerfile index 015e6f595..41a389f69 100644 --- a/docs/check.Dockerfile +++ b/docs/check.Dockerfile @@ -14,8 +14,8 @@ RUN apk --no-cache --no-progress add \ ruby-json \ zlib-dev -RUN gem install nokogiri --version 1.16.8 --no-document -- --use-system-libraries -RUN gem install html-proofer --version 5.0.7 --no-document -- --use-system-libraries +RUN gem install nokogiri --version 1.18.6 --no-document -- --use-system-libraries +RUN gem install html-proofer --version 5.0.10 --no-document -- --use-system-libraries # After Ruby, some NodeJS YAY! RUN apk --no-cache --no-progress add \ diff --git a/docs/content/deprecation/releases.md b/docs/content/deprecation/releases.md index d3fb7914b..1a5fc0b03 100644 --- a/docs/content/deprecation/releases.md +++ b/docs/content/deprecation/releases.md @@ -4,25 +4,31 @@ Below is a non-exhaustive list of versions and their maintenance status: -| Version | Release Date | Community Support | -|---------|--------------|--------------------| -| 3.3 | Jan 06, 2025 | Yes | -| 3.2 | Oct 28, 2024 | Ended Jan 06, 2025 | -| 3.1 | Jul 15, 2024 | Ended Oct 28, 2024 | -| 3.0 | Apr 29, 2024 | Ended Jul 15, 2024 | -| 2.11 | Feb 12, 2024 | Ends Apr 29, 2025 | -| 2.10 | Apr 24, 2023 | Ended Feb 12, 2024 | -| 2.9 | Oct 03, 2022 | Ended Apr 24, 2023 | -| 2.8 | Jun 29, 2022 | Ended Oct 03, 2022 | -| 2.7 | May 24, 2022 | Ended Jun 29, 2022 | -| 2.6 | Jan 24, 2022 | Ended May 24, 2022 | -| 2.5 | Aug 17, 2021 | Ended Jan 24, 2022 | -| 2.4 | Jan 19, 2021 | Ended Aug 17, 2021 | -| 2.3 | Sep 23, 2020 | Ended Jan 19, 2021 | -| 2.2 | Mar 25, 2020 | Ended Sep 23, 2020 | -| 2.1 | Dec 11, 2019 | Ended Mar 25, 2020 | -| 2.0 | Sep 16, 2019 | Ended Dec 11, 2019 | -| 1.7 | Sep 24, 2018 | Ended Dec 31, 2021 | +| Version | Release Date | Active Support | Security Support | +|---------|--------------|--------------------|-------------------| +| 3.3 | Jan 06, 2025 | Yes | Yes | +| 3.2 | Oct 28, 2024 | Ended Jan 06, 2025 | No | +| 3.1 | Jul 15, 2024 | Ended Oct 28, 2024 | No | +| 3.0 | Apr 29, 2024 | Ended Jul 15, 2024 | No | +| 2.11 | Feb 12, 2024 | Ends Apr 29, 2025 | Ends Feb 01, 2026 | +| 2.10 | Apr 24, 2023 | Ended Feb 12, 2024 | No | +| 2.9 | Oct 03, 2022 | Ended Apr 24, 2023 | No | +| 2.8 | Jun 29, 2022 | Ended Oct 03, 2022 | No | +| 2.7 | May 24, 2022 | Ended Jun 29, 2022 | No | +| 2.6 | Jan 24, 2022 | Ended May 24, 2022 | No | +| 2.5 | Aug 17, 2021 | Ended Jan 24, 2022 | No | +| 2.4 | Jan 19, 2021 | Ended Aug 17, 2021 | No | +| 2.3 | Sep 23, 2020 | Ended Jan 19, 2021 | No | +| 2.2 | Mar 25, 2020 | Ended Sep 23, 2020 | No | +| 2.1 | Dec 11, 2019 | Ended Mar 25, 2020 | No | +| 2.0 | Sep 16, 2019 | Ended Dec 11, 2019 | No | +| 1.7 | Sep 24, 2018 | Ended Dec 31, 2021 | No | + +??? example "Active Support / Security Support" + + - **Active support**: Receives any bug fixes. + + - **Security support**: Receives only critical bug and security fixes. This page is maintained and updated periodically to reflect our roadmap and any decisions affecting the end of support for Traefik Proxy. diff --git a/docs/content/getting-started/faq.md b/docs/content/getting-started/faq.md index 1c3d3f6a8..66f2d4d04 100644 --- a/docs/content/getting-started/faq.md +++ b/docs/content/getting-started/faq.md @@ -143,6 +143,21 @@ To take into account the new certificate contents, the update of the dynamic con One way to achieve that, is to trigger a file notification, for example, by using the `touch` command on the configuration file. +## What Are the Forwarded Headers When Proxying HTTP Requests? + +By default, the following headers are automatically added when proxying requests: + +| Property | HTTP Header | +|---------------------------|----------------------------| +| Client's IP | X-Forwarded-For, X-Real-Ip | +| Host | X-Forwarded-Host | +| Port | X-Forwarded-Port | +| Protocol | X-Forwarded-Proto | +| Proxy Server's Hostname | X-Forwarded-Server | + +For more details, +please check out the [forwarded header](../routing/entrypoints.md#forwarded-headers) documentation. + ## How Traefik is Storing and Serving TLS Certificates? ### Storing TLS Certificates diff --git a/docs/content/middlewares/http/compress.md b/docs/content/middlewares/http/compress.md index 1f204be29..58d06de96 100644 --- a/docs/content/middlewares/http/compress.md +++ b/docs/content/middlewares/http/compress.md @@ -264,10 +264,10 @@ http: ### `encodings` -_Optional, Default="zstd, br, gzip"_ +_Optional, Default="gzip, br, zstd"_ `encodings` specifies the list of supported compression encodings. -At least one encoding value must be specified, and valid entries are `zstd` (Zstandard), `br` (Brotli), and `gzip` (Gzip). +At least one encoding value must be specified, and valid entries are `gzip` (Gzip), `br` (Brotli), and `zstd` (Zstandard). The order of the list also sets the priority, the top entry has the highest priority. ```yaml tab="Docker & Swarm" diff --git a/docs/content/middlewares/http/retry.md b/docs/content/middlewares/http/retry.md index c8ae38d22..3c838bef8 100644 --- a/docs/content/middlewares/http/retry.md +++ b/docs/content/middlewares/http/retry.md @@ -12,8 +12,11 @@ Retrying until it Succeeds TODO: add schema --> -The Retry middleware reissues requests a given number of times to a backend server if that server does not reply. -As soon as the server answers, the middleware stops retrying, regardless of the response status. +The Retry middleware reissues requests a given number of times when it cannot contact the backend service. +This applies at the transport level (TCP). +If the service does not respond to the initial connection attempt, the middleware retries. +However, once the service responds, regardless of the HTTP status code, the middleware considers it operational and stops retrying. +This means that the retry mechanism does not handle HTTP errors; it only retries when there is no response at the TCP level. The Retry middleware has an optional configuration to enable an exponential backoff. ## Configuration Examples diff --git a/docs/content/migration/v3.md b/docs/content/migration/v3.md index 205471d7e..a9dbbe553 100644 --- a/docs/content/migration/v3.md +++ b/docs/content/migration/v3.md @@ -188,6 +188,16 @@ and will be removed in the next major version. In `v3.3.4`, the OpenTelemetry Request Duration metric (named `traefik_(entrypoint|router|service)_request_duration_seconds`) unit has been changed from milliseconds to seconds. To be consistent with the naming and other metrics providers, the metric now reports the duration in seconds. +## v3.3.5 + +### Compress Middleware + +In `v3.3.5`, the compress middleware `encodings` option default value is now `gzip, br, zstd`. +This change helps the algorithm selection to favor the `gzip` algorithm over the other algorithms. + +It impacts requests that do not specify their preferred algorithm, +or has no order preference, in the `Accept-Encoding` header. + ## v3.3 to v3.4 ### Kubernetes CRD Provider diff --git a/docs/content/observability/access-logs.md b/docs/content/observability/access-logs.md index 4acf5ab41..950755b64 100644 --- a/docs/content/observability/access-logs.md +++ b/docs/content/observability/access-logs.md @@ -256,9 +256,7 @@ accessLog: | `OriginDuration` | The time taken (in nanoseconds) by the origin server ('upstream') to return its response. | | `OriginContentSize` | The content length specified by the origin server, or 0 if unspecified. | | `OriginStatus` | The HTTP status code returned by the origin server. If the request was handled by this Traefik instance (e.g. with a redirect), then this value will be absent (0). | - | `OriginStatusLine` | `OriginStatus` + Status code explanation | | `DownstreamStatus` | The HTTP status code returned to the client. | - | `DownstreamStatusLine` | `DownstreamStatus` + Status code explanation | | `DownstreamContentSize` | The number of bytes in the response entity returned to the client. This is in addition to the "Content-Length" header, which may be present in the origin response. | | `RequestCount` | The number of requests received since the Traefik instance started. | | `GzipRatio` | The response body compression ratio achieved. | diff --git a/go.mod b/go.mod index 24597fc4c..fcbb9aa21 100644 --- a/go.mod +++ b/go.mod @@ -29,7 +29,7 @@ require ( github.com/golang/protobuf v1.5.4 github.com/google/go-github/v28 v28.1.1 github.com/gorilla/mux v1.8.1 - github.com/gorilla/websocket v1.5.1 + github.com/gorilla/websocket v1.5.3 github.com/hashicorp/consul/api v1.26.1 github.com/hashicorp/go-hclog v1.6.3 github.com/hashicorp/go-multierror v1.1.1 @@ -75,7 +75,7 @@ require ( github.com/unrolled/render v1.0.2 github.com/unrolled/secure v1.0.9 github.com/valyala/fasthttp v1.58.0 - github.com/vulcand/oxy/v2 v2.0.0 + github.com/vulcand/oxy/v2 v2.0.3 github.com/vulcand/predicate v1.2.0 github.com/yuin/gopher-lua v1.1.1 go.opentelemetry.io/collector/pdata v1.10.0 @@ -96,10 +96,10 @@ require ( go.opentelemetry.io/otel/sdk/metric v1.28.0 go.opentelemetry.io/otel/trace v1.32.0 golang.org/x/mod v0.22.0 - golang.org/x/net v0.33.0 - golang.org/x/sync v0.10.0 - golang.org/x/sys v0.29.0 - golang.org/x/text v0.21.0 + golang.org/x/net v0.37.0 + golang.org/x/sync v0.12.0 + golang.org/x/sys v0.31.0 + golang.org/x/text v0.23.0 golang.org/x/time v0.8.0 golang.org/x/tools v0.28.0 google.golang.org/grpc v1.67.1 @@ -210,8 +210,8 @@ require ( github.com/goccy/go-json v0.10.4 // indirect github.com/gofrs/flock v0.12.1 // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang-jwt/jwt/v4 v4.5.1 // indirect - github.com/golang-jwt/jwt/v5 v5.2.1 // indirect + github.com/golang-jwt/jwt/v4 v4.5.2 // indirect + github.com/golang-jwt/jwt/v5 v5.2.2 // indirect github.com/google/gnostic-models v0.6.8 // indirect github.com/google/go-cmp v0.6.0 // indirect github.com/google/go-querystring v1.1.0 // indirect @@ -290,6 +290,7 @@ require ( github.com/nrdcg/nodion v0.1.0 // indirect github.com/nrdcg/porkbun v0.4.0 // indirect github.com/nzdjb/go-metaname v1.0.0 // indirect + github.com/onsi/ginkgo v1.16.5 // indirect github.com/onsi/ginkgo/v2 v2.20.2 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.0 // indirect @@ -357,15 +358,16 @@ require ( go.opentelemetry.io/contrib/propagators/jaeger v1.28.0 // indirect go.opentelemetry.io/contrib/propagators/ot v1.28.0 // indirect go.opentelemetry.io/proto/otlp v1.3.1 // indirect + go.uber.org/atomic v1.11.0 // indirect go.uber.org/mock v0.4.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/ratelimit v0.3.0 // indirect go.uber.org/zap v1.26.0 // indirect golang.org/x/arch v0.4.0 // indirect - golang.org/x/crypto v0.32.0 // indirect + golang.org/x/crypto v0.36.0 // indirect golang.org/x/exp v0.0.0-20241210194714-1829a127f884 // indirect golang.org/x/oauth2 v0.24.0 // indirect - golang.org/x/term v0.28.0 // indirect + golang.org/x/term v0.30.0 // indirect google.golang.org/api v0.214.0 // indirect google.golang.org/genproto v0.0.0-20241021214115-324edc3d5d38 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20241118233622-e639e219e697 // indirect diff --git a/go.sum b/go.sum index 5fc696a57..f856007d3 100644 --- a/go.sum +++ b/go.sum @@ -452,10 +452,10 @@ github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzq github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= -github.com/golang-jwt/jwt/v4 v4.5.1 h1:JdqV9zKUdtaa9gdPlywC3aeoEsR681PlKC+4F5gQgeo= -github.com/golang-jwt/jwt/v4 v4.5.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= -github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= -github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= +github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI= +github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8= +github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -561,8 +561,8 @@ github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+ github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY= -github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= +github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= +github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gravitational/trace v1.1.16-0.20220114165159-14a9a7dd6aaf h1:C1GPyPJrOlJlIrcaBBiBpDsqZena2Ks8spa5xZqr1XQ= github.com/gravitational/trace v1.1.16-0.20220114165159-14a9a7dd6aaf/go.mod h1:zXqxTI6jXDdKnlf8s+nT+3c8LrwUEy3yNpO4XJL90lA= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= @@ -929,8 +929,9 @@ github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+W github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.16.2/go.mod h1:CObGmKUOKaSC0RjmoAK7tKyn4Azo5P2IWuoMnvwxz1E= -github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= +github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= +github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= github.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= github.com/onsi/ginkgo/v2 v2.20.2 h1:7NVCeyIWROIAheY21RLS+3j2bb52W0W82tkberYytp4= @@ -1230,8 +1231,8 @@ github.com/vinyldns/go-vinyldns v0.9.16 h1:GZJStDkcCk1F1AcRc64LuuMh+ENL8pHA0CVd4 github.com/vinyldns/go-vinyldns v0.9.16/go.mod h1:5qIJOdmzAnatKjurI+Tl4uTus7GJKJxb+zitufjHs3Q= github.com/volcengine/volc-sdk-golang v1.0.189 h1:VMDTHWYXakXJtZqPYn0As/h4eB0c4imvyru6mIp+o60= github.com/volcengine/volc-sdk-golang v1.0.189/go.mod h1:u0VtPvlXWpXDTmc9IHkaW1q+5Jjwus4oAqRhNMDRInE= -github.com/vulcand/oxy/v2 v2.0.0 h1:V+scHhd2xBjO8ojBRgxCM+OdZxRA/YTs8M70w5tdNy8= -github.com/vulcand/oxy/v2 v2.0.0/go.mod h1:uIAz3sYafO7i+V3SC8oDlMn/lt1i9aWcyXuXqVswKzE= +github.com/vulcand/oxy/v2 v2.0.3 h1:CPWVPfW4hVZXzwwiQzpFidbnJKpahjPHezM+7TkZRNw= +github.com/vulcand/oxy/v2 v2.0.3/go.mod h1:k3t+xjyqmXVh88FdFDbYmUKMEvNpaejvBW14es6H70A= github.com/vulcand/predicate v1.2.0 h1:uFsW1gcnnR7R+QTID+FVcs0sSYlIGntoGOTb3rQJt50= github.com/vulcand/predicate v1.2.0/go.mod h1:VipoNYXny6c8N381zGUWkjuuNHiRbeAZhE7Qm9c+2GA= github.com/vultr/govultr/v3 v3.9.1 h1:uxSIb8Miel7tqTs3ee+z3t+JelZikwqBBsZzCOPBy/8= @@ -1335,8 +1336,9 @@ go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeX go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= +go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= @@ -1390,8 +1392,8 @@ golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIi golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= -golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= -golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= +golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= +golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -1492,8 +1494,8 @@ golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= -golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= -golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= +golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c= +golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1515,8 +1517,8 @@ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= -golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= +golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -1607,8 +1609,8 @@ golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= -golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= +golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -1619,8 +1621,8 @@ golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o= golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= -golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg= -golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek= +golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y= +golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1637,8 +1639,8 @@ golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= -golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= +golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= diff --git a/init/init.go b/init/init.go deleted file mode 100644 index e54655d0f..000000000 --- a/init/init.go +++ /dev/null @@ -1,21 +0,0 @@ -package init - -import ( - "os" - "strings" -) - -// This makes use of the GODEBUG flag `http2xconnect` to deactivate the connect setting for HTTP2 by default. -// This type of upgrade is yet incompatible with `net/http` http1 reverse proxy. -// Please see https://github.com/golang/go/issues/71128#issuecomment-2574193636. -func init() { - goDebug := os.Getenv("GODEBUG") - if strings.Contains(goDebug, "http2xconnect") { - return - } - - if len(goDebug) > 0 { - goDebug += "," - } - os.Setenv("GODEBUG", goDebug+"http2xconnect=0") -} diff --git a/pkg/config/dynamic/middlewares.go b/pkg/config/dynamic/middlewares.go index 501b75c01..04aa0b6db 100644 --- a/pkg/config/dynamic/middlewares.go +++ b/pkg/config/dynamic/middlewares.go @@ -190,7 +190,7 @@ type Compress struct { } func (c *Compress) SetDefaults() { - c.Encodings = []string{"zstd", "br", "gzip"} + c.Encodings = []string{"gzip", "br", "zstd"} } // +k8s:deepcopy-gen=true diff --git a/pkg/middlewares/compress/acceptencoding.go b/pkg/middlewares/compress/acceptencoding.go index c09c84130..a35362733 100644 --- a/pkg/middlewares/compress/acceptencoding.go +++ b/pkg/middlewares/compress/acceptencoding.go @@ -23,57 +23,44 @@ type Encoding struct { Weight float64 } -func getCompressionEncoding(acceptEncoding []string, defaultEncoding string, supportedEncodings []string) string { - if defaultEncoding == "" { - if slices.Contains(supportedEncodings, brotliName) { - // Keeps the pre-existing default inside Traefik if brotli is a supported encoding. - defaultEncoding = brotliName - } else if len(supportedEncodings) > 0 { - // Otherwise use the first supported encoding. - defaultEncoding = supportedEncodings[0] - } +func (c *compress) getCompressionEncoding(acceptEncoding []string) string { + // RFC says: An Accept-Encoding header field with a field value that is empty implies that the user agent does not want any content coding in response. + // https://datatracker.ietf.org/doc/html/rfc9110#name-accept-encoding + if len(acceptEncoding) == 1 && acceptEncoding[0] == "" { + return identityName } - encodings, hasWeight := parseAcceptEncoding(acceptEncoding, supportedEncodings) + acceptableEncodings := parseAcceptableEncodings(acceptEncoding, c.supportedEncodings) - if hasWeight { - if len(encodings) == 0 { - return identityName - } - - encoding := encodings[0] - - if encoding.Type == identityName && encoding.Weight == 0 { - return notAcceptable - } - - if encoding.Type == wildcardName && encoding.Weight == 0 { - return notAcceptable - } - - if encoding.Type == wildcardName { - return defaultEncoding - } - - return encoding.Type + // An empty Accept-Encoding header field would have been handled earlier. + // If empty, it means no encoding is supported, we do not encode. + if len(acceptableEncodings) == 0 { + // TODO: return 415 status code instead of deactivating the compression, if the backend was not to compress as well. + return notAcceptable } - for _, dt := range supportedEncodings { - if slices.ContainsFunc(encodings, func(e Encoding) bool { return e.Type == dt }) { - return dt + slices.SortFunc(acceptableEncodings, func(a, b Encoding) int { + if a.Weight == b.Weight { + // At same weight, we want to prioritize based on the encoding priority. + // the lower the index, the higher the priority. + return cmp.Compare(c.supportedEncodings[a.Type], c.supportedEncodings[b.Type]) } + return cmp.Compare(b.Weight, a.Weight) + }) + + if acceptableEncodings[0].Type == wildcardName { + if c.defaultEncoding == "" { + return c.encodings[0] + } + + return c.defaultEncoding } - if slices.ContainsFunc(encodings, func(e Encoding) bool { return e.Type == wildcardName }) { - return defaultEncoding - } - - return identityName + return acceptableEncodings[0].Type } -func parseAcceptEncoding(acceptEncoding, supportedEncodings []string) ([]Encoding, bool) { +func parseAcceptableEncodings(acceptEncoding []string, supportedEncodings map[string]int) []Encoding { var encodings []Encoding - var hasWeight bool for _, line := range acceptEncoding { for _, item := range strings.Split(strings.ReplaceAll(line, " ", ""), ",") { @@ -82,9 +69,7 @@ func parseAcceptEncoding(acceptEncoding, supportedEncodings []string) ([]Encodin continue } - if !slices.Contains(supportedEncodings, parsed[0]) && - parsed[0] != identityName && - parsed[0] != wildcardName { + if _, ok := supportedEncodings[parsed[0]]; !ok { continue } @@ -94,8 +79,13 @@ func parseAcceptEncoding(acceptEncoding, supportedEncodings []string) ([]Encodin if len(parsed) > 1 && strings.HasPrefix(parsed[1], "q=") { w, _ := strconv.ParseFloat(strings.TrimPrefix(parsed[1], "q="), 64) + // If the weight is 0, the encoding is not acceptable. + // We can skip the encoding. + if w == 0 { + continue + } + weight = w - hasWeight = true } encodings = append(encodings, Encoding{ @@ -105,9 +95,5 @@ func parseAcceptEncoding(acceptEncoding, supportedEncodings []string) ([]Encodin } } - slices.SortFunc(encodings, func(a, b Encoding) int { - return cmp.Compare(b.Weight, a.Weight) - }) - - return encodings, hasWeight + return encodings } diff --git a/pkg/middlewares/compress/acceptencoding_test.go b/pkg/middlewares/compress/acceptencoding_test.go index 570315b47..d3059af6e 100644 --- a/pkg/middlewares/compress/acceptencoding_test.go +++ b/pkg/middlewares/compress/acceptencoding_test.go @@ -1,9 +1,12 @@ package compress import ( + "context" "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/traefik/traefik/v3/pkg/config/dynamic" ) func Test_getCompressionEncoding(t *testing.T) { @@ -15,14 +18,19 @@ func Test_getCompressionEncoding(t *testing.T) { expected string }{ { - desc: "br > gzip (no weight)", - acceptEncoding: []string{"gzip, br"}, - expected: brotliName, + desc: "Empty Accept-Encoding", + acceptEncoding: []string{""}, + expected: identityName, }, { - desc: "zstd > br > gzip (no weight)", - acceptEncoding: []string{"zstd, gzip, br"}, - expected: zstdName, + desc: "gzip > br (no weight)", + acceptEncoding: []string{"gzip, br"}, + expected: gzipName, + }, + { + desc: "gzip > br > zstd (no weight)", + acceptEncoding: []string{"gzip, br, zstd"}, + expected: gzipName, }, { desc: "known compression encoding (no weight)", @@ -32,24 +40,34 @@ func Test_getCompressionEncoding(t *testing.T) { { desc: "unknown compression encoding (no weight), no encoding", acceptEncoding: []string{"compress, rar"}, - expected: identityName, + expected: notAcceptable, }, { - desc: "wildcard return the default compression encoding", + desc: "wildcard returns the default compression encoding", acceptEncoding: []string{"*"}, - expected: brotliName, + expected: gzipName, }, { - desc: "wildcard return the custom default compression encoding", + desc: "wildcard returns the custom default compression encoding", acceptEncoding: []string{"*"}, - defaultEncoding: "foo", - expected: "foo", + defaultEncoding: brotliName, + expected: brotliName, }, { desc: "follows weight", acceptEncoding: []string{"br;q=0.8, gzip;q=1.0, *;q=0.1"}, expected: gzipName, }, + { + desc: "identity with higher weight is preferred", + acceptEncoding: []string{"br;q=0.8, identity;q=1.0"}, + expected: identityName, + }, + { + desc: "identity with equal weight is not preferred", + acceptEncoding: []string{"br;q=0.8, identity;q=0.8"}, + expected: brotliName, + }, { desc: "ignore unknown compression encoding", acceptEncoding: []string{"compress;q=1.0, gzip;q=0.5"}, @@ -93,6 +111,33 @@ func Test_getCompressionEncoding(t *testing.T) { supportedEncodings: []string{gzipName, brotliName}, expected: gzipName, }, + { + desc: "Zero weights, no compression", + acceptEncoding: []string{"br;q=0, gzip;q=0, zstd;q=0"}, + expected: notAcceptable, + }, + { + desc: "Zero weights, default encoding, no compression", + acceptEncoding: []string{"br;q=0, gzip;q=0, zstd;q=0"}, + defaultEncoding: "br", + expected: notAcceptable, + }, + { + desc: "Same weight, first supported encoding", + acceptEncoding: []string{"br;q=1.0, gzip;q=1.0, zstd;q=1.0"}, + expected: gzipName, + }, + { + desc: "Same weight, first supported encoding, order has no effect", + acceptEncoding: []string{"br;q=1.0, zstd;q=1.0, gzip;q=1.0"}, + expected: gzipName, + }, + { + desc: "Same weight, first supported encoding, defaultEncoding has no effect", + acceptEncoding: []string{"br;q=1.0, zstd;q=1.0, gzip;q=1.0"}, + defaultEncoding: "br", + expected: gzipName, + }, } for _, test := range testCases { @@ -103,7 +148,18 @@ func Test_getCompressionEncoding(t *testing.T) { test.supportedEncodings = defaultSupportedEncodings } - encoding := getCompressionEncoding(test.acceptEncoding, test.defaultEncoding, test.supportedEncodings) + conf := dynamic.Compress{ + Encodings: test.supportedEncodings, + DefaultEncoding: test.defaultEncoding, + } + + h, err := New(context.Background(), nil, conf, "test") + require.NoError(t, err) + + c, ok := h.(*compress) + require.True(t, ok) + + encoding := c.getCompressionEncoding(test.acceptEncoding) assert.Equal(t, test.expected, encoding) }) @@ -147,7 +203,6 @@ func Test_parseAcceptEncoding(t *testing.T) { {Type: zstdName, Weight: 1}, {Type: gzipName, Weight: 1}, {Type: brotliName, Weight: 1}, - {Type: wildcardName, Weight: 0}, }, assertWeight: assert.True, }, @@ -157,7 +212,6 @@ func Test_parseAcceptEncoding(t *testing.T) { supportedEncodings: []string{zstdName}, expected: []Encoding{ {Type: zstdName, Weight: 1}, - {Type: wildcardName, Weight: 0}, }, assertWeight: assert.True, }, @@ -188,7 +242,6 @@ func Test_parseAcceptEncoding(t *testing.T) { expected: []Encoding{ {Type: gzipName, Weight: 1}, {Type: identityName, Weight: 0.5}, - {Type: wildcardName, Weight: 0}, }, assertWeight: assert.True, }, @@ -198,7 +251,6 @@ func Test_parseAcceptEncoding(t *testing.T) { supportedEncodings: []string{"br"}, expected: []Encoding{ {Type: identityName, Weight: 0.5}, - {Type: wildcardName, Weight: 0}, }, assertWeight: assert.True, }, @@ -212,10 +264,11 @@ func Test_parseAcceptEncoding(t *testing.T) { test.supportedEncodings = defaultSupportedEncodings } - aes, hasWeight := parseAcceptEncoding(test.values, test.supportedEncodings) + supportedEncodings := buildSupportedEncodings(test.supportedEncodings) + + aes := parseAcceptableEncodings(test.values, supportedEncodings) assert.Equal(t, test.expected, aes) - test.assertWeight(t, hasWeight) }) } } diff --git a/pkg/middlewares/compress/compress.go b/pkg/middlewares/compress/compress.go index cdf4d972b..0bb3788d2 100644 --- a/pkg/middlewares/compress/compress.go +++ b/pkg/middlewares/compress/compress.go @@ -22,7 +22,7 @@ const typeName = "Compress" // See https://github.com/klauspost/compress/blob/9559b037e79ad673c71f6ef7c732c00949014cd2/gzhttp/compress.go#L47. const defaultMinSize = 1024 -var defaultSupportedEncodings = []string{zstdName, brotliName, gzipName} +var defaultSupportedEncodings = []string{gzipName, brotliName, zstdName} // Compress is a middleware that allows to compress the response. type compress struct { @@ -33,6 +33,8 @@ type compress struct { minSize int encodings []string defaultEncoding string + // supportedEncodings is a map of supported encodings and their priority. + supportedEncodings map[string]int brotliHandler http.Handler gzipHandler http.Handler @@ -85,13 +87,14 @@ func New(ctx context.Context, next http.Handler, conf dynamic.Compress, name str } c := &compress{ - next: next, - name: name, - excludes: excludes, - includes: includes, - minSize: minSize, - encodings: conf.Encodings, - defaultEncoding: conf.DefaultEncoding, + next: next, + name: name, + excludes: excludes, + includes: includes, + minSize: minSize, + encodings: conf.Encodings, + defaultEncoding: conf.DefaultEncoding, + supportedEncodings: buildSupportedEncodings(conf.Encodings), } var err error @@ -114,6 +117,19 @@ func New(ctx context.Context, next http.Handler, conf dynamic.Compress, name str return c, nil } +func buildSupportedEncodings(encodings []string) map[string]int { + supportedEncodings := map[string]int{ + // the most permissive first. + wildcardName: -1, + // the less permissive last. + identityName: len(encodings), + } + for i, encoding := range encodings { + supportedEncodings[encoding] = i + } + return supportedEncodings +} + func (c *compress) ServeHTTP(rw http.ResponseWriter, req *http.Request) { logger := middlewares.GetLogger(req.Context(), c.name, typeName) @@ -149,7 +165,7 @@ func (c *compress) ServeHTTP(rw http.ResponseWriter, req *http.Request) { return } - c.chooseHandler(getCompressionEncoding(acceptEncoding, c.defaultEncoding, c.encodings), rw, req) + c.chooseHandler(c.getCompressionEncoding(acceptEncoding), rw, req) } func (c *compress) chooseHandler(typ string, rw http.ResponseWriter, req *http.Request) { diff --git a/pkg/middlewares/compress/compress_test.go b/pkg/middlewares/compress/compress_test.go index 430df7611..622471f2e 100644 --- a/pkg/middlewares/compress/compress_test.go +++ b/pkg/middlewares/compress/compress_test.go @@ -39,9 +39,14 @@ func TestNegotiation(t *testing.T) { expEncoding: "", }, { + // In this test, the default encodings are defaulted to gzip, brotli, and zstd, + // which make gzip the default encoding, and will be selected. + // However, the klauspost/compress gzhttp handler does not compress when Accept-Encoding: * is set. + // Until klauspost/compress gzhttp package supports the asterisk, + // we will not support it when selecting the gzip encoding. desc: "accept any header", acceptEncHeader: "*", - expEncoding: brotliName, + expEncoding: "", }, { desc: "gzip accept header", @@ -66,7 +71,7 @@ func TestNegotiation(t *testing.T) { { desc: "multi accept header list, prefer br", acceptEncHeader: "gzip, br", - expEncoding: brotliName, + expEncoding: gzipName, }, { desc: "zstd accept header", @@ -78,15 +83,20 @@ func TestNegotiation(t *testing.T) { acceptEncHeader: "zstd;q=0.9, br;q=0.8, gzip;q=0.6", expEncoding: zstdName, }, + { + desc: "multi accept header, prefer brotli", + acceptEncHeader: "gzip;q=0.8, br;q=1.0, zstd;q=0.7", + expEncoding: brotliName, + }, { desc: "multi accept header, prefer gzip", acceptEncHeader: "gzip;q=1.0, br;q=0.8, zstd;q=0.7", expEncoding: gzipName, }, { - desc: "multi accept header list, prefer zstd", + desc: "multi accept header list, prefer gzip", acceptEncHeader: "gzip, br, zstd", - expEncoding: zstdName, + expEncoding: gzipName, }, } @@ -190,6 +200,28 @@ func TestShouldNotCompressWhenNoAcceptEncodingHeader(t *testing.T) { assert.EqualValues(t, rw.Body.Bytes(), fakeBody) } +func TestEmptyAcceptEncoding(t *testing.T) { + req := testhelpers.MustNewRequest(http.MethodGet, "http://localhost", nil) + req.Header.Add(acceptEncodingHeader, "") + + fakeBody := generateBytes(gzhttp.DefaultMinSize) + next := http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { + _, err := rw.Write(fakeBody) + if err != nil { + http.Error(rw, err.Error(), http.StatusInternalServerError) + } + }) + handler, err := New(context.Background(), next, dynamic.Compress{Encodings: defaultSupportedEncodings}, "testing") + require.NoError(t, err) + + rw := httptest.NewRecorder() + handler.ServeHTTP(rw, req) + + assert.Empty(t, rw.Header().Get(contentEncodingHeader)) + assert.Empty(t, rw.Header().Get(varyHeader)) + assert.EqualValues(t, rw.Body.Bytes(), fakeBody) +} + func TestShouldNotCompressWhenIdentityAcceptEncodingHeader(t *testing.T) { req := testhelpers.MustNewRequest(http.MethodGet, "http://localhost", nil) req.Header.Set(acceptEncodingHeader, "identity") diff --git a/pkg/middlewares/compress/compression_handler.go b/pkg/middlewares/compress/compression_handler.go index 215607ccd..07583028b 100644 --- a/pkg/middlewares/compress/compression_handler.go +++ b/pkg/middlewares/compress/compression_handler.go @@ -233,8 +233,12 @@ func (r *responseWriter) Write(p []byte) (int, error) { // Disable compression according to user wishes in excludedContentTypes or includedContentTypes. if ct := r.rw.Header().Get(contentType); ct != "" { mediaType, params, err := mime.ParseMediaType(ct) + // To align the behavior with the klauspost handler for Gzip, + // if the MIME type is not parsable the compression is disabled. if err != nil { - return 0, fmt.Errorf("parsing content-type media type: %w", err) + r.compressionDisabled = true + r.rw.WriteHeader(r.statusCode) + return r.rw.Write(p) } if len(r.includedContentTypes) > 0 { diff --git a/pkg/middlewares/compress/compression_handler_test.go b/pkg/middlewares/compress/compression_handler_test.go index b078ed71f..89bbe062b 100644 --- a/pkg/middlewares/compress/compression_handler_test.go +++ b/pkg/middlewares/compress/compression_handler_test.go @@ -577,6 +577,11 @@ func Test_ExcludedContentTypes(t *testing.T) { contentType: "", expCompression: true, }, + { + desc: "MIME malformed", + contentType: "application/json;charset=UTF-8;charset=utf-8", + expCompression: false, + }, { desc: "MIME match", contentType: "application/json", @@ -687,6 +692,11 @@ func Test_IncludedContentTypes(t *testing.T) { contentType: "", expCompression: true, }, + { + desc: "MIME malformed", + contentType: "application/json;charset=UTF-8;charset=utf-8", + expCompression: false, + }, { desc: "MIME match", contentType: "application/json", diff --git a/pkg/muxer/tcp/matcher.go b/pkg/muxer/tcp/matcher.go index 9e8959928..4ee55eb68 100644 --- a/pkg/muxer/tcp/matcher.go +++ b/pkg/muxer/tcp/matcher.go @@ -67,7 +67,7 @@ func clientIP(tree *matchersTree, clientIP ...string) error { return nil } -var hostOrIP = regexp.MustCompile(`^[[:alnum:]\.\-\:]+$`) +var hostOrIP = regexp.MustCompile(`^[[:word:]\.\-\:]+$`) // hostSNI checks if the SNI Host of the connection match the matcher host. func hostSNI(tree *matchersTree, hosts ...string) error { diff --git a/pkg/muxer/tcp/matcher_test.go b/pkg/muxer/tcp/matcher_test.go index dd6aac008..e5e265fe7 100644 --- a/pkg/muxer/tcp/matcher_test.go +++ b/pkg/muxer/tcp/matcher_test.go @@ -133,6 +133,12 @@ func Test_HostSNI(t *testing.T) { serverName: "foo.example.com", match: true, }, + { + desc: "Matching hosts with subdomains with _", + rule: "HostSNI(`foo_bar.example.com`)", + serverName: "foo_bar.example.com", + match: true, + }, } for _, test := range testCases { diff --git a/pkg/muxer/tcp/matcher_v2_test.go b/pkg/muxer/tcp/matcher_v2_test.go index 254ac515e..3ff836ad6 100644 --- a/pkg/muxer/tcp/matcher_v2_test.go +++ b/pkg/muxer/tcp/matcher_v2_test.go @@ -690,6 +690,11 @@ func Test_HostSNIV2(t *testing.T) { ruleHosts: []string{"foo.bar"}, serverName: "foo.bar", }, + { + desc: "Matching hosts with subdomains with _", + ruleHosts: []string{"foo_bar.example.com"}, + serverName: "foo_bar.example.com", + }, { desc: "Matching IPv4", ruleHosts: []string{"127.0.0.1"}, diff --git a/pkg/proxy/httputil/proxy.go b/pkg/proxy/httputil/proxy.go index 11213952a..7d4c52c9d 100644 --- a/pkg/proxy/httputil/proxy.go +++ b/pkg/proxy/httputil/proxy.go @@ -2,6 +2,7 @@ package httputil import ( "context" + "crypto/tls" "errors" "io" stdlog "log" @@ -112,7 +113,13 @@ func ErrorHandlerWithContext(ctx context.Context, w http.ResponseWriter, err err statusCode := ComputeStatusCode(err) logger := log.Ctx(ctx) - logger.Debug().Err(err).Msgf("%d %s", statusCode, statusText(statusCode)) + + // Log the error with error level if it is a TLS error related to configuration. + if isTLSConfigError(err) { + logger.Error().Err(err).Msgf("%d %s", statusCode, statusText(statusCode)) + } else { + logger.Debug().Err(err).Msgf("%d %s", statusCode, statusText(statusCode)) + } w.WriteHeader(statusCode) if _, werr := w.Write([]byte(statusText(statusCode))); werr != nil { @@ -127,6 +134,22 @@ func statusText(statusCode int) string { return http.StatusText(statusCode) } +// isTLSConfigError returns true if the error is a TLS error which is related to configuration. +// We assume that if the error is a tls.RecordHeaderError or a tls.CertificateVerificationError, +// it is related to configuration, because the client should not send a TLS request to a non-TLS server, +// and the client configuration should allow to verify the server certificate. +func isTLSConfigError(err error) bool { + // tls.RecordHeaderError is returned when the client sends a TLS request to a non-TLS server. + var recordHeaderErr tls.RecordHeaderError + if errors.As(err, &recordHeaderErr) { + return true + } + + // tls.CertificateVerificationError is returned when the server certificate cannot be verified. + var certVerificationErr *tls.CertificateVerificationError + return errors.As(err, &certVerificationErr) +} + // ComputeStatusCode computes the HTTP status code according to the given error. func ComputeStatusCode(err error) int { switch { diff --git a/pkg/proxy/httputil/proxy_test.go b/pkg/proxy/httputil/proxy_test.go index 3d970fc53..93a27f4e0 100644 --- a/pkg/proxy/httputil/proxy_test.go +++ b/pkg/proxy/httputil/proxy_test.go @@ -1,12 +1,15 @@ package httputil import ( + "crypto/tls" + "errors" "net/http" "net/http/httptest" "net/url" "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "github.com/traefik/traefik/v3/pkg/testhelpers" ) @@ -100,3 +103,46 @@ func Test_directorBuilder(t *testing.T) { }) } } + +func Test_isTLSConfigError(t *testing.T) { + testCases := []struct { + desc string + err error + expected bool + }{ + { + desc: "nil", + }, + { + desc: "TLS ECHRejectionError", + err: &tls.ECHRejectionError{}, + }, + { + desc: "TLS AlertError", + err: tls.AlertError(0), + }, + { + desc: "Random error", + err: errors.New("random error"), + }, + { + desc: "TLS RecordHeaderError", + err: tls.RecordHeaderError{}, + expected: true, + }, + { + desc: "TLS CertificateVerificationError", + err: &tls.CertificateVerificationError{}, + expected: true, + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + actual := isTLSConfigError(test.err) + require.Equal(t, test.expected, actual) + }) + } +} diff --git a/script/gcg/traefik-bugfix.toml b/script/gcg/traefik-bugfix.toml index f8c76f1e8..1aa3ab605 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.4 +# example new bugfix v3.3.5 CurrentRef = "v3.3" -PreviousRef = "v3.3.3" +PreviousRef = "v3.3.4" BaseBranch = "v3.3" -FutureCurrentRefName = "v3.3.4" +FutureCurrentRefName = "v3.3.5" ThresholdPreviousRef = 10 ThresholdCurrentRef = 10 diff --git a/webui/src/components/_commons/Chips.vue b/webui/src/components/_commons/Chips.vue index 4108f6d27..531723a22 100644 --- a/webui/src/components/_commons/Chips.vue +++ b/webui/src/components/_commons/Chips.vue @@ -14,7 +14,7 @@