diff --git a/CHANGELOG.md b/CHANGELOG.md index c49d27439..77ccc3844 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,63 @@ +## [v3.1.4](https://github.com/traefik/traefik/tree/v3.1.4) (2024-09-19) +[All Commits](https://github.com/traefik/traefik/compare/v3.1.3...v3.1.4) + +**Bug fixes:** +- **[metrics]** Guess Datadog socket type when prefix is unix ([#11102](https://github.com/traefik/traefik/pull/11102) by [kevinpollet](https://github.com/kevinpollet)) + +**Documentation:** +- Mention v3 in readme ([#11082](https://github.com/traefik/traefik/pull/11082) by [kabaluyot](https://github.com/kabaluyot)) + +**Misc:** +- Merge branch v2.11 into v3.1 ([#11107](https://github.com/traefik/traefik/pull/11107) by [rtribotte](https://github.com/rtribotte)) + +## [v2.11.10](https://github.com/traefik/traefik/tree/v2.11.10) (2024-09-19) +[All Commits](https://github.com/traefik/traefik/compare/v2.11.9...v2.11.10) + +**Bug fixes:** +- **[http3]** Bump github.com/quic-go/quic-go to v0.47.0 ([#11104](https://github.com/traefik/traefik/pull/11104) by [rtribotte](https://github.com/rtribotte)) +- **[server]** Check if ACME certificate resolver is not nil ([#11103](https://github.com/traefik/traefik/pull/11103) by [kevinpollet](https://github.com/kevinpollet)) + +## [v3.1.3](https://github.com/traefik/traefik/tree/v3.1.3) (2024-09-16) +[All Commits](https://github.com/traefik/traefik/compare/v3.1.2...v3.1.3) + +**Bug fixes:** +- **[k8s/ingress,rules,k8s]** Allow configuring rule syntax with Kubernetes Ingress annotation ([#10985](https://github.com/traefik/traefik/pull/10985) by [rtribotte](https://github.com/rtribotte)) +- **[k8s/ingress]** Re-allow empty configuration for Kubernetes Ingress provider ([#11008](https://github.com/traefik/traefik/pull/11008) by [rtribotte](https://github.com/rtribotte)) +- **[middleware,metrics]** Wrap capture for services used by pieces of middleware ([#11058](https://github.com/traefik/traefik/pull/11058) by [rtribotte](https://github.com/rtribotte)) +- **[plugins]** Removes goexport dependency and adds _initialize ([#11088](https://github.com/traefik/traefik/pull/11088) by [juliens](https://github.com/juliens)) + +**Documentation:** +- **[k8s/crd,k8s]** Remove mentions about APIVersion traefik.io/v1 ([#11020](https://github.com/traefik/traefik/pull/11020) by [rtribotte](https://github.com/rtribotte)) +- **[k8s]** Update quick-start-with-kubernetes.md to include required permissions ([#11010](https://github.com/traefik/traefik/pull/11010) by [eastmane](https://github.com/eastmane)) +- **[metrics]** Mention missing metrics removal in the migration guide ([#10982](https://github.com/traefik/traefik/pull/10982) by [rtribotte](https://github.com/rtribotte)) +- **[tracing]** Fix tracing documentation ([#11067](https://github.com/traefik/traefik/pull/11067) by [mmatur](https://github.com/mmatur)) +- **[tracing]** OTLP doc + potential panic ([#11052](https://github.com/traefik/traefik/pull/11052) by [mmatur](https://github.com/mmatur)) + +**Misc:** +- Merge v2.11 into v3.1 ([#11092](https://github.com/traefik/traefik/pull/11092) by [kevinpollet](https://github.com/kevinpollet)) +- Merge v2.11 into v3.1 ([#11065](https://github.com/traefik/traefik/pull/11065) by [mmatur](https://github.com/mmatur)) +- Merge v2.11 into v3.1 ([#11044](https://github.com/traefik/traefik/pull/11044) by [rtribotte](https://github.com/rtribotte)) + +## [v2.11.9](https://github.com/traefik/traefik/tree/v2.11.9) (2024-09-16) +[All Commits](https://github.com/traefik/traefik/compare/v2.11.8...v2.11.9) + +**Bug fixes:** +- **[acme]** Update go-acme/lego to v4.18.0 ([#11060](https://github.com/traefik/traefik/pull/11060) by [ldez](https://github.com/ldez)) +- **[acme]** Allow handling ACME challenges with custom routers ([#10981](https://github.com/traefik/traefik/pull/10981) by [rtribotte](https://github.com/rtribotte)) +- **[logs,middleware]** Make the keys of the accessLog.fields.names map case-insensitive ([#11040](https://github.com/traefik/traefik/pull/11040) by [SpecLad](https://github.com/SpecLad)) +- **[logs,middleware]** Ensure proper logs for aborted streaming responses ([#10819](https://github.com/traefik/traefik/pull/10819) by [hood](https://github.com/hood)) +- **[middleware,server]** Cleanup Connection headers before passing the middleware chain ([#11077](https://github.com/traefik/traefik/pull/11077) by [kevinpollet](https://github.com/kevinpollet)) +- **[plugins]** Upgrade paerser to v0.2.1 ([#11048](https://github.com/traefik/traefik/pull/11048) by [mmatur](https://github.com/mmatur)) +- **[server,tcp]** Prevent error logging when TCP WRR pool is empty ([#10989](https://github.com/traefik/traefik/pull/10989) by [kevinpollet](https://github.com/kevinpollet)) +- **[webui]** Upgrade webui dependencies ([#11031](https://github.com/traefik/traefik/pull/11031) by [mloiseleur](https://github.com/mloiseleur)) + +**Documentation:** +- **[acme]** Fix typo in multiple DNS challenge provider warning ([#11001](https://github.com/traefik/traefik/pull/11001) by [tired-engineer](https://github.com/tired-engineer)) +- **[k8s]** Update k8s quickstart permissions ([#11049](https://github.com/traefik/traefik/pull/11049) by [mmatur](https://github.com/mmatur)) +- **[metrics]** Remove documentation for unimplemented service retries metric ([#10983](https://github.com/traefik/traefik/pull/10983) by [rtribotte](https://github.com/rtribotte)) +- **[middleware]** Unify tab titles ([#11072](https://github.com/traefik/traefik/pull/11072) by [jsoref](https://github.com/jsoref)) +- Give valid examples for exposing dashboard with default Helm values ([#11015](https://github.com/traefik/traefik/pull/11015) by [holysoles](https://github.com/holysoles)) + ## [v3.1.2](https://github.com/traefik/traefik/tree/v3.1.2) (2024-08-06) [All Commits](https://github.com/traefik/traefik/compare/v3.1.1...v3.1.2) diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index e917aa340..588d2aa61 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -47,7 +47,7 @@ Further details of specific enforcement policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. -When an inapropriate behavior is reported, maintainers will discuss on the Maintainer's Discord before marking the message as "abuse". +When an inappropriate behavior is reported, maintainers will discuss on the Maintainer's Discord before marking the message as "abuse". This conversation beforehand avoids one-sided decisions. The first message will be edited and marked as abuse. diff --git a/README.md b/README.md index b3a2cb5ec..6a570e934 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,8 @@ Pointing Traefik at your orchestrator should be the _only_ configuration step yo --- -:warning: Please be aware that the old configurations for Traefik v1.x are NOT compatible with the v2.x config as of now. If you're running v2, please ensure you are using a [v2 configuration](https://doc.traefik.io/traefik/). +:warning: When migrating to a new major version of Traefik, please refer to the [migration guide](https://doc.traefik.io/traefik/migration/v2-to-v3/) to ensure a smooth transition and to be aware of any breaking changes. + ## Overview @@ -87,7 +88,7 @@ You can access the simple HTML frontend of Traefik. ## Documentation -You can find the complete documentation of Traefik v2 at [https://doc.traefik.io/traefik/](https://doc.traefik.io/traefik/). +You can find the complete documentation of Traefik v3 at [https://doc.traefik.io/traefik/](https://doc.traefik.io/traefik/). A collection of contributions around Traefik can be found at [https://awesome.traefik.io](https://awesome.traefik.io). diff --git a/cmd/traefik/traefik.go b/cmd/traefik/traefik.go index 267514b16..6b41ef2e2 100644 --- a/cmd/traefik/traefik.go +++ b/cmd/traefik/traefik.go @@ -364,7 +364,7 @@ func setupServer(staticConfiguration *static.Configuration) (*server.Server, err if _, ok := resolverNames[rt.TLS.CertResolver]; !ok { log.Error().Err(err).Str(logs.RouterName, rtName).Str("certificateResolver", rt.TLS.CertResolver). - Msg("Router uses a non-existent certificate resolver") + Msg("Router uses a nonexistent certificate resolver") } } }) diff --git a/docs/content/https/acme.md b/docs/content/https/acme.md index daaaa2100..955cc0271 100644 --- a/docs/content/https/acme.md +++ b/docs/content/https/acme.md @@ -11,7 +11,7 @@ Automatic HTTPS You can configure Traefik to use an ACME provider (like Let's Encrypt) for automatic certificate generation. !!! warning "Let's Encrypt and Rate Limiting" - Note that Let's Encrypt API has [rate limiting](https://letsencrypt.org/docs/rate-limits). These last up to **one week**, and can not be overridden. + Note that Let's Encrypt API has [rate limiting](https://letsencrypt.org/docs/rate-limits). These last up to **one week**, and cannot be overridden. When running Traefik in a container this file should be persisted across restarts. If Traefik requests new certificates each time it starts up, a crash-looping container can quickly reach Let's Encrypt's ratelimits. @@ -298,7 +298,7 @@ Use the `DNS-01` challenge to generate and renew ACME certificates by provisioni Multiple DNS challenge provider are not supported with Traefik, but you can use `CNAME` to handle that. For example, if you have `example.org` (account foo) and `example.com` (account bar) you can create a CNAME on `example.org` called `_acme-challenge.example.org` pointing to `challenge.example.com`. - This way, you can obtain certificates for `example.com` with the `foo` account. + This way, you can obtain certificates for `example.org` with the `bar` account. !!! important A `provider` is mandatory. diff --git a/docs/content/middlewares/http/inflightreq.md b/docs/content/middlewares/http/inflightreq.md index e7b7ef695..16809ac80 100644 --- a/docs/content/middlewares/http/inflightreq.md +++ b/docs/content/middlewares/http/inflightreq.md @@ -278,7 +278,7 @@ spec: requestHost: true ``` -```yaml tab="Cosul Catalog" +```yaml tab="Consul Catalog" - "traefik.http.middlewares.test-inflightreq.inflightreq.sourcecriterion.requesthost=true" ``` diff --git a/docs/content/middlewares/http/overview.md b/docs/content/middlewares/http/overview.md index 96ba29e74..b6a1eb1b8 100644 --- a/docs/content/middlewares/http/overview.md +++ b/docs/content/middlewares/http/overview.md @@ -24,7 +24,7 @@ whoami: - "traefik.http.routers.router1.middlewares=foo-add-prefix@docker" ``` -```yaml tab="Kubernetes IngressRoute" +```yaml tab="IngressRoute" # As a Kubernetes Traefik IngressRoute --- apiVersion: traefik.io/v1alpha1 diff --git a/docs/content/middlewares/overview.md b/docs/content/middlewares/overview.md index ca9ae3e6b..a6d7f46be 100644 --- a/docs/content/middlewares/overview.md +++ b/docs/content/middlewares/overview.md @@ -35,7 +35,7 @@ whoami: - "traefik.http.routers.router1.middlewares=foo-add-prefix@docker" ``` -```yaml tab="Kubernetes IngressRoute" +```yaml tab="IngressRoute" --- apiVersion: traefik.io/v1alpha1 kind: Middleware diff --git a/docs/content/middlewares/tcp/overview.md b/docs/content/middlewares/tcp/overview.md index 4b461d3ba..288f8d827 100644 --- a/docs/content/middlewares/tcp/overview.md +++ b/docs/content/middlewares/tcp/overview.md @@ -24,7 +24,7 @@ whoami: - "traefik.tcp.routers.router1.middlewares=foo-ip-allowlist@docker" ``` -```yaml tab="Kubernetes IngressRoute" +```yaml tab="IngressRoute" # As a Kubernetes Traefik IngressRoute --- apiVersion: traefik.io/v1alpha1 diff --git a/docs/content/migration/v1-to-v2.md b/docs/content/migration/v1-to-v2.md index 709c53be5..47d89b250 100644 --- a/docs/content/migration/v1-to-v2.md +++ b/docs/content/migration/v1-to-v2.md @@ -44,7 +44,7 @@ Then any router can refer to an instance of the wanted middleware. - "traefik.frontend.auth.basic.users=test:$$apr1$$H6uskkkW$$IgXLP6ewTrSuBkTrqE8wj/,test2:$$apr1$$d9hr9HBB$$4HxwgUir3HP4EsggP/QNo0" ``` - ```yaml tab="K8s Ingress" + ```yaml tab="Ingress" apiVersion: networking.k8s.io/v1beta1 kind: Ingress metadata: @@ -107,7 +107,7 @@ Then any router can refer to an instance of the wanted middleware. - "traefik.http.middlewares.auth.basicauth.users=test:$$apr1$$H6uskkkW$$IgXLP6ewTrSuBkTrqE8wj/,test2:$$apr1$$d9hr9HBB$$4HxwgUir3HP4EsggP/QNo0" ``` - ```yaml tab="K8s IngressRoute" + ```yaml tab="IngressRoute" # The definitions below require the definitions for the Middleware and IngressRoute kinds. # https://doc.traefik.io/traefik/reference/dynamic-configuration/kubernetes-crd/#definitions apiVersion: traefik.io/v1alpha1 @@ -278,7 +278,7 @@ Then, a [router's TLS field](../routing/routers/index.md#tls) can refer to one o ] ``` - ```yaml tab="K8s IngressRoute" + ```yaml tab="IngressRoute" # The definitions below require the definitions for the TLSOption and IngressRoute kinds. # https://doc.traefik.io/traefik/reference/dynamic-configuration/kubernetes-crd/#definitions apiVersion: traefik.io/v1alpha1 @@ -442,7 +442,7 @@ To apply a redirection: traefik.http.middlewares.https_redirect.redirectscheme.permanent: true ``` - ```yaml tab="K8s IngressRoute" + ```yaml tab="IngressRoute" apiVersion: traefik.io/v1alpha1 kind: IngressRoute metadata: @@ -561,7 +561,7 @@ with the path `/admin` stripped, e.g. to `http://:/`. In this case, yo - "traefik.frontend.rule=Host:example.org;PathPrefixStrip:/admin" ``` - ```yaml tab="Kubernetes Ingress" + ```yaml tab="Ingress" apiVersion: networking.k8s.io/v1beta1 kind: Ingress metadata: @@ -595,7 +595,7 @@ with the path `/admin` stripped, e.g. to `http://:/`. In this case, yo - "traefik.http.middlewares.admin-stripprefix.stripprefix.prefixes=/admin" ``` - ```yaml tab="Kubernetes IngressRoute" + ```yaml tab="IngressRoute" --- apiVersion: traefik.io/v1alpha1 kind: IngressRoute diff --git a/docs/content/migration/v2.md b/docs/content/migration/v2.md index 0e56a8fee..3876a1668 100644 --- a/docs/content/migration/v2.md +++ b/docs/content/migration/v2.md @@ -432,7 +432,7 @@ For more advanced use cases, you can use either the [RedirectScheme middleware]( Following up on the deprecation started [previously](#x509-commonname-deprecation), as the `x509ignoreCN=0` value for the `GODEBUG` is [deprecated in Go 1.17](https://tip.golang.org/doc/go1.17#crypto/x509), -the legacy behavior related to the CommonName field can not be enabled at all anymore. +the legacy behavior related to the CommonName field cannot be enabled at all anymore. ## v2.5.3 to v2.5.4 diff --git a/docs/content/observability/metrics/datadog.md b/docs/content/observability/metrics/datadog.md index 134b726df..61c64f4c9 100644 --- a/docs/content/observability/metrics/datadog.md +++ b/docs/content/observability/metrics/datadog.md @@ -27,7 +27,9 @@ _Required, Default="127.0.0.1:8125"_ Address instructs exporter to send metrics to datadog-agent at this address. -This address can be a Unix Domain Socket (UDS) address with the following form: `unix:///path/to/datadog.socket`. +This address can be a Unix Domain Socket (UDS) in the following format: `unix:///path/to/datadog.socket`. +When the prefix is set to `unix`, the socket type will be automatically determined. +To explicitly define the socket type and avoid automatic detection, you can use the prefixes `unixgram` for `SOCK_DGRAM` (datagram sockets) and `unixstream` for `SOCK_STREAM` (stream sockets), respectively. ```yaml tab="File (YAML)" metrics: diff --git a/docs/content/observability/tracing/overview.md b/docs/content/observability/tracing/overview.md index b69aee46b..73840f89d 100644 --- a/docs/content/observability/tracing/overview.md +++ b/docs/content/observability/tracing/overview.md @@ -85,7 +85,7 @@ tracing: ```toml tab="File (TOML)" [tracing] - sampleRate = 0.2 + sampleRate = 0.2 ``` ```bash tab="CLI" @@ -107,9 +107,9 @@ tracing: ```toml tab="File (TOML)" [tracing] - [tracing.globalAttributes] - attr1 = "foo" - attr2 = "bar" + [tracing.globalAttributes] + attr1 = "foo" + attr2 = "bar" ``` ```bash tab="CLI" @@ -132,7 +132,7 @@ tracing: ```toml tab="File (TOML)" [tracing] - capturedRequestHeaders = ["X-CustomHeader"] + capturedRequestHeaders = ["X-CustomHeader"] ``` ```bash tab="CLI" @@ -154,7 +154,7 @@ tracing: ```toml tab="File (TOML)" [tracing] - capturedResponseHeaders = ["X-CustomHeader"] + capturedResponseHeaders = ["X-CustomHeader"] ``` ```bash tab="CLI" @@ -170,14 +170,14 @@ Defines the list of query parameters to not redact. ```yaml tab="File (YAML)" tracing: - safeQueryParams: - - bar - - buz + safeQueryParams: + - bar + - buz ``` ```toml tab="File (TOML)" [tracing] - safeQueryParams = ["bar", "buz"] + safeQueryParams = ["bar", "buz"] ``` ```bash tab="CLI" diff --git a/docs/content/operations/cli.md b/docs/content/operations/cli.md index cf4257ae5..a7c9a7fbf 100644 --- a/docs/content/operations/cli.md +++ b/docs/content/operations/cli.md @@ -33,7 +33,7 @@ traefik [--flag[=true|false| ]] [-f [true|false| ]] All flags are documented in the [(static configuration) CLI reference](../reference/static-configuration/cli.md). -!!! info "Flags are case insensitive." +!!! info "Flags are case-insensitive." ### `healthcheck` diff --git a/docs/content/providers/overview.md b/docs/content/providers/overview.md index 1aba0f932..7ee8ad75c 100644 --- a/docs/content/providers/overview.md +++ b/docs/content/providers/overview.md @@ -81,7 +81,7 @@ For the list of the providers names, see the [supported providers](#supported-pr - "traefik.http.routers.my-container.middlewares=add-foo-prefix@file" ``` - ```yaml tab="Kubernetes Ingress Route" + ```yaml tab="IngressRoute" apiVersion: traefik.io/v1alpha1 kind: IngressRoute metadata: @@ -103,7 +103,7 @@ For the list of the providers names, see the [supported providers](#supported-pr # when the cross-provider syntax is used. ``` - ```yaml tab="Kubernetes Ingress" + ```yaml tab="Ingress" apiVersion: traefik.io/v1alpha1 kind: Middleware metadata: diff --git a/docs/content/reference/dynamic-configuration/consul-catalog.md b/docs/content/reference/dynamic-configuration/consul-catalog.md index 534fd953a..2669b8153 100644 --- a/docs/content/reference/dynamic-configuration/consul-catalog.md +++ b/docs/content/reference/dynamic-configuration/consul-catalog.md @@ -8,7 +8,7 @@ description: "View the reference for performing dynamic configurations with Trae Dynamic configuration with Consul Catalog {: .subtitle } -The labels are case insensitive. +The labels are case-insensitive. ```yaml --8<-- "content/reference/dynamic-configuration/consul-catalog.yml" diff --git a/docs/content/reference/dynamic-configuration/ecs.md b/docs/content/reference/dynamic-configuration/ecs.md index e32038f5a..cf721befd 100644 --- a/docs/content/reference/dynamic-configuration/ecs.md +++ b/docs/content/reference/dynamic-configuration/ecs.md @@ -8,7 +8,7 @@ description: "Learn how to do dynamic configuration in Traefik Proxy with AWS EC Dynamic configuration with ECS provider {: .subtitle } -The labels are case insensitive. +The labels are case-insensitive. ```yaml --8<-- "content/reference/dynamic-configuration/ecs.yml" diff --git a/docs/content/reference/dynamic-configuration/kubernetes-gateway-rbac.yml b/docs/content/reference/dynamic-configuration/kubernetes-gateway-rbac.yml index ed0c744df..31b0837c8 100644 --- a/docs/content/reference/dynamic-configuration/kubernetes-gateway-rbac.yml +++ b/docs/content/reference/dynamic-configuration/kubernetes-gateway-rbac.yml @@ -16,6 +16,7 @@ rules: resources: - services - secrets + - configmaps verbs: - get - list @@ -34,9 +35,10 @@ rules: - gateways - httproutes - grpcroutes - - referencegrants - tcproutes - tlsroutes + - referencegrants + - backendtlspolicies verbs: - get - list @@ -50,6 +52,8 @@ rules: - grpcroutes/status - tcproutes/status - tlsroutes/status + - referencegrants/status + - backendtlspolicies/status verbs: - update diff --git a/docs/content/reference/dynamic-configuration/nomad.md b/docs/content/reference/dynamic-configuration/nomad.md index 0a8b334ad..680e621b4 100644 --- a/docs/content/reference/dynamic-configuration/nomad.md +++ b/docs/content/reference/dynamic-configuration/nomad.md @@ -8,7 +8,7 @@ description: "View the reference for performing dynamic configurations with Trae Dynamic configuration with Nomad Service Discovery {: .subtitle } -The labels are case insensitive. +The labels are case-insensitive. ```yaml --8<-- "content/reference/dynamic-configuration/nomad.yml" diff --git a/docs/content/reference/dynamic-configuration/rancher.md b/docs/content/reference/dynamic-configuration/rancher.md new file mode 100644 index 000000000..e69de29bb diff --git a/docs/content/reference/static-configuration/cli-ref.md b/docs/content/reference/static-configuration/cli-ref.md index b0c02c565..fe1c408e8 100644 --- a/docs/content/reference/static-configuration/cli-ref.md +++ b/docs/content/reference/static-configuration/cli-ref.md @@ -126,9 +126,15 @@ Entry points definition. (Default: ```false```) `--entrypoints..address`: Entry point address. +`--entrypoints..allowacmebypass`: +Enables handling of ACME TLS and HTTP challenges with custom routers. (Default: ```false```) + `--entrypoints..asdefault`: Adds this EntryPoint to the list of default EntryPoints to be used on routers that don't have any Entrypoint defined. (Default: ```false```) +`--entrypoints..forwardedheaders.connection`: +List of Connection headers that are allowed to pass through the middleware chain before being removed. + `--entrypoints..forwardedheaders.insecure`: Trust all forwarded headers. (Default: ```false```) @@ -141,6 +147,9 @@ HTTP configuration. `--entrypoints..http.encodequerysemicolons`: Defines whether request query semicolons should be URLEncoded. (Default: ```false```) +`--entrypoints..http.maxheaderbytes`: +Maximum size of request headers in bytes. (Default: ```1048576```) + `--entrypoints..http.middlewares`: Default middlewares for the routers linked to the entry point. diff --git a/docs/content/reference/static-configuration/env-ref.md b/docs/content/reference/static-configuration/env-ref.md index 2f4c19971..ef6656ddf 100644 --- a/docs/content/reference/static-configuration/env-ref.md +++ b/docs/content/reference/static-configuration/env-ref.md @@ -126,9 +126,15 @@ Entry points definition. (Default: ```false```) `TRAEFIK_ENTRYPOINTS__ADDRESS`: Entry point address. +`TRAEFIK_ENTRYPOINTS__ALLOWACMEBYPASS`: +Enables handling of ACME TLS and HTTP challenges with custom routers. (Default: ```false```) + `TRAEFIK_ENTRYPOINTS__ASDEFAULT`: Adds this EntryPoint to the list of default EntryPoints to be used on routers that don't have any Entrypoint defined. (Default: ```false```) +`TRAEFIK_ENTRYPOINTS__FORWARDEDHEADERS_CONNECTION`: +List of Connection headers that are allowed to pass through the middleware chain before being removed. + `TRAEFIK_ENTRYPOINTS__FORWARDEDHEADERS_INSECURE`: Trust all forwarded headers. (Default: ```false```) @@ -150,6 +156,9 @@ UDP port to advertise, on which HTTP/3 is available. (Default: ```0```) `TRAEFIK_ENTRYPOINTS__HTTP_ENCODEQUERYSEMICOLONS`: Defines whether request query semicolons should be URLEncoded. (Default: ```false```) +`TRAEFIK_ENTRYPOINTS__HTTP_MAXHEADERBYTES`: +Maximum size of request headers in bytes. (Default: ```1048576```) + `TRAEFIK_ENTRYPOINTS__HTTP_MIDDLEWARES`: Default middlewares for the routers linked to the entry point. diff --git a/docs/content/reference/static-configuration/file.toml b/docs/content/reference/static-configuration/file.toml index f92cd3763..392088ac4 100644 --- a/docs/content/reference/static-configuration/file.toml +++ b/docs/content/reference/static-configuration/file.toml @@ -30,6 +30,7 @@ [entryPoints] [entryPoints.EntryPoint0] address = "foobar" + allowACMEByPass = true reusePort = true asDefault = true [entryPoints.EntryPoint0.transport] @@ -48,9 +49,11 @@ [entryPoints.EntryPoint0.forwardedHeaders] insecure = true trustedIPs = ["foobar", "foobar"] + connection = ["foobar", "foobar"] [entryPoints.EntryPoint0.http] middlewares = ["foobar", "foobar"] encodeQuerySemicolons = true + maxHeaderBytes = 42 [entryPoints.EntryPoint0.http.redirections] [entryPoints.EntryPoint0.http.redirections.entryPoint] to = "foobar" diff --git a/docs/content/reference/static-configuration/file.yaml b/docs/content/reference/static-configuration/file.yaml index 12b5efccc..6c79ac9c5 100644 --- a/docs/content/reference/static-configuration/file.yaml +++ b/docs/content/reference/static-configuration/file.yaml @@ -35,6 +35,7 @@ tcpServersTransport: entryPoints: EntryPoint0: address: foobar + allowACMEByPass: true reusePort: true asDefault: true transport: @@ -57,6 +58,9 @@ entryPoints: trustedIPs: - foobar - foobar + connection: + - foobar + - foobar http: redirections: entryPoint: @@ -80,6 +84,7 @@ entryPoints: - foobar - foobar encodeQuerySemicolons: true + maxHeaderBytes: 42 http2: maxConcurrentStreams: 42 http3: diff --git a/docs/content/routing/entrypoints.md b/docs/content/routing/entrypoints.md index 04c730924..f01d06785 100644 --- a/docs/content/routing/entrypoints.md +++ b/docs/content/routing/entrypoints.md @@ -233,6 +233,35 @@ If both TCP and UDP are wanted for the same port, two entryPoints definitions ar Full details for how to specify `address` can be found in [net.Listen](https://golang.org/pkg/net/#Listen) (and [net.Dial](https://golang.org/pkg/net/#Dial)) of the doc for go. +### AllowACMEByPass + +_Optional, Default=false_ + +`allowACMEByPass` determines whether a user defined router can handle ACME TLS or HTTP challenges instead of the Traefik dedicated one. +This option can be used when a Traefik instance has one or more certificate resolvers configured, +but is also used to route challenges connections/requests to services that could also initiate their own ACME challenges. + +??? info "No Certificate Resolvers configured" + + It is not necessary to use the `allowACMEByPass' option certificate option if no certificate resolver is defined. + In fact, Traefik will automatically allow ACME TLS or HTTP requests to be handled by custom routers in this case, since there can be no concurrency with its own challenge handlers. + +```yaml tab="File (YAML)" +entryPoints: + foo: + allowACMEByPass: true +``` + +```toml tab="File (TOML)" +[entryPoints.foo] + [entryPoints.foo.allowACMEByPass] + allowACMEByPass = true +``` + +```bash tab="CLI" +--entryPoints.name.allowACMEByPass=true +``` + ### ReusePort _Optional, Default=false_ @@ -500,6 +529,40 @@ You can configure Traefik to trust the forwarded headers information (`X-Forward --entryPoints.web.forwardedHeaders.insecure ``` +??? info "`forwardedHeaders.connection`" + + As per RFC7230, Traefik respects the Connection options from the client request. + By doing so, it removes any header field(s) listed in the request Connection header and the Connection header field itself when empty. + The removal happens as soon as the request is handled by Traefik, + thus the removed headers are not available when the request passes through the middleware chain. + The `connection` option lists the Connection headers allowed to passthrough the middleware chain before their removal. + + ```yaml tab="File (YAML)" + ## Static configuration + entryPoints: + web: + address: ":80" + forwardedHeaders: + connection: + - foobar + ``` + + ```toml tab="File (TOML)" + ## Static configuration + [entryPoints] + [entryPoints.web] + address = ":80" + + [entryPoints.web.forwardedHeaders] + connection = ["foobar"] + ``` + + ```bash tab="CLI" + ## Static configuration + --entryPoints.web.address=:80 + --entryPoints.web.forwardedHeaders.connection=foobar + ``` + ### Transport #### `respondingTimeouts` diff --git a/docs/content/routing/providers/consul-catalog.md b/docs/content/routing/providers/consul-catalog.md index 4f5b6435f..82bf1542d 100644 --- a/docs/content/routing/providers/consul-catalog.md +++ b/docs/content/routing/providers/consul-catalog.md @@ -24,7 +24,7 @@ With Consul Catalog, Traefik can leverage tags attached to a service to generate !!! info "tags" - - tags are case insensitive. + - tags are case-insensitive. - The complete list of tags can be found [the reference page](../../reference/dynamic-configuration/consul-catalog.md) ### General diff --git a/docs/content/routing/providers/docker.md b/docs/content/routing/providers/docker.md index 41452de4d..7b1f81408 100644 --- a/docs/content/routing/providers/docker.md +++ b/docs/content/routing/providers/docker.md @@ -95,7 +95,7 @@ With Docker, Traefik can leverage labels attached to a container to generate rou !!! info "Labels" - - Labels are case insensitive. + - Labels are case-insensitive. - The complete list of labels can be found in [the reference page](../../reference/dynamic-configuration/docker.md). ### General diff --git a/docs/content/routing/providers/ecs.md b/docs/content/routing/providers/ecs.md index e2167b4e8..fa29eb8a1 100644 --- a/docs/content/routing/providers/ecs.md +++ b/docs/content/routing/providers/ecs.md @@ -22,7 +22,7 @@ With ECS, Traefik can leverage labels attached to a container to generate routin !!! info "labels" - - labels are case insensitive. + - labels are case-insensitive. - The complete list of labels can be found in [the reference page](../../reference/dynamic-configuration/ecs.md). ### General diff --git a/docs/content/routing/providers/kv.md b/docs/content/routing/providers/kv.md index 3a6cc7744..fad639d19 100644 --- a/docs/content/routing/providers/kv.md +++ b/docs/content/routing/providers/kv.md @@ -12,7 +12,7 @@ A Story of key & values !!! info "Keys" - - Keys are case insensitive. + - Keys are case-insensitive. - The complete list of keys can be found in [the reference page](../../reference/dynamic-configuration/kv.md). ### Routers diff --git a/docs/content/routing/providers/marathon.md b/docs/content/routing/providers/marathon.md new file mode 100644 index 000000000..e69de29bb diff --git a/docs/content/routing/providers/nomad.md b/docs/content/routing/providers/nomad.md index 9f585dbc6..c1dc31f30 100644 --- a/docs/content/routing/providers/nomad.md +++ b/docs/content/routing/providers/nomad.md @@ -24,7 +24,7 @@ With Nomad, Traefik can leverage tags attached to a service to generate routing !!! info "tags" - - tags are case insensitive. + - tags are case-insensitive. - The complete list of tags can be found [the reference page](../../reference/dynamic-configuration/nomad.md) ### General diff --git a/docs/content/routing/providers/swarm.md b/docs/content/routing/providers/swarm.md index cdf406f75..f6dcfb1ef 100644 --- a/docs/content/routing/providers/swarm.md +++ b/docs/content/routing/providers/swarm.md @@ -118,7 +118,7 @@ With Docker Swarm, Traefik can leverage labels attached to a service to generate !!! info "Labels" - - Labels are case insensitive. + - Labels are case-insensitive. - The complete list of labels can be found in [the reference page](../../reference/dynamic-configuration/docker.md). ### General diff --git a/docs/content/routing/routers/index.md b/docs/content/routing/routers/index.md index 99c06852b..7e93c79c3 100644 --- a/docs/content/routing/routers/index.md +++ b/docs/content/routing/routers/index.md @@ -1197,7 +1197,7 @@ A value of `0` for the priority is ignored: `priority = 0` means that the defaul | Router-2 | ```ClientIP(`192.168.0.0/24`)``` | 26 | Which means that requests from `192.168.0.12` would go to Router-2 even though Router-1 is intended to specifically handle them. - To achieve this intention, a priority (higher than 26) should be set on Router-1. + To achieve this intention, a priority (greater than 26) should be set on Router-1. ??? example "Setting priorities -- using the [File Provider](../../providers/file.md)" diff --git a/docs/content/user-guides/docker-compose/acme-dns/index.md b/docs/content/user-guides/docker-compose/acme-dns/index.md index 5be0287ba..5affbe009 100644 --- a/docs/content/user-guides/docker-compose/acme-dns/index.md +++ b/docs/content/user-guides/docker-compose/acme-dns/index.md @@ -1,6 +1,6 @@ --- title: "Traefik Docker DNS Challenge Documentation" -description: "Learn how to create a certificate with the Let's Encrypt DNS challenge to use HTTPS on a Service exposed with Traefik Proxy. Read the tehnical documentation." +description: "Learn how to create a certificate with the Let's Encrypt DNS challenge to use HTTPS on a Service exposed with Traefik Proxy. Read the technical documentation." --- # Docker-compose with Let's Encrypt: DNS Challenge diff --git a/go.mod b/go.mod index 545e61b52..387bd30cf 100644 --- a/go.mod +++ b/go.mod @@ -33,7 +33,6 @@ require ( github.com/http-wasm/http-wasm-host-go v0.6.0 github.com/influxdata/influxdb-client-go/v2 v2.7.0 github.com/influxdata/influxdb1-client v0.0.0-20200827194710-b269163b24ab // No tag on the repo. - github.com/juliens/wasm-goexport v0.0.6 github.com/klauspost/compress v1.17.9 github.com/kvtools/consul v1.0.2 github.com/kvtools/etcdv3 v1.0.2 @@ -50,7 +49,7 @@ require ( github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // No tag on the repo. github.com/prometheus/client_golang v1.19.1 github.com/prometheus/client_model v0.5.0 - github.com/quic-go/quic-go v0.45.1 + github.com/quic-go/quic-go v0.47.0 github.com/rs/zerolog v1.29.0 github.com/sirupsen/logrus v1.9.3 github.com/spiffe/go-spiffe/v2 v2.1.1 @@ -82,13 +81,13 @@ require ( go.opentelemetry.io/otel/sdk v1.28.0 go.opentelemetry.io/otel/sdk/metric v1.28.0 go.opentelemetry.io/otel/trace v1.28.0 - golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 // No tag on the repo. - golang.org/x/mod v0.18.0 - golang.org/x/net v0.26.0 - golang.org/x/sys v0.23.0 - golang.org/x/text v0.17.0 + golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 // No tag on the repo. + golang.org/x/mod v0.21.0 + golang.org/x/net v0.29.0 + golang.org/x/sys v0.25.0 + golang.org/x/text v0.18.0 golang.org/x/time v0.5.0 - golang.org/x/tools v0.22.0 + golang.org/x/tools v0.25.0 google.golang.org/grpc v1.64.1 gopkg.in/natefinch/lumberjack.v2 v2.2.1 gopkg.in/yaml.v3 v3.0.1 @@ -190,7 +189,7 @@ require ( github.com/go-openapi/jsonreference v0.21.0 // indirect github.com/go-openapi/swag v0.23.0 // indirect github.com/go-resty/resty/v2 v2.11.0 // indirect - github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect + github.com/go-task/slim-sprig/v3 v3.0.0 // indirect github.com/go-viper/mapstructure/v2 v2.0.0 // indirect github.com/go-zookeeper/zk v1.0.3 // indirect github.com/goccy/go-json v0.10.3 // indirect @@ -204,7 +203,7 @@ require ( github.com/google/go-cmp v0.6.0 // indirect github.com/google/go-querystring v1.1.0 // indirect github.com/google/gofuzz v1.2.0 // indirect - github.com/google/pprof v0.0.0-20230817174616-7a8ec2ada47b // indirect + github.com/google/pprof v0.0.0-20240910150728-a0b0bb1d4134 // indirect github.com/google/s2a-go v0.1.7 // indirect github.com/google/uuid v1.6.0 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect @@ -275,7 +274,7 @@ require ( github.com/nrdcg/porkbun v0.3.0 // indirect github.com/nzdjb/go-metaname v1.0.0 // indirect github.com/onsi/ginkgo v1.16.5 // indirect - github.com/onsi/ginkgo/v2 v2.17.1 // indirect + github.com/onsi/ginkgo/v2 v2.20.2 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.0 // indirect github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b // indirect @@ -288,7 +287,7 @@ require ( github.com/pquerna/otp v1.4.0 // indirect github.com/prometheus/common v0.48.0 // indirect github.com/prometheus/procfs v0.15.1 // indirect - github.com/quic-go/qpack v0.4.0 // indirect + github.com/quic-go/qpack v0.5.1 // indirect github.com/redis/go-redis/v9 v9.2.1 // indirect github.com/rs/cors v1.7.0 // indirect github.com/sacloud/api-client-go v0.2.10 // indirect @@ -338,10 +337,10 @@ require ( go.uber.org/ratelimit v0.3.0 // indirect go.uber.org/zap v1.26.0 // indirect golang.org/x/arch v0.4.0 // indirect - golang.org/x/crypto v0.26.0 // indirect + golang.org/x/crypto v0.27.0 // indirect golang.org/x/oauth2 v0.21.0 // indirect golang.org/x/sync v0.8.0 // indirect - golang.org/x/term v0.23.0 // indirect + golang.org/x/term v0.24.0 // indirect google.golang.org/api v0.172.0 // indirect google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de // indirect google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094 // indirect @@ -371,4 +370,5 @@ replace ( // tencentcloud uses monorepo with multimodule but the go.mod files are incomplete. exclude github.com/tencentcloud/tencentcloud-sdk-go v3.0.83+incompatible +// Replace to handle new wasmexport in official go and wazergo for http calls. replace github.com/http-wasm/http-wasm-host-go => github.com/traefik/http-wasm-host-go v0.0.0-20240618100324-3c53dcaa1a70 diff --git a/go.sum b/go.sum index 927bfbf39..f82bee725 100644 --- a/go.sum +++ b/go.sum @@ -356,23 +356,20 @@ github.com/go-resty/resty/v2 v2.11.0 h1:i7jMfNOJYMp69lq7qozJP+bjgzfAzeOhuGlyDrqx github.com/go-resty/resty/v2 v2.11.0/go.mod h1:iiP/OpA0CkcL3IGt1O0+/SIItFUbkkyw5BGXiVdTu+A= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= -github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= -github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= +github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= +github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/go-viper/mapstructure/v2 v2.0.0 h1:dhn8MZ1gZ0mzeodTG3jt5Vj/o87xZKuNAprG2mQfMfc= github.com/go-viper/mapstructure/v2 v2.0.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/go-zookeeper/zk v1.0.3 h1:7M2kwOsc//9VeeFiPtf+uSJlVpU66x9Ba5+8XK7/TDg= github.com/go-zookeeper/zk v1.0.3/go.mod h1:nOB03cncLtlp4t+UAkGSV+9beXP/akpekBwL+UX1Qcw= github.com/gobs/pretty v0.0.0-20180724170744-09732c25a95b h1:/vQ+oYKu+JoyaMPDsv5FzwuL2wwWBgBbtj/YLCi4LuA= github.com/gobs/pretty v0.0.0-20180724170744-09732c25a95b/go.mod h1:Xo4aNUOrJnVruqWQJBtW6+bTBDTniY8yZum5rF3b5jw= +github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee h1:s+21KNqlpePfkah2I+gwHF8xmJWRjooY+5248k6m4A0= github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo= -github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU= -github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM= +github.com/gobwas/pool v0.2.0 h1:QEmUOlnSjWtnpRGHF3SauEiOsy82Cup83Vf2LcMlnc8= github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= -github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og= -github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= +github.com/gobwas/ws v1.0.2 h1:CoAavW/wd/kulfZmSIBt6p24n4j7tHgNVCjsfHVNUbo= github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM= -github.com/gobwas/ws v1.2.1 h1:F2aeBZrm2NDsc7vbovKrWSogd4wvfAxg0FQ89/iqOTk= -github.com/gobwas/ws v1.2.1/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY= github.com/goccy/go-json v0.7.8/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA= github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= @@ -455,8 +452,8 @@ github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXi github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20230817174616-7a8ec2ada47b h1:h9U78+dx9a4BKdQkBBos92HalKpaGKHrp+3Uo6yTodo= -github.com/google/pprof v0.0.0-20230817174616-7a8ec2ada47b/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik= +github.com/google/pprof v0.0.0-20240910150728-a0b0bb1d4134 h1:c5FlPPgxOn7kJz3VoPLkQYQXGBS3EklQ4Zfi57uOuqQ= +github.com/google/pprof v0.0.0-20240910150728-a0b0bb1d4134/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o= github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw= @@ -590,8 +587,6 @@ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnr github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= -github.com/juliens/wasm-goexport v0.0.6 h1:YU0c+j0dF/HNy32vgYTA+K/6wnsZXgGc+ihl/UDw8iA= -github.com/juliens/wasm-goexport v0.0.6/go.mod h1:VTTpJVY3tIBet0Gv8r5TxdsNg0vDkkqXYm0Hp5hR42A= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213 h1:qGQQKEcAR99REcMpsXCp3lJ03zYT1PkRd3kQGPn9GVg= @@ -800,15 +795,15 @@ github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= github.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= -github.com/onsi/ginkgo/v2 v2.17.1 h1:V++EzdbhI4ZV4ev0UTIj0PzhzOcReJFyJaLjtSF55M8= -github.com/onsi/ginkgo/v2 v2.17.1/go.mod h1:llBI3WDLL9Z6taip6f33H76YcWtJv+7R3HigUjbIBOs= +github.com/onsi/ginkgo/v2 v2.20.2 h1:7NVCeyIWROIAheY21RLS+3j2bb52W0W82tkberYytp4= +github.com/onsi/ginkgo/v2 v2.20.2/go.mod h1:K9gyxPIlb+aIvnZ8bd9Ak+YP18w3APlR+5coaZoE2ag= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs= github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= -github.com/onsi/gomega v1.32.0 h1:JRYU78fJ1LPxlckP6Txi/EYqJvjtMrDC04/MM5XRHPk= -github.com/onsi/gomega v1.32.0/go.mod h1:a4x4gW6Pz2yK1MAmvluYme5lvYTn61afQ2ETw/8n4Lg= +github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k= +github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= @@ -876,10 +871,10 @@ github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+Gx github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= -github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo= -github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A= -github.com/quic-go/quic-go v0.45.1 h1:tPfeYCk+uZHjmDRwHHQmvHRYL2t44ROTujLeFVBmjCA= -github.com/quic-go/quic-go v0.45.1/go.mod h1:1dLehS7TIR64+vxGR70GDcatWTOtMX2PUtnKsjbTurI= +github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI= +github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg= +github.com/quic-go/quic-go v0.47.0 h1:yXs3v7r2bm1wmPTYNLKAAJTHMYkPEsfYJmTazXrCZ7Y= +github.com/quic-go/quic-go v0.47.0/go.mod h1:3bCapYsJvXGZcipOHuu7plYtaV6tnF+z7wIFsU0WK9E= github.com/redis/go-redis/v9 v9.2.1 h1:WlYJg71ODF0dVspZZCpYmoF1+U1Jjk9Rwd7pq6QmlCg= github.com/redis/go-redis/v9 v9.2.1/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= @@ -1161,8 +1156,8 @@ golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4 golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= -golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= -golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= +golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= +golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= 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= @@ -1171,8 +1166,8 @@ golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= -golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM= -golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc= +golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 h1:e66Fs6Z+fZTbFBAxKfP3PALWBtpfqks2bwGcexMxgtk= +golang.org/x/exp v0.0.0-20240909161429-701f63a606c0/go.mod h1:2TbTHSBQa924w8M6Xs1QcRcFwyucIwBGpK1p2f1YFFY= golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= @@ -1193,8 +1188,8 @@ golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0= -golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0= +golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1234,8 +1229,8 @@ golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= -golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= -golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= +golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo= +golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= 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= @@ -1322,8 +1317,8 @@ golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM= -golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= +golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -1333,8 +1328,8 @@ golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= -golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU= -golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk= +golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM= +golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= @@ -1348,8 +1343,8 @@ golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= 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.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= -golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= +golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= 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= @@ -1389,8 +1384,8 @@ golang.org/x/tools v0.0.0-20210114065538-d78b04bdf963/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA= -golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c= +golang.org/x/tools v0.25.0 h1:oFU9pkj/iJgs+0DT+VMHrx+oBKs/LJMV+Uvg78sl+fE= +golang.org/x/tools v0.25.0/go.mod h1:/vtpO8WL1N9cQC3FN5zPqb//fRXskFHbLKk4OW1Q7rg= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/integration/fixtures/headers/connection_hop_by_hop_headers.toml b/integration/fixtures/headers/connection_hop_by_hop_headers.toml new file mode 100644 index 000000000..091e1995c --- /dev/null +++ b/integration/fixtures/headers/connection_hop_by_hop_headers.toml @@ -0,0 +1,37 @@ +[global] + checkNewVersion = false + sendAnonymousUsage = false + +[log] + level = "DEBUG" + +# Limiting the Logs to Specific Fields +[accessLog] + format = "json" + filePath = "access.log" + + [accessLog.fields.headers.names] + "Foo" = "keep" + "Bar" = "keep" + +[entryPoints] + [entryPoints.web] + address = ":8000" + [entryPoints.web.forwardedHeaders] + insecure = true + connection = ["Foo"] + +[providers.file] + filename = "{{ .SelfFilename }}" + +## dynamic configuration ## + +[http.routers] + [http.routers.router1] + rule = "Host(`test.localhost`)" + service = "service1" + +[http.services] + [http.services.service1.loadBalancer] + [[http.services.service1.loadBalancer.servers]] + url = "http://127.0.0.1:9000" diff --git a/integration/fixtures/https/clientca/README.md b/integration/fixtures/https/clientca/README.md index 33a5f5260..f870c622d 100644 --- a/integration/fixtures/https/clientca/README.md +++ b/integration/fixtures/https/clientca/README.md @@ -48,7 +48,7 @@ openssl genrsa -out client3.key 2048 # Locality Name (eg, city) []:. # Organization Name (eg, company) [Internet Widgits Pty Ltd]:. # Organizational Unit Name (eg, section) []:. -# Common Name (e.g. server FQDN or YOUR name) []:clien1.example.com +# Common Name (e.g. server FQDN or YOUR name) []:client1.example.com # Email Address []:. # # Please enter the following 'extra' attributes @@ -58,7 +58,7 @@ openssl genrsa -out client3.key 2048 # Issuer # CN = ca1.example.com # Subject -# CN = clien1.example.com +# CN = client1.example.com openssl req -key client1.key -new -out client1.csr # Country Name (2 letter code) [AU]:. diff --git a/integration/fixtures/k8s-conformance/01-rbac.yml b/integration/fixtures/k8s-conformance/01-rbac.yml index 0e30b4f3b..40ff94c6d 100644 --- a/integration/fixtures/k8s-conformance/01-rbac.yml +++ b/integration/fixtures/k8s-conformance/01-rbac.yml @@ -16,6 +16,7 @@ rules: resources: - services - secrets + - configmaps verbs: - get - list @@ -38,6 +39,7 @@ rules: - tcproutes - tlsroutes - referencegrants + - backendtlspolicies verbs: - get - list @@ -52,6 +54,7 @@ rules: - tcproutes/status - tlsroutes/status - referencegrants/status + - backendtlspolicies/status verbs: - update diff --git a/integration/fixtures/k8s/01-gateway-api-crd.yml b/integration/fixtures/k8s/00-experimental-v1.1.0.yml similarity index 100% rename from integration/fixtures/k8s/01-gateway-api-crd.yml rename to integration/fixtures/k8s/00-experimental-v1.1.0.yml diff --git a/integration/fixtures/simple_max_header_size.toml b/integration/fixtures/simple_max_header_size.toml new file mode 100644 index 000000000..afd1dd7e2 --- /dev/null +++ b/integration/fixtures/simple_max_header_size.toml @@ -0,0 +1,25 @@ +[global] + checkNewVersion = false + sendAnonymousUsage = false + +[entryPoints] + [entryPoints.web] + address = ":8000" + [entryPoints.web.http] + maxHeaderBytes = 1310720 + +[providers.file] + filename = "{{ .SelfFilename }}" + +## dynamic configuration ## + +[http.routers] + [http.routers.test-router] + entryPoints = ["web"] + service = "test-service" + rule = "Host(`127.0.0.1`)" + +[http.services] + [http.services.test-service] + [[http.services.test-service.loadBalancer.servers]] + url = "{{ .TestServer }}" diff --git a/integration/headers_test.go b/integration/headers_test.go index e47fe1d73..c3c1377ca 100644 --- a/integration/headers_test.go +++ b/integration/headers_test.go @@ -4,6 +4,7 @@ import ( "net" "net/http" "net/http/httptest" + "os" "testing" "time" @@ -20,6 +21,11 @@ func TestHeadersSuite(t *testing.T) { suite.Run(t, new(HeadersSuite)) } +func (s *HeadersSuite) TearDownTest() { + s.displayTraefikLogFile(traefikTestLogFile) + _ = os.Remove(traefikTestAccessLogFile) +} + func (s *HeadersSuite) TestSimpleConfiguration() { s.traefikCmd(withConfigFile("fixtures/headers/basic.toml")) @@ -62,6 +68,53 @@ func (s *HeadersSuite) TestReverseProxyHeaderRemoved() { require.NoError(s.T(), err) } +func (s *HeadersSuite) TestConnectionHopByHop() { + file := s.adaptFile("fixtures/headers/connection_hop_by_hop_headers.toml", struct{}{}) + s.traefikCmd(withConfigFile(file)) + + handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + _, found := r.Header["X-Forwarded-For"] + assert.True(s.T(), found) + xHost, found := r.Header["X-Forwarded-Host"] + assert.True(s.T(), found) + assert.Equal(s.T(), "localhost", xHost[0]) + + _, found = r.Header["Foo"] + assert.False(s.T(), found) + _, found = r.Header["Bar"] + assert.False(s.T(), found) + }) + + listener, err := net.Listen("tcp", "127.0.0.1:9000") + require.NoError(s.T(), err) + + ts := &httptest.Server{ + Listener: listener, + Config: &http.Server{Handler: handler}, + } + ts.Start() + defer ts.Close() + + req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/", nil) + require.NoError(s.T(), err) + req.Host = "test.localhost" + req.Header = http.Header{ + "Connection": {"Foo,Bar,X-Forwarded-For,X-Forwarded-Host"}, + "Foo": {"bar"}, + "Bar": {"foo"}, + "X-Forwarded-Host": {"localhost"}, + } + + err = try.Request(req, time.Second, try.StatusCodeIs(http.StatusOK)) + require.NoError(s.T(), err) + + accessLog, err := os.ReadFile(traefikTestAccessLogFile) + require.NoError(s.T(), err) + + assert.Contains(s.T(), string(accessLog), "\"request_Foo\":\"bar\"") + assert.NotContains(s.T(), string(accessLog), "\"request_Bar\":\"\"") +} + func (s *HeadersSuite) TestCorsResponses() { file := s.adaptFile("fixtures/headers/cors.toml", struct{}{}) s.traefikCmd(withConfigFile(file)) diff --git a/integration/k8s_conformance_test.go b/integration/k8s_conformance_test.go index 56c1e07f8..b42112ba4 100644 --- a/integration/k8s_conformance_test.go +++ b/integration/k8s_conformance_test.go @@ -18,6 +18,7 @@ import ( "github.com/testcontainers/testcontainers-go/modules/k3s" "github.com/testcontainers/testcontainers-go/network" "github.com/traefik/traefik/v3/integration/try" + "github.com/traefik/traefik/v3/pkg/provider/kubernetes/gateway" apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" "k8s.io/apimachinery/pkg/util/sets" kclientset "k8s.io/client-go/kubernetes" @@ -33,7 +34,6 @@ import ( "sigs.k8s.io/gateway-api/conformance/tests" "sigs.k8s.io/gateway-api/conformance/utils/config" ksuite "sigs.k8s.io/gateway-api/conformance/utils/suite" - "sigs.k8s.io/gateway-api/pkg/features" "sigs.k8s.io/yaml" ) @@ -199,23 +199,7 @@ func (s *K8sConformanceSuite) TestK8sGatewayAPIConformance() { ksuite.GatewayGRPCConformanceProfileName, ksuite.GatewayTLSConformanceProfileName, ), - SupportedFeatures: sets.New( - features.SupportGateway, - features.SupportGatewayPort8080, - features.SupportGRPCRoute, - features.SupportHTTPRoute, - features.SupportHTTPRouteQueryParamMatching, - features.SupportHTTPRouteMethodMatching, - features.SupportHTTPRoutePortRedirect, - features.SupportHTTPRouteSchemeRedirect, - features.SupportHTTPRouteHostRewrite, - features.SupportHTTPRoutePathRewrite, - features.SupportHTTPRoutePathRedirect, - features.SupportHTTPRouteResponseHeaderModification, - features.SupportTLSRoute, - features.SupportHTTPRouteBackendProtocolH2C, - features.SupportHTTPRouteBackendProtocolWebSocket, - ), + SupportedFeatures: sets.New(gateway.SupportedFeatures()...), }) require.NoError(s.T(), err) diff --git a/integration/log_rotation_test.go b/integration/log_rotation_test.go index 269d17d69..c6ee5574f 100644 --- a/integration/log_rotation_test.go +++ b/integration/log_rotation_test.go @@ -24,7 +24,7 @@ const traefikTestAccessLogFileRotated = traefikTestAccessLogFile + ".rotated" // Log rotation integration test suite. type LogRotationSuite struct{ BaseSuite } -func TestLogRorationSuite(t *testing.T) { +func TestLogRotationSuite(t *testing.T) { suite.Run(t, new(LogRotationSuite)) } diff --git a/integration/simple_test.go b/integration/simple_test.go index 9aae5c1b2..d03acaa64 100644 --- a/integration/simple_test.go +++ b/integration/simple_test.go @@ -1414,7 +1414,7 @@ func (s *SimpleSuite) TestDebugLog() { req, err := http.NewRequest(http.MethodGet, "http://localhost:8000/whoami", http.NoBody) require.NoError(s.T(), err) - req.Header.Set("Autorization", "Bearer ThisIsABearerToken") + req.Header.Set("Authorization", "Bearer ThisIsABearerToken") response, err := http.DefaultClient.Do(req) require.NoError(s.T(), err) @@ -1511,3 +1511,63 @@ func (s *SimpleSuite) TestDenyFragment() { require.NoError(s.T(), err) assert.Equal(s.T(), http.StatusBadRequest, resp.StatusCode) } + +func (s *SimpleSuite) TestMaxHeaderBytes() { + handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + }) + + listener, err := net.Listen("tcp", "127.0.0.1:9000") + require.NoError(s.T(), err) + + ts := &httptest.Server{ + Listener: listener, + Config: &http.Server{ + Handler: handler, + MaxHeaderBytes: 1.25 * 1024 * 1024, // 1.25 MB + }, + } + ts.Start() + defer ts.Close() + + // The test server and traefik config file both specify a max request header size of 1.25 MB. + file := s.adaptFile("fixtures/simple_max_header_size.toml", struct { + TestServer string + }{ts.URL}) + + s.traefikCmd(withConfigFile(file)) + + testCases := []struct { + name string + headerSize int + expectedStatus int + }{ + { + name: "1.25MB header", + headerSize: int(1.25 * 1024 * 1024), + expectedStatus: http.StatusOK, + }, + { + name: "1.5MB header", + headerSize: int(1.5 * 1024 * 1024), + expectedStatus: http.StatusRequestHeaderFieldsTooLarge, + }, + { + name: "500KB header", + headerSize: int(500 * 1024), + expectedStatus: http.StatusOK, + }, + } + + for _, test := range testCases { + s.Run(test.name, func() { + req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000", nil) + require.NoError(s.T(), err) + + req.Header.Set("X-Large-Header", strings.Repeat("A", test.headerSize)) + + err = try.Request(req, 2*time.Second, try.StatusCodeIs(test.expectedStatus)) + require.NoError(s.T(), err) + }) + } +} diff --git a/integration/try/try.go b/integration/try/try.go index 886459e1e..df3db0c9e 100644 --- a/integration/try/try.go +++ b/integration/try/try.go @@ -19,7 +19,7 @@ const ( type timedAction func(timeout time.Duration, operation DoCondition) error // Sleep pauses the current goroutine for at least the duration d. -// Deprecated: Use only when use an other Try[...] functions is not possible. +// Deprecated: Use only when use another Try[...] functions is not possible. func Sleep(d time.Duration) { d = applyCIMultiplier(d) time.Sleep(d) diff --git a/pkg/config/kv/kv_node_test.go b/pkg/config/kv/kv_node_test.go index 86b7d6ea2..7712b839e 100644 --- a/pkg/config/kv/kv_node_test.go +++ b/pkg/config/kv/kv_node_test.go @@ -70,8 +70,8 @@ func TestDecodeToNode(t *testing.T) { { desc: "several entries, level 0", in: map[string]string{ - "traefik": "bar", - "traefic": "bur", + "traefik": "bar", + "traefik_": "bur", }, expected: expected{error: true}, }, @@ -120,7 +120,7 @@ func TestDecodeToNode(t *testing.T) { }}, }, { - desc: "several entries, level 2, case insensitive", + desc: "several entries, level 2, case-insensitive", in: map[string]string{ "traefik/foo/aaa": "bar", "traefik/Foo/bbb": "bur", diff --git a/pkg/config/static/entrypoints.go b/pkg/config/static/entrypoints.go index 9b48ddce4..fe5185971 100644 --- a/pkg/config/static/entrypoints.go +++ b/pkg/config/static/entrypoints.go @@ -3,6 +3,7 @@ package static import ( "fmt" "math" + "net/http" "strings" ptypes "github.com/traefik/paerser/types" @@ -12,6 +13,7 @@ import ( // EntryPoint holds the entry point configuration. type EntryPoint struct { Address string `description:"Entry point address." json:"address,omitempty" toml:"address,omitempty" yaml:"address,omitempty"` + AllowACMEByPass bool `description:"Enables handling of ACME TLS and HTTP challenges with custom routers." json:"allowACMEByPass,omitempty" toml:"allowACMEByPass,omitempty" yaml:"allowACMEByPass,omitempty"` ReusePort bool `description:"Enables EntryPoints from the same or different processes listening on the same TCP/UDP port." json:"reusePort,omitempty" toml:"reusePort,omitempty" yaml:"reusePort,omitempty"` AsDefault bool `description:"Adds this EntryPoint to the list of default EntryPoints to be used on routers that don't have any Entrypoint defined." json:"asDefault,omitempty" toml:"asDefault,omitempty" yaml:"asDefault,omitempty"` Transport *EntryPointsTransport `description:"Configures communication between clients and Traefik." json:"transport,omitempty" toml:"transport,omitempty" yaml:"transport,omitempty" export:"true"` @@ -53,6 +55,8 @@ func (ep *EntryPoint) SetDefaults() { ep.ForwardedHeaders = &ForwardedHeaders{} ep.UDP = &UDPConfig{} ep.UDP.SetDefaults() + ep.HTTP = HTTPConfig{} + ep.HTTP.SetDefaults() ep.HTTP2 = &HTTP2Config{} ep.HTTP2.SetDefaults() } @@ -63,6 +67,12 @@ type HTTPConfig struct { Middlewares []string `description:"Default middlewares for the routers linked to the entry point." json:"middlewares,omitempty" toml:"middlewares,omitempty" yaml:"middlewares,omitempty" export:"true"` TLS *TLSConfig `description:"Default TLS configuration for the routers linked to the entry point." json:"tls,omitempty" toml:"tls,omitempty" yaml:"tls,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` EncodeQuerySemicolons bool `description:"Defines whether request query semicolons should be URLEncoded." json:"encodeQuerySemicolons,omitempty" toml:"encodeQuerySemicolons,omitempty" yaml:"encodeQuerySemicolons,omitempty"` + MaxHeaderBytes int `description:"Maximum size of request headers in bytes." json:"maxHeaderBytes,omitempty" toml:"maxHeaderBytes,omitempty" yaml:"maxHeaderBytes,omitempty" export:"true"` +} + +// SetDefaults sets the default values. +func (c *HTTPConfig) SetDefaults() { + c.MaxHeaderBytes = http.DefaultMaxHeaderBytes } // HTTP2Config is the HTTP2 configuration of an entry point. @@ -111,6 +121,7 @@ type TLSConfig struct { type ForwardedHeaders struct { Insecure bool `description:"Trust all forwarded headers." json:"insecure,omitempty" toml:"insecure,omitempty" yaml:"insecure,omitempty" export:"true"` TrustedIPs []string `description:"Trust only forwarded headers from selected IPs." json:"trustedIPs,omitempty" toml:"trustedIPs,omitempty" yaml:"trustedIPs,omitempty"` + Connection []string `description:"List of Connection headers that are allowed to pass through the middleware chain before being removed." json:"connection,omitempty" toml:"connection,omitempty" yaml:"connection,omitempty"` } // ProxyProtocol contains Proxy-Protocol configuration. diff --git a/pkg/ip/strategy_test.go b/pkg/ip/strategy_test.go index 8bc5d285e..b836223eb 100644 --- a/pkg/ip/strategy_test.go +++ b/pkg/ip/strategy_test.go @@ -46,7 +46,7 @@ func TestDepthStrategy_GetIP(t *testing.T) { expected: "10.0.0.3", }, { - desc: "Use non existing depth in XForwardedFor", + desc: "Use nonexistent depth in XForwardedFor", depth: 2, xForwardedFor: "", expected: "", diff --git a/pkg/metrics/datadog.go b/pkg/metrics/datadog.go index fcb8be6d2..89e7992a1 100644 --- a/pkg/metrics/datadog.go +++ b/pkg/metrics/datadog.go @@ -2,23 +2,30 @@ package metrics import ( "context" + "net" "strings" "time" "github.com/go-kit/kit/metrics/dogstatsd" + "github.com/go-kit/kit/util/conn" + gokitlog "github.com/go-kit/log" "github.com/rs/zerolog/log" "github.com/traefik/traefik/v3/pkg/logs" "github.com/traefik/traefik/v3/pkg/safe" "github.com/traefik/traefik/v3/pkg/types" ) +const ( + unixAddressPrefix = "unix://" + unixAddressDatagramPrefix = "unixgram://" + unixAddressStreamPrefix = "unixstream://" +) + var ( datadogClient *dogstatsd.Dogstatsd datadogLoopCancelFunc context.CancelFunc ) -const unixAddressPrefix = "unix://" - // Metric names consistent with https://github.com/DataDog/integrations-extras/pull/64 const ( ddConfigReloadsName = "config.reload.total" @@ -58,9 +65,10 @@ func RegisterDatadog(ctx context.Context, config *types.Datadog) Registry { config.Prefix = defaultMetricsPrefix } - datadogClient = dogstatsd.New(config.Prefix+".", logs.NewGoKitWrapper(log.Logger.With().Str(logs.MetricsProviderName, "datadog").Logger())) + datadogLogger := logs.NewGoKitWrapper(log.Logger.With().Str(logs.MetricsProviderName, "datadog").Logger()) + datadogClient = dogstatsd.New(config.Prefix+".", datadogLogger) - initDatadogClient(ctx, config) + initDatadogClient(ctx, config, datadogLogger) registry := &standardRegistry{ configReloadsCounter: datadogClient.NewCounter(ddConfigReloadsName, 1.0), @@ -101,7 +109,7 @@ func RegisterDatadog(ctx context.Context, config *types.Datadog) Registry { return registry } -func initDatadogClient(ctx context.Context, config *types.Datadog) { +func initDatadogClient(ctx context.Context, config *types.Datadog, logger gokitlog.LoggerFunc) { network, address := parseDatadogAddress(config.Address) ctx, datadogLoopCancelFunc = context.WithCancel(ctx) @@ -110,10 +118,38 @@ func initDatadogClient(ctx context.Context, config *types.Datadog) { ticker := time.NewTicker(time.Duration(config.PushInterval)) defer ticker.Stop() - datadogClient.SendLoop(ctx, ticker.C, network, address) + dialer := func(network, address string) (net.Conn, error) { + switch network { + case "unix": + // To mimic the Datadog client when the network is unix we will try to guess the UDS type. + newConn, err := net.Dial("unixgram", address) + if err != nil && strings.Contains(err.Error(), "protocol wrong type for socket") { + return net.Dial("unix", address) + } + return newConn, err + + case "unixgram": + return net.Dial("unixgram", address) + + case "unixstream": + return net.Dial("unix", address) + + default: + return net.Dial(network, address) + } + } + datadogClient.WriteLoop(ctx, ticker.C, conn.NewManager(dialer, network, address, time.After, logger)) }) } +// StopDatadog stops the Datadog metrics pusher. +func StopDatadog() { + if datadogLoopCancelFunc != nil { + datadogLoopCancelFunc() + datadogLoopCancelFunc = nil + } +} + func parseDatadogAddress(address string) (string, string) { network := "udp" @@ -122,6 +158,12 @@ func parseDatadogAddress(address string) (string, string) { case strings.HasPrefix(address, unixAddressPrefix): network = "unix" addr = address[len(unixAddressPrefix):] + case strings.HasPrefix(address, unixAddressDatagramPrefix): + network = "unixgram" + addr = address[len(unixAddressDatagramPrefix):] + case strings.HasPrefix(address, unixAddressStreamPrefix): + network = "unixstream" + addr = address[len(unixAddressStreamPrefix):] case address != "": addr = address default: @@ -130,11 +172,3 @@ func parseDatadogAddress(address string) (string, string) { return network, addr } - -// StopDatadog stops the Datadog metrics pusher. -func StopDatadog() { - if datadogLoopCancelFunc != nil { - datadogLoopCancelFunc() - datadogLoopCancelFunc = nil - } -} diff --git a/pkg/metrics/datadog_test.go b/pkg/metrics/datadog_test.go index 0b0dfd3df..0fc5bd758 100644 --- a/pkg/metrics/datadog_test.go +++ b/pkg/metrics/datadog_test.go @@ -64,6 +64,18 @@ func TestDatadog_parseDatadogAddress(t *testing.T) { expNetwork: "unix", expAddress: "/path/to/datadog.socket", }, + { + desc: "unixgram address", + address: "unixgram:///path/to/datadog.socket", + expNetwork: "unixgram", + expAddress: "/path/to/datadog.socket", + }, + { + desc: "unixstream address", + address: "unixstream:///path/to/datadog.socket", + expNetwork: "unixstream", + expAddress: "/path/to/datadog.socket", + }, } for _, test := range tests { diff --git a/pkg/middlewares/accesslog/logger.go b/pkg/middlewares/accesslog/logger.go index 20ee4659f..d6055795e 100644 --- a/pkg/middlewares/accesslog/logger.go +++ b/pkg/middlewares/accesslog/logger.go @@ -106,15 +106,28 @@ func NewHandler(config *types.AccessLog) (*Handler, error) { Level: logrus.InfoLevel, } - // Transform headers names in config to a canonical form, to be used as is without further transformations. - if config.Fields != nil && config.Fields.Headers != nil && len(config.Fields.Headers.Names) > 0 { - fields := map[string]string{} + // Transform header names to a canonical form, to be used as is without further transformations, + // and transform field names to lower case, to enable case-insensitive lookup. + if config.Fields != nil { + if len(config.Fields.Names) > 0 { + fields := map[string]string{} - for h, v := range config.Fields.Headers.Names { - fields[textproto.CanonicalMIMEHeaderKey(h)] = v + for h, v := range config.Fields.Names { + fields[strings.ToLower(h)] = v + } + + config.Fields.Names = fields } - config.Fields.Headers.Names = fields + if config.Fields.Headers != nil && len(config.Fields.Headers.Names) > 0 { + fields := map[string]string{} + + for h, v := range config.Fields.Headers.Names { + fields[textproto.CanonicalMIMEHeaderKey(h)] = v + } + + config.Fields.Headers.Names = fields + } } logHandler := &Handler{ @@ -184,16 +197,6 @@ func (h *Handler) ServeHTTP(rw http.ResponseWriter, req *http.Request, next http }, } - defer func() { - if h.config.BufferingSize > 0 { - h.logHandlerChan <- handlerParams{ - logDataTable: logDataTable, - } - return - } - h.logTheRoundTrip(logDataTable) - }() - reqWithDataTable := req.WithContext(context.WithValue(req.Context(), DataTableKey, logDataTable)) core[RequestCount] = nextRequestCount() @@ -238,19 +241,30 @@ func (h *Handler) ServeHTTP(rw http.ResponseWriter, req *http.Request, next http return } + defer func() { + logDataTable.DownstreamResponse = downstreamResponse{ + headers: rw.Header().Clone(), + } + + logDataTable.DownstreamResponse.status = capt.StatusCode() + logDataTable.DownstreamResponse.size = capt.ResponseSize() + logDataTable.Request.size = capt.RequestSize() + + if _, ok := core[ClientUsername]; !ok { + core[ClientUsername] = usernameIfPresent(reqWithDataTable.URL) + } + + if h.config.BufferingSize > 0 { + h.logHandlerChan <- handlerParams{ + logDataTable: logDataTable, + } + return + } + + h.logTheRoundTrip(logDataTable) + }() + next.ServeHTTP(rw, reqWithDataTable) - - if _, ok := core[ClientUsername]; !ok { - core[ClientUsername] = usernameIfPresent(reqWithDataTable.URL) - } - - logDataTable.DownstreamResponse = downstreamResponse{ - headers: rw.Header().Clone(), - } - - logDataTable.DownstreamResponse.status = capt.StatusCode() - logDataTable.DownstreamResponse.size = capt.ResponseSize() - logDataTable.Request.size = capt.RequestSize() } // Close closes the Logger (i.e. the file, drain logHandlerChan, etc). @@ -334,7 +348,7 @@ func (h *Handler) logTheRoundTrip(logDataTable *LogData) { fields := logrus.Fields{} for k, v := range logDataTable.Core { - if h.config.Fields.Keep(k) { + if h.config.Fields.Keep(strings.ToLower(k)) { fields[k] = v } } diff --git a/pkg/middlewares/accesslog/logger_test.go b/pkg/middlewares/accesslog/logger_test.go index ae72cd365..ba89e344c 100644 --- a/pkg/middlewares/accesslog/logger_test.go +++ b/pkg/middlewares/accesslog/logger_test.go @@ -2,6 +2,7 @@ package accesslog import ( "bytes" + "context" "crypto/tls" "crypto/x509" "crypto/x509/pkix" @@ -24,6 +25,7 @@ import ( "github.com/stretchr/testify/require" ptypes "github.com/traefik/paerser/types" "github.com/traefik/traefik/v3/pkg/middlewares/capture" + "github.com/traefik/traefik/v3/pkg/middlewares/recovery" "github.com/traefik/traefik/v3/pkg/types" ) @@ -164,7 +166,7 @@ func TestLoggerHeaderFields(t *testing.T) { }, }, { - desc: "with case insensitive match on header name", + desc: "with case-insensitive match on header name", header: "User-Agent", expected: types.AccessLogKeep, accessLogFields: types.AccessLogFields{ @@ -465,6 +467,32 @@ func TestLoggerJSON(t *testing.T) { RequestRefererHeader: assertString(testReferer), }, }, + { + desc: "fields and headers with unconventional letter case", + config: &types.AccessLog{ + FilePath: "", + Format: JSONFormat, + Fields: &types.AccessLogFields{ + DefaultMode: "drop", + Names: map[string]string{ + "rEqUeStHoSt": "keep", + }, + Headers: &types.FieldHeaders{ + DefaultMode: "drop", + Names: map[string]string{ + "ReFeReR": "keep", + }, + }, + }, + }, + expected: map[string]func(t *testing.T, value interface{}){ + RequestHost: assertString(testHostname), + "level": assertString("info"), + "msg": assertString(""), + "time": assertNotEmpty(), + RequestRefererHeader: assertString(testReferer), + }, + }, } for _, test := range testCases { @@ -496,6 +524,64 @@ func TestLoggerJSON(t *testing.T) { } } +func TestLogger_AbortedRequest(t *testing.T) { + expected := map[string]func(t *testing.T, value interface{}){ + RequestContentSize: assertFloat64(0), + RequestHost: assertString(testHostname), + RequestAddr: assertString(testHostname), + RequestMethod: assertString(testMethod), + RequestPath: assertString(""), + RequestProtocol: assertString(testProto), + RequestScheme: assertString(testScheme), + RequestPort: assertString("-"), + DownstreamStatus: assertFloat64(float64(200)), + DownstreamContentSize: assertFloat64(float64(40)), + RequestRefererHeader: assertString(testReferer), + RequestUserAgentHeader: assertString(testUserAgent), + ServiceURL: assertString("http://stream"), + ServiceAddr: assertString("127.0.0.1"), + ServiceName: assertString("stream"), + ClientUsername: assertString(testUsername), + ClientHost: assertString(testHostname), + ClientPort: assertString(strconv.Itoa(testPort)), + ClientAddr: assertString(fmt.Sprintf("%s:%d", testHostname, testPort)), + "level": assertString("info"), + "msg": assertString(""), + RequestCount: assertFloat64NotZero(), + Duration: assertFloat64NotZero(), + Overhead: assertFloat64NotZero(), + RetryAttempts: assertFloat64(float64(0)), + "time": assertNotEmpty(), + StartLocal: assertNotEmpty(), + StartUTC: assertNotEmpty(), + "downstream_Content-Type": assertString("text/plain"), + "downstream_Transfer-Encoding": assertString("chunked"), + "downstream_Cache-Control": assertString("no-cache"), + } + + config := &types.AccessLog{ + FilePath: filepath.Join(t.TempDir(), logFileNameSuffix), + Format: JSONFormat, + } + doLoggingWithAbortedStream(t, config) + + logData, err := os.ReadFile(config.FilePath) + require.NoError(t, err) + + jsonData := make(map[string]interface{}) + err = json.Unmarshal(logData, &jsonData) + require.NoError(t, err) + + assert.Equal(t, len(expected), len(jsonData)) + + for field, assertion := range expected { + assertion(t, jsonData[field]) + if t.Failed() { + return + } + } +} + func TestNewLogHandlerOutputStdout(t *testing.T) { testCases := []struct { desc string @@ -832,3 +918,89 @@ func logWriterTestHandlerFunc(rw http.ResponseWriter, r *http.Request) { rw.WriteHeader(testStatus) } + +func doLoggingWithAbortedStream(t *testing.T, config *types.AccessLog) { + t.Helper() + + logger, err := NewHandler(config) + require.NoError(t, err) + t.Cleanup(func() { + err := logger.Close() + require.NoError(t, err) + }) + + if config.FilePath != "" { + _, err = os.Stat(config.FilePath) + require.NoError(t, err, "logger should create "+config.FilePath) + } + + reqContext, cancelRequest := context.WithCancel(context.Background()) + + req := &http.Request{ + Header: map[string][]string{ + "User-Agent": {testUserAgent}, + "Referer": {testReferer}, + }, + Proto: testProto, + Host: testHostname, + Method: testMethod, + RemoteAddr: fmt.Sprintf("%s:%d", testHostname, testPort), + URL: &url.URL{ + User: url.UserPassword(testUsername, ""), + }, + Body: nil, + } + + req = req.WithContext(reqContext) + + chain := alice.New() + chain = chain.Append(func(next http.Handler) (http.Handler, error) { + return recovery.New(context.Background(), next) + }) + chain = chain.Append(capture.Wrap) + chain = chain.Append(WrapHandler(logger)) + + service := NewFieldHandler(http.HandlerFunc(streamBackend), ServiceURL, "http://stream", nil) + service = NewFieldHandler(service, ServiceAddr, "127.0.0.1", nil) + service = NewFieldHandler(service, ServiceName, "stream", AddServiceFields) + + handler, err := chain.Then(service) + require.NoError(t, err) + + go func() { + time.Sleep(499 * time.Millisecond) + cancelRequest() + }() + + handler.ServeHTTP(httptest.NewRecorder(), req) +} + +func streamBackend(rw http.ResponseWriter, r *http.Request) { + // Get the Flusher to flush the response to the client + flusher, ok := rw.(http.Flusher) + if !ok { + http.Error(rw, "Streaming unsupported!", http.StatusInternalServerError) + return + } + + // Set the headers for streaming + rw.Header().Set("Content-Type", "text/plain") + rw.Header().Set("Transfer-Encoding", "chunked") + rw.Header().Set("Cache-Control", "no-cache") + + for { + time.Sleep(100 * time.Millisecond) + + select { + case <-r.Context().Done(): + panic(http.ErrAbortHandler) + + default: + if _, err := fmt.Fprint(rw, "FOOBAR!!!!"); err != nil { + http.Error(rw, err.Error(), http.StatusInternalServerError) + return + } + flusher.Flush() + } + } +} diff --git a/pkg/middlewares/connectionheader/connectionheader.go b/pkg/middlewares/auth/connectionheader.go similarity index 97% rename from pkg/middlewares/connectionheader/connectionheader.go rename to pkg/middlewares/auth/connectionheader.go index 12a994f17..30d3ab87c 100644 --- a/pkg/middlewares/connectionheader/connectionheader.go +++ b/pkg/middlewares/auth/connectionheader.go @@ -1,4 +1,4 @@ -package connectionheader +package auth import ( "net/http" diff --git a/pkg/middlewares/connectionheader/connectionheader_test.go b/pkg/middlewares/auth/connectionheader_test.go similarity index 98% rename from pkg/middlewares/connectionheader/connectionheader_test.go rename to pkg/middlewares/auth/connectionheader_test.go index bd41d58d8..00d719ef0 100644 --- a/pkg/middlewares/connectionheader/connectionheader_test.go +++ b/pkg/middlewares/auth/connectionheader_test.go @@ -1,4 +1,4 @@ -package connectionheader +package auth import ( "net/http" diff --git a/pkg/middlewares/auth/forward.go b/pkg/middlewares/auth/forward.go index fde452954..309511ef2 100644 --- a/pkg/middlewares/auth/forward.go +++ b/pkg/middlewares/auth/forward.go @@ -14,7 +14,6 @@ import ( "github.com/traefik/traefik/v3/pkg/config/dynamic" "github.com/traefik/traefik/v3/pkg/middlewares" "github.com/traefik/traefik/v3/pkg/middlewares/accesslog" - "github.com/traefik/traefik/v3/pkg/middlewares/connectionheader" "github.com/traefik/traefik/v3/pkg/middlewares/observability" "github.com/traefik/traefik/v3/pkg/tracing" "github.com/traefik/traefik/v3/pkg/types" @@ -124,7 +123,7 @@ func (fa *forwardAuth) GetTracingInformation() (string, string, trace.SpanKind) func (fa *forwardAuth) ServeHTTP(rw http.ResponseWriter, req *http.Request) { logger := middlewares.GetLogger(req.Context(), fa.name, typeNameForward) - req = connectionheader.Remove(req) + req = Remove(req) forwardReq, err := http.NewRequestWithContext(req.Context(), http.MethodGet, fa.address, nil) if err != nil { diff --git a/pkg/middlewares/customerrors/custom_errors.go b/pkg/middlewares/customerrors/custom_errors.go index b985b6326..c5a1c6f22 100644 --- a/pkg/middlewares/customerrors/custom_errors.go +++ b/pkg/middlewares/customerrors/custom_errors.go @@ -235,7 +235,7 @@ func (cc *codeCatcher) Flush() { // since we want to serve the ones from the error page, // so we just don't flush. // (e.g., To prevent superfluous WriteHeader on request with a - // `Transfert-Encoding: chunked` header). + // `Transfer-Encoding: chunked` header). if cc.caughtFilteredCode { return } diff --git a/pkg/middlewares/forwardedheaders/forwarded_header.go b/pkg/middlewares/forwardedheaders/forwarded_header.go index 3f4e32301..f03bea9ab 100644 --- a/pkg/middlewares/forwardedheaders/forwarded_header.go +++ b/pkg/middlewares/forwardedheaders/forwarded_header.go @@ -3,10 +3,13 @@ package forwardedheaders import ( "net" "net/http" + "net/textproto" "os" + "slices" "strings" "github.com/traefik/traefik/v3/pkg/ip" + "golang.org/x/net/http/httpguts" ) const ( @@ -42,19 +45,20 @@ var xHeaders = []string{ // Unless insecure is set, // it first removes all the existing values for those headers if the remote address is not one of the trusted ones. type XForwarded struct { - insecure bool - trustedIps []string - ipChecker *ip.Checker - next http.Handler - hostname string + insecure bool + trustedIPs []string + connectionHeaders []string + ipChecker *ip.Checker + next http.Handler + hostname string } // NewXForwarded creates a new XForwarded. -func NewXForwarded(insecure bool, trustedIps []string, next http.Handler) (*XForwarded, error) { +func NewXForwarded(insecure bool, trustedIPs []string, connectionHeaders []string, next http.Handler) (*XForwarded, error) { var ipChecker *ip.Checker - if len(trustedIps) > 0 { + if len(trustedIPs) > 0 { var err error - ipChecker, err = ip.NewChecker(trustedIps) + ipChecker, err = ip.NewChecker(trustedIPs) if err != nil { return nil, err } @@ -66,11 +70,12 @@ func NewXForwarded(insecure bool, trustedIps []string, next http.Handler) (*XFor } return &XForwarded{ - insecure: insecure, - trustedIps: trustedIps, - ipChecker: ipChecker, - next: next, - hostname: hostname, + insecure: insecure, + trustedIPs: trustedIPs, + connectionHeaders: connectionHeaders, + ipChecker: ipChecker, + next: next, + hostname: hostname, }, nil } @@ -189,9 +194,53 @@ func (x *XForwarded) ServeHTTP(w http.ResponseWriter, r *http.Request) { x.rewrite(r) + x.removeConnectionHeaders(r) + x.next.ServeHTTP(w, r) } +func (x *XForwarded) removeConnectionHeaders(req *http.Request) { + var reqUpType string + if httpguts.HeaderValuesContainsToken(req.Header[connection], upgrade) { + reqUpType = unsafeHeader(req.Header).Get(upgrade) + } + + var connectionHopByHopHeaders []string + for _, f := range req.Header[connection] { + for _, sf := range strings.Split(f, ",") { + if sf = textproto.TrimString(sf); sf != "" { + // Connection header cannot dictate to remove X- headers managed by Traefik, + // as per rfc7230 https://datatracker.ietf.org/doc/html/rfc7230#section-6.1, + // A proxy or gateway MUST ... and then remove the Connection header field itself + // (or replace it with the intermediary's own connection options for the forwarded message). + if slices.Contains(xHeaders, sf) { + continue + } + + // Keep headers allowed through the middleware chain. + if slices.Contains(x.connectionHeaders, sf) { + connectionHopByHopHeaders = append(connectionHopByHopHeaders, sf) + continue + } + + // Apply Connection header option. + req.Header.Del(sf) + } + } + } + + if reqUpType != "" { + connectionHopByHopHeaders = append(connectionHopByHopHeaders, upgrade) + unsafeHeader(req.Header).Set(upgrade, reqUpType) + } + if len(connectionHopByHopHeaders) > 0 { + unsafeHeader(req.Header).Set(connection, strings.Join(connectionHopByHopHeaders, ",")) + return + } + + unsafeHeader(req.Header).Del(connection) +} + // unsafeHeader allows to manage Header values. // Must be used only when the header name is already a canonical key. type unsafeHeader map[string][]string diff --git a/pkg/middlewares/forwardedheaders/forwarded_header_test.go b/pkg/middlewares/forwardedheaders/forwarded_header_test.go index 8e1d10925..414fd5007 100644 --- a/pkg/middlewares/forwardedheaders/forwarded_header_test.go +++ b/pkg/middlewares/forwardedheaders/forwarded_header_test.go @@ -12,15 +12,16 @@ import ( func TestServeHTTP(t *testing.T) { testCases := []struct { - desc string - insecure bool - trustedIps []string - incomingHeaders map[string][]string - remoteAddr string - expectedHeaders map[string]string - tls bool - websocket bool - host string + desc string + insecure bool + trustedIps []string + connectionHeaders []string + incomingHeaders map[string][]string + remoteAddr string + expectedHeaders map[string]string + tls bool + websocket bool + host string }{ { desc: "all Empty", @@ -269,6 +270,196 @@ func TestServeHTTP(t *testing.T) { xForwardedServer: "foo.com:8080", }, }, + { + desc: "Untrusted: Connection header has no effect on X- forwarded headers", + insecure: false, + incomingHeaders: map[string][]string{ + connection: { + xForwardedProto, + xForwardedFor, + xForwardedURI, + xForwardedMethod, + xForwardedHost, + xForwardedPort, + xForwardedTLSClientCert, + xForwardedTLSClientCertInfo, + xRealIP, + }, + xForwardedProto: {"foo"}, + xForwardedFor: {"foo"}, + xForwardedURI: {"foo"}, + xForwardedMethod: {"foo"}, + xForwardedHost: {"foo"}, + xForwardedPort: {"foo"}, + xForwardedTLSClientCert: {"foo"}, + xForwardedTLSClientCertInfo: {"foo"}, + xRealIP: {"foo"}, + }, + expectedHeaders: map[string]string{ + xForwardedProto: "http", + xForwardedFor: "", + xForwardedURI: "", + xForwardedMethod: "", + xForwardedHost: "", + xForwardedPort: "80", + xForwardedTLSClientCert: "", + xForwardedTLSClientCertInfo: "", + xRealIP: "", + connection: "", + }, + }, + { + desc: "Trusted (insecure): Connection header has no effect on X- forwarded headers", + insecure: true, + incomingHeaders: map[string][]string{ + connection: { + xForwardedProto, + xForwardedFor, + xForwardedURI, + xForwardedMethod, + xForwardedHost, + xForwardedPort, + xForwardedTLSClientCert, + xForwardedTLSClientCertInfo, + xRealIP, + }, + xForwardedProto: {"foo"}, + xForwardedFor: {"foo"}, + xForwardedURI: {"foo"}, + xForwardedMethod: {"foo"}, + xForwardedHost: {"foo"}, + xForwardedPort: {"foo"}, + xForwardedTLSClientCert: {"foo"}, + xForwardedTLSClientCertInfo: {"foo"}, + xRealIP: {"foo"}, + }, + expectedHeaders: map[string]string{ + xForwardedProto: "foo", + xForwardedFor: "foo", + xForwardedURI: "foo", + xForwardedMethod: "foo", + xForwardedHost: "foo", + xForwardedPort: "foo", + xForwardedTLSClientCert: "foo", + xForwardedTLSClientCertInfo: "foo", + xRealIP: "foo", + connection: "", + }, + }, + { + desc: "Untrusted and Connection: Connection header has no effect on X- forwarded headers", + insecure: false, + connectionHeaders: []string{ + xForwardedProto, + xForwardedFor, + xForwardedURI, + xForwardedMethod, + xForwardedHost, + xForwardedPort, + xForwardedTLSClientCert, + xForwardedTLSClientCertInfo, + xRealIP, + }, + incomingHeaders: map[string][]string{ + connection: { + xForwardedProto, + xForwardedFor, + xForwardedURI, + xForwardedMethod, + xForwardedHost, + xForwardedPort, + xForwardedTLSClientCert, + xForwardedTLSClientCertInfo, + xRealIP, + }, + xForwardedProto: {"foo"}, + xForwardedFor: {"foo"}, + xForwardedURI: {"foo"}, + xForwardedMethod: {"foo"}, + xForwardedHost: {"foo"}, + xForwardedPort: {"foo"}, + xForwardedTLSClientCert: {"foo"}, + xForwardedTLSClientCertInfo: {"foo"}, + xRealIP: {"foo"}, + }, + expectedHeaders: map[string]string{ + xForwardedProto: "http", + xForwardedFor: "", + xForwardedURI: "", + xForwardedMethod: "", + xForwardedHost: "", + xForwardedPort: "80", + xForwardedTLSClientCert: "", + xForwardedTLSClientCertInfo: "", + xRealIP: "", + connection: "", + }, + }, + { + desc: "Trusted (insecure) and Connection: Connection header has no effect on X- forwarded headers", + insecure: true, + connectionHeaders: []string{ + xForwardedProto, + xForwardedFor, + xForwardedURI, + xForwardedMethod, + xForwardedHost, + xForwardedPort, + xForwardedTLSClientCert, + xForwardedTLSClientCertInfo, + xRealIP, + }, + incomingHeaders: map[string][]string{ + connection: { + xForwardedProto, + xForwardedFor, + xForwardedURI, + xForwardedMethod, + xForwardedHost, + xForwardedPort, + xForwardedTLSClientCert, + xForwardedTLSClientCertInfo, + xRealIP, + }, + xForwardedProto: {"foo"}, + xForwardedFor: {"foo"}, + xForwardedURI: {"foo"}, + xForwardedMethod: {"foo"}, + xForwardedHost: {"foo"}, + xForwardedPort: {"foo"}, + xForwardedTLSClientCert: {"foo"}, + xForwardedTLSClientCertInfo: {"foo"}, + xRealIP: {"foo"}, + }, + expectedHeaders: map[string]string{ + xForwardedProto: "foo", + xForwardedFor: "foo", + xForwardedURI: "foo", + xForwardedMethod: "foo", + xForwardedHost: "foo", + xForwardedPort: "foo", + xForwardedTLSClientCert: "foo", + xForwardedTLSClientCertInfo: "foo", + xRealIP: "foo", + connection: "", + }, + }, + { + desc: "Connection: one remove, and one passthrough header", + connectionHeaders: []string{ + "foo", + }, + incomingHeaders: map[string][]string{ + connection: { + "foo", + }, + "Foo": {"bar"}, + "Bar": {"foo"}, + }, + expectedHeaders: map[string]string{ + "Bar": "foo", + }, + }, } for _, test := range testCases { @@ -299,7 +490,7 @@ func TestServeHTTP(t *testing.T) { } } - m, err := NewXForwarded(test.insecure, test.trustedIps, + m, err := NewXForwarded(test.insecure, test.trustedIps, test.connectionHeaders, http.HandlerFunc(func(_ http.ResponseWriter, _ *http.Request) {})) require.NoError(t, err) @@ -382,3 +573,74 @@ func Test_isWebsocketRequest(t *testing.T) { }) } } + +func TestConnection(t *testing.T) { + testCases := []struct { + desc string + reqHeaders map[string]string + connectionHeaders []string + expected http.Header + }{ + { + desc: "simple remove", + reqHeaders: map[string]string{ + "Foo": "bar", + connection: "foo", + }, + expected: http.Header{}, + }, + { + desc: "remove and upgrade", + reqHeaders: map[string]string{ + upgrade: "test", + "Foo": "bar", + connection: "upgrade,foo", + }, + expected: http.Header{ + upgrade: []string{"test"}, + connection: []string{"Upgrade"}, + }, + }, + { + desc: "no remove", + reqHeaders: map[string]string{ + "Foo": "bar", + connection: "fii", + }, + expected: http.Header{ + "Foo": []string{"bar"}, + }, + }, + { + desc: "no remove because connection header pass through", + reqHeaders: map[string]string{ + "Foo": "bar", + connection: "Foo", + }, + connectionHeaders: []string{"Foo"}, + expected: http.Header{ + "Foo": []string{"bar"}, + connection: []string{"Foo"}, + }, + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + forwarded, err := NewXForwarded(true, nil, test.connectionHeaders, nil) + require.NoError(t, err) + + req := httptest.NewRequest(http.MethodGet, "https://localhost", nil) + + for k, v := range test.reqHeaders { + req.Header.Set(k, v) + } + + forwarded.removeConnectionHeaders(req) + + assert.Equal(t, test.expected, req.Header) + }) + } +} diff --git a/pkg/middlewares/headers/headers.go b/pkg/middlewares/headers/headers.go index e393aa1a6..861d1066d 100644 --- a/pkg/middlewares/headers/headers.go +++ b/pkg/middlewares/headers/headers.go @@ -8,7 +8,6 @@ import ( "github.com/traefik/traefik/v3/pkg/config/dynamic" "github.com/traefik/traefik/v3/pkg/middlewares" - "github.com/traefik/traefik/v3/pkg/middlewares/connectionheader" "go.opentelemetry.io/otel/trace" ) @@ -46,12 +45,11 @@ func New(ctx context.Context, next http.Handler, cfg dynamic.Headers, name strin if hasCustomHeaders || hasCorsHeaders { logger.Debug().Msgf("Setting up customHeaders/Cors from %v", cfg) - h, err := NewHeader(nextHandler, cfg) + var err error + handler, err = NewHeader(nextHandler, cfg) if err != nil { return nil, err } - - handler = connectionheader.Remover(h) } return &headers{ diff --git a/pkg/middlewares/ratelimiter/rate_limiter.go b/pkg/middlewares/ratelimiter/rate_limiter.go index 0b1a6aaa1..d22b99a42 100644 --- a/pkg/middlewares/ratelimiter/rate_limiter.go +++ b/pkg/middlewares/ratelimiter/rate_limiter.go @@ -149,7 +149,7 @@ func (rl *rateLimiter) ServeHTTP(rw http.ResponseWriter, req *http.Request) { } // We Set even in the case where the source already exists, - // because we want to update the expiryTime everytime we get the source, + // because we want to update the expiryTime every time we get the source, // as the expiryTime is supposed to reflect the activity (or lack thereof) on that source. if err := rl.buckets.Set(source, bucket, rl.ttl); err != nil { logger.Error().Err(err).Msg("Could not insert/update bucket") diff --git a/pkg/plugins/middlewarewasm.go b/pkg/plugins/middlewarewasm.go index a9f4f5ada..f8dc7d1bf 100644 --- a/pkg/plugins/middlewarewasm.go +++ b/pkg/plugins/middlewarewasm.go @@ -12,7 +12,6 @@ import ( "github.com/http-wasm/http-wasm-host-go/handler" wasm "github.com/http-wasm/http-wasm-host-go/handler/nethttp" - "github.com/juliens/wasm-goexport/host" "github.com/tetratelabs/wazero" "github.com/traefik/traefik/v3/pkg/logs" "github.com/traefik/traefik/v3/pkg/middlewares" @@ -67,7 +66,7 @@ func (b *wasmMiddlewareBuilder) buildMiddleware(ctx context.Context, next http.H return nil, nil, fmt.Errorf("loading binary: %w", err) } - rt := host.NewRuntime(wazero.NewRuntimeWithConfig(ctx, wazero.NewRuntimeConfig().WithCompilationCache(b.cache))) + rt := wazero.NewRuntimeWithConfig(ctx, wazero.NewRuntimeConfig().WithCompilationCache(b.cache)) guestModule, err := rt.CompileModule(ctx, code) if err != nil { @@ -81,7 +80,7 @@ func (b *wasmMiddlewareBuilder) buildMiddleware(ctx context.Context, next http.H logger := middlewares.GetLogger(ctx, middlewareName, "wasm") - config := wazero.NewModuleConfig().WithSysWalltime() + config := wazero.NewModuleConfig().WithSysWalltime().WithStartFunctions("_start", "_initialize") for _, env := range b.settings.Envs { config.WithEnv(env, os.Getenv(env)) } diff --git a/pkg/provider/acme/provider.go b/pkg/provider/acme/provider.go index 912b4073e..74a86d0ef 100644 --- a/pkg/provider/acme/provider.go +++ b/pkg/provider/acme/provider.go @@ -801,7 +801,7 @@ func deleteUnnecessaryDomains(ctx context.Context, domains []types.Domain) []typ } // Check if CN or SANS to check already exists - // or can not be checked by a wildcard + // or cannot be checked by a wildcard var newDomainsToCheck []string for _, domainProcessed := range domainToCheck.ToStrArray() { if idxDomain < idxDomainToCheck && isDomainAlreadyChecked(domainProcessed, domain.ToStrArray()) { diff --git a/pkg/provider/docker/builder_test.go b/pkg/provider/docker/builder_test.go index f8723c74e..1213b7c01 100644 --- a/pkg/provider/docker/builder_test.go +++ b/pkg/provider/docker/builder_test.go @@ -205,7 +205,7 @@ func withEndpointSpec(ops ...func(*swarm.EndpointSpec)) func(*swarm.Service) { } } -func modeDNSSR(spec *swarm.EndpointSpec) { +func modeDNSRR(spec *swarm.EndpointSpec) { spec.Mode = swarm.ResolutionModeDNSRR } diff --git a/pkg/provider/docker/config_test.go b/pkg/provider/docker/config_test.go index 114a00076..2d4d151c0 100644 --- a/pkg/provider/docker/config_test.go +++ b/pkg/provider/docker/config_test.go @@ -3976,7 +3976,7 @@ func TestDynConfBuilder_getIPAddress_swarm(t *testing.T) { networks map[string]*network.Summary }{ { - service: swarmService(withEndpointSpec(modeDNSSR)), + service: swarmService(withEndpointSpec(modeDNSRR)), expected: "", networks: map[string]*network.Summary{}, }, diff --git a/pkg/provider/docker/pswarm_test.go b/pkg/provider/docker/pswarm_test.go index f1cb4b27e..c9fae5f12 100644 --- a/pkg/provider/docker/pswarm_test.go +++ b/pkg/provider/docker/pswarm_test.go @@ -114,7 +114,7 @@ func TestSwarmProvider_listServices(t *testing.T) { "traefik.docker.network": "barnet", "traefik.docker.LBSwarm": "true", }), - withEndpointSpec(modeDNSSR)), + withEndpointSpec(modeDNSRR)), }, dockerVersion: "1.30", networks: []network.Summary{}, @@ -140,7 +140,7 @@ func TestSwarmProvider_listServices(t *testing.T) { "traefik.docker.network": "barnet", "traefik.docker.LBSwarm": "true", }), - withEndpointSpec(modeDNSSR)), + withEndpointSpec(modeDNSRR)), }, dockerVersion: "1.30", networks: []network.Summary{ @@ -185,7 +185,7 @@ func TestSwarmProvider_listServices(t *testing.T) { serviceLabels(map[string]string{ "traefik.docker.network": "barnet", }), - withEndpointSpec(modeDNSSR)), + withEndpointSpec(modeDNSRR)), }, tasks: []swarm.Task{ swarmTask("id1", diff --git a/pkg/provider/docker/shared_test.go b/pkg/provider/docker/shared_test.go index 630a7fee9..0f542d00a 100644 --- a/pkg/provider/docker/shared_test.go +++ b/pkg/provider/docker/shared_test.go @@ -86,7 +86,7 @@ func Test_getPort_swarm(t *testing.T) { }{ { service: swarmService( - withEndpointSpec(modeDNSSR), + withEndpointSpec(modeDNSRR), ), networks: map[string]*docker.NetworkResource{}, serverPort: "8080", diff --git a/pkg/provider/kubernetes/gateway/client.go b/pkg/provider/kubernetes/gateway/client.go index b318411d3..638760762 100644 --- a/pkg/provider/kubernetes/gateway/client.go +++ b/pkg/provider/kubernetes/gateway/client.go @@ -25,6 +25,7 @@ import ( "k8s.io/client-go/util/retry" gatev1 "sigs.k8s.io/gateway-api/apis/v1" gatev1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" + gatev1alpha3 "sigs.k8s.io/gateway-api/apis/v1alpha3" gatev1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" gateclientset "sigs.k8s.io/gateway-api/pkg/client/clientset/versioned" gateinformers "sigs.k8s.io/gateway-api/pkg/client/informers/externalversions" @@ -48,30 +49,6 @@ func (reh *resourceEventHandler) OnDelete(obj interface{}) { eventHandlerFunc(reh.ev, obj) } -// Client is a client for the Provider master. -// WatchAll starts the watch of the Provider resources and updates the stores. -// The stores can then be accessed via the Get* functions. -type Client interface { - WatchAll(namespaces []string, stopCh <-chan struct{}) (<-chan interface{}, error) - UpdateGatewayStatus(ctx context.Context, gateway ktypes.NamespacedName, status gatev1.GatewayStatus) error - UpdateGatewayClassStatus(ctx context.Context, name string, status gatev1.GatewayClassStatus) error - UpdateHTTPRouteStatus(ctx context.Context, route ktypes.NamespacedName, status gatev1.HTTPRouteStatus) error - UpdateGRPCRouteStatus(ctx context.Context, route ktypes.NamespacedName, status gatev1.GRPCRouteStatus) error - UpdateTCPRouteStatus(ctx context.Context, route ktypes.NamespacedName, status gatev1alpha2.TCPRouteStatus) error - UpdateTLSRouteStatus(ctx context.Context, route ktypes.NamespacedName, status gatev1alpha2.TLSRouteStatus) error - ListGatewayClasses() ([]*gatev1.GatewayClass, error) - ListGateways() []*gatev1.Gateway - ListHTTPRoutes() ([]*gatev1.HTTPRoute, error) - ListGRPCRoutes() ([]*gatev1.GRPCRoute, error) - ListTCPRoutes() ([]*gatev1alpha2.TCPRoute, error) - ListTLSRoutes() ([]*gatev1alpha2.TLSRoute, error) - ListNamespaces(selector labels.Selector) ([]string, error) - ListReferenceGrants(namespace string) ([]*gatev1beta1.ReferenceGrant, error) - ListEndpointSlicesForService(namespace, serviceName string) ([]*discoveryv1.EndpointSlice, error) - GetService(namespace, name string) (*corev1.Service, bool, error) - GetSecret(namespace, name string) (*corev1.Secret, bool, error) -} - type clientWrapper struct { csGateway gateclientset.Interface csKube kclientset.Interface @@ -198,6 +175,16 @@ func (c *clientWrapper) WatchAll(namespaces []string, stopCh <-chan struct{}) (< } for _, ns := range namespaces { + factoryKube := kinformers.NewSharedInformerFactoryWithOptions(c.csKube, resyncPeriod, kinformers.WithNamespace(ns)) + _, err = factoryKube.Core().V1().Services().Informer().AddEventHandler(eventHandler) + if err != nil { + return nil, err + } + _, err = factoryKube.Discovery().V1().EndpointSlices().Informer().AddEventHandler(eventHandler) + if err != nil { + return nil, err + } + factoryGateway := gateinformers.NewSharedInformerFactoryWithOptions(c.csGateway, resyncPeriod, gateinformers.WithNamespace(ns)) _, err = factoryGateway.Gateway().V1().Gateways().Informer().AddEventHandler(eventHandler) if err != nil { @@ -225,16 +212,14 @@ func (c *clientWrapper) WatchAll(namespaces []string, stopCh <-chan struct{}) (< if err != nil { return nil, err } - } - - factoryKube := kinformers.NewSharedInformerFactoryWithOptions(c.csKube, resyncPeriod, kinformers.WithNamespace(ns)) - _, err = factoryKube.Core().V1().Services().Informer().AddEventHandler(eventHandler) - if err != nil { - return nil, err - } - _, err = factoryKube.Discovery().V1().EndpointSlices().Informer().AddEventHandler(eventHandler) - if err != nil { - return nil, err + _, err = factoryGateway.Gateway().V1alpha3().BackendTLSPolicies().Informer().AddEventHandler(eventHandler) + if err != nil { + return nil, err + } + _, err = factoryKube.Core().V1().ConfigMaps().Informer().AddEventHandler(eventHandler) + if err != nil { + return nil, err + } } factorySecret := kinformers.NewSharedInformerFactoryWithOptions(c.csKube, resyncPeriod, kinformers.WithNamespace(ns), kinformers.WithTweakListOptions(notOwnedByHelm)) @@ -367,8 +352,6 @@ func (c *clientWrapper) ListTLSRoutes() ([]*gatev1alpha2.TLSRoute, error) { func (c *clientWrapper) ListReferenceGrants(namespace string) ([]*gatev1beta1.ReferenceGrant, error) { if !c.isWatchedNamespace(namespace) { - log.Warn().Msgf("Failed to get ReferenceGrants: %q is not within watched namespaces", namespace) - return nil, fmt.Errorf("failed to get ReferenceGrants: namespace %s is not within watched namespaces", namespace) } @@ -424,7 +407,7 @@ func (c *clientWrapper) UpdateGatewayClassStatus(ctx context.Context, name strin return nil }) if err != nil { - return fmt.Errorf("failed to update GatewayClass %q status: %w", name, err) + return fmt.Errorf("failed to update GatewayClass %s status: %w", name, err) } return nil @@ -459,7 +442,7 @@ func (c *clientWrapper) UpdateGatewayStatus(ctx context.Context, gateway ktypes. return nil }) if err != nil { - return fmt.Errorf("failed to update Gateway %q status: %w", gateway.Name, err) + return fmt.Errorf("failed to update Gateway %s/%s status: %w", gateway.Namespace, gateway.Name, err) } return nil @@ -486,7 +469,6 @@ func (c *clientWrapper) UpdateHTTPRouteStatus(ctx context.Context, route ktypes. for _, parentStatus := range currentRoute.Status.Parents { if parentStatus.ControllerName != controllerName { parentStatuses = append(parentStatuses, parentStatus) - continue } } @@ -511,7 +493,7 @@ func (c *clientWrapper) UpdateHTTPRouteStatus(ctx context.Context, route ktypes. return nil }) if err != nil { - return fmt.Errorf("failed to update HTTPRoute %q status: %w", route.Name, err) + return fmt.Errorf("failed to update HTTPRoute %s/%s status: %w", route.Namespace, route.Name, err) } return nil @@ -538,7 +520,6 @@ func (c *clientWrapper) UpdateGRPCRouteStatus(ctx context.Context, route ktypes. for _, parentStatus := range currentRoute.Status.Parents { if parentStatus.ControllerName != controllerName { parentStatuses = append(parentStatuses, parentStatus) - continue } } @@ -590,7 +571,6 @@ func (c *clientWrapper) UpdateTCPRouteStatus(ctx context.Context, route ktypes.N for _, parentStatus := range currentRoute.Status.Parents { if parentStatus.ControllerName != controllerName { parentStatuses = append(parentStatuses, parentStatus) - continue } } @@ -615,7 +595,7 @@ func (c *clientWrapper) UpdateTCPRouteStatus(ctx context.Context, route ktypes.N return nil }) if err != nil { - return fmt.Errorf("failed to update TCPRoute %q status: %w", route.Name, err) + return fmt.Errorf("failed to update TCPRoute %s/%s status: %w", route.Namespace, route.Name, err) } return nil @@ -642,7 +622,6 @@ func (c *clientWrapper) UpdateTLSRouteStatus(ctx context.Context, route ktypes.N for _, parentStatus := range currentRoute.Status.Parents { if parentStatus.ControllerName != controllerName { parentStatuses = append(parentStatuses, parentStatus) - continue } } @@ -667,7 +646,69 @@ func (c *clientWrapper) UpdateTLSRouteStatus(ctx context.Context, route ktypes.N return nil }) if err != nil { - return fmt.Errorf("failed to update TLSRoute %q status: %w", route.Name, err) + return fmt.Errorf("failed to update TLSRoute %s/%s status: %w", route.Namespace, route.Name, err) + } + + return nil +} + +func (c *clientWrapper) UpdateBackendTLSPolicyStatus(ctx context.Context, policy ktypes.NamespacedName, status gatev1alpha2.PolicyStatus) error { + if !c.isWatchedNamespace(policy.Namespace) { + return fmt.Errorf("updating BackendTLSPolicy status %s/%s: namespace is not within watched namespaces", policy.Namespace, policy.Name) + } + + err := retry.RetryOnConflict(retry.DefaultRetry, func() error { + currentPolicy, err := c.factoriesGateway[c.lookupNamespace(policy.Namespace)].Gateway().V1alpha3().BackendTLSPolicies().Lister().BackendTLSPolicies(policy.Namespace).Get(policy.Name) + if err != nil { + // We have to return err itself here (not wrapped inside another error) + // so that RetryOnConflict can identify it correctly. + return err + } + + ancestorStatuses := make([]gatev1alpha2.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 { + if ancestorStatus.ControllerName != controllerName { + ancestorStatuses = append(ancestorStatuses, ancestorStatus) + continue + } + + if slices.ContainsFunc(status.Ancestors, func(status gatev1alpha2.PolicyAncestorStatus) bool { + return reflect.DeepEqual(ancestorStatus.AncestorRef, status.AncestorRef) + }) { + continue + } + + 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. + if policyAncestorStatusesEqual(currentPolicy.Status.Ancestors, ancestorStatuses) { + return nil + } + + currentPolicy = currentPolicy.DeepCopy() + currentPolicy.Status = gatev1alpha2.PolicyStatus{ + Ancestors: ancestorStatuses, + } + + if _, err = c.csGateway.GatewayV1alpha3().BackendTLSPolicies(policy.Namespace).UpdateStatus(ctx, currentPolicy, metav1.UpdateOptions{}); err != nil { + // We have to return err itself here (not wrapped inside another error) + // so that RetryOnConflict can identify it correctly. + return err + } + + return nil + }) + if err != nil { + return fmt.Errorf("failed to update BackendTLSPolicy %s/%s status: %w", policy.Namespace, policy.Name, err) } return nil @@ -701,6 +742,32 @@ func (c *clientWrapper) ListEndpointSlicesForService(namespace, serviceName stri return c.factoriesKube[c.lookupNamespace(namespace)].Discovery().V1().EndpointSlices().Lister().EndpointSlices(namespace).List(serviceSelector) } +// ListBackendTLSPoliciesForService returns the BackendTLSPolicy for the given service name in the given namespace. +func (c *clientWrapper) ListBackendTLSPoliciesForService(namespace, serviceName string) ([]*gatev1alpha3.BackendTLSPolicy, error) { + if !c.isWatchedNamespace(namespace) { + return nil, fmt.Errorf("failed to get BackendTLSPolicies for service %s/%s: namespace is not within watched namespaces", namespace, serviceName) + } + + policies, err := c.factoriesGateway[c.lookupNamespace(namespace)].Gateway().V1alpha3().BackendTLSPolicies().Lister().BackendTLSPolicies(namespace).List(labels.Everything()) + if err != nil { + return nil, fmt.Errorf("failed to list BackendTLSPolicies in namespace %s", namespace) + } + + var servicePolicies []*gatev1alpha3.BackendTLSPolicy + for _, policy := range policies { + for _, ref := range policy.Spec.TargetRefs { + // The policy does not target the service. + if ref.Group != groupCore || ref.Kind != kindService || string(ref.Name) != serviceName { + continue + } + + servicePolicies = append(servicePolicies, policy) + } + } + + return servicePolicies, nil +} + // GetSecret returns the named secret from the given namespace. func (c *clientWrapper) GetSecret(namespace, name string) (*corev1.Secret, bool, error) { if !c.isWatchedNamespace(namespace) { @@ -713,6 +780,18 @@ func (c *clientWrapper) GetSecret(namespace, name string) (*corev1.Secret, bool, return secret, exist, err } +// GetConfigMap returns the named configMap from the given namespace. +func (c *clientWrapper) GetConfigMap(namespace, name string) (*corev1.ConfigMap, bool, error) { + if !c.isWatchedNamespace(namespace) { + return nil, false, fmt.Errorf("failed to get configMap %s/%s: namespace is not within watched namespaces", namespace, name) + } + + configMap, err := c.factoriesKube[c.lookupNamespace(namespace)].Core().V1().ConfigMaps().Lister().ConfigMaps(namespace).Get(name) + exist, err := translateNotFoundError(err) + + return configMap, exist, err +} + // lookupNamespace returns the lookup namespace key for the given namespace. // When listening on all namespaces, it returns the client-go identifier ("") // for all-namespaces. Otherwise, it returns the given namespace. @@ -761,6 +840,36 @@ func gatewayStatusEqual(statusA, statusB gatev1.GatewayStatus) bool { conditionsEqual(statusA.Conditions, statusB.Conditions) } +func policyAncestorStatusesEqual(policyAncestorStatusesA, policyAncestorStatusesB []gatev1alpha2.PolicyAncestorStatus) bool { + if len(policyAncestorStatusesA) != len(policyAncestorStatusesB) { + return false + } + + for _, sA := range policyAncestorStatusesA { + if !slices.ContainsFunc(policyAncestorStatusesB, func(sB gatev1alpha2.PolicyAncestorStatus) bool { + return policyAncestorStatusEqual(sB, sA) + }) { + return false + } + } + + for _, sB := range policyAncestorStatusesB { + if !slices.ContainsFunc(policyAncestorStatusesA, func(sA gatev1alpha2.PolicyAncestorStatus) bool { + return policyAncestorStatusEqual(sA, sB) + }) { + return false + } + } + + return true +} + +func policyAncestorStatusEqual(sA, sB gatev1alpha2.PolicyAncestorStatus) bool { + return sA.ControllerName == sB.ControllerName && + reflect.DeepEqual(sA.AncestorRef, sB.AncestorRef) && + conditionsEqual(sA.Conditions, sB.Conditions) +} + func routeParentStatusesEqual(routeParentStatusesA, routeParentStatusesB []gatev1alpha2.RouteParentStatus) bool { if len(routeParentStatusesA) != len(routeParentStatusesB) { return false diff --git a/pkg/provider/kubernetes/gateway/features.go b/pkg/provider/kubernetes/gateway/features.go new file mode 100644 index 000000000..6d95c54d7 --- /dev/null +++ b/pkg/provider/kubernetes/gateway/features.go @@ -0,0 +1,23 @@ +package gateway + +import "sigs.k8s.io/gateway-api/pkg/features" + +func SupportedFeatures() []features.SupportedFeature { + return []features.SupportedFeature{ + features.SupportGateway, + features.SupportGatewayPort8080, + features.SupportGRPCRoute, + features.SupportHTTPRoute, + features.SupportHTTPRouteQueryParamMatching, + features.SupportHTTPRouteMethodMatching, + features.SupportHTTPRoutePortRedirect, + features.SupportHTTPRouteSchemeRedirect, + features.SupportHTTPRouteHostRewrite, + features.SupportHTTPRoutePathRewrite, + features.SupportHTTPRoutePathRedirect, + features.SupportHTTPRouteResponseHeaderModification, + features.SupportTLSRoute, + features.SupportHTTPRouteBackendProtocolH2C, + features.SupportHTTPRouteBackendProtocolWebSocket, + } +} diff --git a/pkg/provider/kubernetes/gateway/fixtures/httproute/with_backend_tls_policy.yml b/pkg/provider/kubernetes/gateway/fixtures/httproute/with_backend_tls_policy.yml new file mode 100644 index 000000000..e64a341b6 --- /dev/null +++ b/pkg/provider/kubernetes/gateway/fixtures/httproute/with_backend_tls_policy.yml @@ -0,0 +1,78 @@ +--- +kind: GatewayClass +apiVersion: gateway.networking.k8s.io/v1 +metadata: + name: my-gateway-class +spec: + controllerName: traefik.io/gateway-controller + +--- +kind: Gateway +apiVersion: gateway.networking.k8s.io/v1 +metadata: + name: my-gateway + namespace: default +spec: + gatewayClassName: my-gateway-class + listeners: # Use GatewayClass defaults for listener definition. + - name: http + protocol: HTTP + port: 80 + allowedRoutes: + kinds: + - kind: HTTPRoute + group: gateway.networking.k8s.io + namespaces: + from: Same + +--- +kind: HTTPRoute +apiVersion: gateway.networking.k8s.io/v1 +metadata: + name: http-app-1 + namespace: default +spec: + parentRefs: + - name: my-gateway + kind: Gateway + group: gateway.networking.k8s.io + hostnames: + - "foo.com" + rules: + - matches: + - path: + type: Exact + value: /bar + backendRefs: + - name: whoami + port: 80 + weight: 1 + kind: Service + group: "" + +--- +kind: BackendTLSPolicy +apiVersion: gateway.networking.k8s.io/v1alpha3 +metadata: + name: policy-1 + namespace: default +spec: + targetRefs: + - group: core + kind: Service + name: whoami + validation: + hostname: whoami + caCertificateRefs: + - group: core + kind: ConfigMap + name: ca-file + +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: ca-file + namespace: default +data: + ca.crt: "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0=" diff --git a/pkg/provider/kubernetes/gateway/fixtures/httproute/with_backend_tls_policy_system.yml b/pkg/provider/kubernetes/gateway/fixtures/httproute/with_backend_tls_policy_system.yml new file mode 100644 index 000000000..cc36c0468 --- /dev/null +++ b/pkg/provider/kubernetes/gateway/fixtures/httproute/with_backend_tls_policy_system.yml @@ -0,0 +1,66 @@ +--- +kind: GatewayClass +apiVersion: gateway.networking.k8s.io/v1 +metadata: + name: my-gateway-class +spec: + controllerName: traefik.io/gateway-controller + +--- +kind: Gateway +apiVersion: gateway.networking.k8s.io/v1 +metadata: + name: my-gateway + namespace: default +spec: + gatewayClassName: my-gateway-class + listeners: # Use GatewayClass defaults for listener definition. + - name: http + protocol: HTTP + port: 80 + allowedRoutes: + kinds: + - kind: HTTPRoute + group: gateway.networking.k8s.io + namespaces: + from: Same + +--- +kind: HTTPRoute +apiVersion: gateway.networking.k8s.io/v1 +metadata: + name: http-app-1 + namespace: default +spec: + parentRefs: + - name: my-gateway + kind: Gateway + group: gateway.networking.k8s.io + hostnames: + - "foo.com" + rules: + - matches: + - path: + type: Exact + value: /bar + backendRefs: + - name: whoami + port: 80 + weight: 1 + kind: Service + group: "" + +--- +kind: BackendTLSPolicy +apiVersion: gateway.networking.k8s.io/v1alpha3 +metadata: + name: policy-1 + namespace: default +spec: + targetRefs: + - group: core + kind: Service + name: whoami + validation: + hostname: whoami + wellKnownCACertificates: System diff --git a/pkg/provider/kubernetes/gateway/httproute.go b/pkg/provider/kubernetes/gateway/httproute.go index c18dc48d3..d6c5a4c08 100644 --- a/pkg/provider/kubernetes/gateway/httproute.go +++ b/pkg/provider/kubernetes/gateway/httproute.go @@ -13,11 +13,14 @@ import ( "github.com/rs/zerolog/log" "github.com/traefik/traefik/v3/pkg/config/dynamic" "github.com/traefik/traefik/v3/pkg/provider" + "github.com/traefik/traefik/v3/pkg/types" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ktypes "k8s.io/apimachinery/pkg/types" "k8s.io/utils/ptr" gatev1 "sigs.k8s.io/gateway-api/apis/v1" + gatev1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" + gatev1alpha3 "sigs.k8s.io/gateway-api/apis/v1alpha3" ) func (p *Provider) loadHTTPRoutes(ctx context.Context, gatewayListeners []gatewayListener, conf *dynamic.Configuration) { @@ -158,7 +161,7 @@ func (p *Provider) loadHTTPRoute(ctx context.Context, listener gatewayListener, default: var serviceCondition *metav1.Condition - router.Service, serviceCondition = p.loadWRRService(conf, routerName, routeRule, route) + router.Service, serviceCondition = p.loadWRRService(ctx, listener, conf, routerName, routeRule, route) if serviceCondition != nil { condition = *serviceCondition } @@ -173,7 +176,7 @@ func (p *Provider) loadHTTPRoute(ctx context.Context, listener gatewayListener, return conf, condition } -func (p *Provider) loadWRRService(conf *dynamic.Configuration, routeKey string, routeRule gatev1.HTTPRouteRule, route *gatev1.HTTPRoute) (string, *metav1.Condition) { +func (p *Provider) loadWRRService(ctx context.Context, listener gatewayListener, conf *dynamic.Configuration, routeKey string, routeRule gatev1.HTTPRouteRule, route *gatev1.HTTPRoute) (string, *metav1.Condition) { name := routeKey + "-wrr" if _, ok := conf.HTTP.Services[name]; ok { return name, nil @@ -182,7 +185,7 @@ func (p *Provider) loadWRRService(conf *dynamic.Configuration, routeKey string, var wrr dynamic.WeightedRoundRobin var condition *metav1.Condition for _, backendRef := range routeRule.BackendRefs { - svcName, svc, errCondition := p.loadService(route, backendRef) + svcName, errCondition := p.loadService(ctx, listener, conf, route, backendRef) weight := ptr.To(int(ptr.Deref(backendRef.Weight, 1))) if errCondition != nil { condition = errCondition @@ -194,10 +197,6 @@ func (p *Provider) loadWRRService(conf *dynamic.Configuration, routeKey string, continue } - if svc != nil { - conf.HTTP.Services[svcName] = svc - } - wrr.Services = append(wrr.Services, dynamic.WRRService{ Name: svcName, Weight: weight, @@ -210,7 +209,7 @@ func (p *Provider) loadWRRService(conf *dynamic.Configuration, routeKey string, // loadService returns a dynamic.Service config corresponding to the given gatev1.HTTPBackendRef. // Note that the returned dynamic.Service config can be nil (for cross-provider, internal services, and backendFunc). -func (p *Provider) loadService(route *gatev1.HTTPRoute, backendRef gatev1.HTTPBackendRef) (string, *dynamic.Service, *metav1.Condition) { +func (p *Provider) loadService(ctx context.Context, listener gatewayListener, conf *dynamic.Configuration, route *gatev1.HTTPRoute, backendRef gatev1.HTTPBackendRef) (string, *metav1.Condition) { kind := ptr.Deref(backendRef.Kind, kindService) group := groupCore @@ -226,7 +225,7 @@ func (p *Provider) loadService(route *gatev1.HTTPRoute, backendRef gatev1.HTTPBa serviceName := provider.Normalize(namespace + "-" + string(backendRef.Name)) if err := p.isReferenceGranted(kindHTTPRoute, route.Namespace, group, string(kind), string(backendRef.Name), namespace); err != nil { - return serviceName, nil, &metav1.Condition{ + return serviceName, &metav1.Condition{ Type: string(gatev1.RouteConditionResolvedRefs), Status: metav1.ConditionFalse, ObservedGeneration: route.Generation, @@ -239,7 +238,7 @@ func (p *Provider) loadService(route *gatev1.HTTPRoute, backendRef gatev1.HTTPBa if group != groupCore || kind != kindService { name, service, err := p.loadHTTPBackendRef(namespace, backendRef) if err != nil { - return serviceName, nil, &metav1.Condition{ + return serviceName, &metav1.Condition{ Type: string(gatev1.RouteConditionResolvedRefs), Status: metav1.ConditionFalse, ObservedGeneration: route.Generation, @@ -249,12 +248,16 @@ func (p *Provider) loadService(route *gatev1.HTTPRoute, backendRef gatev1.HTTPBa } } - return name, service, nil + if service != nil { + conf.HTTP.Services[name] = service + } + + return name, nil } port := ptr.Deref(backendRef.Port, gatev1.PortNumber(0)) if port == 0 { - return serviceName, nil, &metav1.Condition{ + return serviceName, &metav1.Condition{ Type: string(gatev1.RouteConditionResolvedRefs), Status: metav1.ConditionFalse, ObservedGeneration: route.Generation, @@ -267,12 +270,97 @@ func (p *Provider) loadService(route *gatev1.HTTPRoute, backendRef gatev1.HTTPBa portStr := strconv.FormatInt(int64(port), 10) serviceName = provider.Normalize(serviceName + "-" + portStr) - lb, errCondition := p.loadHTTPServers(namespace, route, backendRef) + lb, svcPort, errCondition := p.loadHTTPServers(namespace, route, backendRef) if errCondition != nil { - return serviceName, nil, errCondition + return serviceName, errCondition } - return serviceName, &dynamic.Service{LoadBalancer: lb}, nil + if !p.ExperimentalChannel { + conf.HTTP.Services[serviceName] = &dynamic.Service{LoadBalancer: lb} + + return serviceName, nil + } + + servicePolicies, err := p.client.ListBackendTLSPoliciesForService(namespace, string(backendRef.Name)) + if err != nil { + return serviceName, &metav1.Condition{ + Type: string(gatev1.RouteConditionResolvedRefs), + Status: metav1.ConditionFalse, + ObservedGeneration: route.Generation, + LastTransitionTime: metav1.Now(), + Reason: string(gatev1.RouteReasonRefNotPermitted), + Message: fmt.Sprintf("Cannot list BackendTLSPolicies for Service %s/%s: %s", namespace, string(backendRef.Name), err), + } + } + + var matchedPolicy *gatev1alpha3.BackendTLSPolicy + for _, policy := range servicePolicies { + matched := false + for _, targetRef := range policy.Spec.TargetRefs { + if targetRef.SectionName == nil || svcPort.Name == string(*targetRef.SectionName) { + matchedPolicy = policy + matched = true + break + } + } + + // If the policy targets the service, but doesn't match any port. + if !matched { + // update policy status + status := gatev1alpha2.PolicyStatus{ + Ancestors: []gatev1alpha2.PolicyAncestorStatus{{ + AncestorRef: gatev1alpha2.ParentReference{ + Group: ptr.To(gatev1.Group(groupGateway)), + Kind: ptr.To(gatev1.Kind(kindGateway)), + Namespace: ptr.To(gatev1.Namespace(namespace)), + Name: gatev1.ObjectName(listener.GWName), + SectionName: ptr.To(gatev1.SectionName(listener.Name)), + }, + ControllerName: controllerName, + Conditions: []metav1.Condition{{ + Type: string(gatev1.RouteConditionResolvedRefs), + Status: metav1.ConditionFalse, + ObservedGeneration: route.Generation, + LastTransitionTime: metav1.Now(), + Reason: string(gatev1.RouteReasonBackendNotFound), + Message: fmt.Sprintf("BackendTLSPolicy has no valid TargetRef for Service %s/%s", namespace, string(backendRef.Name)), + }}, + }}, + } + + if err := p.client.UpdateBackendTLSPolicyStatus(ctx, ktypes.NamespacedName{Namespace: policy.Namespace, Name: policy.Name}, status); err != nil { + logger := log.Ctx(ctx).With(). + Str("http_route", route.Name). + Str("namespace", route.Namespace).Logger() + logger.Warn(). + Err(err). + Msg("Unable to update TLSRoute status") + } + } + } + + if matchedPolicy != nil { + st, err := p.loadServersTransport(namespace, *matchedPolicy) + if err != nil { + return serviceName, &metav1.Condition{ + Type: string(gatev1.RouteConditionResolvedRefs), + Status: metav1.ConditionFalse, + ObservedGeneration: route.Generation, + LastTransitionTime: metav1.Now(), + Reason: string(gatev1.RouteReasonRefNotPermitted), + Message: fmt.Sprintf("Cannot apply BackendTLSPolicy for Service %s/%s: %s", namespace, string(backendRef.Name), err), + } + } + + if st != nil { + lb.ServersTransport = serviceName + conf.HTTP.ServersTransports[serviceName] = st + } + } + + conf.HTTP.Services[serviceName] = &dynamic.Service{LoadBalancer: lb} + + return serviceName, nil } func (p *Provider) loadHTTPBackendRef(namespace string, backendRef gatev1.HTTPBackendRef) (string, *dynamic.Service, error) { @@ -365,10 +453,10 @@ func (p *Provider) loadHTTPRouteFilterExtensionRef(namespace string, extensionRe return filterFunc(string(extensionRef.Name), namespace) } -func (p *Provider) loadHTTPServers(namespace string, route *gatev1.HTTPRoute, backendRef gatev1.HTTPBackendRef) (*dynamic.ServersLoadBalancer, *metav1.Condition) { +func (p *Provider) loadHTTPServers(namespace string, route *gatev1.HTTPRoute, backendRef gatev1.HTTPBackendRef) (*dynamic.ServersLoadBalancer, corev1.ServicePort, *metav1.Condition) { backendAddresses, svcPort, err := p.getBackendAddresses(namespace, backendRef.BackendRef) if err != nil { - return nil, &metav1.Condition{ + return nil, corev1.ServicePort{}, &metav1.Condition{ Type: string(gatev1.RouteConditionResolvedRefs), Status: metav1.ConditionFalse, ObservedGeneration: route.Generation, @@ -380,7 +468,7 @@ func (p *Provider) loadHTTPServers(namespace string, route *gatev1.HTTPRoute, ba protocol, err := getProtocol(svcPort) if err != nil { - return nil, &metav1.Condition{ + return nil, corev1.ServicePort{}, &metav1.Condition{ Type: string(gatev1.RouteConditionResolvedRefs), Status: metav1.ConditionFalse, ObservedGeneration: route.Generation, @@ -398,7 +486,40 @@ func (p *Provider) loadHTTPServers(namespace string, route *gatev1.HTTPRoute, ba URL: fmt.Sprintf("%s://%s", protocol, net.JoinHostPort(ba.Address, strconv.Itoa(int(ba.Port)))), }) } - return lb, nil + return lb, svcPort, nil +} + +func (p *Provider) loadServersTransport(namespace string, policy gatev1alpha3.BackendTLSPolicy) (*dynamic.ServersTransport, error) { + st := &dynamic.ServersTransport{ + ServerName: string(policy.Spec.Validation.Hostname), + } + + if policy.Spec.Validation.WellKnownCACertificates != nil { + return st, nil + } + + for _, caCertRef := range policy.Spec.Validation.CACertificateRefs { + if caCertRef.Group != groupCore || caCertRef.Kind != "ConfigMap" { + continue + } + + configMap, exists, err := p.client.GetConfigMap(namespace, string(caCertRef.Name)) + if err != nil { + return nil, fmt.Errorf("getting configmap: %w", err) + } + if !exists { + return nil, fmt.Errorf("configmap %s/%s not found", namespace, string(caCertRef.Name)) + } + + caCRT, ok := configMap.Data["ca.crt"] + if !ok { + return nil, fmt.Errorf("configmap %s/%s does not have ca.crt", namespace, string(caCertRef.Name)) + } + + st.RootCAs = append(st.RootCAs, types.FileOrContent(caCRT)) + } + + return st, nil } func buildHostRule(hostnames []gatev1.Hostname) (string, int) { @@ -715,4 +836,11 @@ func mergeHTTPConfiguration(from, to *dynamic.Configuration) { for serviceName, service := range from.HTTP.Services { to.HTTP.Services[serviceName] = service } + + if to.HTTP.ServersTransports == nil { + to.HTTP.ServersTransports = map[string]*dynamic.ServersTransport{} + } + for name, serversTransport := range from.HTTP.ServersTransports { + to.HTTP.ServersTransports[name] = serversTransport + } } diff --git a/pkg/provider/kubernetes/gateway/kubernetes.go b/pkg/provider/kubernetes/gateway/kubernetes.go index f3e50e274..144bdf31a 100644 --- a/pkg/provider/kubernetes/gateway/kubernetes.go +++ b/pkg/provider/kubernetes/gateway/kubernetes.go @@ -316,6 +316,12 @@ func (p *Provider) loadConfigurationFromGateways(ctx context.Context) *dynamic.C return nil } + var supportedFeatures []gatev1.SupportedFeature + for _, feature := range SupportedFeatures() { + supportedFeatures = append(supportedFeatures, gatev1.SupportedFeature(feature)) + } + slices.Sort(supportedFeatures) + gatewayClassNames := map[string]struct{}{} for _, gatewayClass := range gatewayClasses { if gatewayClass.Spec.ControllerName != controllerName { @@ -333,6 +339,7 @@ func (p *Provider) loadConfigurationFromGateways(ctx context.Context) *dynamic.C Message: "Handled by Traefik controller", LastTransitionTime: metav1.Now(), }), + SupportedFeatures: supportedFeatures, } if err := p.client.UpdateGatewayClassStatus(ctx, gatewayClass.Name, status); err != nil { diff --git a/pkg/provider/kubernetes/gateway/kubernetes_test.go b/pkg/provider/kubernetes/gateway/kubernetes_test.go index c5cdb413b..211381d75 100644 --- a/pkg/provider/kubernetes/gateway/kubernetes_test.go +++ b/pkg/provider/kubernetes/gateway/kubernetes_test.go @@ -26,6 +26,7 @@ import ( "k8s.io/utils/ptr" gatev1 "sigs.k8s.io/gateway-api/apis/v1" gatev1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" + gatev1alpha3 "sigs.k8s.io/gateway-api/apis/v1alpha3" gatev1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1" gatefake "sigs.k8s.io/gateway-api/pkg/client/clientset/versioned/fake" ) @@ -43,6 +44,9 @@ func init() { if err := gatev1alpha2.AddToScheme(kscheme.Scheme); err != nil { panic(err) } + if err := gatev1alpha3.AddToScheme(kscheme.Scheme); err != nil { + panic(err) + } } func TestLoadHTTPRoutes(t *testing.T) { @@ -2132,6 +2136,204 @@ func TestLoadHTTPRoutes(t *testing.T) { TLS: &dynamic.TLSConfiguration{}, }, }, + { + desc: "Simple HTTPRoute and BackendTLSPolicy, experimental channel disabled", + paths: []string{"services.yml", "httproute/with_backend_tls_policy.yml"}, + entryPoints: map[string]Entrypoint{"web": { + Address: ":80", + }}, + expected: &dynamic.Configuration{ + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Middlewares: map[string]*dynamic.TCPMiddleware{}, + Services: map[string]*dynamic.TCPService{}, + ServersTransports: map[string]*dynamic.TCPServersTransport{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{ + "default-http-app-1-my-gateway-web-0-1c0cf64bde37d9d0df06": { + EntryPoints: []string{"web"}, + Service: "default-http-app-1-my-gateway-web-0-1c0cf64bde37d9d0df06-wrr", + Rule: "Host(`foo.com`) && Path(`/bar`)", + Priority: 100008, + RuleSyntax: "v3", + }, + }, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{ + "default-http-app-1-my-gateway-web-0-1c0cf64bde37d9d0df06-wrr": { + Weighted: &dynamic.WeightedRoundRobin{ + Services: []dynamic.WRRService{ + { + Name: "default-whoami-80", + Weight: ptr.To(1), + }, + }, + }, + }, + "default-whoami-80": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + Servers: []dynamic.Server{ + { + URL: "http://10.10.0.1:80", + }, + { + URL: "http://10.10.0.2:80", + }, + }, + PassHostHeader: ptr.To(true), + ResponseForwarding: &dynamic.ResponseForwarding{ + FlushInterval: ptypes.Duration(100 * time.Millisecond), + }, + }, + }, + }, + ServersTransports: map[string]*dynamic.ServersTransport{}, + }, + TLS: &dynamic.TLSConfiguration{}, + }, + }, + { + desc: "Simple HTTPRoute and BackendTLSPolicy with CA certificate, experimental channel enabled", + paths: []string{"services.yml", "httproute/with_backend_tls_policy.yml"}, + entryPoints: map[string]Entrypoint{"web": { + Address: ":80", + }}, + experimentalChannel: true, + expected: &dynamic.Configuration{ + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Middlewares: map[string]*dynamic.TCPMiddleware{}, + Services: map[string]*dynamic.TCPService{}, + ServersTransports: map[string]*dynamic.TCPServersTransport{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{ + "default-http-app-1-my-gateway-web-0-1c0cf64bde37d9d0df06": { + EntryPoints: []string{"web"}, + Service: "default-http-app-1-my-gateway-web-0-1c0cf64bde37d9d0df06-wrr", + Rule: "Host(`foo.com`) && Path(`/bar`)", + Priority: 100008, + RuleSyntax: "v3", + }, + }, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{ + "default-http-app-1-my-gateway-web-0-1c0cf64bde37d9d0df06-wrr": { + Weighted: &dynamic.WeightedRoundRobin{ + Services: []dynamic.WRRService{ + { + Name: "default-whoami-80", + Weight: ptr.To(1), + }, + }, + }, + }, + "default-whoami-80": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + Servers: []dynamic.Server{ + { + URL: "http://10.10.0.1:80", + }, + { + URL: "http://10.10.0.2:80", + }, + }, + PassHostHeader: ptr.To(true), + ResponseForwarding: &dynamic.ResponseForwarding{ + FlushInterval: ptypes.Duration(100 * time.Millisecond), + }, + ServersTransport: "default-whoami-80", + }, + }, + }, + ServersTransports: map[string]*dynamic.ServersTransport{ + "default-whoami-80": { + ServerName: "whoami", + RootCAs: []types.FileOrContent{ + "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0=", + }, + }, + }, + }, + TLS: &dynamic.TLSConfiguration{}, + }, + }, + { + desc: "Simple HTTPRoute and BackendTLSPolicy with System CA, experimental channel enabled", + paths: []string{"services.yml", "httproute/with_backend_tls_policy_system.yml"}, + entryPoints: map[string]Entrypoint{"web": { + Address: ":80", + }}, + experimentalChannel: true, + expected: &dynamic.Configuration{ + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Middlewares: map[string]*dynamic.TCPMiddleware{}, + Services: map[string]*dynamic.TCPService{}, + ServersTransports: map[string]*dynamic.TCPServersTransport{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{ + "default-http-app-1-my-gateway-web-0-1c0cf64bde37d9d0df06": { + EntryPoints: []string{"web"}, + Service: "default-http-app-1-my-gateway-web-0-1c0cf64bde37d9d0df06-wrr", + Rule: "Host(`foo.com`) && Path(`/bar`)", + Priority: 100008, + RuleSyntax: "v3", + }, + }, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{ + "default-http-app-1-my-gateway-web-0-1c0cf64bde37d9d0df06-wrr": { + Weighted: &dynamic.WeightedRoundRobin{ + Services: []dynamic.WRRService{ + { + Name: "default-whoami-80", + Weight: ptr.To(1), + }, + }, + }, + }, + "default-whoami-80": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + Servers: []dynamic.Server{ + { + URL: "http://10.10.0.1:80", + }, + { + URL: "http://10.10.0.2:80", + }, + }, + PassHostHeader: ptr.To(true), + ResponseForwarding: &dynamic.ResponseForwarding{ + FlushInterval: ptypes.Duration(100 * time.Millisecond), + }, + ServersTransport: "default-whoami-80", + }, + }, + }, + ServersTransports: map[string]*dynamic.ServersTransport{ + "default-whoami-80": { + ServerName: "whoami", + }, + }, + }, + TLS: &dynamic.TLSConfiguration{}, + }, + }, } for _, test := range testCases { @@ -2148,6 +2350,7 @@ func TestLoadHTTPRoutes(t *testing.T) { gwClient := newGatewaySimpleClientSet(t, gwObjects...) client := newClientImpl(kubeClient, gwClient) + client.experimentalChannel = test.experimentalChannel eventCh, err := client.WatchAll(nil, make(chan struct{})) require.NoError(t, err) diff --git a/pkg/provider/kubernetes/k8s/parser.go b/pkg/provider/kubernetes/k8s/parser.go index 0c50ab61e..422513ade 100644 --- a/pkg/provider/kubernetes/k8s/parser.go +++ b/pkg/provider/kubernetes/k8s/parser.go @@ -12,7 +12,7 @@ import ( // MustParseYaml parses a YAML to objects. func MustParseYaml(content []byte) []runtime.Object { - acceptedK8sTypes := regexp.MustCompile(`^(Namespace|Deployment|EndpointSlice|Node|Service|Ingress|IngressRoute|IngressRouteTCP|IngressRouteUDP|Middleware|MiddlewareTCP|Secret|TLSOption|TLSStore|TraefikService|IngressClass|ServersTransport|ServersTransportTCP|GatewayClass|Gateway|HTTPRoute|TCPRoute|TLSRoute|ReferenceGrant)$`) + acceptedK8sTypes := regexp.MustCompile(`^(Namespace|Deployment|EndpointSlice|Node|Service|ConfigMap|Ingress|IngressRoute|IngressRouteTCP|IngressRouteUDP|Middleware|MiddlewareTCP|Secret|TLSOption|TLSStore|TraefikService|IngressClass|ServersTransport|ServersTransportTCP|GatewayClass|Gateway|HTTPRoute|TCPRoute|TLSRoute|ReferenceGrant|BackendTLSPolicy)$`) files := strings.Split(string(content), "---\n") retVal := make([]runtime.Object, 0, len(files)) diff --git a/pkg/provider/traefik/internal.go b/pkg/provider/traefik/internal.go index 1f1b09a52..ca0add5be 100644 --- a/pkg/provider/traefik/internal.go +++ b/pkg/provider/traefik/internal.go @@ -91,15 +91,27 @@ func (i *Provider) createConfiguration(ctx context.Context) *dynamic.Configurati } func (i *Provider) acme(cfg *dynamic.Configuration) { - var eps []string + allowACMEByPass := map[string]bool{} + for name, ep := range i.staticCfg.EntryPoints { + allowACMEByPass[name] = ep.AllowACMEByPass + } + var eps []string + var epsByPass []string uniq := map[string]struct{}{} for _, resolver := range i.staticCfg.CertificatesResolvers { if resolver.ACME != nil && resolver.ACME.HTTPChallenge != nil && resolver.ACME.HTTPChallenge.EntryPoint != "" { - if _, ok := uniq[resolver.ACME.HTTPChallenge.EntryPoint]; !ok { - eps = append(eps, resolver.ACME.HTTPChallenge.EntryPoint) - uniq[resolver.ACME.HTTPChallenge.EntryPoint] = struct{}{} + if _, ok := uniq[resolver.ACME.HTTPChallenge.EntryPoint]; ok { + continue } + uniq[resolver.ACME.HTTPChallenge.EntryPoint] = struct{}{} + + if allowByPass, ok := allowACMEByPass[resolver.ACME.HTTPChallenge.EntryPoint]; ok && allowByPass { + epsByPass = append(epsByPass, resolver.ACME.HTTPChallenge.EntryPoint) + continue + } + + eps = append(eps, resolver.ACME.HTTPChallenge.EntryPoint) } } @@ -115,6 +127,17 @@ func (i *Provider) acme(cfg *dynamic.Configuration) { cfg.HTTP.Routers["acme-http"] = rt cfg.HTTP.Services["acme-http"] = &dynamic.Service{} } + + if len(epsByPass) > 0 { + rt := &dynamic.Router{ + Rule: "PathPrefix(`/.well-known/acme-challenge/`)", + EntryPoints: epsByPass, + Service: "acme-http@internal", + } + + cfg.HTTP.Routers["acme-http-bypass"] = rt + cfg.HTTP.Services["acme-http"] = &dynamic.Service{} + } } func (i *Provider) redirection(ctx context.Context, cfg *dynamic.Configuration) { diff --git a/pkg/redactor/redactor_config_test.go b/pkg/redactor/redactor_config_test.go index 592d91cfd..15c996193 100644 --- a/pkg/redactor/redactor_config_test.go +++ b/pkg/redactor/redactor_config_test.go @@ -777,7 +777,7 @@ func TestDo_staticConfiguration(t *testing.T) { } config.Providers.HTTP = &http.Provider{ - Endpoint: "Myenpoint", + Endpoint: "Myendpoint", PollInterval: 42, PollTimeout: 42, TLS: &types.ClientTLS{ diff --git a/pkg/server/router/router_test.go b/pkg/server/router/router_test.go index c1ae3436e..9b1cab388 100644 --- a/pkg/server/router/router_test.go +++ b/pkg/server/router/router_test.go @@ -858,7 +858,7 @@ func BenchmarkService(b *testing.B) { LoadBalancer: &dynamic.ServersLoadBalancer{ Servers: []dynamic.Server{ { - URL: "tchouck", + URL: "tchouk", }, }, }, diff --git a/pkg/server/router/tcp/router.go b/pkg/server/router/tcp/router.go index 89096c7ca..06ea5c223 100644 --- a/pkg/server/router/tcp/router.go +++ b/pkg/server/router/tcp/router.go @@ -21,6 +21,8 @@ const defaultBufSize = 4096 // Router is a TCP router. type Router struct { + acmeTLSPassthrough bool + // Contains TCP routes. muxerTCP tcpmuxer.Muxer // Contains TCP TLS routes. @@ -164,7 +166,7 @@ func (r *Router) ServeTCP(conn tcp.WriteCloser) { } // Handling ACME-TLS/1 challenges. - if slices.Contains(hello.protos, tlsalpn01.ACMETLS1Protocol) { + if !r.acmeTLSPassthrough && slices.Contains(hello.protos, tlsalpn01.ACMETLS1Protocol) { r.acmeTLSALPNHandler().ServeTCP(r.GetConn(conn, hello.peeked)) return } @@ -317,6 +319,10 @@ func (r *Router) SetHTTPSHandler(handler http.Handler, config *tls.Config) { r.httpsTLSConfig = config } +func (r *Router) EnableACMETLSPassthrough() { + r.acmeTLSPassthrough = true +} + // Conn is a connection proxy that handles Peeked bytes. type Conn struct { // Peeked are the bytes that have been read from Conn for the purposes of route matching, diff --git a/pkg/server/router/tcp/router_test.go b/pkg/server/router/tcp/router_test.go index b95f0d867..16f633443 100644 --- a/pkg/server/router/tcp/router_test.go +++ b/pkg/server/router/tcp/router_test.go @@ -212,9 +212,10 @@ func Test_Routing(t *testing.T) { } testCases := []struct { - desc string - routers []applyRouter - checks []checkCase + desc string + routers []applyRouter + checks []checkCase + allowACMETLSPassthrough bool }{ { desc: "No routers", @@ -271,6 +272,18 @@ func Test_Routing(t *testing.T) { }, }, }, + { + desc: "TCP TLS passthrough catches ACME TLS", + allowACMETLSPassthrough: true, + routers: []applyRouter{routerTCPTLSCatchAllPassthrough}, + checks: []checkCase{ + { + desc: "ACME TLS Challenge", + checkRouter: checkACMETLS, + expectedError: "tls: first record does not look like a TLS handshake", + }, + }, + }, { desc: "Single TCP CatchAll router", routers: []applyRouter{routerTCPCatchAll}, @@ -596,6 +609,10 @@ func Test_Routing(t *testing.T) { router, err := manager.buildEntryPointHandler(context.Background(), dynConf.TCPRouters, dynConf.Routers, nil, nil) require.NoError(t, err) + if test.allowACMETLSPassthrough { + router.EnableACMETLSPassthrough() + } + epListener, err := net.Listen("tcp", "127.0.0.1:0") require.NoError(t, err) @@ -717,7 +734,7 @@ func routerTCPTLSCatchAll(conf *runtime.Configuration) { } } -// routerTCPTLSCatchAllPassthrough a TCP TLS CatchAll Passthrough - HostSNI(`*`) router with TLS 1.0 config. +// routerTCPTLSCatchAllPassthrough a TCP TLS CatchAll Passthrough - HostSNI(`*`) router with TLS 1.2 config. func routerTCPTLSCatchAllPassthrough(conf *runtime.Configuration) { conf.TCPRouters["tcp-tls-catchall-passthrough"] = &runtime.TCPRouterInfo{ TCPRouter: &dynamic.TCPRouter{ diff --git a/pkg/server/routerfactory.go b/pkg/server/routerfactory.go index 1ece7096a..b0700849c 100644 --- a/pkg/server/routerfactory.go +++ b/pkg/server/routerfactory.go @@ -21,8 +21,9 @@ import ( // RouterFactory the factory of TCP/UDP routers. type RouterFactory struct { - entryPointsTCP []string - entryPointsUDP []string + entryPointsTCP []string + entryPointsUDP []string + allowACMEByPass map[string]bool managerFactory *service.ManagerFactory @@ -40,9 +41,20 @@ type RouterFactory struct { func NewRouterFactory(staticConfiguration static.Configuration, managerFactory *service.ManagerFactory, tlsManager *tls.Manager, observabilityMgr *middleware.ObservabilityMgr, pluginBuilder middleware.PluginsBuilder, dialerManager *tcp.DialerManager, ) *RouterFactory { + handlesTLSChallenge := false + for _, resolver := range staticConfiguration.CertificatesResolvers { + if resolver.ACME != nil && resolver.ACME.TLSChallenge != nil { + handlesTLSChallenge = true + break + } + } + + allowACMEByPass := map[string]bool{} var entryPointsTCP, entryPointsUDP []string - for name, cfg := range staticConfiguration.EntryPoints { - protocol, err := cfg.GetProtocol() + for name, ep := range staticConfiguration.EntryPoints { + allowACMEByPass[name] = ep.AllowACMEByPass || !handlesTLSChallenge + + protocol, err := ep.GetProtocol() if err != nil { // Should never happen because Traefik should not start if protocol is invalid. log.Error().Err(err).Msg("Invalid protocol") @@ -63,6 +75,7 @@ func NewRouterFactory(staticConfiguration static.Configuration, managerFactory * tlsManager: tlsManager, pluginBuilder: pluginBuilder, dialerManager: dialerManager, + allowACMEByPass: allowACMEByPass, } } @@ -95,6 +108,12 @@ func (f *RouterFactory) CreateRouters(rtConf *runtime.Configuration) (map[string rtTCPManager := tcprouter.NewManager(rtConf, svcTCPManager, middlewaresTCPBuilder, handlersNonTLS, handlersTLS, f.tlsManager) routersTCP := rtTCPManager.BuildHandlers(ctx, f.entryPointsTCP) + for ep, r := range routersTCP { + if allowACMEByPass, ok := f.allowACMEByPass[ep]; ok && allowACMEByPass { + r.EnableACMETLSPassthrough() + } + } + // UDP svcUDPManager := udpsvc.NewManager(rtConf) rtUDPManager := udprouter.NewManager(rtConf, svcUDPManager) diff --git a/pkg/server/server_entrypoint_tcp.go b/pkg/server/server_entrypoint_tcp.go index ca901c90f..094332ea3 100644 --- a/pkg/server/server_entrypoint_tcp.go +++ b/pkg/server/server_entrypoint_tcp.go @@ -185,7 +185,10 @@ func NewTCPEntryPoint(ctx context.Context, name string, config *static.EntryPoin return nil, fmt.Errorf("error preparing server: %w", err) } - rt := &tcprouter.Router{} + rt, err := tcprouter.NewRouter() + if err != nil { + return nil, fmt.Errorf("error preparing tcp router: %w", err) + } reqDecorator := requestdecorator.New(hostResolverConfig) @@ -607,6 +610,7 @@ func createHTTPServer(ctx context.Context, ln net.Listener, configuration *stati handler, err = forwardedheaders.NewXForwarded( configuration.ForwardedHeaders.Insecure, configuration.ForwardedHeaders.TrustedIPs, + configuration.ForwardedHeaders.Connection, next) if err != nil { return nil, err @@ -633,11 +637,12 @@ func createHTTPServer(ctx context.Context, ln net.Listener, configuration *stati } serverHTTP := &http.Server{ - Handler: handler, - ErrorLog: stdlog.New(logs.NoLevel(log.Logger, zerolog.DebugLevel), "", 0), - ReadTimeout: time.Duration(configuration.Transport.RespondingTimeouts.ReadTimeout), - WriteTimeout: time.Duration(configuration.Transport.RespondingTimeouts.WriteTimeout), - IdleTimeout: time.Duration(configuration.Transport.RespondingTimeouts.IdleTimeout), + Handler: handler, + ErrorLog: stdlog.New(logs.NoLevel(log.Logger, zerolog.DebugLevel), "", 0), + ReadTimeout: time.Duration(configuration.Transport.RespondingTimeouts.ReadTimeout), + WriteTimeout: time.Duration(configuration.Transport.RespondingTimeouts.WriteTimeout), + IdleTimeout: time.Duration(configuration.Transport.RespondingTimeouts.IdleTimeout), + MaxHeaderBytes: configuration.HTTP.MaxHeaderBytes, } if debugConnection || (configuration.Transport != nil && (configuration.Transport.KeepAliveMaxTime > 0 || configuration.Transport.KeepAliveMaxRequests > 0)) { serverHTTP.ConnContext = func(ctx context.Context, c net.Conn) context.Context { diff --git a/pkg/server/server_entrypoint_tcp_test.go b/pkg/server/server_entrypoint_tcp_test.go index a8f5719d7..c300c45e8 100644 --- a/pkg/server/server_entrypoint_tcp_test.go +++ b/pkg/server/server_entrypoint_tcp_test.go @@ -20,7 +20,9 @@ import ( ) func TestShutdownHijacked(t *testing.T) { - router := &tcprouter.Router{} + router, err := tcprouter.NewRouter() + require.NoError(t, err) + router.SetHTTPHandler(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { conn, _, err := rw.(http.Hijacker).Hijack() require.NoError(t, err) @@ -34,7 +36,9 @@ func TestShutdownHijacked(t *testing.T) { } func TestShutdownHTTP(t *testing.T) { - router := &tcprouter.Router{} + router, err := tcprouter.NewRouter() + require.NoError(t, err) + router.SetHTTPHandler(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { rw.WriteHeader(http.StatusOK) time.Sleep(time.Second) @@ -167,7 +171,9 @@ func TestReadTimeoutWithoutFirstByte(t *testing.T) { }, nil, nil) require.NoError(t, err) - router := &tcprouter.Router{} + router, err := tcprouter.NewRouter() + require.NoError(t, err) + router.SetHTTPHandler(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { rw.WriteHeader(http.StatusOK) })) @@ -204,7 +210,9 @@ func TestReadTimeoutWithFirstByte(t *testing.T) { }, nil, nil) require.NoError(t, err) - router := &tcprouter.Router{} + router, err := tcprouter.NewRouter() + require.NoError(t, err) + router.SetHTTPHandler(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { rw.WriteHeader(http.StatusOK) })) @@ -244,7 +252,9 @@ func TestKeepAliveMaxRequests(t *testing.T) { }, nil, nil) require.NoError(t, err) - router := &tcprouter.Router{} + router, err := tcprouter.NewRouter() + require.NoError(t, err) + router.SetHTTPHandler(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { rw.WriteHeader(http.StatusOK) })) @@ -290,7 +300,9 @@ func TestKeepAliveMaxTime(t *testing.T) { }, nil, nil) require.NoError(t, err) - router := &tcprouter.Router{} + router, err := tcprouter.NewRouter() + require.NoError(t, err) + router.SetHTTPHandler(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { rw.WriteHeader(http.StatusOK) })) diff --git a/pkg/tls/certificate_store_test.go b/pkg/tls/certificate_store_test.go index aacc9b759..2daec7c5a 100644 --- a/pkg/tls/certificate_store_test.go +++ b/pkg/tls/certificate_store_test.go @@ -47,7 +47,7 @@ func TestGetBestCertificate(t *testing.T) { expectedCert: "*.snitest.com", }, { - desc: "Best Match with dynamic wildcard only, case insensitive", + desc: "Best Match with dynamic wildcard only, case-insensitive", domainToCheck: "bar.www.snitest.com", dynamicCert: "*.www.snitest.com", expectedCert: "*.www.snitest.com", diff --git a/pkg/types/domain_test.go b/pkg/types/domain_test.go index f9f7d42cb..490e1732a 100644 --- a/pkg/types/domain_test.go +++ b/pkg/types/domain_test.go @@ -137,7 +137,7 @@ func TestMatchDomain(t *testing.T) { expected: true, }, { - desc: "dot replaced by a cahr", + desc: "dot replaced by a char", certDomain: "sub.sub.traefik.wtf", domain: "sub.sub.traefikiwtf", expected: false, diff --git a/script/gcg/traefik-bugfix.toml b/script/gcg/traefik-bugfix.toml index d7f6efa88..56b36a3df 100644 --- a/script/gcg/traefik-bugfix.toml +++ b/script/gcg/traefik-bugfix.toml @@ -4,11 +4,11 @@ RepositoryName = "traefik" OutputType = "file" FileName = "traefik_changelog.md" -# example new bugfix v3.1.2 +# example new bugfix v3.1.4 CurrentRef = "v3.1" -PreviousRef = "v3.1.1" +PreviousRef = "v3.1.3" BaseBranch = "v3.1" -FutureCurrentRefName = "v3.1.2" +FutureCurrentRefName = "v3.1.4" ThresholdPreviousRef = 10 ThresholdCurrentRef = 10 diff --git a/webui/src/components/_commons/NavBar.vue b/webui/src/components/_commons/NavBar.vue index d57d68dd5..8a618b9d4 100644 --- a/webui/src/components/_commons/NavBar.vue +++ b/webui/src/components/_commons/NavBar.vue @@ -129,7 +129,7 @@ align="left" icon="eva-github-outline" no-caps - label="Github repository" + label="GitHub repository" class="btn-submenu full-width" />