Merge branch v3.6 into master

This commit is contained in:
kevinpollet 2026-02-13 16:04:04 +01:00
commit f0da74e641
No known key found for this signature in database
GPG Key ID: 0C9A5DDD1B292453
23 changed files with 365 additions and 97 deletions

View File

@ -1,3 +1,61 @@
## [v3.6.8](https://github.com/traefik/traefik/tree/v3.6.8) (2026-02-11)
[All Commits](https://github.com/traefik/traefik/compare/v3.6.7...v3.6.8)
**Bug fixes:**
- **[acme]** Remove invalid private key in log ([#12574](https://github.com/traefik/traefik/pull/12574) by [juliens](https://github.com/juliens))
- **[acme]** Alter TLS renewal period ([#12479](https://github.com/traefik/traefik/pull/12479) by [LtHummus](https://github.com/LtHummus))
- **[healthcheck]** Reject absolute URL in healthcheck path configuration ([#12653](https://github.com/traefik/traefik/pull/12653) by [rtribotte](https://github.com/rtribotte))
- **[http3]** Bump github.com/quic-go/quic-go to v0.59.0 ([#12553](https://github.com/traefik/traefik/pull/12553) by [jnoordsij](https://github.com/jnoordsij))
- **[metrics,tracing,accesslogs]** Fix ObservabilityConfig SetDefaults ([#12636](https://github.com/traefik/traefik/pull/12636) by [mmatur](https://github.com/mmatur))
- **[server]** Remove conn deadline after STARTTLS negociation ([#12639](https://github.com/traefik/traefik/pull/12639) by [rtribotte](https://github.com/rtribotte))
- **[tls]** Fix verifyServerCertMatchesURI function behavior ([#12575](https://github.com/traefik/traefik/pull/12575) by [kevinpollet](https://github.com/kevinpollet))
- **[tracing,otel]** Use ParentBased sampler to respect parent span sampling decision ([#12403](https://github.com/traefik/traefik/pull/12403) by [xe-leon](https://github.com/xe-leon))
- **[webui]** Use url.Parse to validate X-Forwarded-Prefix value ([#12643](https://github.com/traefik/traefik/pull/12643) by [kevinpollet](https://github.com/kevinpollet))
- **[healthcheck]** Validate healthcheck path configuration (#12642 by @rtribotte)
- **[tls, server]** Cap TLS record length to RFC 8446 limit in ClientHello peeking (#12638 by @mmatur)
- **[service]** Avoid recursion with services ([#12591](https://github.com/traefik/traefik/pull/12591) by [juliens](https://github.com/juliens))
- **[webui]** Bump dependencies of documentation and webui ([#12581](https://github.com/traefik/traefik/pull/12581) by [gndz07](https://github.com/gndz07))
**Documentation:**
- **[k8s]** Fix kubernetes.md with correct http redirections ([#12603](https://github.com/traefik/traefik/pull/12603) by [MartenM](https://github.com/MartenM))
- **[middleware,k8s/crd]** Fix the errors middleware's document for Kubernetes CRD ([#12600](https://github.com/traefik/traefik/pull/12600) by [yuito-it](https://github.com/yuito-it))
- **[tls]** Clarify SNI selection ([#12482](https://github.com/traefik/traefik/pull/12482) by [AnuragEkkati](https://github.com/AnuragEkkati))
- Fix typo on JWT documentation ([#12616](https://github.com/traefik/traefik/pull/12616) by [mdevino](https://github.com/mdevino))
- Add @gndz07 as a current maintainer ([#12594](https://github.com/traefik/traefik/pull/12594) by [emilevauge](https://github.com/emilevauge))
- Remove extraneous dots in migration guide ([#12571](https://github.com/traefik/traefik/pull/12571) by [dathbe](https://github.com/dathbe))
- Document Path matcher placeholder removal in v3 migration guide ([#12570](https://github.com/traefik/traefik/pull/12570) by [sheddy-traefik](https://github.com/sheddy-traefik))
- Improve Service Reference page ([#12541](https://github.com/traefik/traefik/pull/12541) by [sheddy-traefik](https://github.com/sheddy-traefik))
- Document negative priority support for routers ([#12505](https://github.com/traefik/traefik/pull/12505) by [understood-the-assignment](https://github.com/understood-the-assignment))
- Improve the structure of the routing reference pages ([#12429](https://github.com/traefik/traefik/pull/12429) by [sheddy-traefik](https://github.com/sheddy-traefik))
- Clean Up Menu Entries & Update Expose Overview ([#12405](https://github.com/traefik/traefik/pull/12405) by [sheddy-traefik](https://github.com/sheddy-traefik))
- Split Expose User Guides & Add Multi-Layer Routing Section ([#12238](https://github.com/traefik/traefik/pull/12238) by [sheddy-traefik](https://github.com/sheddy-traefik))
- Remove extra dots in migration guide ([#12573](https://github.com/traefik/traefik/pull/12573) by [rtribotte](https://github.com/rtribotte))
**Misc:**
- Merge v2.11 into v3.6 ([#12652](https://github.com/traefik/traefik/pull/12652) by [mmatur](https://github.com/mmatur))
- Merge v2.11 into v3.6 ([#12644](https://github.com/traefik/traefik/pull/12644) by [mmatur](https://github.com/mmatur))
- Merge branch v2.11 into v3.6 ([#12617](https://github.com/traefik/traefik/pull/12617) by [mmatur](https://github.com/mmatur))
- Merge v2.11 into v3.6 ([#12605](https://github.com/traefik/traefik/pull/12605) by [mmatur](https://github.com/mmatur))
- Merge v2.11 into v3.6 ([#12601](https://github.com/traefik/traefik/pull/12601) by [mmatur](https://github.com/mmatur))
- Merge branch v2.11 into v3.6 ([#12556](https://github.com/traefik/traefik/pull/12556) by [mmatur](https://github.com/mmatur))
## [v2.11.37](https://github.com/traefik/traefik/tree/v2.11.37) (2026-02-11)
[All Commits](https://github.com/traefik/traefik/compare/v2.11.36...v2.11.37)
**Bug fixes:**
- **[healthcheck]** Validate healthcheck path configuration (#12642 by @rtribotte)
- **[tls, server]** Cap TLS record length to RFC 8446 limit in ClientHello peeking (#12638 by @mmatur)
## [v2.11.36](https://github.com/traefik/traefik/tree/v2.11.36) (2026-02-02)
[All Commits](https://github.com/traefik/traefik/compare/v2.11.35...v2.11.36)
**Bug fixes:**
- **[service]** Avoid recursion with services ([#12591](https://github.com/traefik/traefik/pull/12591) by [juliens](https://github.com/juliens))
- **[webui]** Bump dependencies of documentation and webui ([#12581](https://github.com/traefik/traefik/pull/12581) by [gndz07](https://github.com/gndz07))
**Documentation:**
- Remove extra dots in migration guide ([#12573](https://github.com/traefik/traefik/pull/12573) by [rtribotte](https://github.com/rtribotte))
## [v3.6.7](https://github.com/traefik/traefik/tree/v3.6.7) (2026-01-14)
[All Commits](https://github.com/traefik/traefik/compare/v3.6.6...v3.6.7)

View File

@ -0,0 +1,4 @@
/* Use a wider grid to accommodate table content and code blocks. */
.md-grid {
max-width: 1650px;
}

View File

@ -604,6 +604,12 @@ in [RFC3986 section-3](https://datatracker.ietf.org/doc/html/rfc3986#section-3).
Please check out the entrypoint [encodedCharacters option](../routing/entrypoints.md#encoded-characters) documentation
for more details.
## v3.6.8
### Health Check Request Path
Since `v3.6.8`, the configured path for the health check request is now verified to be a relative URL, and the health check will fail if it is not.
## v3.7.0
### Ingress NGINX Provider

View File

@ -290,13 +290,13 @@ See [label-selectors](https://kubernetes.io/docs/concepts/overview/working-with-
```yaml tab="File (YAML)"
providers:
kubernetesGateway:
labelselector: "app=traefik"
labelSelector: "app=traefik"
# ...
```
```toml tab="File (TOML)"
[providers.kubernetesGateway]
labelselector = "app=traefik"
labelSelector = "app=traefik"
# ...
```

View File

@ -91,7 +91,7 @@ The provider then watches for incoming Knative events and derives the correspond
| <a id="opt-providers-knative-token" href="#opt-providers-knative-token" title="#opt-providers-knative-token">providers.knative.token</a> | Bearer token used for the Kubernetes client configuration. | |
| <a id="opt-providers-knative-certauthfilepath" href="#opt-providers-knative-certauthfilepath" title="#opt-providers-knative-certauthfilepath">providers.knative.certauthfilepath</a> | Path to the certificate authority file.<br />Used for the Kubernetes client configuration. | |
| <a id="opt-providers-knative-namespaces" href="#opt-providers-knative-namespaces" title="#opt-providers-knative-namespaces">providers.knative.namespaces</a> | Array of namespaces to watch.<br />If left empty, watch all namespaces. | |
| <a id="opt-providers-knative-labelselector" href="#opt-providers-knative-labelselector" title="#opt-providers-knative-labelselector">providers.knative.labelselector</a> | Allow filtering Knative Ingress objects using label selectors. | |
| <a id="opt-providers-knative-labelSelector" href="#opt-providers-knative-labelSelector" title="#opt-providers-knative-labelSelector">providers.knative.labelSelector</a> | Allow filtering Knative Ingress objects using label selectors. | |
| <a id="opt-providers-knative-throttleduration" href="#opt-providers-knative-throttleduration" title="#opt-providers-knative-throttleduration">providers.knative.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. | 0 |
| <a id="opt-providers-knative-privateentrypoints" href="#opt-providers-knative-privateentrypoints" title="#opt-providers-knative-privateentrypoints">providers.knative.privateentrypoints</a> | Entrypoint names used to expose the Ingress privately. If empty local Ingresses are skipped. | |
| <a id="opt-providers-knative-privateservice" href="#opt-providers-knative-privateservice" title="#opt-providers-knative-privateservice">providers.knative.privateservice</a> | Kubernetes service used to expose the networking controller privately. | |

View File

@ -59,7 +59,7 @@ providers:
| <a id="opt-providers-kubernetesCRD-token" href="#opt-providers-kubernetesCRD-token" title="#opt-providers-kubernetesCRD-token">`providers.kubernetesCRD.token`</a> | Bearer token used for the Kubernetes client configuration. | "" | No |
| <a id="opt-providers-kubernetesCRD-certAuthFilePath" href="#opt-providers-kubernetesCRD-certAuthFilePath" title="#opt-providers-kubernetesCRD-certAuthFilePath">`providers.kubernetesCRD.certAuthFilePath`</a> | Path to the certificate authority file.<br />Used for the Kubernetes client configuration. | "" | No |
| <a id="opt-providers-kubernetesCRD-namespaces" href="#opt-providers-kubernetesCRD-namespaces" title="#opt-providers-kubernetesCRD-namespaces">`providers.kubernetesCRD.namespaces`</a> | Array of namespaces to watch.<br />If left empty, watch all namespaces. | [] | No |
| <a id="opt-providers-kubernetesCRD-labelselector" href="#opt-providers-kubernetesCRD-labelselector" title="#opt-providers-kubernetesCRD-labelselector">`providers.kubernetesCRD.labelselector`</a> | Allow filtering on specific resource objects only using label selectors.<br />Only to Traefik [Custom Resources](#list-of-resources) (they all must match the filter).<br />No effect on Kubernetes `Secrets`, `EndpointSlices` and `Services`.<br />See [label-selectors](https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors) for details. | "" | No |
| <a id="opt-providers-kubernetesCRD-labelSelector" href="#opt-providers-kubernetesCRD-labelSelector" title="#opt-providers-kubernetesCRD-labelSelector">`providers.kubernetesCRD.labelSelector`</a> | Allow filtering on specific resource objects only using label selectors.<br />Only to Traefik [Custom Resources](#list-of-resources) (they all must match the filter).<br />No effect on Kubernetes `Secrets`, `EndpointSlices` and `Services`.<br />See [label-selectors](https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors) for details. | "" | No |
| <a id="opt-providers-kubernetesCRD-ingressClass" href="#opt-providers-kubernetesCRD-ingressClass" title="#opt-providers-kubernetesCRD-ingressClass">`providers.kubernetesCRD.ingressClass`</a> | Value of `spec.ingressClassName` field (or the deprecated `kubernetes.io/ingress.class` annotation) that identifies resource objects to be processed.<br />If empty, resources missing the field/annotation, having an empty value, or the value `traefik` are processed.<br />The `spec.ingressClassName` field takes precedence over the annotation. | "" | No |
| <a id="opt-providers-kubernetesCRD-throttleDuration" href="#opt-providers-kubernetesCRD-throttleDuration" title="#opt-providers-kubernetesCRD-throttleDuration">`providers.kubernetesCRD.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-kubernetesCRD-allowEmptyServices" href="#opt-providers-kubernetesCRD-allowEmptyServices" title="#opt-providers-kubernetesCRD-allowEmptyServices">`providers.kubernetesCRD.allowEmptyServices`</a> | Allows creating a route to reach a service that has no endpoint available.<br />It allows Traefik to handle the requests and responses targeting this service (applying middleware or observability operations) before returning a `503` HTTP Status. | false | No |

View File

@ -75,7 +75,7 @@ providers:
| <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 specific resource objects only using label selectors.<br />Only to Traefik [Custom Resources](./kubernetes-crd.md#list-of-resources) (they all must match the filter).<br />No effect on Kubernetes `Secrets`, `EndpointSlices` and `Services`.<br />See [label-selectors](https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors) for details. | "" | No |
| <a id="opt-providers-kubernetesGateway-labelSelector" href="#opt-providers-kubernetesGateway-labelSelector" title="#opt-providers-kubernetesGateway-labelSelector">`providers.kubernetesGateway.labelSelector`</a> | Allow filtering on specific resource objects only using label selectors.<br />Only to Traefik [Custom Resources](./kubernetes-crd.md#list-of-resources) (they all must match the filter).<br />No effect on Kubernetes `Secrets`, `EndpointSlices` and `Services`.<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 |

View File

@ -52,7 +52,7 @@ which in turn creates the resulting routers, services, handlers, etc.
| <a id="opt-providers-kubernetesIngress-token" href="#opt-providers-kubernetesIngress-token" title="#opt-providers-kubernetesIngress-token">`providers.kubernetesIngress.token`</a> | Bearer token used for the Kubernetes client configuration. | "" | No |
| <a id="opt-providers-kubernetesIngress-certAuthFilePath" href="#opt-providers-kubernetesIngress-certAuthFilePath" title="#opt-providers-kubernetesIngress-certAuthFilePath">`providers.kubernetesIngress.certAuthFilePath`</a> | Path to the certificate authority file.<br />Used for the Kubernetes client configuration. | "" | No |
| <a id="opt-providers-kubernetesIngress-namespaces" href="#opt-providers-kubernetesIngress-namespaces" title="#opt-providers-kubernetesIngress-namespaces">`providers.kubernetesIngress.namespaces`</a> | Array of namespaces to watch.<br />If left empty, watch all namespaces. | | No |
| <a id="opt-providers-kubernetesIngress-labelselector" href="#opt-providers-kubernetesIngress-labelselector" title="#opt-providers-kubernetesIngress-labelselector">`providers.kubernetesIngress.labelselector`</a> | Allow filtering on Ingress objects using label selectors.<br />No effect on Kubernetes `Secrets`, `EndpointSlices` and `Services`.<br />See [label-selectors](https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors) for details. | "" | No |
| <a id="opt-providers-kubernetesIngress-labelSelector" href="#opt-providers-kubernetesIngress-labelSelector" title="#opt-providers-kubernetesIngress-labelSelector">`providers.kubernetesIngress.labelSelector`</a> | Allow filtering on Ingress objects using label selectors.<br />No effect on Kubernetes `Secrets`, `EndpointSlices` and `Services`.<br />See [label-selectors](https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors) for details. | "" | No |
| <a id="opt-providers-kubernetesIngress-ingressClass" href="#opt-providers-kubernetesIngress-ingressClass" title="#opt-providers-kubernetesIngress-ingressClass">`providers.kubernetesIngress.ingressClass`</a> | The `IngressClass` resource name or the `kubernetes.io/ingress.class` annotation value that identifies resource objects to be processed.<br />If empty, resources missing the annotation, having an empty value, or the value `traefik` are processed. | "" | No |
| <a id="opt-providers-kubernetesIngress-disableIngressClassLookup" href="#opt-providers-kubernetesIngress-disableIngressClassLookup" title="#opt-providers-kubernetesIngress-disableIngressClassLookup">`providers.kubernetesIngress.disableIngressClassLookup`</a> | Prevent to discover IngressClasses in the cluster.<br />It alleviates the requirement of giving Traefik the rights to look IngressClasses up.<br />Ignore Ingresses with IngressClass.<br />Annotations are not affected by this option. | false | No |
| <a id="opt-providers-kubernetesIngress-ingressEndpoint-hostname" href="#opt-providers-kubernetesIngress-ingressEndpoint-hostname" title="#opt-providers-kubernetesIngress-ingressEndpoint-hostname">`providers.kubernetesIngress.`<br />`ingressEndpoint.hostname`</a> | Hostname used for Kubernetes Ingress endpoints. | "" | No |

View File

@ -292,9 +292,9 @@ To propagate status changes (e.g. all servers of this service are down) upwards,
Below are the available options for the health check mechanism:
| Field | Description | Default | Required |
|---------------------|-------------------------------------------------------------------------------------------------------------------------------|---------|----------|
| <a id="opt-path" href="#opt-path" title="#opt-path">`path`</a> | Defines the server URL path for the health check endpoint. | "" | Yes |
| Field | Description | Default | Required |
|--------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------|---------|----------|
| <a id="opt-path" href="#opt-path" title="#opt-path">`path`</a> | Defines the server URL path for the health check endpoint. The configured path must be relative URL. | "" | Yes |
| <a id="opt-scheme" href="#opt-scheme" title="#opt-scheme">`scheme`</a> | Replaces the server URL scheme for the health check endpoint. | | No |
| <a id="opt-mode" href="#opt-mode" title="#opt-mode">`mode`</a> | If defined to `grpc`, will use the gRPC health check protocol to probe the server. | http | No |
| <a id="opt-hostname" href="#opt-hostname" title="#opt-hostname">`hostname`</a> | Defines the value of hostname in the Host header of the health check request. | "" | No |

View File

@ -80,36 +80,36 @@ spec:
## Configuration Options
| Field | Description | Default | Required |
|:---------------------------------------------------------------------------------|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:---------------------------------------------------------------------|:---------|
| <a id="opt-kind" href="#opt-kind" title="#opt-kind">`kind`</a> | Kind of the service targeted.<br />Two values allowed:<br />- **Service**: Kubernetes Service<br /> **TraefikService**: Traefik Service.<br />More information [here](#externalname-service). | "Service" | No |
| <a id="opt-name" href="#opt-name" title="#opt-name">`name`</a> | Service name.<br />The character `@` is not authorized. <br />More information [here](#middleware). | | Yes |
| <a id="opt-namespace" href="#opt-namespace" title="#opt-namespace">`namespace`</a> | Service namespace.<br />Can be empty if the service belongs to the same namespace as the IngressRoute. <br />More information [here](#externalname-service). | | No |
| <a id="opt-port" href="#opt-port" title="#opt-port">`port`</a> | Service port (number or port name).<br />Evaluated only if the kind is **Service**. | | No |
| <a id="opt-responseForwarding-flushInterval" href="#opt-responseForwarding-flushInterval" title="#opt-responseForwarding-flushInterval">`responseForwarding.`<br />`flushInterval`</a> | Interval, in milliseconds, in between flushes to the client while copying the response body.<br />A negative value means to flush immediately after each write to the client.<br />This configuration is ignored when a response is a streaming response; for such responses, writes are flushed to the client immediately.<br />Evaluated only if the kind is **Service**. | 100ms | No |
| <a id="opt-scheme" href="#opt-scheme" title="#opt-scheme">`scheme`</a> | Scheme to use for the request to the upstream Kubernetes Service.<br />Evaluated only if the kind is **Service**. | "http"<br />"https" if `port` is 443 or contains the string *https*. | No |
| <a id="opt-serversTransport" href="#opt-serversTransport" title="#opt-serversTransport">`serversTransport`</a> | Name of ServersTransport resource to use to configure the transport between Traefik and your servers.<br />Evaluated only if the kind is **Service**. | "" | No |
| <a id="opt-passHostHeader" href="#opt-passHostHeader" title="#opt-passHostHeader">`passHostHeader`</a> | Forward client Host header to server.<br />Evaluated only if the kind is **Service**. | true | No |
| <a id="opt-healthCheck-scheme" href="#opt-healthCheck-scheme" title="#opt-healthCheck-scheme">`healthCheck.scheme`</a> | Server URL scheme for the health check endpoint.<br />Evaluated only if the kind is **Service**.<br />Only for [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) of type [ExternalName](#externalname-service). | "" | No |
| <a id="opt-healthCheck-mode" href="#opt-healthCheck-mode" title="#opt-healthCheck-mode">`healthCheck.mode`</a> | Health check mode.<br /> If defined to grpc, will use the gRPC health check protocol to probe the server.<br />Evaluated only if the kind is **Service**.<br />Only for [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) of type [ExternalName](#externalname-service). | "http" | No |
| <a id="opt-healthCheck-path" href="#opt-healthCheck-path" title="#opt-healthCheck-path">`healthCheck.path`</a> | Server URL path for the health check endpoint.<br />Evaluated only if the kind is **Service**.<br />Only for [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) of type [ExternalName](#externalname-service). | "" | No |
| <a id="opt-healthCheck-interval" href="#opt-healthCheck-interval" title="#opt-healthCheck-interval">`healthCheck.interval`</a> | Frequency of the health check calls for healthy targets.<br />Evaluated only if the kind is **Service**.<br />Only for [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) of type [ExternalName](#externalname-service). | "100ms" | No |
| <a id="opt-healthCheck-unhealthyInterval" href="#opt-healthCheck-unhealthyInterval" title="#opt-healthCheck-unhealthyInterval">`healthCheck.unhealthyInterval`</a> | Frequency of the health check calls for unhealthy targets.<br />When not defined, it defaults to the `interval` value.<br />Evaluated only if the kind is **Service**.<br />Only for [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) of type [ExternalName](#externalname-service). | "100ms" | No |
| <a id="opt-healthCheck-method" href="#opt-healthCheck-method" title="#opt-healthCheck-method">`healthCheck.method`</a> | HTTP method for the health check endpoint.<br />Evaluated only if the kind is **Service**.<br />Only for [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) of type [ExternalName](#externalname-service). | "GET" | No |
| <a id="opt-healthCheck-status" href="#opt-healthCheck-status" title="#opt-healthCheck-status">`healthCheck.status`</a> | Expected HTTP status code of the response to the health check request.<br />Only for [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) of type ExternalName.<br />If not set, expect a status between 200 and 399.<br />Evaluated only if the kind is **Service**. | | No |
| <a id="opt-healthCheck-port" href="#opt-healthCheck-port" title="#opt-healthCheck-port">`healthCheck.port`</a> | URL port for the health check endpoint.<br />Evaluated only if the kind is **Service**.<br />Only for [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) of type [ExternalName](#externalname-service). | | No |
| <a id="opt-healthCheck-timeout" href="#opt-healthCheck-timeout" title="#opt-healthCheck-timeout">`healthCheck.timeout`</a> | Maximum duration to wait before considering the server unhealthy.<br />Evaluated only if the kind is **Service**.<br />Only for [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) of type [ExternalName](#externalname-service). | "5s" | No |
| <a id="opt-healthCheck-hostname" href="#opt-healthCheck-hostname" title="#opt-healthCheck-hostname">`healthCheck.hostname`</a> | Value in the Host header of the health check request.<br />Evaluated only if the kind is **Service**.<br />Only for [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) of type [ExternalName](#externalname-service). | "" | No |
| <a id="opt-healthCheck-followRedirect" href="#opt-healthCheck-followRedirect" title="#opt-healthCheck-followRedirect">`healthCheck.`<br />`followRedirect`</a> | Follow the redirections during the healtchcheck.<br />Evaluated only if the kind is **Service**.<br />Only for [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) of type [ExternalName](#externalname-service). | true | No |
| <a id="opt-healthCheck-headers" href="#opt-healthCheck-headers" title="#opt-healthCheck-headers">`healthCheck.headers`</a> | Map of header to send to the health check endpoint<br />Evaluated only if the kind is **Service**.<br />Only for [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) of type [ExternalName](#externalname-service)). | | No |
| <a id="opt-sticky-cookie-name" href="#opt-sticky-cookie-name" title="#opt-sticky-cookie-name">`sticky.`<br />`cookie.name`</a> | Name of the cookie used for the stickiness.<br />When sticky sessions are enabled, a `Set-Cookie` header is set on the initial response to let the client know which server handles the first response.<br />On subsequent requests, to keep the session alive with the same server, the client should send the cookie with the value set.<br />If the server pecified in the cookie becomes unhealthy, the request will be forwarded to a new server (and the cookie will keep track of the new server).<br />Evaluated only if the kind is **Service**. | "" | No |
| <a id="opt-sticky-cookie-httpOnly" href="#opt-sticky-cookie-httpOnly" title="#opt-sticky-cookie-httpOnly">`sticky.`<br />`cookie.httpOnly`</a> | Allow the cookie can be accessed by client-side APIs, such as JavaScript.<br />Evaluated only if the kind is **Service**. | false | No |
| <a id="opt-sticky-cookie-secure" href="#opt-sticky-cookie-secure" title="#opt-sticky-cookie-secure">`sticky.`<br />`cookie.secure`</a> | Allow the cookie can only be transmitted over an encrypted connection (i.e. HTTPS).<br />Evaluated only if the kind is **Service**. | false | No |
| <a id="opt-sticky-cookie-sameSite" href="#opt-sticky-cookie-sameSite" title="#opt-sticky-cookie-sameSite">`sticky.`<br />`cookie.sameSite`</a> | [SameSite](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite) policy<br />Allowed values:<br />-`none`<br />-`lax`<br />`strict`<br />Evaluated only if the kind is **Service**. | "" | No |
| <a id="opt-sticky-cookie-maxAge" href="#opt-sticky-cookie-maxAge" title="#opt-sticky-cookie-maxAge">`sticky.`<br />`cookie.maxAge`</a> | Number of seconds until the cookie expires.<br />Negative number, the cookie expires immediately.<br />0, the cookie never expires.<br />Evaluated only if the kind is **Service**. | 0 | No |
| <a id="opt-strategy" href="#opt-strategy" title="#opt-strategy">`strategy`</a> | Strategy defines the load balancing strategy between the servers.<br />Supported values are: wrr (Weighed round-robin), p2c (Power of two choices), hrw (Highest Random Weight), and leasttime (Least-Time).<br />Evaluated only if the kind is **Service**. | "RoundRobin" | No |
| <a id="opt-nativeLB" href="#opt-nativeLB" title="#opt-nativeLB">`nativeLB`</a> | Allow using the Kubernetes Service load balancing between the pods instead of the one provided by Traefik.<br /> Evaluated only if the kind is **Service**. | false | No |
| <a id="opt-nodePortLB" href="#opt-nodePortLB" title="#opt-nodePortLB">`nodePortLB`</a> | Use the nodePort IP address when the service type is NodePort.<br />It allows services to be reachable when Traefik runs externally from the Kubernetes cluster but within the same network of the nodes.<br />Evaluated only if the kind is **Service**. | false | No |
| Field | Description | Default | Required |
|:---------------------------------------------------------------------------------|:----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:---------------------------------------------------------------------|:---------|
| <a id="opt-kind" href="#opt-kind" title="#opt-kind">`kind`</a> | Kind of the service targeted.<br />Two values allowed:<br />- **Service**: Kubernetes Service<br /> **TraefikService**: Traefik Service.<br />More information [here](#externalname-service). | "Service" | No |
| <a id="opt-name" href="#opt-name" title="#opt-name">`name`</a> | Service name.<br />The character `@` is not authorized. <br />More information [here](#middleware). | | Yes |
| <a id="opt-namespace" href="#opt-namespace" title="#opt-namespace">`namespace`</a> | Service namespace.<br />Can be empty if the service belongs to the same namespace as the IngressRoute. <br />More information [here](#externalname-service). | | No |
| <a id="opt-port" href="#opt-port" title="#opt-port">`port`</a> | Service port (number or port name).<br />Evaluated only if the kind is **Service**. | | No |
| <a id="opt-responseForwarding-flushInterval" href="#opt-responseForwarding-flushInterval" title="#opt-responseForwarding-flushInterval">`responseForwarding.`<br />`flushInterval`</a> | Interval, in milliseconds, in between flushes to the client while copying the response body.<br />A negative value means to flush immediately after each write to the client.<br />This configuration is ignored when a response is a streaming response; for such responses, writes are flushed to the client immediately.<br />Evaluated only if the kind is **Service**. | 100ms | No |
| <a id="opt-scheme" href="#opt-scheme" title="#opt-scheme">`scheme`</a> | Scheme to use for the request to the upstream Kubernetes Service.<br />Evaluated only if the kind is **Service**. | "http"<br />"https" if `port` is 443 or contains the string *https*. | No |
| <a id="opt-serversTransport" href="#opt-serversTransport" title="#opt-serversTransport">`serversTransport`</a> | Name of ServersTransport resource to use to configure the transport between Traefik and your servers.<br />Evaluated only if the kind is **Service**. | "" | No |
| <a id="opt-passHostHeader" href="#opt-passHostHeader" title="#opt-passHostHeader">`passHostHeader`</a> | Forward client Host header to server.<br />Evaluated only if the kind is **Service**. | true | No |
| <a id="opt-healthCheck-scheme" href="#opt-healthCheck-scheme" title="#opt-healthCheck-scheme">`healthCheck.scheme`</a> | Server URL scheme for the health check endpoint.<br />Evaluated only if the kind is **Service**.<br />Only for [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) of type [ExternalName](#externalname-service). | "" | No |
| <a id="opt-healthCheck-mode" href="#opt-healthCheck-mode" title="#opt-healthCheck-mode">`healthCheck.mode`</a> | Health check mode.<br /> If defined to grpc, will use the gRPC health check protocol to probe the server.<br />Evaluated only if the kind is **Service**.<br />Only for [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) of type [ExternalName](#externalname-service). | "http" | No |
| <a id="opt-healthCheck-path" href="#opt-healthCheck-path" title="#opt-healthCheck-path">`healthCheck.path`</a> | Server URL path for the health check endpoint. <br />The configured path must be relative URL. <br />Evaluated only if the kind is **Service**.<br />Only for [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) of type [ExternalName](#externalname-service). | "" | No |
| <a id="opt-healthCheck-interval" href="#opt-healthCheck-interval" title="#opt-healthCheck-interval">`healthCheck.interval`</a> | Frequency of the health check calls for healthy targets.<br />Evaluated only if the kind is **Service**.<br />Only for [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) of type [ExternalName](#externalname-service). | "100ms" | No |
| <a id="opt-healthCheck-unhealthyInterval" href="#opt-healthCheck-unhealthyInterval" title="#opt-healthCheck-unhealthyInterval">`healthCheck.unhealthyInterval`</a> | Frequency of the health check calls for unhealthy targets.<br />When not defined, it defaults to the `interval` value.<br />Evaluated only if the kind is **Service**.<br />Only for [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) of type [ExternalName](#externalname-service). | "100ms" | No |
| <a id="opt-healthCheck-method" href="#opt-healthCheck-method" title="#opt-healthCheck-method">`healthCheck.method`</a> | HTTP method for the health check endpoint.<br />Evaluated only if the kind is **Service**.<br />Only for [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) of type [ExternalName](#externalname-service). | "GET" | No |
| <a id="opt-healthCheck-status" href="#opt-healthCheck-status" title="#opt-healthCheck-status">`healthCheck.status`</a> | Expected HTTP status code of the response to the health check request.<br />Only for [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) of type ExternalName.<br />If not set, expect a status between 200 and 399.<br />Evaluated only if the kind is **Service**. | | No |
| <a id="opt-healthCheck-port" href="#opt-healthCheck-port" title="#opt-healthCheck-port">`healthCheck.port`</a> | URL port for the health check endpoint.<br />Evaluated only if the kind is **Service**.<br />Only for [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) of type [ExternalName](#externalname-service). | | No |
| <a id="opt-healthCheck-timeout" href="#opt-healthCheck-timeout" title="#opt-healthCheck-timeout">`healthCheck.timeout`</a> | Maximum duration to wait before considering the server unhealthy.<br />Evaluated only if the kind is **Service**.<br />Only for [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) of type [ExternalName](#externalname-service). | "5s" | No |
| <a id="opt-healthCheck-hostname" href="#opt-healthCheck-hostname" title="#opt-healthCheck-hostname">`healthCheck.hostname`</a> | Value in the Host header of the health check request.<br />Evaluated only if the kind is **Service**.<br />Only for [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) of type [ExternalName](#externalname-service). | "" | No |
| <a id="opt-healthCheck-followRedirect" href="#opt-healthCheck-followRedirect" title="#opt-healthCheck-followRedirect">`healthCheck.`<br />`followRedirect`</a> | Follow the redirections during the healtchcheck.<br />Evaluated only if the kind is **Service**.<br />Only for [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) of type [ExternalName](#externalname-service). | true | No |
| <a id="opt-healthCheck-headers" href="#opt-healthCheck-headers" title="#opt-healthCheck-headers">`healthCheck.headers`</a> | Map of header to send to the health check endpoint<br />Evaluated only if the kind is **Service**.<br />Only for [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) of type [ExternalName](#externalname-service)). | | No |
| <a id="opt-sticky-cookie-name" href="#opt-sticky-cookie-name" title="#opt-sticky-cookie-name">`sticky.`<br />`cookie.name`</a> | Name of the cookie used for the stickiness.<br />When sticky sessions are enabled, a `Set-Cookie` header is set on the initial response to let the client know which server handles the first response.<br />On subsequent requests, to keep the session alive with the same server, the client should send the cookie with the value set.<br />If the server pecified in the cookie becomes unhealthy, the request will be forwarded to a new server (and the cookie will keep track of the new server).<br />Evaluated only if the kind is **Service**. | "" | No |
| <a id="opt-sticky-cookie-httpOnly" href="#opt-sticky-cookie-httpOnly" title="#opt-sticky-cookie-httpOnly">`sticky.`<br />`cookie.httpOnly`</a> | Allow the cookie can be accessed by client-side APIs, such as JavaScript.<br />Evaluated only if the kind is **Service**. | false | No |
| <a id="opt-sticky-cookie-secure" href="#opt-sticky-cookie-secure" title="#opt-sticky-cookie-secure">`sticky.`<br />`cookie.secure`</a> | Allow the cookie can only be transmitted over an encrypted connection (i.e. HTTPS).<br />Evaluated only if the kind is **Service**. | false | No |
| <a id="opt-sticky-cookie-sameSite" href="#opt-sticky-cookie-sameSite" title="#opt-sticky-cookie-sameSite">`sticky.`<br />`cookie.sameSite`</a> | [SameSite](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite) policy<br />Allowed values:<br />-`none`<br />-`lax`<br />`strict`<br />Evaluated only if the kind is **Service**. | "" | No |
| <a id="opt-sticky-cookie-maxAge" href="#opt-sticky-cookie-maxAge" title="#opt-sticky-cookie-maxAge">`sticky.`<br />`cookie.maxAge`</a> | Number of seconds until the cookie expires.<br />Negative number, the cookie expires immediately.<br />0, the cookie never expires.<br />Evaluated only if the kind is **Service**. | 0 | No |
| <a id="opt-strategy" href="#opt-strategy" title="#opt-strategy">`strategy`</a> | Strategy defines the load balancing strategy between the servers.<br />Supported values are: wrr (Weighed round-robin), p2c (Power of two choices), hrw (Highest Random Weight), and leasttime (Least-Time).<br />Evaluated only if the kind is **Service**. | "RoundRobin" | No |
| <a id="opt-nativeLB" href="#opt-nativeLB" title="#opt-nativeLB">`nativeLB`</a> | Allow using the Kubernetes Service load balancing between the pods instead of the one provided by Traefik.<br /> Evaluated only if the kind is **Service**. | false | No |
| <a id="opt-nodePortLB" href="#opt-nodePortLB" title="#opt-nodePortLB">`nodePortLB`</a> | Use the nodePort IP address when the service type is NodePort.<br />It allows services to be reachable when Traefik runs externally from the Kubernetes cluster but within the same network of the nodes.<br />Evaluated only if the kind is **Service**. | false | No |
### ExternalName Service

View File

@ -439,7 +439,7 @@ To propagate status changes (e.g. all servers of this service are down) upwards,
Below are the available options for the health check mechanism:
- `path` (required), defines the server URL path for the health check endpoint .
- `path` (required), defines the server URL path for the health check endpoint (must be a relative URL).
- `scheme` (optional), replaces the server URL `scheme` for the health check endpoint.
- `mode` (default: http), if defined to `grpc`, will use the gRPC health check protocol to probe the server.
- `hostname` (optional), sets the value of `hostname` in the `Host` header of the health check request.

View File

@ -39,6 +39,7 @@ extra_javascript:
extra_css:
- assets/css/menu-icons.css
- assets/css/code-copy.css
- assets/css/content-width.css
plugins:
- search

View File

@ -1,7 +1,6 @@
package integration
import (
"encoding/json"
"io"
"net/http"
"testing"
@ -75,16 +74,8 @@ func (s *DockerSuite) TestDefaultDockerContainers() {
require.NoError(s.T(), err)
req.Host = "simple.docker.localhost"
resp, err := try.ResponseUntilStatusCode(req, 3*time.Second, http.StatusOK)
_, err = try.ResponseUntilStatusCode(req, 3*time.Second, http.StatusOK)
require.NoError(s.T(), err)
body, err := io.ReadAll(resp.Body)
require.NoError(s.T(), err)
var version map[string]any
assert.NoError(s.T(), json.Unmarshal(body, &version))
assert.Equal(s.T(), "swarm/1.0.0", version["Version"])
}
func (s *DockerSuite) TestDockerContainersWithTCPLabels() {
@ -139,16 +130,8 @@ func (s *DockerSuite) TestDockerContainersWithLabels() {
require.NoError(s.T(), err)
req.Host = "my.super.host"
resp, err := try.ResponseUntilStatusCode(req, 3*time.Second, http.StatusOK)
_, err = try.ResponseUntilStatusCode(req, 3*time.Second, http.StatusOK)
require.NoError(s.T(), err)
body, err := io.ReadAll(resp.Body)
require.NoError(s.T(), err)
var version map[string]any
assert.NoError(s.T(), json.Unmarshal(body, &version))
assert.Equal(s.T(), "swarm/1.0.0", version["Version"])
}
func (s *DockerSuite) TestDockerContainersWithOneMissingLabels() {
@ -197,17 +180,9 @@ func (s *DockerSuite) TestRestartDockerContainers() {
req.Host = "my.super.host"
// TODO Need to wait than 500 milliseconds more (for swarm or traefik to boot up ?)
resp, err := try.ResponseUntilStatusCode(req, 1500*time.Millisecond, http.StatusOK)
_, err = try.ResponseUntilStatusCode(req, 1500*time.Millisecond, http.StatusOK)
require.NoError(s.T(), err)
body, err := io.ReadAll(resp.Body)
require.NoError(s.T(), err)
var version map[string]any
assert.NoError(s.T(), json.Unmarshal(body, &version))
assert.Equal(s.T(), "swarm/1.0.0", version["Version"])
err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 60*time.Second, try.BodyContains("powpow"))
require.NoError(s.T(), err)

View File

@ -1,7 +1,6 @@
services:
simple:
image: swarm:1.0.0
command: [ "manage", "token://blablabla" ]
image: traefik/whoami
withtcplabels:
image: traefik/whoamitcp
@ -12,26 +11,24 @@ services:
traefik.tcp.Services.Super.Loadbalancer.server.port: 8080
withlabels1:
image: swarm:1.0.0
command: [ "manage", "token://blabla" ]
image: traefik/whoami
labels:
traefik.http.Routers.Super.Rule: Host(`my.super.host`)
withlabels2:
image: swarm:1.0.0
command: [ "manage", "token://blablabla" ]
image: traefik/whoami
labels:
traefik.http.Routers.SuperHost.Rule: Host(`my-super.host`)
withonelabelmissing:
image: swarm:1.0.0
command: [ "manage", "token://blabla" ]
image: traefik/whoami
labels:
traefik.random.value: my.super.host
powpow:
image: swarm:1.0.0
command: [ "manage", "token://blabla" ]
image: traefik/whoami
command:
- --port=2375
labels:
traefik.http.Routers.Super.Rule: Host(`my.super.host`)
traefik.http.Services.powpow.LoadBalancer.server.Port: 2375

View File

@ -4,6 +4,7 @@ import (
"fmt"
"io/fs"
"net/http"
"net/url"
"strings"
"text/template"
@ -80,7 +81,9 @@ func Append(router *mux.Router, basePath string, customAssets fs.FS) error {
Path(basePath).
HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
xfPrefix := req.Header.Get("X-Forwarded-Prefix")
if strings.Contains(xfPrefix, "//") {
// Validates that the X-Forwarded-Prefix value contains a relative URL.
if u, err := url.Parse(xfPrefix); err != nil || u.Host != "" || u.Scheme != "" {
log.Error().Msgf("X-Forwarded-Prefix contains an invalid value: %s, defaulting to empty prefix", xfPrefix)
xfPrefix = ""
}

View File

@ -246,6 +246,15 @@ func (shc *ServiceHealthChecker) checkHealthHTTP(ctx context.Context, target *ur
}
func (shc *ServiceHealthChecker) newRequest(ctx context.Context, target *url.URL) (*http.Request, error) {
pathURL, err := url.Parse(shc.config.Path)
if err != nil {
return nil, fmt.Errorf("parsing health check path: %w", err)
}
if pathURL.Host != "" || pathURL.Scheme != "" {
return nil, fmt.Errorf("health check path must be a relative URL, got: %q", shc.config.Path)
}
u, err := target.Parse(shc.config.Path)
if err != nil {
return nil, err

View File

@ -147,6 +147,14 @@ func TestServiceHealthChecker_newRequest(t *testing.T) {
expHostname: "backend1:80",
expMethod: http.MethodGet,
},
{
desc: "path is an ablsolute URL",
targetURL: "http://backend1:80",
config: dynamic.ServerHealthCheck{
Path: "http://backend2/health?powpow=do",
},
expError: true,
},
{
desc: "path with param",
targetURL: "http://backend1:80",

View File

@ -44,6 +44,8 @@ var hopHeaders = []string{
forward.Upgrade,
}
var userAgentHeader = http.CanonicalHeaderKey("User-Agent")
type forwardAuth struct {
address string
authResponseHeaders []string
@ -369,6 +371,12 @@ func writeHeader(req, forwardReq *http.Request, trustForwardHeader bool, allowed
RemoveConnectionHeaders(forwardReq)
utils.RemoveHeaders(forwardReq.Header, hopHeaders...)
if _, ok := req.Header[userAgentHeader]; !ok {
// If the incoming request doesn't have a User-Agent header set,
// don't send the default Go HTTP client User-Agent for the forwarded request.
forwardReq.Header.Set(userAgentHeader, "")
}
forwardReq.Header = filterForwardRequestHeaders(forwardReq.Header, allowedHeaders)
if clientIP, _, err := net.SplitHostPort(req.RemoteAddr); err == nil {

View File

@ -536,8 +536,7 @@ func Test_writeHeader(t *testing.T) {
trustForwardHeader: false,
emptyHost: true,
expectedHeaders: map[string]string{
"Accept": "application/json",
"X-Forwarded-Host": "",
"Accept": "application/json",
},
},
{
@ -610,6 +609,7 @@ func Test_writeHeader(t *testing.T) {
"X-Forwarded-Method": "GET",
forward.ProxyAuthenticate: "ProxyAuthenticate",
forward.ProxyAuthorization: "ProxyAuthorization",
"User-Agent": "",
},
checkForUnexpectedHeaders: true,
},
@ -652,6 +652,65 @@ func Test_writeHeader(t *testing.T) {
},
checkForUnexpectedHeaders: true,
},
{
name: "set empty User-Agent header if header is allowed but missing",
headers: map[string]string{
"X-CustomHeader": "CustomHeader",
"Accept": "application/json",
},
authRequestHeaders: []string{
"X-CustomHeader",
"Accept",
"User-Agent",
},
expectedHeaders: map[string]string{
"X-CustomHeader": "CustomHeader",
"Accept": "application/json",
"X-Forwarded-Proto": "http",
"X-Forwarded-Host": "foo.bar",
"X-Forwarded-Uri": "/path?q=1",
"X-Forwarded-Method": "GET",
"User-Agent": "",
},
checkForUnexpectedHeaders: true,
},
{
name: "ignore User-Agent header if header is not allowed and missing",
headers: map[string]string{
"X-CustomHeader": "CustomHeader",
"Accept": "application/json",
},
authRequestHeaders: []string{
"X-CustomHeader",
"Accept",
},
expectedHeaders: map[string]string{
"X-CustomHeader": "CustomHeader",
"Accept": "application/json",
"X-Forwarded-Proto": "http",
"X-Forwarded-Host": "foo.bar",
"X-Forwarded-Uri": "/path?q=1",
"X-Forwarded-Method": "GET",
},
checkForUnexpectedHeaders: true,
},
{
name: "set empty User-Agent header if header is missing",
headers: map[string]string{
"X-CustomHeader": "CustomHeader",
"Accept": "application/json",
},
expectedHeaders: map[string]string{
"X-CustomHeader": "CustomHeader",
"Accept": "application/json",
"X-Forwarded-Proto": "http",
"X-Forwarded-Host": "foo.bar",
"X-Forwarded-Uri": "/path?q=1",
"X-Forwarded-Method": "GET",
"User-Agent": "",
},
checkForUnexpectedHeaders: true,
},
}
for _, test := range testCases {
@ -673,9 +732,14 @@ func Test_writeHeader(t *testing.T) {
expectedHeaders := test.expectedHeaders
for key, value := range expectedHeaders {
_, headerExists := actualHeaders[http.CanonicalHeaderKey(key)]
assert.True(t, headerExists, "Expected header %s not found", key)
assert.Equal(t, value, actualHeaders.Get(key))
actualHeaders.Del(key)
}
if test.checkForUnexpectedHeaders {
for key := range actualHeaders {
assert.Fail(t, "Unexpected header found", key)

View File

@ -7,6 +7,7 @@ import (
"io"
"net"
"sync"
"time"
"github.com/rs/zerolog/log"
tcpmuxer "github.com/traefik/traefik/v3/pkg/muxer/tcp"
@ -46,7 +47,7 @@ func isPostgres(br *bufio.Reader) (bool, error) {
func (r *Router) servePostgres(conn tcp.WriteCloser) {
_, err := conn.Write(PostgresStartTLSReply)
if err != nil {
conn.Close()
_ = conn.Close()
return
}
@ -55,32 +56,39 @@ func (r *Router) servePostgres(conn tcp.WriteCloser) {
b := make([]byte, len(PostgresStartTLSMsg))
_, err = br.Read(b)
if err != nil {
conn.Close()
_ = conn.Close()
return
}
hello, err := clientHelloInfo(br)
if err != nil {
conn.Close()
_ = conn.Close()
return
}
if !hello.isTLS {
conn.Close()
_ = conn.Close()
return
}
// The deadline was there to prevent hanging connections while waiting for the client,
// now that the STARTTLS message and Client Hello have been read,
// we can remove it and leave its handling to the TCP reverse proxy eventually.
if err := conn.SetDeadline(time.Time{}); err != nil {
log.Error().Err(err).Msg("Error while setting deadline")
}
connData, err := tcpmuxer.NewConnData(hello.serverName, conn, hello.protos)
if err != nil {
log.Error().Err(err).Msg("Error while reading TCP connection data")
conn.Close()
_ = conn.Close()
return
}
// Contains also TCP TLS passthrough routes.
handlerTCPTLS, _ := r.muxerTCPTLS.Match(connData)
if handlerTCPTLS == nil {
conn.Close()
_ = conn.Close()
return
}

View File

@ -18,7 +18,15 @@ import (
"github.com/traefik/traefik/v3/pkg/tcp"
)
const defaultBufSize = 4096
const (
defaultBufSize = 4096
// Per RFC 8446 Section 5.1, the maximum TLS record payload length is 2^14 (16384) bytes.
// A ClientHello is always a plaintext record, so any value exceeding this limit is invalid
// and likely indicates an attack attempting to force oversized per-connection buffer allocations.
// However, in practice the go server handshake can read up to 16384 + 2048 bytes,
// so we need to allow for some extra bytes to avoid rejecting valid handshakes.
maxTLSRecordLen = 16384 + 2048
)
// Router is a TCP router.
type Router struct {
@ -126,11 +134,6 @@ func (r *Router) ServeTCP(conn tcp.WriteCloser) {
}
if postgres {
// Remove read/write deadline and delegate this to underlying TCP server.
if err := conn.SetDeadline(time.Time{}); err != nil {
log.Error().Err(err).Msg("Error while setting deadline")
}
r.servePostgres(r.GetConn(conn, getPeeked(br)))
return
}
@ -141,7 +144,9 @@ func (r *Router) ServeTCP(conn tcp.WriteCloser) {
return
}
// Remove read/write deadline and delegate this to underlying TCP server (for now only handled by HTTP Server)
// The deadline was set to avoid blocking on the initial read of the ClientHello,
// but now that we have it, we can remove it,
// and delegate this to underlying TCP server (for now only handled by HTTP Server).
if err := conn.SetDeadline(time.Time{}); err != nil {
log.Error().Err(err).Msg("Error while setting deadline")
}
@ -409,6 +414,14 @@ func clientHelloInfo(br *bufio.Reader) (*clientHello, error) {
recLen := int(hdr[3])<<8 | int(hdr[4]) // ignoring version in hdr[1:3]
if recLen > maxTLSRecordLen {
log.Debug().Msgf("Error while peeking client hello bytes, oversized record: %d", recLen)
return &clientHello{
isTLS: true,
peeked: getPeeked(br),
}, nil
}
if recordHeaderLen+recLen > defaultBufSize {
br = bufio.NewReaderSize(br, recordHeaderLen+recLen)
}

View File

@ -1,6 +1,7 @@
package tcp
import (
"bufio"
"bytes"
"crypto/tls"
"errors"
@ -648,6 +649,7 @@ func Test_Routing(t *testing.T) {
_ = serverHTTPS.Serve(httpsForwarder)
}()
// The HTTPS forwarder will be added as tcp.TLSHandler (to handle TLS).
router.SetHTTPSForwarder(httpsForwarder)
stoppedTCP := make(chan struct{})
@ -1082,6 +1084,118 @@ func checkHTTPSTLS12(addr string, timeout time.Duration) error {
return checkHTTPS(addr, timeout, tls.VersionTLS12)
}
// Test_clientHelloInfo_oversizedRecordLength verifies that clientHelloInfo
// does not block or allocate excessive memory when a client sends a TLS
// record header with a maliciously large record length (up to 0xFFFF).
//
// Without the fix, clientHelloInfo allocates a ~65KB bufio.Reader and blocks
// on Peek(65540), waiting for bytes that never arrive (until readTimeout).
// With the fix, records exceeding the TLS maximum plaintext size (16384)
// are rejected immediately.
func Test_clientHelloInfo_oversizedRecordLength(t *testing.T) {
testCases := []struct {
desc string
recLen uint16
}{
{
desc: "max uint16 record length (0xFFFF)",
recLen: 0xFFFF,
},
{
desc: "just above TLS maximum (18433)",
recLen: 18433,
},
}
for _, test := range testCases {
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
serverConn, clientConn := net.Pipe()
defer serverConn.Close()
defer clientConn.Close()
type result struct {
hello *clientHello
err error
}
resultCh := make(chan result, 1)
go func() {
br := bufio.NewReader(serverConn)
hello, err := clientHelloInfo(br)
resultCh <- result{hello, err}
}()
// Send a TLS record header with an oversized record length.
// Only the 5-byte header is sent; the client then stalls.
hdr := []byte{
0x16, // Content Type: Handshake
0x03, 0x03, // Version: TLS 1.2
byte(test.recLen >> 8), // Length high byte
byte(test.recLen & 0xFF), // Length low byte
}
_, err := clientConn.Write(hdr)
require.NoError(t, err)
// Without the fix, clientHelloInfo blocks on Peek(recLen+5)
// since only 5 bytes are available. The test would time out.
// With the fix, it returns immediately.
select {
case r := <-resultCh:
require.NoError(t, r.err)
require.NotNil(t, r.hello)
assert.True(t, r.hello.isTLS)
case <-time.After(5 * time.Second):
t.Fatal("clientHelloInfo blocked on oversized TLS record length — recLen is not capped")
}
})
}
}
// Test_clientHelloInfo_validRecordLength verifies that clientHelloInfo
// still works correctly with legitimate TLS record sizes.
func Test_clientHelloInfo_validRecordLength(t *testing.T) {
serverConn, clientConn := net.Pipe()
defer serverConn.Close()
defer clientConn.Close()
type result struct {
hello *clientHello
err error
}
resultCh := make(chan result, 1)
go func() {
br := bufio.NewReader(serverConn)
hello, err := clientHelloInfo(br)
resultCh <- result{hello, err}
}()
// Build a TLS record header with a small (valid) record length.
recLen := 100
hdr := []byte{
0x16, // Content Type: Handshake
0x03, 0x03, // Version: TLS 1.2
byte(recLen >> 8), // Length high byte
byte(recLen & 0xFF), // Length low byte
}
payload := make([]byte, recLen)
_, err := clientConn.Write(append(hdr, payload...))
require.NoError(t, err)
clientConn.Close()
select {
case r := <-resultCh:
require.NoError(t, r.err)
require.NotNil(t, r.hello)
assert.True(t, r.hello.isTLS)
case <-time.After(5 * time.Second):
t.Fatal("clientHelloInfo blocked on valid TLS record")
}
}
func TestPostgres(t *testing.T) {
router, err := NewRouter()
require.NoError(t, err)

View File

@ -4,11 +4,11 @@ RepositoryName = "traefik"
OutputType = "file"
FileName = "traefik_changelog.md"
# example new bugfix v3.6.7
# example new bugfix v3.6.8
CurrentRef = "v3.6"
PreviousRef = "v3.6.6"
PreviousRef = "v3.6.7"
BaseBranch = "v3.6"
FutureCurrentRefName = "v3.6.7"
FutureCurrentRefName = "v3.6.8"
ThresholdPreviousRef = 10000
ThresholdCurrentRef = 10000