From 533c102d4f5e67f12b5ad074ea9a239272746aa4 Mon Sep 17 00:00:00 2001 From: Michael Date: Tue, 3 Sep 2024 14:02:03 +0200 Subject: [PATCH 01/21] Fix tracing documentation --- .../content/observability/tracing/overview.md | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) 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" From 3f74993f4ae8dd049b314e879f570f48f33bf10d Mon Sep 17 00:00:00 2001 From: tired-engineer <122563281+tired-engineer@users.noreply.github.com> Date: Tue, 3 Sep 2024 14:40:04 +0200 Subject: [PATCH 02/21] Fix typo in multiple DNS challenge provider warning --- docs/content/https/acme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/content/https/acme.md b/docs/content/https/acme.md index 3355bdbb2..1dad1cca4 100644 --- a/docs/content/https/acme.md +++ b/docs/content/https/acme.md @@ -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. From 3d92f1645f6ba9ecdc98e97a49b88dd8009bea7f Mon Sep 17 00:00:00 2001 From: Michael Date: Tue, 3 Sep 2024 15:12:04 +0200 Subject: [PATCH 03/21] Fix Go version to 1.23 when running Gateway API conformance tests --- .github/workflows/test-conformance.yaml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test-conformance.yaml b/.github/workflows/test-conformance.yaml index e6147d2c6..606242b09 100644 --- a/.github/workflows/test-conformance.yaml +++ b/.github/workflows/test-conformance.yaml @@ -5,11 +5,13 @@ on: branches: - '*' paths: + - '.github/workflows/test-conformance.yaml' - 'pkg/provider/kubernetes/gateway/**' + - 'integration/fixtures/k8s-conformance/**' - 'integration/k8s_conformance_test.go' env: - GO_VERSION: '1.22' + GO_VERSION: '1.23' CGO_ENABLED: 0 jobs: From be5c429825fd1c9e6501b33b60dfd43d3ba52148 Mon Sep 17 00:00:00 2001 From: Josh Soref <2119212+jsoref@users.noreply.github.com> Date: Mon, 9 Sep 2024 04:10:06 -0400 Subject: [PATCH 04/21] Unify tab titles --- docs/content/middlewares/http/overview.md | 2 +- docs/content/middlewares/overview.md | 2 +- docs/content/middlewares/tcp/overview.md | 2 +- docs/content/migration/v1-to-v2.md | 12 ++++++------ docs/content/providers/overview.md | 4 ++-- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/docs/content/middlewares/http/overview.md b/docs/content/middlewares/http/overview.md index c4bb0b942..412f94760 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: apiextensions.k8s.io/v1beta1 kind: CustomResourceDefinition diff --git a/docs/content/middlewares/overview.md b/docs/content/middlewares/overview.md index 5998f53bc..de799e842 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 bb385f240..3479c0611 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-whitelist@docker" ``` -```yaml tab="Kubernetes IngressRoute" +```yaml tab="IngressRoute" # As a Kubernetes Traefik IngressRoute apiVersion: apiextensions.k8s.io/v1beta1 kind: CustomResourceDefinition diff --git a/docs/content/migration/v1-to-v2.md b/docs/content/migration/v1-to-v2.md index 8992fff87..71099c580 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/providers/overview.md b/docs/content/providers/overview.md index fd30aca7d..ced9ff5ab 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: From 71d4b3b13c3a2d1792ccc26d3eb8e1854a451767 Mon Sep 17 00:00:00 2001 From: Roman Donchenko Date: Fri, 13 Sep 2024 11:04:07 +0300 Subject: [PATCH 05/21] Make the keys of the `accessLog.fields.names` map case-insensitive --- pkg/middlewares/accesslog/logger.go | 27 ++++++++++++++++++------ pkg/middlewares/accesslog/logger_test.go | 26 +++++++++++++++++++++++ 2 files changed, 46 insertions(+), 7 deletions(-) diff --git a/pkg/middlewares/accesslog/logger.go b/pkg/middlewares/accesslog/logger.go index cf729c6de..ed85a8888 100644 --- a/pkg/middlewares/accesslog/logger.go +++ b/pkg/middlewares/accesslog/logger.go @@ -105,15 +105,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{ @@ -332,7 +345,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 338467293..46563db61 100644 --- a/pkg/middlewares/accesslog/logger_test.go +++ b/pkg/middlewares/accesslog/logger_test.go @@ -462,6 +462,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 { From d547b943df3b9abcc9482a86ebfef395b23786c1 Mon Sep 17 00:00:00 2001 From: Josh Soref <2119212+jsoref@users.noreply.github.com> Date: Fri, 13 Sep 2024 05:40:04 -0400 Subject: [PATCH 06/21] Spelling --- .github/workflows/validate.yaml | 6 +++--- CODE_OF_CONDUCT.md | 2 +- cmd/traefik/traefik.go | 2 +- docs/content/https/acme.md | 2 +- docs/content/middlewares/http/inflightreq.md | 2 +- docs/content/migration/v2.md | 2 +- docs/content/operations/cli.md | 2 +- .../reference/dynamic-configuration/consul-catalog.md | 2 +- docs/content/reference/dynamic-configuration/docker.md | 2 +- docs/content/reference/dynamic-configuration/ecs.md | 2 +- docs/content/reference/dynamic-configuration/nomad.md | 2 +- docs/content/reference/dynamic-configuration/rancher.md | 2 +- docs/content/routing/providers/consul-catalog.md | 2 +- docs/content/routing/providers/docker.md | 2 +- docs/content/routing/providers/ecs.md | 2 +- docs/content/routing/providers/kv.md | 2 +- docs/content/routing/providers/marathon.md | 2 +- docs/content/routing/providers/nomad.md | 2 +- docs/content/routing/providers/rancher.md | 2 +- docs/content/routing/routers/index.md | 4 ++-- docs/content/user-guides/docker-compose/acme-dns/index.md | 2 +- integration/fixtures/https/clientca/README.md | 4 ++-- integration/log_rotation_test.go | 2 +- integration/simple_test.go | 2 +- integration/try/try.go | 2 +- pkg/config/kv/kv_node_test.go | 6 +++--- pkg/ip/strategy_test.go | 2 +- pkg/middlewares/accesslog/logger_test.go | 2 +- pkg/middlewares/customerrors/custom_errors.go | 2 +- pkg/middlewares/ratelimiter/rate_limiter.go | 2 +- pkg/provider/acme/provider.go | 2 +- pkg/provider/docker/builder_test.go | 2 +- pkg/provider/docker/config_test.go | 4 ++-- pkg/provider/docker/swarm_test.go | 6 +++--- pkg/redactor/redactor_config_test.go | 2 +- pkg/server/router/router_test.go | 2 +- pkg/tls/certificate_store_test.go | 2 +- pkg/types/domain_test.go | 2 +- webui/src/components/_commons/NavBar.vue | 2 +- 39 files changed, 48 insertions(+), 48 deletions(-) diff --git a/.github/workflows/validate.yaml b/.github/workflows/validate.yaml index 708b88e24..b7195defa 100644 --- a/.github/workflows/validate.yaml +++ b/.github/workflows/validate.yaml @@ -8,7 +8,7 @@ on: env: GO_VERSION: '1.23' GOLANGCI_LINT_VERSION: v1.60.3 - MISSSPELL_VERSION: v0.6.0 + MISSPELL_VERSION: v0.6.0 jobs: @@ -29,8 +29,8 @@ jobs: - name: Install golangci-lint ${{ env.GOLANGCI_LINT_VERSION }} run: curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin ${GOLANGCI_LINT_VERSION} - - name: Install missspell ${{ env.MISSSPELL_VERSION }} - run: curl -sfL https://raw.githubusercontent.com/golangci/misspell/master/install-misspell.sh | sh -s -- -b $(go env GOPATH)/bin ${MISSSPELL_VERSION} + - name: Install misspell ${{ env.MISSPELL_VERSION }} + run: curl -sfL https://raw.githubusercontent.com/golangci/misspell/master/install-misspell.sh | sh -s -- -b $(go env GOPATH)/bin ${MISSPELL_VERSION} - name: Avoid generating webui run: touch webui/static/index.html 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/cmd/traefik/traefik.go b/cmd/traefik/traefik.go index 1010ce80a..140242d3d 100644 --- a/cmd/traefik/traefik.go +++ b/cmd/traefik/traefik.go @@ -316,7 +316,7 @@ func setupServer(staticConfiguration *static.Configuration) (*server.Server, err } if _, ok := resolverNames[rt.TLS.CertResolver]; !ok { - log.WithoutContext().Errorf("the router %s uses a non-existent resolver: %s", rtName, rt.TLS.CertResolver) + log.WithoutContext().Errorf("Router %s uses a nonexistent resolver: %s", rtName, rt.TLS.CertResolver) } } }) diff --git a/docs/content/https/acme.md b/docs/content/https/acme.md index 1dad1cca4..07f65b5c3 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. diff --git a/docs/content/middlewares/http/inflightreq.md b/docs/content/middlewares/http/inflightreq.md index 273b6b25f..6354d5a14 100644 --- a/docs/content/middlewares/http/inflightreq.md +++ b/docs/content/middlewares/http/inflightreq.md @@ -335,7 +335,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/migration/v2.md b/docs/content/migration/v2.md index 979fc75e7..e0c000cad 100644 --- a/docs/content/migration/v2.md +++ b/docs/content/migration/v2.md @@ -429,7 +429,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/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/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/docker.md b/docs/content/reference/dynamic-configuration/docker.md index 1d56559bb..3b9163a53 100644 --- a/docs/content/reference/dynamic-configuration/docker.md +++ b/docs/content/reference/dynamic-configuration/docker.md @@ -8,7 +8,7 @@ description: "Reference dynamic configuration with Docker labels in Traefik Prox Dynamic configuration with Docker Labels {: .subtitle } -The labels are case insensitive. +The labels are case-insensitive. ```yaml labels: 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/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 index c2762999d..bd658d59f 100644 --- a/docs/content/reference/dynamic-configuration/rancher.md +++ b/docs/content/reference/dynamic-configuration/rancher.md @@ -8,7 +8,7 @@ description: "Read the official Traefik documentation to learn more on dynamic c Dynamic configuration with Rancher Labels {: .subtitle } -The labels are case insensitive. +The labels are case-insensitive. ```yaml labels: diff --git a/docs/content/routing/providers/consul-catalog.md b/docs/content/routing/providers/consul-catalog.md index 786cea2ae..e8b0dc2a2 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 e03e93dce..9e3bd38ae 100644 --- a/docs/content/routing/providers/docker.md +++ b/docs/content/routing/providers/docker.md @@ -147,7 +147,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 1d2e523ba..8f694025a 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 be4339b4c..79d16c04f 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 index 7c215f1b6..f8659582b 100644 --- a/docs/content/routing/providers/marathon.md +++ b/docs/content/routing/providers/marathon.md @@ -14,7 +14,7 @@ See also [Marathon user guide](../../user-guides/marathon.md). !!! 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/marathon.md). ### General diff --git a/docs/content/routing/providers/nomad.md b/docs/content/routing/providers/nomad.md index 35a2c5a5f..fd3b9fbac 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/rancher.md b/docs/content/routing/providers/rancher.md index 49215542f..99094331b 100644 --- a/docs/content/routing/providers/rancher.md +++ b/docs/content/routing/providers/rancher.md @@ -29,7 +29,7 @@ With Rancher, Traefik can leverage labels attached to a service to generate rout !!! 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/rancher.md). ### General diff --git a/docs/content/routing/routers/index.md b/docs/content/routing/routers/index.md index e7714af38..bec2ac632 100644 --- a/docs/content/routing/routers/index.md +++ b/docs/content/routing/routers/index.md @@ -259,7 +259,7 @@ The table below lists all the available matchers: The regexp name (`name` in the above example) is an arbitrary value, that exists only for historical reasons. Any `regexp` supported by [Go's regexp package](https://golang.org/pkg/regexp/) may be used. - For example, here is a case insensitive path matcher syntax: ```Path(`/{path:(?i:Products)}`)```. + For example, here is a case-insensitive path matcher syntax: ```Path(`/{path:(?i:Products)}`)```. !!! info "Combining Matchers Using Operators and Parenthesis" @@ -946,7 +946,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/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/log_rotation_test.go b/integration/log_rotation_test.go index ebabcfd5b..e5979e50d 100644 --- a/integration/log_rotation_test.go +++ b/integration/log_rotation_test.go @@ -27,7 +27,7 @@ const ( // 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 fc5936069..8ab5e0626 100644 --- a/integration/simple_test.go +++ b/integration/simple_test.go @@ -1288,7 +1288,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) diff --git a/integration/try/try.go b/integration/try/try.go index af54eb8f0..9f17267e0 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/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/middlewares/accesslog/logger_test.go b/pkg/middlewares/accesslog/logger_test.go index 46563db61..d205bb9d7 100644 --- a/pkg/middlewares/accesslog/logger_test.go +++ b/pkg/middlewares/accesslog/logger_test.go @@ -162,7 +162,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{ diff --git a/pkg/middlewares/customerrors/custom_errors.go b/pkg/middlewares/customerrors/custom_errors.go index 39d411622..bd5b78baa 100644 --- a/pkg/middlewares/customerrors/custom_errors.go +++ b/pkg/middlewares/customerrors/custom_errors.go @@ -249,7 +249,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/ratelimiter/rate_limiter.go b/pkg/middlewares/ratelimiter/rate_limiter.go index e06679787..390163463 100644 --- a/pkg/middlewares/ratelimiter/rate_limiter.go +++ b/pkg/middlewares/ratelimiter/rate_limiter.go @@ -147,7 +147,7 @@ func (rl *rateLimiter) ServeHTTP(w http.ResponseWriter, r *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.Errorf("could not insert/update bucket: %v", err) diff --git a/pkg/provider/acme/provider.go b/pkg/provider/acme/provider.go index f0a13d0ca..3b4c6d845 100644 --- a/pkg/provider/acme/provider.go +++ b/pkg/provider/acme/provider.go @@ -728,7 +728,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 9d55bd2b9..ed371e7ad 100644 --- a/pkg/provider/docker/config_test.go +++ b/pkg/provider/docker/config_test.go @@ -3870,7 +3870,7 @@ func TestSwarmGetIPAddress(t *testing.T) { networks map[string]*network.Summary }{ { - service: swarmService(withEndpointSpec(modeDNSSR)), + service: swarmService(withEndpointSpec(modeDNSRR)), expected: "", networks: map[string]*network.Summary{}, }, @@ -3935,7 +3935,7 @@ func TestSwarmGetPort(t *testing.T) { }{ { service: swarmService( - withEndpointSpec(modeDNSSR), + withEndpointSpec(modeDNSRR), ), networks: map[string]*network.Summary{}, serverPort: "8080", diff --git a/pkg/provider/docker/swarm_test.go b/pkg/provider/docker/swarm_test.go index 951983f82..b1e51083d 100644 --- a/pkg/provider/docker/swarm_test.go +++ b/pkg/provider/docker/swarm_test.go @@ -156,7 +156,7 @@ func TestListServices(t *testing.T) { "traefik.docker.network": "barnet", "traefik.docker.LBSwarm": "true", }), - withEndpointSpec(modeDNSSR)), + withEndpointSpec(modeDNSRR)), }, dockerVersion: "1.30", networks: []network.Summary{}, @@ -182,7 +182,7 @@ func TestListServices(t *testing.T) { "traefik.docker.network": "barnet", "traefik.docker.LBSwarm": "true", }), - withEndpointSpec(modeDNSSR)), + withEndpointSpec(modeDNSRR)), }, dockerVersion: "1.30", networks: []network.Summary{ @@ -227,7 +227,7 @@ func TestListServices(t *testing.T) { serviceLabels(map[string]string{ "traefik.docker.network": "barnet", }), - withEndpointSpec(modeDNSSR)), + withEndpointSpec(modeDNSRR)), }, tasks: []swarm.Task{ swarmTask("id1", diff --git a/pkg/redactor/redactor_config_test.go b/pkg/redactor/redactor_config_test.go index 1e7b8449e..468db5d62 100644 --- a/pkg/redactor/redactor_config_test.go +++ b/pkg/redactor/redactor_config_test.go @@ -786,7 +786,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 d5fba5af1..3f6c9bbe9 100644 --- a/pkg/server/router/router_test.go +++ b/pkg/server/router/router_test.go @@ -986,7 +986,7 @@ func BenchmarkService(b *testing.B) { LoadBalancer: &dynamic.ServersLoadBalancer{ Servers: []dynamic.Server{ { - URL: "tchouck", + URL: "tchouk", }, }, }, diff --git a/pkg/tls/certificate_store_test.go b/pkg/tls/certificate_store_test.go index aaf55b647..4cd541d78 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/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" /> From 0cf2032c1525cee2bb80ae3cf5a31e48a1f28999 Mon Sep 17 00:00:00 2001 From: Romain Date: Fri, 13 Sep 2024 15:54:04 +0200 Subject: [PATCH 07/21] Allow handling ACME challenges with custom routers --- .../reference/static-configuration/cli-ref.md | 3 ++ .../reference/static-configuration/env-ref.md | 3 ++ .../reference/static-configuration/file.toml | 1 + .../reference/static-configuration/file.yaml | 1 + docs/content/routing/entrypoints.md | 29 +++++++++++++++ pkg/config/static/entrypoints.go | 1 + pkg/provider/traefik/internal.go | 31 +++++++++++++--- pkg/server/router/tcp/router.go | 8 ++++- pkg/server/router/tcp/router_test.go | 25 ++++++++++--- pkg/server/routerfactory.go | 35 ++++++++++++++----- pkg/server/server_entrypoint_tcp.go | 5 ++- pkg/server/server_entrypoint_tcp_test.go | 24 +++++++++---- 12 files changed, 142 insertions(+), 24 deletions(-) diff --git a/docs/content/reference/static-configuration/cli-ref.md b/docs/content/reference/static-configuration/cli-ref.md index f98e1a177..362146ac0 100644 --- a/docs/content/reference/static-configuration/cli-ref.md +++ b/docs/content/reference/static-configuration/cli-ref.md @@ -108,6 +108,9 @@ 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..forwardedheaders.insecure`: Trust all forwarded headers. (Default: ```false```) diff --git a/docs/content/reference/static-configuration/env-ref.md b/docs/content/reference/static-configuration/env-ref.md index d71ffe6c7..6ca27db89 100644 --- a/docs/content/reference/static-configuration/env-ref.md +++ b/docs/content/reference/static-configuration/env-ref.md @@ -108,6 +108,9 @@ 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__FORWARDEDHEADERS_INSECURE`: Trust all forwarded headers. (Default: ```false```) diff --git a/docs/content/reference/static-configuration/file.toml b/docs/content/reference/static-configuration/file.toml index 42c4e6fdf..8f6f9510e 100644 --- a/docs/content/reference/static-configuration/file.toml +++ b/docs/content/reference/static-configuration/file.toml @@ -16,6 +16,7 @@ [entryPoints] [entryPoints.EntryPoint0] address = "foobar" + allowACMEByPass = true [entryPoints.EntryPoint0.transport] keepAliveMaxTime = "42s" keepAliveMaxRequests = 42 diff --git a/docs/content/reference/static-configuration/file.yaml b/docs/content/reference/static-configuration/file.yaml index abb8e05d8..09e66b545 100644 --- a/docs/content/reference/static-configuration/file.yaml +++ b/docs/content/reference/static-configuration/file.yaml @@ -16,6 +16,7 @@ serversTransport: entryPoints: EntryPoint0: address: foobar + allowACMEByPass: true transport: lifeCycle: requestAcceptGraceTimeout: 42s diff --git a/docs/content/routing/entrypoints.md b/docs/content/routing/entrypoints.md index e874764bc..ca34161d2 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 +``` + ### HTTP/2 #### `maxConcurrentStreams` diff --git a/pkg/config/static/entrypoints.go b/pkg/config/static/entrypoints.go index 905d74961..8bfbc59f1 100644 --- a/pkg/config/static/entrypoints.go +++ b/pkg/config/static/entrypoints.go @@ -12,6 +12,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"` Transport *EntryPointsTransport `description:"Configures communication between clients and Traefik." json:"transport,omitempty" toml:"transport,omitempty" yaml:"transport,omitempty" export:"true"` ProxyProtocol *ProxyProtocol `description:"Proxy-Protocol configuration." json:"proxyProtocol,omitempty" toml:"proxyProtocol,omitempty" yaml:"proxyProtocol,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` ForwardedHeaders *ForwardedHeaders `description:"Trust client forwarding headers." json:"forwardedHeaders,omitempty" toml:"forwardedHeaders,omitempty" yaml:"forwardedHeaders,omitempty" export:"true"` diff --git a/pkg/provider/traefik/internal.go b/pkg/provider/traefik/internal.go index 83bfee8bc..da44f8ff2 100644 --- a/pkg/provider/traefik/internal.go +++ b/pkg/provider/traefik/internal.go @@ -87,15 +87,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) } } @@ -110,6 +122,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/server/router/tcp/router.go b/pkg/server/router/tcp/router.go index ccb8c06bf..0da33e10a 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. @@ -148,7 +150,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 } @@ -303,6 +305,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 8f6ae37c1..2f997c7f1 100644 --- a/pkg/server/router/tcp/router_test.go +++ b/pkg/server/router/tcp/router_test.go @@ -209,9 +209,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", @@ -268,6 +269,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}, @@ -578,6 +591,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) @@ -699,7 +716,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 6b7b80ff4..c7534ef26 100644 --- a/pkg/server/routerfactory.go +++ b/pkg/server/routerfactory.go @@ -21,25 +21,37 @@ 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 - managerFactory *service.ManagerFactory metricsRegistry metrics.Registry pluginBuilder middleware.PluginsBuilder - - chainBuilder *middleware.ChainBuilder - tlsManager *tls.Manager + chainBuilder *middleware.ChainBuilder + tlsManager *tls.Manager } // NewRouterFactory creates a new RouterFactory. func NewRouterFactory(staticConfiguration static.Configuration, managerFactory *service.ManagerFactory, tlsManager *tls.Manager, chainBuilder *middleware.ChainBuilder, pluginBuilder middleware.PluginsBuilder, metricsRegistry metrics.Registry, ) *RouterFactory { + handlesTLSChallenge := false + for _, resolver := range staticConfiguration.CertificatesResolvers { + if 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.WithoutContext().Errorf("Invalid protocol: %v", err) @@ -60,6 +72,7 @@ func NewRouterFactory(staticConfiguration static.Configuration, managerFactory * tlsManager: tlsManager, chainBuilder: chainBuilder, pluginBuilder: pluginBuilder, + allowACMEByPass: allowACMEByPass, } } @@ -87,6 +100,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 := udp.NewManager(rtConf) rtUDPManager := udprouter.NewManager(rtConf, svcUDPManager) diff --git a/pkg/server/server_entrypoint_tcp.go b/pkg/server/server_entrypoint_tcp.go index 1fb371d08..6e30de331 100644 --- a/pkg/server/server_entrypoint_tcp.go +++ b/pkg/server/server_entrypoint_tcp.go @@ -172,7 +172,10 @@ func NewTCPEntryPoint(ctx context.Context, configuration *static.EntryPoint, hos 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) diff --git a/pkg/server/server_entrypoint_tcp_test.go b/pkg/server/server_entrypoint_tcp_test.go index f0b12c8dd..4dc9ee428 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) 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) 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) 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) 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) })) From 584144100524277829f26219baaab29a53b8134f Mon Sep 17 00:00:00 2001 From: Kevin Pollet Date: Mon, 16 Sep 2024 11:10:04 +0200 Subject: [PATCH 08/21] Cleanup Connection headers before passing the middleware chain Co-authored-by: Romain --- .../reference/static-configuration/cli-ref.md | 3 + .../reference/static-configuration/env-ref.md | 3 + .../reference/static-configuration/file.toml | 1 + .../reference/static-configuration/file.yaml | 3 + docs/content/routing/entrypoints.md | 34 +++ .../connection_hop_by_hop_headers.toml | 37 +++ integration/headers_test.go | 53 ++++ pkg/config/static/entrypoints.go | 1 + .../connectionheader.go | 2 +- .../connectionheader_test.go | 2 +- pkg/middlewares/auth/forward.go | 3 +- .../forwardedheaders/forwarded_header.go | 75 ++++- .../forwardedheaders/forwarded_header_test.go | 282 +++++++++++++++++- pkg/middlewares/headers/headers.go | 6 +- pkg/server/server_entrypoint_tcp.go | 1 + 15 files changed, 475 insertions(+), 31 deletions(-) create mode 100644 integration/fixtures/headers/connection_hop_by_hop_headers.toml rename pkg/middlewares/{connectionheader => auth}/connectionheader.go (97%) rename pkg/middlewares/{connectionheader => auth}/connectionheader_test.go (98%) diff --git a/docs/content/reference/static-configuration/cli-ref.md b/docs/content/reference/static-configuration/cli-ref.md index 362146ac0..540ff8ad0 100644 --- a/docs/content/reference/static-configuration/cli-ref.md +++ b/docs/content/reference/static-configuration/cli-ref.md @@ -111,6 +111,9 @@ Entry point address. `--entrypoints..allowacmebypass`: Enables handling of ACME TLS and HTTP challenges with custom routers. (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```) diff --git a/docs/content/reference/static-configuration/env-ref.md b/docs/content/reference/static-configuration/env-ref.md index 6ca27db89..173c18c3e 100644 --- a/docs/content/reference/static-configuration/env-ref.md +++ b/docs/content/reference/static-configuration/env-ref.md @@ -111,6 +111,9 @@ Entry point address. `TRAEFIK_ENTRYPOINTS__ALLOWACMEBYPASS`: Enables handling of ACME TLS and HTTP challenges with custom routers. (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```) diff --git a/docs/content/reference/static-configuration/file.toml b/docs/content/reference/static-configuration/file.toml index 8f6f9510e..89844adfe 100644 --- a/docs/content/reference/static-configuration/file.toml +++ b/docs/content/reference/static-configuration/file.toml @@ -33,6 +33,7 @@ [entryPoints.EntryPoint0.forwardedHeaders] insecure = true trustedIPs = ["foobar", "foobar"] + connection = ["foobar", "foobar"] [entryPoints.EntryPoint0.http] middlewares = ["foobar", "foobar"] encodeQuerySemicolons = true diff --git a/docs/content/reference/static-configuration/file.yaml b/docs/content/reference/static-configuration/file.yaml index 09e66b545..38541da46 100644 --- a/docs/content/reference/static-configuration/file.yaml +++ b/docs/content/reference/static-configuration/file.yaml @@ -37,6 +37,9 @@ entryPoints: trustedIPs: - foobar - foobar + connection: + - foobar + - foobar http: redirections: entryPoint: diff --git a/docs/content/routing/entrypoints.md b/docs/content/routing/entrypoints.md index ca34161d2..b87e09b92 100644 --- a/docs/content/routing/entrypoints.md +++ b/docs/content/routing/entrypoints.md @@ -422,6 +422,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/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/headers_test.go b/integration/headers_test.go index 68522cc47..2b6c53be2 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/pkg/config/static/entrypoints.go b/pkg/config/static/entrypoints.go index 8bfbc59f1..27b9f7a0b 100644 --- a/pkg/config/static/entrypoints.go +++ b/pkg/config/static/entrypoints.go @@ -110,6 +110,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/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 b7a64910a..1cd1da81a 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 70b3374ab..27d42973c 100644 --- a/pkg/middlewares/auth/forward.go +++ b/pkg/middlewares/auth/forward.go @@ -15,7 +15,6 @@ import ( "github.com/traefik/traefik/v2/pkg/config/dynamic" "github.com/traefik/traefik/v2/pkg/log" "github.com/traefik/traefik/v2/pkg/middlewares" - "github.com/traefik/traefik/v2/pkg/middlewares/connectionheader" "github.com/traefik/traefik/v2/pkg/tracing" "github.com/vulcand/oxy/v2/forward" "github.com/vulcand/oxy/v2/utils" @@ -90,7 +89,7 @@ func NewForward(ctx context.Context, next http.Handler, config dynamic.ForwardAu fa.authResponseHeadersRegex = re } - return connectionheader.Remover(fa), nil + return Remover(fa), nil } func (fa *forwardAuth) GetTracingInformation() (string, ext.SpanKindEnum) { diff --git a/pkg/middlewares/forwardedheaders/forwarded_header.go b/pkg/middlewares/forwardedheaders/forwarded_header.go index 19881ad74..6f715ccfb 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/v2/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 503944f24..5ed9f2807 100644 --- a/pkg/middlewares/headers/headers.go +++ b/pkg/middlewares/headers/headers.go @@ -10,7 +10,6 @@ import ( "github.com/traefik/traefik/v2/pkg/config/dynamic" "github.com/traefik/traefik/v2/pkg/log" "github.com/traefik/traefik/v2/pkg/middlewares" - "github.com/traefik/traefik/v2/pkg/middlewares/connectionheader" "github.com/traefik/traefik/v2/pkg/tracing" ) @@ -69,12 +68,11 @@ func New(ctx context.Context, next http.Handler, cfg dynamic.Headers, name strin if hasCustomHeaders || hasCorsHeaders { logger.Debugf("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/server/server_entrypoint_tcp.go b/pkg/server/server_entrypoint_tcp.go index 6e30de331..5cd6cbb49 100644 --- a/pkg/server/server_entrypoint_tcp.go +++ b/pkg/server/server_entrypoint_tcp.go @@ -565,6 +565,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 From 8c977b8f8cb43396c21bd38c96e01fc5c617907b Mon Sep 17 00:00:00 2001 From: Julien Salleyron Date: Mon, 16 Sep 2024 11:12:04 +0200 Subject: [PATCH 09/21] Removes goexport dependency and adds _initialize --- go.mod | 2 +- go.sum | 2 -- pkg/plugins/middlewarewasm.go | 5 ++--- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 545e61b52..b7ae066bf 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 @@ -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..63b785fcf 100644 --- a/go.sum +++ b/go.sum @@ -590,8 +590,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= 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)) } From 9750bbc353cca9d690e0b934bea065a459b1199a Mon Sep 17 00:00:00 2001 From: Lucas Rodriguez Date: Mon, 16 Sep 2024 04:30:04 -0500 Subject: [PATCH 10/21] Configurable max request header size --- .../reference/static-configuration/cli-ref.md | 3 + .../reference/static-configuration/env-ref.md | 3 + .../reference/static-configuration/file.toml | 1 + .../reference/static-configuration/file.yaml | 1 + .../fixtures/simple_max_header_size.toml | 25 ++++++++ integration/simple_test.go | 60 +++++++++++++++++++ pkg/config/static/entrypoints.go | 9 +++ pkg/server/server_entrypoint_tcp.go | 11 ++-- 8 files changed, 108 insertions(+), 5 deletions(-) create mode 100644 integration/fixtures/simple_max_header_size.toml diff --git a/docs/content/reference/static-configuration/cli-ref.md b/docs/content/reference/static-configuration/cli-ref.md index b0c02c565..617359216 100644 --- a/docs/content/reference/static-configuration/cli-ref.md +++ b/docs/content/reference/static-configuration/cli-ref.md @@ -141,6 +141,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..045da7830 100644 --- a/docs/content/reference/static-configuration/env-ref.md +++ b/docs/content/reference/static-configuration/env-ref.md @@ -150,6 +150,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..8f899b211 100644 --- a/docs/content/reference/static-configuration/file.toml +++ b/docs/content/reference/static-configuration/file.toml @@ -51,6 +51,7 @@ [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..31b8e485f 100644 --- a/docs/content/reference/static-configuration/file.yaml +++ b/docs/content/reference/static-configuration/file.yaml @@ -80,6 +80,7 @@ entryPoints: - foobar - foobar encodeQuerySemicolons: true + maxHeaderBytes: 42 http2: maxConcurrentStreams: 42 http3: 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/simple_test.go b/integration/simple_test.go index 9aae5c1b2..e57fb2c9a 100644 --- a/integration/simple_test.go +++ b/integration/simple_test.go @@ -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/pkg/config/static/entrypoints.go b/pkg/config/static/entrypoints.go index 9b48ddce4..bc3f3c30c 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" @@ -53,6 +54,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 +66,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. diff --git a/pkg/server/server_entrypoint_tcp.go b/pkg/server/server_entrypoint_tcp.go index ca901c90f..e40244219 100644 --- a/pkg/server/server_entrypoint_tcp.go +++ b/pkg/server/server_entrypoint_tcp.go @@ -633,11 +633,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 { From f90f9df1db95bccb6bf5f46f90f350134ce62f8d Mon Sep 17 00:00:00 2001 From: Andrea Cappuccio Date: Mon, 16 Sep 2024 12:06:03 +0200 Subject: [PATCH 11/21] Ensure proper logs for aborted streaming responses --- pkg/middlewares/accesslog/logger.go | 45 +++---- pkg/middlewares/accesslog/logger_test.go | 146 +++++++++++++++++++++++ 2 files changed, 169 insertions(+), 22 deletions(-) diff --git a/pkg/middlewares/accesslog/logger.go b/pkg/middlewares/accesslog/logger.go index ed85a8888..b5a533b8c 100644 --- a/pkg/middlewares/accesslog/logger.go +++ b/pkg/middlewares/accesslog/logger.go @@ -196,16 +196,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() @@ -249,19 +239,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). diff --git a/pkg/middlewares/accesslog/logger_test.go b/pkg/middlewares/accesslog/logger_test.go index d205bb9d7..01433c5f6 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" "encoding/json" "fmt" @@ -22,6 +23,7 @@ import ( "github.com/stretchr/testify/require" ptypes "github.com/traefik/paerser/types" "github.com/traefik/traefik/v2/pkg/middlewares/capture" + "github.com/traefik/traefik/v2/pkg/middlewares/recovery" "github.com/traefik/traefik/v2/pkg/types" ) @@ -519,6 +521,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 @@ -852,3 +912,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() + } + } +} From 06d7fab820dc1e486fe59371aeb359c965767ded Mon Sep 17 00:00:00 2001 From: Kevin Pollet Date: Mon, 16 Sep 2024 15:26:12 +0200 Subject: [PATCH 12/21] Prepare release v2.11.9 --- CHANGELOG.md | 20 ++++++++++++++++++++ script/gcg/traefik-bugfix.toml | 6 +++--- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d787fa989..f29591330 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,23 @@ +## [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)) + ## [v2.11.8](https://github.com/traefik/traefik/tree/v2.11.8) (2024-08-06) [All Commits](https://github.com/traefik/traefik/compare/v2.11.7...v2.11.8) diff --git a/script/gcg/traefik-bugfix.toml b/script/gcg/traefik-bugfix.toml index 984605fa4..f3af5d7fc 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 v2.11.8 +# example new bugfix v2.11.9 CurrentRef = "v2.11" -PreviousRef = "v2.11.7" +PreviousRef = "v2.11.8" BaseBranch = "v2.11" -FutureCurrentRefName = "v2.11.8" +FutureCurrentRefName = "v2.11.9" ThresholdPreviousRef = 10 ThresholdCurrentRef = 10 From 89f3b272c3d4ed3f075f92f8acca494cdba4f708 Mon Sep 17 00:00:00 2001 From: Kevin Pollet Date: Mon, 16 Sep 2024 17:06:03 +0200 Subject: [PATCH 13/21] Prepare release v3.1.3 --- CHANGELOG.md | 21 +++++++++++++++++++++ script/gcg/traefik-bugfix.toml | 6 +++--- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 24b1ceff8..b9090fb16 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,24 @@ +## [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) diff --git a/script/gcg/traefik-bugfix.toml b/script/gcg/traefik-bugfix.toml index d7f6efa88..030bc014b 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.3 CurrentRef = "v3.1" -PreviousRef = "v3.1.1" +PreviousRef = "v3.1.2" BaseBranch = "v3.1" -FutureCurrentRefName = "v3.1.2" +FutureCurrentRefName = "v3.1.3" ThresholdPreviousRef = 10 ThresholdCurrentRef = 10 From 1ebd12ff82394cc154bfa87c94847c2c5c1b49b1 Mon Sep 17 00:00:00 2001 From: Romain Date: Tue, 17 Sep 2024 10:50:04 +0200 Subject: [PATCH 14/21] Add support for Gateway API BackendTLSPolicies --- .../kubernetes-gateway-rbac.yml | 6 +- .../fixtures/k8s-conformance/01-rbac.yml | 3 + ...api-crd.yml => 00-experimental-v1.1.0.yml} | 0 pkg/provider/kubernetes/gateway/client.go | 199 +++++++++++++---- .../httproute/with_backend_tls_policy.yml | 78 +++++++ .../with_backend_tls_policy_system.yml | 66 ++++++ pkg/provider/kubernetes/gateway/httproute.go | 166 ++++++++++++-- .../kubernetes/gateway/kubernetes_test.go | 203 ++++++++++++++++++ pkg/provider/kubernetes/k8s/parser.go | 2 +- 9 files changed, 657 insertions(+), 66 deletions(-) rename integration/fixtures/k8s/{01-gateway-api-crd.yml => 00-experimental-v1.1.0.yml} (100%) create mode 100644 pkg/provider/kubernetes/gateway/fixtures/httproute/with_backend_tls_policy.yml create mode 100644 pkg/provider/kubernetes/gateway/fixtures/httproute/with_backend_tls_policy_system.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/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/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/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_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)) From bbeceba580a462a4800d6042b2ca87d4319dc35e Mon Sep 17 00:00:00 2001 From: Karl Anthony Baluyot <38805756+kabaluyot@users.noreply.github.com> Date: Tue, 17 Sep 2024 21:20:04 +0800 Subject: [PATCH 15/21] Mention v3 in readme --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) 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). From 42e1f2c9b14a31afb92c0b6b04bff0b0c0b0b0a9 Mon Sep 17 00:00:00 2001 From: Romain Date: Tue, 17 Sep 2024 16:40:04 +0200 Subject: [PATCH 16/21] Add supported features to the Gateway API GatewayClass status --- integration/k8s_conformance_test.go | 20 ++-------------- pkg/provider/kubernetes/gateway/features.go | 23 +++++++++++++++++++ pkg/provider/kubernetes/gateway/kubernetes.go | 7 ++++++ 3 files changed, 32 insertions(+), 18 deletions(-) create mode 100644 pkg/provider/kubernetes/gateway/features.go 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/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/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 { From 4b5968e0cc39271968e5fa707e674ce9c17a44b3 Mon Sep 17 00:00:00 2001 From: Romain Date: Thu, 19 Sep 2024 11:36:04 +0200 Subject: [PATCH 17/21] Bump github.com/quic-go/quic-go to v0.47.0 --- go.mod | 30 +++++++++++++-------------- go.sum | 64 +++++++++++++++++++++++++++++----------------------------- 2 files changed, 47 insertions(+), 47 deletions(-) diff --git a/go.mod b/go.mod index c85e0412a..a32b04ba6 100644 --- a/go.mod +++ b/go.mod @@ -51,7 +51,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/rancher/go-rancher-metadata v0.0.0-20200311180630-7f4c936a06ac // No tag on the repo. github.com/sirupsen/logrus v1.9.3 github.com/stretchr/testify v1.9.0 @@ -67,12 +67,12 @@ require ( github.com/vulcand/predicate v1.2.0 go.elastic.co/apm/module/apmot/v2 v2.4.8 go.elastic.co/apm/v2 v2.4.8 - 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/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/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.63.1 gopkg.in/DataDog/dd-trace-go.v1 v1.56.1 gopkg.in/yaml.v3 v3.0.1 @@ -173,14 +173,14 @@ require ( github.com/go-errors/errors v1.0.1 // indirect github.com/go-jose/go-jose/v4 v4.0.2 // indirect github.com/go-logfmt/logfmt v0.5.1 // indirect - github.com/go-logr/logr v1.4.1 // indirect + github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-ole/go-ole v1.2.6 // indirect github.com/go-openapi/jsonpointer v0.19.5 // indirect github.com/go-openapi/jsonreference v0.20.0 // indirect github.com/go-openapi/swag v0.19.14 // 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 @@ -194,7 +194,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-20240402174815-29b9bb013b0f // 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 @@ -263,7 +263,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-contrib/go-observer v0.0.0-20170622124052-a52f23424492 // indirect @@ -277,7 +277,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/sacloud/api-client-go v0.2.10 // indirect github.com/sacloud/go-http v0.1.8 // indirect @@ -328,17 +328,17 @@ require ( go.uber.org/zap v1.21.0 // indirect go4.org/intern v0.0.0-20230525184215-6c62f75575cb // indirect go4.org/unsafe/assume-no-moving-gc v0.0.0-20230525183740-e7c30c78aeb2 // 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/sys v0.23.0 // indirect - golang.org/x/term v0.23.0 // indirect + golang.org/x/sys v0.25.0 // indirect + golang.org/x/term v0.24.0 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // 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-20240311132316-a219d84964c2 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 // indirect - google.golang.org/protobuf v1.33.0 // indirect + google.golang.org/protobuf v1.34.1 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/ns1/ns1-go.v2 v2.9.1 // indirect diff --git a/go.sum b/go.sum index 75a34ba8f..4d4a05089 100644 --- a/go.sum +++ b/go.sum @@ -410,8 +410,8 @@ github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTg github.com/go-logr/logr v0.4.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= -github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-logr/zapr v0.4.0/go.mod h1:tabnROwaDl0UNxkVeFRbY8bwB37GwRv0P8lg6aAiEnk= @@ -443,8 +443,8 @@ 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= @@ -564,8 +564,8 @@ github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20240402174815-29b9bb013b0f h1:f00RU+zOX+B3rLAmMMkzHUF2h1z4DeYR9tTCvEq2REY= -github.com/google/pprof v0.0.0-20240402174815-29b9bb013b0f/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw= +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= @@ -951,8 +951,8 @@ 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 v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= @@ -962,8 +962,8 @@ github.com/onsi/gomega v1.14.0/go.mod h1:cIuvLEne0aoVhAgh/O6ac0Op8WWw9H6eYCriF+t 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.31.1 h1:KYppCUK+bUgAZwHOu7EXVBKyQA6ILvOESHkn/tgoqvo= -github.com/onsi/gomega v1.31.1/go.mod h1:y40C95dwAD1Nz36SsEnxvfFe8FFfNxzI5eJ0EYGyAy0= +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= @@ -1058,10 +1058,10 @@ github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1 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/rancher/go-rancher-metadata v0.0.0-20200311180630-7f4c936a06ac h1:wBGhHdXKICZmvAPWS8gQoMyOWDH7QAi9bU4Z1nDWnFU= github.com/rancher/go-rancher-metadata v0.0.0-20200311180630-7f4c936a06ac/go.mod h1:67sLWL17mVlO1HFROaTBmU71NB4R8UNCesFHhg0f6LQ= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= @@ -1349,8 +1349,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= @@ -1364,8 +1364,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -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= @@ -1396,8 +1396,8 @@ 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.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= 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= @@ -1457,8 +1457,8 @@ golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= 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= @@ -1588,8 +1588,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-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -1601,8 +1601,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.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1618,8 +1618,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-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -1699,8 +1699,8 @@ golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyj golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ= 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= @@ -1834,8 +1834,8 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= -google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= +google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/DataDog/dd-trace-go.v1 v1.56.1 h1:AUe/ZF7xm6vYnigPe+TY54DmfWYJxhMRaw/TfvrbzvE= gopkg.in/DataDog/dd-trace-go.v1 v1.56.1/go.mod h1:KDLJ3CWVOSuVVwu+0ZR5KZo2rP6c7YyBV3v387dIpUU= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= From ac42dd8f83d21601fd97393d5a5b11ebfcad3440 Mon Sep 17 00:00:00 2001 From: Kevin Pollet Date: Thu, 19 Sep 2024 11:50:04 +0200 Subject: [PATCH 18/21] Check if ACME certificate resolver is not nil --- pkg/server/routerfactory.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/server/routerfactory.go b/pkg/server/routerfactory.go index c7534ef26..e2941028e 100644 --- a/pkg/server/routerfactory.go +++ b/pkg/server/routerfactory.go @@ -40,7 +40,7 @@ func NewRouterFactory(staticConfiguration static.Configuration, managerFactory * ) *RouterFactory { handlesTLSChallenge := false for _, resolver := range staticConfiguration.CertificatesResolvers { - if resolver.ACME.TLSChallenge != nil { + if resolver.ACME != nil && resolver.ACME.TLSChallenge != nil { handlesTLSChallenge = true break } From b00f640d7262a63766c69d1942470aa035de362c Mon Sep 17 00:00:00 2001 From: Romain Date: Thu, 19 Sep 2024 12:08:04 +0200 Subject: [PATCH 19/21] Prepare release v2.11.10 --- CHANGELOG.md | 7 +++++++ script/gcg/traefik-bugfix.toml | 6 +++--- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f29591330..7cca4f6a1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [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)) + ## [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) diff --git a/script/gcg/traefik-bugfix.toml b/script/gcg/traefik-bugfix.toml index f3af5d7fc..b2a7f7401 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 v2.11.9 +# example new bugfix v2.11.10 CurrentRef = "v2.11" -PreviousRef = "v2.11.8" +PreviousRef = "v2.11.9" BaseBranch = "v2.11" -FutureCurrentRefName = "v2.11.9" +FutureCurrentRefName = "v2.11.10" ThresholdPreviousRef = 10 ThresholdCurrentRef = 10 From f3eba8d3a24b3fcc07ebc5f9a4d8e43c61675ac0 Mon Sep 17 00:00:00 2001 From: Kevin Pollet Date: Thu, 19 Sep 2024 15:30:05 +0200 Subject: [PATCH 20/21] Guess Datadog socket type when prefix is unix --- docs/content/observability/metrics/datadog.md | 4 +- pkg/metrics/datadog.go | 62 ++++++++++++++----- pkg/metrics/datadog_test.go | 12 ++++ 3 files changed, 63 insertions(+), 15 deletions(-) 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/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 { From 0be01cc067a3089fd9b108dc84e146d223962882 Mon Sep 17 00:00:00 2001 From: Kevin Pollet Date: Thu, 19 Sep 2024 15:44:04 +0200 Subject: [PATCH 21/21] Prepare release v3.1.4 --- CHANGELOG.md | 12 ++++++++++++ script/gcg/traefik-bugfix.toml | 6 +++--- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b81fb4e25..77ccc3844 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,15 @@ +## [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) diff --git a/script/gcg/traefik-bugfix.toml b/script/gcg/traefik-bugfix.toml index 030bc014b..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.3 +# example new bugfix v3.1.4 CurrentRef = "v3.1" -PreviousRef = "v3.1.2" +PreviousRef = "v3.1.3" BaseBranch = "v3.1" -FutureCurrentRefName = "v3.1.3" +FutureCurrentRefName = "v3.1.4" ThresholdPreviousRef = 10 ThresholdCurrentRef = 10