Merge branch v3.3 into master

This commit is contained in:
kevinpollet 2025-03-31 10:43:49 +02:00
commit ec38a0675f
No known key found for this signature in database
GPG Key ID: 0C9A5DDD1B292453
33 changed files with 411 additions and 178 deletions

View File

@ -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) ## [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) [All Commits](https://github.com/traefik/traefik/compare/v3.3.3...v3.3.4)

View File

@ -26,7 +26,6 @@ import (
"github.com/traefik/traefik/v3/cmd" "github.com/traefik/traefik/v3/cmd"
"github.com/traefik/traefik/v3/cmd/healthcheck" "github.com/traefik/traefik/v3/cmd/healthcheck"
cmdVersion "github.com/traefik/traefik/v3/cmd/version" cmdVersion "github.com/traefik/traefik/v3/cmd/version"
_ "github.com/traefik/traefik/v3/init"
tcli "github.com/traefik/traefik/v3/pkg/cli" tcli "github.com/traefik/traefik/v3/pkg/cli"
"github.com/traefik/traefik/v3/pkg/collector" "github.com/traefik/traefik/v3/pkg/collector"
"github.com/traefik/traefik/v3/pkg/config/dynamic" "github.com/traefik/traefik/v3/pkg/config/dynamic"

View File

@ -14,8 +14,8 @@ RUN apk --no-cache --no-progress add \
ruby-json \ ruby-json \
zlib-dev zlib-dev
RUN gem install nokogiri --version 1.16.8 --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.7 --no-document -- --use-system-libraries RUN gem install html-proofer --version 5.0.10 --no-document -- --use-system-libraries
# After Ruby, some NodeJS YAY! # After Ruby, some NodeJS YAY!
RUN apk --no-cache --no-progress add \ RUN apk --no-cache --no-progress add \

View File

@ -4,25 +4,31 @@
Below is a non-exhaustive list of versions and their maintenance status: Below is a non-exhaustive list of versions and their maintenance status:
| Version | Release Date | Community Support | | Version | Release Date | Active Support | Security Support |
|---------|--------------|--------------------| |---------|--------------|--------------------|-------------------|
| 3.3 | Jan 06, 2025 | Yes | | 3.3 | Jan 06, 2025 | Yes | Yes |
| 3.2 | Oct 28, 2024 | Ended Jan 06, 2025 | | 3.2 | Oct 28, 2024 | Ended Jan 06, 2025 | No |
| 3.1 | Jul 15, 2024 | Ended Oct 28, 2024 | | 3.1 | Jul 15, 2024 | Ended Oct 28, 2024 | No |
| 3.0 | Apr 29, 2024 | Ended Jul 15, 2024 | | 3.0 | Apr 29, 2024 | Ended Jul 15, 2024 | No |
| 2.11 | Feb 12, 2024 | Ends Apr 29, 2025 | | 2.11 | Feb 12, 2024 | Ends Apr 29, 2025 | Ends Feb 01, 2026 |
| 2.10 | Apr 24, 2023 | Ended Feb 12, 2024 | | 2.10 | Apr 24, 2023 | Ended Feb 12, 2024 | No |
| 2.9 | Oct 03, 2022 | Ended Apr 24, 2023 | | 2.9 | Oct 03, 2022 | Ended Apr 24, 2023 | No |
| 2.8 | Jun 29, 2022 | Ended Oct 03, 2022 | | 2.8 | Jun 29, 2022 | Ended Oct 03, 2022 | No |
| 2.7 | May 24, 2022 | Ended Jun 29, 2022 | | 2.7 | May 24, 2022 | Ended Jun 29, 2022 | No |
| 2.6 | Jan 24, 2022 | Ended May 24, 2022 | | 2.6 | Jan 24, 2022 | Ended May 24, 2022 | No |
| 2.5 | Aug 17, 2021 | Ended Jan 24, 2022 | | 2.5 | Aug 17, 2021 | Ended Jan 24, 2022 | No |
| 2.4 | Jan 19, 2021 | Ended Aug 17, 2021 | | 2.4 | Jan 19, 2021 | Ended Aug 17, 2021 | No |
| 2.3 | Sep 23, 2020 | Ended Jan 19, 2021 | | 2.3 | Sep 23, 2020 | Ended Jan 19, 2021 | No |
| 2.2 | Mar 25, 2020 | Ended Sep 23, 2020 | | 2.2 | Mar 25, 2020 | Ended Sep 23, 2020 | No |
| 2.1 | Dec 11, 2019 | Ended Mar 25, 2020 | | 2.1 | Dec 11, 2019 | Ended Mar 25, 2020 | No |
| 2.0 | Sep 16, 2019 | Ended Dec 11, 2019 | | 2.0 | Sep 16, 2019 | Ended Dec 11, 2019 | No |
| 1.7 | Sep 24, 2018 | Ended Dec 31, 2021 | | 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. This page is maintained and updated periodically to reflect our roadmap and any decisions affecting the end of support for Traefik Proxy.

View File

@ -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, One way to achieve that, is to trigger a file notification,
for example, by using the `touch` command on the configuration file. 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? ## How Traefik is Storing and Serving TLS Certificates?
### Storing TLS Certificates ### Storing TLS Certificates

View File

@ -264,10 +264,10 @@ http:
### `encodings` ### `encodings`
_Optional, Default="zstd, br, gzip"_ _Optional, Default="gzip, br, zstd"_
`encodings` specifies the list of supported compression encodings. `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. The order of the list also sets the priority, the top entry has the highest priority.
```yaml tab="Docker & Swarm" ```yaml tab="Docker & Swarm"

View File

@ -12,8 +12,11 @@ Retrying until it Succeeds
TODO: add schema TODO: add schema
--> -->
The Retry middleware reissues requests a given number of times to a backend server if that server does not reply. The Retry middleware reissues requests a given number of times when it cannot contact the backend service.
As soon as the server answers, the middleware stops retrying, regardless of the response status. 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. The Retry middleware has an optional configuration to enable an exponential backoff.
## Configuration Examples ## Configuration Examples

View File

@ -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. 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. 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 ## v3.3 to v3.4
### Kubernetes CRD Provider ### Kubernetes CRD Provider

View File

@ -256,9 +256,7 @@ accessLog:
| `OriginDuration` | The time taken (in nanoseconds) by the origin server ('upstream') to return its response. | | `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. | | `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). | | `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. | | `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. | | `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. | | `RequestCount` | The number of requests received since the Traefik instance started. |
| `GzipRatio` | The response body compression ratio achieved. | | `GzipRatio` | The response body compression ratio achieved. |

22
go.mod
View File

@ -29,7 +29,7 @@ require (
github.com/golang/protobuf v1.5.4 github.com/golang/protobuf v1.5.4
github.com/google/go-github/v28 v28.1.1 github.com/google/go-github/v28 v28.1.1
github.com/gorilla/mux v1.8.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/consul/api v1.26.1
github.com/hashicorp/go-hclog v1.6.3 github.com/hashicorp/go-hclog v1.6.3
github.com/hashicorp/go-multierror v1.1.1 github.com/hashicorp/go-multierror v1.1.1
@ -75,7 +75,7 @@ require (
github.com/unrolled/render v1.0.2 github.com/unrolled/render v1.0.2
github.com/unrolled/secure v1.0.9 github.com/unrolled/secure v1.0.9
github.com/valyala/fasthttp v1.58.0 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/vulcand/predicate v1.2.0
github.com/yuin/gopher-lua v1.1.1 github.com/yuin/gopher-lua v1.1.1
go.opentelemetry.io/collector/pdata v1.10.0 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/sdk/metric v1.28.0
go.opentelemetry.io/otel/trace v1.32.0 go.opentelemetry.io/otel/trace v1.32.0
golang.org/x/mod v0.22.0 golang.org/x/mod v0.22.0
golang.org/x/net v0.33.0 golang.org/x/net v0.37.0
golang.org/x/sync v0.10.0 golang.org/x/sync v0.12.0
golang.org/x/sys v0.29.0 golang.org/x/sys v0.31.0
golang.org/x/text v0.21.0 golang.org/x/text v0.23.0
golang.org/x/time v0.8.0 golang.org/x/time v0.8.0
golang.org/x/tools v0.28.0 golang.org/x/tools v0.28.0
google.golang.org/grpc v1.67.1 google.golang.org/grpc v1.67.1
@ -210,8 +210,8 @@ require (
github.com/goccy/go-json v0.10.4 // indirect github.com/goccy/go-json v0.10.4 // indirect
github.com/gofrs/flock v0.12.1 // indirect github.com/gofrs/flock v0.12.1 // indirect
github.com/gogo/protobuf v1.3.2 // indirect github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang-jwt/jwt/v4 v4.5.1 // indirect github.com/golang-jwt/jwt/v4 v4.5.2 // indirect
github.com/golang-jwt/jwt/v5 v5.2.1 // indirect github.com/golang-jwt/jwt/v5 v5.2.2 // indirect
github.com/google/gnostic-models v0.6.8 // indirect github.com/google/gnostic-models v0.6.8 // indirect
github.com/google/go-cmp v0.6.0 // indirect github.com/google/go-cmp v0.6.0 // indirect
github.com/google/go-querystring v1.1.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/nodion v0.1.0 // indirect
github.com/nrdcg/porkbun v0.4.0 // indirect github.com/nrdcg/porkbun v0.4.0 // indirect
github.com/nzdjb/go-metaname v1.0.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/onsi/ginkgo/v2 v2.20.2 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.1.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/jaeger v1.28.0 // indirect
go.opentelemetry.io/contrib/propagators/ot v1.28.0 // indirect go.opentelemetry.io/contrib/propagators/ot v1.28.0 // indirect
go.opentelemetry.io/proto/otlp v1.3.1 // 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/mock v0.4.0 // indirect
go.uber.org/multierr v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect
go.uber.org/ratelimit v0.3.0 // indirect go.uber.org/ratelimit v0.3.0 // indirect
go.uber.org/zap v1.26.0 // indirect go.uber.org/zap v1.26.0 // indirect
golang.org/x/arch v0.4.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/exp v0.0.0-20241210194714-1829a127f884 // indirect
golang.org/x/oauth2 v0.24.0 // 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/api v0.214.0 // indirect
google.golang.org/genproto v0.0.0-20241021214115-324edc3d5d38 // indirect google.golang.org/genproto v0.0.0-20241021214115-324edc3d5d38 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20241118233622-e639e219e697 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20241118233622-e639e219e697 // indirect

46
go.sum
View File

@ -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.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.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.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.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI=
github.com/golang-jwt/jwt/v4 v4.5.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang-jwt/jwt/v4 v4.5.2/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.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8=
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= 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/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/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= 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/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.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.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.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= 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 h1:C1GPyPJrOlJlIrcaBBiBpDsqZena2Ks8spa5xZqr1XQ=
github.com/gravitational/trace v1.1.16-0.20220114165159-14a9a7dd6aaf/go.mod h1:zXqxTI6jXDdKnlf8s+nT+3c8LrwUEy3yNpO4XJL90lA= 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= 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.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.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
github.com/onsi/ginkgo v1.16.2/go.mod h1:CObGmKUOKaSC0RjmoAK7tKyn4Azo5P2IWuoMnvwxz1E= 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.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.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.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c=
github.com/onsi/ginkgo/v2 v2.20.2 h1:7NVCeyIWROIAheY21RLS+3j2bb52W0W82tkberYytp4= 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/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 h1:VMDTHWYXakXJtZqPYn0As/h4eB0c4imvyru6mIp+o60=
github.com/volcengine/volc-sdk-golang v1.0.189/go.mod h1:u0VtPvlXWpXDTmc9IHkaW1q+5Jjwus4oAqRhNMDRInE= 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.3 h1:CPWVPfW4hVZXzwwiQzpFidbnJKpahjPHezM+7TkZRNw=
github.com/vulcand/oxy/v2 v2.0.0/go.mod h1:uIAz3sYafO7i+V3SC8oDlMn/lt1i9aWcyXuXqVswKzE= 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 h1:uFsW1gcnnR7R+QTID+FVcs0sSYlIGntoGOTb3rQJt50=
github.com/vulcand/predicate v1.2.0/go.mod h1:VipoNYXny6c8N381zGUWkjuuNHiRbeAZhE7Qm9c+2GA= 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= 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.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.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.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.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.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 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= 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.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= 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.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= 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-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-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/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.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= 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.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c=
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= 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-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-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/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-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.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.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 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-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-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/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.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.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.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 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-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-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 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.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= 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.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg= golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y=
golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek= 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.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.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/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.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.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.15.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.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= 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-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-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=

View File

@ -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")
}

View File

@ -190,7 +190,7 @@ type Compress struct {
} }
func (c *Compress) SetDefaults() { func (c *Compress) SetDefaults() {
c.Encodings = []string{"zstd", "br", "gzip"} c.Encodings = []string{"gzip", "br", "zstd"}
} }
// +k8s:deepcopy-gen=true // +k8s:deepcopy-gen=true

View File

@ -23,57 +23,44 @@ type Encoding struct {
Weight float64 Weight float64
} }
func getCompressionEncoding(acceptEncoding []string, defaultEncoding string, supportedEncodings []string) string { func (c *compress) getCompressionEncoding(acceptEncoding []string) string {
if defaultEncoding == "" { // 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.
if slices.Contains(supportedEncodings, brotliName) { // https://datatracker.ietf.org/doc/html/rfc9110#name-accept-encoding
// Keeps the pre-existing default inside Traefik if brotli is a supported encoding. if len(acceptEncoding) == 1 && acceptEncoding[0] == "" {
defaultEncoding = brotliName
} else if len(supportedEncodings) > 0 {
// Otherwise use the first supported encoding.
defaultEncoding = supportedEncodings[0]
}
}
encodings, hasWeight := parseAcceptEncoding(acceptEncoding, supportedEncodings)
if hasWeight {
if len(encodings) == 0 {
return identityName return identityName
} }
encoding := encodings[0] acceptableEncodings := parseAcceptableEncodings(acceptEncoding, c.supportedEncodings)
if encoding.Type == identityName && encoding.Weight == 0 { // 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 return notAcceptable
} }
if encoding.Type == wildcardName && encoding.Weight == 0 { slices.SortFunc(acceptableEncodings, func(a, b Encoding) int {
return notAcceptable 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]
} }
if encoding.Type == wildcardName { return c.defaultEncoding
return defaultEncoding
} }
return encoding.Type return acceptableEncodings[0].Type
}
for _, dt := range supportedEncodings {
if slices.ContainsFunc(encodings, func(e Encoding) bool { return e.Type == dt }) {
return dt
}
}
if slices.ContainsFunc(encodings, func(e Encoding) bool { return e.Type == wildcardName }) {
return defaultEncoding
}
return identityName
} }
func parseAcceptEncoding(acceptEncoding, supportedEncodings []string) ([]Encoding, bool) { func parseAcceptableEncodings(acceptEncoding []string, supportedEncodings map[string]int) []Encoding {
var encodings []Encoding var encodings []Encoding
var hasWeight bool
for _, line := range acceptEncoding { for _, line := range acceptEncoding {
for _, item := range strings.Split(strings.ReplaceAll(line, " ", ""), ",") { for _, item := range strings.Split(strings.ReplaceAll(line, " ", ""), ",") {
@ -82,9 +69,7 @@ func parseAcceptEncoding(acceptEncoding, supportedEncodings []string) ([]Encodin
continue continue
} }
if !slices.Contains(supportedEncodings, parsed[0]) && if _, ok := supportedEncodings[parsed[0]]; !ok {
parsed[0] != identityName &&
parsed[0] != wildcardName {
continue continue
} }
@ -94,8 +79,13 @@ func parseAcceptEncoding(acceptEncoding, supportedEncodings []string) ([]Encodin
if len(parsed) > 1 && strings.HasPrefix(parsed[1], "q=") { if len(parsed) > 1 && strings.HasPrefix(parsed[1], "q=") {
w, _ := strconv.ParseFloat(strings.TrimPrefix(parsed[1], "q="), 64) 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 weight = w
hasWeight = true
} }
encodings = append(encodings, Encoding{ encodings = append(encodings, Encoding{
@ -105,9 +95,5 @@ func parseAcceptEncoding(acceptEncoding, supportedEncodings []string) ([]Encodin
} }
} }
slices.SortFunc(encodings, func(a, b Encoding) int { return encodings
return cmp.Compare(b.Weight, a.Weight)
})
return encodings, hasWeight
} }

View File

@ -1,9 +1,12 @@
package compress package compress
import ( import (
"context"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/traefik/traefik/v3/pkg/config/dynamic"
) )
func Test_getCompressionEncoding(t *testing.T) { func Test_getCompressionEncoding(t *testing.T) {
@ -15,14 +18,19 @@ func Test_getCompressionEncoding(t *testing.T) {
expected string expected string
}{ }{
{ {
desc: "br > gzip (no weight)", desc: "Empty Accept-Encoding",
acceptEncoding: []string{"gzip, br"}, acceptEncoding: []string{""},
expected: brotliName, expected: identityName,
}, },
{ {
desc: "zstd > br > gzip (no weight)", desc: "gzip > br (no weight)",
acceptEncoding: []string{"zstd, gzip, br"}, acceptEncoding: []string{"gzip, br"},
expected: zstdName, expected: gzipName,
},
{
desc: "gzip > br > zstd (no weight)",
acceptEncoding: []string{"gzip, br, zstd"},
expected: gzipName,
}, },
{ {
desc: "known compression encoding (no weight)", desc: "known compression encoding (no weight)",
@ -32,24 +40,34 @@ func Test_getCompressionEncoding(t *testing.T) {
{ {
desc: "unknown compression encoding (no weight), no encoding", desc: "unknown compression encoding (no weight), no encoding",
acceptEncoding: []string{"compress, rar"}, acceptEncoding: []string{"compress, rar"},
expected: identityName, expected: notAcceptable,
}, },
{ {
desc: "wildcard return the default compression encoding", desc: "wildcard returns the default compression encoding",
acceptEncoding: []string{"*"}, acceptEncoding: []string{"*"},
expected: gzipName,
},
{
desc: "wildcard returns the custom default compression encoding",
acceptEncoding: []string{"*"},
defaultEncoding: brotliName,
expected: brotliName, expected: brotliName,
}, },
{
desc: "wildcard return the custom default compression encoding",
acceptEncoding: []string{"*"},
defaultEncoding: "foo",
expected: "foo",
},
{ {
desc: "follows weight", desc: "follows weight",
acceptEncoding: []string{"br;q=0.8, gzip;q=1.0, *;q=0.1"}, acceptEncoding: []string{"br;q=0.8, gzip;q=1.0, *;q=0.1"},
expected: gzipName, 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", desc: "ignore unknown compression encoding",
acceptEncoding: []string{"compress;q=1.0, gzip;q=0.5"}, acceptEncoding: []string{"compress;q=1.0, gzip;q=0.5"},
@ -93,6 +111,33 @@ func Test_getCompressionEncoding(t *testing.T) {
supportedEncodings: []string{gzipName, brotliName}, supportedEncodings: []string{gzipName, brotliName},
expected: gzipName, 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 { for _, test := range testCases {
@ -103,7 +148,18 @@ func Test_getCompressionEncoding(t *testing.T) {
test.supportedEncodings = defaultSupportedEncodings 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) assert.Equal(t, test.expected, encoding)
}) })
@ -147,7 +203,6 @@ func Test_parseAcceptEncoding(t *testing.T) {
{Type: zstdName, Weight: 1}, {Type: zstdName, Weight: 1},
{Type: gzipName, Weight: 1}, {Type: gzipName, Weight: 1},
{Type: brotliName, Weight: 1}, {Type: brotliName, Weight: 1},
{Type: wildcardName, Weight: 0},
}, },
assertWeight: assert.True, assertWeight: assert.True,
}, },
@ -157,7 +212,6 @@ func Test_parseAcceptEncoding(t *testing.T) {
supportedEncodings: []string{zstdName}, supportedEncodings: []string{zstdName},
expected: []Encoding{ expected: []Encoding{
{Type: zstdName, Weight: 1}, {Type: zstdName, Weight: 1},
{Type: wildcardName, Weight: 0},
}, },
assertWeight: assert.True, assertWeight: assert.True,
}, },
@ -188,7 +242,6 @@ func Test_parseAcceptEncoding(t *testing.T) {
expected: []Encoding{ expected: []Encoding{
{Type: gzipName, Weight: 1}, {Type: gzipName, Weight: 1},
{Type: identityName, Weight: 0.5}, {Type: identityName, Weight: 0.5},
{Type: wildcardName, Weight: 0},
}, },
assertWeight: assert.True, assertWeight: assert.True,
}, },
@ -198,7 +251,6 @@ func Test_parseAcceptEncoding(t *testing.T) {
supportedEncodings: []string{"br"}, supportedEncodings: []string{"br"},
expected: []Encoding{ expected: []Encoding{
{Type: identityName, Weight: 0.5}, {Type: identityName, Weight: 0.5},
{Type: wildcardName, Weight: 0},
}, },
assertWeight: assert.True, assertWeight: assert.True,
}, },
@ -212,10 +264,11 @@ func Test_parseAcceptEncoding(t *testing.T) {
test.supportedEncodings = defaultSupportedEncodings 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) assert.Equal(t, test.expected, aes)
test.assertWeight(t, hasWeight)
}) })
} }
} }

