mirror of
https://github.com/traefik/traefik.git
synced 2026-06-08 16:22:52 -04:00
Merge branch v3.7 into master
Some checks failed
CodeQL / Analyze (push) Has been cancelled
Build and Publish Documentation / Doc Process (push) Has been cancelled
Build experimental image on branch / build-webui (push) Has been cancelled
Build experimental image on branch / Build experimental image on branch (push) Has been cancelled
Some checks failed
CodeQL / Analyze (push) Has been cancelled
Build and Publish Documentation / Doc Process (push) Has been cancelled
Build experimental image on branch / build-webui (push) Has been cancelled
Build experimental image on branch / Build experimental image on branch (push) Has been cancelled
This commit is contained in:
commit
8773d7ead4
57 changed files with 1383 additions and 970 deletions
|
|
@ -348,6 +348,9 @@ linters:
|
|||
text: 'appendAssign: append result not assigned to the same slice'
|
||||
linters:
|
||||
- gocritic
|
||||
- path: pkg/server/conncontext.go
|
||||
linters:
|
||||
- fatcontext
|
||||
paths:
|
||||
- pkg/provider/kubernetes/crd/generated/
|
||||
|
||||
|
|
|
|||
119
CHANGELOG.md
119
CHANGELOG.md
|
|
@ -1,3 +1,122 @@
|
|||
## [v3.7.4](https://github.com/traefik/traefik/tree/v3.7.4) (2026-06-05)
|
||||
[All Commits](https://github.com/traefik/traefik/compare/v3.7.3...v3.7.4)
|
||||
|
||||
**Bug fixes:**
|
||||
- **[middleware]** Fix redis write timeout option configuration ([#13273](https://github.com/traefik/traefik/pull/13273) @bzyy1024)
|
||||
- **[webui]** Bump react-router and jsdom ([#13301](https://github.com/traefik/traefik/pull/13301) @gndz07)
|
||||
- **[k8s/gatewayapi]** Fix BackendTLSPolicy status update ([#13306](https://github.com/traefik/traefik/pull/13306) @AnatoleLucet)
|
||||
- **[http3]** Bump github.com/quic-go/quic-go to v0.59.1 ([#13300](https://github.com/traefik/traefik/pull/13300) @rtribotte)
|
||||
- **[webui]** Bump axios to v1.17.0 ([#13299](https://github.com/traefik/traefik/pull/13299) @gndz07)
|
||||
- **[tls]** Fix snicheck with keepalive ([#13305](https://github.com/traefik/traefik/pull/13305) @juliens)
|
||||
|
||||
## [v3.6.20](https://github.com/traefik/traefik/tree/v3.6.20) (2026-06-05)
|
||||
[All Commits](https://github.com/traefik/traefik/compare/v3.6.19...v3.6.20)
|
||||
|
||||
**Bug fixes:**
|
||||
- **[middleware]** Fix redis write timeout option configuration ([#13273](https://github.com/traefik/traefik/pull/13273) @bzyy1024)
|
||||
- **[webui]** Bump react-router and jsdom ([#13301](https://github.com/traefik/traefik/pull/13301) @gndz07)
|
||||
- **[k8s/gatewayapi]** Fix BackendTLSPolicy status update ([#13306](https://github.com/traefik/traefik/pull/13306) @AnatoleLucet)
|
||||
- **[http3]** Bump github.com/quic-go/quic-go to v0.59.1 ([#13300](https://github.com/traefik/traefik/pull/13300) @rtribotte)
|
||||
- **[webui]** Bump axios to v1.17.0 ([#13299](https://github.com/traefik/traefik/pull/13299) @gndz07)
|
||||
- **[tls]** Fix snicheck with keepalive ([#13305](https://github.com/traefik/traefik/pull/13305) @juliens)
|
||||
|
||||
## [v2.11.49](https://github.com/traefik/traefik/tree/v2.11.49) (2026-06-05)
|
||||
[All Commits](https://github.com/traefik/traefik/compare/v2.11.48...v2.11.49)
|
||||
|
||||
**Bug fixes:**
|
||||
- **[http3]** Bump github.com/quic-go/quic-go to v0.59.1 ([#13300](https://github.com/traefik/traefik/pull/13300) @rtribotte)
|
||||
- **[webui]** Bump axios to v1.17.0 ([#13299](https://github.com/traefik/traefik/pull/13299) @gndz07)
|
||||
- **[tls]** Fix snicheck with keepalive ([#13305](https://github.com/traefik/traefik/pull/13305) @juliens)
|
||||
|
||||
## [v3.7.3](https://github.com/traefik/traefik/tree/v3.7.3) (2026-06-04)
|
||||
[All Commits](https://github.com/traefik/traefik/compare/v3.7.1...v3.7.3)
|
||||
|
||||
**Bug fixes:**
|
||||
- **[tls]** Compute resolved tlsOptions after applying models ([#13291](https://github.com/traefik/traefik/pull/13291) @rtribotte)
|
||||
- **[webui, tcp]** Fix TCP router service resolution in dashboard flow diagram ([#13155](https://github.com/traefik/traefik/pull/13155) @aliamerj)
|
||||
- **[k8s/ingress-nginx]** Trim quotes from proxy_set_header header name ([#13203](https://github.com/traefik/traefik/pull/13203) @crisbal)
|
||||
- **[accesslogs]** Escape double quotes in quoted log fields ([#13180](https://github.com/traefik/traefik/pull/13180) @KaanSimsek)
|
||||
- **[k8s/gatewayapi]** Escape exact gRPC method matches ([#13201](https://github.com/traefik/traefik/pull/13201) @nickmnt)
|
||||
- **[logs, middleware]** Allow query parameters to be dropped from RequestPath in access log ([#13091](https://github.com/traefik/traefik/pull/13091) @calinelson)
|
||||
- **[k8s/ingress-nginx]** Clear Ssl-Client-* headers when no client certificate is present ([#13260](https://github.com/traefik/traefik/pull/13260) @gndz07)
|
||||
- **[k8s/gatewayapi]** Bump github.com/moby/spdystream to v0.5.1 ([#13252](https://github.com/traefik/traefik/pull/13252) @kevinpollet)
|
||||
- **[file]** Improve file provider behavior regarding dangling symlinks ([#12449](https://github.com/traefik/traefik/pull/12449) @fh-yuxiao-zeng)
|
||||
- **[server]** Bump github.com/bytedance/sonic to v1.15.1 ([#13254](https://github.com/traefik/traefik/pull/13254) @kevinpollet)
|
||||
- **[middleware, authentication]** Add error on basic auth build if users is empty ([#13195](https://github.com/traefik/traefik/pull/13195) @rtribotte)
|
||||
- **[k8s/ingress]** Avoid ingress path matcher injection and backport 11d251415 ([#13227](https://github.com/traefik/traefik/pull/13227) @rtribotte)
|
||||
- **[server]** Move snicheck to ctx instead of simulated routing ([#13214](https://github.com/traefik/traefik/pull/13214) @juliens)
|
||||
- **[middleware]** Reject requests with different paths after StripPrefix and StripPrefixRegex normalisation ([#13215](https://github.com/traefik/traefik/pull/13215) @rtribotte)
|
||||
- **[server]** Bump golang.org/x/net to v0.55.0 ([#13251](https://github.com/traefik/traefik/pull/13251) @kevinpollet)
|
||||
- **[k8s/gatewayapi]** Change default values and expose configuration for Kubernetes client QPS and Burst ([#13277](https://github.com/traefik/traefik/pull/13277) @kevinpollet)
|
||||
- **[server]** Bump golang.org/x/crypto to v0.52.0 ([#13276](https://github.com/traefik/traefik/pull/13276) @rtribotte)
|
||||
|
||||
**Documentation:**
|
||||
- **[k8s]** Document new chart behavior on Gateway API ([#13167](https://github.com/traefik/traefik/pull/13167) @mloiseleur)
|
||||
- **[file]** Replace generated File routing reference page ([#13170](https://github.com/traefik/traefik/pull/13170) @sheddy-traefik)
|
||||
- **[k8s/crd]** Fix typo in accesslogs field name ([#13177](https://github.com/traefik/traefik/pull/13177) @PlayMTL)
|
||||
- **[k8s/ingress-nginx]** Surface the Ingress status race condition during NGINX coexistence ([#13205](https://github.com/traefik/traefik/pull/13205) @emilevauge)
|
||||
- Polish grammar in migration guides ([#13174](https://github.com/traefik/traefik/pull/13174) @quyentonndbs)
|
||||
- **[middleware]** Remove whitespace in HTML tag ([#13160](https://github.com/traefik/traefik/pull/13160) @marbon87)
|
||||
- Add @LBF38 as a current maintainer ([#13225](https://github.com/traefik/traefik/pull/13225) @emilevauge)
|
||||
- Add ingressClassName to Kubernetes CRD provider migration guide ([#13248](https://github.com/traefik/traefik/pull/13248) @kevinpollet)
|
||||
- **[k8s/ingress-nginx]** Add nginx.ingress.kubernetes.io/enable-global-auth to the list of supported annotations ([#13219](https://github.com/traefik/traefik/pull/13219) @filip2mac)
|
||||
- **[k8s/ingress-nginx]** Capitalize NGINX in kubernetesIngressNGINX ([#13236](https://github.com/traefik/traefik/pull/13236) @smellems)
|
||||
|
||||
## [v3.6.19](https://github.com/traefik/traefik/tree/v3.6.19) (2026-06-04)
|
||||
[All Commits](https://github.com/traefik/traefik/compare/v3.6.17...v3.6.19)
|
||||
|
||||
**Bug fixes:**
|
||||
- **[tls]** Compute resolved tlsOptions after applying models ([#13291](https://github.com/traefik/traefik/pull/13291) @rtribotte)
|
||||
- **[accesslogs]** Escape double quotes in quoted log fields ([#13180](https://github.com/traefik/traefik/pull/13180) @KaanSimsek)
|
||||
- **[k8s/gatewayapi]** Escape exact gRPC method matches ([#13201](https://github.com/traefik/traefik/pull/13201) @nickmnt)
|
||||
- **[logs, middleware]** Allow query parameters to be dropped from RequestPath in access log ([#13091](https://github.com/traefik/traefik/pull/13091) @calinelson)
|
||||
- **[k8s/gatewayapi]** Bump github.com/moby/spdystream to v0.5.1 ([#13252](https://github.com/traefik/traefik/pull/13252) @kevinpollet)
|
||||
- **[file]** Improve file provider behavior regarding dangling symlinks ([#12449](https://github.com/traefik/traefik/pull/12449) @fh-yuxiao-zeng)
|
||||
- **[server]** Bump github.com/bytedance/sonic to v1.15.1 ([#13254](https://github.com/traefik/traefik/pull/13254) @kevinpollet)
|
||||
- **[middleware, authentication]** Add error on basic auth build if users is empty ([#13195](https://github.com/traefik/traefik/pull/13195) @rtribotte)
|
||||
- **[k8s/ingress]** Avoid ingress path matcher injection and backport 11d251415 ([#13227](https://github.com/traefik/traefik/pull/13227) @rtribotte)
|
||||
- **[server]** Move snicheck to ctx instead of simulated routing ([#13214](https://github.com/traefik/traefik/pull/13214) @juliens)
|
||||
- **[middleware]** Reject requests with different paths after StripPrefix and StripPrefixRegex normalisation ([#13215](https://github.com/traefik/traefik/pull/13215) @rtribotte)
|
||||
- **[server]** Bump golang.org/x/net to v0.55.0 ([#13251](https://github.com/traefik/traefik/pull/13251) @kevinpollet)
|
||||
- **[k8s/gatewayapi]** Change default values and expose configuration for Kubernetes client QPS and Burst ([#13277](https://github.com/traefik/traefik/pull/13277) @kevinpollet)
|
||||
- **[server]** Bump golang.org/x/crypto to v0.52.0 ([#13276](https://github.com/traefik/traefik/pull/13276) @rtribotte)
|
||||
|
||||
**Documentation:**
|
||||
- **[file]** Replace generated File routing reference page ([#13170](https://github.com/traefik/traefik/pull/13170) @sheddy-traefik)
|
||||
- **[k8s/crd]** Fix typo in accesslogs field name ([#13177](https://github.com/traefik/traefik/pull/13177) @PlayMTL)
|
||||
- **[k8s/ingress-nginx]** Surface the Ingress status race condition during NGINX coexistence ([#13205](https://github.com/traefik/traefik/pull/13205) @emilevauge)
|
||||
- Polish grammar in migration guides ([#13174](https://github.com/traefik/traefik/pull/13174) @quyentonndbs)
|
||||
- **[middleware]** Remove whitespace in HTML tag ([#13160](https://github.com/traefik/traefik/pull/13160) @marbon87)
|
||||
- Add @LBF38 as a current maintainer ([#13225](https://github.com/traefik/traefik/pull/13225) @emilevauge)
|
||||
- **[k8s/ingress-nginx]** Capitalize NGINX in kubernetesIngressNGINX ([#13236](https://github.com/traefik/traefik/pull/13236) @smellems)
|
||||
|
||||
## [v2.11.48](https://github.com/traefik/traefik/tree/v2.11.48) (2026-06-04)
|
||||
[All Commits](https://github.com/traefik/traefik/compare/v2.11.46...v2.11.48)
|
||||
|
||||
**Bug fixes:**
|
||||
- **[tls]** Compute resolved tlsOptions after applying models ([#13291](https://github.com/traefik/traefik/pull/13291) @rtribotte)
|
||||
- **[middleware, authentication]** Add error on basic auth build if users is empty ([#13195](https://github.com/traefik/traefik/pull/13195) @rtribotte)
|
||||
- **[k8s/ingress]** Avoid ingress path matcher injection and backport 11d251415 ([#13227](https://github.com/traefik/traefik/pull/13227) @rtribotte)
|
||||
- **[server]** Move snicheck to ctx instead of simulated routing ([#13214](https://github.com/traefik/traefik/pull/13214) @juliens)
|
||||
- **[middleware]** Reject requests with different paths after StripPrefix and StripPrefixRegex normalisation ([#13215](https://github.com/traefik/traefik/pull/13215) @rtribotte)
|
||||
- **[server]** Bump golang.org/x/net to v0.55.0 ([#13251](https://github.com/traefik/traefik/pull/13251) @kevinpollet)
|
||||
- **[server]** Bump golang.org/x/crypto to v0.52.0 ([#13276](https://github.com/traefik/traefik/pull/13276) @rtribotte)
|
||||
-
|
||||
## [v3.7.2](https://github.com/traefik/traefik/tree/v3.7.2) (2026-06-03)
|
||||
[All Commits](https://github.com/traefik/traefik/compare/v3.7.1...v3.7.2)
|
||||
|
||||
Release canceled.
|
||||
|
||||
## [v3.6.18](https://github.com/traefik/traefik/tree/v3.6.18) (2026-06-03)
|
||||
[All Commits](https://github.com/traefik/traefik/compare/v3.6.17...v3.6.18)
|
||||
|
||||
Release canceled.
|
||||
|
||||
## [v2.11.47](https://github.com/traefik/traefik/tree/v2.11.47) (2026-06-03)
|
||||
[All Commits](https://github.com/traefik/traefik/compare/v2.11.46...v2.11.47)
|
||||
|
||||
Release canceled.
|
||||
|
||||
## [v3.7.1](https://github.com/traefik/traefik/tree/v3.7.1) (2026-05-11)
|
||||
[All Commits](https://github.com/traefik/traefik/compare/v3.7.0...v3.7.1)
|
||||
|
||||
|
|
|
|||
|
|
@ -246,7 +246,7 @@ helm repo update
|
|||
```bash
|
||||
helm upgrade --install traefik traefik/traefik \
|
||||
--namespace traefik --create-namespace \
|
||||
--set providers.kubernetesIngressNginx.enabled=true
|
||||
--set providers.kubernetesIngressNGINX.enabled=true
|
||||
```
|
||||
|
||||
Or using a [values file](https://github.com/traefik/traefik-helm-chart/blob/master/traefik/VALUES.md) for more configuration:
|
||||
|
|
@ -254,7 +254,7 @@ Or using a [values file](https://github.com/traefik/traefik-helm-chart/blob/mast
|
|||
```yaml tab="traefik-values.yaml"
|
||||
...
|
||||
providers:
|
||||
kubernetesIngressNginx:
|
||||
kubernetesIngressNGINX:
|
||||
enabled: true
|
||||
...
|
||||
```
|
||||
|
|
|
|||
|
|
@ -9,6 +9,43 @@ This guide provides detailed migration steps for upgrading between different Tra
|
|||
|
||||
---
|
||||
|
||||
## v3.7.3
|
||||
|
||||
### Kubernetes Gateway API Provider
|
||||
|
||||
Starting with `v3.7.3`, the QPS and Burst values of the Kubernetes client used by the Kubernetes Gateway API provider have been increased to `50` and `100` respectively (10x the default values of the Kubernetes client).
|
||||
|
||||
The Kubernetes Gateway API provider writes status updates intensively to comply with the Kubernetes Gateway API specification.
|
||||
This change helps avoid performance issues related to Kubernetes API rate limiting, which can increase the setup time when a new routing configuration is built.
|
||||
|
||||
These values are configurable through the [`kubernetesGateway.qps`](../reference/install-configuration/providers/kubernetes/kubernetes-gateway.md#opt-providers-kubernetesgateway-qps)
|
||||
and [`kubernetesGateway.burst`](../reference/install-configuration/providers/kubernetes/kubernetes-gateway.md#opt-providers-kubernetesgateway-burst) provider options.
|
||||
|
||||
### BasicAuth Middleware
|
||||
|
||||
From version `v3.7.3` onwards, the BasicAuth middleware requires a non-empty users configuration in order to be built successfully.
|
||||
Previously, the middleware would be built successfully but always return a 401 status code for any request.
|
||||
Now, an error occurs and any routers using it will be unmounted. For the same request, a 404 status code is served instead of a 401 status code.
|
||||
|
||||
### StripPrefix and StripPrefixRegex Middleware
|
||||
|
||||
From version `v3.7.3` onwards, the StripPrefix middleware and the StripPrefixRegex middleware reject requests (`400 Bad Request`)
|
||||
when stripping the configured prefix produces a path that differs from its normalised form
|
||||
(i.e. a path containing `.` or `..` segments that would be collapsed by normalisation).
|
||||
|
||||
This prevents the stripped path from being interpreted as a different resource by the upstream service.
|
||||
|
||||
Examples with a configured prefix of `/api`:
|
||||
|
||||
| Request path | Path after strip | Normalised path | Result |
|
||||
|--------------|------------------|-----------------|--------------|
|
||||
| `/api/foo` | `/foo` | `/foo` | `200` (sent) |
|
||||
| `/api/` | `/` | `/` | `200` (sent) |
|
||||
| `/api./foo` | `/./foo` | `/foo` | `400` |
|
||||
| `/api../foo` | `/../foo` | `/foo` | `400` |
|
||||
|
||||
---
|
||||
|
||||
## v3.7.1
|
||||
|
||||
### Kubernetes providers: `crossProviderNamespaces`
|
||||
|
|
@ -80,7 +117,7 @@ kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/downloa
|
|||
|
||||
### Kubernetes CRD Provider
|
||||
|
||||
To use the new options of the `retry` middleware with the Kubernetes CRD provider, you need to update your CRDs.
|
||||
To use the new options of the `retry` middleware or the new `ingressClassName` field with the Kubernetes CRD provider, you need to update your CRDs.
|
||||
|
||||
**Apply Updated CRDs:**
|
||||
|
||||
|
|
@ -107,6 +144,43 @@ Note: TLSOptions for `HostRegexp` matchers remains unsupported. Use wildcard `Ho
|
|||
|
||||
---
|
||||
|
||||
## v3.6.19
|
||||
|
||||
### Kubernetes Gateway API Provider
|
||||
|
||||
Starting with `v3.6.19`, the QPS and Burst values of the Kubernetes client used by the Kubernetes Gateway API provider have been increased to `50` and `100` respectively (10x the default values of the Kubernetes client).
|
||||
|
||||
The Kubernetes Gateway API provider writes status updates intensively to comply with the Kubernetes Gateway API specification.
|
||||
This change helps avoid performance issues related to Kubernetes API rate limiting, which can increase the setup time when a new routing configuration is built.
|
||||
|
||||
These values are configurable through the [`kubernetesGateway.qps`](../reference/install-configuration/providers/kubernetes/kubernetes-gateway.md#opt-providers-kubernetesgateway-qps)
|
||||
and [`kubernetesGateway.burst`](../reference/install-configuration/providers/kubernetes/kubernetes-gateway.md#opt-providers-kubernetesgateway-burst) provider options.
|
||||
|
||||
### BasicAuth Middleware
|
||||
|
||||
From version `v3.6.19` onwards, the BasicAuth middleware requires a non-empty users configuration in order to be built successfully.
|
||||
Previously, the middleware would be built successfully but always return a 401 status code for any request.
|
||||
Now, an error occurs and any routers using it will be unmounted. For the same request, a 404 status code is served instead of a 401 status code.
|
||||
|
||||
### StripPrefix and StripPrefixRegex Middleware
|
||||
|
||||
From version `v3.6.19` onwards, the StripPrefix middleware and the StripPrefixRegex middleware reject requests (`400 Bad Request`)
|
||||
when stripping the configured prefix produces a path that differs from its normalised form
|
||||
(i.e. a path containing `.` or `..` segments that would be collapsed by normalisation).
|
||||
|
||||
This prevents the stripped path from being interpreted as a different resource by the upstream service.
|
||||
|
||||
Examples with a configured prefix of `/api`:
|
||||
|
||||
| Request path | Path after strip | Normalised path | Result |
|
||||
|--------------|------------------|-----------------|--------------|
|
||||
| `/api/foo` | `/foo` | `/foo` | `200` (sent) |
|
||||
| `/api/` | `/` | `/` | `200` (sent) |
|
||||
| `/api./foo` | `/./foo` | `/foo` | `400` |
|
||||
| `/api../foo` | `/../foo` | `/foo` | `400` |
|
||||
|
||||
---
|
||||
|
||||
## v3.6.17
|
||||
|
||||
### Kubernetes providers: `crossProviderNamespaces`
|
||||
|
|
|
|||
|
|
@ -364,6 +364,7 @@ THIS FILE MUST NOT BE EDITED BY HAND
|
|||
| <a id="opt-providers-kubernetescrd-throttleduration" href="#opt-providers-kubernetescrd-throttleduration" title="#opt-providers-kubernetescrd-throttleduration">providers.kubernetescrd.throttleduration</a> | Ingress refresh throttle duration | 0 |
|
||||
| <a id="opt-providers-kubernetescrd-token" href="#opt-providers-kubernetescrd-token" title="#opt-providers-kubernetescrd-token">providers.kubernetescrd.token</a> | Kubernetes bearer token (not needed for in-cluster client). It accepts either a token value or a file path to the token. | |
|
||||
| <a id="opt-providers-kubernetesgateway" href="#opt-providers-kubernetesgateway" title="#opt-providers-kubernetesgateway">providers.kubernetesgateway</a> | Enables Kubernetes Gateway API provider. | false |
|
||||
| <a id="opt-providers-kubernetesgateway-burst" href="#opt-providers-kubernetesgateway-burst" title="#opt-providers-kubernetesgateway-burst">providers.kubernetesgateway.burst</a> | Defines the maximum burst of requests to the Kubernetes API server. | 100 |
|
||||
| <a id="opt-providers-kubernetesgateway-certauthfilepath" href="#opt-providers-kubernetesgateway-certauthfilepath" title="#opt-providers-kubernetesgateway-certauthfilepath">providers.kubernetesgateway.certauthfilepath</a> | Kubernetes certificate authority file path (not needed for in-cluster client). | |
|
||||
| <a id="opt-providers-kubernetesgateway-crossprovidernamespaces" href="#opt-providers-kubernetesgateway-crossprovidernamespaces" title="#opt-providers-kubernetesgateway-crossprovidernamespaces">providers.kubernetesgateway.crossprovidernamespaces</a> | List of namespaces from which Gateway API routes are allowed to declare TraefikService backendRef references. | |
|
||||
| <a id="opt-providers-kubernetesgateway-endpoint" href="#opt-providers-kubernetesgateway-endpoint" title="#opt-providers-kubernetesgateway-endpoint">providers.kubernetesgateway.endpoint</a> | Kubernetes server endpoint (required for external cluster client). | |
|
||||
|
|
@ -371,6 +372,7 @@ THIS FILE MUST NOT BE EDITED BY HAND
|
|||
| <a id="opt-providers-kubernetesgateway-labelselector" href="#opt-providers-kubernetesgateway-labelselector" title="#opt-providers-kubernetesgateway-labelselector">providers.kubernetesgateway.labelselector</a> | Kubernetes label selector to select specific GatewayClasses. | |
|
||||
| <a id="opt-providers-kubernetesgateway-namespaces" href="#opt-providers-kubernetesgateway-namespaces" title="#opt-providers-kubernetesgateway-namespaces">providers.kubernetesgateway.namespaces</a> | Kubernetes namespaces. | |
|
||||
| <a id="opt-providers-kubernetesgateway-nativelbbydefault" href="#opt-providers-kubernetesgateway-nativelbbydefault" title="#opt-providers-kubernetesgateway-nativelbbydefault">providers.kubernetesgateway.nativelbbydefault</a> | Defines whether to use Native Kubernetes load-balancing by default. | false |
|
||||
| <a id="opt-providers-kubernetesgateway-qps" href="#opt-providers-kubernetesgateway-qps" title="#opt-providers-kubernetesgateway-qps">providers.kubernetesgateway.qps</a> | Defines the maximum QPS to the Kubernetes API server. Setting this to a negative value will disable client-side ratelimiting. | 50 |
|
||||
| <a id="opt-providers-kubernetesgateway-statusaddress-hostname" href="#opt-providers-kubernetesgateway-statusaddress-hostname" title="#opt-providers-kubernetesgateway-statusaddress-hostname">providers.kubernetesgateway.statusaddress.hostname</a> | Hostname used for Kubernetes Gateway status address. | |
|
||||
| <a id="opt-providers-kubernetesgateway-statusaddress-ip" href="#opt-providers-kubernetesgateway-statusaddress-ip" title="#opt-providers-kubernetesgateway-statusaddress-ip">providers.kubernetesgateway.statusaddress.ip</a> | IP used to set Kubernetes Gateway status address. | |
|
||||
| <a id="opt-providers-kubernetesgateway-statusaddress-service" href="#opt-providers-kubernetesgateway-statusaddress-service" title="#opt-providers-kubernetesgateway-statusaddress-service">providers.kubernetesgateway.statusaddress.service</a> | Published Kubernetes Service to copy status addresses from. | |
|
||||
|
|
|
|||
|
|
@ -66,22 +66,24 @@ providers:
|
|||
|
||||
<!-- markdownlint-disable MD013 -->
|
||||
|
||||
| Field | Description | Default | Required |
|
||||
|:----------------------------------------------------------------------|:------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:--------|:---------|
|
||||
| <a id="opt-providers-providersThrottleDuration" href="#opt-providers-providersThrottleDuration" title="#opt-providers-providersThrottleDuration">`providers.providersThrottleDuration`</a> | Minimum amount of time to wait for, after a configuration reload, before taking into account any new configuration refresh event.<br />If multiple events occur within this time, only the most recent one is taken into account, and all others are discarded.<br />**This option cannot be set per provider, but the throttling algorithm applies to each of them independently.** | 2s | No |
|
||||
| <a id="opt-providers-kubernetesGateway-endpoint" href="#opt-providers-kubernetesGateway-endpoint" title="#opt-providers-kubernetesGateway-endpoint">`providers.kubernetesGateway.endpoint`</a> | Server endpoint URL.<br />More information [here](#endpoint). | "" | No |
|
||||
| <a id="opt-providers-kubernetesGateway-experimentalChannel" href="#opt-providers-kubernetesGateway-experimentalChannel" title="#opt-providers-kubernetesGateway-experimentalChannel">`providers.kubernetesGateway.experimentalChannel`</a> | Toggles support for the Experimental Channel resources ([Gateway API release channels documentation](https://gateway-api.sigs.k8s.io/concepts/versioning/#release-channels)).<br />(ex: `TCPRoute`) | false | No |
|
||||
| <a id="opt-providers-kubernetesGateway-token" href="#opt-providers-kubernetesGateway-token" title="#opt-providers-kubernetesGateway-token">`providers.kubernetesGateway.token`</a> | Bearer token used for the Kubernetes client configuration. | "" | No |
|
||||
| <a id="opt-providers-kubernetesGateway-certAuthFilePath" href="#opt-providers-kubernetesGateway-certAuthFilePath" title="#opt-providers-kubernetesGateway-certAuthFilePath">`providers.kubernetesGateway.certAuthFilePath`</a> | Path to the certificate authority file.<br />Used for the Kubernetes client configuration. | "" | No |
|
||||
| <a id="opt-providers-kubernetesGateway-namespaces" href="#opt-providers-kubernetesGateway-namespaces" title="#opt-providers-kubernetesGateway-namespaces">`providers.kubernetesGateway.namespaces`</a> | Array of namespaces to watch.<br />If left empty, watch all namespaces. | [] | No |
|
||||
| <a id="opt-providers-kubernetesGateway-labelselector" href="#opt-providers-kubernetesGateway-labelselector" title="#opt-providers-kubernetesGateway-labelselector">`providers.kubernetesGateway.labelselector`</a> | Allow filtering on `GatewayClass` only. If left empty, Traefik processes all GatewayClass objects in the configured namespaces.<br />See [label-selectors](https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors) for details. | "" | No |
|
||||
| <a id="opt-providers-kubernetesGateway-throttleDuration" href="#opt-providers-kubernetesGateway-throttleDuration" title="#opt-providers-kubernetesGateway-throttleDuration">`providers.kubernetesGateway.throttleDuration`</a> | Minimum amount of time to wait between two Kubernetes events before producing a new configuration.<br />This prevents a Kubernetes cluster that updates many times per second from continuously changing your Traefik configuration.<br />If empty, every event is caught. | 0s | No |
|
||||
| <a id="opt-providers-kubernetesGateway-nativeLBByDefault" href="#opt-providers-kubernetesGateway-nativeLBByDefault" title="#opt-providers-kubernetesGateway-nativeLBByDefault">`providers.kubernetesGateway.nativeLBByDefault`</a> | Defines whether to use Native Kubernetes load-balancing mode by default. For more information, please check out the `traefik.io/service.nativelb` service annotation documentation. | false | No |
|
||||
| <a id="opt-providers-kubernetesGateway-statusAddress-hostname" href="#opt-providers-kubernetesGateway-statusAddress-hostname" title="#opt-providers-kubernetesGateway-statusAddress-hostname">`providers.kubernetesGateway.`<br />`statusAddress.hostname`</a> | Hostname copied to the Gateway `status.addresses`. | "" | No |
|
||||
| <a id="opt-providers-kubernetesGateway-statusAddress-ip" href="#opt-providers-kubernetesGateway-statusAddress-ip" title="#opt-providers-kubernetesGateway-statusAddress-ip">`providers.kubernetesGateway.`<br />`statusAddress.ip`</a> | IP address copied to the Gateway `status.addresses`, and currently only supports one IP value (IPv4 or IPv6). | "" | No |
|
||||
| <a id="opt-providers-kubernetesGateway-statusAddress-service-namespace" href="#opt-providers-kubernetesGateway-statusAddress-service-namespace" title="#opt-providers-kubernetesGateway-statusAddress-service-namespace">`providers.kubernetesGateway.`<br />`statusAddress.service.namespace`</a> | The namespace of the Kubernetes service to copy status addresses from.<br />When using third parties tools like External-DNS, this option can be used to copy the service `loadbalancer.status` (containing the service's endpoints IPs) to the Gateway `status.addresses`. | "" | No |
|
||||
| <a id="opt-providers-kubernetesGateway-statusAddress-service-name" href="#opt-providers-kubernetesGateway-statusAddress-service-name" title="#opt-providers-kubernetesGateway-statusAddress-service-name">`providers.kubernetesGateway.`<br />`statusAddress.service.name`</a> | The name of the Kubernetes service to copy status addresses from.<br />When using third parties tools like External-DNS, this option can be used to copy the service `loadbalancer.status` (containing the service's endpoints IPs) to the Gateway `status.addresses`. | "" | No |
|
||||
| <a id="opt-providers-kubernetesGateway-crossProviderNamespaces" href="#opt-providers-kubernetesGateway-crossProviderNamespaces" title="#opt-providers-kubernetesGateway-crossProviderNamespaces">`providers.kubernetesGateway.crossProviderNamespaces`</a> | List of namespaces from which Gateway API routes (`HTTPRoute`, `TCPRoute`, `TLSRoute`) are allowed to declare a `backendRef` of kind `TraefikService`.<br />When unset, all namespaces are allowed. When set to `[]`, every such backendRef is rejected and the route is dropped. | [] | No |
|
||||
| Field | Description | Default | Required |
|
||||
|:----------------------------------------------------------------------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:--------|:---------|
|
||||
| <a id="opt-providers-providersThrottleDuration" href="#opt-providers-providersThrottleDuration" title="#opt-providers-providersThrottleDuration">`providers.providersThrottleDuration`</a> | Minimum amount of time to wait for, after a configuration reload, before taking into account any new configuration refresh event.<br />If multiple events occur within this time, only the most recent one is taken into account, and all others are discarded.<br />**This option cannot be set per provider, but the throttling algorithm applies to each of them independently.** | 2s | No |
|
||||
| <a id="opt-providers-kubernetesGateway-endpoint" href="#opt-providers-kubernetesGateway-endpoint" title="#opt-providers-kubernetesGateway-endpoint">`providers.kubernetesGateway.endpoint`</a> | Server endpoint URL.<br />More information [here](#endpoint). | "" | No |
|
||||
| <a id="opt-providers-kubernetesGateway-experimentalChannel" href="#opt-providers-kubernetesGateway-experimentalChannel" title="#opt-providers-kubernetesGateway-experimentalChannel">`providers.kubernetesGateway.experimentalChannel`</a> | Toggles support for the Experimental Channel resources ([Gateway API release channels documentation](https://gateway-api.sigs.k8s.io/concepts/versioning/#release-channels)).<br />(ex: `TCPRoute`) | false | No |
|
||||
| <a id="opt-providers-kubernetesGateway-token" href="#opt-providers-kubernetesGateway-token" title="#opt-providers-kubernetesGateway-token">`providers.kubernetesGateway.token`</a> | Bearer token used for the Kubernetes client configuration. | "" | No |
|
||||
| <a id="opt-providers-kubernetesGateway-certAuthFilePath" href="#opt-providers-kubernetesGateway-certAuthFilePath" title="#opt-providers-kubernetesGateway-certAuthFilePath">`providers.kubernetesGateway.certAuthFilePath`</a> | Path to the certificate authority file.<br />Used for the Kubernetes client configuration. | "" | No |
|
||||
| <a id="opt-providers-kubernetesGateway-namespaces" href="#opt-providers-kubernetesGateway-namespaces" title="#opt-providers-kubernetesGateway-namespaces">`providers.kubernetesGateway.namespaces`</a> | Array of namespaces to watch.<br />If left empty, watch all namespaces. | [] | No |
|
||||
| <a id="opt-providers-kubernetesGateway-labelselector" href="#opt-providers-kubernetesGateway-labelselector" title="#opt-providers-kubernetesGateway-labelselector">`providers.kubernetesGateway.labelselector`</a> | Allow filtering on `GatewayClass` only. If left empty, Traefik processes all GatewayClass objects in the configured namespaces.<br />See [label-selectors](https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors) for details. | "" | No |
|
||||
| <a id="opt-providers-kubernetesGateway-throttleDuration" href="#opt-providers-kubernetesGateway-throttleDuration" title="#opt-providers-kubernetesGateway-throttleDuration">`providers.kubernetesGateway.throttleDuration`</a> | Minimum amount of time to wait between two Kubernetes events before producing a new configuration.<br />This prevents a Kubernetes cluster that updates many times per second from continuously changing your Traefik configuration.<br />If empty, every event is caught. | 0s | No |
|
||||
| <a id="opt-providers-kubernetesGateway-nativeLBByDefault" href="#opt-providers-kubernetesGateway-nativeLBByDefault" title="#opt-providers-kubernetesGateway-nativeLBByDefault">`providers.kubernetesGateway.nativeLBByDefault`</a> | Defines whether to use Native Kubernetes load-balancing mode by default. For more information, please check out the `traefik.io/service.nativelb` service annotation documentation. | false | No |
|
||||
| <a id="opt-providers-kubernetesGateway-statusAddress-hostname" href="#opt-providers-kubernetesGateway-statusAddress-hostname" title="#opt-providers-kubernetesGateway-statusAddress-hostname">`providers.kubernetesGateway.`<br />`statusAddress.hostname`</a> | Hostname copied to the Gateway `status.addresses`. | "" | No |
|
||||
| <a id="opt-providers-kubernetesGateway-statusAddress-ip" href="#opt-providers-kubernetesGateway-statusAddress-ip" title="#opt-providers-kubernetesGateway-statusAddress-ip">`providers.kubernetesGateway.`<br />`statusAddress.ip`</a> | IP address copied to the Gateway `status.addresses`, and currently only supports one IP value (IPv4 or IPv6). | "" | No |
|
||||
| <a id="opt-providers-kubernetesGateway-statusAddress-service-namespace" href="#opt-providers-kubernetesGateway-statusAddress-service-namespace" title="#opt-providers-kubernetesGateway-statusAddress-service-namespace">`providers.kubernetesGateway.`<br />`statusAddress.service.namespace`</a> | The namespace of the Kubernetes service to copy status addresses from.<br />When using third parties tools like External-DNS, this option can be used to copy the service `loadbalancer.status` (containing the service's endpoints IPs) to the Gateway `status.addresses`. | "" | No |
|
||||
| <a id="opt-providers-kubernetesGateway-statusAddress-service-name" href="#opt-providers-kubernetesGateway-statusAddress-service-name" title="#opt-providers-kubernetesGateway-statusAddress-service-name">`providers.kubernetesGateway.`<br />`statusAddress.service.name`</a> | The name of the Kubernetes service to copy status addresses from.<br />When using third parties tools like External-DNS, this option can be used to copy the service `loadbalancer.status` (containing the service's endpoints IPs) to the Gateway `status.addresses`. | "" | No |
|
||||
| <a id="opt-providers-kubernetesGateway-crossProviderNamespaces" href="#opt-providers-kubernetesGateway-crossProviderNamespaces" title="#opt-providers-kubernetesGateway-crossProviderNamespaces">`providers.kubernetesGateway.crossProviderNamespaces`</a> | List of namespaces from which Gateway API routes (`HTTPRoute`, `TCPRoute`, `TLSRoute`) are allowed to declare a `backendRef` of kind `TraefikService`.<br />When unset, all namespaces are allowed. When set to `[]`, every such backendRef is rejected and the route is dropped. | [] | No |
|
||||
| <a id="opt-providers-kubernetesgateway-qps" href="#opt-providers-kubernetesgateway-qps" title="#opt-providers-kubernetesgateway-qps">providers.kubernetesgateway.qps</a> | Defines the maximum QPS to the Kubernetes API server. Setting this to a negative value will disable client-side ratelimiting. | 50 | No |
|
||||
| <a id="opt-providers-kubernetesgateway-burst" href="#opt-providers-kubernetesgateway-burst" title="#opt-providers-kubernetesgateway-burst">providers.kubernetesgateway.burst</a> | Defines the maximum burst of requests to the Kubernetes API server. | 100 | No |
|
||||
|
||||
<!-- markdownlint-enable MD013 -->
|
||||
|
||||
|
|
|
|||
|
|
@ -280,6 +280,7 @@ The following annotations are organized by category for easier navigation.
|
|||
| <a id="opt-nginx-ingress-kubernetes-ioauth-snippet" href="#opt-nginx-ingress-kubernetes-ioauth-snippet" title="#opt-nginx-ingress-kubernetes-ioauth-snippet">`nginx.ingress.kubernetes.io/auth-snippet`</a> | Supported directives: `proxy_method`, `more_set_headers`, `proxy_set_header`, `more_set_input_headers`, `set`, `if`, `return code [text]`. It supports minimal variable interpolation by using the following NGINX variables: `$scheme`, `$host`, `$http_*`, `$hostname`, `$request_uri`, `$request_method`, `$query_string`, `$args`, `$arg_*`, `$remote_addr`, `$uri`, `$document_uri`, `$server_name`, `$server_port`, `$content_type`, `$content_length`, `$cookie_*`, `$is_args`, `$best_http_host`, `$escaped_request_uri`, `$proxy_add_x_forwarded_for`. |
|
||||
| <a id="opt-nginx-ingress-kubernetes-ioauth-method" href="#opt-nginx-ingress-kubernetes-ioauth-method" title="#opt-nginx-ingress-kubernetes-ioauth-method">`nginx.ingress.kubernetes.io/auth-method`</a> | This annotation uses the `proxy_method` directive in Nginx. Thus, it can't be defined on an ingress that already have an `auth-snippet` annotation with the `proxy_method` directive. |
|
||||
| <a id="opt-nginx-ingress-kubernetes-ioauth-response-headers" href="#opt-nginx-ingress-kubernetes-ioauth-response-headers" title="#opt-nginx-ingress-kubernetes-ioauth-response-headers">`nginx.ingress.kubernetes.io/auth-response-headers`</a> | |
|
||||
| <a id="opt-nginx-ingress-kubernetes-ioenable-global-auth" href="#opt-nginx-ingress-kubernetes-ioenable-global-auth" title="#opt-nginx-ingress-kubernetes-ioenable-global-auth">`nginx.ingress.kubernetes.io/enable-global-auth`</a> | |
|
||||
|
||||
### SSL/TLS
|
||||
|
||||
|
|
@ -456,7 +457,6 @@ In practice, Traefik is slightly more lenient under bursty load, as it smooths o
|
|||
| <a id="opt-nginx-ingress-kubernetes-ioauth-keepalive-requests" href="#opt-nginx-ingress-kubernetes-ioauth-keepalive-requests" title="#opt-nginx-ingress-kubernetes-ioauth-keepalive-requests">`nginx.ingress.kubernetes.io/auth-keepalive-requests`</a> | |
|
||||
| <a id="opt-nginx-ingress-kubernetes-ioauth-keepalive-timeout" href="#opt-nginx-ingress-kubernetes-ioauth-keepalive-timeout" title="#opt-nginx-ingress-kubernetes-ioauth-keepalive-timeout">`nginx.ingress.kubernetes.io/auth-keepalive-timeout`</a> | |
|
||||
| <a id="opt-nginx-ingress-kubernetes-ioauth-proxy-set-headers" href="#opt-nginx-ingress-kubernetes-ioauth-proxy-set-headers" title="#opt-nginx-ingress-kubernetes-ioauth-proxy-set-headers">`nginx.ingress.kubernetes.io/auth-proxy-set-headers`</a> | |
|
||||
| <a id="opt-nginx-ingress-kubernetes-ioenable-global-auth" href="#opt-nginx-ingress-kubernetes-ioenable-global-auth" title="#opt-nginx-ingress-kubernetes-ioenable-global-auth">`nginx.ingress.kubernetes.io/enable-global-auth`</a> | |
|
||||
| <a id="opt-nginx-ingress-kubernetes-iodisable-proxy-intercept-errors" href="#opt-nginx-ingress-kubernetes-iodisable-proxy-intercept-errors" title="#opt-nginx-ingress-kubernetes-iodisable-proxy-intercept-errors">`nginx.ingress.kubernetes.io/disable-proxy-intercept-errors`</a> | |
|
||||
| <a id="opt-nginx-ingress-kubernetes-iolimit-rate-after" href="#opt-nginx-ingress-kubernetes-iolimit-rate-after" title="#opt-nginx-ingress-kubernetes-iolimit-rate-after">`nginx.ingress.kubernetes.io/limit-rate-after`</a> | |
|
||||
| <a id="opt-nginx-ingress-kubernetes-iolimit-rate" href="#opt-nginx-ingress-kubernetes-iolimit-rate" title="#opt-nginx-ingress-kubernetes-iolimit-rate">`nginx.ingress.kubernetes.io/limit-rate`</a> | |
|
||||
|
|
|
|||
19
go.mod
19
go.mod
|
|
@ -22,7 +22,7 @@ require (
|
|||
github.com/docker/cli v29.4.0+incompatible
|
||||
github.com/docker/go-connections v0.6.0
|
||||
github.com/fatih/structs v1.1.0
|
||||
github.com/fsnotify/fsnotify v1.9.0
|
||||
github.com/fsnotify/fsnotify v1.10.1
|
||||
github.com/go-acme/lego/v4 v4.35.2
|
||||
github.com/go-kit/kit v0.13.0
|
||||
github.com/go-kit/log v0.2.1
|
||||
|
|
@ -57,7 +57,7 @@ require (
|
|||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // No tag on the repo.
|
||||
github.com/prometheus/client_golang v1.23.2
|
||||
github.com/prometheus/client_model v0.6.2
|
||||
github.com/quic-go/quic-go v0.59.0
|
||||
github.com/quic-go/quic-go v0.59.1
|
||||
github.com/redis/go-redis/v9 v9.8.0
|
||||
github.com/rs/zerolog v1.33.0
|
||||
github.com/sirupsen/logrus v1.9.4
|
||||
|
|
@ -99,12 +99,12 @@ require (
|
|||
go.opentelemetry.io/otel/sdk/log v0.19.0
|
||||
go.opentelemetry.io/otel/sdk/metric v1.43.0
|
||||
go.opentelemetry.io/otel/trace v1.43.0
|
||||
golang.org/x/crypto v0.50.0
|
||||
golang.org/x/crypto v0.52.0
|
||||
golang.org/x/mod v0.35.0
|
||||
golang.org/x/net v0.53.0
|
||||
golang.org/x/net v0.55.0
|
||||
golang.org/x/sync v0.20.0
|
||||
golang.org/x/sys v0.43.0
|
||||
golang.org/x/text v0.36.0
|
||||
golang.org/x/sys v0.45.0
|
||||
golang.org/x/text v0.37.0
|
||||
golang.org/x/time v0.15.0
|
||||
golang.org/x/tools v0.44.0
|
||||
google.golang.org/grpc v1.80.0
|
||||
|
|
@ -182,7 +182,7 @@ require (
|
|||
github.com/blendle/zapdriver v1.3.1 // indirect
|
||||
github.com/bodgit/tsig v1.2.2 // indirect
|
||||
github.com/boombuler/barcode v1.0.1 // indirect
|
||||
github.com/bytedance/sonic v1.12.0 // indirect
|
||||
github.com/bytedance/sonic v1.15.1 // indirect
|
||||
github.com/cenkalti/backoff/v5 v5.0.3 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/clbanning/mxj/v2 v2.7.0 // indirect
|
||||
|
|
@ -274,7 +274,6 @@ require (
|
|||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/json-iterator/go v1.1.13-0.20220915233716-71ac16282d12 // indirect
|
||||
github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.5 // indirect
|
||||
github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b // indirect
|
||||
github.com/kylelemons/godebug v1.1.0 // indirect
|
||||
github.com/labbsr0x/bindman-dns-webhook v1.0.2 // indirect
|
||||
|
|
@ -298,7 +297,7 @@ require (
|
|||
github.com/moby/docker-image-spec v1.3.1 // indirect
|
||||
github.com/moby/go-archive v0.2.0 // indirect
|
||||
github.com/moby/patternmatcher v0.6.1 // indirect
|
||||
github.com/moby/spdystream v0.5.0 // indirect
|
||||
github.com/moby/spdystream v0.5.1 // indirect
|
||||
github.com/moby/sys/sequential v0.6.0 // indirect
|
||||
github.com/moby/sys/user v0.4.0 // indirect
|
||||
github.com/moby/sys/userns v0.1.0 // indirect
|
||||
|
|
@ -401,7 +400,7 @@ require (
|
|||
golang.org/x/arch v0.4.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f // indirect
|
||||
golang.org/x/oauth2 v0.36.0 // indirect
|
||||
golang.org/x/term v0.42.0 // indirect
|
||||
golang.org/x/term v0.43.0 // indirect
|
||||
gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect
|
||||
google.golang.org/api v0.276.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 // indirect
|
||||
|
|
|
|||
52
go.sum
52
go.sum
|
|
@ -835,10 +835,12 @@ github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
|
|||
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
|
||||
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
|
||||
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
|
||||
github.com/bytedance/sonic v1.12.0 h1:YGPgxF9xzaCNvd/ZKdQ28yRovhfMFZQjuk6fKBzZ3ls=
|
||||
github.com/bytedance/sonic v1.12.0/go.mod h1:B8Gt/XvtZ3Fqj+iSKMypzymZxw/FVwgIGKzMzT9r/rk=
|
||||
github.com/bytedance/sonic/loader v0.2.0 h1:zNprn+lsIP06C/IqCHs3gPQIvnvpKbbxyXQP1iU4kWM=
|
||||
github.com/bytedance/sonic/loader v0.2.0/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
||||
github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M=
|
||||
github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM=
|
||||
github.com/bytedance/sonic v1.15.1 h1:nJD5PmM0vY7J8CT6MxoqbVAAMhkSmV2HgRAUrrpLoOw=
|
||||
github.com/bytedance/sonic v1.15.1/go.mod h1:mT2NbXunuaEbnZ+mRIX/vYqKISmgEuHFDI4UzmKx2SA=
|
||||
github.com/bytedance/sonic/loader v0.5.1 h1:Ygpfa9zwRCCKSlrp5bBP/b/Xzc3VxsAW+5NIYXrOOpI=
|
||||
github.com/bytedance/sonic/loader v0.5.1/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo=
|
||||
github.com/c-bata/go-prompt v0.2.5/go.mod h1:vFnjEGDIIA/Lib7giyE4E9c50Lvl8j0S+7FVlAwDAVw=
|
||||
github.com/c-bata/go-prompt v0.2.6/go.mod h1:/LMAke8wD2FsNu9EXNdHxNLbd9MedkPnCdfpU9wwHfY=
|
||||
github.com/casbin/casbin/v2 v2.37.0/go.mod h1:vByNa/Fchek0KZUgG5wEsl7iFsiviAYKRtgrQfcJqHg=
|
||||
|
|
@ -866,10 +868,8 @@ github.com/clbanning/mxj v1.8.4/go.mod h1:BVjHeAH+rl9rs6f+QIpeRl0tfu10SXn1pUSa5P
|
|||
github.com/clbanning/mxj/v2 v2.7.0 h1:WA/La7UGCanFe5NpHF0Q3DNtnCsVoxbPKuyBNHWRyME=
|
||||
github.com/clbanning/mxj/v2 v2.7.0/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
|
||||
github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
|
||||
github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
|
||||
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
|
||||
github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M=
|
||||
github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||
|
|
@ -1004,8 +1004,8 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo
|
|||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU=
|
||||
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
|
||||
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
||||
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||
github.com/fsnotify/fsnotify v1.10.1 h1:b0/UzAf9yR5rhf3RPm9gf3ehBPpf0oZKIjtpKrx59Ho=
|
||||
github.com/fsnotify/fsnotify v1.10.1/go.mod h1:TLheqan6HD6GBK6PrDWyDPBaEV8LspOxvPSjC+bVfgo=
|
||||
github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM=
|
||||
github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
|
||||
github.com/gabriel-vasile/mimetype v1.4.13 h1:46nXokslUBsAJE/wMsp5gtO500a4F3Nkz9Ufpk2AcUM=
|
||||
|
|
@ -1467,8 +1467,8 @@ github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHU
|
|||
github.com/klauspost/compress v1.18.5 h1:/h1gH5Ce+VWNLSWqPzOVn6XBO+vJbCNGvjoaGBFW2IE=
|
||||
github.com/klauspost/compress v1.18.5/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
|
||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg=
|
||||
github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||
github.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY=
|
||||
github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8=
|
||||
github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b h1:udzkj9S/zlT5X367kqJis0QP7YMxobob6zhzq6Yre00=
|
||||
github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b/go.mod h1:pcaDhQK0/NJZEvtCO0qQPPropqV0sJOJ6YW7X+9kRwM=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
|
|
@ -1629,8 +1629,8 @@ github.com/moby/moby/client v0.4.0 h1:S+2XegzHQrrvTCvF6s5HFzcrywWQmuVnhOXe2kiWjI
|
|||
github.com/moby/moby/client v0.4.0/go.mod h1:QWPbvWchQbxBNdaLSpoKpCdf5E+WxFAgNHogCWDoa7g=
|
||||
github.com/moby/patternmatcher v0.6.1 h1:qlhtafmr6kgMIJjKJMDmMWq7WLkKIo23hsrpR3x084U=
|
||||
github.com/moby/patternmatcher v0.6.1/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc=
|
||||
github.com/moby/spdystream v0.5.0 h1:7r0J1Si3QO/kjRitvSLVVFUjxMEb/YLj6S9FF62JBCU=
|
||||
github.com/moby/spdystream v0.5.0/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI=
|
||||
github.com/moby/spdystream v0.5.1 h1:9sNYeYZUcci9R6/w7KDaFWEWeV4LStVG78Mpyq/Zm/Y=
|
||||
github.com/moby/spdystream v0.5.1/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI=
|
||||
github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU=
|
||||
github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko=
|
||||
github.com/moby/sys/user v0.4.0 h1:jhcMKit7SA80hivmFJcbB1vqmw//wU61Zdui2eQXuMs=
|
||||
|
|
@ -1811,8 +1811,8 @@ github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05Zp
|
|||
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
|
||||
github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8=
|
||||
github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII=
|
||||
github.com/quic-go/quic-go v0.59.0 h1:OLJkp1Mlm/aS7dpKgTc6cnpynnD2Xg7C1pwL6vy/SAw=
|
||||
github.com/quic-go/quic-go v0.59.0/go.mod h1:upnsH4Ju1YkqpLXC305eW3yDZ4NfnNbmQRCMWS58IKU=
|
||||
github.com/quic-go/quic-go v0.59.1 h1:0Gmua0HW1Tv7ANR7hUYwRyD0MG5OJfgvYSZasGZzBic=
|
||||
github.com/quic-go/quic-go v0.59.1/go.mod h1:upnsH4Ju1YkqpLXC305eW3yDZ4NfnNbmQRCMWS58IKU=
|
||||
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
|
||||
github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
|
||||
github.com/redis/go-redis/v9 v9.8.0 h1:q3nRvjrlge/6UD7eTu/DSg2uYiU2mCL0G/uzBWqhicI=
|
||||
|
|
@ -2239,8 +2239,8 @@ golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDf
|
|||
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
|
||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||
golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM=
|
||||
golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI=
|
||||
golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q=
|
||||
golang.org/x/crypto v0.52.0 h1:RMs7fP2rXdep0CftQlK8Uf+kibLm7qkCcradZWYz988=
|
||||
golang.org/x/crypto v0.52.0/go.mod h1:1QgfPxDqh0T2M/elOJtp9RvuR95kVjir0e6/BvEmGbc=
|
||||
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=
|
||||
|
|
@ -2391,8 +2391,8 @@ golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
|||
golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
|
||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||
golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
|
||||
golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA=
|
||||
golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs=
|
||||
golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8=
|
||||
golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww=
|
||||
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=
|
||||
|
|
@ -2572,8 +2572,8 @@ golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
|||
golang.org/x/sys v0.18.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.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI=
|
||||
golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY=
|
||||
golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
|
||||
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=
|
||||
|
|
@ -2595,8 +2595,8 @@ golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
|||
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
|
||||
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
||||
golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0=
|
||||
golang.org/x/term v0.42.0 h1:UiKe+zDFmJobeJ5ggPwOshJIVt6/Ft0rcfrXZDLWAWY=
|
||||
golang.org/x/term v0.42.0/go.mod h1:Dq/D+snpsbazcBG5+F9Q1n2rXV8Ma+71xEjTRufARgY=
|
||||
golang.org/x/term v0.43.0 h1:S4RLU2sB31O/NCl+zFN9Aru9A/Cq2aqKpTZJ6B+DwT4=
|
||||
golang.org/x/term v0.43.0/go.mod h1:lrhlHNdQJHO+1qVYiHfFKVuVioJIheAc3fBSMFYEIsk=
|
||||
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=
|
||||
|
|
@ -2618,8 +2618,8 @@ golang.org/x/text v0.13.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.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
|
||||
golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg=
|
||||
golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164=
|
||||
golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc=
|
||||
golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38=
|
||||
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=
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
[entryPoints.websecure]
|
||||
address = ":4443"
|
||||
[entryPoints.websecure.http3]
|
||||
|
||||
[api]
|
||||
insecure = true
|
||||
|
|
@ -33,6 +34,35 @@
|
|||
[http.routers.router3.tls]
|
||||
options = "mytls"
|
||||
|
||||
[http.routers.router4]
|
||||
rule = "Host(`site4.www.snitest.com`)"
|
||||
service = "service4"
|
||||
[http.routers.router4.tls]
|
||||
|
||||
[http.routers.router4path]
|
||||
rule = "Host(`site4.www.snitest.com`) && PathPrefix(`/foo`)"
|
||||
service = "service4"
|
||||
[http.routers.router4path.tls]
|
||||
options = "mytls"
|
||||
|
||||
[http.routers.router5]
|
||||
rule = "Host(`site5.www.snitest.com`)"
|
||||
service = "service5"
|
||||
[http.routers.router5.tls]
|
||||
options = "mytls"
|
||||
|
||||
[http.routers.router5path]
|
||||
rule = "Host(`site5.www.snitest.com`) && PathPrefix(`/bar`)"
|
||||
service = "service5"
|
||||
[http.routers.router5path.tls]
|
||||
options = "mytls"
|
||||
|
||||
[http.routers.router6]
|
||||
rule = "Host(`site6.www.snitest.com.`)"
|
||||
service = "service6"
|
||||
[http.routers.router6.tls]
|
||||
options = "mytls"
|
||||
|
||||
[http.services.service1]
|
||||
[[http.services.service1.loadBalancer.servers]]
|
||||
url = "http://127.0.0.1:9010"
|
||||
|
|
@ -45,10 +75,22 @@
|
|||
[[http.services.service3.loadBalancer.servers]]
|
||||
url = "http://127.0.0.1:9030"
|
||||
|
||||
[http.services.service4]
|
||||
[[http.services.service4.loadBalancer.servers]]
|
||||
url = "http://127.0.0.1:9040"
|
||||
|
||||
[http.services.service5]
|
||||
[[http.services.service5.loadBalancer.servers]]
|
||||
url = "http://127.0.0.1:9050"
|
||||
|
||||
[http.services.service6]
|
||||
[[http.services.service6.loadBalancer.servers]]
|
||||
url = "http://127.0.0.1:9060"
|
||||
|
||||
[[tls.certificates]]
|
||||
certFile = "fixtures/https/wildcard.www.snitest.com.cert"
|
||||
keyFile = "fixtures/https/wildcard.www.snitest.com.key"
|
||||
|
||||
[tls.options]
|
||||
[tls.options.mytls]
|
||||
maxVersion = "VersionTLS12"
|
||||
maxVersion = "VersionTLS13"
|
||||
|
|
|
|||
53
integration/fixtures/https/https_entrypoint_tls.toml
Normal file
53
integration/fixtures/https/https_entrypoint_tls.toml
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
[global]
|
||||
checkNewVersion = false
|
||||
sendAnonymousUsage = false
|
||||
|
||||
[log]
|
||||
level = "DEBUG"
|
||||
|
||||
[entryPoints]
|
||||
[entryPoints.websecure]
|
||||
address = ":4443"
|
||||
[entryPoints.websecure.http.tls]
|
||||
|
||||
[entryPoints.websecure-options]
|
||||
address = ":4444"
|
||||
[entryPoints.websecure-options.http.tls]
|
||||
options = "foo"
|
||||
|
||||
[api]
|
||||
insecure = true
|
||||
|
||||
[providers.file]
|
||||
filename = "{{ .SelfFilename }}"
|
||||
|
||||
## dynamic configuration ##
|
||||
|
||||
[http.routers]
|
||||
[http.routers.router1]
|
||||
entryPoints = ["websecure"]
|
||||
service = "service1"
|
||||
rule = "Host(`snitest.com`)"
|
||||
|
||||
[http.routers.router2]
|
||||
entryPoints = ["websecure-options"]
|
||||
service = "service1"
|
||||
rule = "Host(`snitest.org`)"
|
||||
|
||||
[http.services]
|
||||
[http.services.service1]
|
||||
[http.services.service1.loadBalancer]
|
||||
[[http.services.service1.loadBalancer.servers]]
|
||||
url = "http://127.0.0.1:9010"
|
||||
|
||||
[[tls.certificates]]
|
||||
certFile = "fixtures/https/snitest.com.cert"
|
||||
keyFile = "fixtures/https/snitest.com.key"
|
||||
|
||||
[[tls.certificates]]
|
||||
certFile = "fixtures/https/snitest.org.cert"
|
||||
keyFile = "fixtures/https/snitest.org.key"
|
||||
|
||||
[tls.options]
|
||||
[tls.options.foo]
|
||||
maxVersion = "VersionTLS12"
|
||||
|
|
@ -13,6 +13,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/BurntSushi/toml"
|
||||
"github.com/quic-go/quic-go/http3"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/stretchr/testify/suite"
|
||||
|
|
@ -149,7 +150,69 @@ func (s *HTTPSSuite) TestWithSNIConfigRoute() {
|
|||
require.NoError(s.T(), err)
|
||||
}
|
||||
|
||||
// TestWithTLSOptions verifies that traefik routes the requests with the associated tls options.
|
||||
// TestWithEntryPointTLSConfig verifies that a router relying on the entry point
|
||||
// TLS configuration (without an explicit router TLS section) is served over HTTPS,
|
||||
// including when the entry point references user-defined TLS options.
|
||||
// Regression test for https://github.com/traefik/traefik/issues/13289.
|
||||
func (s *HTTPSSuite) TestWithEntryPointTLSConfig() {
|
||||
file := s.adaptFile("fixtures/https/https_entrypoint_tls.toml", struct{}{})
|
||||
s.traefikCmd(withConfigFile(file))
|
||||
|
||||
// wait for Traefik
|
||||
err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("Host(`snitest.com`)"))
|
||||
require.NoError(s.T(), err)
|
||||
|
||||
backend := startTestServer("9010", http.StatusNoContent, "")
|
||||
defer backend.Close()
|
||||
|
||||
err = try.GetRequest(backend.URL, 1*time.Second, try.StatusCodeIs(http.StatusNoContent))
|
||||
require.NoError(s.T(), err)
|
||||
|
||||
tr := &http.Transport{
|
||||
TLSClientConfig: &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
ServerName: "snitest.com",
|
||||
},
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, "https://127.0.0.1:4443/", nil)
|
||||
require.NoError(s.T(), err)
|
||||
req.Host = tr.TLSClientConfig.ServerName
|
||||
req.Header.Set("Host", tr.TLSClientConfig.ServerName)
|
||||
req.Header.Set("Accept", "*/*")
|
||||
|
||||
err = try.RequestWithTransport(req, 30*time.Second, tr, try.HasCn(tr.TLSClientConfig.ServerName), try.StatusCodeIs(http.StatusNoContent))
|
||||
require.NoError(s.T(), err)
|
||||
|
||||
// The websecure-options entry point references the user-defined "foo" TLS options (maxVersion VersionTLS12).
|
||||
// A request with no router-level TLS must still have these options resolved and applied.
|
||||
trOptions := &http.Transport{
|
||||
TLSClientConfig: &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
ServerName: "snitest.org",
|
||||
},
|
||||
}
|
||||
|
||||
req, err = http.NewRequest(http.MethodGet, "https://127.0.0.1:4444/", nil)
|
||||
require.NoError(s.T(), err)
|
||||
req.Host = trOptions.TLSClientConfig.ServerName
|
||||
req.Header.Set("Host", trOptions.TLSClientConfig.ServerName)
|
||||
req.Header.Set("Accept", "*/*")
|
||||
|
||||
err = try.RequestWithTransport(req, 30*time.Second, trOptions, try.HasCn(trOptions.TLSClientConfig.ServerName), try.StatusCodeIs(http.StatusNoContent))
|
||||
require.NoError(s.T(), err)
|
||||
|
||||
// A TLS 1.3-only client must fail the handshake, proving the "foo" options
|
||||
// (resolved from the entry point) are effectively enforced.
|
||||
_, err = tls.Dial("tcp", "127.0.0.1:4444", &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
ServerName: "snitest.org",
|
||||
MinVersion: tls.VersionTLS13,
|
||||
})
|
||||
assert.Error(s.T(), err)
|
||||
}
|
||||
|
||||
// TestWithTLSOptions verifies that traefik routes the requests with the associated tls options.
|
||||
func (s *HTTPSSuite) TestWithTLSOptions() {
|
||||
file := s.adaptFile("fixtures/https/https_tls_options.toml", struct{}{})
|
||||
s.traefikCmd(withConfigFile(file))
|
||||
|
|
@ -352,7 +415,7 @@ func (s *HTTPSSuite) TestWithConflictingTLSOptions() {
|
|||
assert.ErrorContains(s.T(), err, "tls: no supported versions satisfy MinVersion and MaxVersion")
|
||||
|
||||
// with unknown tls option
|
||||
err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains(fmt.Sprintf("found different TLS options for routers on the same host %v, so using the default TLS options instead", tr4.TLSClientConfig.ServerName)))
|
||||
err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("found different TLS options for routers on the same host, so using the default TLS options instead"))
|
||||
require.NoError(s.T(), err)
|
||||
}
|
||||
|
||||
|
|
@ -1083,19 +1146,20 @@ func (s *HTTPSSuite) TestWithDomainFronting() {
|
|||
defer backend2.Close()
|
||||
backend3 := startTestServer("9030", http.StatusOK, "server3")
|
||||
defer backend3.Close()
|
||||
backend5 := startTestServer("9050", http.StatusOK, "server5")
|
||||
defer backend5.Close()
|
||||
|
||||
file := s.adaptFile("fixtures/https/https_domain_fronting.toml", struct{}{})
|
||||
s.traefikCmd(withConfigFile(file))
|
||||
|
||||
// wait for Traefik
|
||||
err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", 500*time.Millisecond, try.BodyContains("Host(`site1.www.snitest.com`)"))
|
||||
err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1000*time.Millisecond, try.BodyContains("Host(`site1.www.snitest.com`)"))
|
||||
require.NoError(s.T(), err)
|
||||
|
||||
testCases := []struct {
|
||||
desc string
|
||||
hostHeader string
|
||||
serverName string
|
||||
expectedError bool
|
||||
expectedContent string
|
||||
expectedStatusCode int
|
||||
}{
|
||||
|
|
@ -1113,14 +1177,6 @@ func (s *HTTPSSuite) TestWithDomainFronting() {
|
|||
expectedContent: "server3",
|
||||
expectedStatusCode: http.StatusOK,
|
||||
},
|
||||
{
|
||||
desc: "Spaces after the host header",
|
||||
hostHeader: "site3.www.snitest.com ",
|
||||
serverName: "site3.www.snitest.com",
|
||||
expectedError: true,
|
||||
expectedContent: "server3",
|
||||
expectedStatusCode: http.StatusOK,
|
||||
},
|
||||
{
|
||||
desc: "Spaces after the servername",
|
||||
hostHeader: "site3.www.snitest.com",
|
||||
|
|
@ -1128,14 +1184,6 @@ func (s *HTTPSSuite) TestWithDomainFronting() {
|
|||
expectedContent: "server3",
|
||||
expectedStatusCode: http.StatusOK,
|
||||
},
|
||||
{
|
||||
desc: "Spaces after the servername and host header",
|
||||
hostHeader: "site3.www.snitest.com ",
|
||||
serverName: "site3.www.snitest.com ",
|
||||
expectedError: true,
|
||||
expectedContent: "server3",
|
||||
expectedStatusCode: http.StatusOK,
|
||||
},
|
||||
{
|
||||
desc: "Domain Fronting with same tlsOptions should follow header",
|
||||
hostHeader: "site1.www.snitest.com",
|
||||
|
|
@ -1171,6 +1219,34 @@ func (s *HTTPSSuite) TestWithDomainFronting() {
|
|||
expectedContent: "server1",
|
||||
expectedStatusCode: http.StatusOK,
|
||||
},
|
||||
{
|
||||
desc: "Domain Fronting with ambiguous TLS options should produce a 421",
|
||||
hostHeader: "site4.www.snitest.com",
|
||||
serverName: "site3.www.snitest.com",
|
||||
expectedContent: "",
|
||||
expectedStatusCode: http.StatusMisdirectedRequest,
|
||||
},
|
||||
{
|
||||
desc: "Domain Fronting with same non-default TLS options should not produce a 421",
|
||||
hostHeader: "site5.www.snitest.com",
|
||||
serverName: "site3.www.snitest.com",
|
||||
expectedContent: "server5",
|
||||
expectedStatusCode: http.StatusOK,
|
||||
},
|
||||
{
|
||||
desc: "FQDN host header with empty SNI to non-default TLS options route should produce a 421",
|
||||
hostHeader: "site3.www.snitest.com.",
|
||||
serverName: "",
|
||||
expectedContent: "",
|
||||
expectedStatusCode: http.StatusMisdirectedRequest,
|
||||
},
|
||||
{
|
||||
desc: "Non-FQDN host header with empty SNI matching FQDN route rule should produce a 421",
|
||||
hostHeader: "site6.www.snitest.com",
|
||||
serverName: "",
|
||||
expectedContent: "",
|
||||
expectedStatusCode: http.StatusMisdirectedRequest,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
|
|
@ -1179,11 +1255,10 @@ func (s *HTTPSSuite) TestWithDomainFronting() {
|
|||
req.Host = test.hostHeader
|
||||
|
||||
err = try.RequestWithTransport(req, 500*time.Millisecond, &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true, ServerName: test.serverName}}, try.StatusCodeIs(test.expectedStatusCode), try.BodyContains(test.expectedContent))
|
||||
if test.expectedError {
|
||||
assert.Error(s.T(), err)
|
||||
} else {
|
||||
require.NoError(s.T(), err)
|
||||
}
|
||||
assert.NoError(s.T(), err, "test %s failed with: %v", test.desc, err)
|
||||
|
||||
err = try.RequestWithTransport(req, 500*time.Millisecond, &http3.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true, ServerName: test.serverName}}, try.StatusCodeIs(test.expectedStatusCode), try.BodyContains(test.expectedContent))
|
||||
assert.NoError(s.T(), err, "test %s failed with: %v", test.desc, err)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -949,7 +949,7 @@ func (s *SimpleSuite) TestRouterConfigErrors() {
|
|||
s.traefikCmd(withConfigFile(file))
|
||||
|
||||
// All errors
|
||||
err := try.GetRequest("http://127.0.0.1:8080/api/http/routers", 1000*time.Millisecond, try.BodyContains(`["middleware \"unknown@file\" does not exist","found different TLS options for routers on the same host snitest.net, so using the default TLS options instead"]`))
|
||||
err := try.GetRequest("http://127.0.0.1:8080/api/http/routers", 1000*time.Millisecond, try.BodyContains(`["middleware \"unknown@file\" does not exist","found different TLS options for routers on the same host, so using the default TLS options instead"]`))
|
||||
require.NoError(s.T(), err)
|
||||
|
||||
// router3 has an error because it uses an unknown entrypoint
|
||||
|
|
|
|||
|
|
@ -76,7 +76,7 @@ func Request(req *http.Request, timeout time.Duration, conditions ...ResponseCon
|
|||
// the condition on the response.
|
||||
// ResponseCondition may be nil, in which case only the request against the URL must
|
||||
// succeed.
|
||||
func RequestWithTransport(req *http.Request, timeout time.Duration, transport *http.Transport, conditions ...ResponseCondition) error {
|
||||
func RequestWithTransport(req *http.Request, timeout time.Duration, transport http.RoundTripper, conditions ...ResponseCondition) error {
|
||||
resp, err := doTryRequest(req, timeout, transport, conditions...)
|
||||
|
||||
if resp != nil && resp.Body != nil {
|
||||
|
|
@ -140,12 +140,12 @@ func doTryRequest(request *http.Request, timeout time.Duration, transport http.R
|
|||
func doRequest(action timedAction, timeout time.Duration, request *http.Request, transport http.RoundTripper, conditions ...ResponseCondition) (*http.Response, error) {
|
||||
var resp *http.Response
|
||||
return resp, action(timeout, func() error {
|
||||
var err error
|
||||
client := http.DefaultClient
|
||||
var client http.Client
|
||||
if transport != nil {
|
||||
client.Transport = transport
|
||||
}
|
||||
|
||||
var err error
|
||||
resp, err = client.Do(request)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
|
|||
|
|
@ -149,9 +149,10 @@ func (r *RouterDeniedEncodedPathCharacters) Map() map[string]struct{} {
|
|||
|
||||
// RouterTLSConfig holds the TLS configuration for a router.
|
||||
type RouterTLSConfig struct {
|
||||
Options string `json:"options,omitempty" toml:"options,omitempty" yaml:"options,omitempty" export:"true"`
|
||||
CertResolver string `json:"certResolver,omitempty" toml:"certResolver,omitempty" yaml:"certResolver,omitempty" export:"true"`
|
||||
Domains []types.Domain `json:"domains,omitempty" toml:"domains,omitempty" yaml:"domains,omitempty" export:"true"`
|
||||
Options string `json:"options,omitempty" toml:"options,omitempty" yaml:"options,omitempty" export:"true"`
|
||||
ResolvedOptions string `json:"-" toml:"-" yaml:"-" label:"-" file:"-" kv:"-" export:"false"`
|
||||
CertResolver string `json:"certResolver,omitempty" toml:"certResolver,omitempty" yaml:"certResolver,omitempty" export:"true"`
|
||||
Domains []types.Domain `json:"domains,omitempty" toml:"domains,omitempty" yaml:"domains,omitempty" export:"true"`
|
||||
}
|
||||
|
||||
// +k8s:deepcopy-gen=true
|
||||
|
|
|
|||
|
|
@ -43,6 +43,10 @@ func NewBasic(ctx context.Context, next http.Handler, authConfig dynamic.BasicAu
|
|||
return nil, err
|
||||
}
|
||||
|
||||
if len(users) == 0 {
|
||||
return nil, fmt.Errorf("no users found in %s", authConfig.UsersFile)
|
||||
}
|
||||
|
||||
// To prevent timing attacks, we need to compute a hash even if the user is not found.
|
||||
// We assume it to be safe only when the users hashes are all from the same algorithm,
|
||||
// so we can pick the first one as a random hash to compute.
|
||||
|
|
|
|||
|
|
@ -16,6 +16,15 @@ import (
|
|||
"github.com/traefik/traefik/v3/pkg/testhelpers"
|
||||
)
|
||||
|
||||
func TestNewBasicEmpty(t *testing.T) {
|
||||
auth := dynamic.BasicAuth{
|
||||
Users: []string{},
|
||||
}
|
||||
|
||||
_, err := NewBasic(t.Context(), nil, auth, "authName")
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func TestNewBasicNotFoundSecretIsSet(t *testing.T) {
|
||||
auth := dynamic.BasicAuth{
|
||||
Users: []string{"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/"},
|
||||
|
|
|
|||
|
|
@ -65,6 +65,10 @@ func (p *authTLSPassCertificateToUpstream) ServeHTTP(rw http.ResponseWriter, req
|
|||
if req.TLS == nil || len(req.TLS.PeerCertificates) == 0 {
|
||||
logger.Debug().Msg("Tried to extract a certificate on a request without mutual TLS")
|
||||
req.Header.Set(sslClientVerify, "NONE")
|
||||
// Prevent client-supplied values from reaching the upstream on the no-mTLS path.
|
||||
req.Header.Del(sslClientCert)
|
||||
req.Header.Del(sslClientSubjectDN)
|
||||
req.Header.Del(sslClientIssuerDN)
|
||||
p.next.ServeHTTP(rw, req)
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -360,6 +360,26 @@ func TestAuthTLSPassCertificateToUpstream(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestAuthTLSNoMTLSClearsCertHeaders(t *testing.T) {
|
||||
config := dynamic.AuthTLSPassCertificateToUpstream{
|
||||
ClientAuthType: tls.VerifyClientCertIfGiven,
|
||||
}
|
||||
handler, err := NewAuthTLSPassCertificateToUpstream(t.Context(), next, config, "test")
|
||||
require.NoError(t, err)
|
||||
|
||||
req := testhelpers.MustNewRequest(http.MethodGet, "http://example.com/foo", nil)
|
||||
req.Header.Set(sslClientCert, "client-cert")
|
||||
req.Header.Set(sslClientSubjectDN, "CN=client")
|
||||
req.Header.Set(sslClientIssuerDN, "CN=client-CA")
|
||||
|
||||
handler.ServeHTTP(httptest.NewRecorder(), req)
|
||||
|
||||
assert.Equal(t, "NONE", req.Header.Get(sslClientVerify))
|
||||
assert.Empty(t, req.Header.Get(sslClientCert))
|
||||
assert.Empty(t, req.Header.Get(sslClientSubjectDN))
|
||||
assert.Empty(t, req.Header.Get(sslClientIssuerDN))
|
||||
}
|
||||
|
||||
func buildTLSWith(certContents []string) *cryptoTLS.ConnectionState {
|
||||
var peerCertificates []*x509.Certificate
|
||||
|
||||
|
|
|
|||
|
|
@ -52,8 +52,6 @@ func TestSemConvServerMetrics(t *testing.T) {
|
|||
|
||||
for _, test := range tests {
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var cfg otypes.OTLP
|
||||
(&cfg).SetDefaults()
|
||||
cfg.AddRoutersLabels = true
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ func newRedisLimiter(ctx context.Context, rate rate.Limit, burst int64, maxDelay
|
|||
}
|
||||
|
||||
if config.Redis.WriteTimeout != nil {
|
||||
if *config.Redis.ReadTimeout > 0 {
|
||||
if *config.Redis.WriteTimeout > 0 {
|
||||
options.WriteTimeout = time.Duration(*config.Redis.WriteTimeout)
|
||||
} else {
|
||||
options.WriteTimeout = -1
|
||||
|
|
|
|||
|
|
@ -1,24 +1,26 @@
|
|||
package snicheck
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/traefik/traefik/v3/pkg/middlewares/requestdecorator"
|
||||
traefiktls "github.com/traefik/traefik/v3/pkg/tls"
|
||||
"github.com/traefik/traefik/v3/pkg/tcp"
|
||||
)
|
||||
|
||||
// SNICheck is an HTTP handler that checks whether the TLS configuration for the server name is the same as for the host header.
|
||||
type SNICheck struct {
|
||||
next http.Handler
|
||||
tlsOptionsForHost map[string]string
|
||||
next http.Handler
|
||||
routerName string
|
||||
tlsOptionsName string
|
||||
}
|
||||
|
||||
// New creates a new SNICheck.
|
||||
func New(tlsOptionsForHost map[string]string, next http.Handler) *SNICheck {
|
||||
return &SNICheck{next: next, tlsOptionsForHost: tlsOptionsForHost}
|
||||
func New(routerName, tlsOptionsName string, next http.Handler) *SNICheck {
|
||||
return &SNICheck{
|
||||
next: next,
|
||||
routerName: routerName,
|
||||
tlsOptionsName: tlsOptionsName,
|
||||
}
|
||||
}
|
||||
|
||||
func (s SNICheck) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
|
|
@ -27,81 +29,16 @@ func (s SNICheck) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
host := getHost(req)
|
||||
serverName := strings.TrimSpace(req.TLS.ServerName)
|
||||
|
||||
// Domain Fronting
|
||||
if !strings.EqualFold(host, serverName) {
|
||||
tlsOptionHeader := findTLSOptionName(s.tlsOptionsForHost, host, true)
|
||||
tlsOptionSNI := findTLSOptionName(s.tlsOptionsForHost, serverName, false)
|
||||
|
||||
if tlsOptionHeader != tlsOptionSNI {
|
||||
log.Debug().
|
||||
Str("host", host).
|
||||
Str("req.Host", req.Host).
|
||||
Str("req.TLS.ServerName", req.TLS.ServerName).
|
||||
Msgf("TLS options difference: SNI:%s, Header:%s", tlsOptionSNI, tlsOptionHeader)
|
||||
http.Error(rw, http.StatusText(http.StatusMisdirectedRequest), http.StatusMisdirectedRequest)
|
||||
return
|
||||
}
|
||||
tlsOptionsNameUsed := tcp.GetTLSOptionsName(req.Context())
|
||||
if s.tlsOptionsName != tlsOptionsNameUsed {
|
||||
log.Debug().
|
||||
Str("routerName", s.routerName).
|
||||
Str("req.Host", req.Host).
|
||||
Str("req.TLS.ServerName", req.TLS.ServerName).
|
||||
Msgf("TLS options difference: SNI:%s, Header:%s", tlsOptionsNameUsed, s.tlsOptionsName)
|
||||
http.Error(rw, http.StatusText(http.StatusMisdirectedRequest), http.StatusMisdirectedRequest)
|
||||
return
|
||||
}
|
||||
|
||||
s.next.ServeHTTP(rw, req)
|
||||
}
|
||||
|
||||
func getHost(req *http.Request) string {
|
||||
h := requestdecorator.GetCNAMEFlatten(req.Context())
|
||||
if h != "" {
|
||||
return h
|
||||
}
|
||||
|
||||
h = requestdecorator.GetCanonicalHost(req.Context())
|
||||
if h != "" {
|
||||
return h
|
||||
}
|
||||
|
||||
host, _, err := net.SplitHostPort(req.Host)
|
||||
if err != nil {
|
||||
host = req.Host
|
||||
}
|
||||
|
||||
return strings.TrimSpace(host)
|
||||
}
|
||||
|
||||
func findTLSOptionName(tlsOptionsForHost map[string]string, host string, fqdn bool) string {
|
||||
name := findTLSOptName(tlsOptionsForHost, host, fqdn)
|
||||
if name != "" {
|
||||
return name
|
||||
}
|
||||
|
||||
name = findTLSOptName(tlsOptionsForHost, strings.ToLower(host), fqdn)
|
||||
if name != "" {
|
||||
return name
|
||||
}
|
||||
|
||||
return traefiktls.DefaultTLSConfigName
|
||||
}
|
||||
|
||||
func findTLSOptName(tlsOptionsForHost map[string]string, host string, fqdn bool) string {
|
||||
if tlsOptions, ok := tlsOptionsForHost[host]; ok {
|
||||
return tlsOptions
|
||||
}
|
||||
|
||||
if !fqdn {
|
||||
return ""
|
||||
}
|
||||
|
||||
if last := len(host) - 1; last >= 0 && host[last] == '.' {
|
||||
if tlsOptions, ok := tlsOptionsForHost[host[:last]]; ok {
|
||||
return tlsOptions
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
if tlsOptions, ok := tlsOptionsForHost[host+"."]; ok {
|
||||
return tlsOptions
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,59 +0,0 @@
|
|||
package snicheck
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestSNICheck_ServeHTTP(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
tlsOptionsForHost map[string]string
|
||||
host string
|
||||
expected int
|
||||
}{
|
||||
{
|
||||
desc: "no TLS options",
|
||||
expected: http.StatusOK,
|
||||
},
|
||||
{
|
||||
desc: "with TLS options",
|
||||
tlsOptionsForHost: map[string]string{
|
||||
"example.com": "foo",
|
||||
},
|
||||
expected: http.StatusOK,
|
||||
},
|
||||
{
|
||||
desc: "server name and host doesn't have the same TLS configuration",
|
||||
tlsOptionsForHost: map[string]string{
|
||||
"example.com": "foo",
|
||||
},
|
||||
host: "example.com",
|
||||
expected: http.StatusMisdirectedRequest,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
next := http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {})
|
||||
|
||||
sniCheck := New(test.tlsOptionsForHost, next)
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "https://localhost", nil)
|
||||
if test.host != "" {
|
||||
req.Host = test.host
|
||||
}
|
||||
|
||||
recorder := httptest.NewRecorder()
|
||||
|
||||
sniCheck.ServeHTTP(recorder, req)
|
||||
|
||||
assert.Equal(t, test.expected, recorder.Code)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -54,6 +54,8 @@ func (s *stripPrefix) GetTracingInformation() (string, string) {
|
|||
}
|
||||
|
||||
func (s *stripPrefix) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
logger := middlewares.GetLogger(req.Context(), s.name, typeName)
|
||||
|
||||
for _, prefix := range s.prefixes {
|
||||
if strings.HasPrefix(req.URL.Path, prefix) {
|
||||
req.URL.Path = s.getPathStripped(req.URL.Path, prefix)
|
||||
|
|
@ -64,10 +66,18 @@ func (s *stripPrefix) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
|||
// Here we are sanitizing the URL when the path is not empty,
|
||||
// as the JoinPath method is adding a leading slash if the path is empty
|
||||
// to be aligned with ensureLeadingSlash behavior.
|
||||
if req.URL.Path != "" {
|
||||
path := req.URL.Path
|
||||
if path != "" {
|
||||
req.URL = req.URL.JoinPath()
|
||||
}
|
||||
|
||||
// Stop here if the normalization of the path produces a different path.
|
||||
if path != req.URL.Path {
|
||||
logger.Debug().Msgf("Rejecting request, sanitized path: %q is not equivalent to stripped path: %q", path, req.URL.Path)
|
||||
http.Error(rw, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
req.Header.Add(ForwardedPrefixHeader, prefix)
|
||||
req.RequestURI = req.URL.RequestURI()
|
||||
break
|
||||
|
|
|
|||
|
|
@ -148,10 +148,7 @@ func TestStripPrefix(t *testing.T) {
|
|||
Prefixes: []string{"/api"},
|
||||
},
|
||||
path: "/api./foo",
|
||||
expectedStatusCode: http.StatusOK,
|
||||
expectedPath: "/foo",
|
||||
expectedRawPath: "",
|
||||
expectedHeader: "/api",
|
||||
expectedStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
{
|
||||
desc: "multiple dots in the path not stripped by the prefix",
|
||||
|
|
@ -159,10 +156,7 @@ func TestStripPrefix(t *testing.T) {
|
|||
Prefixes: []string{"/api"},
|
||||
},
|
||||
path: "/api../foo",
|
||||
expectedStatusCode: http.StatusOK,
|
||||
expectedPath: "/foo",
|
||||
expectedRawPath: "",
|
||||
expectedHeader: "/api",
|
||||
expectedStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
{
|
||||
desc: "multiple dots in the path not stripped by the prefix with forceSlash",
|
||||
|
|
@ -171,10 +165,7 @@ func TestStripPrefix(t *testing.T) {
|
|||
ForceSlash: ptr.To(true),
|
||||
},
|
||||
path: "/api../foo",
|
||||
expectedStatusCode: http.StatusOK,
|
||||
expectedPath: "/foo",
|
||||
expectedRawPath: "",
|
||||
expectedHeader: "/api",
|
||||
expectedStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -204,6 +195,10 @@ func TestStripPrefix(t *testing.T) {
|
|||
handler.ServeHTTP(resp, req)
|
||||
|
||||
assert.Equal(t, test.expectedStatusCode, resp.Code, "Unexpected status code.")
|
||||
if test.expectedStatusCode != http.StatusOK {
|
||||
return
|
||||
}
|
||||
|
||||
assert.Equal(t, test.expectedPath, actualPath, "Unexpected path.")
|
||||
assert.Equal(t, test.expectedRawPath, actualRawPath, "Unexpected raw path.")
|
||||
assert.Equal(t, test.expectedHeader, actualHeader, "Unexpected '%s' header.", ForwardedPrefixHeader)
|
||||
|
|
|
|||
|
|
@ -47,6 +47,8 @@ func (s *stripPrefixRegex) GetTracingInformation() (string, string) {
|
|||
}
|
||||
|
||||
func (s *stripPrefixRegex) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
logger := middlewares.GetLogger(req.Context(), s.name, typeName)
|
||||
|
||||
for _, exp := range s.expressions {
|
||||
parts := exp.FindStringSubmatch(req.URL.Path)
|
||||
if len(parts) > 0 && len(parts[0]) > 0 {
|
||||
|
|
@ -65,10 +67,18 @@ func (s *stripPrefixRegex) ServeHTTP(rw http.ResponseWriter, req *http.Request)
|
|||
// Here we are sanitizing the URL when the path is not empty,
|
||||
// as the JoinPath method is adding a leading slash if the path is empty
|
||||
// to be aligned with ensureLeadingSlash behavior.
|
||||
if req.URL.Path != "" {
|
||||
path := req.URL.Path
|
||||
if path != "" {
|
||||
req.URL = req.URL.JoinPath()
|
||||
}
|
||||
|
||||
// Stop here if the normalization of the path produces a different path.
|
||||
if path != req.URL.Path {
|
||||
logger.Debug().Msgf("Rejecting request, sanitized path: %q is not equivalent to stripped path: %q", path, req.URL.Path)
|
||||
http.Error(rw, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
req.RequestURI = req.URL.RequestURI()
|
||||
break
|
||||
}
|
||||
|
|
|
|||
|
|
@ -201,21 +201,13 @@ func TestStripPrefixRegex(t *testing.T) {
|
|||
desc: "/api./foo",
|
||||
config: dynamic.StripPrefixRegex{Regex: []string{"/api"}},
|
||||
path: "/api./foo",
|
||||
expectedStatusCode: http.StatusOK,
|
||||
expectedPath: "/foo",
|
||||
expectedRawPath: "",
|
||||
expectedRequestURI: "/foo",
|
||||
expectedHeader: "/api",
|
||||
expectedStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
{
|
||||
desc: "/api../foo",
|
||||
config: dynamic.StripPrefixRegex{Regex: []string{"/api"}},
|
||||
path: "/api../foo",
|
||||
expectedStatusCode: http.StatusOK,
|
||||
expectedPath: "/foo",
|
||||
expectedRawPath: "",
|
||||
expectedRequestURI: "/foo",
|
||||
expectedHeader: "/api",
|
||||
expectedStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -491,7 +491,7 @@ func Test_addTCPRouteV2(t *testing.T) {
|
|||
remoteAddr: fakeAddr{addr: addr},
|
||||
}
|
||||
|
||||
connData, err := NewConnData(test.serverName, conn, test.protos)
|
||||
connData, err := NewConnData(test.serverName, conn.RemoteAddr(), test.protos)
|
||||
require.NoError(t, err)
|
||||
|
||||
matchingHandler, _ := router.Match(connData)
|
||||
|
|
|
|||
|
|
@ -22,10 +22,10 @@ type ConnData struct {
|
|||
}
|
||||
|
||||
// NewConnData builds a connData struct from the given parameters.
|
||||
func NewConnData(serverName string, conn tcp.WriteCloser, alpnProtos []string) (ConnData, error) {
|
||||
remoteIP, _, err := net.SplitHostPort(conn.RemoteAddr().String())
|
||||
func NewConnData(serverName string, remoteAddr net.Addr, alpnProtos []string) (ConnData, error) {
|
||||
remoteIP, _, err := net.SplitHostPort(remoteAddr.String())
|
||||
if err != nil {
|
||||
return ConnData{}, fmt.Errorf("error while parsing remote address %q: %w", conn.RemoteAddr().String(), err)
|
||||
return ConnData{}, fmt.Errorf("parsing remote address %q: %w", remoteAddr.String(), err)
|
||||
}
|
||||
|
||||
return ConnData{
|
||||
|
|
|
|||
|
|
@ -293,7 +293,7 @@ func Test_addTCPRoute(t *testing.T) {
|
|||
remoteAddr: fakeAddr{addr: addr},
|
||||
}
|
||||
|
||||
connData, err := NewConnData(test.serverName, conn, test.protos)
|
||||
connData, err := NewConnData(test.serverName, conn.RemoteAddr(), test.protos)
|
||||
require.NoError(t, err)
|
||||
|
||||
matchingHandler, _ := router.Match(connData)
|
||||
|
|
|
|||
|
|
@ -72,9 +72,16 @@ func (p *Provider) Provide(configurationChan chan<- dynamic.Message, pool *safe.
|
|||
// ignore sub-dir
|
||||
continue
|
||||
}
|
||||
if !isFileSupported(entry.Name()) {
|
||||
// ignore unsupported file extension
|
||||
continue
|
||||
}
|
||||
watchItems = append(watchItems, path.Join(p.Directory, entry.Name()))
|
||||
}
|
||||
case len(p.Filename) > 0:
|
||||
if !isFileSupported(p.Filename) {
|
||||
return fmt.Errorf("unsupported file extension for file %s", p.Filename)
|
||||
}
|
||||
watchItems = append(watchItems, filepath.Dir(p.Filename), p.Filename)
|
||||
default:
|
||||
return errors.New("error using file configuration provider, neither filename nor directory is defined")
|
||||
|
|
@ -169,7 +176,7 @@ func (p *Provider) addWatcher(pool *safe.Pool, items []string, configurationChan
|
|||
log.Debug().Msgf("add watcher on: %s", item)
|
||||
err = watcher.Add(item)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error adding file watcher: %w", err)
|
||||
return fmt.Errorf("error adding file watcher for %s: %w", item, err)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -392,6 +399,8 @@ func (p *Provider) collectFileConfigs(ctx context.Context, directory, prefix str
|
|||
}
|
||||
|
||||
for _, item := range fileList {
|
||||
logger := log.Ctx(ctx).With().Str("filename", item.Name()).Logger()
|
||||
|
||||
itemPath := filepath.Join(directory, item.Name())
|
||||
filename := item.Name()
|
||||
if prefix != "" {
|
||||
|
|
@ -407,10 +416,8 @@ func (p *Provider) collectFileConfigs(ctx context.Context, directory, prefix str
|
|||
continue
|
||||
}
|
||||
|
||||
switch strings.ToLower(filepath.Ext(item.Name())) {
|
||||
case ".toml", ".yaml", ".yml":
|
||||
// noop
|
||||
default:
|
||||
if !isFileSupported(item.Name()) {
|
||||
logger.Debug().Msg("Skipping file, unsupported extension")
|
||||
continue
|
||||
}
|
||||
|
||||
|
|
@ -500,3 +507,12 @@ func readFile(filename string) (string, error) {
|
|||
}
|
||||
return "", fmt.Errorf("invalid filename: %s", filename)
|
||||
}
|
||||
|
||||
func isFileSupported(filename string) bool {
|
||||
switch strings.ToLower(filepath.Ext(filename)) {
|
||||
case ".toml", ".yaml", ".yml":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -197,6 +197,38 @@ func TestProvideWithWatch(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestProvideWatchWithNonConfigDanglingSymlink(t *testing.T) {
|
||||
tempDir := t.TempDir()
|
||||
|
||||
err := copyFile("./fixtures/yaml/simple_file_01.yml", filepath.Join(tempDir, "simple_file_01.yml"))
|
||||
require.NoError(t, err)
|
||||
|
||||
err = os.Symlink(filepath.Join(tempDir, "non_existent_file.txt"), filepath.Join(tempDir, "dangling_symlink.txt"))
|
||||
require.NoError(t, err)
|
||||
|
||||
provider := &Provider{
|
||||
Directory: tempDir,
|
||||
Watch: true,
|
||||
}
|
||||
configChan := make(chan dynamic.Message)
|
||||
go func() {
|
||||
err := provider.Provide(configChan, safe.NewPool(t.Context()))
|
||||
assert.NoError(t, err)
|
||||
}()
|
||||
|
||||
timeout := time.After(time.Second)
|
||||
select {
|
||||
case conf := <-configChan:
|
||||
require.NotNil(t, conf.Configuration.HTTP)
|
||||
numServices := len(conf.Configuration.HTTP.Services) + len(conf.Configuration.TCP.Services) + len(conf.Configuration.UDP.Services)
|
||||
numRouters := len(conf.Configuration.HTTP.Routers) + len(conf.Configuration.TCP.Routers) + len(conf.Configuration.UDP.Routers)
|
||||
assert.Equal(t, 6, numServices)
|
||||
assert.Equal(t, 3, numRouters)
|
||||
case <-timeout:
|
||||
t.Errorf("timeout while waiting for config")
|
||||
}
|
||||
}
|
||||
|
||||
func getTestCases() []ProvideTestCase {
|
||||
return []ProvideTestCase{
|
||||
{
|
||||
|
|
|
|||
|
|
@ -49,7 +49,10 @@ type clientWrapper struct {
|
|||
experimentalChannel bool
|
||||
}
|
||||
|
||||
func createClientFromConfig(c *rest.Config) (*clientWrapper, error) {
|
||||
func createClientFromConfig(c *rest.Config, qps, burst int) (*clientWrapper, error) {
|
||||
c.QPS = float32(qps)
|
||||
c.Burst = burst
|
||||
|
||||
csGateway, err := gateclientset.NewForConfig(c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
@ -75,7 +78,7 @@ func newClientImpl(csKube kclientset.Interface, csGateway gateclientset.Interfac
|
|||
|
||||
// newInClusterClient returns a new Provider client that is expected to run
|
||||
// inside the cluster.
|
||||
func newInClusterClient(endpoint string) (*clientWrapper, error) {
|
||||
func newInClusterClient(endpoint string, qps, burst int) (*clientWrapper, error) {
|
||||
config, err := rest.InClusterConfig()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create in-cluster configuration: %w", err)
|
||||
|
|
@ -85,20 +88,20 @@ func newInClusterClient(endpoint string) (*clientWrapper, error) {
|
|||
config.Host = endpoint
|
||||
}
|
||||
|
||||
return createClientFromConfig(config)
|
||||
return createClientFromConfig(config, qps, burst)
|
||||
}
|
||||
|
||||
func newExternalClusterClientFromFile(file string) (*clientWrapper, error) {
|
||||
func newExternalClusterClientFromFile(file string, qps, burst int) (*clientWrapper, error) {
|
||||
configFromFlags, err := clientcmd.BuildConfigFromFlags("", file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return createClientFromConfig(configFromFlags)
|
||||
return createClientFromConfig(configFromFlags, qps, burst)
|
||||
}
|
||||
|
||||
// newExternalClusterClient returns a new Provider client that may run outside of the cluster.
|
||||
// The endpoint parameter must not be empty.
|
||||
func newExternalClusterClient(endpoint, caFilePath string, token types.FileOrContent) (*clientWrapper, error) {
|
||||
func newExternalClusterClient(endpoint, caFilePath string, token types.FileOrContent, qps, burst int) (*clientWrapper, error) {
|
||||
if endpoint == "" {
|
||||
return nil, errors.New("endpoint missing for external cluster client")
|
||||
}
|
||||
|
|
@ -122,7 +125,7 @@ func newExternalClusterClient(endpoint, caFilePath string, token types.FileOrCon
|
|||
config.TLSClientConfig = rest.TLSClientConfig{CAData: caData}
|
||||
}
|
||||
|
||||
return createClientFromConfig(config)
|
||||
return createClientFromConfig(config, qps, burst)
|
||||
}
|
||||
|
||||
// WatchAll starts namespace-specific controllers for all relevant kinds.
|
||||
|
|
@ -718,20 +721,27 @@ func (c *clientWrapper) UpdateBackendTLSPolicyStatus(ctx context.Context, policy
|
|||
ancestorStatuses := make([]gatev1.PolicyAncestorStatus, len(status.Ancestors))
|
||||
copy(ancestorStatuses, status.Ancestors)
|
||||
|
||||
// keep statuses added by other gateway controllers,
|
||||
// and statuses for Traefik gateway controller but not for the same Gateway as the one in parameter (AncestorRef).
|
||||
for _, ancestorStatus := range currentPolicy.Status.Ancestors {
|
||||
// Keep statuses added by other gateway controllers.
|
||||
if ancestorStatus.ControllerName != controllerName {
|
||||
ancestorStatuses = append(ancestorStatuses, ancestorStatus)
|
||||
continue
|
||||
}
|
||||
|
||||
// Keep statuses added by Traefik for other ancestors.
|
||||
// A BackendTLSPolicy can target services attached to different listeners.
|
||||
if !slices.ContainsFunc(status.Ancestors, func(s gatev1.PolicyAncestorStatus) bool {
|
||||
return reflect.DeepEqual(s.AncestorRef, ancestorStatus.AncestorRef)
|
||||
}) {
|
||||
ancestorStatuses = append(ancestorStatuses, ancestorStatus)
|
||||
}
|
||||
}
|
||||
|
||||
if len(ancestorStatuses) > 16 {
|
||||
return fmt.Errorf("failed to update BackendTLSPolicy %s/%s status: PolicyAncestor statuses count exceeds 16", policy.Namespace, policy.Name)
|
||||
}
|
||||
|
||||
// do not update status when nothing has changed.
|
||||
// Do not update status when nothing has changed.
|
||||
if policyAncestorStatusesEqual(currentPolicy.Status.Ancestors, ancestorStatuses) {
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -468,6 +468,12 @@ func (p *Provider) loadHTTPServers(ctx context.Context, namespace string, route
|
|||
var serversTransport *dynamic.ServersTransport
|
||||
for _, policy := range backendTLSPolicies {
|
||||
for _, targetRef := range policy.Spec.TargetRefs {
|
||||
// Skip targetRefs that doesn't match the backendRef,
|
||||
// since a BackendTLSPolicy can select multiple services.
|
||||
if targetRef.Name != backendRef.Name {
|
||||
continue
|
||||
}
|
||||
// Skip the targetRef if the sectionName doesn't match the backendRef port.
|
||||
if targetRef.SectionName != nil && svcPort.Name != string(*targetRef.SectionName) {
|
||||
continue
|
||||
}
|
||||
|
|
|
|||
|
|
@ -65,6 +65,8 @@ const (
|
|||
type Provider struct {
|
||||
Endpoint string `description:"Kubernetes server endpoint (required for external cluster client)." json:"endpoint,omitempty" toml:"endpoint,omitempty" yaml:"endpoint,omitempty"`
|
||||
Token types.FileOrContent `description:"Kubernetes bearer token (not needed for in-cluster client). It accepts either a token value or a file path to the token." json:"token,omitempty" toml:"token,omitempty" yaml:"token,omitempty" loggable:"false"`
|
||||
QPS int `description:"Defines the maximum QPS to the Kubernetes API server. Setting this to a negative value will disable client-side ratelimiting." json:"qps,omitempty" toml:"qps,omitempty" yaml:"qps,omitempty" export:"true"`
|
||||
Burst int `description:"Defines the maximum burst of requests to the Kubernetes API server." json:"burst,omitempty" toml:"burst,omitempty" yaml:"burst,omitempty" export:"true"`
|
||||
CertAuthFilePath string `description:"Kubernetes certificate authority file path (not needed for in-cluster client)." json:"certAuthFilePath,omitempty" toml:"certAuthFilePath,omitempty" yaml:"certAuthFilePath,omitempty"`
|
||||
Namespaces []string `description:"Kubernetes namespaces." json:"namespaces,omitempty" toml:"namespaces,omitempty" yaml:"namespaces,omitempty" export:"true"`
|
||||
LabelSelector string `description:"Kubernetes label selector to select specific GatewayClasses." json:"labelSelector,omitempty" toml:"labelSelector,omitempty" yaml:"labelSelector,omitempty" export:"true"`
|
||||
|
|
@ -86,6 +88,11 @@ type Provider struct {
|
|||
client *clientWrapper
|
||||
}
|
||||
|
||||
func (p *Provider) SetDefaults() {
|
||||
p.QPS = 50 // the default value for the QPS is 10x the default Kubernetes client QPS value.
|
||||
p.Burst = 100 // the default value for the Burst is 10x the default Kubernetes client Burst value.
|
||||
}
|
||||
|
||||
// Entrypoint defines the available entry points.
|
||||
type Entrypoint struct {
|
||||
Address string
|
||||
|
|
@ -275,13 +282,13 @@ func (p *Provider) newK8sClient(ctx context.Context) (*clientWrapper, error) {
|
|||
switch {
|
||||
case os.Getenv("KUBERNETES_SERVICE_HOST") != "" && os.Getenv("KUBERNETES_SERVICE_PORT") != "":
|
||||
logger.Info().Str("endpoint", p.Endpoint).Msg("Creating in-cluster Provider client")
|
||||
client, err = newInClusterClient(p.Endpoint)
|
||||
client, err = newInClusterClient(p.Endpoint, p.QPS, p.Burst)
|
||||
case os.Getenv("KUBECONFIG") != "":
|
||||
logger.Info().Msgf("Creating cluster-external Provider client from KUBECONFIG %s", os.Getenv("KUBECONFIG"))
|
||||
client, err = newExternalClusterClientFromFile(os.Getenv("KUBECONFIG"))
|
||||
client, err = newExternalClusterClientFromFile(os.Getenv("KUBECONFIG"), p.QPS, p.Burst)
|
||||
default:
|
||||
logger.Info().Str("endpoint", p.Endpoint).Msg("Creating cluster-external Provider client")
|
||||
client, err = newExternalClusterClient(p.Endpoint, p.CertAuthFilePath, p.Token)
|
||||
client, err = newExternalClusterClient(p.Endpoint, p.CertAuthFilePath, p.Token, p.QPS, p.Burst)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,50 @@
|
|||
---
|
||||
kind: Ingress
|
||||
apiVersion: networking.k8s.io/v1
|
||||
metadata:
|
||||
name: ""
|
||||
namespace: testing
|
||||
annotations:
|
||||
traefik.ingress.kubernetes.io/router.pathmatcher: 'Host("injection") || PathPrefix'
|
||||
spec:
|
||||
rules:
|
||||
- http:
|
||||
paths:
|
||||
- path: /bar
|
||||
pathType: ImplementationSpecific
|
||||
backend:
|
||||
service:
|
||||
name: service1
|
||||
port:
|
||||
number: 80
|
||||
|
||||
---
|
||||
kind: Service
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: service1
|
||||
namespace: testing
|
||||
|
||||
spec:
|
||||
ports:
|
||||
- port: 80
|
||||
clusterIP: 10.0.0.1
|
||||
|
||||
---
|
||||
kind: EndpointSlice
|
||||
apiVersion: discovery.k8s.io/v1
|
||||
metadata:
|
||||
name: service1
|
||||
namespace: testing
|
||||
labels:
|
||||
kubernetes.io/service-name: service1
|
||||
|
||||
addressType: IPv4
|
||||
ports:
|
||||
- port: 8080
|
||||
name: ""
|
||||
endpoints:
|
||||
- addresses:
|
||||
- 10.10.0.1
|
||||
conditions:
|
||||
ready: true
|
||||
|
|
@ -395,7 +395,14 @@ func (p *Provider) loadConfigurationFromIngresses(ctx context.Context, client Cl
|
|||
serviceName := provider.Normalize(ingress.Namespace + "-" + pa.Backend.Service.Name + "-" + portString(pa.Backend.Service.Port))
|
||||
conf.HTTP.Services[serviceName] = service
|
||||
|
||||
rt := p.loadRouter(ingress, rule, pa, rtConfig, serviceName)
|
||||
rt, err := p.loadRouter(ingress, rule, pa, rtConfig, serviceName)
|
||||
if err != nil {
|
||||
logger.Error().Err(err).
|
||||
Str("serviceName", pa.Backend.Service.Name).
|
||||
Str("path", pa.Path).
|
||||
Msg("Skipping path.")
|
||||
continue
|
||||
}
|
||||
|
||||
p.applyRouterTransform(ctxIngress, rt, ingress)
|
||||
|
||||
|
|
@ -721,7 +728,7 @@ func (p *Provider) loadService(client Client, namespace string, backend netv1.In
|
|||
return svc, nil
|
||||
}
|
||||
|
||||
func (p *Provider) loadRouter(ingress *netv1.Ingress, rule netv1.IngressRule, pa netv1.HTTPIngressPath, rtConfig *RouterConfig, serviceName string) *dynamic.Router {
|
||||
func (p *Provider) loadRouter(ingress *netv1.Ingress, rule netv1.IngressRule, pa netv1.HTTPIngressPath, rtConfig *RouterConfig, serviceName string) (*dynamic.Router, error) {
|
||||
rt := &dynamic.Router{
|
||||
Service: serviceName,
|
||||
Observability: &dynamic.RouterObservabilityConfig{
|
||||
|
|
@ -765,6 +772,12 @@ func (p *Provider) loadRouter(ingress *netv1.Ingress, rule netv1.IngressRule, pa
|
|||
|
||||
if pa.PathType == nil || *pa.PathType == "" || *pa.PathType == netv1.PathTypeImplementationSpecific {
|
||||
if rtConfig != nil && rtConfig.Router != nil && rtConfig.Router.PathMatcher != "" {
|
||||
switch rtConfig.Router.PathMatcher {
|
||||
case "Path", "PathPrefix", "PathRegexp":
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid router path matcher %q: must be one of Path, PathPrefix, PathRegexp", rtConfig.Router.PathMatcher)
|
||||
}
|
||||
|
||||
matcher = rtConfig.Router.PathMatcher
|
||||
}
|
||||
} else if *pa.PathType == netv1.PathTypeExact {
|
||||
|
|
@ -775,7 +788,7 @@ func (p *Provider) loadRouter(ingress *netv1.Ingress, rule netv1.IngressRule, pa
|
|||
}
|
||||
|
||||
rt.Rule = strings.Join(rules, " && ")
|
||||
return rt
|
||||
return rt, nil
|
||||
}
|
||||
|
||||
func buildHostRuleV2(host string) string {
|
||||
|
|
|
|||
|
|
@ -2170,6 +2170,31 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
|
|||
},
|
||||
strictPrefixMatching: true,
|
||||
},
|
||||
{
|
||||
desc: "Ingress with invalid pathmatcher annotation",
|
||||
expected: &dynamic.Configuration{
|
||||
HTTP: &dynamic.HTTPConfiguration{
|
||||
Middlewares: map[string]*dynamic.Middleware{},
|
||||
Routers: map[string]*dynamic.Router{},
|
||||
Services: map[string]*dynamic.Service{
|
||||
"testing-service1-80": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
Strategy: dynamic.BalancerStrategyWRR,
|
||||
PassHostHeader: pointer(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://10.10.0.1:8080",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
|
|
|
|||
|
|
@ -57,8 +57,6 @@ func TestObservabilityRoundTripper_metrics(t *testing.T) {
|
|||
|
||||
for _, test := range tests {
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var cfg otypes.OTLP
|
||||
(&cfg).SetDefaults()
|
||||
cfg.AddRoutersLabels = true
|
||||
|
|
|
|||
|
|
@ -1,16 +1,18 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/go-acme/lego/v4/challenge/tlsalpn01"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/traefik/traefik/v3/pkg/config/dynamic"
|
||||
httpmuxer "github.com/traefik/traefik/v3/pkg/muxer/http"
|
||||
"github.com/traefik/traefik/v3/pkg/observability/logs"
|
||||
otypes "github.com/traefik/traefik/v3/pkg/observability/types"
|
||||
"github.com/traefik/traefik/v3/pkg/server/provider"
|
||||
"github.com/traefik/traefik/v3/pkg/tls"
|
||||
traefiktls "github.com/traefik/traefik/v3/pkg/tls"
|
||||
)
|
||||
|
||||
func mergeConfiguration(configurations dynamic.Configurations, defaultEntryPoints []string) dynamic.Configuration {
|
||||
|
|
@ -36,8 +38,8 @@ func mergeConfiguration(configurations dynamic.Configurations, defaultEntryPoint
|
|||
Services: make(map[string]*dynamic.UDPService),
|
||||
},
|
||||
TLS: &dynamic.TLSConfiguration{
|
||||
Stores: make(map[string]tls.Store),
|
||||
Options: make(map[string]tls.Options),
|
||||
Stores: make(map[string]traefiktls.Store),
|
||||
Options: make(map[string]traefiktls.Options),
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -134,7 +136,7 @@ func mergeConfiguration(configurations dynamic.Configurations, defaultEntryPoint
|
|||
}
|
||||
|
||||
for key, store := range configuration.TLS.Stores {
|
||||
if key != tls.DefaultTLSStoreName {
|
||||
if key != traefiktls.DefaultTLSStoreName {
|
||||
key = provider.MakeQualifiedName(pvd, key)
|
||||
} else {
|
||||
defaultTLSStoreProviders = append(defaultTLSStoreProviders, pvd)
|
||||
|
|
@ -156,21 +158,97 @@ func mergeConfiguration(configurations dynamic.Configurations, defaultEntryPoint
|
|||
|
||||
if len(defaultTLSStoreProviders) > 1 {
|
||||
log.Error().Msgf("Default TLS Store defined in multiple providers: %v", defaultTLSStoreProviders)
|
||||
delete(conf.TLS.Stores, tls.DefaultTLSStoreName)
|
||||
delete(conf.TLS.Stores, traefiktls.DefaultTLSStoreName)
|
||||
}
|
||||
|
||||
if len(defaultTLSOptionProviders) == 0 {
|
||||
conf.TLS.Options[tls.DefaultTLSConfigName] = tls.DefaultTLSOptions
|
||||
conf.TLS.Options[traefiktls.DefaultTLSConfigName] = traefiktls.DefaultTLSOptions
|
||||
} else if len(defaultTLSOptionProviders) > 1 {
|
||||
log.Error().Msgf("Default TLS Options defined in multiple providers %v", defaultTLSOptionProviders)
|
||||
// We do not set an empty tls.TLS{} as above so that we actually get a "cascading failure" later on,
|
||||
// i.e. routers depending on this missing TLS option will fail to initialize as well.
|
||||
delete(conf.TLS.Options, tls.DefaultTLSConfigName)
|
||||
delete(conf.TLS.Options, traefiktls.DefaultTLSConfigName)
|
||||
}
|
||||
|
||||
return conf
|
||||
}
|
||||
|
||||
func resolveHTTPTLSOptions(cfg dynamic.Configuration) dynamic.Configuration {
|
||||
if cfg.HTTP == nil || len(cfg.HTTP.Routers) == 0 {
|
||||
return cfg
|
||||
}
|
||||
|
||||
rts := make(map[string]*dynamic.Router)
|
||||
|
||||
// Keyed by domain, then by options reference.
|
||||
// The actual source of truth for what TLS options will actually be used for the connection.
|
||||
// As opposed to tlsOptionsForHost, it keeps track of all the (different) TLS
|
||||
// options that occur for a given host name, so that later on we can set relevant
|
||||
// errors and logging for all the routers concerned (i.e. wrongly configured).
|
||||
tlsOptionsForHostSNI := map[string]map[string][]string{}
|
||||
|
||||
for routerHTTPName, routerHTTPConfig := range cfg.HTTP.Routers {
|
||||
rts[routerHTTPName] = routerHTTPConfig.DeepCopy()
|
||||
|
||||
if routerHTTPConfig.TLS == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
ctxRouter := provider.AddInContext(context.Background(), routerHTTPName)
|
||||
logger := log.Ctx(ctxRouter).With().Str(logs.RouterName, routerHTTPName).Logger()
|
||||
|
||||
tlsOptionsName := traefiktls.DefaultTLSConfigName
|
||||
if len(routerHTTPConfig.TLS.Options) > 0 && routerHTTPConfig.TLS.Options != traefiktls.DefaultTLSConfigName {
|
||||
tlsOptionsName = provider.GetQualifiedName(ctxRouter, routerHTTPConfig.TLS.Options)
|
||||
}
|
||||
|
||||
domains, err := httpmuxer.ParseDomains(routerHTTPConfig.Rule)
|
||||
if err != nil {
|
||||
logger.Error().Err(err).Msgf("Invalid rule %s", routerHTTPConfig.Rule)
|
||||
continue
|
||||
}
|
||||
|
||||
if len(domains) == 0 {
|
||||
rts[routerHTTPName].TLS.ResolvedOptions = "default"
|
||||
logger.Warn().Msgf("No domain found in rule %v, the TLS options applied for this router will depend on the SNI of each request", routerHTTPConfig.Rule)
|
||||
}
|
||||
|
||||
for _, domain := range domains {
|
||||
// domain is already in lower case thanks to the domain parsing
|
||||
if tlsOptionsForHostSNI[domain] == nil {
|
||||
tlsOptionsForHostSNI[domain] = make(map[string][]string)
|
||||
}
|
||||
tlsOptionsForHostSNI[domain][tlsOptionsName] = append(tlsOptionsForHostSNI[domain][tlsOptionsName], routerHTTPName)
|
||||
}
|
||||
}
|
||||
|
||||
for hostSNI, tlsConfigs := range tlsOptionsForHostSNI {
|
||||
if len(tlsConfigs) == 1 {
|
||||
for optionsName, v := range tlsConfigs {
|
||||
log.Debug().Msgf("Adding route for %s with TLS options %s", hostSNI, optionsName)
|
||||
for _, s := range v {
|
||||
rts[s].TLS.ResolvedOptions = optionsName
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// multiple tlsConfigs
|
||||
routers := make([]string, 0, len(tlsConfigs))
|
||||
for _, v := range tlsConfigs {
|
||||
for _, s := range v {
|
||||
rts[s].TLS.ResolvedOptions = traefiktls.DefaultTLSConfigName
|
||||
routers = append(routers, s)
|
||||
}
|
||||
}
|
||||
|
||||
log.Warn().Msgf("Found different TLS options for routers on the same host %v, so using the default TLS options instead for these routers: %#v", hostSNI, routers)
|
||||
}
|
||||
|
||||
cfg.HTTP.Routers = rts
|
||||
return cfg
|
||||
}
|
||||
|
||||
func applyModel(cfg dynamic.Configuration) dynamic.Configuration {
|
||||
if cfg.HTTP != nil && len(cfg.HTTP.Models) > 0 {
|
||||
rts := make(map[string]*dynamic.Router)
|
||||
|
|
|
|||
|
|
@ -176,6 +176,7 @@ func (c *ConfigurationWatcher) applyConfigurations(ctx context.Context) {
|
|||
|
||||
conf := mergeConfiguration(newConfigs.DeepCopy(), c.defaultEntryPoints)
|
||||
conf = applyModel(conf)
|
||||
conf = resolveHTTPTLSOptions(conf)
|
||||
|
||||
for _, listener := range c.configurationListeners {
|
||||
listener(conf)
|
||||
|
|
|
|||
|
|
@ -992,3 +992,52 @@ func TestConfigurationWatcher_MultipleTransformers(t *testing.T) {
|
|||
assert.Equal(t, 1, callCount1)
|
||||
assert.Equal(t, 1, callCount2)
|
||||
}
|
||||
|
||||
// TestEntryPointTLSResolvedOptions is a regression test for
|
||||
// https://github.com/traefik/traefik/issues/13289: a router whose TLS
|
||||
// configuration comes from the entry point (and not from an explicit router TLS
|
||||
// section) must still have its TLS options resolved in the published configuration.
|
||||
func TestEntryPointTLSResolvedOptions(t *testing.T) {
|
||||
routinesPool := safe.NewPool(t.Context())
|
||||
t.Cleanup(routinesPool.Stop)
|
||||
|
||||
pvd := &mockProvider{
|
||||
messages: []dynamic.Message{{
|
||||
ProviderName: "internal",
|
||||
Configuration: &dynamic.Configuration{
|
||||
HTTP: &dynamic.HTTPConfiguration{
|
||||
Routers: map[string]*dynamic.Router{
|
||||
"foo": {
|
||||
EntryPoints: []string{"websecure"},
|
||||
Rule: "Host(`foo.example.com`)",
|
||||
Service: "service",
|
||||
},
|
||||
},
|
||||
Models: map[string]*dynamic.Model{
|
||||
"websecure": {
|
||||
TLS: &dynamic.RouterTLSConfig{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}},
|
||||
}
|
||||
|
||||
watcher := NewConfigurationWatcher(routinesPool, pvd, []string{}, "")
|
||||
|
||||
run := make(chan struct{})
|
||||
watcher.AddListener(func(conf dynamic.Configuration) {
|
||||
router := conf.HTTP.Routers["foo@internal"]
|
||||
if router == nil || router.TLS == nil {
|
||||
return
|
||||
}
|
||||
|
||||
assert.Equal(t, "default", router.TLS.ResolvedOptions)
|
||||
close(run)
|
||||
})
|
||||
|
||||
watcher.Start()
|
||||
t.Cleanup(watcher.Stop)
|
||||
|
||||
<-run
|
||||
}
|
||||
|
|
|
|||
29
pkg/server/conncontext.go
Normal file
29
pkg/server/conncontext.go
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
)
|
||||
|
||||
type connContextFunc func(context.Context, net.Conn) context.Context
|
||||
|
||||
type multipleConnContext struct {
|
||||
fns []connContextFunc
|
||||
}
|
||||
|
||||
func (m *multipleConnContext) AddConnContextFunc(fn connContextFunc) {
|
||||
m.fns = append(m.fns, fn)
|
||||
}
|
||||
|
||||
func (m *multipleConnContext) Build() connContextFunc {
|
||||
if len(m.fns) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return func(ctx context.Context, c net.Conn) context.Context {
|
||||
for _, contextFunc := range m.fns {
|
||||
ctx = contextFunc(ctx, c)
|
||||
}
|
||||
return ctx
|
||||
}
|
||||
}
|
||||
26
pkg/server/conncontext_test.go
Normal file
26
pkg/server/conncontext_test.go
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type keyTest string
|
||||
|
||||
func TestConnContext(t *testing.T) {
|
||||
var connContext multipleConnContext
|
||||
connContext.AddConnContextFunc(func(ctx context.Context, _ net.Conn) context.Context {
|
||||
return context.WithValue(ctx, keyTest("test"), "test")
|
||||
})
|
||||
connContext.AddConnContextFunc(func(ctx context.Context, _ net.Conn) context.Context {
|
||||
return context.WithValue(ctx, keyTest("test2"), "test2")
|
||||
})
|
||||
|
||||
ctx := connContext.Build()(context.Background(), nil)
|
||||
|
||||
require.Equal(t, "test", ctx.Value(keyTest("test")))
|
||||
require.Equal(t, "test2", ctx.Value(keyTest("test2")))
|
||||
}
|
||||
|
|
@ -19,6 +19,7 @@ import (
|
|||
metricsMiddle "github.com/traefik/traefik/v3/pkg/middlewares/metrics"
|
||||
"github.com/traefik/traefik/v3/pkg/middlewares/observability"
|
||||
"github.com/traefik/traefik/v3/pkg/middlewares/recovery"
|
||||
"github.com/traefik/traefik/v3/pkg/middlewares/snicheck"
|
||||
httpmuxer "github.com/traefik/traefik/v3/pkg/muxer/http"
|
||||
"github.com/traefik/traefik/v3/pkg/observability/logs"
|
||||
"github.com/traefik/traefik/v3/pkg/server/middleware"
|
||||
|
|
@ -375,6 +376,12 @@ func (m *Manager) buildHTTPHandler(ctx context.Context, router *runtime.RouterIn
|
|||
})
|
||||
}
|
||||
|
||||
if router.TLS != nil {
|
||||
chain = chain.Append(func(next http.Handler) (http.Handler, error) {
|
||||
return snicheck.New(routerName, router.TLS.ResolvedOptions, next), nil
|
||||
})
|
||||
}
|
||||
|
||||
mHandler := m.middlewaresBuilder.BuildMiddlewareChain(ctx, router.Middlewares)
|
||||
|
||||
return chain.Extend(*mHandler).Then(nextHandler)
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ package tcp
|
|||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
|
|
@ -11,7 +10,6 @@ import (
|
|||
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/traefik/traefik/v3/pkg/config/runtime"
|
||||
"github.com/traefik/traefik/v3/pkg/middlewares/snicheck"
|
||||
"github.com/traefik/traefik/v3/pkg/muxer"
|
||||
httpmuxer "github.com/traefik/traefik/v3/pkg/muxer/http"
|
||||
tcpmuxer "github.com/traefik/traefik/v3/pkg/muxer/tcp"
|
||||
|
|
@ -97,11 +95,6 @@ func (m *Manager) getHTTPRouters(ctx context.Context, entryPoints []string, tls
|
|||
return make(map[string]map[string]*runtime.RouterInfo)
|
||||
}
|
||||
|
||||
type nameAndConfig struct {
|
||||
routerName string // just so we have it as additional information when logging
|
||||
TLSConfig *tls.Config
|
||||
}
|
||||
|
||||
func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string]*runtime.TCPRouterInfo, configsHTTP map[string]*runtime.RouterInfo, handlerHTTP, handlerHTTPS http.Handler) (*Router, error) {
|
||||
// Build a new Router.
|
||||
router, err := NewRouter(m.providersPrecedence)
|
||||
|
|
@ -119,18 +112,6 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string
|
|||
log.Ctx(ctx).Error().Err(err).Msg("Error during the build of the default TLS configuration")
|
||||
}
|
||||
|
||||
// Keyed by domain. The source of truth for doing SNI checking (domain fronting).
|
||||
// As soon as there's (at least) two different tlsOptions found for the same domain,
|
||||
// we set the value to the default TLS conf.
|
||||
tlsOptionsForHost := map[string]string{}
|
||||
|
||||
// Keyed by domain, then by options reference.
|
||||
// The actual source of truth for what TLS options will actually be used for the connection.
|
||||
// As opposed to tlsOptionsForHost, it keeps track of all the (different) TLS
|
||||
// options that occur for a given host name, so that later on we can set relevant
|
||||
// errors and logging for all the routers concerned (i.e. wrongly configured).
|
||||
tlsOptionsForHostSNI := map[string]map[string]nameAndConfig{}
|
||||
|
||||
for routerHTTPName, routerHTTPConfig := range configsHTTP {
|
||||
if routerHTTPConfig.TLS == nil {
|
||||
continue
|
||||
|
|
@ -139,6 +120,8 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string
|
|||
logger := log.Ctx(ctx).With().Str(logs.RouterName, routerHTTPName).Logger()
|
||||
ctxRouter := logger.WithContext(provider.AddInContext(ctx, routerHTTPName))
|
||||
|
||||
// Even if the TLS options mismatch between the configured and the resolved one is handled in the aggregator
|
||||
// we also have to handle it here to be able to mark the router in error.
|
||||
tlsOptionsName := traefiktls.DefaultTLSConfigName
|
||||
if len(routerHTTPConfig.TLS.Options) > 0 && routerHTTPConfig.TLS.Options != traefiktls.DefaultTLSConfigName {
|
||||
tlsOptionsName = provider.GetQualifiedName(ctxRouter, routerHTTPConfig.TLS.Options)
|
||||
|
|
@ -158,7 +141,7 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string
|
|||
// This is only about choosing the TLS configuration.
|
||||
// The actual routing will be done further on by the HTTPS handler.
|
||||
// See examples below.
|
||||
router.AddHTTPTLSConfig("*", defaultTLSConf)
|
||||
router.AddHTTPTLSConfig("*", defaultTLSConf, traefiktls.DefaultTLSConfigName)
|
||||
|
||||
// The server name (from a Host(SNI) rule) is the only parameter (available in HTTP routing rules) on which we can map a TLS config,
|
||||
// because it is the only one accessible before decryption (we obtain it during the ClientHello).
|
||||
|
|
@ -185,82 +168,41 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string
|
|||
// # Otherwise, it will fallback to the default TLS config.
|
||||
if tlsOptionsName != traefiktls.DefaultTLSConfigName {
|
||||
logger.Warn().Msgf("No domain found in rule %v, the TLS option %s cannot be applied", routerHTTPConfig.Rule, tlsOptionsName)
|
||||
routerHTTPConfig.AddError(fmt.Errorf("no domain found in rule %v, the TLS option %s cannot be applied", routerHTTPConfig.Rule, tlsOptionsName), false)
|
||||
}
|
||||
}
|
||||
|
||||
if len(domains) > 0 && routerHTTPConfig.TLS.ResolvedOptions != tlsOptionsName {
|
||||
logger.Warn().Msg("Found different TLS options for routers on the same host, so using the default TLS options instead.")
|
||||
routerHTTPConfig.AddError(errors.New("found different TLS options for routers on the same host, so using the default TLS options instead"), false)
|
||||
}
|
||||
|
||||
// Even though the error is seemingly ignored (aside from logging it),
|
||||
// we actually rely later on the fact that a tls config is nil (which happens when an error is returned) to take special steps
|
||||
// when assigning a handler to a route.
|
||||
tlsConf, tlsConfErr := m.tlsManager.Get(traefiktls.DefaultTLSStoreName, tlsOptionsName)
|
||||
tlsConf, tlsConfErr := m.tlsManager.Get(traefiktls.DefaultTLSStoreName, routerHTTPConfig.TLS.ResolvedOptions)
|
||||
if tlsConfErr != nil {
|
||||
// Note: we do not call AddError here because we already did so when buildRouterHandler errored for the same reason.
|
||||
logger.Error().Err(tlsConfErr).Send()
|
||||
}
|
||||
|
||||
for _, domain := range domains {
|
||||
// domain is already in lower case thanks to the domain parsing
|
||||
if tlsOptionsForHostSNI[domain] == nil {
|
||||
tlsOptionsForHostSNI[domain] = make(map[string]nameAndConfig)
|
||||
}
|
||||
tlsOptionsForHostSNI[domain][tlsOptionsName] = nameAndConfig{
|
||||
routerName: routerHTTPName,
|
||||
TLSConfig: tlsConf,
|
||||
}
|
||||
|
||||
if name, ok := tlsOptionsForHost[domain]; ok && name != tlsOptionsName {
|
||||
// Different tlsOptions on the same domain, so fallback to default
|
||||
tlsOptionsForHost[domain] = traefiktls.DefaultTLSConfigName
|
||||
} else {
|
||||
tlsOptionsForHost[domain] = tlsOptionsName
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sniCheck := snicheck.New(tlsOptionsForHost, handlerHTTPS)
|
||||
|
||||
// Keep in mind that defaultTLSConf might be nil here.
|
||||
router.SetHTTPSHandler(sniCheck, defaultTLSConf)
|
||||
|
||||
logger := log.Ctx(ctx)
|
||||
for hostSNI, tlsConfigs := range tlsOptionsForHostSNI {
|
||||
if len(tlsConfigs) == 1 {
|
||||
var optionsName string
|
||||
var config *tls.Config
|
||||
for k, v := range tlsConfigs {
|
||||
optionsName = k
|
||||
config = v.TLSConfig
|
||||
break
|
||||
}
|
||||
|
||||
if config == nil {
|
||||
if tlsConf == nil {
|
||||
// we use nil config as a signal to insert a handler
|
||||
// that enforces that TLS connection attempts to the corresponding (broken) router should fail.
|
||||
logger.Debug().Msgf("Adding special closing route for %s because broken TLS options %s", hostSNI, optionsName)
|
||||
router.AddHTTPTLSConfig(hostSNI, nil)
|
||||
logger.Debug().Msgf("Adding special closing route for %s because of a broken TLS options %s", domain, routerHTTPConfig.TLS.ResolvedOptions)
|
||||
router.AddHTTPTLSConfig(domain, nil, "")
|
||||
continue
|
||||
}
|
||||
|
||||
logger.Debug().Msgf("Adding route for %s with TLS options %s", hostSNI, optionsName)
|
||||
router.AddHTTPTLSConfig(hostSNI, config)
|
||||
continue
|
||||
logger.Debug().Msgf("Adding route for %s with TLS options %s", domain, routerHTTPConfig.TLS.ResolvedOptions)
|
||||
router.AddHTTPTLSConfig(domain, tlsConf, routerHTTPConfig.TLS.ResolvedOptions)
|
||||
}
|
||||
|
||||
// multiple tlsConfigs
|
||||
|
||||
routers := make([]string, 0, len(tlsConfigs))
|
||||
for _, v := range tlsConfigs {
|
||||
configsHTTP[v.routerName].AddError(fmt.Errorf("found different TLS options for routers on the same host %v, so using the default TLS options instead", hostSNI), false)
|
||||
routers = append(routers, v.routerName)
|
||||
}
|
||||
|
||||
logger.Warn().Msgf("Found different TLS options for routers on the same host %v, so using the default TLS options instead for these routers: %#v", hostSNI, routers)
|
||||
if defaultTLSConf == nil {
|
||||
logger.Debug().Msgf("Adding special closing route for %s because broken default TLS options", hostSNI)
|
||||
}
|
||||
|
||||
router.AddHTTPTLSConfig(hostSNI, defaultTLSConf)
|
||||
}
|
||||
|
||||
// Keep in mind that defaultTLSConf might be nil here.
|
||||
router.SetHTTPSHandler(handlerHTTPS, defaultTLSConf)
|
||||
|
||||
m.addTCPHandlers(ctx, configs, router)
|
||||
|
||||
return router, nil
|
||||
|
|
@ -399,8 +341,9 @@ func (m *Manager) addTCPHandlers(ctx context.Context, configs map[string]*runtim
|
|||
}
|
||||
|
||||
handler = &tcp.TLSHandler{
|
||||
Next: handler,
|
||||
Config: tlsConf,
|
||||
Next: handler,
|
||||
Config: tlsConf,
|
||||
TLSOptionsName: tlsOptionsName,
|
||||
}
|
||||
|
||||
logger.Debug().Msgf("Adding TLS route for %q", routerConfig.Rule)
|
||||
|
|
|
|||
|
|
@ -1,14 +1,10 @@
|
|||
package tcp
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"math"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/traefik/traefik/v3/pkg/config/dynamic"
|
||||
"github.com/traefik/traefik/v3/pkg/config/runtime"
|
||||
tcpmiddleware "github.com/traefik/traefik/v3/pkg/server/middleware/tcp"
|
||||
|
|
@ -130,7 +126,8 @@ func TestRuntimeConfiguration(t *testing.T) {
|
|||
Service: "foo-service",
|
||||
Rule: "Host(`bar.foo`)",
|
||||
TLS: &dynamic.RouterTLSConfig{
|
||||
Options: "foo",
|
||||
Options: "foo",
|
||||
ResolvedOptions: "default",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
@ -140,7 +137,8 @@ func TestRuntimeConfiguration(t *testing.T) {
|
|||
Service: "foo-service",
|
||||
Rule: "Host(`bar.foo`) && PathPrefix(`/path`)",
|
||||
TLS: &dynamic.RouterTLSConfig{
|
||||
Options: "bar",
|
||||
Options: "bar",
|
||||
ResolvedOptions: "default",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
@ -399,293 +397,3 @@ func TestRuntimeConfiguration(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDomainFronting(t *testing.T) {
|
||||
tlsOptionsBase := map[string]traefiktls.Options{
|
||||
"default": {
|
||||
MinVersion: "VersionTLS10",
|
||||
},
|
||||
"host1@file": {
|
||||
MinVersion: "VersionTLS12",
|
||||
},
|
||||
"host1@crd": {
|
||||
MinVersion: "VersionTLS12",
|
||||
},
|
||||
}
|
||||
|
||||
entryPoints := []string{"web"}
|
||||
|
||||
tests := []struct {
|
||||
desc string
|
||||
routers map[string]*runtime.RouterInfo
|
||||
tlsOptions map[string]traefiktls.Options
|
||||
host string
|
||||
ServerName string
|
||||
expectedStatus int
|
||||
}{
|
||||
{
|
||||
desc: "Request is misdirected when TLS options are different",
|
||||
routers: map[string]*runtime.RouterInfo{
|
||||
"router-1@file": {
|
||||
Router: &dynamic.Router{
|
||||
EntryPoints: entryPoints,
|
||||
Rule: "Host(`host1.local`)",
|
||||
TLS: &dynamic.RouterTLSConfig{
|
||||
Options: "host1",
|
||||
},
|
||||
},
|
||||
},
|
||||
"router-2@file": {
|
||||
Router: &dynamic.Router{
|
||||
EntryPoints: entryPoints,
|
||||
Rule: "Host(`host2.local`)",
|
||||
TLS: &dynamic.RouterTLSConfig{},
|
||||
},
|
||||
},
|
||||
},
|
||||
tlsOptions: tlsOptionsBase,
|
||||
host: "host1.local",
|
||||
ServerName: "host2.local",
|
||||
expectedStatus: http.StatusMisdirectedRequest,
|
||||
},
|
||||
{
|
||||
desc: "Request is OK when TLS options are the same",
|
||||
routers: map[string]*runtime.RouterInfo{
|
||||
"router-1@file": {
|
||||
Router: &dynamic.Router{
|
||||
EntryPoints: entryPoints,
|
||||
Rule: "Host(`host1.local`)",
|
||||
TLS: &dynamic.RouterTLSConfig{
|
||||
Options: "host1",
|
||||
},
|
||||
},
|
||||
},
|
||||
"router-2@file": {
|
||||
Router: &dynamic.Router{
|
||||
EntryPoints: entryPoints,
|
||||
Rule: "Host(`host2.local`)",
|
||||
TLS: &dynamic.RouterTLSConfig{
|
||||
Options: "host1",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
tlsOptions: tlsOptionsBase,
|
||||
host: "host1.local",
|
||||
ServerName: "host2.local",
|
||||
expectedStatus: http.StatusOK,
|
||||
},
|
||||
{
|
||||
desc: "Default TLS options is used when options are ambiguous for the same host",
|
||||
routers: map[string]*runtime.RouterInfo{
|
||||
"router-1@file": {
|
||||
Router: &dynamic.Router{
|
||||
EntryPoints: entryPoints,
|
||||
Rule: "Host(`host1.local`)",
|
||||
TLS: &dynamic.RouterTLSConfig{
|
||||
Options: "host1",
|
||||
},
|
||||
},
|
||||
},
|
||||
"router-2@file": {
|
||||
Router: &dynamic.Router{
|
||||
EntryPoints: entryPoints,
|
||||
Rule: "Host(`host1.local`) && PathPrefix(`/foo`)",
|
||||
TLS: &dynamic.RouterTLSConfig{
|
||||
Options: "default",
|
||||
},
|
||||
},
|
||||
},
|
||||
"router-3@file": {
|
||||
Router: &dynamic.Router{
|
||||
EntryPoints: entryPoints,
|
||||
Rule: "Host(`host2.local`)",
|
||||
TLS: &dynamic.RouterTLSConfig{
|
||||
Options: "host1",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
tlsOptions: tlsOptionsBase,
|
||||
host: "host1.local",
|
||||
ServerName: "host2.local",
|
||||
expectedStatus: http.StatusMisdirectedRequest,
|
||||
},
|
||||
{
|
||||
desc: "Default TLS options should not be used when options are the same for the same host",
|
||||
routers: map[string]*runtime.RouterInfo{
|
||||
"router-1@file": {
|
||||
Router: &dynamic.Router{
|
||||
EntryPoints: entryPoints,
|
||||
Rule: "Host(`host1.local`)",
|
||||
TLS: &dynamic.RouterTLSConfig{
|
||||
Options: "host1",
|
||||
},
|
||||
},
|
||||
},
|
||||
"router-2@file": {
|
||||
Router: &dynamic.Router{
|
||||
EntryPoints: entryPoints,
|
||||
Rule: "Host(`host1.local`) && PathPrefix(`/bar`)",
|
||||
TLS: &dynamic.RouterTLSConfig{
|
||||
Options: "host1",
|
||||
},
|
||||
},
|
||||
},
|
||||
"router-3@file": {
|
||||
Router: &dynamic.Router{
|
||||
EntryPoints: entryPoints,
|
||||
Rule: "Host(`host2.local`)",
|
||||
TLS: &dynamic.RouterTLSConfig{
|
||||
Options: "host1",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
tlsOptions: tlsOptionsBase,
|
||||
host: "host1.local",
|
||||
ServerName: "host2.local",
|
||||
expectedStatus: http.StatusOK,
|
||||
},
|
||||
{
|
||||
desc: "Request is misdirected when TLS options have the same name but from different providers",
|
||||
routers: map[string]*runtime.RouterInfo{
|
||||
"router-1@file": {
|
||||
Router: &dynamic.Router{
|
||||
EntryPoints: entryPoints,
|
||||
Rule: "Host(`host1.local`)",
|
||||
TLS: &dynamic.RouterTLSConfig{
|
||||
Options: "host1",
|
||||
},
|
||||
},
|
||||
},
|
||||
"router-2@crd": {
|
||||
Router: &dynamic.Router{
|
||||
EntryPoints: entryPoints,
|
||||
Rule: "Host(`host2.local`)",
|
||||
TLS: &dynamic.RouterTLSConfig{
|
||||
Options: "host1",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
tlsOptions: tlsOptionsBase,
|
||||
host: "host1.local",
|
||||
ServerName: "host2.local",
|
||||
expectedStatus: http.StatusMisdirectedRequest,
|
||||
},
|
||||
{
|
||||
desc: "Request is OK when TLS options reference from a different provider is the same",
|
||||
routers: map[string]*runtime.RouterInfo{
|
||||
"router-1@file": {
|
||||
Router: &dynamic.Router{
|
||||
EntryPoints: entryPoints,
|
||||
Rule: "Host(`host1.local`)",
|
||||
TLS: &dynamic.RouterTLSConfig{
|
||||
Options: "host1@crd",
|
||||
},
|
||||
},
|
||||
},
|
||||
"router-2@crd": {
|
||||
Router: &dynamic.Router{
|
||||
EntryPoints: entryPoints,
|
||||
Rule: "Host(`host2.local`)",
|
||||
TLS: &dynamic.RouterTLSConfig{
|
||||
Options: "host1@crd",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
tlsOptions: tlsOptionsBase,
|
||||
host: "host1.local",
|
||||
ServerName: "host2.local",
|
||||
expectedStatus: http.StatusOK,
|
||||
},
|
||||
{
|
||||
desc: "Request is misdirected when server name is empty and the host name is an FQDN, but router's rule is not",
|
||||
routers: map[string]*runtime.RouterInfo{
|
||||
"router-1@file": {
|
||||
Router: &dynamic.Router{
|
||||
EntryPoints: entryPoints,
|
||||
Rule: "Host(`host1.local`)",
|
||||
TLS: &dynamic.RouterTLSConfig{
|
||||
Options: "host1@file",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
tlsOptions: map[string]traefiktls.Options{
|
||||
"default": {
|
||||
MinVersion: "VersionTLS13",
|
||||
},
|
||||
"host1@file": {
|
||||
MinVersion: "VersionTLS12",
|
||||
},
|
||||
},
|
||||
host: "host1.local.",
|
||||
expectedStatus: http.StatusMisdirectedRequest,
|
||||
},
|
||||
{
|
||||
desc: "Request is misdirected when server name is empty and the host name is not FQDN, but router's rule is",
|
||||
routers: map[string]*runtime.RouterInfo{
|
||||
"router-1@file": {
|
||||
Router: &dynamic.Router{
|
||||
EntryPoints: entryPoints,
|
||||
Rule: "Host(`host1.local.`)",
|
||||
TLS: &dynamic.RouterTLSConfig{
|
||||
Options: "host1@file",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
tlsOptions: map[string]traefiktls.Options{
|
||||
"default": {
|
||||
MinVersion: "VersionTLS13",
|
||||
},
|
||||
"host1@file": {
|
||||
MinVersion: "VersionTLS12",
|
||||
},
|
||||
},
|
||||
host: "host1.local",
|
||||
expectedStatus: http.StatusMisdirectedRequest,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
conf := &runtime.Configuration{
|
||||
Routers: test.routers,
|
||||
}
|
||||
|
||||
serviceManager := tcp.NewManager(conf, tcp2.NewDialerManager(nil))
|
||||
|
||||
tlsManager := traefiktls.NewManager(nil)
|
||||
tlsManager.UpdateConfigs(t.Context(), map[string]traefiktls.Store{}, test.tlsOptions, []*traefiktls.CertAndStores{})
|
||||
|
||||
httpsHandler := map[string]http.Handler{
|
||||
"web": http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {}),
|
||||
}
|
||||
|
||||
middlewaresBuilder := tcpmiddleware.NewBuilder(conf.TCPMiddlewares)
|
||||
|
||||
routerManager := NewManager(conf, serviceManager, middlewaresBuilder, nil, httpsHandler, tlsManager, nil)
|
||||
|
||||
routers := routerManager.BuildHandlers(t.Context(), entryPoints)
|
||||
|
||||
router, ok := routers["web"]
|
||||
require.True(t, ok)
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||
req.Host = test.host
|
||||
req.TLS = &tls.ConnectionState{
|
||||
ServerName: test.ServerName,
|
||||
}
|
||||
|
||||
rw := httptest.NewRecorder()
|
||||
|
||||
router.GetHTTPSHandler().ServeHTTP(rw, req)
|
||||
|
||||
assert.Equal(t, test.expectedStatus, rw.Code)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ func (r *Router) servePostgres(conn *peekConn) error {
|
|||
log.Error().Err(err).Msg("Error while setting deadline")
|
||||
}
|
||||
|
||||
connData, err := tcpmuxer.NewConnData(hello.serverName, conn, hello.protos)
|
||||
connData, err := tcpmuxer.NewConnData(hello.serverName, conn.RemoteAddr(), hello.protos)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Error while reading TCP connection data")
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -16,11 +16,17 @@ import (
|
|||
"github.com/rs/zerolog/log"
|
||||
tcpmuxer "github.com/traefik/traefik/v3/pkg/muxer/tcp"
|
||||
"github.com/traefik/traefik/v3/pkg/tcp"
|
||||
traefiktls "github.com/traefik/traefik/v3/pkg/tls"
|
||||
)
|
||||
|
||||
// errClientHelloRead is used as a sentinel error to break the TLS handshake once we have read the ClientHello.
|
||||
var errClientHelloRead = errors.New("client hello successfully read")
|
||||
|
||||
type tlsConfigWithOptionsName struct {
|
||||
cfg *tls.Config
|
||||
optionsName string
|
||||
}
|
||||
|
||||
// Router is a TCP router.
|
||||
type Router struct {
|
||||
acmeTLSPassthrough bool
|
||||
|
|
@ -47,7 +53,7 @@ type Router struct {
|
|||
httpsTLSConfig *tls.Config // default TLS config
|
||||
// hostHTTPTLSConfig contains TLS configs keyed by SNI.
|
||||
// A nil config is the hint to set up a brokenTLSRouter.
|
||||
hostHTTPTLSConfig map[string]*tls.Config // TLS configs keyed by SNI
|
||||
hostHTTPTLSConfig map[string]tlsConfigWithOptionsName // TLS configs keyed by SNI
|
||||
}
|
||||
|
||||
// NewRouter returns a new TCP router.
|
||||
|
|
@ -74,14 +80,20 @@ func NewRouter(providersPrecedence []string) (*Router, error) {
|
|||
}, nil
|
||||
}
|
||||
|
||||
// GetTLSGetClientInfo is called after a ClientHello is received from a client.
|
||||
func (r *Router) GetTLSGetClientInfo() func(info *tls.ClientHelloInfo) (*tls.Config, error) {
|
||||
return func(info *tls.ClientHelloInfo) (*tls.Config, error) {
|
||||
if tlsConfig, ok := r.hostHTTPTLSConfig[info.ServerName]; ok {
|
||||
return tlsConfig, nil
|
||||
// HTTP3TLSConfigMatcherFunc returns a matcher func for HTTP/3 which returns a tls.Config with its corresponding
|
||||
// TLSOptionName matching the given HostSNI in the connection data, or the default TLS config if there is no match.
|
||||
func (r *Router) HTTP3TLSConfigMatcherFunc() func(connData tcpmuxer.ConnData) (*tls.Config, string, error) {
|
||||
return func(connData tcpmuxer.ConnData) (*tls.Config, string, error) {
|
||||
h, _ := r.muxerHTTPS.Match(connData)
|
||||
if h == nil {
|
||||
return r.httpsTLSConfig, traefiktls.DefaultTLSConfigName, nil
|
||||
}
|
||||
|
||||
return r.httpsTLSConfig, nil
|
||||
if tlsHandler, ok := h.(*tcp.TLSHandler); ok {
|
||||
return tlsHandler.Config, tlsHandler.TLSOptionsName, nil
|
||||
}
|
||||
|
||||
return nil, "", errors.New("matching handler is not a TLSHandler")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -93,7 +105,7 @@ func (r *Router) ServeTCP(conn tcp.WriteCloser) {
|
|||
// we would block forever on clientHelloInfo,
|
||||
// which is why we want to detect and handle that case first and foremost.
|
||||
if r.muxerTCP.HasRoutes() && !r.muxerTCPTLS.HasRoutes() && !r.muxerHTTPS.HasRoutes() {
|
||||
connData, err := tcpmuxer.NewConnData("", conn, nil)
|
||||
connData, err := tcpmuxer.NewConnData("", conn.RemoteAddr(), nil)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Error while reading TCP connection data")
|
||||
conn.Close()
|
||||
|
|
@ -159,7 +171,7 @@ func (r *Router) ServeTCP(conn tcp.WriteCloser) {
|
|||
log.Error().Err(err).Msg("Error while setting deadline")
|
||||
}
|
||||
|
||||
connData, err := tcpmuxer.NewConnData(hello.serverName, pConn, hello.protos)
|
||||
connData, err := tcpmuxer.NewConnData(hello.serverName, pConn.RemoteAddr(), hello.protos)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Error while reading TCP connection data")
|
||||
_ = pConn.Close()
|
||||
|
|
@ -235,12 +247,15 @@ func (r *Router) AddTCPRoute(rule string, priority int, providerName string, tar
|
|||
}
|
||||
|
||||
// AddHTTPTLSConfig defines a handler for a given sniHost and sets the matching tlsConfig.
|
||||
func (r *Router) AddHTTPTLSConfig(sniHost string, config *tls.Config) {
|
||||
func (r *Router) AddHTTPTLSConfig(sniHost string, config *tls.Config, optionsName string) {
|
||||
if r.hostHTTPTLSConfig == nil {
|
||||
r.hostHTTPTLSConfig = map[string]*tls.Config{}
|
||||
r.hostHTTPTLSConfig = map[string]tlsConfigWithOptionsName{}
|
||||
}
|
||||
|
||||
r.hostHTTPTLSConfig[sniHost] = config
|
||||
r.hostHTTPTLSConfig[sniHost] = tlsConfigWithOptionsName{
|
||||
cfg: config,
|
||||
optionsName: optionsName,
|
||||
}
|
||||
}
|
||||
|
||||
// GetHTTPHandler gets the attached http handler.
|
||||
|
|
@ -264,12 +279,13 @@ func (r *Router) SetHTTPForwarder(handler tcp.Handler) {
|
|||
func (r *Router) SetHTTPSForwarder(handler tcp.Handler) {
|
||||
for sniHost, tlsConf := range r.hostHTTPTLSConfig {
|
||||
var tcpHandler tcp.Handler
|
||||
if tlsConf == nil {
|
||||
if tlsConf.cfg == nil {
|
||||
tcpHandler = &brokenTLSRouter{}
|
||||
} else {
|
||||
tcpHandler = &tcp.TLSHandler{
|
||||
Next: handler,
|
||||
Config: tlsConf,
|
||||
Next: handler,
|
||||
Config: tlsConf.cfg,
|
||||
TLSOptionsName: tlsConf.optionsName,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -287,8 +303,9 @@ func (r *Router) SetHTTPSForwarder(handler tcp.Handler) {
|
|||
}
|
||||
|
||||
r.httpsForwarder = &tcp.TLSHandler{
|
||||
Next: handler,
|
||||
Config: r.httpsTLSConfig,
|
||||
Next: handler,
|
||||
Config: r.httpsTLSConfig,
|
||||
TLSOptionsName: "default",
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package tcp
|
|||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
|
@ -21,7 +22,7 @@ import (
|
|||
"github.com/traefik/traefik/v3/pkg/config/runtime"
|
||||
tcpmiddleware "github.com/traefik/traefik/v3/pkg/server/middleware/tcp"
|
||||
"github.com/traefik/traefik/v3/pkg/server/service/tcp"
|
||||
tcp2 "github.com/traefik/traefik/v3/pkg/tcp"
|
||||
traefiktcp "github.com/traefik/traefik/v3/pkg/tcp"
|
||||
traefiktls "github.com/traefik/traefik/v3/pkg/tls"
|
||||
"github.com/traefik/traefik/v3/pkg/tls/generate"
|
||||
"github.com/traefik/traefik/v3/pkg/types"
|
||||
|
|
@ -125,7 +126,7 @@ func Test_Routing(t *testing.T) {
|
|||
},
|
||||
}
|
||||
|
||||
dialerManager := tcp2.NewDialerManager(nil)
|
||||
dialerManager := traefiktcp.NewDialerManager(nil)
|
||||
dialerManager.Update(map[string]*dynamic.TCPServersTransport{"default@internal": {}})
|
||||
serviceManager := tcp.NewManager(conf, dialerManager)
|
||||
|
||||
|
|
@ -599,6 +600,16 @@ func Test_Routing(t *testing.T) {
|
|||
_, err = fmt.Fprint(w, "HTTPS")
|
||||
require.NoError(t, err)
|
||||
}),
|
||||
|
||||
ConnContext: func(ctx context.Context, c net.Conn) context.Context {
|
||||
if tlsConn, ok := c.(*tls.Conn); ok {
|
||||
if tlsConnWithOptionsName, ok := tlsConn.NetConn().(traefiktcp.TLSConn); ok {
|
||||
return traefiktcp.AddTLSOptionsNameInContext(ctx, tlsConnWithOptionsName.TLSOptionsName)
|
||||
}
|
||||
}
|
||||
|
||||
return ctx
|
||||
},
|
||||
}
|
||||
|
||||
stoppedHTTPS := make(chan struct{})
|
||||
|
|
@ -856,9 +867,9 @@ func TestPostgresTLSTermination(t *testing.T) {
|
|||
|
||||
// Register a TCPTLS route (TLS termination, not passthrough) with a TLSHandler.
|
||||
// The TLSHandler wraps the actual handler, performing the TLS handshake.
|
||||
err = router.muxerTCPTLS.AddRoute("HostSNI(`test.localhost`)", "", 0, "", &tcp2.TLSHandler{
|
||||
err = router.muxerTCPTLS.AddRoute("HostSNI(`test.localhost`)", "", 0, "", &traefiktcp.TLSHandler{
|
||||
Config: tlsConf,
|
||||
Next: tcp2.HandlerFunc(func(conn tcp2.WriteCloser) {
|
||||
Next: traefiktcp.HandlerFunc(func(conn traefiktcp.WriteCloser) {
|
||||
_, _ = conn.Write([]byte("OK"))
|
||||
_ = conn.Close()
|
||||
}),
|
||||
|
|
@ -921,7 +932,7 @@ func TestPostgresTLSPassthrough(t *testing.T) {
|
|||
require.NoError(t, err)
|
||||
|
||||
// Register a TCPTLS route (TLS passthrough) with a tcp.Handler.
|
||||
err = router.muxerTCPTLS.AddRoute("HostSNI(`test.localhost`)", "", 0, "", tcp2.HandlerFunc(func(conn tcp2.WriteCloser) {
|
||||
err = router.muxerTCPTLS.AddRoute("HostSNI(`test.localhost`)", "", 0, "", traefiktcp.HandlerFunc(func(conn traefiktcp.WriteCloser) {
|
||||
// First we should receive the PostgresStartTLSMsg.
|
||||
buf := make([]byte, len(PostgresStartTLSMsg))
|
||||
_, err := conn.Read(buf)
|
||||
|
|
@ -1056,7 +1067,8 @@ func routerHTTPSPathPrefix(conf *runtime.Configuration) {
|
|||
Service: "http",
|
||||
Rule: "PathPrefix(`/`)",
|
||||
TLS: &dynamic.RouterTLSConfig{
|
||||
Options: "tls10",
|
||||
Options: "tls10",
|
||||
ResolvedOptions: "tls10",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
@ -1070,7 +1082,8 @@ func routerHTTPS(conf *runtime.Configuration) {
|
|||
Service: "http",
|
||||
Rule: "Host(`foo.bar`)",
|
||||
TLS: &dynamic.RouterTLSConfig{
|
||||
Options: "tls12",
|
||||
Options: "tls12",
|
||||
ResolvedOptions: "tls12",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
@ -1366,7 +1379,7 @@ func (h *httpForwarder) Close() error {
|
|||
}
|
||||
|
||||
// ServeTCP uses the connection to serve it later in "Accept".
|
||||
func (h *httpForwarder) ServeTCP(conn tcp2.WriteCloser) {
|
||||
func (h *httpForwarder) ServeTCP(conn traefiktcp.WriteCloser) {
|
||||
h.connChan <- conn
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package server
|
|||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"expvar"
|
||||
"fmt"
|
||||
|
|
@ -677,6 +678,44 @@ func newHTTPServer(ctx context.Context, ln net.Listener, configuration *static.E
|
|||
|
||||
handler = denyFragment(handler)
|
||||
|
||||
var connContext multipleConnContext
|
||||
connContext.AddConnContextFunc(func(ctx context.Context, c net.Conn) context.Context {
|
||||
// This adds an empty struct in order to store a RoundTripper in the ConnContext in case of Kerberos or NTLM.
|
||||
ctx = service.AddTransportOnContext(ctx)
|
||||
|
||||
if tlsConn, ok := c.(*tls.Conn); ok {
|
||||
if tlsConnWithOptionsName, ok := tlsConn.NetConn().(tcp.TLSConn); ok {
|
||||
return tcp.AddTLSOptionsNameInContext(ctx, tlsConnWithOptionsName.TLSOptionsName)
|
||||
}
|
||||
}
|
||||
|
||||
return ctx
|
||||
})
|
||||
|
||||
if debugConnection || (configuration.Transport != nil && (configuration.Transport.KeepAliveMaxTime > 0 || configuration.Transport.KeepAliveMaxRequests > 0)) {
|
||||
connContext.AddConnContextFunc(func(ctx context.Context, c net.Conn) context.Context {
|
||||
cState := &connState{Start: time.Now()}
|
||||
if debugConnection {
|
||||
clientConnectionStatesMu.Lock()
|
||||
clientConnectionStates[getConnKey(c)] = cState
|
||||
clientConnectionStatesMu.Unlock()
|
||||
}
|
||||
|
||||
return context.WithValue(ctx, connStateKey, cState)
|
||||
})
|
||||
}
|
||||
|
||||
var connState func(c net.Conn, state http.ConnState)
|
||||
if debugConnection {
|
||||
connState = func(c net.Conn, state http.ConnState) {
|
||||
clientConnectionStatesMu.Lock()
|
||||
if clientConnectionStates[getConnKey(c)] != nil {
|
||||
clientConnectionStates[getConnKey(c)].State = state.String()
|
||||
}
|
||||
clientConnectionStatesMu.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
serverHTTP := &http.Server{
|
||||
Protocols: &protocols,
|
||||
Handler: handler,
|
||||
|
|
@ -690,37 +729,8 @@ func newHTTPServer(ctx context.Context, ln net.Listener, configuration *static.E
|
|||
MaxDecoderHeaderTableSize: int(configuration.HTTP2.MaxDecoderHeaderTableSize),
|
||||
MaxEncoderHeaderTableSize: int(configuration.HTTP2.MaxEncoderHeaderTableSize),
|
||||
},
|
||||
}
|
||||
if debugConnection || (configuration.Transport != nil && (configuration.Transport.KeepAliveMaxTime > 0 || configuration.Transport.KeepAliveMaxRequests > 0)) {
|
||||
serverHTTP.ConnContext = func(ctx context.Context, c net.Conn) context.Context {
|
||||
cState := &connState{Start: time.Now()}
|
||||
if debugConnection {
|
||||
clientConnectionStatesMu.Lock()
|
||||
clientConnectionStates[getConnKey(c)] = cState
|
||||
clientConnectionStatesMu.Unlock()
|
||||
}
|
||||
return context.WithValue(ctx, connStateKey, cState)
|
||||
}
|
||||
|
||||
if debugConnection {
|
||||
serverHTTP.ConnState = func(c net.Conn, state http.ConnState) {
|
||||
clientConnectionStatesMu.Lock()
|
||||
if clientConnectionStates[getConnKey(c)] != nil {
|
||||
clientConnectionStates[getConnKey(c)].State = state.String()
|
||||
}
|
||||
clientConnectionStatesMu.Unlock()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
prevConnContext := serverHTTP.ConnContext
|
||||
serverHTTP.ConnContext = func(ctx context.Context, c net.Conn) context.Context {
|
||||
// This adds an empty struct in order to store a RoundTripper in the ConnContext in case of Kerberos or NTLM.
|
||||
ctx = service.AddTransportOnContext(ctx)
|
||||
if prevConnContext != nil {
|
||||
return prevConnContext(ctx, c)
|
||||
}
|
||||
return ctx
|
||||
ConnContext: connContext.Build(),
|
||||
ConnState: connState,
|
||||
}
|
||||
|
||||
listener := newHTTPForwarder(ln)
|
||||
|
|
|
|||
|
|
@ -13,7 +13,9 @@ import (
|
|||
"github.com/quic-go/quic-go/http3"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/traefik/traefik/v3/pkg/config/static"
|
||||
tcpmuxer "github.com/traefik/traefik/v3/pkg/muxer/tcp"
|
||||
tcprouter "github.com/traefik/traefik/v3/pkg/server/router/tcp"
|
||||
"github.com/traefik/traefik/v3/pkg/tcp"
|
||||
)
|
||||
|
||||
type http3server struct {
|
||||
|
|
@ -22,7 +24,7 @@ type http3server struct {
|
|||
http3conn net.PacketConn
|
||||
|
||||
lock sync.RWMutex
|
||||
getter func(info *tls.ClientHelloInfo) (*tls.Config, error)
|
||||
getter func(data tcpmuxer.ConnData) (*tls.Config, string, error)
|
||||
}
|
||||
|
||||
func newHTTP3Server(ctx context.Context, name string, config *static.EntryPoint, httpsServer *httpServer) (*http3server, error) {
|
||||
|
|
@ -55,8 +57,8 @@ func newHTTP3Server(ctx context.Context, name string, config *static.EntryPoint,
|
|||
|
||||
h3 := &http3server{
|
||||
http3conn: conn,
|
||||
getter: func(info *tls.ClientHelloInfo) (*tls.Config, error) {
|
||||
return nil, errors.New("no tls config")
|
||||
getter: func(data tcpmuxer.ConnData) (*tls.Config, string, error) {
|
||||
return nil, "", errors.New("no TLS config")
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -64,10 +66,18 @@ func newHTTP3Server(ctx context.Context, name string, config *static.EntryPoint,
|
|||
Addr: config.GetAddress(),
|
||||
Port: config.HTTP3.AdvertisedPort,
|
||||
Handler: httpsServer.Server.(*http.Server).Handler,
|
||||
TLSConfig: &tls.Config{GetConfigForClient: h3.getGetConfigForClient},
|
||||
TLSConfig: &tls.Config{GetConfigForClient: h3.getTLSConfigForClient},
|
||||
QUICConfig: &quic.Config{
|
||||
Allow0RTT: false,
|
||||
},
|
||||
ConnContext: func(ctx context.Context, c *quic.Conn) context.Context {
|
||||
tlsOptionsName, err := h3.getTLSOptionsName(c)
|
||||
if err != nil {
|
||||
log.Error().Msgf("Error getting TLS options name for client: %v", err)
|
||||
return ctx
|
||||
}
|
||||
return tcp.AddTLSOptionsNameInContext(ctx, tlsOptionsName)
|
||||
},
|
||||
}
|
||||
|
||||
previousHandler := httpsServer.Server.(*http.Server).Handler
|
||||
|
|
@ -91,7 +101,7 @@ func (e *http3server) Switch(rt *tcprouter.Router) {
|
|||
e.lock.Lock()
|
||||
defer e.lock.Unlock()
|
||||
|
||||
e.getter = rt.GetTLSGetClientInfo()
|
||||
e.getter = rt.HTTP3TLSConfigMatcherFunc()
|
||||
}
|
||||
|
||||
func (e *http3server) Shutdown(_ context.Context) error {
|
||||
|
|
@ -99,9 +109,28 @@ func (e *http3server) Shutdown(_ context.Context) error {
|
|||
return e.Server.Close()
|
||||
}
|
||||
|
||||
func (e *http3server) getGetConfigForClient(info *tls.ClientHelloInfo) (*tls.Config, error) {
|
||||
func (e *http3server) getTLSConfigForClient(info *tls.ClientHelloInfo) (*tls.Config, error) {
|
||||
e.lock.RLock()
|
||||
defer e.lock.RUnlock()
|
||||
|
||||
return e.getter(info)
|
||||
connData, err := tcpmuxer.NewConnData(info.ServerName, info.Conn.RemoteAddr(), info.SupportedProtos)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("creating ConnData from client hello: %w", err)
|
||||
}
|
||||
|
||||
conf, _, err := e.getter(connData)
|
||||
return conf, err
|
||||
}
|
||||
|
||||
func (e *http3server) getTLSOptionsName(c *quic.Conn) (string, error) {
|
||||
e.lock.RLock()
|
||||
defer e.lock.RUnlock()
|
||||
|
||||
connData, err := tcpmuxer.NewConnData(c.ConnectionState().TLS.ServerName, c.RemoteAddr(), []string{c.ConnectionState().TLS.NegotiatedProtocol})
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("creating ConnData from quic Conn: %w", err)
|
||||
}
|
||||
|
||||
_, name, err := e.getter(connData)
|
||||
return name, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import (
|
|||
"github.com/stretchr/testify/require"
|
||||
"github.com/traefik/traefik/v3/pkg/config/static"
|
||||
tcprouter "github.com/traefik/traefik/v3/pkg/server/router/tcp"
|
||||
traefiktls "github.com/traefik/traefik/v3/pkg/tls"
|
||||
"github.com/traefik/traefik/v3/pkg/types"
|
||||
)
|
||||
|
||||
|
|
@ -102,7 +103,7 @@ func TestHTTP3AdvertisedPort(t *testing.T) {
|
|||
|
||||
router.AddHTTPTLSConfig("*", &tls.Config{
|
||||
Certificates: []tls.Certificate{tlsCert},
|
||||
})
|
||||
}, traefiktls.DefaultTLSConfigName)
|
||||
router.SetHTTPSHandler(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
rw.WriteHeader(http.StatusOK)
|
||||
}), nil)
|
||||
|
|
@ -164,7 +165,7 @@ func TestHTTP30RTT(t *testing.T) {
|
|||
|
||||
router.AddHTTPTLSConfig("example.com", &tls.Config{
|
||||
Certificates: []tls.Certificate{tlsCert},
|
||||
})
|
||||
}, traefiktls.DefaultTLSConfigName)
|
||||
router.SetHTTPSHandler(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
rw.WriteHeader(http.StatusOK)
|
||||
}), nil)
|
||||
|
|
|
|||
|
|
@ -1,16 +1,39 @@
|
|||
package tcp
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
)
|
||||
|
||||
// TLSConn is a TLS connection that also carries the name of the TLS config used.
|
||||
type TLSConn struct {
|
||||
WriteCloser
|
||||
|
||||
TLSOptionsName string
|
||||
}
|
||||
|
||||
// TLSHandler handles TLS connections.
|
||||
type TLSHandler struct {
|
||||
Next Handler
|
||||
Config *tls.Config
|
||||
Next Handler
|
||||
Config *tls.Config
|
||||
TLSOptionsName string
|
||||
}
|
||||
|
||||
// ServeTCP terminates the TLS connection.
|
||||
func (t *TLSHandler) ServeTCP(conn WriteCloser) {
|
||||
t.Next.ServeTCP(tls.Server(conn, t.Config))
|
||||
t.Next.ServeTCP(tls.Server(TLSConn{WriteCloser: conn, TLSOptionsName: t.TLSOptionsName}, t.Config))
|
||||
}
|
||||
|
||||
type tlsOptionsNameKey struct{}
|
||||
|
||||
func AddTLSOptionsNameInContext(ctx context.Context, name string) context.Context {
|
||||
return context.WithValue(ctx, tlsOptionsNameKey{}, name)
|
||||
}
|
||||
|
||||
func GetTLSOptionsName(ctx context.Context) string {
|
||||
if name, ok := ctx.Value(tlsOptionsNameKey{}).(string); ok {
|
||||
return name
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,11 +4,11 @@ RepositoryName = "traefik"
|
|||
OutputType = "file"
|
||||
FileName = "traefik_changelog.md"
|
||||
|
||||
# example new bugfix v3.7.1
|
||||
# example new bugfix v3.7.4
|
||||
CurrentRef = "v3.7"
|
||||
PreviousRef = "v3.7.0"
|
||||
PreviousRef = "v3.7.3"
|
||||
BaseBranch = "v3.7"
|
||||
FutureCurrentRefName = "v3.7.1"
|
||||
FutureCurrentRefName = "v3.7.4"
|
||||
|
||||
ThresholdPreviousRef = 10000
|
||||
ThresholdCurrentRef = 10000
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@
|
|||
"framer-motion": "^11.18.2",
|
||||
"globals": "^16.0.0",
|
||||
"jest-extended": "^4.0.2",
|
||||
"jsdom": "^24.0.0",
|
||||
"jsdom": "29.1.1",
|
||||
"lodash": "4.18.1",
|
||||
"msw": "^2.1.7",
|
||||
"query-string": "^6.9.0",
|
||||
|
|
@ -75,7 +75,7 @@
|
|||
"react-helmet-async": "^2.0.4",
|
||||
"react-icons": "^5.0.1",
|
||||
"react-infinite-scroll-hook": "^4.1.1",
|
||||
"react-router-dom": "6.30.2",
|
||||
"react-router-dom": "6.30.4",
|
||||
"swr": "^2.2.4",
|
||||
"typescript": "^5.2.2",
|
||||
"typescript-eslint": "^8.38.0",
|
||||
|
|
@ -98,6 +98,8 @@
|
|||
"packageManager": "yarn@4.13.0",
|
||||
"resolutions": {
|
||||
"form-data": "4.0.4",
|
||||
"js-yaml": "4.1.1"
|
||||
"js-yaml": "4.1.1",
|
||||
"@csstools/css-syntax-patches-for-csstree": "1.1.4",
|
||||
"undici": "7.27.0"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
488
webui/yarn.lock
488
webui/yarn.lock
|
|
@ -22,16 +22,43 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@asamuzakjp/css-color@npm:^3.1.1":
|
||||
version: 3.1.1
|
||||
resolution: "@asamuzakjp/css-color@npm:3.1.1"
|
||||
"@asamuzakjp/css-color@npm:^5.1.11":
|
||||
version: 5.1.11
|
||||
resolution: "@asamuzakjp/css-color@npm:5.1.11"
|
||||
dependencies:
|
||||
"@csstools/css-calc": "npm:^2.1.2"
|
||||
"@csstools/css-color-parser": "npm:^3.0.8"
|
||||
"@csstools/css-parser-algorithms": "npm:^3.0.4"
|
||||
"@csstools/css-tokenizer": "npm:^3.0.3"
|
||||
lru-cache: "npm:^10.4.3"
|
||||
checksum: 10c0/4abb010fd29de8acae8571eba738468c22cb45a1f77647df3c59a80f1c83d83d728cae3ebbf99e5c73f2517761abaaffbe5e4176fc46b5f9bf60f1478463b51e
|
||||
"@asamuzakjp/generational-cache": "npm:^1.0.1"
|
||||
"@csstools/css-calc": "npm:^3.2.0"
|
||||
"@csstools/css-color-parser": "npm:^4.1.0"
|
||||
"@csstools/css-parser-algorithms": "npm:^4.0.0"
|
||||
"@csstools/css-tokenizer": "npm:^4.0.0"
|
||||
checksum: 10c0/32720bdff8daea6a8847aba6cdfae55baa3b4a2690b51d21db7f0382bbd183f3d9f2d5126df50afd889062635684b2819e47113629ee2e80c99389e75f48d060
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@asamuzakjp/dom-selector@npm:^7.1.1":
|
||||
version: 7.1.1
|
||||
resolution: "@asamuzakjp/dom-selector@npm:7.1.1"
|
||||
dependencies:
|
||||
"@asamuzakjp/generational-cache": "npm:^1.0.1"
|
||||
"@asamuzakjp/nwsapi": "npm:^2.3.9"
|
||||
bidi-js: "npm:^1.0.3"
|
||||
css-tree: "npm:^3.2.1"
|
||||
is-potential-custom-element-name: "npm:^1.0.1"
|
||||
checksum: 10c0/8cec1c618781c94de5836a215bbe5aafb4d8b835b18c51faf8547f4574afa39f92def3951e40123860062467613dd825f1e1600ff32e8045cc099a91796dcfb8
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@asamuzakjp/generational-cache@npm:^1.0.1":
|
||||
version: 1.0.1
|
||||
resolution: "@asamuzakjp/generational-cache@npm:1.0.1"
|
||||
checksum: 10c0/1de62de43764e13fca3b9a31b7ea9b1bf0780fe053d266e40378a19ff8c66b543e011e6a0df02d410cd59bf981126706f176cdbb938985165202c4a079fe1057
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@asamuzakjp/nwsapi@npm:^2.3.9":
|
||||
version: 2.3.9
|
||||
resolution: "@asamuzakjp/nwsapi@npm:2.3.9"
|
||||
checksum: 10c0/869b81382e775499c96c45c6dbe0d0766a6da04bcf0abb79f5333535c4e19946851acaa43398f896e2ecc5a1de9cf3db7cf8c4b1afac1ee3d15e21584546d74d
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
|
@ -301,6 +328,17 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@bramus/specificity@npm:^2.4.2":
|
||||
version: 2.4.2
|
||||
resolution: "@bramus/specificity@npm:2.4.2"
|
||||
dependencies:
|
||||
css-tree: "npm:^3.0.0"
|
||||
bin:
|
||||
specificity: bin/cli.js
|
||||
checksum: 10c0/c5f4e04e0bca0d2202598207a5eb0733c8109d12a68a329caa26373bec598d99db5bb785b8865fefa00fc01b08c6068138807ceb11a948fe15e904ed6cf4ba72
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@bundled-es-modules/cookie@npm:^2.0.1":
|
||||
version: 2.0.1
|
||||
resolution: "@bundled-es-modules/cookie@npm:2.0.1"
|
||||
|
|
@ -329,49 +367,61 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@csstools/color-helpers@npm:^5.0.2":
|
||||
version: 5.0.2
|
||||
resolution: "@csstools/color-helpers@npm:5.0.2"
|
||||
checksum: 10c0/bebaddb28b9eb58b0449edd5d0c0318fa88f3cb079602ee27e88c9118070d666dcc4e09a5aa936aba2fde6ba419922ade07b7b506af97dd7051abd08dfb2959b
|
||||
"@csstools/color-helpers@npm:^6.0.2":
|
||||
version: 6.0.2
|
||||
resolution: "@csstools/color-helpers@npm:6.0.2"
|
||||
checksum: 10c0/4c66574563d7c960010c11e41c2673675baff07c427cca6e8dddffa5777de45770d13ff3efce1c0642798089ad55de52870d9d8141f78db3fa5bba012f2d3789
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@csstools/css-calc@npm:^2.1.2":
|
||||
version: 2.1.2
|
||||
resolution: "@csstools/css-calc@npm:2.1.2"
|
||||
"@csstools/css-calc@npm:^3.2.0, @csstools/css-calc@npm:^3.2.1":
|
||||
version: 3.2.1
|
||||
resolution: "@csstools/css-calc@npm:3.2.1"
|
||||
peerDependencies:
|
||||
"@csstools/css-parser-algorithms": ^3.0.4
|
||||
"@csstools/css-tokenizer": ^3.0.3
|
||||
checksum: 10c0/34ced30553968ef5d5f9e00e3b90b48c47480cf130e282e99d57ec9b09f803aab8bc06325683e72a1518b5e7180a3da8b533f1b462062757c21989a53b482e1a
|
||||
"@csstools/css-parser-algorithms": ^4.0.0
|
||||
"@csstools/css-tokenizer": ^4.0.0
|
||||
checksum: 10c0/0191c8d1cd4dffa0d3b6bfd1e78a721934b1d7a6c972966e4fdaa72208c6789e8ff443ee81764a32f1e6107825695b5524ef2b4dc1681b5b29230f2a1277e5df
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@csstools/css-color-parser@npm:^3.0.8":
|
||||
version: 3.0.8
|
||||
resolution: "@csstools/css-color-parser@npm:3.0.8"
|
||||
"@csstools/css-color-parser@npm:^4.1.0":
|
||||
version: 4.1.1
|
||||
resolution: "@csstools/css-color-parser@npm:4.1.1"
|
||||
dependencies:
|
||||
"@csstools/color-helpers": "npm:^5.0.2"
|
||||
"@csstools/css-calc": "npm:^2.1.2"
|
||||
"@csstools/color-helpers": "npm:^6.0.2"
|
||||
"@csstools/css-calc": "npm:^3.2.1"
|
||||
peerDependencies:
|
||||
"@csstools/css-parser-algorithms": ^3.0.4
|
||||
"@csstools/css-tokenizer": ^3.0.3
|
||||
checksum: 10c0/90722c5a62ca94e9d578ddf59be604a76400b932bd3d4bd23cb1ae9b7ace8fcf83c06995d2b31f96f4afef24a7cefba79beb11ed7ee4999d7ecfec3869368359
|
||||
"@csstools/css-parser-algorithms": ^4.0.0
|
||||
"@csstools/css-tokenizer": ^4.0.0
|
||||
checksum: 10c0/427bd32f1a8917342a70a6fd97b93bb492aae7c8790e7782b5d6edc8c08064bb8aef0a86099f286db00288f9afea85eb92c46350e9057f5fea058e03a2a09203
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@csstools/css-parser-algorithms@npm:^3.0.4":
|
||||
version: 3.0.4
|
||||
resolution: "@csstools/css-parser-algorithms@npm:3.0.4"
|
||||
"@csstools/css-parser-algorithms@npm:^4.0.0":
|
||||
version: 4.0.0
|
||||
resolution: "@csstools/css-parser-algorithms@npm:4.0.0"
|
||||
peerDependencies:
|
||||
"@csstools/css-tokenizer": ^3.0.3
|
||||
checksum: 10c0/d411f07765e14eede17bccc6bd4f90ff303694df09aabfede3fd104b2dfacfd4fe3697cd25ddad14684c850328f3f9420ebfa9f78380892492974db24ae47dbd
|
||||
"@csstools/css-tokenizer": ^4.0.0
|
||||
checksum: 10c0/94558c2428d6ef0ddef542e86e0a8376aa1263a12a59770abb13ba50d7b83086822c75433f32aa2e7fef00555e1cc88292f9ca5bce79aed232bb3fed73b1528d
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@csstools/css-tokenizer@npm:^3.0.3":
|
||||
version: 3.0.3
|
||||
resolution: "@csstools/css-tokenizer@npm:3.0.3"
|
||||
checksum: 10c0/c31bf410e1244b942e71798e37c54639d040cb59e0121b21712b40015fced2b0fb1ffe588434c5f8923c9cd0017cfc1c1c8f3921abc94c96edf471aac2eba5e5
|
||||
"@csstools/css-syntax-patches-for-csstree@npm:1.1.4":
|
||||
version: 1.1.4
|
||||
resolution: "@csstools/css-syntax-patches-for-csstree@npm:1.1.4"
|
||||
peerDependencies:
|
||||
css-tree: ^3.2.1
|
||||
peerDependenciesMeta:
|
||||
css-tree:
|
||||
optional: true
|
||||
checksum: 10c0/3872a7befb553c53249c87e964ac00f55d059f4574d2cc023e03e1dafc86a5ad19f6a6d05fa2c14fb192e6a4538a73158104cc2e32e0688f27fd841b9ba76568
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@csstools/css-tokenizer@npm:^4.0.0":
|
||||
version: 4.0.0
|
||||
resolution: "@csstools/css-tokenizer@npm:4.0.0"
|
||||
checksum: 10c0/669cf3d0f9c8e1ffdf8c9955ad8beba0c8cfe03197fe29a4fcbd9ee6f7a18856cfa42c62670021a75183d9ab37f5d14a866e6a9df753a6c07f59e36797a9ea9f
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
|
@ -850,6 +900,18 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@exodus/bytes@npm:^1.11.0, @exodus/bytes@npm:^1.15.0, @exodus/bytes@npm:^1.6.0":
|
||||
version: 1.15.1
|
||||
resolution: "@exodus/bytes@npm:1.15.1"
|
||||
peerDependencies:
|
||||
"@noble/hashes": ^1.8.0 || ^2.0.0
|
||||
peerDependenciesMeta:
|
||||
"@noble/hashes":
|
||||
optional: true
|
||||
checksum: 10c0/333056a6953bbf875d9f3b86c32314de29458d842e5f56f6ef8034b18c2d9660184550093d1bae5de0064043d5e23f54cc03148798d9d29cf5167ac03f2e9f8c
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@floating-ui/core@npm:^1.6.0":
|
||||
version: 1.6.9
|
||||
resolution: "@floating-ui/core@npm:1.6.9"
|
||||
|
|
@ -2417,10 +2479,10 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@remix-run/router@npm:1.23.1":
|
||||
version: 1.23.1
|
||||
resolution: "@remix-run/router@npm:1.23.1"
|
||||
checksum: 10c0/94ac9632c0070199b8275cd6dffe78eb4c02e8926328937c65561c5c30d7ddf842743df3c8f7df302f00a593dd204846d93667fbbbe3c3641437d7b8f333ed90
|
||||
"@remix-run/router@npm:1.23.3":
|
||||
version: 1.23.3
|
||||
resolution: "@remix-run/router@npm:1.23.3"
|
||||
checksum: 10c0/73622465dd9e9a2c5a7ced7d1151ea6e9195a8a979c99b4f70a67093eeff7f339daf03a7c6304709a9c27deb2081a8fff6737663aacb33529c1a12a0a0827a17
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
|
@ -3841,13 +3903,6 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"asynckit@npm:^0.4.0":
|
||||
version: 0.4.0
|
||||
resolution: "asynckit@npm:0.4.0"
|
||||
checksum: 10c0/d73e2ddf20c4eb9337e1b3df1a0f6159481050a5de457c55b14ea2e5cb6d90bb69e004c9af54737a5ee0917fcf2c9e25de67777bbe58261847846066ba75bc9d
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"available-typed-arrays@npm:^1.0.7":
|
||||
version: 1.0.7
|
||||
resolution: "available-typed-arrays@npm:1.0.7"
|
||||
|
|
@ -3878,6 +3933,15 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"bidi-js@npm:^1.0.3":
|
||||
version: 1.0.3
|
||||
resolution: "bidi-js@npm:1.0.3"
|
||||
dependencies:
|
||||
require-from-string: "npm:^2.0.2"
|
||||
checksum: 10c0/fdddea4aa4120a34285486f2267526cd9298b6e8b773ad25e765d4f104b6d7437ab4ba542e6939e3ac834a7570bcf121ee2cf6d3ae7cd7082c4b5bedc8f271e1
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"brace-expansion@npm:^1.1.7":
|
||||
version: 1.1.11
|
||||
resolution: "brace-expansion@npm:1.1.11"
|
||||
|
|
@ -4103,15 +4167,6 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"combined-stream@npm:^1.0.8":
|
||||
version: 1.0.8
|
||||
resolution: "combined-stream@npm:1.0.8"
|
||||
dependencies:
|
||||
delayed-stream: "npm:~1.0.0"
|
||||
checksum: 10c0/0dbb829577e1b1e839fa82b40c07ffaf7de8a09b935cadd355a73652ae70a88b4320db322f6634a4ad93424292fa80973ac6480986247f1734a1137debf271d5
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"commander@npm:^13.1.0":
|
||||
version: 13.1.0
|
||||
resolution: "commander@npm:13.1.0"
|
||||
|
|
@ -4151,6 +4206,16 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"css-tree@npm:^3.0.0, css-tree@npm:^3.2.1":
|
||||
version: 3.2.1
|
||||
resolution: "css-tree@npm:3.2.1"
|
||||
dependencies:
|
||||
mdn-data: "npm:2.27.1"
|
||||
source-map-js: "npm:^1.2.1"
|
||||
checksum: 10c0/1f65e9ccaa56112a4706d6f003dd43d777f0dbcf848e66fd320f823192533581f8dd58daa906cb80622658332d50284d6be13b87a6ab4556cbbfe9ef535bbf7e
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"css-what@npm:^6.1.0":
|
||||
version: 6.2.2
|
||||
resolution: "css-what@npm:6.2.2"
|
||||
|
|
@ -4172,16 +4237,6 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"cssstyle@npm:^4.0.1":
|
||||
version: 4.3.0
|
||||
resolution: "cssstyle@npm:4.3.0"
|
||||
dependencies:
|
||||
"@asamuzakjp/css-color": "npm:^3.1.1"
|
||||
rrweb-cssom: "npm:^0.8.0"
|
||||
checksum: 10c0/770ccb288a99257fd0d5b129e03878f848e922d3b017358acb02e8dd530e8f0c7c6f74e6ae5367d715e2da36a490a734b4177fc1b78f3f08eca25f204a56a692
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"csstype@npm:^3.0.2":
|
||||
version: 3.1.3
|
||||
resolution: "csstype@npm:3.1.3"
|
||||
|
|
@ -4203,13 +4258,13 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"data-urls@npm:^5.0.0":
|
||||
version: 5.0.0
|
||||
resolution: "data-urls@npm:5.0.0"
|
||||
"data-urls@npm:^7.0.0":
|
||||
version: 7.0.0
|
||||
resolution: "data-urls@npm:7.0.0"
|
||||
dependencies:
|
||||
whatwg-mimetype: "npm:^4.0.0"
|
||||
whatwg-url: "npm:^14.0.0"
|
||||
checksum: 10c0/1b894d7d41c861f3a4ed2ae9b1c3f0909d4575ada02e36d3d3bc584bdd84278e20709070c79c3b3bff7ac98598cb191eb3e86a89a79ea4ee1ef360e1694f92ad
|
||||
whatwg-mimetype: "npm:^5.0.0"
|
||||
whatwg-url: "npm:^16.0.0"
|
||||
checksum: 10c0/08d88ef50d8966a070ffdaa703e1e4b29f01bb2da364dfbc1612b1c2a4caa8045802c9532d81347b21781100132addb36a585071c8323b12cce97973961dee9f
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
|
@ -4298,10 +4353,10 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"decimal.js@npm:^10.4.3":
|
||||
version: 10.5.0
|
||||
resolution: "decimal.js@npm:10.5.0"
|
||||
checksum: 10c0/785c35279df32762143914668df35948920b6c1c259b933e0519a69b7003fc0a5ed2a766b1e1dda02574450c566b21738a45f15e274b47c2ac02072c0d1f3ac3
|
||||
"decimal.js@npm:^10.6.0":
|
||||
version: 10.6.0
|
||||
resolution: "decimal.js@npm:10.6.0"
|
||||
checksum: 10c0/07d69fbcc54167a340d2d97de95f546f9ff1f69d2b45a02fd7a5292412df3cd9eb7e23065e532a318f5474a2e1bccf8392fdf0443ef467f97f3bf8cb0477e5aa
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
|
@ -4393,13 +4448,6 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"delayed-stream@npm:~1.0.0":
|
||||
version: 1.0.0
|
||||
resolution: "delayed-stream@npm:1.0.0"
|
||||
checksum: 10c0/d758899da03392e6712f042bec80aa293bbe9e9ff1b2634baae6a360113e708b91326594c8a486d475c69d6259afb7efacdc3537bfcda1c6c648e390ce601b19
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"dequal@npm:^2.0.3":
|
||||
version: 2.0.3
|
||||
resolution: "dequal@npm:2.0.3"
|
||||
|
|
@ -4499,10 +4547,10 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"entities@npm:^4.5.0":
|
||||
version: 4.5.0
|
||||
resolution: "entities@npm:4.5.0"
|
||||
checksum: 10c0/5b039739f7621f5d1ad996715e53d964035f75ad3b9a4d38c6b3804bb226e282ffeae2443624d8fdd9c47d8e926ae9ac009c54671243f0c3294c26af7cc85250
|
||||
"entities@npm:^8.0.0":
|
||||
version: 8.0.0
|
||||
resolution: "entities@npm:8.0.0"
|
||||
checksum: 10c0/938e631664c19451823344a351aeeafd74fae2d5fa51e4d5b6ff635afaefd4bacf0f609989888c04c42733f46ffdac15211608267ebb02488005891a4793e94d
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
|
@ -5430,19 +5478,6 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"form-data@npm:4.0.4":
|
||||
version: 4.0.4
|
||||
resolution: "form-data@npm:4.0.4"
|
||||
dependencies:
|
||||
asynckit: "npm:^0.4.0"
|
||||
combined-stream: "npm:^1.0.8"
|
||||
es-set-tostringtag: "npm:^2.1.0"
|
||||
hasown: "npm:^2.0.2"
|
||||
mime-types: "npm:^2.1.12"
|
||||
checksum: 10c0/373525a9a034b9d57073e55eab79e501a714ffac02e7a9b01be1c820780652b16e4101819785e1e18f8d98f0aee866cc654d660a435c378e16a72f2e7cac9695
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"framer-motion@npm:^11.18.2":
|
||||
version: 11.18.2
|
||||
resolution: "framer-motion@npm:11.18.2"
|
||||
|
|
@ -5761,12 +5796,12 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"html-encoding-sniffer@npm:^4.0.0":
|
||||
version: 4.0.0
|
||||
resolution: "html-encoding-sniffer@npm:4.0.0"
|
||||
"html-encoding-sniffer@npm:^6.0.0":
|
||||
version: 6.0.0
|
||||
resolution: "html-encoding-sniffer@npm:6.0.0"
|
||||
dependencies:
|
||||
whatwg-encoding: "npm:^3.1.1"
|
||||
checksum: 10c0/523398055dc61ac9b34718a719cb4aa691e4166f29187e211e1607de63dc25ac7af52ca7c9aead0c4b3c0415ffecb17326396e1202e2e86ff4bca4c0ee4c6140
|
||||
"@exodus/bytes": "npm:^1.6.0"
|
||||
checksum: 10c0/66dc3f6f5539cc3beb814fcbfae7eacf4ec38cf824d6e1425b72039b51a40f4456bd8541ba66f4f4fe09cdf885ab5cd5bae6ec6339d6895a930b2fdb83c53025
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
|
@ -5784,7 +5819,7 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"http-proxy-agent@npm:^7.0.0, http-proxy-agent@npm:^7.0.2":
|
||||
"http-proxy-agent@npm:^7.0.0":
|
||||
version: 7.0.2
|
||||
resolution: "http-proxy-agent@npm:7.0.2"
|
||||
dependencies:
|
||||
|
|
@ -5794,7 +5829,7 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"https-proxy-agent@npm:^7.0.1, https-proxy-agent@npm:^7.0.5":
|
||||
"https-proxy-agent@npm:^7.0.1":
|
||||
version: 7.0.6
|
||||
resolution: "https-proxy-agent@npm:7.0.6"
|
||||
dependencies:
|
||||
|
|
@ -5820,7 +5855,7 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"iconv-lite@npm:0.6.3, iconv-lite@npm:^0.6.2":
|
||||
"iconv-lite@npm:^0.6.2":
|
||||
version: 0.6.3
|
||||
resolution: "iconv-lite@npm:0.6.3"
|
||||
dependencies:
|
||||
|
|
@ -6355,37 +6390,37 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"jsdom@npm:^24.0.0":
|
||||
version: 24.1.3
|
||||
resolution: "jsdom@npm:24.1.3"
|
||||
"jsdom@npm:29.1.1":
|
||||
version: 29.1.1
|
||||
resolution: "jsdom@npm:29.1.1"
|
||||
dependencies:
|
||||
cssstyle: "npm:^4.0.1"
|
||||
data-urls: "npm:^5.0.0"
|
||||
decimal.js: "npm:^10.4.3"
|
||||
form-data: "npm:^4.0.0"
|
||||
html-encoding-sniffer: "npm:^4.0.0"
|
||||
http-proxy-agent: "npm:^7.0.2"
|
||||
https-proxy-agent: "npm:^7.0.5"
|
||||
"@asamuzakjp/css-color": "npm:^5.1.11"
|
||||
"@asamuzakjp/dom-selector": "npm:^7.1.1"
|
||||
"@bramus/specificity": "npm:^2.4.2"
|
||||
"@csstools/css-syntax-patches-for-csstree": "npm:^1.1.3"
|
||||
"@exodus/bytes": "npm:^1.15.0"
|
||||
css-tree: "npm:^3.2.1"
|
||||
data-urls: "npm:^7.0.0"
|
||||
decimal.js: "npm:^10.6.0"
|
||||
html-encoding-sniffer: "npm:^6.0.0"
|
||||
is-potential-custom-element-name: "npm:^1.0.1"
|
||||
nwsapi: "npm:^2.2.12"
|
||||
parse5: "npm:^7.1.2"
|
||||
rrweb-cssom: "npm:^0.7.1"
|
||||
lru-cache: "npm:^11.3.5"
|
||||
parse5: "npm:^8.0.1"
|
||||
saxes: "npm:^6.0.0"
|
||||
symbol-tree: "npm:^3.2.4"
|
||||
tough-cookie: "npm:^4.1.4"
|
||||
tough-cookie: "npm:^6.0.1"
|
||||
undici: "npm:^7.25.0"
|
||||
w3c-xmlserializer: "npm:^5.0.0"
|
||||
webidl-conversions: "npm:^7.0.0"
|
||||
whatwg-encoding: "npm:^3.1.1"
|
||||
whatwg-mimetype: "npm:^4.0.0"
|
||||
whatwg-url: "npm:^14.0.0"
|
||||
ws: "npm:^8.18.0"
|
||||
webidl-conversions: "npm:^8.0.1"
|
||||
whatwg-mimetype: "npm:^5.0.0"
|
||||
whatwg-url: "npm:^16.0.1"
|
||||
xml-name-validator: "npm:^5.0.0"
|
||||
peerDependencies:
|
||||
canvas: ^2.11.2
|
||||
canvas: ^3.0.0
|
||||
peerDependenciesMeta:
|
||||
canvas:
|
||||
optional: true
|
||||
checksum: 10c0/e48b342afacd7418a23dac204a62deea729c50f4d072a7c04c09fd32355fdb4335f8779fa79fd0277a2dbeb2d356250a950955719d00047324b251233b11277f
|
||||
checksum: 10c0/20e2174b09d9d06393cb48e1392b7a1cb7191d6656a6f7b3b8fbf9853b4ab0ef60b4a42c2c55f71b55ca5da50ffa75bcdc6986210963182e7993c6f9cd4f499b
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
|
@ -6595,6 +6630,13 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"lru-cache@npm:^11.3.5":
|
||||
version: 11.5.1
|
||||
resolution: "lru-cache@npm:11.5.1"
|
||||
checksum: 10c0/7b341cea79a8efe9c6a6f20c8757a77eca5b25d7ff983ccf4e11e547b81f6787824baa1c84705251dff84ab4ffac85717ac354b9d02e465f86a9f8b166409979
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"lru-cache@npm:^5.1.1":
|
||||
version: 5.1.1
|
||||
resolution: "lru-cache@npm:5.1.1"
|
||||
|
|
@ -6677,6 +6719,13 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"mdn-data@npm:2.27.1":
|
||||
version: 2.27.1
|
||||
resolution: "mdn-data@npm:2.27.1"
|
||||
checksum: 10c0/eb8abf5d22e4d1e090346f5e81b67d23cef14c83940e445da5c44541ad874dc8fb9f6ca236e8258c3a489d9fb5884188a4d7d58773adb9089ac2c0b966796393
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"media-query-parser@npm:^2.0.2":
|
||||
version: 2.0.2
|
||||
resolution: "media-query-parser@npm:2.0.2"
|
||||
|
|
@ -6710,22 +6759,6 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"mime-db@npm:1.52.0":
|
||||
version: 1.52.0
|
||||
resolution: "mime-db@npm:1.52.0"
|
||||
checksum: 10c0/0557a01deebf45ac5f5777fe7740b2a5c309c6d62d40ceab4e23da9f821899ce7a900b7ac8157d4548ddbb7beffe9abc621250e6d182b0397ec7f10c7b91a5aa
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"mime-types@npm:^2.1.12":
|
||||
version: 2.1.35
|
||||
resolution: "mime-types@npm:2.1.35"
|
||||
dependencies:
|
||||
mime-db: "npm:1.52.0"
|
||||
checksum: 10c0/82fb07ec56d8ff1fc999a84f2f217aa46cb6ed1033fefaabd5785b9a974ed225c90dc72fff460259e66b95b73648596dbcc50d51ed69cdf464af2d237d3149b2
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"mimic-fn@npm:^4.0.0":
|
||||
version: 4.0.0
|
||||
resolution: "mimic-fn@npm:4.0.0"
|
||||
|
|
@ -7015,13 +7048,6 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"nwsapi@npm:^2.2.12":
|
||||
version: 2.2.20
|
||||
resolution: "nwsapi@npm:2.2.20"
|
||||
checksum: 10c0/07f4dafa3186aef7c007863e90acd4342a34ba9d44b22f14f644fdb311f6086887e21c2fc15efaa826c2bc39ab2bc841364a1a630e7c87e0cb723ba59d729297
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"object-assign@npm:^4.1.1":
|
||||
version: 4.1.1
|
||||
resolution: "object-assign@npm:4.1.1"
|
||||
|
|
@ -7205,12 +7231,12 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"parse5@npm:^7.1.2":
|
||||
version: 7.2.1
|
||||
resolution: "parse5@npm:7.2.1"
|
||||
"parse5@npm:^8.0.1":
|
||||
version: 8.0.1
|
||||
resolution: "parse5@npm:8.0.1"
|
||||
dependencies:
|
||||
entities: "npm:^4.5.0"
|
||||
checksum: 10c0/829d37a0c709215a887e410a7118d754f8e1afd7edb529db95bc7bbf8045fb0266a7b67801331d8e8d9d073ea75793624ec27ce9ff3b96862c3b9008f4d68e80
|
||||
entities: "npm:^8.0.0"
|
||||
checksum: 10c0/c3c1c5aab55f6e4be5245599790e56e64be7764a4a0edd7f98db4fe3bb380f63add752fa047dff0496446c25f4104f0c7c1967723de640bde92306a7bb67ed2f
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
|
@ -7604,27 +7630,27 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"react-router-dom@npm:6.30.2":
|
||||
version: 6.30.2
|
||||
resolution: "react-router-dom@npm:6.30.2"
|
||||
"react-router-dom@npm:6.30.4":
|
||||
version: 6.30.4
|
||||
resolution: "react-router-dom@npm:6.30.4"
|
||||
dependencies:
|
||||
"@remix-run/router": "npm:1.23.1"
|
||||
react-router: "npm:6.30.2"
|
||||
"@remix-run/router": "npm:1.23.3"
|
||||
react-router: "npm:6.30.4"
|
||||
peerDependencies:
|
||||
react: ">=16.8"
|
||||
react-dom: ">=16.8"
|
||||
checksum: 10c0/d0c6edf4e2aa7639b4a4f64a7747f03d8861bdf4857e8981b1cff1451b7cb91fcdcd7e315a6e3df910271b2f5071825d2aec218d5f7890f2269fc074f198e42a
|
||||
checksum: 10c0/1b25ab26a288da852f7b58eec5c14b3f37919e6773a557cd846d3a05d7a7d890c8d49bda93c0a7f497f240df1df8b0ad50635f990aafb55f2fc545ad8269a822
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"react-router@npm:6.30.2":
|
||||
version: 6.30.2
|
||||
resolution: "react-router@npm:6.30.2"
|
||||
"react-router@npm:6.30.4":
|
||||
version: 6.30.4
|
||||
resolution: "react-router@npm:6.30.4"
|
||||
dependencies:
|
||||
"@remix-run/router": "npm:1.23.1"
|
||||
"@remix-run/router": "npm:1.23.3"
|
||||
peerDependencies:
|
||||
react: ">=16.8"
|
||||
checksum: 10c0/cff5ea92d248d2230adc46d4e2ed3fbeddfaf1ae2e63411da8b7ea6ddc207d71dbc522c05c492e671e553e2153934f4ab180ac02bd36205b274e097f2cfe6fc4
|
||||
checksum: 10c0/fb6de7d1002bcab9ea12c4072d93792eca494f1e4f30cfec334ccb6523756d23ce3e093c7c1241399296cebed94789a3fb89f96ee76004e0e746458a8f6bab33
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
|
@ -7707,6 +7733,13 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"require-from-string@npm:^2.0.2":
|
||||
version: 2.0.2
|
||||
resolution: "require-from-string@npm:2.0.2"
|
||||
checksum: 10c0/aaa267e0c5b022fc5fd4eef49d8285086b15f2a1c54b28240fdf03599cbd9c26049fee3eab894f2e1f6ca65e513b030a7c264201e3f005601e80c49fb2937ce2
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"requires-port@npm:^1.0.0":
|
||||
version: 1.0.0
|
||||
resolution: "requires-port@npm:1.0.0"
|
||||
|
|
@ -7967,20 +8000,6 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"rrweb-cssom@npm:^0.7.1":
|
||||
version: 0.7.1
|
||||
resolution: "rrweb-cssom@npm:0.7.1"
|
||||
checksum: 10c0/127b8ca6c8aac45e2755abbae6138d4a813b1bedc2caabf79466ae83ab3cfc84b5bfab513b7033f0aa4561c7753edf787d0dd01163ceacdee2e8eb1b6bf7237e
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"rrweb-cssom@npm:^0.8.0":
|
||||
version: 0.8.0
|
||||
resolution: "rrweb-cssom@npm:0.8.0"
|
||||
checksum: 10c0/56f2bfd56733adb92c0b56e274c43f864b8dd48784d6fe946ef5ff8d438234015e59ad837fc2ad54714b6421384141c1add4eb569e72054e350d1f8a50b8ac7b
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"run-parallel@npm:^1.1.9":
|
||||
version: 1.2.0
|
||||
resolution: "run-parallel@npm:1.2.0"
|
||||
|
|
@ -8628,6 +8647,24 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"tldts-core@npm:^7.4.2":
|
||||
version: 7.4.2
|
||||
resolution: "tldts-core@npm:7.4.2"
|
||||
checksum: 10c0/e8e02a43f6823ea1beab8f5a9da370b9a6cbf1a942d4f7d828700e65f03a119348f8d19faa95b599f3ca76fcb5fe5c4611d5b9c274f5a58c7487d342f6083a06
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"tldts@npm:^7.0.5":
|
||||
version: 7.4.2
|
||||
resolution: "tldts@npm:7.4.2"
|
||||
dependencies:
|
||||
tldts-core: "npm:^7.4.2"
|
||||
bin:
|
||||
tldts: bin/cli.js
|
||||
checksum: 10c0/68f7ec58c9ea41f1583a6384f0fdf1b33d2d8ee55e7d403ec5cf9de4a7226a25c585b5ce1a73de8ddc9abbbe7b783bb3554d1c811fd47fceb0d5511e06d0d766
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"to-regex-range@npm:^5.0.1":
|
||||
version: 5.0.1
|
||||
resolution: "to-regex-range@npm:5.0.1"
|
||||
|
|
@ -8649,12 +8686,21 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"tr46@npm:^5.1.0":
|
||||
version: 5.1.0
|
||||
resolution: "tr46@npm:5.1.0"
|
||||
"tough-cookie@npm:^6.0.1":
|
||||
version: 6.0.1
|
||||
resolution: "tough-cookie@npm:6.0.1"
|
||||
dependencies:
|
||||
tldts: "npm:^7.0.5"
|
||||
checksum: 10c0/ec70bd6b1215efe4ed31a158f0be3e4c9088fcbd8620edc23a5860d4f3d85c757b77e274baaa700f7b25e409f4181552ed189603c2b2e1a9f88104da3a61a37d
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"tr46@npm:^6.0.0":
|
||||
version: 6.0.0
|
||||
resolution: "tr46@npm:6.0.0"
|
||||
dependencies:
|
||||
punycode: "npm:^2.3.1"
|
||||
checksum: 10c0/d761f7144e0cb296187674ef245c74f761e334d7cf25ca73ef60e4c72c097c75051031c093fa1a2fee04b904977b316716a7915854bcae8fb1a371746513cbe8
|
||||
checksum: 10c0/83130df2f649228aa91c17754b66248030a3af34911d713b5ea417066fa338aa4bc8668d06bd98aa21a2210f43fc0a3db8b9099e7747fb5830e40e39a6a1058e
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
|
@ -8691,7 +8737,7 @@ __metadata:
|
|||
globals: "npm:^16.0.0"
|
||||
husky: "npm:^9.0.0"
|
||||
jest-extended: "npm:^4.0.2"
|
||||
jsdom: "npm:^24.0.0"
|
||||
jsdom: "npm:29.1.1"
|
||||
lint-staged: "npm:^15.0.0"
|
||||
lodash: "npm:4.18.1"
|
||||
msw: "npm:^2.1.7"
|
||||
|
|
@ -8704,7 +8750,7 @@ __metadata:
|
|||
react-helmet-async: "npm:^2.0.4"
|
||||
react-icons: "npm:^5.0.1"
|
||||
react-infinite-scroll-hook: "npm:^4.1.1"
|
||||
react-router-dom: "npm:6.30.2"
|
||||
react-router-dom: "npm:6.30.4"
|
||||
swr: "npm:^2.2.4"
|
||||
typescript: "npm:^5.2.2"
|
||||
typescript-eslint: "npm:^8.38.0"
|
||||
|
|
@ -8888,6 +8934,13 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"undici@npm:7.27.0":
|
||||
version: 7.27.0
|
||||
resolution: "undici@npm:7.27.0"
|
||||
checksum: 10c0/6fd15a81b0ca177b2667738b830ed175363e5e2164e992251d03aaee6c6517098b930085bd68b8fe5911920371076526657de035e07dc72377b9c5c731b90f0b
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"unique-filename@npm:^4.0.0":
|
||||
version: 4.0.0
|
||||
resolution: "unique-filename@npm:4.0.0"
|
||||
|
|
@ -9266,36 +9319,28 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"webidl-conversions@npm:^7.0.0":
|
||||
version: 7.0.0
|
||||
resolution: "webidl-conversions@npm:7.0.0"
|
||||
checksum: 10c0/228d8cb6d270c23b0720cb2d95c579202db3aaf8f633b4e9dd94ec2000a04e7e6e43b76a94509cdb30479bd00ae253ab2371a2da9f81446cc313f89a4213a2c4
|
||||
"webidl-conversions@npm:^8.0.1":
|
||||
version: 8.0.1
|
||||
resolution: "webidl-conversions@npm:8.0.1"
|
||||
checksum: 10c0/3f6f327ca5fa0c065ed8ed0ef3b72f33623376e68f958e9b7bd0df49fdb0b908139ac2338d19fb45bd0e05595bda96cb6d1622222a8b413daa38a17aacc4dd46
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"whatwg-encoding@npm:^3.1.1":
|
||||
version: 3.1.1
|
||||
resolution: "whatwg-encoding@npm:3.1.1"
|
||||
"whatwg-mimetype@npm:^5.0.0":
|
||||
version: 5.0.0
|
||||
resolution: "whatwg-mimetype@npm:5.0.0"
|
||||
checksum: 10c0/eead164fe73a00dd82f817af6fc0bd22e9c273e1d55bf4bc6bdf2da7ad8127fca82ef00ea6a37892f5f5641f8e34128e09508f92126086baba126b9e0d57feb4
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"whatwg-url@npm:^16.0.0, whatwg-url@npm:^16.0.1":
|
||||
version: 16.0.1
|
||||
resolution: "whatwg-url@npm:16.0.1"
|
||||
dependencies:
|
||||
iconv-lite: "npm:0.6.3"
|
||||
checksum: 10c0/273b5f441c2f7fda3368a496c3009edbaa5e43b71b09728f90425e7f487e5cef9eb2b846a31bd760dd8077739c26faf6b5ca43a5f24033172b003b72cf61a93e
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"whatwg-mimetype@npm:^4.0.0":
|
||||
version: 4.0.0
|
||||
resolution: "whatwg-mimetype@npm:4.0.0"
|
||||
checksum: 10c0/a773cdc8126b514d790bdae7052e8bf242970cebd84af62fb2f35a33411e78e981f6c0ab9ed1fe6ec5071b09d5340ac9178e05b52d35a9c4bcf558ba1b1551df
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"whatwg-url@npm:^14.0.0":
|
||||
version: 14.2.0
|
||||
resolution: "whatwg-url@npm:14.2.0"
|
||||
dependencies:
|
||||
tr46: "npm:^5.1.0"
|
||||
webidl-conversions: "npm:^7.0.0"
|
||||
checksum: 10c0/f746fc2f4c906607d09537de1227b13f9494c171141e5427ed7d2c0dd0b6a48b43d8e71abaae57d368d0c06b673fd8ec63550b32ad5ed64990c7b0266c2b4272
|
||||
"@exodus/bytes": "npm:^1.11.0"
|
||||
tr46: "npm:^6.0.0"
|
||||
webidl-conversions: "npm:^8.0.1"
|
||||
checksum: 10c0/e75565566abf3a2cdbd9f06c965dbcccee6ec4e9f0d3728ad5e08ceb9944279848bcaa211d35a29cb6d2df1e467dd05cfb59fbddf8a0adcd7d0bce9ffb703fd2
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
|
@ -9445,21 +9490,6 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"ws@npm:^8.18.0":
|
||||
version: 8.18.1
|
||||
resolution: "ws@npm:8.18.1"
|
||||
peerDependencies:
|
||||
bufferutil: ^4.0.1
|
||||
utf-8-validate: ">=5.0.2"
|
||||
peerDependenciesMeta:
|
||||
bufferutil:
|
||||
optional: true
|
||||
utf-8-validate:
|
||||
optional: true
|
||||
checksum: 10c0/e498965d6938c63058c4310ffb6967f07d4fa06789d3364829028af380d299fe05762961742971c764973dce3d1f6a2633fe8b2d9410c9b52e534b4b882a99fa
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"xml-name-validator@npm:^5.0.0":
|
||||
version: 5.0.0
|
||||
resolution: "xml-name-validator@npm:5.0.0"
|
||||
|
|
|
|||
Loading…
Reference in a new issue