View File

@ -22,7 +22,7 @@ const typeName = "Compress"
// See https://github.com/klauspost/compress/blob/9559b037e79ad673c71f6ef7c732c00949014cd2/gzhttp/compress.go#L47. // See https://github.com/klauspost/compress/blob/9559b037e79ad673c71f6ef7c732c00949014cd2/gzhttp/compress.go#L47.
const defaultMinSize = 1024 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. // Compress is a middleware that allows to compress the response.
type compress struct { type compress struct {
@ -33,6 +33,8 @@ type compress struct {
minSize int minSize int
encodings []string encodings []string
defaultEncoding string defaultEncoding string
// supportedEncodings is a map of supported encodings and their priority.
supportedEncodings map[string]int
brotliHandler http.Handler brotliHandler http.Handler
gzipHandler http.Handler gzipHandler http.Handler
@ -92,6 +94,7 @@ func New(ctx context.Context, next http.Handler, conf dynamic.Compress, name str
minSize: minSize, minSize: minSize,
encodings: conf.Encodings, encodings: conf.Encodings,
defaultEncoding: conf.DefaultEncoding, defaultEncoding: conf.DefaultEncoding,
supportedEncodings: buildSupportedEncodings(conf.Encodings),
} }
var err error var err error
@ -114,6 +117,19 @@ func New(ctx context.Context, next http.Handler, conf dynamic.Compress, name str
return c, nil 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) { func (c *compress) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
logger := middlewares.GetLogger(req.Context(), c.name, typeName) logger := middlewares.GetLogger(req.Context(), c.name, typeName)
@ -149,7 +165,7 @@ func (c *compress) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
return 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) { func (c *compress) chooseHandler(typ string, rw http.ResponseWriter, req *http.Request) {

View File

@ -39,9 +39,14 @@ func TestNegotiation(t *testing.T) {
expEncoding: "", 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", desc: "accept any header",
acceptEncHeader: "*", acceptEncHeader: "*",
expEncoding: brotliName, expEncoding: "",
}, },
{ {
desc: "gzip accept header", desc: "gzip accept header",
@ -66,7 +71,7 @@ func TestNegotiation(t *testing.T) {
{ {
desc: "multi accept header list, prefer br", desc: "multi accept header list, prefer br",
acceptEncHeader: "gzip, br", acceptEncHeader: "gzip, br",
expEncoding: brotliName, expEncoding: gzipName,
}, },
{ {
desc: "zstd accept header", 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", acceptEncHeader: "zstd;q=0.9, br;q=0.8, gzip;q=0.6",
expEncoding: zstdName, 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", desc: "multi accept header, prefer gzip",
acceptEncHeader: "gzip;q=1.0, br;q=0.8, zstd;q=0.7", acceptEncHeader: "gzip;q=1.0, br;q=0.8, zstd;q=0.7",
expEncoding: gzipName, expEncoding: gzipName,
}, },
{ {
desc: "multi accept header list, prefer zstd", desc: "multi accept header list, prefer gzip",
acceptEncHeader: "gzip, br, zstd", acceptEncHeader: "gzip, br, zstd",
expEncoding: zstdName, expEncoding: gzipName,
}, },
} }
@ -190,6 +200,28 @@ func TestShouldNotCompressWhenNoAcceptEncodingHeader(t *testing.T) {
assert.EqualValues(t, rw.Body.Bytes(), fakeBody) 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) { func TestShouldNotCompressWhenIdentityAcceptEncodingHeader(t *testing.T) {
req := testhelpers.MustNewRequest(http.MethodGet, "http://localhost", nil) req := testhelpers.MustNewRequest(http.MethodGet, "http://localhost", nil)
req.Header.Set(acceptEncodingHeader, "identity") req.Header.Set(acceptEncodingHeader, "identity")

View File

@ -233,8 +233,12 @@ func (r *responseWriter) Write(p []byte) (int, error) {
// Disable compression according to user wishes in excludedContentTypes or includedContentTypes. // Disable compression according to user wishes in excludedContentTypes or includedContentTypes.
if ct := r.rw.Header().Get(contentType); ct != "" { if ct := r.rw.Header().Get(contentType); ct != "" {
mediaType, params, err := mime.ParseMediaType(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 { 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 { if len(r.includedContentTypes) > 0 {

View File

@ -577,6 +577,11 @@ func Test_ExcludedContentTypes(t *testing.T) {
contentType: "", contentType: "",
expCompression: true, expCompression: true,
}, },
{
desc: "MIME malformed",
contentType: "application/json;charset=UTF-8;charset=utf-8",
expCompression: false,
},
{ {
desc: "MIME match", desc: "MIME match",
contentType: "application/json", contentType: "application/json",
@ -687,6 +692,11 @@ func Test_IncludedContentTypes(t *testing.T) {
contentType: "", contentType: "",
expCompression: true, expCompression: true,
}, },
{
desc: "MIME malformed",
contentType: "application/json;charset=UTF-8;charset=utf-8",
expCompression: false,
},
{ {
desc: "MIME match", desc: "MIME match",
contentType: "application/json", contentType: "application/json",

View File

@ -67,7 +67,7 @@ func clientIP(tree *matchersTree, clientIP ...string) error {
return nil 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. // hostSNI checks if the SNI Host of the connection match the matcher host.
func hostSNI(tree *matchersTree, hosts ...string) error { func hostSNI(tree *matchersTree, hosts ...string) error {

View File

@ -133,6 +133,12 @@ func Test_HostSNI(t *testing.T) {
serverName: "foo.example.com", serverName: "foo.example.com",
match: true, 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 { for _, test := range testCases {

View File

@ -690,6 +690,11 @@ func Test_HostSNIV2(t *testing.T) {
ruleHosts: []string{"foo.bar"}, ruleHosts: []string{"foo.bar"},
serverName: "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", desc: "Matching IPv4",
ruleHosts: []string{"127.0.0.1"}, ruleHosts: []string{"127.0.0.1"},

View File

@ -2,6 +2,7 @@ package httputil
import ( import (
"context" "context"
"crypto/tls"
"errors" "errors"
"io" "io"
stdlog "log" stdlog "log"
@ -112,7 +113,13 @@ func ErrorHandlerWithContext(ctx context.Context, w http.ResponseWriter, err err
statusCode := ComputeStatusCode(err) statusCode := ComputeStatusCode(err)
logger := log.Ctx(ctx) logger := log.Ctx(ctx)
// 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)) logger.Debug().Err(err).Msgf("%d %s", statusCode, statusText(statusCode))
}
w.WriteHeader(statusCode) w.WriteHeader(statusCode)
if _, werr := w.Write([]byte(statusText(statusCode))); werr != nil { if _, werr := w.Write([]byte(statusText(statusCode))); werr != nil {
@ -127,6 +134,22 @@ func statusText(statusCode int) string {
return http.StatusText(statusCode) 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. // ComputeStatusCode computes the HTTP status code according to the given error.
func ComputeStatusCode(err error) int { func ComputeStatusCode(err error) int {
switch { switch {

View File

@ -1,12 +1,15 @@
package httputil package httputil
import ( import (
"crypto/tls"
"errors"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"net/url" "net/url"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/traefik/traefik/v3/pkg/testhelpers" "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)
})
}
}

View File

@ -4,11 +4,11 @@ RepositoryName = "traefik"
OutputType = "file" OutputType = "file"
FileName = "traefik_changelog.md" FileName = "traefik_changelog.md"
# example new bugfix v3.3.4 # example new bugfix v3.3.5
CurrentRef = "v3.3" CurrentRef = "v3.3"
PreviousRef = "v3.3.3" PreviousRef = "v3.3.4"
BaseBranch = "v3.3" BaseBranch = "v3.3"
FutureCurrentRefName = "v3.3.4" FutureCurrentRefName = "v3.3.5"
ThresholdPreviousRef = 10 ThresholdPreviousRef = 10
ThresholdCurrentRef = 10 ThresholdCurrentRef = 10

View File

@ -14,7 +14,7 @@
<script> <script>
export default { export default {
props: { props: {
dense: Boolean, dense: { type: Boolean, default: undefined },
classNames: Array[String], classNames: Array[String],
list: Array[Object] list: Array[Object]
} }

View File

@ -138,7 +138,7 @@ export default {
}, },
props: { props: {
data: { type: Object, default: undefined, required: false }, data: { type: Object, default: undefined, required: false },
dense: Boolean dense: { type: Boolean, default: undefined }
}, },
computed: { computed: {
isDense () { isDense () {

View File

@ -1634,7 +1634,7 @@ export default defineComponent({
}, },
props: { props: {
data: Array[Object], data: Array[Object],
dense: Boolean dense: { type: Boolean, default: undefined }
}, },
computed: { computed: {
protocol () { protocol () {

View File

@ -71,7 +71,7 @@ export default {
name: 'PanelMirroringServices', name: 'PanelMirroringServices',
props: { props: {
data: { type: Object, default: undefined, required: false }, data: { type: Object, default: undefined, required: false },
dense: Boolean dense: { type: Boolean, default: undefined }
}, },
computed: { computed: {
isDense () { isDense () {

View File

@ -103,8 +103,8 @@ export default defineComponent({
}, },
props: { props: {
data: { type: Object, default: undefined, required: false }, data: { type: Object, default: undefined, required: false },
dense: Boolean, dense: { type: Boolean, default: undefined },
hasStatus: Boolean hasStatus: { type: Boolean, default: undefined }
}, },
computed: { computed: {
isDense () { isDense () {

View File

@ -156,7 +156,7 @@ export default defineComponent({
}, },
props: { props: {
data: { type: Object, default: undefined, required: false }, data: { type: Object, default: undefined, required: false },
dense: Boolean dense: { type: Boolean, default: undefined }
}, },
computed: { computed: {
isDense () { isDense () {

View File

@ -67,7 +67,7 @@ export default defineComponent({
components: {}, components: {},
props: { props: {
data: { type: Object, default: undefined, required: false }, data: { type: Object, default: undefined, required: false },
dense: Boolean dense: { type: Boolean, default: undefined }
}, },
computed: { computed: {
isDense () { isDense () {

View File

@ -56,7 +56,7 @@ export default defineComponent({
}, },
props: { props: {
sticky: { type: Object, default: undefined, required: false }, sticky: { type: Object, default: undefined, required: false },
dense: Boolean dense: { type: Boolean, default: undefined }
} }
}) })
</script> </script>