From 7c72780820a9d397ef12da4e323d0c3b70ac13a7 Mon Sep 17 00:00:00 2001 From: Romain Date: Thu, 24 Nov 2022 12:30:05 +0100 Subject: [PATCH 01/19] Add missing serialNumber passTLSClientCert option to middleware panel --- webui/src/components/_commons/PanelMiddlewares.vue | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/webui/src/components/_commons/PanelMiddlewares.vue b/webui/src/components/_commons/PanelMiddlewares.vue index 3437f8bef..db88fb96f 100644 --- a/webui/src/components/_commons/PanelMiddlewares.vue +++ b/webui/src/components/_commons/PanelMiddlewares.vue @@ -831,10 +831,16 @@
Not Before
+ +
Sans
+
+
Serial Number
+ +
From 18d66d7432e7530b3c5856ed43bfd50677d34379 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Mon, 28 Nov 2022 08:48:04 +0100 Subject: [PATCH 02/19] Update go-acme/lego to v4.9.1 --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 7c35dcbcf..cb9086d78 100644 --- a/go.mod +++ b/go.mod @@ -19,7 +19,7 @@ require ( github.com/docker/go-connections v0.4.0 github.com/fatih/structs v1.1.0 github.com/gambol99/go-marathon v0.0.0-20180614232016-99a156b96fb2 - github.com/go-acme/lego/v4 v4.9.0 + github.com/go-acme/lego/v4 v4.9.1 github.com/go-check/check v0.0.0-00010101000000-000000000000 github.com/go-kit/kit v0.10.1-0.20200915143503-439c4d2ed3ea github.com/golang/protobuf v1.5.2 diff --git a/go.sum b/go.sum index 30ae478c4..8c1a6e5a6 100644 --- a/go.sum +++ b/go.sum @@ -655,8 +655,8 @@ github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm github.com/gin-gonic/gin v1.7.4/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY= github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= -github.com/go-acme/lego/v4 v4.9.0 h1:8Hjj44IqRS7cigshMyFQ+0pIZvwgkG/+9A0UnNh7G8A= -github.com/go-acme/lego/v4 v4.9.0/go.mod h1:g3JRUyWS3L/VObpp4bCxzJftKyf/Wba8QrSSnoiqjg4= +github.com/go-acme/lego/v4 v4.9.1 h1:n9Z5MQwANeGSQKlVE3bEh9SDvAySK9oVYOKCGCESqQE= +github.com/go-acme/lego/v4 v4.9.1/go.mod h1:g3JRUyWS3L/VObpp4bCxzJftKyf/Wba8QrSSnoiqjg4= github.com/go-asn1-ber/asn1-ber v1.3.1/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= github.com/go-chi/chi/v5 v5.0.0/go.mod h1:BBug9lr0cqtdAhsu6R4AAdvufI0/XBzAQSsUqJpoZOs= github.com/go-cmd/cmd v1.0.5/go.mod h1:y8q8qlK5wQibcw63djSl/ntiHUHXHGdCkPk0j4QeW4s= From 4d86668af3bdee54da63664f45615eb2397ddd78 Mon Sep 17 00:00:00 2001 From: Antoine <13622487+skwair@users.noreply.github.com> Date: Mon, 28 Nov 2022 15:48:05 +0100 Subject: [PATCH 03/19] Update routing syntax Co-authored-by: Tom Moulard --- docs/content/migration/v2-to-v3.md | 15 + docs/content/operations/dashboard.md | 4 +- docs/content/routing/routers/index.md | 402 ++++++-- go.mod | 7 +- go.sum | 15 +- .../https_sni_case_insensitive_dynamic.toml | 4 +- integration/fixtures/simple_muxer.toml | 5 +- .../fixtures/websocket/config_https.toml | 2 +- integration/https_test.go | 2 +- integration/simple_test.go | 6 +- pkg/muxer/http/matcher.go | 253 +++++ pkg/muxer/http/matcher_test.go | 974 ++++++++++++++++++ pkg/muxer/http/mux.go | 242 +---- pkg/muxer/http/mux_test.go | 808 +++------------ pkg/muxer/tcp/matcher.go | 134 +++ pkg/muxer/tcp/matcher_test.go | 383 +++++++ pkg/muxer/tcp/mux.go | 241 +---- pkg/muxer/tcp/mux_test.go | 958 +++-------------- pkg/provider/hub/hub.go | 2 +- pkg/provider/kubernetes/gateway/kubernetes.go | 42 +- .../kubernetes/gateway/kubernetes_test.go | 44 +- pkg/provider/kubernetes/ingress/kubernetes.go | 6 +- .../kubernetes/ingress/kubernetes_test.go | 6 +- .../traefik/fixtures/redirection.json | 4 +- .../traefik/fixtures/redirection_port.json | 4 +- .../fixtures/redirection_with_protocol.json | 4 +- pkg/provider/traefik/internal.go | 2 +- 27 files changed, 2484 insertions(+), 2085 deletions(-) create mode 100644 pkg/muxer/http/matcher.go create mode 100644 pkg/muxer/http/matcher_test.go create mode 100644 pkg/muxer/tcp/matcher.go create mode 100644 pkg/muxer/tcp/matcher_test.go diff --git a/docs/content/migration/v2-to-v3.md b/docs/content/migration/v2-to-v3.md index 31218c466..4a8a9bf24 100644 --- a/docs/content/migration/v2-to-v3.md +++ b/docs/content/migration/v2-to-v3.md @@ -30,3 +30,18 @@ In v3, the reported status code for gRPC requests is now the value of the `Grpc- - `sslRedirect`, `sslTemporaryRedirect`, `sslHost`, `sslForceHost` and `featurePolicy` options of the Headers middleware have been removed. - The `forceSlash` option of the StripPrefix middleware has been removed. - the `preferServerCipherSuites` option has been removed. + +## Matchers + +In v3, the `Headers` and `HeadersRegexp` matchers have been renamed to `Header` and `HeaderRegexp` respectively. + +`QueryRegexp` has been introduced to match query values using a regular expression. + +`HeaderRegexp`, `HostRegexp`, `PathRegexp`, `QueryRegexp`, and `HostSNIRegexp` matchers now uses the [Go regexp syntax](https://golang.org/pkg/regexp/syntax/). + +All matchers now take a single value (except `Headers`, `HeaderRegexp`, `Query`, and `QueryRegexp` which take two) +and should be explicitly combined using logical operators to mimic previous behavior. + +`Query` can take a single value to match is the query value that has no value (e.g. `/search?mobile`). + +`HostHeader` has been removed, use `Host` instead. diff --git a/docs/content/operations/dashboard.md b/docs/content/operations/dashboard.md index 61e17e742..5b79dfc98 100644 --- a/docs/content/operations/dashboard.md +++ b/docs/content/operations/dashboard.md @@ -93,12 +93,12 @@ rule = "Host(`traefik.example.com`)" ```bash tab="Path Prefix Rule" # The dashboard can be accessed on http://example.com/dashboard/ or http://traefik.example.com/dashboard/ -rule = "PathPrefix(`/api`, `/dashboard`)" +rule = "PathPrefix(`/api`) || PathPrefix(`/dashboard`)" ``` ```bash tab="Combination of Rules" # The dashboard can be accessed on http://traefik.example.com/dashboard/ -rule = "Host(`traefik.example.com`) && PathPrefix(`/api`, `/dashboard`)" +rule = "Host(`traefik.example.com`) && (PathPrefix(`/api`) || PathPrefix(`/dashboard`))" ``` ??? example "Dashboard Dynamic Configuration Examples" diff --git a/docs/content/routing/routers/index.md b/docs/content/routing/routers/index.md index 557dcd8ef..14acc4448 100644 --- a/docs/content/routing/routers/index.md +++ b/docs/content/routing/routers/index.md @@ -214,78 +214,224 @@ If you want to limit the router scope to a set of entry points, set the `entryPo Rules are a set of matchers configured with values, that determine if a particular request matches specific criteria. If the rule is verified, the router becomes active, calls middlewares, and then forwards the request to the service. -??? tip "Backticks or Quotes?" - To set the value of a rule, use [backticks](https://en.wiktionary.org/wiki/backtick) ``` ` ``` or escaped double-quotes `\"`. - - Single quotes `'` are not accepted since the values are [Golang's String Literals](https://golang.org/ref/spec#String_literals). - -!!! example "Host is example.com" - - ```toml - rule = "Host(`example.com`)" - ``` - -!!! example "Host is example.com OR Host is example.org AND path is /traefik" - - ```toml - rule = "Host(`example.com`) || (Host(`example.org`) && Path(`/traefik`))" - ``` - The table below lists all the available matchers: -| Rule | Description | -|------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------| -| ```Headers(`key`, `value`)``` | Check if there is a key `key`defined in the headers, with the value `value` | -| ```HeadersRegexp(`key`, `regexp`)``` | Check if there is a key `key`defined in the headers, with a value that matches the regular expression `regexp` | -| ```Host(`example.com`, ...)``` | Check if the request domain (host header value) targets one of the given `domains`. | -| ```HostHeader(`example.com`, ...)``` | Same as `Host`, only exists for historical reasons. | -| ```HostRegexp(`example.com`, `{subdomain:[a-z]+}.example.com`, ...)``` | Match the request domain. See "Regexp Syntax" below. | -| ```Method(`GET`, ...)``` | Check if the request method is one of the given `methods` (`GET`, `POST`, `PUT`, `DELETE`, `PATCH`, `HEAD`) | -| ```Path(`/path`, `/articles/{cat:[a-z]+}/{id:[0-9]+}`, ...)``` | Match exact request path. See "Regexp Syntax" below. | -| ```PathPrefix(`/products/`, `/articles/{cat:[a-z]+}/{id:[0-9]+}`)``` | Match request prefix path. See "Regexp Syntax" below. | -| ```Query(`foo=bar`, `bar=baz`)``` | Match Query String parameters. It accepts a sequence of key=value pairs. | -| ```ClientIP(`10.0.0.0/16`, `::1`)``` | Match if the request client IP is one of the given IP/CIDR. It accepts IPv4, IPv6 and CIDR formats. | +| Rule | Description | +|-----------------------------------------------------------------|:-------------------------------------------------------------------------------| +| [```Header(`key`, `value`)```](#header-and-headerregexp) | Matches requests containing a header named `key` set to `value`. | +| [```HeaderRegexp(`key`, `regexp`)```](#header-and-headerregexp) | Matches requests containing a header named `key` matching `regexp`. | +| [```Host(`domain`)```](#host-and-hostregexp) | Matches requests host set to `domain`. | +| [```HostRegexp(`regexp`)```](#host-and-hostregexp) | Matches requests host matching `regexp`. | +| [```Method(`method`)```](#method) | Matches requests method set to `method`. | +| [```Path(`path`)```](#path-pathprefix-and-pathregexp) | Matches requests path set to `path`. | +| [```PathPrefix(`prefix`)```](#path-pathprefix-and-pathregexp) | Matches requests path prefix set to `prefix`. | +| [```PathRegexp(`regexp`)```](#path-pathprefix-and-pathregexp) | Matches request path using `regexp`. | +| [```Query(`key`, `value`)```](#query-and-queryregexp) | Matches requests query parameters named `key` set to `value`. | +| [```QueryRegexp(`key`, `regexp`)```](#query-and-queryregexp) | Matches requests query parameters named `key` matching `regexp`. | +| [```ClientIP(`ip`)```](#clientip) | Matches requests client IP using `ip`. It accepts IPv4, IPv6 and CIDR formats. | -!!! important "Non-ASCII Domain Names" +!!! tip "Backticks or Quotes?" - Non-ASCII characters are not supported in `Host` and `HostRegexp` expressions, and by doing so the associated router will be invalid. - For the `Host` expression, domain names containing non-ASCII characters must be provided as punycode encoded values ([rfc 3492](https://tools.ietf.org/html/rfc3492)). - As well, when using the `HostRegexp` expressions, in order to match domain names containing non-ASCII characters, the regular expression should match a punycode encoded domain name. + To set the value of a rule, use [backticks](https://en.wiktionary.org/wiki/backtick) ``` ` ``` or escaped double-quotes `\"`. + + Single quotes `'` are not accepted since the values are [Go's String Literals](https://golang.org/ref/spec#String_literals). !!! important "Regexp Syntax" - `HostRegexp`, `PathPrefix`, and `Path` accept an expression with zero or more groups enclosed by curly braces, which are called named regexps. - Named regexps, of the form `{name:regexp}`, are the only expressions considered for regexp matching. - The regexp name (`name` in the above example) is an arbitrary value, that exists only for historical reasons. + Matchers that accept a regexp as their value use a [Go](https://golang.org/pkg/regexp/) flavored syntax. - 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)}`)```. - -!!! info "Combining Matchers Using Operators and Parenthesis" +!!! info "Expressing Complex Rules Using Operators and Parenthesis" The usual AND (`&&`) and OR (`||`) logical operators can be used, with the expected precedence rules, as well as parentheses. + + One can invert a matcher by using the NOT (`!`) operator. + + The following rule matches requests where: + + - either host is `example.com` OR, + - host is `example.org` AND path is NOT `/traefik` + + ```yaml + Host(`example.com`) || (Host(`example.org`) && !Path(`/traefik`)) + ``` -!!! info "Inverting a matcher" +#### Header and HeaderRegexp - One can invert a matcher by using the `!` operator. +The `Header` and `HeaderRegexp` matchers allow to match requests that contain specific header. -!!! important "Rule, Middleware, and Services" +!!! example "Examples" + + Match requests with a `Content-Type` header set to `application/yaml`: + + ```yaml + Header(`Content-Type`, `application/yaml`) + ``` + + Match requests with a `Content-Type` header set to either `application/json` or `application/yaml`: + + ```yaml + HeaderRegexp(`Content-Type`, `^application/(json|yaml)$`) + ``` + + To match headers [case-insensitively](https://en.wikipedia.org/wiki/Case_sensitivity), use the `(?i)` option: + + ```yaml + HeaderRegexp(`Content-Type`, `(?i)^application/(json|yaml)$`) + ``` - The rule is evaluated "before" any middleware has the opportunity to work, and "before" the request is forwarded to the service. +#### Host and HostRegexp -!!! info "Path Vs PathPrefix" +The `Host` and `HostRegexp` matchers allow to match requests that are targeted to a given host. - Use `Path` if your service listens on the exact path only. For instance, ```Path(`/products`)``` would match `/products` but not `/products/shoes`. +These matchers do not support non-ASCII characters, use punycode encoded values ([rfc 3492](https://tools.ietf.org/html/rfc3492)) to match such domains. - Use a `*Prefix*` matcher if your service listens on a particular base path but also serves requests on sub-paths. - For instance, ```PathPrefix(`/products`)``` would match `/products` and `/products/shoes`, - as well as `/productsforsale`, and `/productsforsale/shoes`. - Since the path is forwarded as-is, your service is expected to listen on `/products`. +If no Host is set in the request URL (e.g., it's an IP address), these matchers will look at the `Host` header. -!!! info "ClientIP matcher" +!!! example "Examples" - The `ClientIP` matcher will only match the request client IP and does not use the `X-Forwarded-For` header for matching. + Match requests with `Host` set to `example.com`: + + ```yaml + Host(`example.com`) + ``` + + Match requests sent to any subdomain of `example.com`: + + ```yaml + HostRegexp(`^.+\.example\.com$`) + ``` + + Match requests with `Host` set to either `example.com` or `example.org`: + + ```yaml + HostRegexp(`^example\.(com|org)$`) + ``` + + To match domains [case-insensitively](https://en.wikipedia.org/wiki/Case_sensitivity), use the `(?i)` option: + + ```yaml + HostRegexp(`(?i)^example\.(com|org)$`) + ``` + +#### Method + +The `Method` matchers allows to match requests sent with the given method. + +!!! example "Example" + + Match `OPTIONS` requests: + + ```yaml + Method(`OPTIONS`) + ``` + +#### Path, PathPrefix, and PathRegexp + +These matchers allow matching requests based on their URL path. + +For exact matches, use `Path` and its prefixed alternative `PathPrefix`, for regexp matches, use `PathRegexp`. + +Path are always starting with a `/`, except for `PathRegexp`. + +!!! example "Examples" + + Match `/products` but neither `/products/shoes` nor `/products/`: + + ```yaml + Path(`/products`) + ``` + + Match `/products` as well as everything under `/products`, + such as `/products/shoes`, `/products/` but also `/products-for-sale`: + + ```yaml + PathPrefix(`/products`) + ``` + + Match both `/products/shoes` and `/products/socks` with and ID like `/products/shoes/57`: + + ```yaml + PathRegexp(`^/products/(shoes|socks)/[0-9]+$`) + ``` + + Match requests with a path ending in either `.jpeg`, `.jpg` or `.png`: + + ```yaml + PathRegexp(`\.(jpeg|jpg|png)$`) + ``` + + Match `/products` as well as everything under `/products`, + such as `/products/shoes`, `/products/` but also `/products-for-sale`, + [case-insensitively](https://en.wikipedia.org/wiki/Case_sensitivity): + + ```yaml + HostRegexp(`(?i)^/products`) + ``` + +#### Query and QueryRegexp + +The `Query` and `QueryRegexp` matchers allow to match requests based on query parameters. + +!!! example "Examples" + + Match requests with a `mobile` query parameter set to `true`, such as in `/search?mobile=true`: + + ```yaml + Query(`mobile`, `true`) + ``` + + To match requests with a query parameter `mobile` that has no value, such as in `/search?mobile`, use: + + ```yaml + Query(`mobile`) + ``` + + Match requests with a `mobile` query parameter set to either `true` or `yes`: + + ```yaml + QueryRegexp(`mobile`, `^(true|yes)$`) + ``` + + Match requests with a `mobile` query parameter set to any value (including the empty value): + + ```yaml + QueryRegexp(`mobile`, `^.*$`) + ``` + + To match query parameters [case-insensitively](https://en.wikipedia.org/wiki/Case_sensitivity), use the `(?i)` option: + + ```yaml + QueryRegexp(`mobile`, `(?i)^(true|yes)$`) + ``` + +#### ClientIP + +The `ClientIP` matcher allows matching requests sent from the given client IP. + +It only matches the request client IP and does not use the `X-Forwarded-For` header for matching. + +!!! example "Examples" + + Match requests coming from a given IP: + + ```yaml tab="IPv4" + ClientIP(`10.76.105.11`) + ``` + + ```yaml tab="IPv6" + ClientIP(`::1`) + ``` + + Match requests coming from a given subnet: + + ```yaml tab="IPv4" + ClientIP(`192.168.1.0/24`) + ``` + + ```yaml tab="IPv6" + ClientIP(`fe80::/10`) + ``` ### Priority @@ -300,7 +446,7 @@ A value of `0` for the priority is ignored: `priority = 0` means that the defaul http: routers: Router-1: - rule: "HostRegexp(`{subdomain:[a-z]+}.traefik.com`)" + rule: "HostRegexp(`[a-z]+\.traefik\.com`)" # ... Router-2: rule: "Host(`foobar.traefik.com`)" @@ -311,7 +457,7 @@ A value of `0` for the priority is ignored: `priority = 0` means that the defaul ## Dynamic configuration [http.routers] [http.routers.Router-1] - rule = "HostRegexp(`{subdomain:[a-z]+}.traefik.com`)" + rule = "HostRegexp(`[a-z]+\\.traefik\\.com`)" # ... [http.routers.Router-2] rule = "Host(`foobar.traefik.com`)" @@ -320,10 +466,10 @@ A value of `0` for the priority is ignored: `priority = 0` means that the defaul In this case, all requests with host `foobar.traefik.com` will be routed through `Router-1` instead of `Router-2`. - | Name | Rule | Priority | - |----------|----------------------------------------------------|----------| - | Router-1 | ```HostRegexp(`{subdomain:[a-z]+}.traefik.com`)``` | 44 | - | Router-2 | ```Host(`foobar.traefik.com`)``` | 26 | + | Name | Rule | Priority | + |----------|------------------------------------------|----------| + | Router-1 | ```HostRegexp(`[a-z]+\.traefik\.com`)``` | 44 | + | Router-2 | ```Host(`foobar.traefik.com`)``` | 26 | The previous table shows that `Router-1` has a higher priority than `Router-2`. @@ -336,7 +482,7 @@ A value of `0` for the priority is ignored: `priority = 0` means that the defaul http: routers: Router-1: - rule: "HostRegexp(`{subdomain:[a-z]+}.traefik.com`)" + rule: "HostRegexp(`[a-z]+\\.traefik\\.com`)" entryPoints: - "web" service: service-1 @@ -353,7 +499,7 @@ A value of `0` for the priority is ignored: `priority = 0` means that the defaul ## Dynamic configuration [http.routers] [http.routers.Router-1] - rule = "HostRegexp(`{subdomain:[a-z]+}.traefik.com`)" + rule = "HostRegexp(`[a-z]+\\.traefik\\.com`)" entryPoints = ["web"] service = "service-1" priority = 1 @@ -818,48 +964,49 @@ If you want to limit the router scope to a set of entry points, set the entry po ### Rule -Rules are a set of matchers configured with values, that determine if a particular request matches specific criteria. +Rules are a set of matchers configured with values, that determine if a particular connection matches specific criteria. If the rule is verified, the router becomes active, calls middlewares, and then forwards the request to the service. -??? tip "Backticks or Quotes?" - - To set the value of a rule, use [backticks](https://en.wiktionary.org/wiki/backtick) ``` ` ``` or escaped double-quotes `\"`. - - Single quotes `'` are not accepted since the values are [Golang's String Literals](https://golang.org/ref/spec#String_literals). - -!!! example "HostSNI is example.com" - - ```toml - rule = "HostSNI(`example.com`)" - ``` - -!!! example "HostSNI is example.com OR HostSNI is example.org AND ClientIP is 0.0.0.0" - - ```toml - rule = "HostSNI(`example.com`) || (HostSNI(`example.org`) && ClientIP(`0.0.0.0`))" - ``` - The table below lists all the available matchers: -| Rule | Description | -|---------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------| -| ```HostSNI(`domain-1`, ...)``` | Checks if the Server Name Indication corresponds to the given `domains`. | -| ```HostSNIRegexp(`example.com`, `{subdomain:[a-z]+}.example.com`, ...)``` | Checks if the Server Name Indication matches the given regular expressions. See "Regexp Syntax" below. | -| ```ClientIP(`10.0.0.0/16`, `::1`)``` | Checks if the connection client IP is one of the given IP/CIDR. It accepts IPv4, IPv6 and CIDR formats. | -| ```ALPN(`mqtt`, `h2c`)``` | Checks if any of the connection ALPN protocols is one of the given protocols. | +| Rule | Description | +|-------------------------------------------------------------|:-------------------------------------------------------------------------------------------------| +| [```HostSNI(`domain`)```](#hostsni-and-hostsniregexp) | Checks if the connection's Server Name Indication is equal to `domain`. | +| [```HostSNIRegexp(`regexp`)```](#hostsni-and-hostsniregexp) | Checks if the connection's Server Name Indication matches `regexp`. | +| [```ClientIP(`ip`)```](#clientip_1) | Checks if the connection's client IP correspond to `ip`. It accepts IPv4, IPv6 and CIDR formats. | +| [```ALPN(`protocol`)```](#alpn) | Checks if the connection's ALPN protocol equals `protocol`. | -!!! important "Non-ASCII Domain Names" +!!! tip "Backticks or Quotes?" - Non-ASCII characters are not supported in the `HostSNI` and `HostSNIRegexp` expressions, and so using them would invalidate the associated TCP router. - Domain names containing non-ASCII characters must be provided as punycode encoded values ([rfc 3492](https://tools.ietf.org/html/rfc3492)). + To set the value of a rule, use [backticks](https://en.wiktionary.org/wiki/backtick) ``` ` ``` or escaped double-quotes `\"`. + + Single quotes `'` are not accepted since the values are [Go's String Literals](https://golang.org/ref/spec#String_literals). !!! important "Regexp Syntax" - `HostSNIRegexp` accepts an expression with zero or more groups enclosed by curly braces, which are called named regexps. - Named regexps, of the form `{name:regexp}`, are the only expressions considered for regexp matching. - The regexp name (`name` in the above example) is an arbitrary value, that exists only for historical reasons. + Matchers that accept a regexp as their value use a [Go](https://golang.org/pkg/regexp/) flavored syntax. - Any `regexp` supported by [Go's regexp package](https://golang.org/pkg/regexp/) may be used. +!!! info "Expressing Complex Rules Using Operators and Parenthesis" + + The usual AND (`&&`) and OR (`||`) logical operators can be used, with the expected precedence rules, + as well as parentheses. + + One can invert a matcher by using the NOT (`!`) operator. + + The following rule matches connections where: + + - either Server Name Indication is `example.com` OR, + - Server Name Indication is `example.org` AND ALPN protocol is NOT `h2` + + ```yaml + HostSNI(`example.com`) || (HostSNI(`example.org`) && !ALPN(`h2`)) + ``` + +#### HostSNI and HostSNIRegexp + +`HostSNI` and `HostSNIRegexp` matchers allow to match connections targeted to a given domain. + +These matchers do not support non-ASCII characters, use punycode encoded values ([rfc 3492](https://tools.ietf.org/html/rfc3492)) to match such domains. !!! important "HostSNI & TLS" @@ -869,25 +1016,72 @@ The table below lists all the available matchers: when one wants a non-TLS router that matches all (non-TLS) requests, one should use the specific ```HostSNI(`*`)``` syntax. -!!! info "Combining Matchers Using Operators and Parenthesis" +!!! example "Examples" - The usual AND (`&&`) and OR (`||`) logical operators can be used, with the expected precedence rules, - as well as parentheses. + Match all connections: + + ```yaml tab="HostSNI" + HostSNI(`*`) + ``` + + ```yaml tab="HostSNIRegexp" + HostSNIRegexp(`^.*$`) + ``` -!!! info "Inverting a matcher" + Match TCP connections sent to `example.com`: + + ```yaml + HostSNI(`example.com`) + ``` - One can invert a matcher by using the `!` operator. + Match TCP connections openned on any subdomain of `example.com`: + + ```yaml + HostSNIRegexp(`^.+\.example\.com$`) + ``` -!!! important "Rule, Middleware, and Services" +#### ClientIP - The rule is evaluated "before" any middleware has the opportunity to work, and "before" the request is forwarded to the service. +The `ClientIP` matcher allows matching connections opened by a client with the given IP. -!!! important "ALPN ACME-TLS/1" +!!! example "Examples" - It would be a security issue to let a user-defined router catch the response to - an ACME TLS challenge previously initiated by Traefik. - For this reason, the `ALPN` matcher is not allowed to match the `ACME-TLS/1` - protocol, and Traefik returns an error if this is attempted. + Match connections opened by a given IP: + + ```yaml tab="IPv4" + ClientIP(`10.76.105.11`) + ``` + + ```yaml tab="IPv6" + ClientIP(`::1`) + ``` + + Match connections coming from a given subnet: + + ```yaml tab="IPv4" + ClientIP(`192.168.1.0/24`) + ``` + + ```yaml tab="IPv6" + ClientIP(`fe80::/10`) + ``` + +#### ALPN + +The `ALPN` matcher allows matching connections the given protocol. + +It would be a security issue to let a user-defined router catch the response to +an ACME TLS challenge previously initiated by Traefik. +For this reason, the `ALPN` matcher is not allowed to match the `ACME-TLS/1` +protocol, and Traefik returns an error if this is attempted. + +!!! example "Example" + + Match connections using the ALPN protocol `h2`: + + ```yaml + ALPN(`h2`) + ``` ### Priority diff --git a/go.mod b/go.mod index adf17cfd4..86872dde8 100644 --- a/go.mod +++ b/go.mod @@ -77,11 +77,12 @@ require ( github.com/vulcand/predicate v1.2.0 go.elastic.co/apm v1.13.1 go.elastic.co/apm/module/apmot v1.13.1 - golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 + golang.org/x/exp v0.0.0-20221114191408-850992195362 + golang.org/x/mod v0.6.0 golang.org/x/net v0.1.0 golang.org/x/text v0.4.0 golang.org/x/time v0.0.0-20220224211638-0e9765cccd65 - golang.org/x/tools v0.1.12 + golang.org/x/tools v0.2.0 google.golang.org/grpc v1.46.0 gopkg.in/DataDog/dd-trace-go.v1 v1.43.1 gopkg.in/fsnotify.v1 v1.4.7 @@ -330,7 +331,7 @@ require ( go.uber.org/zap v1.18.1 // indirect go4.org/intern v0.0.0-20211027215823-ae77deb06f29 // indirect go4.org/unsafe/assume-no-moving-gc v0.0.0-20220617031537-928513b29760 // indirect - golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f // indirect + golang.org/x/crypto v0.1.0 // indirect golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 // indirect golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1 // indirect golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 // indirect diff --git a/go.sum b/go.sum index d546c08fe..25a719ffa 100644 --- a/go.sum +++ b/go.sum @@ -2028,8 +2028,8 @@ golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211202192323-5770296d904e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f h1:OeJjE6G4dgCY4PIXvIRQbE8+RX+uXZyGhUy/ksMGJoc= -golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU= +golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= 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= @@ -2044,7 +2044,8 @@ golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u0 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-20200331195152-e8c3332aa8e5/go.mod h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw= -golang.org/x/exp v0.0.0-20200908183739-ae8ad444f925 h1:5XVKs2rlCg8EFyRcvO8/XFwYxh1oKJO1Q3X5vttIf9c= +golang.org/x/exp v0.0.0-20221114191408-850992195362 h1:NoHlPRbyl1VFI6FjwHtPQCN7wAMXI6cKcqrmXhOOfBQ= +golang.org/x/exp v0.0.0-20221114191408-850992195362/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= 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= @@ -2074,8 +2075,8 @@ golang.org/x/mod v0.3.1-0.20200828183125-ce943fd02449/go.mod h1:s0Qsj1ACt9ePp/hM golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.6.0 h1:b9gGHsz9/HhJ3HF5DHQytPpuwocVTChQJK3AvoLRD5I= +golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI= golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180811021610-c39426892332/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -2417,8 +2418,8 @@ golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.2.0 h1:G6AHpWxTMGY1KyEYoAQ5WTtIekUUvDNjan3ugu60JvE= +golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/integration/fixtures/https/https_sni_case_insensitive_dynamic.toml b/integration/fixtures/https/https_sni_case_insensitive_dynamic.toml index 718477cb3..77c87a519 100644 --- a/integration/fixtures/https/https_sni_case_insensitive_dynamic.toml +++ b/integration/fixtures/https/https_sni_case_insensitive_dynamic.toml @@ -20,12 +20,12 @@ [http.routers] [http.routers.router1] - rule = "HostRegexp(`{subdomain:[a-z1-9-]+}.snitest.com`)" + rule = "HostRegexp(`[a-z1-9-]+\\.snitest\\.com`)" service = "service1" [http.routers.router1.tls] [http.routers.router2] - rule = "HostRegexp(`{subdomain:[a-z1-9-]+}.www.snitest.com`)" + rule = "HostRegexp(`[a-z1-9-]+\\.www\\.snitest\\.com`)" service = "service1" [http.routers.router2.tls] diff --git a/integration/fixtures/simple_muxer.toml b/integration/fixtures/simple_muxer.toml index 9ec55ebdc..92a86edb4 100644 --- a/integration/fixtures/simple_muxer.toml +++ b/integration/fixtures/simple_muxer.toml @@ -4,6 +4,7 @@ [log] level = "DEBUG" + noColor = true [entryPoints] [entryPoints.webHost] @@ -30,12 +31,12 @@ [http.routers.router2] entryPoints = ["webHostRegexp"] service = "service1" - rule = "!HostRegexp(`test.localhost`)" + rule = "!HostRegexp(`test\\.localhost`)" [http.routers.router3] entryPoints = ["webQuery"] service = "service1" - rule = "!Query(`foo=`)" + rule = "!QueryRegexp(`foo`, `.*`)" [http.services] diff --git a/integration/fixtures/websocket/config_https.toml b/integration/fixtures/websocket/config_https.toml index cdc92b862..71f0f5465 100644 --- a/integration/fixtures/websocket/config_https.toml +++ b/integration/fixtures/websocket/config_https.toml @@ -24,7 +24,7 @@ [http.routers] [http.routers.router1] service = "service1" - rule = "Path(`/echo`,`/ws`)" + rule = "Path(`/echo`) || Path(`/ws`)" [http.routers.router1.tls] [http.services] diff --git a/integration/https_test.go b/integration/https_test.go index 868d31aff..ba80a25f0 100644 --- a/integration/https_test.go +++ b/integration/https_test.go @@ -1092,7 +1092,7 @@ func (s *HTTPSSuite) TestWithSNIDynamicCaseInsensitive(c *check.C) { defer s.killCmd(cmd) // wait for Traefik - err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 500*time.Millisecond, try.BodyContains("HostRegexp(`{subdomain:[a-z1-9-]+}.www.snitest.com`)")) + err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 500*time.Millisecond, try.BodyContains("HostRegexp(`[a-z1-9-]+\\\\.www\\\\.snitest\\\\.com`)")) c.Assert(err, checker.IsNil) tlsConfig := &tls.Config{ diff --git a/integration/simple_test.go b/integration/simple_test.go index 287fe72e0..8738699a1 100644 --- a/integration/simple_test.go +++ b/integration/simple_test.go @@ -1337,7 +1337,7 @@ func (s *SimpleSuite) TestMuxer(c *check.C) { expected: http.StatusOK, }, { - desc: "!Query with semicolon, no match", + desc: "!Query with semicolon and empty query param value, no match", request: "GET /?foo=; HTTP/1.1\r\nHost: other.localhost\r\n\r\n", target: "127.0.0.1:8002", expected: http.StatusNotFound, @@ -1367,9 +1367,7 @@ func (s *SimpleSuite) TestMuxer(c *check.C) { resp, err := http.ReadResponse(bufio.NewReader(conn), nil) c.Assert(err, checker.IsNil) - if resp.StatusCode != test.expected { - c.Errorf("%s failed with %d instead of %d", test.desc, resp.StatusCode, test.expected) - } + c.Assert(resp.StatusCode, checker.Equals, test.expected, check.Commentf(test.desc)) if test.body != "" { body, err := io.ReadAll(resp.Body) diff --git a/pkg/muxer/http/matcher.go b/pkg/muxer/http/matcher.go new file mode 100644 index 000000000..ceafbd6b4 --- /dev/null +++ b/pkg/muxer/http/matcher.go @@ -0,0 +1,253 @@ +package http + +import ( + "fmt" + "net/http" + "regexp" + "strings" + "unicode/utf8" + + "github.com/gorilla/mux" + "github.com/rs/zerolog/log" + "github.com/traefik/traefik/v2/pkg/ip" + "github.com/traefik/traefik/v2/pkg/middlewares/requestdecorator" + "golang.org/x/exp/slices" +) + +var httpFuncs = map[string]func(*mux.Route, ...string) error{ + "ClientIP": expectNParameters(clientIP, 1), + "Method": expectNParameters(method, 1), + "Host": expectNParameters(host, 1), + "HostRegexp": expectNParameters(hostRegexp, 1), + "Path": expectNParameters(path, 1), + "PathRegexp": expectNParameters(pathRegexp, 1), + "PathPrefix": expectNParameters(pathPrefix, 1), + "Header": expectNParameters(header, 2), + "HeaderRegexp": expectNParameters(headerRegexp, 2), + "Query": expectNParameters(query, 1, 2), + "QueryRegexp": expectNParameters(queryRegexp, 1, 2), +} + +func expectNParameters(fn func(*mux.Route, ...string) error, n ...int) func(*mux.Route, ...string) error { + return func(route *mux.Route, s ...string) error { + if !slices.Contains(n, len(s)) { + return fmt.Errorf("unexpected number of parameters; got %d, expected one of %v", len(s), n) + } + + return fn(route, s...) + } +} + +func clientIP(route *mux.Route, clientIP ...string) error { + checker, err := ip.NewChecker(clientIP) + if err != nil { + return fmt.Errorf("initializing IP checker for ClientIP matcher: %w", err) + } + + strategy := ip.RemoteAddrStrategy{} + + route.MatcherFunc(func(req *http.Request, _ *mux.RouteMatch) bool { + ok, err := checker.Contains(strategy.GetIP(req)) + if err != nil { + log.Ctx(req.Context()).Warn().Err(err).Msg("ClientIP matcher: could not match remote address") + return false + } + + return ok + }) + + return nil +} + +func method(route *mux.Route, methods ...string) error { + return route.Methods(methods...).GetError() +} + +func host(route *mux.Route, hosts ...string) error { + host := hosts[0] + + if !IsASCII(host) { + return fmt.Errorf("invalid value %q for Host matcher, non-ASCII characters are not allowed", host) + } + + host = strings.ToLower(host) + + route.MatcherFunc(func(req *http.Request, _ *mux.RouteMatch) bool { + reqHost := requestdecorator.GetCanonizedHost(req.Context()) + if len(reqHost) == 0 { + // If the request is an HTTP/1.0 request, then a Host may not be defined. + if req.ProtoAtLeast(1, 1) { + log.Ctx(req.Context()).Warn().Str("host", req.Host).Msg("Could not retrieve CanonizedHost, rejecting") + } + + return false + } + + flatH := requestdecorator.GetCNAMEFlatten(req.Context()) + if len(flatH) > 0 { + if strings.EqualFold(reqHost, host) || strings.EqualFold(flatH, host) { + return true + } + + log.Ctx(req.Context()).Debug(). + Str("host", reqHost). + Str("flattenHost", flatH). + Str("matcher", host). + Msg("CNAMEFlattening: resolved Host does not match") + return false + } + + if reqHost == host { + return true + } + + // Check for match on trailing period on host + if last := len(host) - 1; last >= 0 && host[last] == '.' { + h := host[:last] + if reqHost == h { + return true + } + } + + // Check for match on trailing period on request + if last := len(reqHost) - 1; last >= 0 && reqHost[last] == '.' { + h := reqHost[:last] + if h == host { + return true + } + } + + return false + }) + + return nil +} + +func hostRegexp(route *mux.Route, hosts ...string) error { + host := hosts[0] + + if !IsASCII(host) { + return fmt.Errorf("invalid value %q for HostRegexp matcher, non-ASCII characters are not allowed", host) + } + + re, err := regexp.Compile(host) + if err != nil { + return fmt.Errorf("compiling HostRegexp matcher: %w", err) + } + + route.MatcherFunc(func(req *http.Request, _ *mux.RouteMatch) bool { + return re.MatchString(req.Host) + }) + + return nil +} + +func path(route *mux.Route, paths ...string) error { + path := paths[0] + + if !strings.HasPrefix(path, "/") { + return fmt.Errorf("path %q does not start with a '/'", path) + } + + route.MatcherFunc(func(req *http.Request, _ *mux.RouteMatch) bool { + return req.URL.Path == path + }) + + return nil +} + +func pathRegexp(route *mux.Route, paths ...string) error { + path := paths[0] + + re, err := regexp.Compile(path) + if err != nil { + return fmt.Errorf("compiling PathPrefix matcher: %w", err) + } + + route.MatcherFunc(func(req *http.Request, _ *mux.RouteMatch) bool { + return re.MatchString(req.URL.Path) + }) + + return nil +} + +func pathPrefix(route *mux.Route, paths ...string) error { + path := paths[0] + + if !strings.HasPrefix(path, "/") { + return fmt.Errorf("path %q does not start with a '/'", path) + } + + route.MatcherFunc(func(req *http.Request, _ *mux.RouteMatch) bool { + return strings.HasPrefix(req.URL.Path, path) + }) + + return nil +} + +func header(route *mux.Route, headers ...string) error { + return route.Headers(headers...).GetError() +} + +func headerRegexp(route *mux.Route, headers ...string) error { + return route.HeadersRegexp(headers...).GetError() +} + +func query(route *mux.Route, queries ...string) error { + key := queries[0] + + var value string + if len(queries) == 2 { + value = queries[1] + } + + route.MatcherFunc(func(req *http.Request, _ *mux.RouteMatch) bool { + values, ok := req.URL.Query()[key] + if !ok { + return false + } + + return slices.Contains(values, value) + }) + + return nil +} + +func queryRegexp(route *mux.Route, queries ...string) error { + if len(queries) == 1 { + return query(route, queries...) + } + + key, value := queries[0], queries[1] + + re, err := regexp.Compile(value) + if err != nil { + return fmt.Errorf("compiling QueryRegexp matcher: %w", err) + } + + route.MatcherFunc(func(req *http.Request, _ *mux.RouteMatch) bool { + values, ok := req.URL.Query()[key] + if !ok { + return false + } + + idx := slices.IndexFunc(values, func(value string) bool { + return re.MatchString(value) + }) + + return idx >= 0 + }) + + return nil +} + +// IsASCII checks if the given string contains only ASCII characters. +func IsASCII(s string) bool { + for i := 0; i < len(s); i++ { + if s[i] >= utf8.RuneSelf { + return false + } + } + + return true +} diff --git a/pkg/muxer/http/matcher_test.go b/pkg/muxer/http/matcher_test.go new file mode 100644 index 000000000..231d34f91 --- /dev/null +++ b/pkg/muxer/http/matcher_test.go @@ -0,0 +1,974 @@ +package http + +import ( + "net/http" + "net/http/httptest" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/traefik/traefik/v2/pkg/middlewares/requestdecorator" +) + +func TestClientIPMatcher(t *testing.T) { + testCases := []struct { + desc string + rule string + expected map[string]int + expectedError bool + }{ + { + desc: "invalid ClientIP matcher", + rule: "ClientIP(`1`)", + expectedError: true, + }, + { + desc: "invalid ClientIP matcher (no parameter)", + rule: "ClientIP()", + expectedError: true, + }, + { + desc: "invalid ClientIP matcher (empty parameter)", + rule: "ClientIP(``)", + expectedError: true, + }, + { + desc: "invalid ClientIP matcher (too many parameters)", + rule: "ClientIP(`127.0.0.1`, `192.168.1.0/24`)", + expectedError: true, + }, + { + desc: "valid ClientIP matcher", + rule: "ClientIP(`127.0.0.1`)", + expected: map[string]int{ + "127.0.0.1": http.StatusOK, + "192.168.1.1": http.StatusNotFound, + }, + }, + { + desc: "valid ClientIP matcher but invalid remote address", + rule: "ClientIP(`127.0.0.1`)", + expected: map[string]int{ + "1": http.StatusNotFound, + }, + }, + { + desc: "valid ClientIP matcher using CIDR", + rule: "ClientIP(`192.168.1.0/24`)", + expected: map[string]int{ + "192.168.1.1": http.StatusOK, + "192.168.1.100": http.StatusOK, + "192.168.2.1": http.StatusNotFound, + }, + }, + } + + for _, test := range testCases { + test := test + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}) + muxer, err := NewMuxer() + require.NoError(t, err) + + err = muxer.AddRoute(test.rule, 0, handler) + if test.expectedError { + require.Error(t, err) + return + } + + require.NoError(t, err) + + results := make(map[string]int) + for remoteAddr := range test.expected { + w := httptest.NewRecorder() + + req := httptest.NewRequest(http.MethodGet, "https://example.com", http.NoBody) + req.RemoteAddr = remoteAddr + + muxer.ServeHTTP(w, req) + results[remoteAddr] = w.Code + } + assert.Equal(t, test.expected, results) + }) + } +} + +func TestMethodMatcher(t *testing.T) { + testCases := []struct { + desc string + rule string + expected map[string]int + expectedError bool + }{ + { + desc: "invalid Method matcher (no parameter)", + rule: "Method()", + expectedError: true, + }, + { + desc: "invalid Method matcher (empty parameter)", + rule: "Method(``)", + expectedError: true, + }, + { + desc: "invalid Method matcher (too many parameters)", + rule: "Method(`GET`, `POST`)", + expectedError: true, + }, + { + desc: "valid Method matcher", + rule: "Method(`GET`)", + expected: map[string]int{ + http.MethodGet: http.StatusOK, + http.MethodPost: http.StatusMethodNotAllowed, + }, + }, + { + desc: "valid Method matcher (lower case)", + rule: "Method(`get`)", + expected: map[string]int{ + http.MethodGet: http.StatusOK, + http.MethodPost: http.StatusMethodNotAllowed, + }, + }, + } + + for _, test := range testCases { + test := test + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}) + muxer, err := NewMuxer() + require.NoError(t, err) + + err = muxer.AddRoute(test.rule, 0, handler) + if test.expectedError { + require.Error(t, err) + return + } + + require.NoError(t, err) + + results := make(map[string]int) + for method := range test.expected { + w := httptest.NewRecorder() + + req := httptest.NewRequest(method, "https://example.com", http.NoBody) + + muxer.ServeHTTP(w, req) + results[method] = w.Code + } + assert.Equal(t, test.expected, results) + }) + } +} + +func TestHostMatcher(t *testing.T) { + testCases := []struct { + desc string + rule string + expected map[string]int + expectedError bool + }{ + { + desc: "invalid Host matcher (no parameter)", + rule: "Host()", + expectedError: true, + }, + { + desc: "invalid Host matcher (empty parameter)", + rule: "Host(``)", + expectedError: true, + }, + { + desc: "invalid Host matcher (non-ASCII)", + rule: "Host(`🦭.com`)", + expectedError: true, + }, + { + desc: "invalid Host matcher (too many parameters)", + rule: "Host(`example.com`, `example.org`)", + expectedError: true, + }, + { + desc: "valid Host matcher", + rule: "Host(`example.com`)", + expected: map[string]int{ + "https://example.com": http.StatusOK, + "https://example.com/path": http.StatusOK, + "https://example.org": http.StatusNotFound, + "https://example.org/path": http.StatusNotFound, + }, + }, + { + desc: "valid Host matcher - matcher ending with a dot", + rule: "Host(`example.com.`)", + expected: map[string]int{ + "https://example.com": http.StatusOK, + "https://example.com/path": http.StatusOK, + "https://example.org": http.StatusNotFound, + "https://example.org/path": http.StatusNotFound, + "https://example.com.": http.StatusOK, + "https://example.com./path": http.StatusOK, + "https://example.org.": http.StatusNotFound, + "https://example.org./path": http.StatusNotFound, + }, + }, + { + desc: "valid Host matcher - URL ending with a dot", + rule: "Host(`example.com`)", + expected: map[string]int{ + "https://example.com.": http.StatusOK, + "https://example.com./path": http.StatusOK, + "https://example.org.": http.StatusNotFound, + "https://example.org./path": http.StatusNotFound, + }, + }, + { + desc: "valid Host matcher - puny-coded emoji", + rule: "Host(`xn--9t9h.com`)", + expected: map[string]int{ + "https://xn--9t9h.com": http.StatusOK, + "https://xn--9t9h.com/path": http.StatusOK, + "https://example.com": http.StatusNotFound, + "https://example.com/path": http.StatusNotFound, + // The request's sender must use puny-code. + "https://🦭.com": http.StatusNotFound, + }, + }, + } + + for _, test := range testCases { + test := test + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}) + muxer, err := NewMuxer() + require.NoError(t, err) + + err = muxer.AddRoute(test.rule, 0, handler) + if test.expectedError { + require.Error(t, err) + return + } + + require.NoError(t, err) + + // RequestDecorator is necessary for the host rule + reqHost := requestdecorator.New(nil) + + results := make(map[string]int) + for calledURL := range test.expected { + w := httptest.NewRecorder() + + req := httptest.NewRequest(http.MethodGet, calledURL, http.NoBody) + + reqHost.ServeHTTP(w, req, muxer.ServeHTTP) + results[calledURL] = w.Code + } + assert.Equal(t, test.expected, results) + }) + } +} + +func TestHostRegexpMatcher(t *testing.T) { + testCases := []struct { + desc string + rule string + expected map[string]int + expectedError bool + }{ + { + desc: "invalid HostRegexp matcher (no parameter)", + rule: "HostRegexp()", + expectedError: true, + }, + { + desc: "invalid HostRegexp matcher (empty parameter)", + rule: "HostRegexp(``)", + expectedError: true, + }, + { + desc: "invalid HostRegexp matcher (non-ASCII)", + rule: "HostRegexp(`🦭.com`)", + expectedError: true, + }, + { + desc: "invalid HostRegexp matcher (invalid regexp)", + rule: "HostRegexp(`(example.com`)", + expectedError: true, + }, + { + desc: "invalid HostRegexp matcher (too many parameters)", + rule: "HostRegexp(`example.com`, `example.org`)", + expectedError: true, + }, + { + desc: "valid HostRegexp matcher", + rule: "HostRegexp(`^[a-zA-Z-]+\\.com$`)", + expected: map[string]int{ + "https://example.com": http.StatusOK, + "https://example.com/path": http.StatusOK, + "https://example.org": http.StatusNotFound, + "https://example.org/path": http.StatusNotFound, + }, + }, + { + desc: "valid HostRegexp matcher with Traefik v2 syntax", + rule: "HostRegexp(`{domain:[a-zA-Z-]+\\.com}`)", + expected: map[string]int{ + "https://example.com": http.StatusNotFound, + "https://example.com/path": http.StatusNotFound, + "https://example.org": http.StatusNotFound, + "https://example.org/path": http.StatusNotFound, + }, + }, + } + + for _, test := range testCases { + test := test + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}) + muxer, err := NewMuxer() + require.NoError(t, err) + + err = muxer.AddRoute(test.rule, 0, handler) + if test.expectedError { + require.Error(t, err) + return + } + + require.NoError(t, err) + + results := make(map[string]int) + for calledURL := range test.expected { + w := httptest.NewRecorder() + + req := httptest.NewRequest(http.MethodGet, calledURL, http.NoBody) + + muxer.ServeHTTP(w, req) + results[calledURL] = w.Code + } + assert.Equal(t, test.expected, results) + }) + } +} + +func TestPathMatcher(t *testing.T) { + testCases := []struct { + desc string + rule string + expected map[string]int + expectedError bool + }{ + { + desc: "invalid Path matcher (no parameter)", + rule: "Path()", + expectedError: true, + }, + { + desc: "invalid Path matcher (empty parameter)", + rule: "Path(``)", + expectedError: true, + }, + { + desc: "invalid Path matcher (no leading /)", + rule: "Path(`css`)", + expectedError: true, + }, + { + desc: "invalid Path matcher (too many parameters)", + rule: "Path(`/css`, `/js`)", + expectedError: true, + }, + { + desc: "valid Path matcher", + rule: "Path(`/css`)", + expected: map[string]int{ + "https://example.com": http.StatusNotFound, + "https://example.com/html": http.StatusNotFound, + "https://example.org/css": http.StatusOK, + "https://example.com/css": http.StatusOK, + "https://example.com/css/": http.StatusNotFound, + "https://example.com/css/main.css": http.StatusNotFound, + }, + }, + } + + for _, test := range testCases { + test := test + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}) + muxer, err := NewMuxer() + require.NoError(t, err) + + err = muxer.AddRoute(test.rule, 0, handler) + if test.expectedError { + require.Error(t, err) + return + } + + require.NoError(t, err) + + results := make(map[string]int) + for calledURL := range test.expected { + w := httptest.NewRecorder() + + req := httptest.NewRequest(http.MethodGet, calledURL, http.NoBody) + + muxer.ServeHTTP(w, req) + results[calledURL] = w.Code + } + assert.Equal(t, test.expected, results) + }) + } +} + +func TestPathRegexpMatcher(t *testing.T) { + testCases := []struct { + desc string + rule string + expected map[string]int + expectedError bool + }{ + { + desc: "invalid PathRegexp matcher (no parameter)", + rule: "PathRegexp()", + expectedError: true, + }, + { + desc: "invalid PathRegexp matcher (empty parameter)", + rule: "PathRegexp(``)", + expectedError: true, + }, + { + desc: "invalid PathRegexp matcher (invalid regexp)", + rule: "PathRegexp(`/(css`)", + expectedError: true, + }, + { + desc: "invalid PathRegexp matcher (too many parameters)", + rule: "PathRegexp(`/css`, `/js`)", + expectedError: true, + }, + { + desc: "valid PathRegexp matcher", + rule: "PathRegexp(`^/(css|js)`)", + expected: map[string]int{ + "https://example.com": http.StatusNotFound, + "https://example.com/html": http.StatusNotFound, + "https://example.org/css": http.StatusOK, + "https://example.com/CSS": http.StatusNotFound, + "https://example.com/css": http.StatusOK, + "https://example.com/css/": http.StatusOK, + "https://example.com/css/main.css": http.StatusOK, + "https://example.com/js": http.StatusOK, + "https://example.com/js/": http.StatusOK, + "https://example.com/js/main.js": http.StatusOK, + }, + }, + { + desc: "valid PathRegexp matcher with Traefik v2 syntax", + rule: `PathRegexp("/{path:(css|js)}")`, + expected: map[string]int{ + "https://example.com": http.StatusNotFound, + "https://example.com/html": http.StatusNotFound, + "https://example.org/css": http.StatusNotFound, + "https://example.com/{path:css}": http.StatusOK, + "https://example.com/{path:css}/": http.StatusOK, + "https://example.com/%7Bpath:css%7D": http.StatusOK, + "https://example.com/%7Bpath:css%7D/": http.StatusOK, + "https://example.com/{path:js}": http.StatusOK, + "https://example.com/{path:js}/": http.StatusOK, + "https://example.com/%7Bpath:js%7D": http.StatusOK, + "https://example.com/%7Bpath:js%7D/": http.StatusOK, + }, + }, + } + + for _, test := range testCases { + test := test + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}) + muxer, err := NewMuxer() + require.NoError(t, err) + + err = muxer.AddRoute(test.rule, 0, handler) + if test.expectedError { + require.Error(t, err) + return + } + + require.NoError(t, err) + + results := make(map[string]int) + for calledURL := range test.expected { + w := httptest.NewRecorder() + + req := httptest.NewRequest(http.MethodGet, calledURL, http.NoBody) + + muxer.ServeHTTP(w, req) + results[calledURL] = w.Code + } + assert.Equal(t, test.expected, results) + }) + } +} + +func TestPathPrefixMatcher(t *testing.T) { + testCases := []struct { + desc string + rule string + expected map[string]int + expectedError bool + }{ + { + desc: "invalid PathPrefix matcher (no parameter)", + rule: "PathPrefix()", + expectedError: true, + }, + { + desc: "invalid PathPrefix matcher (empty parameter)", + rule: "PathPrefix(``)", + expectedError: true, + }, + { + desc: "invalid PathPrefix matcher (no leading /)", + rule: "PathPrefix(`css`)", + expectedError: true, + }, + { + desc: "invalid PathPrefix matcher (too many parameters)", + rule: "PathPrefix(`/css`, `/js`)", + expectedError: true, + }, + { + desc: "valid PathPrefix matcher", + rule: `PathPrefix("/css")`, + expected: map[string]int{ + "https://example.com": http.StatusNotFound, + "https://example.com/html": http.StatusNotFound, + "https://example.org/css": http.StatusOK, + "https://example.com/css": http.StatusOK, + "https://example.com/css/": http.StatusOK, + "https://example.com/css/main.css": http.StatusOK, + }, + }, + } + + for _, test := range testCases { + test := test + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}) + muxer, err := NewMuxer() + require.NoError(t, err) + + err = muxer.AddRoute(test.rule, 0, handler) + if test.expectedError { + require.Error(t, err) + return + } + + require.NoError(t, err) + + results := make(map[string]int) + for calledURL := range test.expected { + w := httptest.NewRecorder() + + req := httptest.NewRequest(http.MethodGet, calledURL, http.NoBody) + + muxer.ServeHTTP(w, req) + results[calledURL] = w.Code + } + assert.Equal(t, test.expected, results) + }) + } +} + +func TestHeaderMatcher(t *testing.T) { + testCases := []struct { + desc string + rule string + expected map[*http.Header]int + expectedError bool + }{ + { + desc: "invalid Header matcher (no parameter)", + rule: "Header()", + expectedError: true, + }, + { + desc: "invalid Header matcher (missing value parameter)", + rule: "Header(`X-Forwarded-Host`)", + expectedError: true, + }, + { + desc: "invalid Header matcher (missing value parameter)", + rule: "Header(`X-Forwarded-Host`, ``)", + expectedError: true, + }, + { + desc: "invalid Header matcher (missing key parameter)", + rule: "Header(``, `example.com`)", + expectedError: true, + }, + { + desc: "invalid Header matcher (too many parameters)", + rule: "Header(`X-Forwarded-Host`, `example.com`, `example.org`)", + expectedError: true, + }, + { + desc: "valid Header matcher", + rule: "Header(`X-Forwarded-Proto`, `https`)", + expected: map[*http.Header]int{ + {"X-Forwarded-Proto": []string{"https"}}: http.StatusOK, + {"x-forwarded-proto": []string{"https"}}: http.StatusNotFound, + {"X-Forwarded-Proto": []string{"http", "https"}}: http.StatusOK, + {"X-Forwarded-Proto": []string{"https", "http"}}: http.StatusOK, + {"X-Forwarded-Host": []string{"example.com"}}: http.StatusNotFound, + }, + }, + } + + for _, test := range testCases { + test := test + + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}) + muxer, err := NewMuxer() + require.NoError(t, err) + + err = muxer.AddRoute(test.rule, 0, handler) + if test.expectedError { + require.Error(t, err) + return + } + + require.NoError(t, err) + + for headers := range test.expected { + w := httptest.NewRecorder() + + req := httptest.NewRequest(http.MethodGet, "https://example.com", http.NoBody) + req.Header = *headers + + muxer.ServeHTTP(w, req) + assert.Equal(t, test.expected[headers], w.Code, headers) + } + }) + } +} + +func TestHeaderRegexpMatcher(t *testing.T) { + testCases := []struct { + desc string + rule string + expected map[*http.Header]int + expectedError bool + }{ + { + desc: "invalid HeaderRegexp matcher (no parameter)", + rule: "HeaderRegexp()", + expectedError: true, + }, + { + desc: "invalid HeaderRegexp matcher (missing value parameter)", + rule: "HeaderRegexp(`X-Forwarded-Host`)", + expectedError: true, + }, + { + desc: "invalid HeaderRegexp matcher (missing value parameter)", + rule: "HeaderRegexp(`X-Forwarded-Host`, ``)", + expectedError: true, + }, + { + desc: "invalid HeaderRegexp matcher (missing key parameter)", + rule: "HeaderRegexp(``, `example.com`)", + expectedError: true, + }, + { + desc: "invalid HeaderRegexp matcher (invalid regexp)", + rule: "HeaderRegexp(`X-Forwarded-Host`,`(example.com`)", + expectedError: true, + }, + { + desc: "invalid HeaderRegexp matcher (too many parameters)", + rule: "HeaderRegexp(`X-Forwarded-Host`, `example.com`, `example.org`)", + expectedError: true, + }, + { + desc: "valid HeaderRegexp matcher", + rule: "HeaderRegexp(`X-Forwarded-Proto`, `^https?$`)", + expected: map[*http.Header]int{ + {"X-Forwarded-Proto": []string{"http"}}: http.StatusOK, + {"x-forwarded-proto": []string{"http"}}: http.StatusNotFound, + {"X-Forwarded-Proto": []string{"https"}}: http.StatusOK, + {"X-Forwarded-Proto": []string{"HTTPS"}}: http.StatusNotFound, + {"X-Forwarded-Proto": []string{"ws", "https"}}: http.StatusOK, + {"X-Forwarded-Host": []string{"example.com"}}: http.StatusNotFound, + }, + }, + { + desc: "valid HeaderRegexp matcher with Traefik v2 syntax", + rule: "HeaderRegexp(`X-Forwarded-Proto`, `http{secure:s?}`)", + expected: map[*http.Header]int{ + {"X-Forwarded-Proto": []string{"http"}}: http.StatusNotFound, + {"X-Forwarded-Proto": []string{"https"}}: http.StatusNotFound, + {"X-Forwarded-Proto": []string{"http{secure:}"}}: http.StatusOK, + {"X-Forwarded-Proto": []string{"HTTP{secure:}"}}: http.StatusNotFound, + {"X-Forwarded-Proto": []string{"http{secure:s}"}}: http.StatusOK, + {"X-Forwarded-Proto": []string{"http{secure:S}"}}: http.StatusNotFound, + {"X-Forwarded-Proto": []string{"HTTPS"}}: http.StatusNotFound, + {"X-Forwarded-Proto": []string{"ws", "http{secure:s}"}}: http.StatusOK, + {"X-Forwarded-Host": []string{"example.com"}}: http.StatusNotFound, + }, + }, + } + + for _, test := range testCases { + test := test + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}) + muxer, err := NewMuxer() + require.NoError(t, err) + + err = muxer.AddRoute(test.rule, 0, handler) + if test.expectedError { + require.Error(t, err) + return + } + + require.NoError(t, err) + + for headers := range test.expected { + w := httptest.NewRecorder() + + req := httptest.NewRequest(http.MethodGet, "https://example.com", http.NoBody) + req.Header = *headers + + muxer.ServeHTTP(w, req) + assert.Equal(t, test.expected[headers], w.Code, *headers) + } + }) + } +} + +func TestQueryMatcher(t *testing.T) { + testCases := []struct { + desc string + rule string + expected map[string]int + expectedError bool + }{ + { + desc: "invalid Query matcher (no parameter)", + rule: "Query()", + expectedError: true, + }, + { + desc: "invalid Query matcher (empty key, one parameter)", + rule: "Query(``)", + expectedError: true, + }, + { + desc: "invalid Query matcher (empty key)", + rule: "Query(``, `traefik`)", + expectedError: true, + }, + { + desc: "invalid Query matcher (empty value)", + rule: "Query(`q`, ``)", + expectedError: true, + }, + { + desc: "invalid Query matcher (too many parameters)", + rule: "Query(`q`, `traefik`, `proxy`)", + expectedError: true, + }, + { + desc: "valid Query matcher", + rule: "Query(`q`, `traefik`)", + expected: map[string]int{ + "https://example.com": http.StatusNotFound, + "https://example.com?q=traefik": http.StatusOK, + "https://example.com?rel=ddg&q=traefik": http.StatusOK, + "https://example.com?q=traefik&q=proxy": http.StatusOK, + "https://example.com?q=awesome&q=traefik": http.StatusOK, + "https://example.com?q=nginx": http.StatusNotFound, + "https://example.com?rel=ddg": http.StatusNotFound, + "https://example.com?q=TRAEFIK": http.StatusNotFound, + "https://example.com?Q=traefik": http.StatusNotFound, + "https://example.com?rel=traefik": http.StatusNotFound, + }, + }, + { + desc: "valid Query matcher with empty value", + rule: "Query(`mobile`)", + expected: map[string]int{ + "https://example.com": http.StatusNotFound, + "https://example.com?mobile": http.StatusOK, + "https://example.com?mobile=true": http.StatusNotFound, + }, + }, + } + + for _, test := range testCases { + test := test + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}) + muxer, err := NewMuxer() + require.NoError(t, err) + + err = muxer.AddRoute(test.rule, 0, handler) + if test.expectedError { + require.Error(t, err) + return + } + + require.NoError(t, err) + + results := make(map[string]int) + for calledURL := range test.expected { + w := httptest.NewRecorder() + + req := httptest.NewRequest(http.MethodGet, calledURL, http.NoBody) + + muxer.ServeHTTP(w, req) + results[calledURL] = w.Code + } + assert.Equal(t, test.expected, results) + }) + } +} + +func TestQueryRegexpMatcher(t *testing.T) { + testCases := []struct { + desc string + rule string + expected map[string]int + expectedError bool + }{ + { + desc: "invalid QueryRegexp matcher (no parameter)", + rule: "QueryRegexp()", + expectedError: true, + }, + { + desc: "invalid QueryRegexp matcher (empty parameter)", + rule: "QueryRegexp(``)", + expectedError: true, + }, + { + desc: "invalid QueryRegexp matcher (invalid regexp)", + rule: "QueryRegexp(`q`, `(traefik`)", + expectedError: true, + }, + { + desc: "invalid QueryRegexp matcher (too many parameters)", + rule: "QueryRegexp(`q`, `traefik`, `proxy`)", + expectedError: true, + }, + { + desc: "valid QueryRegexp matcher", + rule: "QueryRegexp(`q`, `^(traefik|nginx)$`)", + expected: map[string]int{ + "https://example.com": http.StatusNotFound, + "https://example.com?q=traefik": http.StatusOK, + "https://example.com?rel=ddg&q=traefik": http.StatusOK, + "https://example.com?q=traefik&q=proxy": http.StatusOK, + "https://example.com?q=awesome&q=traefik": http.StatusOK, + "https://example.com?q=TRAEFIK": http.StatusNotFound, + "https://example.com?Q=traefik": http.StatusNotFound, + "https://example.com?rel=traefik": http.StatusNotFound, + "https://example.com?q=nginx": http.StatusOK, + "https://example.com?rel=ddg&q=nginx": http.StatusOK, + "https://example.com?q=nginx&q=proxy": http.StatusOK, + "https://example.com?q=awesome&q=nginx": http.StatusOK, + "https://example.com?q=NGINX": http.StatusNotFound, + "https://example.com?Q=nginx": http.StatusNotFound, + "https://example.com?rel=nginx": http.StatusNotFound, + "https://example.com?q=haproxy": http.StatusNotFound, + "https://example.com?rel=ddg": http.StatusNotFound, + }, + }, + { + desc: "valid QueryRegexp matcher", + rule: "QueryRegexp(`q`, `^.*$`)", + expected: map[string]int{ + "https://example.com": http.StatusNotFound, + "https://example.com?q=traefik": http.StatusOK, + "https://example.com?rel=ddg&q=traefik": http.StatusOK, + "https://example.com?q=traefik&q=proxy": http.StatusOK, + "https://example.com?q=awesome&q=traefik": http.StatusOK, + "https://example.com?q=TRAEFIK": http.StatusOK, + "https://example.com?Q=traefik": http.StatusNotFound, + "https://example.com?rel=traefik": http.StatusNotFound, + "https://example.com?q=nginx": http.StatusOK, + "https://example.com?rel=ddg&q=nginx": http.StatusOK, + "https://example.com?q=nginx&q=proxy": http.StatusOK, + "https://example.com?q=awesome&q=nginx": http.StatusOK, + "https://example.com?q=NGINX": http.StatusOK, + "https://example.com?Q=nginx": http.StatusNotFound, + "https://example.com?rel=nginx": http.StatusNotFound, + "https://example.com?q=haproxy": http.StatusOK, + "https://example.com?rel=ddg": http.StatusNotFound, + }, + }, + { + desc: "valid QueryRegexp matcher with Traefik v2 syntax", + rule: "QueryRegexp(`q`, `{value:(traefik|nginx)}`)", + expected: map[string]int{ + "https://example.com?q=traefik": http.StatusNotFound, + "https://example.com?q={value:traefik}": http.StatusOK, + }, + }, + } + + for _, test := range testCases { + test := test + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}) + muxer, err := NewMuxer() + require.NoError(t, err) + + err = muxer.AddRoute(test.rule, 0, handler) + if test.expectedError { + require.Error(t, err) + return + } + + require.NoError(t, err) + + results := make(map[string]int) + for calledURL := range test.expected { + w := httptest.NewRecorder() + + req := httptest.NewRequest(http.MethodGet, calledURL, http.NoBody) + + muxer.ServeHTTP(w, req) + results[calledURL] = w.Code + } + assert.Equal(t, test.expected, results) + }) + } +} diff --git a/pkg/muxer/http/mux.go b/pkg/muxer/http/mux.go index 16f6e69fe..237977044 100644 --- a/pkg/muxer/http/mux.go +++ b/pkg/muxer/http/mux.go @@ -3,32 +3,12 @@ package http import ( "fmt" "net/http" - "strings" - "unicode/utf8" "github.com/gorilla/mux" - "github.com/rs/zerolog/log" - "github.com/traefik/traefik/v2/pkg/ip" - "github.com/traefik/traefik/v2/pkg/middlewares/requestdecorator" "github.com/traefik/traefik/v2/pkg/rules" "github.com/vulcand/predicate" ) -const hostMatcher = "Host" - -var httpFuncs = map[string]func(*mux.Route, ...string) error{ - hostMatcher: host, - "HostHeader": host, - "HostRegexp": hostRegexp, - "ClientIP": clientIP, - "Path": path, - "PathPrefix": pathPrefix, - "Method": methods, - "Headers": headers, - "HeadersRegexp": headersRegexp, - "Query": query, -} - // Muxer handles routing with rules. type Muxer struct { *mux.Router @@ -80,171 +60,6 @@ func (r *Muxer) AddRoute(rule string, priority int, handler http.Handler) error return nil } -// ParseDomains extract domains from rule. -func ParseDomains(rule string) ([]string, error) { - var matchers []string - for matcher := range httpFuncs { - matchers = append(matchers, matcher) - } - - parser, err := rules.NewParser(matchers) - if err != nil { - return nil, err - } - - parse, err := parser.Parse(rule) - if err != nil { - return nil, err - } - - buildTree, ok := parse.(rules.TreeBuilder) - if !ok { - return nil, fmt.Errorf("error while parsing rule %s", rule) - } - - return buildTree().ParseMatchers([]string{hostMatcher}), nil -} - -func path(route *mux.Route, paths ...string) error { - rt := route.Subrouter() - - for _, path := range paths { - if err := rt.Path(path).GetError(); err != nil { - return err - } - } - - return nil -} - -func pathPrefix(route *mux.Route, paths ...string) error { - rt := route.Subrouter() - - for _, path := range paths { - if err := rt.PathPrefix(path).GetError(); err != nil { - return err - } - } - - return nil -} - -func host(route *mux.Route, hosts ...string) error { - for i, host := range hosts { - if !IsASCII(host) { - return fmt.Errorf("invalid value %q for \"Host\" matcher, non-ASCII characters are not allowed", host) - } - - hosts[i] = strings.ToLower(host) - } - - route.MatcherFunc(func(req *http.Request, _ *mux.RouteMatch) bool { - reqHost := requestdecorator.GetCanonizedHost(req.Context()) - if len(reqHost) == 0 { - // If the request is an HTTP/1.0 request, then a Host may not be defined. - if req.ProtoAtLeast(1, 1) { - log.Ctx(req.Context()).Warn().Msgf("Could not retrieve CanonizedHost, rejecting %s", req.Host) - } - - return false - } - - flatH := requestdecorator.GetCNAMEFlatten(req.Context()) - if len(flatH) > 0 { - for _, host := range hosts { - if strings.EqualFold(reqHost, host) || strings.EqualFold(flatH, host) { - return true - } - log.Ctx(req.Context()).Debug().Msgf("CNAMEFlattening: request %s which resolved to %s, is not matched to route %s", reqHost, flatH, host) - } - return false - } - - for _, host := range hosts { - if reqHost == host { - return true - } - - // Check for match on trailing period on host - if last := len(host) - 1; last >= 0 && host[last] == '.' { - h := host[:last] - if reqHost == h { - return true - } - } - - // Check for match on trailing period on request - if last := len(reqHost) - 1; last >= 0 && reqHost[last] == '.' { - h := reqHost[:last] - if h == host { - return true - } - } - } - return false - }) - return nil -} - -func clientIP(route *mux.Route, clientIPs ...string) error { - checker, err := ip.NewChecker(clientIPs) - if err != nil { - return fmt.Errorf("could not initialize IP Checker for \"ClientIP\" matcher: %w", err) - } - - strategy := ip.RemoteAddrStrategy{} - - route.MatcherFunc(func(req *http.Request, _ *mux.RouteMatch) bool { - ok, err := checker.Contains(strategy.GetIP(req)) - if err != nil { - log.Ctx(req.Context()).Warn().Err(err).Msg("\"ClientIP\" matcher: could not match remote address") - return false - } - - return ok - }) - - return nil -} - -func hostRegexp(route *mux.Route, hosts ...string) error { - router := route.Subrouter() - for _, host := range hosts { - if !IsASCII(host) { - return fmt.Errorf("invalid value %q for HostRegexp matcher, non-ASCII characters are not allowed", host) - } - - tmpRt := router.Host(host) - if tmpRt.GetError() != nil { - return tmpRt.GetError() - } - } - return nil -} - -func methods(route *mux.Route, methods ...string) error { - return route.Methods(methods...).GetError() -} - -func headers(route *mux.Route, headers ...string) error { - return route.Headers(headers...).GetError() -} - -func headersRegexp(route *mux.Route, headers ...string) error { - return route.HeadersRegexp(headers...).GetError() -} - -func query(route *mux.Route, query ...string) error { - var queries []string - for _, elem := range query { - queries = append(queries, strings.SplitN(elem, "=", 2)...) - } - - route.Queries(queries...) - // Queries can return nil so we can't chain the GetError() - return route.GetError() -} - func addRuleOnRouter(router *mux.Router, rule *rules.Tree) error { switch rule.Matcher { case "and": @@ -276,20 +91,6 @@ func addRuleOnRouter(router *mux.Router, rule *rules.Tree) error { } } -func not(m func(*mux.Route, ...string) error) func(*mux.Route, ...string) error { - return func(r *mux.Route, v ...string) error { - router := mux.NewRouter() - err := m(router.NewRoute(), v...) - if err != nil { - return err - } - r.MatcherFunc(func(req *http.Request, ma *mux.RouteMatch) bool { - return !router.Match(req, ma) - }) - return nil - } -} - func addRuleOnRoute(route *mux.Route, rule *rules.Tree) error { switch rule.Matcher { case "and": @@ -322,13 +123,44 @@ func addRuleOnRoute(route *mux.Route, rule *rules.Tree) error { } } -// IsASCII checks if the given string contains only ASCII characters. -func IsASCII(s string) bool { - for i := 0; i < len(s); i++ { - if s[i] >= utf8.RuneSelf { - return false +func not(m func(*mux.Route, ...string) error) func(*mux.Route, ...string) error { + return func(r *mux.Route, v ...string) error { + router := mux.NewRouter() + + err := m(router.NewRoute(), v...) + if err != nil { + return err } + + r.MatcherFunc(func(req *http.Request, ma *mux.RouteMatch) bool { + return !router.Match(req, ma) + }) + + return nil + } +} + +// ParseDomains extract domains from rule. +func ParseDomains(rule string) ([]string, error) { + var matchers []string + for matcher := range httpFuncs { + matchers = append(matchers, matcher) } - return true + parser, err := rules.NewParser(matchers) + if err != nil { + return nil, err + } + + parse, err := parser.Parse(rule) + if err != nil { + return nil, err + } + + buildTree, ok := parse.(rules.TreeBuilder) + if !ok { + return nil, fmt.Errorf("error while parsing rule %s", rule) + } + + return buildTree().ParseMatchers([]string{"Host"}), nil } diff --git a/pkg/muxer/http/mux_test.go b/pkg/muxer/http/mux_test.go index be8e2de30..bdc98df37 100644 --- a/pkg/muxer/http/mux_test.go +++ b/pkg/muxer/http/mux_test.go @@ -7,14 +7,13 @@ import ( "net/http/httptest" "testing" - "github.com/gorilla/mux" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/traefik/traefik/v2/pkg/middlewares/requestdecorator" "github.com/traefik/traefik/v2/pkg/testhelpers" ) -func Test_addRoute(t *testing.T) { +func TestMuxer(t *testing.T) { testCases := []struct { desc string rule string @@ -33,607 +32,177 @@ func Test_addRoute(t *testing.T) { expectedError: true, }, { - desc: "Host empty", - rule: "Host(``)", + desc: "Rule without quote", + rule: "Host(example.com)", expectedError: true, }, - { - desc: "PathPrefix empty", - rule: "PathPrefix(``)", - expectedError: true, - }, - { - desc: "PathPrefix", - rule: "PathPrefix(`/foo`)", - expected: map[string]int{ - "http://localhost/foo": http.StatusOK, - }, - }, - { - desc: "wrong PathPrefix", - rule: "PathPrefix(`/bar`)", - expected: map[string]int{ - "http://localhost/foo": http.StatusNotFound, - }, - }, - { - desc: "Host", - rule: "Host(`localhost`)", - expected: map[string]int{ - "http://localhost/foo": http.StatusOK, - }, - }, - { - desc: "Non-ASCII Host", - rule: "Host(`locàlhost`)", - expectedError: true, - }, - { - desc: "Non-ASCII HostRegexp", - rule: "HostRegexp(`locàlhost`)", - expectedError: true, - }, - { - desc: "HostHeader equivalent to Host", - rule: "HostHeader(`localhost`)", - expected: map[string]int{ - "http://localhost/foo": http.StatusOK, - "http://bar/foo": http.StatusNotFound, - }, - }, - { - desc: "Host with trailing period in rule", - rule: "Host(`localhost.`)", - expected: map[string]int{ - "http://localhost/foo": http.StatusOK, - }, - }, - { - desc: "Host with trailing period in domain", - rule: "Host(`localhost`)", - expected: map[string]int{ - "http://localhost./foo": http.StatusOK, - }, - }, - { - desc: "Host with trailing period in domain and rule", - rule: "Host(`localhost.`)", - expected: map[string]int{ - "http://localhost./foo": http.StatusOK, - }, - }, - { - desc: "wrong Host", - rule: "Host(`nope`)", - expected: map[string]int{ - "http://localhost/foo": http.StatusNotFound, - }, - }, { desc: "Host and PathPrefix", - rule: "Host(`localhost`) && PathPrefix(`/foo`)", + rule: "Host(`localhost`) && PathPrefix(`/css`)", expected: map[string]int{ - "http://localhost/foo": http.StatusOK, - }, - }, - { - desc: "Host and PathPrefix wrong PathPrefix", - rule: "Host(`localhost`) && PathPrefix(`/bar`)", - expected: map[string]int{ - "http://localhost/foo": http.StatusNotFound, - }, - }, - { - desc: "Host and PathPrefix wrong Host", - rule: "Host(`nope`) && PathPrefix(`/foo`)", - expected: map[string]int{ - "http://localhost/foo": http.StatusNotFound, - }, - }, - { - desc: "Host and PathPrefix Host OR, first host", - rule: "Host(`nope`,`localhost`) && PathPrefix(`/foo`)", - expected: map[string]int{ - "http://localhost/foo": http.StatusOK, - }, - }, - { - desc: "Host and PathPrefix Host OR, second host", - rule: "Host(`nope`,`localhost`) && PathPrefix(`/foo`)", - expected: map[string]int{ - "http://nope/foo": http.StatusOK, - }, - }, - { - desc: "Host and PathPrefix Host OR, first host and wrong PathPrefix", - rule: "Host(`nope,localhost`) && PathPrefix(`/bar`)", - expected: map[string]int{ - "http://localhost/foo": http.StatusNotFound, - }, - }, - { - desc: "HostRegexp with capturing group", - rule: "HostRegexp(`{subdomain:(foo\\.)?bar\\.com}`)", - expected: map[string]int{ - "http://foo.bar.com": http.StatusOK, - "http://bar.com": http.StatusOK, - "http://fooubar.com": http.StatusNotFound, - "http://barucom": http.StatusNotFound, - "http://barcom": http.StatusNotFound, - }, - }, - { - desc: "HostRegexp with non capturing group", - rule: "HostRegexp(`{subdomain:(?:foo\\.)?bar\\.com}`)", - expected: map[string]int{ - "http://foo.bar.com": http.StatusOK, - "http://bar.com": http.StatusOK, - "http://fooubar.com": http.StatusNotFound, - "http://barucom": http.StatusNotFound, - "http://barcom": http.StatusNotFound, - }, - }, - { - desc: "Methods with GET", - rule: "Method(`GET`)", - expected: map[string]int{ - "http://localhost/foo": http.StatusOK, - }, - }, - { - desc: "Methods with GET and POST", - rule: "Method(`GET`,`POST`)", - expected: map[string]int{ - "http://localhost/foo": http.StatusOK, - }, - }, - { - desc: "Methods with POST", - rule: "Method(`POST`)", - expected: map[string]int{ - "http://localhost/foo": http.StatusMethodNotAllowed, - }, - }, - { - desc: "Header with matching header", - rule: "Headers(`Content-Type`,`application/json`)", - headers: map[string]string{ - "Content-Type": "application/json", - }, - expected: map[string]int{ - "http://localhost/foo": http.StatusOK, - }, - }, - { - desc: "Header without matching header", - rule: "Headers(`Content-Type`,`application/foo`)", - headers: map[string]string{ - "Content-Type": "application/json", - }, - expected: map[string]int{ - "http://localhost/foo": http.StatusNotFound, - }, - }, - { - desc: "HeaderRegExp with matching header", - rule: "HeadersRegexp(`Content-Type`, `application/(text|json)`)", - headers: map[string]string{ - "Content-Type": "application/json", - }, - expected: map[string]int{ - "http://localhost/foo": http.StatusOK, - }, - }, - { - desc: "HeaderRegExp without matching header", - rule: "HeadersRegexp(`Content-Type`, `application/(text|json)`)", - headers: map[string]string{ - "Content-Type": "application/foo", - }, - expected: map[string]int{ - "http://localhost/foo": http.StatusNotFound, - }, - }, - { - desc: "HeaderRegExp with matching second header", - rule: "HeadersRegexp(`Content-Type`, `application/(text|json)`)", - headers: map[string]string{ - "Content-Type": "application/text", - }, - expected: map[string]int{ - "http://localhost/foo": http.StatusOK, - }, - }, - { - desc: "Query with multiple params", - rule: "Query(`foo=bar`, `bar=baz`)", - expected: map[string]int{ - "http://localhost/foo?foo=bar&bar=baz": http.StatusOK, - "http://localhost/foo?bar=baz": http.StatusNotFound, - }, - }, - { - desc: "Query with multiple equals", - rule: "Query(`foo=b=ar`)", - expected: map[string]int{ - "http://localhost/foo?foo=b=ar": http.StatusOK, - "http://localhost/foo?foo=bar": http.StatusNotFound, - }, - }, - { - desc: "Rule with simple path", - rule: `Path("/a")`, - expected: map[string]int{ - "http://plop/a": http.StatusOK, - }, - }, - { - desc: `Rule with a simple host`, - rule: `Host("plop")`, - expected: map[string]int{ - "http://plop": http.StatusOK, - }, - }, - { - desc: "Rule with Path AND Host", - rule: `Path("/a") && Host("plop")`, - expected: map[string]int{ - "http://plop/a": http.StatusOK, - "http://plopi/a": http.StatusNotFound, + "https://localhost/css": http.StatusOK, + "https://localhost/js": http.StatusNotFound, }, }, { desc: "Rule with Host OR Host", - rule: `Host("tchouk") || Host("pouet")`, + rule: "Host(`example.com`) || Host(`example.org`)", expected: map[string]int{ - "http://tchouk/toto": http.StatusOK, - "http://pouet/a": http.StatusOK, - "http://plopi/a": http.StatusNotFound, + "https://example.com/css": http.StatusOK, + "https://example.org/js": http.StatusOK, + "https://example.eu/html": http.StatusNotFound, }, }, { desc: "Rule with host OR (host AND path)", - rule: `Host("tchouk") || (Host("pouet") && Path("/powpow"))`, + rule: `Host("example.com") || (Host("example.org") && Path("/css"))`, expected: map[string]int{ - "http://tchouk/toto": http.StatusOK, - "http://tchouk/powpow": http.StatusOK, - "http://pouet/powpow": http.StatusOK, - "http://pouet/toto": http.StatusNotFound, - "http://plopi/a": http.StatusNotFound, + "https://example.com/css": http.StatusOK, + "https://example.com/js": http.StatusOK, + "https://example.org/css": http.StatusOK, + "https://example.org/js": http.StatusNotFound, + "https://example.eu/css": http.StatusNotFound, }, }, { desc: "Rule with host OR host AND path", - rule: `Host("tchouk") || Host("pouet") && Path("/powpow")`, + rule: `Host("example.com") || Host("example.org") && Path("/css")`, expected: map[string]int{ - "http://tchouk/toto": http.StatusOK, - "http://tchouk/powpow": http.StatusOK, - "http://pouet/powpow": http.StatusOK, - "http://pouet/toto": http.StatusNotFound, - "http://plopi/a": http.StatusNotFound, + "https://example.com/css": http.StatusOK, + "https://example.com/js": http.StatusOK, + "https://example.org/css": http.StatusOK, + "https://example.org/js": http.StatusNotFound, + "https://example.eu/css": http.StatusNotFound, }, }, { desc: "Rule with (host OR host) AND path", - rule: `(Host("tchouk") || Host("pouet")) && Path("/powpow")`, + rule: `(Host("example.com") || Host("example.org")) && Path("/css")`, expected: map[string]int{ - "http://tchouk/toto": http.StatusNotFound, - "http://tchouk/powpow": http.StatusOK, - "http://pouet/powpow": http.StatusOK, - "http://pouet/toto": http.StatusNotFound, - "http://plopi/a": http.StatusNotFound, - }, - }, - { - desc: "Rule with multiple host AND path", - rule: `(Host("tchouk","pouet")) && Path("/powpow")`, - expected: map[string]int{ - "http://tchouk/toto": http.StatusNotFound, - "http://tchouk/powpow": http.StatusOK, - "http://pouet/powpow": http.StatusOK, - "http://pouet/toto": http.StatusNotFound, - "http://plopi/a": http.StatusNotFound, - }, - }, - { - desc: "Rule with multiple host AND multiple path", - rule: `Host("tchouk","pouet") && Path("/powpow", "/titi")`, - expected: map[string]int{ - "http://tchouk/toto": http.StatusNotFound, - "http://tchouk/powpow": http.StatusOK, - "http://pouet/powpow": http.StatusOK, - "http://tchouk/titi": http.StatusOK, - "http://pouet/titi": http.StatusOK, - "http://pouet/toto": http.StatusNotFound, - "http://plopi/a": http.StatusNotFound, + "https://example.com/css": http.StatusOK, + "https://example.com/js": http.StatusNotFound, + "https://example.org/css": http.StatusOK, + "https://example.org/js": http.StatusNotFound, + "https://example.eu/css": http.StatusNotFound, }, }, { desc: "Rule with (host AND path) OR (host AND path)", - rule: `(Host("tchouk") && Path("/titi")) || ((Host("pouet")) && Path("/powpow"))`, + rule: `(Host("example.com") && Path("/js")) || ((Host("example.org")) && Path("/css"))`, expected: map[string]int{ - "http://tchouk/titi": http.StatusOK, - "http://tchouk/powpow": http.StatusNotFound, - "http://pouet/powpow": http.StatusOK, - "http://pouet/toto": http.StatusNotFound, - "http://plopi/a": http.StatusNotFound, + "https://example.com/css": http.StatusNotFound, + "https://example.com/js": http.StatusOK, + "https://example.org/css": http.StatusOK, + "https://example.org/js": http.StatusNotFound, + "https://example.eu/css": http.StatusNotFound, }, }, - { - desc: "Rule without quote", - rule: `Host(tchouk)`, - expectedError: true, - }, { desc: "Rule case UPPER", - rule: `(HOST("tchouk") && PATHPREFIX("/titi"))`, + rule: `PATHPREFIX("/css")`, expected: map[string]int{ - "http://tchouk/titi": http.StatusOK, - "http://tchouk/powpow": http.StatusNotFound, + "https://example.com/css": http.StatusOK, + "https://example.com/js": http.StatusNotFound, }, }, { desc: "Rule case lower", - rule: `(host("tchouk") && pathprefix("/titi"))`, + rule: `pathprefix("/css")`, expected: map[string]int{ - "http://tchouk/titi": http.StatusOK, - "http://tchouk/powpow": http.StatusNotFound, + "https://example.com/css": http.StatusOK, + "https://example.com/js": http.StatusNotFound, }, }, { desc: "Rule case CamelCase", - rule: `(Host("tchouk") && PathPrefix("/titi"))`, + rule: `PathPrefix("/css")`, expected: map[string]int{ - "http://tchouk/titi": http.StatusOK, - "http://tchouk/powpow": http.StatusNotFound, + "https://example.com/css": http.StatusOK, + "https://example.com/js": http.StatusNotFound, }, }, { desc: "Rule case Title", - rule: `(Host("tchouk") && Pathprefix("/titi"))`, + rule: `Pathprefix("/css")`, expected: map[string]int{ - "http://tchouk/titi": http.StatusOK, - "http://tchouk/powpow": http.StatusNotFound, + "https://example.com/css": http.StatusOK, + "https://example.com/js": http.StatusNotFound, }, }, - { - desc: "Rule Path with error", - rule: `Path("titi")`, - expectedError: true, - }, - { - desc: "Rule PathPrefix with error", - rule: `PathPrefix("titi")`, - expectedError: true, - }, - { - desc: "Rule HostRegexp with error", - rule: `HostRegexp("{test")`, - expectedError: true, - }, - { - desc: "Rule Headers with error", - rule: `Headers("titi")`, - expectedError: true, - }, - { - desc: "Rule HeadersRegexp with error", - rule: `HeadersRegexp("titi")`, - expectedError: true, - }, - { - desc: "Rule Query", - rule: `Query("titi")`, - expectedError: true, - }, - { - desc: "Rule Query with bad syntax", - rule: `Query("titi={test")`, - expectedError: true, - }, - { - desc: "Rule with Path without args", - rule: `Host("tchouk") && Path()`, - expectedError: true, - }, - { - desc: "Rule with an empty path", - rule: `Host("tchouk") && Path("")`, - expectedError: true, - }, - { - desc: "Rule with an empty path", - rule: `Host("tchouk") && Path("", "/titi")`, - expectedError: true, - }, { desc: "Rule with not", - rule: `!Host("tchouk")`, + rule: `!Host("example.com")`, expected: map[string]int{ - "http://tchouk/titi": http.StatusNotFound, - "http://test/powpow": http.StatusOK, - }, - }, - { - desc: "Rule with not on Path", - rule: `!Path("/titi")`, - expected: map[string]int{ - "http://tchouk/titi": http.StatusNotFound, - "http://tchouk/powpow": http.StatusOK, + "https://example.org": http.StatusOK, + "https://example.com": http.StatusNotFound, }, }, { desc: "Rule with not on multiple route with or", - rule: `!(Host("tchouk") || Host("toto"))`, + rule: `!(Host("example.com") || Host("example.org"))`, expected: map[string]int{ - "http://tchouk/titi": http.StatusNotFound, - "http://toto/powpow": http.StatusNotFound, - "http://test/powpow": http.StatusOK, + "https://example.eu/js": http.StatusOK, + "https://example.com/css": http.StatusNotFound, + "https://example.org/js": http.StatusNotFound, }, }, { desc: "Rule with not on multiple route with and", - rule: `!(Host("tchouk") && Path("/titi"))`, + rule: `!(Host("example.com") && Path("/css"))`, expected: map[string]int{ - "http://tchouk/titi": http.StatusNotFound, - "http://tchouk/toto": http.StatusOK, - "http://test/titi": http.StatusOK, + "https://example.com/js": http.StatusOK, + "https://example.eu/css": http.StatusOK, + "https://example.com/css": http.StatusNotFound, }, }, { desc: "Rule with not on multiple route with and another not", - rule: `!(Host("tchouk") && !Path("/titi"))`, + rule: `!(Host("example.com") && !Path("/css"))`, expected: map[string]int{ - "http://tchouk/titi": http.StatusOK, - "http://toto/titi": http.StatusOK, - "http://tchouk/toto": http.StatusNotFound, + "https://example.com/css": http.StatusOK, + "https://example.org/css": http.StatusOK, + "https://example.com/js": http.StatusNotFound, }, }, { desc: "Rule with not on two rule", - rule: `!Host("tchouk") || !Path("/titi")`, + rule: `!Host("example.com") || !Path("/css")`, expected: map[string]int{ - "http://tchouk/titi": http.StatusNotFound, - "http://tchouk/toto": http.StatusOK, - "http://test/titi": http.StatusOK, + "https://example.com/js": http.StatusOK, + "https://example.org/css": http.StatusOK, + "https://example.com/css": http.StatusNotFound, }, }, { desc: "Rule case with double not", - rule: `!(!(Host("tchouk") && Pathprefix("/titi")))`, + rule: `!(!(Host("example.com") && Pathprefix("/css")))`, expected: map[string]int{ - "http://tchouk/titi": http.StatusOK, - "http://tchouk/powpow": http.StatusNotFound, - "http://test/titi": http.StatusNotFound, + "https://example.com/css": http.StatusOK, + "https://example.com/js": http.StatusNotFound, + "https://example.org/css": http.StatusNotFound, }, }, { desc: "Rule case with not domain", - rule: `!Host("tchouk") && Pathprefix("/titi")`, + rule: `!Host("example.com") && Pathprefix("/css")`, expected: map[string]int{ - "http://tchouk/titi": http.StatusNotFound, - "http://tchouk/powpow": http.StatusNotFound, - "http://toto/powpow": http.StatusNotFound, - "http://toto/titi": http.StatusOK, + "https://example.org/css": http.StatusOK, + "https://example.org/js": http.StatusNotFound, + "https://example.com/css": http.StatusNotFound, + "https://example.com/js": http.StatusNotFound, }, }, { desc: "Rule with multiple host AND multiple path AND not", - rule: `!(Host("tchouk","pouet") && Path("/powpow", "/titi"))`, + rule: `!(Host("example.com") && Path("/js"))`, expected: map[string]int{ - "http://tchouk/toto": http.StatusOK, - "http://tchouk/powpow": http.StatusNotFound, - "http://pouet/powpow": http.StatusNotFound, - "http://tchouk/titi": http.StatusNotFound, - "http://pouet/titi": http.StatusNotFound, - "http://pouet/toto": http.StatusOK, - "http://plopi/a": http.StatusOK, - }, - }, - { - desc: "ClientIP empty", - rule: "ClientIP(``)", - expectedError: true, - }, - { - desc: "Invalid ClientIP", - rule: "ClientIP(`invalid`)", - expectedError: true, - }, - { - desc: "Non matching ClientIP", - rule: "ClientIP(`10.10.1.1`)", - remoteAddr: "10.0.0.0", - expected: map[string]int{ - "http://tchouk/toto": http.StatusNotFound, - }, - }, - { - desc: "Non matching IPv6", - rule: "ClientIP(`10::10`)", - remoteAddr: "::1", - expected: map[string]int{ - "http://tchouk/toto": http.StatusNotFound, - }, - }, - { - desc: "Matching IP", - rule: "ClientIP(`10.0.0.0`)", - remoteAddr: "10.0.0.0:8456", - expected: map[string]int{ - "http://tchouk/toto": http.StatusOK, - }, - }, - { - desc: "Matching IPv6", - rule: "ClientIP(`10::10`)", - remoteAddr: "10::10", - expected: map[string]int{ - "http://tchouk/toto": http.StatusOK, - }, - }, - { - desc: "Matching IP among several IP", - rule: "ClientIP(`10.0.0.1`, `10.0.0.0`)", - remoteAddr: "10.0.0.0", - expected: map[string]int{ - "http://tchouk/toto": http.StatusOK, - }, - }, - { - desc: "Non Matching IP with CIDR", - rule: "ClientIP(`11.0.0.0/24`)", - remoteAddr: "10.0.0.0", - expected: map[string]int{ - "http://tchouk/toto": http.StatusNotFound, - }, - }, - { - desc: "Non Matching IPv6 with CIDR", - rule: "ClientIP(`11::/16`)", - remoteAddr: "10::", - expected: map[string]int{ - "http://tchouk/toto": http.StatusNotFound, - }, - }, - { - desc: "Matching IP with CIDR", - rule: "ClientIP(`10.0.0.0/16`)", - remoteAddr: "10.0.0.0", - expected: map[string]int{ - "http://tchouk/toto": http.StatusOK, - }, - }, - { - desc: "Matching IPv6 with CIDR", - rule: "ClientIP(`10::/16`)", - remoteAddr: "10::10", - expected: map[string]int{ - "http://tchouk/toto": http.StatusOK, - }, - }, - { - desc: "Matching IP among several CIDR", - rule: "ClientIP(`11.0.0.0/16`, `10.0.0.0/16`)", - remoteAddr: "10.0.0.0", - expected: map[string]int{ - "http://tchouk/toto": http.StatusOK, - }, - }, - { - desc: "Matching IP among non matching CIDR and matching IP", - rule: "ClientIP(`11.0.0.0/16`, `10.0.0.0`)", - remoteAddr: "10.0.0.0", - expected: map[string]int{ - "http://tchouk/toto": http.StatusOK, - }, - }, - { - desc: "Matching IP among matching CIDR and non matching IP", - rule: "ClientIP(`11.0.0.0`, `10.0.0.0/16`)", - remoteAddr: "10.0.0.0", - expected: map[string]int{ - "http://tchouk/toto": http.StatusOK, + "https://example.com/js": http.StatusNotFound, + "https://example.com/html": http.StatusOK, + "https://example.org/js": http.StatusOK, + "https://example.com/css": http.StatusOK, + "https://example.org/css": http.StatusOK, + "https://example.org/html": http.StatusOK, + "https://example.eu/images": http.StatusOK, }, }, } @@ -644,36 +213,37 @@ func Test_addRoute(t *testing.T) { t.Run(test.desc, func(t *testing.T) { t.Parallel() - handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}) muxer, err := NewMuxer() require.NoError(t, err) + handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}) err = muxer.AddRoute(test.rule, 0, handler) if test.expectedError { require.Error(t, err) - } else { - require.NoError(t, err) - - // RequestDecorator is necessary for the host rule - reqHost := requestdecorator.New(nil) - - results := make(map[string]int) - for calledURL := range test.expected { - w := httptest.NewRecorder() - - req := testhelpers.MustNewRequest(http.MethodGet, calledURL, nil) - - // Useful for the ClientIP matcher - req.RemoteAddr = test.remoteAddr - - for key, value := range test.headers { - req.Header.Set(key, value) - } - reqHost.ServeHTTP(w, req, muxer.ServeHTTP) - results[calledURL] = w.Code - } - assert.Equal(t, test.expected, results) + return } + require.NoError(t, err) + + // RequestDecorator is necessary for the host rule + reqHost := requestdecorator.New(nil) + + results := make(map[string]int) + for calledURL := range test.expected { + req := testhelpers.MustNewRequest(http.MethodGet, calledURL, http.NoBody) + + // Useful for the ClientIP matcher + req.RemoteAddr = test.remoteAddr + + for key, value := range test.headers { + req.Header.Set(key, value) + } + + w := httptest.NewRecorder() + reqHost.ServeHTTP(w, req, muxer.ServeHTTP) + results[calledURL] = w.Code + } + + assert.Equal(t, test.expected, results) }) } } @@ -813,7 +383,7 @@ func Test_addRoutePriority(t *testing.T) { muxer.SortRoutes() w := httptest.NewRecorder() - req := testhelpers.MustNewRequest(http.MethodGet, test.path, nil) + req := testhelpers.MustNewRequest(http.MethodGet, test.path, http.NoBody) muxer.ServeHTTP(w, req) @@ -822,86 +392,6 @@ func Test_addRoutePriority(t *testing.T) { } } -func TestHostRegexp(t *testing.T) { - testCases := []struct { - desc string - hostExp string - urls map[string]bool - }{ - { - desc: "capturing group", - hostExp: "{subdomain:(foo\\.)?bar\\.com}", - urls: map[string]bool{ - "http://foo.bar.com": true, - "http://bar.com": true, - "http://fooubar.com": false, - "http://barucom": false, - "http://barcom": false, - }, - }, - { - desc: "non capturing group", - hostExp: "{subdomain:(?:foo\\.)?bar\\.com}", - urls: map[string]bool{ - "http://foo.bar.com": true, - "http://bar.com": true, - "http://fooubar.com": false, - "http://barucom": false, - "http://barcom": false, - }, - }, - { - desc: "regex insensitive", - hostExp: "{dummy:[A-Za-z-]+\\.bar\\.com}", - urls: map[string]bool{ - "http://FOO.bar.com": true, - "http://foo.bar.com": true, - "http://fooubar.com": false, - "http://barucom": false, - "http://barcom": false, - }, - }, - { - desc: "insensitive host", - hostExp: "{dummy:[a-z-]+\\.bar\\.com}", - urls: map[string]bool{ - "http://FOO.bar.com": true, - "http://foo.bar.com": true, - "http://fooubar.com": false, - "http://barucom": false, - "http://barcom": false, - }, - }, - { - desc: "insensitive host simple", - hostExp: "foo.bar.com", - urls: map[string]bool{ - "http://FOO.bar.com": true, - "http://foo.bar.com": true, - "http://fooubar.com": false, - "http://barucom": false, - "http://barcom": false, - }, - }, - } - - for _, test := range testCases { - test := test - t.Run(test.desc, func(t *testing.T) { - t.Parallel() - - rt := &mux.Route{} - err := hostRegexp(rt, test.hostExp) - require.NoError(t, err) - - for testURL, match := range test.urls { - req := testhelpers.MustNewRequest(http.MethodGet, testURL, nil) - assert.Equal(t, match, rt.Match(req, &mux.RouteMatch{}), testURL) - } - }) - } -} - func TestParseDomains(t *testing.T) { testCases := []struct { description string @@ -914,21 +404,6 @@ func TestParseDomains(t *testing.T) { expression: "Foobar(`foo.bar`,`test.bar`)", errorExpected: true, }, - { - description: "Several host rules", - expression: "Host(`foo.bar`,`test.bar`)", - domain: []string{"foo.bar", "test.bar"}, - }, - { - description: "Several host rules upper", - expression: "HOST(`foo.bar`,`test.bar`)", - domain: []string{"foo.bar", "test.bar"}, - }, - { - description: "Several host rules lower", - expression: "host(`foo.bar`,`test.bar`)", - domain: []string{"foo.bar", "test.bar"}, - }, { description: "No host rule", expression: "Path(`/test`)", @@ -938,6 +413,11 @@ func TestParseDomains(t *testing.T) { expression: "Host(`foo.bar`) && Path(`/test`)", domain: []string{"foo.bar"}, }, + { + description: "Host rule to trim and another rule", + expression: "Host(`Foo.Bar`) || Host(`bar.buz`) && Path(`/test`)", + domain: []string{"foo.bar", "bar.buz"}, + }, { description: "Host rule to trim and another rule", expression: "Host(`Foo.Bar`) && Path(`/test`)", @@ -967,7 +447,9 @@ func TestParseDomains(t *testing.T) { } } -func TestAbsoluteFormURL(t *testing.T) { +// TestEmptyHost is a non regression test for +// https://github.com/traefik/traefik/pull/9131 +func TestEmptyHost(t *testing.T) { testCases := []struct { desc string request string @@ -975,41 +457,41 @@ func TestAbsoluteFormURL(t *testing.T) { expected int }{ { - desc: "!HostRegexp with absolute-form URL with empty host with non-matching host header", - request: "GET http://@/ HTTP/1.1\r\nHost: test.localhost\r\n\r\n", - rule: "!HostRegexp(`test.localhost`)", - expected: http.StatusNotFound, - }, - { - desc: "!Host with absolute-form URL with empty host with non-matching host header", - request: "GET http://@/ HTTP/1.1\r\nHost: test.localhost\r\n\r\n", - rule: "!Host(`test.localhost`)", - expected: http.StatusNotFound, - }, - { - desc: "!HostRegexp with absolute-form URL with matching host header", - request: "GET http://test.localhost/ HTTP/1.1\r\nHost: toto.localhost\r\n\r\n", - rule: "!HostRegexp(`test.localhost`)", - expected: http.StatusNotFound, - }, - { - desc: "!Host with absolute-form URL with matching host header", - request: "GET http://test.localhost/ HTTP/1.1\r\nHost: toto.localhost\r\n\r\n", - rule: "!Host(`test.localhost`)", - expected: http.StatusNotFound, - }, - { - desc: "!HostRegexp with absolute-form URL with non-matching host header", - request: "GET http://test.localhost/ HTTP/1.1\r\nHost: toto.localhost\r\n\r\n", - rule: "!HostRegexp(`toto.localhost`)", + desc: "HostRegexp with absolute-form URL with empty host with non-matching host header", + request: "GET http://@/ HTTP/1.1\r\nHost: example.com\r\n\r\n", + rule: "HostRegexp(`example.com`)", expected: http.StatusOK, }, { - desc: "!Host with absolute-form URL with non-matching host header", - request: "GET http://test.localhost/ HTTP/1.1\r\nHost: toto.localhost\r\n\r\n", - rule: "!Host(`toto.localhost`)", + desc: "Host with absolute-form URL with empty host with non-matching host header", + request: "GET http://@/ HTTP/1.1\r\nHost: example.com\r\n\r\n", + rule: "Host(`example.com`)", expected: http.StatusOK, }, + { + desc: "HostRegexp with absolute-form URL with matching host header", + request: "GET http://example.com/ HTTP/1.1\r\nHost: example.org\r\n\r\n", + rule: "HostRegexp(`example.com`)", + expected: http.StatusOK, + }, + { + desc: "Host with absolute-form URL with matching host header", + request: "GET http://example.com/ HTTP/1.1\r\nHost: example.org\r\n\r\n", + rule: "Host(`example.com`)", + expected: http.StatusOK, + }, + { + desc: "HostRegexp with absolute-form URL with non-matching host header", + request: "GET http://example.com/ HTTP/1.1\r\nHost: example.org\r\n\r\n", + rule: "HostRegexp(`example.org`)", + expected: http.StatusNotFound, + }, + { + desc: "Host with absolute-form URL with non-matching host header", + request: "GET http://example.com/ HTTP/1.1\r\nHost: example.org\r\n\r\n", + rule: "Host(`example.org`)", + expected: http.StatusNotFound, + }, } for _, test := range testCases { diff --git a/pkg/muxer/tcp/matcher.go b/pkg/muxer/tcp/matcher.go new file mode 100644 index 000000000..40694c3ea --- /dev/null +++ b/pkg/muxer/tcp/matcher.go @@ -0,0 +1,134 @@ +package tcp + +import ( + "fmt" + "regexp" + "strings" + "unicode/utf8" + + "github.com/go-acme/lego/v4/challenge/tlsalpn01" + "github.com/rs/zerolog/log" + "github.com/traefik/traefik/v2/pkg/ip" +) + +var tcpFuncs = map[string]func(*matchersTree, ...string) error{ + "ALPN": expect1Parameter(alpn), + "ClientIP": expect1Parameter(clientIP), + "HostSNI": expect1Parameter(hostSNI), + "HostSNIRegexp": expect1Parameter(hostSNIRegexp), +} + +func expect1Parameter(fn func(*matchersTree, ...string) error) func(*matchersTree, ...string) error { + return func(route *matchersTree, s ...string) error { + if len(s) != 1 { + return fmt.Errorf("unexpected number of parameters; got %d, expected 1", len(s)) + } + + return fn(route, s...) + } +} + +// alpn checks if any of the connection ALPN protocols matches one of the matcher protocols. +func alpn(tree *matchersTree, protos ...string) error { + proto := protos[0] + + if proto == tlsalpn01.ACMETLS1Protocol { + return fmt.Errorf("invalid protocol value for ALPN matcher, %q is not allowed", proto) + } + + tree.matcher = func(meta ConnData) bool { + for _, alpnProto := range meta.alpnProtos { + if alpnProto == proto { + return true + } + } + + return false + } + + return nil +} + +func clientIP(tree *matchersTree, clientIP ...string) error { + checker, err := ip.NewChecker(clientIP) + if err != nil { + return fmt.Errorf("initializing IP checker for ClientIP matcher: %w", err) + } + + tree.matcher = func(meta ConnData) bool { + ok, err := checker.Contains(meta.remoteIP) + if err != nil { + log.Warn().Err(err).Msg("ClientIP matcher: could not match remote address") + return false + } + return ok + } + + return nil +} + +var almostFQDN = regexp.MustCompile(`^[[:alnum:]\.-]+$`) + +// hostSNI checks if the SNI Host of the connection match the matcher host. +func hostSNI(tree *matchersTree, hosts ...string) error { + host := hosts[0] + + if host == "*" { + // Since a HostSNI(`*`) rule has been provided as catchAll for non-TLS TCP, + // it allows matching with an empty serverName. + tree.matcher = func(meta ConnData) bool { return true } + return nil + } + + if !almostFQDN.MatchString(host) { + return fmt.Errorf("invalid value for HostSNI matcher, %q is not a valid hostname", host) + } + + tree.matcher = func(meta ConnData) bool { + if meta.serverName == "" { + return false + } + + if host == meta.serverName { + return true + } + + // trim trailing period in case of FQDN + host = strings.TrimSuffix(host, ".") + + return host == meta.serverName + } + + return nil +} + +// hostSNIRegexp checks if the SNI Host of the connection matches the matcher host regexp. +func hostSNIRegexp(tree *matchersTree, templates ...string) error { + template := templates[0] + + if !isASCII(template) { + return fmt.Errorf("invalid value for HostSNIRegexp matcher, %q is not a valid hostname", template) + } + + re, err := regexp.Compile(template) + if err != nil { + return fmt.Errorf("compiling HostSNIRegexp matcher: %w", err) + } + + tree.matcher = func(meta ConnData) bool { + return re.MatchString(meta.serverName) + } + + return nil +} + +// isASCII checks if the given string contains only ASCII characters. +func isASCII(s string) bool { + for i := 0; i < len(s); i++ { + if s[i] >= utf8.RuneSelf { + return false + } + } + + return true +} diff --git a/pkg/muxer/tcp/matcher_test.go b/pkg/muxer/tcp/matcher_test.go new file mode 100644 index 000000000..06b45e186 --- /dev/null +++ b/pkg/muxer/tcp/matcher_test.go @@ -0,0 +1,383 @@ +package tcp + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/traefik/traefik/v2/pkg/tcp" +) + +func Test_HostSNICatchAll(t *testing.T) { + testCases := []struct { + desc string + rule string + isCatchAll bool + }{ + { + desc: "HostSNI(`example.com`) is not catchAll", + rule: "HostSNI(`example.com`)", + }, + { + desc: "HostSNI(`*`) is catchAll", + rule: "HostSNI(`*`)", + isCatchAll: true, + }, + { + desc: "HostSNIRegexp(`^.*$`) is not catchAll", + rule: "HostSNIRegexp(`.*`)", + isCatchAll: false, + }, + } + + for _, test := range testCases { + test := test + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + muxer, err := NewMuxer() + require.NoError(t, err) + + err = muxer.AddRoute(test.rule, 0, tcp.HandlerFunc(func(conn tcp.WriteCloser) {})) + require.NoError(t, err) + + handler, catchAll := muxer.Match(ConnData{ + serverName: "example.com", + }) + require.NotNil(t, handler) + assert.Equal(t, test.isCatchAll, catchAll) + }) + } +} + +func Test_HostSNI(t *testing.T) { + testCases := []struct { + desc string + rule string + serverName string + buildErr bool + match bool + }{ + { + desc: "Empty", + buildErr: true, + }, + { + desc: "Invalid HostSNI matcher (empty host)", + rule: "HostSNI(``)", + buildErr: true, + }, + { + desc: "Invalid HostSNI matcher (too many parameters)", + rule: "HostSNI(`example.com`, `example.org`)", + buildErr: true, + }, + { + desc: "Invalid HostSNI matcher (globing sub domain)", + rule: "HostSNI(`*.com`)", + buildErr: true, + }, + { + desc: "Invalid HostSNI matcher (non ASCII host)", + rule: "HostSNI(`🦭.com`)", + buildErr: true, + }, + { + desc: "Valid HostSNI matcher - puny-coded emoji", + rule: "HostSNI(`xn--9t9h.com`)", + serverName: "xn--9t9h.com", + match: true, + }, + { + desc: "Valid HostSNI matcher - puny-coded emoji but emoji in server name", + rule: "HostSNI(`xn--9t9h.com`)", + serverName: "🦭.com", + }, + { + desc: "Matching hosts", + rule: "HostSNI(`example.com`)", + serverName: "example.com", + match: true, + }, + { + desc: "No matching hosts", + rule: "HostSNI(`example.com`)", + serverName: "example.org", + }, + { + desc: "Matching globing host `*`", + rule: "HostSNI(`*`)", + serverName: "example.com", + match: true, + }, + { + desc: "Matching globing host `*` and empty server name", + rule: "HostSNI(`*`)", + serverName: "", + match: true, + }, + { + desc: "Matching host with trailing dot", + rule: "HostSNI(`example.com.`)", + serverName: "example.com.", + match: true, + }, + { + desc: "Matching host with trailing dot but not in server name", + rule: "HostSNI(`example.com.`)", + serverName: "example.com", + match: true, + }, + { + desc: "Matching hosts with subdomains", + rule: "HostSNI(`foo.example.com`)", + serverName: "foo.example.com", + match: true, + }, + } + + for _, test := range testCases { + test := test + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + muxer, err := NewMuxer() + require.NoError(t, err) + + err = muxer.AddRoute(test.rule, 0, tcp.HandlerFunc(func(conn tcp.WriteCloser) {})) + if test.buildErr { + require.Error(t, err) + return + } + require.NoError(t, err) + + meta := ConnData{ + serverName: test.serverName, + } + + handler, _ := muxer.Match(meta) + require.Equal(t, test.match, handler != nil) + }) + } +} + +func Test_HostSNIRegexp(t *testing.T) { + testCases := []struct { + desc string + rule string + expected map[string]bool + buildErr bool + match bool + }{ + { + desc: "Empty", + buildErr: true, + }, + { + desc: "Invalid HostSNIRegexp matcher (empty host)", + rule: "HostSNIRegexp(``)", + buildErr: true, + }, + { + desc: "Invalid HostSNIRegexp matcher (non ASCII host)", + rule: "HostSNIRegexp(`🦭.com`)", + buildErr: true, + }, + { + desc: "Invalid HostSNIRegexp matcher (invalid regexp)", + rule: "HostSNIRegexp(`(example.com`)", + buildErr: true, + }, + { + desc: "Invalid HostSNIRegexp matcher (too many parameters)", + rule: "HostSNIRegexp(`example.com`, `example.org`)", + buildErr: true, + }, + { + desc: "valid HostSNIRegexp matcher", + rule: "HostSNIRegexp(`^example\\.(com|org)$`)", + expected: map[string]bool{ + "example.com": true, + "example.com.": false, + "EXAMPLE.com": false, + "example.org": true, + "exampleuorg": false, + "": false, + }, + }, + { + desc: "valid HostSNIRegexp matcher with Traefik v2 syntax", + rule: "HostSNIRegexp(`example.{tld:(com|org)}`)", + expected: map[string]bool{ + "example.com": false, + "example.com.": false, + "EXAMPLE.com": false, + "example.org": false, + "exampleuorg": false, + "": false, + }, + }, + } + + for _, test := range testCases { + test := test + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + muxer, err := NewMuxer() + require.NoError(t, err) + + err = muxer.AddRoute(test.rule, 0, tcp.HandlerFunc(func(conn tcp.WriteCloser) {})) + if test.buildErr { + require.Error(t, err) + return + } + require.NoError(t, err) + + for serverName, match := range test.expected { + meta := ConnData{ + serverName: serverName, + } + + handler, _ := muxer.Match(meta) + assert.Equal(t, match, handler != nil, serverName) + } + }) + } +} + +func Test_ClientIP(t *testing.T) { + testCases := []struct { + desc string + rule string + expected map[string]bool + buildErr bool + }{ + { + desc: "Empty", + buildErr: true, + }, + { + desc: "Invalid ClientIP matcher (empty host)", + rule: "ClientIP(``)", + buildErr: true, + }, + { + desc: "Invalid ClientIP matcher (non ASCII host)", + rule: "ClientIP(`🦭/32`)", + buildErr: true, + }, + { + desc: "Invalid ClientIP matcher (too many parameters)", + rule: "ClientIP(`127.0.0.1`, `127.0.0.2`)", + buildErr: true, + }, + { + desc: "valid ClientIP matcher", + rule: "ClientIP(`20.20.20.20`)", + expected: map[string]bool{ + "20.20.20.20": true, + "10.10.10.10": false, + }, + }, + { + desc: "valid ClientIP matcher with CIDR", + rule: "ClientIP(`20.20.20.20/24`)", + expected: map[string]bool{ + "20.20.20.20": true, + "20.20.20.40": true, + "10.10.10.10": false, + }, + }, + } + + for _, test := range testCases { + test := test + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + muxer, err := NewMuxer() + require.NoError(t, err) + + err = muxer.AddRoute(test.rule, 0, tcp.HandlerFunc(func(conn tcp.WriteCloser) {})) + if test.buildErr { + require.Error(t, err) + return + } + require.NoError(t, err) + + for remoteIP, match := range test.expected { + meta := ConnData{ + remoteIP: remoteIP, + } + + handler, _ := muxer.Match(meta) + assert.Equal(t, match, handler != nil, remoteIP) + } + }) + } +} + +func Test_ALPN(t *testing.T) { + testCases := []struct { + desc string + rule string + expected map[string]bool + buildErr bool + }{ + { + desc: "Empty", + buildErr: true, + }, + { + desc: "Invalid ALPN matcher (TLS proto)", + rule: "ALPN(`acme-tls/1`)", + buildErr: true, + }, + { + desc: "Invalid ALPN matcher (empty parameters)", + rule: "ALPN(``)", + buildErr: true, + }, + { + desc: "Invalid ALPN matcher (too many parameters)", + rule: "ALPN(`h2`, `mqtt`)", + buildErr: true, + }, + { + desc: "Valid ALPN matcher", + rule: "ALPN(`h2`)", + expected: map[string]bool{ + "h2": true, + "mqtt": false, + "": false, + }, + }, + } + + for _, test := range testCases { + test := test + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + muxer, err := NewMuxer() + require.NoError(t, err) + + err = muxer.AddRoute(test.rule, 0, tcp.HandlerFunc(func(conn tcp.WriteCloser) {})) + if test.buildErr { + require.Error(t, err) + return + } + require.NoError(t, err) + + for proto, match := range test.expected { + meta := ConnData{ + alpnProtos: []string{proto}, + } + + handler, _ := muxer.Match(meta) + assert.Equal(t, match, handler != nil, proto) + } + }) + } +} diff --git a/pkg/muxer/tcp/mux.go b/pkg/muxer/tcp/mux.go index 6d7421119..3c5abffaf 100644 --- a/pkg/muxer/tcp/mux.go +++ b/pkg/muxer/tcp/mux.go @@ -1,31 +1,18 @@ package tcp import ( - "bytes" - "errors" "fmt" "net" - "regexp" "sort" - "strconv" "strings" - "github.com/go-acme/lego/v4/challenge/tlsalpn01" "github.com/rs/zerolog/log" - "github.com/traefik/traefik/v2/pkg/ip" "github.com/traefik/traefik/v2/pkg/rules" "github.com/traefik/traefik/v2/pkg/tcp" "github.com/traefik/traefik/v2/pkg/types" "github.com/vulcand/predicate" ) -var tcpFuncs = map[string]func(*matchersTree, ...string) error{ - "HostSNI": hostSNI, - "HostSNIRegexp": hostSNIRegexp, - "ClientIP": clientIP, - "ALPN": alpn, -} - // ParseHostSNI extracts the HostSNIs declared in a rule. // This is a first naive implementation used in TCP routing. func ParseHostSNI(rule string) ([]string, error) { @@ -261,233 +248,7 @@ func (m *matchersTree) match(meta ConnData) bool { return m.left.match(meta) && m.right.match(meta) default: // This should never happen as it should have been detected during parsing. - log.Warn().Msgf("Invalid rule operator %s", m.operator) + log.Warn().Str("operator", m.operator).Msg("Invalid rule operator") return false } } - -func clientIP(tree *matchersTree, clientIPs ...string) error { - checker, err := ip.NewChecker(clientIPs) - if err != nil { - return fmt.Errorf("could not initialize IP Checker for \"ClientIP\" matcher: %w", err) - } - - tree.matcher = func(meta ConnData) bool { - if meta.remoteIP == "" { - return false - } - - ok, err := checker.Contains(meta.remoteIP) - if err != nil { - log.Warn().Err(err).Msg("\"ClientIP\" matcher: could not match remote address") - return false - } - return ok - } - - return nil -} - -// alpn checks if any of the connection ALPN protocols matches one of the matcher protocols. -func alpn(tree *matchersTree, protos ...string) error { - if len(protos) == 0 { - return errors.New("empty value for \"ALPN\" matcher is not allowed") - } - - for _, proto := range protos { - if proto == tlsalpn01.ACMETLS1Protocol { - return fmt.Errorf("invalid protocol value for \"ALPN\" matcher, %q is not allowed", proto) - } - } - - tree.matcher = func(meta ConnData) bool { - for _, proto := range meta.alpnProtos { - for _, filter := range protos { - if proto == filter { - return true - } - } - } - - return false - } - - return nil -} - -var almostFQDN = regexp.MustCompile(`^[[:alnum:]\.-]+$`) - -// hostSNI checks if the SNI Host of the connection match the matcher host. -func hostSNI(tree *matchersTree, hosts ...string) error { - if len(hosts) == 0 { - return errors.New("empty value for \"HostSNI\" matcher is not allowed") - } - - for i, host := range hosts { - // Special case to allow global wildcard - if host == "*" { - continue - } - - if !almostFQDN.MatchString(host) { - return fmt.Errorf("invalid value for \"HostSNI\" matcher, %q is not a valid hostname", host) - } - - hosts[i] = strings.ToLower(host) - } - - tree.matcher = func(meta ConnData) bool { - // Since a HostSNI(`*`) rule has been provided as catchAll for non-TLS TCP, - // it allows matching with an empty serverName. - // Which is why we make sure to take that case into account before - // checking meta.serverName. - if hosts[0] == "*" { - return true - } - - if meta.serverName == "" { - return false - } - - for _, host := range hosts { - if host == "*" { - return true - } - - if host == meta.serverName { - return true - } - - // trim trailing period in case of FQDN - host = strings.TrimSuffix(host, ".") - if host == meta.serverName { - return true - } - } - - return false - } - - return nil -} - -// hostSNIRegexp checks if the SNI Host of the connection matches the matcher host regexp. -func hostSNIRegexp(tree *matchersTree, templates ...string) error { - if len(templates) == 0 { - return fmt.Errorf("empty value for \"HostSNIRegexp\" matcher is not allowed") - } - - var regexps []*regexp.Regexp - - for _, template := range templates { - preparedPattern, err := preparePattern(template) - if err != nil { - return fmt.Errorf("invalid pattern value for \"HostSNIRegexp\" matcher, %q is not a valid pattern: %w", template, err) - } - - regexp, err := regexp.Compile(preparedPattern) - if err != nil { - return err - } - - regexps = append(regexps, regexp) - } - - tree.matcher = func(meta ConnData) bool { - for _, regexp := range regexps { - if regexp.MatchString(meta.serverName) { - return true - } - } - - return false - } - - return nil -} - -// TODO: expose more of containous/mux fork to get rid of the following copied code (https://github.com/containous/mux/blob/8ffa4f6d063c/regexp.go). - -// preparePattern builds a regexp pattern from the initial user defined expression. -// This function reuses the code dedicated to host matching of the newRouteRegexp func from the gorilla/mux library. -// https://github.com/containous/mux/tree/8ffa4f6d063c1e2b834a73be6a1515cca3992618. -func preparePattern(template string) (string, error) { - // Check if it is well-formed. - idxs, errBraces := braceIndices(template) - if errBraces != nil { - return "", errBraces - } - - defaultPattern := "[^.]+" - pattern := bytes.NewBufferString("") - - // Host SNI matching is case-insensitive - fmt.Fprint(pattern, "(?i)") - - pattern.WriteByte('^') - var end int - var err error - for i := 0; i < len(idxs); i += 2 { - // Set all values we are interested in. - raw := template[end:idxs[i]] - end = idxs[i+1] - parts := strings.SplitN(template[idxs[i]+1:end-1], ":", 2) - name := parts[0] - patt := defaultPattern - if len(parts) == 2 { - patt = parts[1] - } - // Name or pattern can't be empty. - if name == "" || patt == "" { - return "", fmt.Errorf("mux: missing name or pattern in %q", - template[idxs[i]:end]) - } - // Build the regexp pattern. - fmt.Fprintf(pattern, "%s(?P<%s>%s)", regexp.QuoteMeta(raw), varGroupName(i/2), patt) - - // Append variable name and compiled pattern. - if err != nil { - return "", err - } - } - - // Add the remaining. - raw := template[end:] - pattern.WriteString(regexp.QuoteMeta(raw)) - pattern.WriteByte('$') - - return pattern.String(), nil -} - -// varGroupName builds a capturing group name for the indexed variable. -// This function is a copy of varGroupName func from the gorilla/mux library. -// https://github.com/containous/mux/tree/8ffa4f6d063c1e2b834a73be6a1515cca3992618. -func varGroupName(idx int) string { - return "v" + strconv.Itoa(idx) -} - -// braceIndices returns the first level curly brace indices from a string. -// This function is a copy of braceIndices func from the gorilla/mux library. -// https://github.com/containous/mux/tree/8ffa4f6d063c1e2b834a73be6a1515cca3992618. -func braceIndices(s string) ([]int, error) { - var level, idx int - var idxs []int - for i := 0; i < len(s); i++ { - switch s[i] { - case '{': - if level++; level == 1 { - idx = i - } - case '}': - if level--; level == 0 { - idxs = append(idxs, idx, i+1) - } else if level < 0 { - return nil, fmt.Errorf("mux: unbalanced braces in %q", s) - } - } - } - if level != 0 { - return nil, fmt.Errorf("mux: unbalanced braces in %q", s) - } - return idxs, nil -} diff --git a/pkg/muxer/tcp/mux_test.go b/pkg/muxer/tcp/mux_test.go index 50b8938cf..95e84a245 100644 --- a/pkg/muxer/tcp/mux_test.go +++ b/pkg/muxer/tcp/mux_test.go @@ -1,59 +1,15 @@ package tcp import ( - "fmt" "net" "testing" "time" - "github.com/go-acme/lego/v4/challenge/tlsalpn01" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/traefik/traefik/v2/pkg/tcp" ) -type fakeConn struct { - call map[string]int - remoteAddr net.Addr -} - -func (f *fakeConn) Read(b []byte) (n int, err error) { - panic("implement me") -} - -func (f *fakeConn) Write(b []byte) (n int, err error) { - f.call[string(b)]++ - return len(b), nil -} - -func (f *fakeConn) Close() error { - panic("implement me") -} - -func (f *fakeConn) LocalAddr() net.Addr { - panic("implement me") -} - -func (f *fakeConn) RemoteAddr() net.Addr { - return f.remoteAddr -} - -func (f *fakeConn) SetDeadline(t time.Time) error { - panic("implement me") -} - -func (f *fakeConn) SetReadDeadline(t time.Time) error { - panic("implement me") -} - -func (f *fakeConn) SetWriteDeadline(t time.Time) error { - panic("implement me") -} - -func (f *fakeConn) CloseWrite() error { - panic("implement me") -} - func Test_addTCPRoute(t *testing.T) { testCases := []struct { desc string @@ -73,430 +29,225 @@ func Test_addTCPRoute(t *testing.T) { rule: "rulewithnotmatcher", routeErr: true, }, - { - desc: "Empty HostSNI rule", - rule: "HostSNI()", - serverName: "foobar", - routeErr: true, - }, { desc: "Empty HostSNI rule", rule: "HostSNI(``)", - serverName: "foobar", + serverName: "example.org", routeErr: true, }, { desc: "Valid HostSNI rule matching", - rule: "HostSNI(`foobar`)", - serverName: "foobar", + rule: "HostSNI(`example.org`)", + serverName: "example.org", }, { desc: "Valid negative HostSNI rule matching", - rule: "!HostSNI(`bar`)", - serverName: "foobar", + rule: "!HostSNI(`example.com`)", + serverName: "example.org", }, { desc: "Valid HostSNI rule matching with alternative case", - rule: "hostsni(`foobar`)", - serverName: "foobar", + rule: "hostsni(`example.org`)", + serverName: "example.org", }, { desc: "Valid HostSNI rule matching with alternative case", - rule: "HOSTSNI(`foobar`)", - serverName: "foobar", + rule: "HOSTSNI(`example.org`)", + serverName: "example.org", }, { desc: "Valid HostSNI rule not matching", - rule: "HostSNI(`foobar`)", - serverName: "bar", - matchErr: true, - }, - { - desc: "Empty HostSNIRegexp rule", - rule: "HostSNIRegexp()", - serverName: "foobar", - routeErr: true, - }, - { - desc: "Empty HostSNIRegexp rule", - rule: "HostSNIRegexp(``)", - serverName: "foobar", - routeErr: true, - }, - { - desc: "Valid HostSNIRegexp rule matching", - rule: "HostSNIRegexp(`{subdomain:[a-z]+}.foobar`)", - serverName: "sub.foobar", - }, - { - desc: "Valid negative HostSNIRegexp rule matching", - rule: "!HostSNIRegexp(`bar`)", - serverName: "foobar", - }, - { - desc: "Valid HostSNIRegexp rule matching with alternative case", - rule: "hostsniregexp(`foobar`)", - serverName: "foobar", - }, - { - desc: "Valid HostSNIRegexp rule matching with alternative case", - rule: "HOSTSNIREGEXP(`foobar`)", - serverName: "foobar", - }, - { - desc: "Valid HostSNIRegexp rule not matching", - rule: "HostSNIRegexp(`foobar`)", - serverName: "bar", + rule: "HostSNI(`example.org`)", + serverName: "example.com", matchErr: true, }, { desc: "Valid negative HostSNI rule not matching", - rule: "!HostSNI(`bar`)", - serverName: "bar", + rule: "!HostSNI(`example.com`)", + serverName: "example.com", matchErr: true, }, - { - desc: "Valid HostSNIRegexp rule matching empty servername", - rule: "HostSNIRegexp(`{subdomain:[a-z]*}`)", - serverName: "", - }, - { - desc: "Valid HostSNIRegexp rule with one name", - rule: "HostSNIRegexp(`{dummy}`)", - serverName: "toto", - }, - { - desc: "Valid HostSNIRegexp rule with one name 2", - rule: "HostSNIRegexp(`{dummy}`)", - serverName: "toto.com", - matchErr: true, - }, - { - desc: "Empty ClientIP rule", - rule: "ClientIP()", - routeErr: true, - }, - { - desc: "Empty ClientIP rule", - rule: "ClientIP(``)", - routeErr: true, - }, - { - desc: "Invalid ClientIP", - rule: "ClientIP(`invalid`)", - routeErr: true, - }, - { - desc: "Invalid remoteAddr", - rule: "ClientIP(`10.0.0.1`)", - remoteAddr: "not.an.IP:80", - matchErr: true, - }, - { - desc: "Valid ClientIP rule matching", - rule: "ClientIP(`10.0.0.1`)", - remoteAddr: "10.0.0.1:80", - }, - { - desc: "Valid negative ClientIP rule matching", - rule: "!ClientIP(`20.0.0.1`)", - remoteAddr: "10.0.0.1:80", - }, - { - desc: "Valid ClientIP rule matching with alternative case", - rule: "clientip(`10.0.0.1`)", - remoteAddr: "10.0.0.1:80", - }, - { - desc: "Valid ClientIP rule matching with alternative case", - rule: "CLIENTIP(`10.0.0.1`)", - remoteAddr: "10.0.0.1:80", - }, - { - desc: "Valid ClientIP rule not matching", - rule: "ClientIP(`10.0.0.1`)", - remoteAddr: "10.0.0.2:80", - matchErr: true, - }, - { - desc: "Valid negative ClientIP rule not matching", - rule: "!ClientIP(`10.0.0.2`)", - remoteAddr: "10.0.0.2:80", - matchErr: true, - }, - { - desc: "Valid ClientIP rule matching IPv6", - rule: "ClientIP(`10::10`)", - remoteAddr: "[10::10]:80", - }, - { - desc: "Valid negative ClientIP rule matching IPv6", - rule: "!ClientIP(`10::10`)", - remoteAddr: "[::1]:80", - }, - { - desc: "Valid ClientIP rule not matching IPv6", - rule: "ClientIP(`10::10`)", - remoteAddr: "[::1]:80", - matchErr: true, - }, - { - desc: "Valid ClientIP rule matching multiple IPs", - rule: "ClientIP(`10.0.0.1`, `10.0.0.0`)", - remoteAddr: "10.0.0.0:80", - }, - { - desc: "Valid ClientIP rule matching CIDR", - rule: "ClientIP(`11.0.0.0/24`)", - remoteAddr: "11.0.0.0:80", - }, - { - desc: "Valid ClientIP rule not matching CIDR", - rule: "ClientIP(`11.0.0.0/24`)", - remoteAddr: "10.0.0.0:80", - matchErr: true, - }, - { - desc: "Valid ClientIP rule matching CIDR IPv6", - rule: "ClientIP(`11::/16`)", - remoteAddr: "[11::]:80", - }, - { - desc: "Valid ClientIP rule not matching CIDR IPv6", - rule: "ClientIP(`11::/16`)", - remoteAddr: "[10::]:80", - matchErr: true, - }, - { - desc: "Valid ClientIP rule matching multiple CIDR", - rule: "ClientIP(`11.0.0.0/16`, `10.0.0.0/16`)", - remoteAddr: "10.0.0.0:80", - }, - { - desc: "Valid ClientIP rule not matching CIDR and matching IP", - rule: "ClientIP(`11.0.0.0/16`, `10.0.0.0`)", - remoteAddr: "10.0.0.0:80", - }, - { - desc: "Valid ClientIP rule matching CIDR and not matching IP", - rule: "ClientIP(`11.0.0.0`, `10.0.0.0/16`)", - remoteAddr: "10.0.0.0:80", - }, { desc: "Valid HostSNI and ClientIP rule matching", - rule: "HostSNI(`foobar`) && ClientIP(`10.0.0.1`)", - serverName: "foobar", + rule: "HostSNI(`example.org`) && ClientIP(`10.0.0.1`)", + serverName: "example.org", remoteAddr: "10.0.0.1:80", }, { desc: "Valid negative HostSNI and ClientIP rule matching", - rule: "!HostSNI(`bar`) && ClientIP(`10.0.0.1`)", - serverName: "foobar", + rule: "!HostSNI(`example.com`) && ClientIP(`10.0.0.1`)", + serverName: "example.org", remoteAddr: "10.0.0.1:80", }, { desc: "Valid HostSNI and negative ClientIP rule matching", - rule: "HostSNI(`foobar`) && !ClientIP(`10.0.0.2`)", - serverName: "foobar", + rule: "HostSNI(`example.org`) && !ClientIP(`10.0.0.2`)", + serverName: "example.org", remoteAddr: "10.0.0.1:80", }, { desc: "Valid negative HostSNI and negative ClientIP rule matching", - rule: "!HostSNI(`bar`) && !ClientIP(`10.0.0.2`)", - serverName: "foobar", + rule: "!HostSNI(`example.com`) && !ClientIP(`10.0.0.2`)", + serverName: "example.org", remoteAddr: "10.0.0.1:80", }, { desc: "Valid negative HostSNI or negative ClientIP rule matching", - rule: "!(HostSNI(`bar`) || ClientIP(`10.0.0.2`))", - serverName: "foobar", + rule: "!(HostSNI(`example.com`) || ClientIP(`10.0.0.2`))", + serverName: "example.org", remoteAddr: "10.0.0.1:80", }, { desc: "Valid negative HostSNI and negative ClientIP rule matching", - rule: "!(HostSNI(`bar`) && ClientIP(`10.0.0.2`))", - serverName: "foobar", + rule: "!(HostSNI(`example.com`) && ClientIP(`10.0.0.2`))", + serverName: "example.org", remoteAddr: "10.0.0.2:80", }, { desc: "Valid negative HostSNI and negative ClientIP rule matching", - rule: "!(HostSNI(`bar`) && ClientIP(`10.0.0.2`))", - serverName: "bar", + rule: "!(HostSNI(`example.com`) && ClientIP(`10.0.0.2`))", + serverName: "example.com", remoteAddr: "10.0.0.1:80", }, { desc: "Valid negative HostSNI and negative ClientIP rule matching", - rule: "!(HostSNI(`bar`) && ClientIP(`10.0.0.2`))", - serverName: "bar", + rule: "!(HostSNI(`example.com`) && ClientIP(`10.0.0.2`))", + serverName: "example.com", remoteAddr: "10.0.0.2:80", matchErr: true, }, { desc: "Valid negative HostSNI and negative ClientIP rule matching", - rule: "!(HostSNI(`bar`) && ClientIP(`10.0.0.2`))", - serverName: "foobar", + rule: "!(HostSNI(`example.com`) && ClientIP(`10.0.0.2`))", + serverName: "example.org", remoteAddr: "10.0.0.1:80", }, { desc: "Valid HostSNI and ClientIP rule not matching", - rule: "HostSNI(`foobar`) && ClientIP(`10.0.0.1`)", - serverName: "bar", + rule: "HostSNI(`example.org`) && ClientIP(`10.0.0.1`)", + serverName: "example.com", remoteAddr: "10.0.0.1:80", matchErr: true, }, { desc: "Valid HostSNI and ClientIP rule not matching", - rule: "HostSNI(`foobar`) && ClientIP(`10.0.0.1`)", - serverName: "foobar", + rule: "HostSNI(`example.org`) && ClientIP(`10.0.0.1`)", + serverName: "example.org", remoteAddr: "10.0.0.2:80", matchErr: true, }, { desc: "Valid HostSNI or ClientIP rule matching", - rule: "HostSNI(`foobar`) || ClientIP(`10.0.0.1`)", - serverName: "foobar", + rule: "HostSNI(`example.org`) || ClientIP(`10.0.0.1`)", + serverName: "example.org", remoteAddr: "10.0.0.1:80", }, { desc: "Valid HostSNI or ClientIP rule matching", - rule: "HostSNI(`foobar`) || ClientIP(`10.0.0.1`)", - serverName: "bar", + rule: "HostSNI(`example.org`) || ClientIP(`10.0.0.1`)", + serverName: "example.com", remoteAddr: "10.0.0.1:80", }, { desc: "Valid HostSNI or ClientIP rule matching", - rule: "HostSNI(`foobar`) || ClientIP(`10.0.0.1`)", - serverName: "foobar", + rule: "HostSNI(`example.org`) || ClientIP(`10.0.0.1`)", + serverName: "example.org", remoteAddr: "10.0.0.2:80", }, { desc: "Valid HostSNI or ClientIP rule not matching", - rule: "HostSNI(`foobar`) || ClientIP(`10.0.0.1`)", - serverName: "bar", + rule: "HostSNI(`example.org`) || ClientIP(`10.0.0.1`)", + serverName: "example.com", remoteAddr: "10.0.0.2:80", matchErr: true, }, { desc: "Valid HostSNI x 3 OR rule matching", - rule: "HostSNI(`foobar`) || HostSNI(`foo`) || HostSNI(`bar`)", - serverName: "foobar", + rule: "HostSNI(`example.org`) || HostSNI(`example.eu`) || HostSNI(`example.com`)", + serverName: "example.org", }, { desc: "Valid HostSNI x 3 OR rule not matching", - rule: "HostSNI(`foobar`) || HostSNI(`foo`) || HostSNI(`bar`)", + rule: "HostSNI(`example.org`) || HostSNI(`example.eu`) || HostSNI(`example.com`)", serverName: "baz", matchErr: true, }, { desc: "Valid HostSNI and ClientIP Combined rule matching", - rule: "HostSNI(`foobar`) || HostSNI(`bar`) && ClientIP(`10.0.0.1`)", - serverName: "foobar", + rule: "HostSNI(`example.org`) || HostSNI(`example.com`) && ClientIP(`10.0.0.1`)", + serverName: "example.org", remoteAddr: "10.0.0.2:80", }, { desc: "Valid HostSNI and ClientIP Combined rule matching", - rule: "HostSNI(`foobar`) || HostSNI(`bar`) && ClientIP(`10.0.0.1`)", - serverName: "bar", + rule: "HostSNI(`example.org`) || HostSNI(`example.com`) && ClientIP(`10.0.0.1`)", + serverName: "example.com", remoteAddr: "10.0.0.1:80", }, { desc: "Valid HostSNI and ClientIP Combined rule not matching", - rule: "HostSNI(`foobar`) || HostSNI(`bar`) && ClientIP(`10.0.0.1`)", - serverName: "bar", + rule: "HostSNI(`example.org`) || HostSNI(`example.com`) && ClientIP(`10.0.0.1`)", + serverName: "example.com", remoteAddr: "10.0.0.2:80", matchErr: true, }, { desc: "Valid HostSNI and ClientIP Combined rule not matching", - rule: "HostSNI(`foobar`) || HostSNI(`bar`) && ClientIP(`10.0.0.1`)", + rule: "HostSNI(`example.org`) || HostSNI(`example.com`) && ClientIP(`10.0.0.1`)", serverName: "baz", remoteAddr: "10.0.0.1:80", matchErr: true, }, { desc: "Valid HostSNI and ClientIP complex combined rule matching", - rule: "(HostSNI(`foobar`) || HostSNI(`bar`)) && (ClientIP(`10.0.0.1`) || ClientIP(`10.0.0.2`))", - serverName: "bar", + rule: "(HostSNI(`example.org`) || HostSNI(`example.com`)) && (ClientIP(`10.0.0.1`) || ClientIP(`10.0.0.2`))", + serverName: "example.com", remoteAddr: "10.0.0.1:80", }, { desc: "Valid HostSNI and ClientIP complex combined rule not matching", - rule: "(HostSNI(`foobar`) || HostSNI(`bar`)) && (ClientIP(`10.0.0.1`) || ClientIP(`10.0.0.2`))", + rule: "(HostSNI(`example.org`) || HostSNI(`example.com`)) && (ClientIP(`10.0.0.1`) || ClientIP(`10.0.0.2`))", serverName: "baz", remoteAddr: "10.0.0.1:80", matchErr: true, }, { desc: "Valid HostSNI and ClientIP complex combined rule not matching", - rule: "(HostSNI(`foobar`) || HostSNI(`bar`)) && (ClientIP(`10.0.0.1`) || ClientIP(`10.0.0.2`))", - serverName: "bar", + rule: "(HostSNI(`example.org`) || HostSNI(`example.com`)) && (ClientIP(`10.0.0.1`) || ClientIP(`10.0.0.2`))", + serverName: "example.com", remoteAddr: "10.0.0.3:80", matchErr: true, }, { desc: "Valid HostSNI and ClientIP more complex (but absurd) combined rule matching", - rule: "(HostSNI(`foobar`) || (HostSNI(`bar`) && !HostSNI(`foobar`))) && ((ClientIP(`10.0.0.1`) && !ClientIP(`10.0.0.2`)) || ClientIP(`10.0.0.2`)) ", - serverName: "bar", + rule: "(HostSNI(`example.org`) || (HostSNI(`example.com`) && !HostSNI(`example.org`))) && ((ClientIP(`10.0.0.1`) && !ClientIP(`10.0.0.2`)) || ClientIP(`10.0.0.2`)) ", + serverName: "example.com", remoteAddr: "10.0.0.1:80", }, - { - desc: "Invalid ALPN rule matching ACME-TLS/1", - rule: fmt.Sprintf("ALPN(`%s`)", tlsalpn01.ACMETLS1Protocol), - protos: []string{"foo"}, - routeErr: true, - }, - { - desc: "Valid ALPN rule matching single protocol", - rule: "ALPN(`foo`)", - protos: []string{"foo"}, - }, - { - desc: "Valid ALPN rule matching ACME-TLS/1 protocol", - rule: "ALPN(`foo`)", - protos: []string{tlsalpn01.ACMETLS1Protocol}, - matchErr: true, - }, - { - desc: "Valid ALPN rule not matching single protocol", - rule: "ALPN(`foo`)", - protos: []string{"bar"}, - matchErr: true, - }, - { - desc: "Valid alternative case ALPN rule matching single protocol without another being supported", - rule: "ALPN(`foo`) && !alpn(`h2`)", - protos: []string{"foo", "bar"}, - }, - { - desc: "Valid alternative case ALPN rule not matching single protocol because of another being supported", - rule: "ALPN(`foo`) && !alpn(`h2`)", - protos: []string{"foo", "h2", "bar"}, - matchErr: true, - }, { desc: "Valid complex alternative case ALPN and HostSNI rule", - rule: "ALPN(`foo`) && (!alpn(`h2`) || hostsni(`foo`))", - protos: []string{"foo", "bar"}, - serverName: "foo", + rule: "ALPN(`h2c`) && (!ALPN(`h2`) || HostSNI(`example.eu`))", + protos: []string{"h2c", "mqtt"}, + serverName: "example.eu", }, { desc: "Valid complex alternative case ALPN and HostSNI rule not matching by SNI", - rule: "ALPN(`foo`) && (!alpn(`h2`) || hostsni(`foo`))", - protos: []string{"foo", "bar", "h2"}, - serverName: "bar", + rule: "ALPN(`h2c`) && (!ALPN(`h2`) || HostSNI(`example.eu`))", + protos: []string{"h2c", "http/1.1", "h2"}, + serverName: "example.com", matchErr: true, }, { desc: "Valid complex alternative case ALPN and HostSNI rule matching by ALPN", - rule: "ALPN(`foo`) && (!alpn(`h2`) || hostsni(`foo`))", - protos: []string{"foo", "bar"}, - serverName: "bar", + rule: "ALPN(`h2c`) && (!ALPN(`h2`) || HostSNI(`example.eu`))", + protos: []string{"h2c", "http/1.1"}, + serverName: "example.com", }, { desc: "Valid complex alternative case ALPN and HostSNI rule not matching by protos", - rule: "ALPN(`foo`) && (!alpn(`h2`) || hostsni(`foo`))", - protos: []string{"h2", "bar"}, - serverName: "bar", + rule: "ALPN(`h2c`) && (!ALPN(`h2`) || HostSNI(`example.eu`))", + protos: []string{"http/1.1", "mqtt"}, + serverName: "example.com", matchErr: true, }, } @@ -554,68 +305,56 @@ func Test_addTCPRoute(t *testing.T) { } } -type fakeAddr struct { - addr string -} - -func (f fakeAddr) String() string { - return f.addr -} - -func (f fakeAddr) Network() string { - panic("Implement me") -} - func TestParseHostSNI(t *testing.T) { testCases := []struct { - description string + desc string expression string domain []string errorExpected bool }{ { - description: "Unknown rule", - expression: "Foobar(`foo.bar`,`test.bar`)", + desc: "Unknown rule", + expression: "Unknown(`example.com`)", errorExpected: true, }, { - description: "Many hostSNI rules", - expression: "HostSNI(`foo.bar`,`test.bar`)", - domain: []string{"foo.bar", "test.bar"}, + desc: "HostSNI rule", + expression: "HostSNI(`example.com`)", + domain: []string{"example.com"}, }, { - description: "Many hostSNI rules upper", - expression: "HOSTSNI(`foo.bar`,`test.bar`)", - domain: []string{"foo.bar", "test.bar"}, + desc: "HostSNI rule upper", + expression: "HOSTSNI(`example.com`)", + domain: []string{"example.com"}, }, { - description: "Many hostSNI rules lower", - expression: "hostsni(`foo.bar`,`test.bar`)", - domain: []string{"foo.bar", "test.bar"}, + desc: "HostSNI rule lower", + expression: "hostsni(`example.com`)", + domain: []string{"example.com"}, }, { - description: "No hostSNI rule", - expression: "ClientIP(`10.1`)", + desc: "No hostSNI rule", + expression: "ClientIP(`10.1`)", }, { - description: "HostSNI rule and another rule", - expression: "HostSNI(`foo.bar`) && ClientIP(`10.1`)", - domain: []string{"foo.bar"}, + desc: "HostSNI rule and another rule", + expression: "HostSNI(`example.com`) && ClientIP(`10.1`)", + domain: []string{"example.com"}, }, { - description: "HostSNI rule to lower and another rule", - expression: "HostSNI(`Foo.Bar`) && ClientIP(`10.1`)", - domain: []string{"foo.bar"}, + desc: "HostSNI rule to lower and another rule", + expression: "HostSNI(`example.com`) && ClientIP(`10.1`)", + domain: []string{"example.com"}, }, { - description: "HostSNI rule with no domain", - expression: "HostSNI() && ClientIP(`10.1`)", + desc: "HostSNI rule with no domain", + expression: "HostSNI() && ClientIP(`10.1`)", }, } for _, test := range testCases { test := test - t.Run(test.expression, func(t *testing.T) { + t.Run(test.desc, func(t *testing.T) { t.Parallel() domains, err := ParseHostSNI(test.expression) @@ -631,468 +370,48 @@ func TestParseHostSNI(t *testing.T) { } } -func Test_HostSNICatchAll(t *testing.T) { - testCases := []struct { - desc string - rule string - isCatchAll bool - }{ - { - desc: "HostSNI(`foobar`) is not catchAll", - rule: "HostSNI(`foobar`)", - }, - { - desc: "HostSNI(`*`) is catchAll", - rule: "HostSNI(`*`)", - isCatchAll: true, - }, - { - desc: "HOSTSNI(`*`) is catchAll", - rule: "HOSTSNI(`*`)", - isCatchAll: true, - }, - { - desc: `HostSNI("*") is catchAll`, - rule: `HostSNI("*")`, - isCatchAll: true, - }, - } - - for _, test := range testCases { - test := test - - t.Run(test.desc, func(t *testing.T) { - t.Parallel() - - muxer, err := NewMuxer() - require.NoError(t, err) - - err = muxer.AddRoute(test.rule, 0, tcp.HandlerFunc(func(conn tcp.WriteCloser) {})) - require.NoError(t, err) - - handler, catchAll := muxer.Match(ConnData{ - serverName: "foobar", - }) - require.NotNil(t, handler) - assert.Equal(t, test.isCatchAll, catchAll) - }) - } -} - -func Test_HostSNI(t *testing.T) { - testCases := []struct { - desc string - ruleHosts []string - serverName string - buildErr bool - matchErr bool - }{ - { - desc: "Empty", - buildErr: true, - }, - { - desc: "Non ASCII host", - ruleHosts: []string{"héhé"}, - buildErr: true, - }, - { - desc: "Not Matching hosts", - ruleHosts: []string{"foobar"}, - serverName: "bar", - matchErr: true, - }, - { - desc: "Matching globing host `*`", - ruleHosts: []string{"*"}, - serverName: "foobar", - }, - { - desc: "Matching globing host `*` and empty serverName", - ruleHosts: []string{"*"}, - serverName: "", - }, - { - desc: "Matching globing host `*` and another non matching host", - ruleHosts: []string{"foo", "*"}, - serverName: "bar", - }, - { - desc: "Matching globing host `*` and another non matching host, and empty servername", - ruleHosts: []string{"foo", "*"}, - serverName: "", - matchErr: true, - }, - { - desc: "Not Matching globing host with subdomain", - ruleHosts: []string{"*.bar"}, - buildErr: true, - }, - { - desc: "Not Matching host with trailing dot with ", - ruleHosts: []string{"foobar."}, - serverName: "foobar.", - }, - { - desc: "Matching host with trailing dot", - ruleHosts: []string{"foobar."}, - serverName: "foobar", - }, - { - desc: "Matching hosts", - ruleHosts: []string{"foobar"}, - serverName: "foobar", - }, - { - desc: "Matching hosts with subdomains", - ruleHosts: []string{"foo.bar"}, - serverName: "foo.bar", - }, - } - - for _, test := range testCases { - test := test - - t.Run(test.desc, func(t *testing.T) { - t.Parallel() - - matcherTree := &matchersTree{} - err := hostSNI(matcherTree, test.ruleHosts...) - if test.buildErr { - require.Error(t, err) - return - } - require.NoError(t, err) - - meta := ConnData{ - serverName: test.serverName, - } - - assert.Equal(t, test.matchErr, !matcherTree.match(meta)) - }) - } -} - -func Test_HostSNIRegexp(t *testing.T) { - testCases := []struct { - desc string - pattern string - serverNames map[string]bool - buildErr bool - }{ - { - desc: "unbalanced braces", - pattern: "subdomain:(foo\\.)?bar\\.com}", - buildErr: true, - }, - { - desc: "empty group name", - pattern: "{:(foo\\.)?bar\\.com}", - buildErr: true, - }, - { - desc: "empty capturing group", - pattern: "{subdomain:}", - buildErr: true, - }, - { - desc: "malformed capturing group", - pattern: "{subdomain:(foo\\.?bar\\.com}", - buildErr: true, - }, - { - desc: "not interpreted as a regexp", - pattern: "bar.com", - serverNames: map[string]bool{ - "bar.com": true, - "barucom": false, - }, - }, - { - desc: "capturing group", - pattern: "{subdomain:(foo\\.)?bar\\.com}", - serverNames: map[string]bool{ - "foo.bar.com": true, - "bar.com": true, - "fooubar.com": false, - "barucom": false, - "barcom": false, - }, - }, - { - desc: "non capturing group", - pattern: "{subdomain:(?:foo\\.)?bar\\.com}", - serverNames: map[string]bool{ - "foo.bar.com": true, - "bar.com": true, - "fooubar.com": false, - "barucom": false, - "barcom": false, - }, - }, - { - desc: "regex insensitive", - pattern: "{dummy:[A-Za-z-]+\\.bar\\.com}", - serverNames: map[string]bool{ - "FOO.bar.com": true, - "foo.bar.com": true, - "fooubar.com": false, - "barucom": false, - "barcom": false, - }, - }, - { - desc: "insensitive host", - pattern: "{dummy:[a-z-]+\\.bar\\.com}", - serverNames: map[string]bool{ - "FOO.bar.com": true, - "foo.bar.com": true, - "fooubar.com": false, - "barucom": false, - "barcom": false, - }, - }, - { - desc: "insensitive host simple", - pattern: "foo.bar.com", - serverNames: map[string]bool{ - "FOO.bar.com": true, - "foo.bar.com": true, - "fooubar.com": false, - "barucom": false, - "barcom": false, - }, - }, - } - - for _, test := range testCases { - test := test - - t.Run(test.desc, func(t *testing.T) { - t.Parallel() - - matchersTree := &matchersTree{} - err := hostSNIRegexp(matchersTree, test.pattern) - if test.buildErr { - require.Error(t, err) - return - } - require.NoError(t, err) - - for serverName, match := range test.serverNames { - meta := ConnData{ - serverName: serverName, - } - - assert.Equal(t, match, matchersTree.match(meta)) - } - }) - } -} - -func Test_ClientIP(t *testing.T) { - testCases := []struct { - desc string - ruleCIDRs []string - remoteIP string - buildErr bool - matchErr bool - }{ - { - desc: "Empty", - buildErr: true, - }, - { - desc: "Malformed CIDR", - ruleCIDRs: []string{"héhé"}, - buildErr: true, - }, - { - desc: "Not matching empty remote IP", - ruleCIDRs: []string{"20.20.20.20"}, - matchErr: true, - }, - { - desc: "Not matching IP", - ruleCIDRs: []string{"20.20.20.20"}, - remoteIP: "10.10.10.10", - matchErr: true, - }, - { - desc: "Matching IP", - ruleCIDRs: []string{"10.10.10.10"}, - remoteIP: "10.10.10.10", - }, - { - desc: "Not matching multiple IPs", - ruleCIDRs: []string{"20.20.20.20", "30.30.30.30"}, - remoteIP: "10.10.10.10", - matchErr: true, - }, - { - desc: "Matching multiple IPs", - ruleCIDRs: []string{"10.10.10.10", "20.20.20.20", "30.30.30.30"}, - remoteIP: "20.20.20.20", - }, - { - desc: "Not matching CIDR", - ruleCIDRs: []string{"20.0.0.0/24"}, - remoteIP: "10.10.10.10", - matchErr: true, - }, - { - desc: "Matching CIDR", - ruleCIDRs: []string{"20.0.0.0/8"}, - remoteIP: "20.10.10.10", - }, - { - desc: "Not matching multiple CIDRs", - ruleCIDRs: []string{"10.0.0.0/24", "20.0.0.0/24"}, - remoteIP: "10.10.10.10", - matchErr: true, - }, - { - desc: "Matching multiple CIDRs", - ruleCIDRs: []string{"10.0.0.0/8", "20.0.0.0/8"}, - remoteIP: "20.10.10.10", - }, - } - - for _, test := range testCases { - test := test - - t.Run(test.desc, func(t *testing.T) { - t.Parallel() - - matchersTree := &matchersTree{} - err := clientIP(matchersTree, test.ruleCIDRs...) - if test.buildErr { - require.Error(t, err) - return - } - require.NoError(t, err) - - meta := ConnData{ - remoteIP: test.remoteIP, - } - - assert.Equal(t, test.matchErr, !matchersTree.match(meta)) - }) - } -} - -func Test_ALPN(t *testing.T) { - testCases := []struct { - desc string - ruleALPNProtos []string - connProto string - buildErr bool - matchErr bool - }{ - { - desc: "Empty", - buildErr: true, - }, - { - desc: "ACME TLS proto", - ruleALPNProtos: []string{tlsalpn01.ACMETLS1Protocol}, - buildErr: true, - }, - { - desc: "Not matching empty proto", - ruleALPNProtos: []string{"h2"}, - matchErr: true, - }, - { - desc: "Not matching ALPN", - ruleALPNProtos: []string{"h2"}, - connProto: "mqtt", - matchErr: true, - }, - { - desc: "Matching ALPN", - ruleALPNProtos: []string{"h2"}, - connProto: "h2", - }, - { - desc: "Not matching multiple ALPNs", - ruleALPNProtos: []string{"h2", "mqtt"}, - connProto: "h2c", - matchErr: true, - }, - { - desc: "Matching multiple ALPNs", - ruleALPNProtos: []string{"h2", "h2c", "mqtt"}, - connProto: "h2c", - }, - } - - for _, test := range testCases { - test := test - - t.Run(test.desc, func(t *testing.T) { - t.Parallel() - - matchersTree := &matchersTree{} - err := alpn(matchersTree, test.ruleALPNProtos...) - if test.buildErr { - require.Error(t, err) - return - } - require.NoError(t, err) - - meta := ConnData{ - alpnProtos: []string{test.connProto}, - } - - assert.Equal(t, test.matchErr, !matchersTree.match(meta)) - }) - } -} - func Test_Priority(t *testing.T) { testCases := []struct { desc string rules map[string]int serverName string - remoteIP string expectedRule string }{ { desc: "One matching rule, calculated priority", rules: map[string]int{ - "HostSNI(`bar`)": 0, - "HostSNI(`foobar`)": 0, + "HostSNI(`example.com`)": 0, + "HostSNI(`example.org`)": 0, }, - expectedRule: "HostSNI(`bar`)", - serverName: "bar", + expectedRule: "HostSNI(`example.com`)", + serverName: "example.com", }, { desc: "One matching rule, custom priority", rules: map[string]int{ - "HostSNI(`foobar`)": 0, - "HostSNI(`bar`)": 10000, + "HostSNI(`example.org`)": 0, + "HostSNI(`example.com`)": 10000, }, - expectedRule: "HostSNI(`foobar`)", - serverName: "foobar", + expectedRule: "HostSNI(`example.org`)", + serverName: "example.org", }, { desc: "Two matching rules, calculated priority", rules: map[string]int{ - "HostSNI(`foobar`)": 0, - "HostSNI(`foobar`, `bar`)": 0, + "HostSNI(`example.org`)": 0, + "HostSNI(`example.com`)": 0, }, - expectedRule: "HostSNI(`foobar`, `bar`)", - serverName: "foobar", + expectedRule: "HostSNI(`example.org`)", + serverName: "example.org", }, { desc: "Two matching rules, custom priority", rules: map[string]int{ - "HostSNI(`foobar`)": 10000, - "HostSNI(`foobar`, `bar`)": 0, + "HostSNI(`example.com`)": 10000, + "HostSNI(`example.org`)": 0, }, - expectedRule: "HostSNI(`foobar`)", - serverName: "foobar", + expectedRule: "HostSNI(`example.com`)", + serverName: "example.com", }, } @@ -1116,7 +435,6 @@ func Test_Priority(t *testing.T) { handler, _ := muxer.Match(ConnData{ serverName: test.serverName, - remoteIP: test.remoteIP, }) require.NotNil(t, handler) @@ -1125,3 +443,57 @@ func Test_Priority(t *testing.T) { }) } } + +type fakeConn struct { + call map[string]int + remoteAddr net.Addr +} + +func (f *fakeConn) Read(b []byte) (n int, err error) { + panic("implement me") +} + +func (f *fakeConn) Write(b []byte) (n int, err error) { + f.call[string(b)]++ + return len(b), nil +} + +func (f *fakeConn) Close() error { + panic("implement me") +} + +func (f *fakeConn) LocalAddr() net.Addr { + panic("implement me") +} + +func (f *fakeConn) RemoteAddr() net.Addr { + return f.remoteAddr +} + +func (f *fakeConn) SetDeadline(t time.Time) error { + panic("implement me") +} + +func (f *fakeConn) SetReadDeadline(t time.Time) error { + panic("implement me") +} + +func (f *fakeConn) SetWriteDeadline(t time.Time) error { + panic("implement me") +} + +func (f *fakeConn) CloseWrite() error { + panic("implement me") +} + +type fakeAddr struct { + addr string +} + +func (f fakeAddr) String() string { + return f.addr +} + +func (f fakeAddr) Network() string { + panic("Implement me") +} diff --git a/pkg/provider/hub/hub.go b/pkg/provider/hub/hub.go index 07fc50d30..c44cb7e97 100644 --- a/pkg/provider/hub/hub.go +++ b/pkg/provider/hub/hub.go @@ -101,7 +101,7 @@ func patchDynamicConfiguration(cfg *dynamic.Configuration, ep string, port int, cfg.HTTP.Routers["traefik-hub-agent-service"] = &dynamic.Router{ EntryPoints: []string{ep}, Service: "traefik-hub-agent-service", - Rule: "Host(`proxy.traefik`) && PathPrefix(`/config`, `/discover-ip`, `/state`)", + Rule: "Host(`proxy.traefik`) && (PathPrefix(`/config`) || PathPrefix(`/discover-ip`) || PathPrefix(`/state`))", } cfg.HTTP.Services["traefik-hub-agent-service"] = &dynamic.Service{ diff --git a/pkg/provider/kubernetes/gateway/kubernetes.go b/pkg/provider/kubernetes/gateway/kubernetes.go index 7bc3dc754..f06bdac9b 100644 --- a/pkg/provider/kubernetes/gateway/kubernetes.go +++ b/pkg/provider/kubernetes/gateway/kubernetes.go @@ -7,6 +7,7 @@ import ( "fmt" "net" "os" + "regexp" "sort" "strconv" "strings" @@ -1163,8 +1164,7 @@ func getRouteBindingSelectorNamespace(client Client, gatewayNamespace string, ro } func hostRule(hostnames []v1alpha2.Hostname) (string, error) { - var hostNames []string - var hostRegexNames []string + var rules []string for _, hostname := range hostnames { host := string(hostname) @@ -1177,7 +1177,7 @@ func hostRule(hostnames []v1alpha2.Hostname) (string, error) { wildcard := strings.Count(host, "*") if wildcard == 0 { - hostNames = append(hostNames, host) + rules = append(rules, fmt.Sprintf("Host(`%s`)", host)) continue } @@ -1186,25 +1186,18 @@ func hostRule(hostnames []v1alpha2.Hostname) (string, error) { return "", fmt.Errorf("invalid rule: %q", host) } - hostRegexNames = append(hostRegexNames, strings.Replace(host, "*.", "{subdomain:[a-zA-Z0-9-]+}.", 1)) + host = strings.Replace(regexp.QuoteMeta(host), `\*\.`, `[a-zA-Z0-9-]+\.`, 1) + rules = append(rules, fmt.Sprintf("HostRegexp(`^%s$`)", host)) } - var res string - if len(hostNames) > 0 { - res = "Host(`" + strings.Join(hostNames, "`, `") + "`)" + switch len(rules) { + case 0: + return "", nil + case 1: + return rules[0], nil + default: + return fmt.Sprintf("(%s)", strings.Join(rules, " || ")), nil } - - if len(hostRegexNames) == 0 { - return res, nil - } - - hostRegexp := "HostRegexp(`" + strings.Join(hostRegexNames, "`, `") + "`)" - - if len(res) > 0 { - return "(" + res + " || " + hostRegexp + ")", nil - } - - return hostRegexp, nil } func hostSNIRule(hostnames []v1alpha2.Hostname) (string, error) { @@ -1227,15 +1220,18 @@ func hostSNIRule(hostnames []v1alpha2.Hostname) (string, error) { return "", fmt.Errorf("wildcard hostname is not supported: %q", h) } - matchers = append(matchers, "`"+h+"`") + matchers = append(matchers, fmt.Sprintf("HostSNI(`%s`)", h)) uniqHostnames[hostname] = struct{}{} } - if len(matchers) == 0 { + switch len(matchers) { + case 0: return "HostSNI(`*`)", nil + case 1: + return matchers[0], nil + default: + return fmt.Sprintf("(%s)", strings.Join(matchers, " || ")), nil } - - return "HostSNI(" + strings.Join(matchers, ",") + ")", nil } func extractRule(routeRule v1alpha2.HTTPRouteRule, hostRule string) (string, error) { diff --git a/pkg/provider/kubernetes/gateway/kubernetes_test.go b/pkg/provider/kubernetes/gateway/kubernetes_test.go index 803701476..44b3ce249 100644 --- a/pkg/provider/kubernetes/gateway/kubernetes_test.go +++ b/pkg/provider/kubernetes/gateway/kubernetes_test.go @@ -744,15 +744,15 @@ func TestLoadHTTPRoutes(t *testing.T) { }, HTTP: &dynamic.HTTPConfiguration{ Routers: map[string]*dynamic.Router{ - "default-http-app-1-my-gateway-web-75dd1ad561e42725558a": { + "default-http-app-1-my-gateway-web-66e726cd8903b49727ae": { EntryPoints: []string{"web"}, - Service: "default-http-app-1-my-gateway-web-75dd1ad561e42725558a-wrr", - Rule: "Host(`foo.com`, `bar.com`) && PathPrefix(`/`)", + Service: "default-http-app-1-my-gateway-web-66e726cd8903b49727ae-wrr", + Rule: "(Host(`foo.com`) || Host(`bar.com`)) && PathPrefix(`/`)", }, }, Middlewares: map[string]*dynamic.Middleware{}, Services: map[string]*dynamic.Service{ - "default-http-app-1-my-gateway-web-75dd1ad561e42725558a-wrr": { + "default-http-app-1-my-gateway-web-66e726cd8903b49727ae-wrr": { Weighted: &dynamic.WeightedRoundRobin{ Services: []dynamic.WRRService{ { @@ -802,15 +802,15 @@ func TestLoadHTTPRoutes(t *testing.T) { }, HTTP: &dynamic.HTTPConfiguration{ Routers: map[string]*dynamic.Router{ - "default-http-app-1-my-gateway-web-2dbd7883f5537db39bca": { + "default-http-app-1-my-gateway-web-3b78e2feb3295ddd87f0": { EntryPoints: []string{"web"}, - Service: "default-http-app-1-my-gateway-web-2dbd7883f5537db39bca-wrr", - Rule: "(Host(`foo.com`) || HostRegexp(`{subdomain:[a-zA-Z0-9-]+}.bar.com`)) && PathPrefix(`/`)", + Service: "default-http-app-1-my-gateway-web-3b78e2feb3295ddd87f0-wrr", + Rule: "(Host(`foo.com`) || HostRegexp(`^[a-zA-Z0-9-]+\\.bar\\.com$`)) && PathPrefix(`/`)", }, }, Middlewares: map[string]*dynamic.Middleware{}, Services: map[string]*dynamic.Service{ - "default-http-app-1-my-gateway-web-2dbd7883f5537db39bca-wrr": { + "default-http-app-1-my-gateway-web-3b78e2feb3295ddd87f0-wrr": { Weighted: &dynamic.WeightedRoundRobin{ Services: []dynamic.WRRService{ { @@ -860,15 +860,15 @@ func TestLoadHTTPRoutes(t *testing.T) { }, HTTP: &dynamic.HTTPConfiguration{ Routers: map[string]*dynamic.Router{ - "default-http-app-1-my-gateway-web-eb1490f180299bf5ed29": { + "default-http-app-1-my-gateway-web-b0521a61fb43068694b4": { EntryPoints: []string{"web"}, - Service: "default-http-app-1-my-gateway-web-eb1490f180299bf5ed29-wrr", - Rule: "(Host(`foo.com`) || HostRegexp(`{subdomain:[a-zA-Z0-9-]+}.foo.com`)) && PathPrefix(`/`)", + Service: "default-http-app-1-my-gateway-web-b0521a61fb43068694b4-wrr", + Rule: "(Host(`foo.com`) || HostRegexp(`^[a-zA-Z0-9-]+\\.foo\\.com$`)) && PathPrefix(`/`)", }, }, Middlewares: map[string]*dynamic.Middleware{}, Services: map[string]*dynamic.Service{ - "default-http-app-1-my-gateway-web-eb1490f180299bf5ed29-wrr": { + "default-http-app-1-my-gateway-web-b0521a61fb43068694b4-wrr": { Weighted: &dynamic.WeightedRoundRobin{ Services: []dynamic.WRRService{ { @@ -3011,10 +3011,10 @@ func TestLoadTLSRoutes(t *testing.T) { }, TCP: &dynamic.TCPConfiguration{ Routers: map[string]*dynamic.TCPRouter{ - "default-tls-app-1-my-gateway-tls-339184c3296a9c2c39fa": { + "default-tls-app-1-my-gateway-tls-dfc5c7506ac1b172c8b7": { EntryPoints: []string{"tls"}, - Service: "default-tls-app-1-my-gateway-tls-339184c3296a9c2c39fa-wrr-0", - Rule: "HostSNI(`foo.example.com`,`bar.example.com`)", + Service: "default-tls-app-1-my-gateway-tls-dfc5c7506ac1b172c8b7-wrr-0", + Rule: "(HostSNI(`foo.example.com`) || HostSNI(`bar.example.com`))", TLS: &dynamic.RouterTCPTLSConfig{ Passthrough: true, }, @@ -3022,7 +3022,7 @@ func TestLoadTLSRoutes(t *testing.T) { }, Middlewares: map[string]*dynamic.TCPMiddleware{}, Services: map[string]*dynamic.TCPService{ - "default-tls-app-1-my-gateway-tls-339184c3296a9c2c39fa-wrr-0": { + "default-tls-app-1-my-gateway-tls-dfc5c7506ac1b172c8b7-wrr-0": { Weighted: &dynamic.TCPWeightedRoundRobin{ Services: []dynamic.TCPWRRService{ { @@ -4362,7 +4362,7 @@ func Test_hostRule(t *testing.T) { "Bar", "Bir", }, - expectedRule: "Host(`Foo`, `Bar`, `Bir`)", + expectedRule: "(Host(`Foo`) || Host(`Bar`) || Host(`Bir`))", }, { desc: "Multiple Hosts with empty one", @@ -4389,14 +4389,14 @@ func Test_hostRule(t *testing.T) { "bar.foo", "foo.foo", }, - expectedRule: "(Host(`bar.foo`, `foo.foo`) || HostRegexp(`{subdomain:[a-zA-Z0-9-]+}.bar.foo`))", + expectedRule: "(HostRegexp(`^[a-zA-Z0-9-]+\\.bar\\.foo$`) || Host(`bar.foo`) || Host(`foo.foo`))", }, { desc: "Host with wildcard", hostnames: []v1alpha2.Hostname{ "*.bar.foo", }, - expectedRule: "HostRegexp(`{subdomain:[a-zA-Z0-9-]+}.bar.foo`)", + expectedRule: "HostRegexp(`^[a-zA-Z0-9-]+\\.bar\\.foo$`)", }, { desc: "Alone wildcard", @@ -4708,7 +4708,7 @@ func Test_hostSNIRule(t *testing.T) { { desc: "Some empty hostnames", hostnames: []v1alpha2.Hostname{"foo", "", "bar"}, - expectedRule: "HostSNI(`foo`,`bar`)", + expectedRule: "(HostSNI(`foo`) || HostSNI(`bar`))", }, { desc: "Valid hostname", @@ -4718,12 +4718,12 @@ func Test_hostSNIRule(t *testing.T) { { desc: "Multiple valid hostnames", hostnames: []v1alpha2.Hostname{"foo", "bar"}, - expectedRule: "HostSNI(`foo`,`bar`)", + expectedRule: "(HostSNI(`foo`) || HostSNI(`bar`))", }, { desc: "Multiple overlapping hostnames", hostnames: []v1alpha2.Hostname{"foo", "bar", "foo", "baz"}, - expectedRule: "HostSNI(`foo`,`bar`,`baz`)", + expectedRule: "(HostSNI(`foo`) || HostSNI(`bar`) || HostSNI(`baz`))", }, } diff --git a/pkg/provider/kubernetes/ingress/kubernetes.go b/pkg/provider/kubernetes/ingress/kubernetes.go index 502c7cbe9..206f37d50 100644 --- a/pkg/provider/kubernetes/ingress/kubernetes.go +++ b/pkg/provider/kubernetes/ingress/kubernetes.go @@ -8,6 +8,7 @@ import ( "math" "net" "os" + "regexp" "sort" "strconv" "strings" @@ -400,10 +401,11 @@ func (p *Provider) shouldProcessIngress(ingress *networkingv1.Ingress, ingressCl func buildHostRule(host string) string { if strings.HasPrefix(host, "*.") { - return "HostRegexp(`" + strings.Replace(host, "*.", "{subdomain:[a-zA-Z0-9-]+}.", 1) + "`)" + host = strings.Replace(regexp.QuoteMeta(host), `\*\.`, `[a-zA-Z0-9-]+\.`, 1) + return fmt.Sprintf("HostRegexp(`^%s$`)", host) } - return "Host(`" + host + "`)" + return fmt.Sprintf("Host(`%s`)", host) } func getCertificates(ctx context.Context, ingress *networkingv1.Ingress, k8sClient Client, tlsConfigs map[string]*tls.CertAndStores) error { diff --git a/pkg/provider/kubernetes/ingress/kubernetes_test.go b/pkg/provider/kubernetes/ingress/kubernetes_test.go index d1b76ec8a..3b032f996 100644 --- a/pkg/provider/kubernetes/ingress/kubernetes_test.go +++ b/pkg/provider/kubernetes/ingress/kubernetes_test.go @@ -189,8 +189,8 @@ func TestLoadConfigurationFromIngresses(t *testing.T) { HTTP: &dynamic.HTTPConfiguration{ Middlewares: map[string]*dynamic.Middleware{}, Routers: map[string]*dynamic.Router{ - "testing-bar-bar-3be6cfd7daba66cf2fdd": { - Rule: "HostRegexp(`{subdomain:[a-zA-Z0-9-]+}.bar`) && PathPrefix(`/bar`)", + "testing-bar-bar-aba9a7d00e9b06a78e16": { + Rule: "HostRegexp(`^[a-zA-Z0-9-]+\\.bar$`) && PathPrefix(`/bar`)", Service: "testing-service1-80", }, "testing-bar-bar-636bf36c00fedaab3d44": { @@ -1104,7 +1104,7 @@ func TestLoadConfigurationFromIngresses(t *testing.T) { Middlewares: map[string]*dynamic.Middleware{}, Routers: map[string]*dynamic.Router{ "testing-foobar-com-bar": { - Rule: "HostRegexp(`{subdomain:[a-zA-Z0-9-]+}.foobar.com`) && PathPrefix(`/bar`)", + Rule: "HostRegexp(`^[a-zA-Z0-9-]+\\.foobar\\.com$`) && PathPrefix(`/bar`)", Service: "testing-service1-80", }, }, diff --git a/pkg/provider/traefik/fixtures/redirection.json b/pkg/provider/traefik/fixtures/redirection.json index 4ffbb756c..2b3b271fa 100644 --- a/pkg/provider/traefik/fixtures/redirection.json +++ b/pkg/provider/traefik/fixtures/redirection.json @@ -9,7 +9,7 @@ "redirect-web-to-websecure" ], "service": "noop@internal", - "rule": "HostRegexp(`{host:.+}`)" + "rule": "HostRegexp(`^.+$`)" } }, "middlewares": { @@ -27,4 +27,4 @@ }, "tcp": {}, "tls": {} -} \ No newline at end of file +} diff --git a/pkg/provider/traefik/fixtures/redirection_port.json b/pkg/provider/traefik/fixtures/redirection_port.json index e113d21b3..ead9bc0b1 100644 --- a/pkg/provider/traefik/fixtures/redirection_port.json +++ b/pkg/provider/traefik/fixtures/redirection_port.json @@ -9,7 +9,7 @@ "redirect-web-to-443" ], "service": "noop@internal", - "rule": "HostRegexp(`{host:.+}`)" + "rule": "HostRegexp(`^.+$`)" } }, "middlewares": { @@ -27,4 +27,4 @@ }, "tcp": {}, "tls": {} -} \ No newline at end of file +} diff --git a/pkg/provider/traefik/fixtures/redirection_with_protocol.json b/pkg/provider/traefik/fixtures/redirection_with_protocol.json index 4ffbb756c..2b3b271fa 100644 --- a/pkg/provider/traefik/fixtures/redirection_with_protocol.json +++ b/pkg/provider/traefik/fixtures/redirection_with_protocol.json @@ -9,7 +9,7 @@ "redirect-web-to-websecure" ], "service": "noop@internal", - "rule": "HostRegexp(`{host:.+}`)" + "rule": "HostRegexp(`^.+$`)" } }, "middlewares": { @@ -27,4 +27,4 @@ }, "tcp": {}, "tls": {} -} \ No newline at end of file +} diff --git a/pkg/provider/traefik/internal.go b/pkg/provider/traefik/internal.go index 175ce07b9..1e1c23bec 100644 --- a/pkg/provider/traefik/internal.go +++ b/pkg/provider/traefik/internal.go @@ -137,7 +137,7 @@ func (i *Provider) redirection(ctx context.Context, cfg *dynamic.Configuration) mdName := "redirect-" + rtName rt := &dynamic.Router{ - Rule: "HostRegexp(`{host:.+}`)", + Rule: "HostRegexp(`^.+$`)", EntryPoints: []string{name}, Middlewares: []string{mdName}, Service: "noop@internal", From db287c4d316d81c71075f66c8ea28a5df0bee268 Mon Sep 17 00:00:00 2001 From: Simon Delicata Date: Tue, 29 Nov 2022 11:48:05 +0100 Subject: [PATCH 04/19] Disable Content-Type auto-detection by default --- docs/content/middlewares/http/contenttype.md | 57 ++++--------- docs/content/migration/v2-to-v3.md | 5 ++ .../dynamic-configuration/docker-labels.yml | 2 +- .../reference/dynamic-configuration/file.toml | 1 - .../reference/dynamic-configuration/file.yaml | 3 +- .../kubernetes-crd-definition-v1.yml | 16 +--- .../reference/dynamic-configuration/kv-ref.md | 2 +- .../marathon-labels.json | 2 +- .../traefik.containo.us_middlewares.yaml | 16 +--- integration/fixtures/k8s/01-traefik-crd.yml | 16 +--- integration/fixtures/simple_contenttype.toml | 28 +------ integration/simple_test.go | 51 +++++------- pkg/config/dynamic/middlewares.go | 14 +--- pkg/middlewares/contenttype/content_type.go | 46 +++++++++++ .../contenttype/content_type_test.go | 79 +++++++++++++++++++ pkg/redactor/redactor_config_test.go | 4 +- .../testdata/anonymized-dynamic-config.json | 4 +- .../testdata/secured-dynamic-config.json | 4 +- pkg/server/middleware/middlewares.go | 8 +- pkg/server/server_entrypoint_tcp.go | 3 + 20 files changed, 193 insertions(+), 168 deletions(-) create mode 100644 pkg/middlewares/contenttype/content_type.go create mode 100644 pkg/middlewares/contenttype/content_type_test.go diff --git a/docs/content/middlewares/http/contenttype.md b/docs/content/middlewares/http/contenttype.md index 252a3fda6..38072958a 100644 --- a/docs/content/middlewares/http/contenttype.md +++ b/docs/content/middlewares/http/contenttype.md @@ -1,6 +1,6 @@ --- title: "Traefik ContentType Documentation" -description: "Traefik Proxy's HTTP middleware can automatically specify the content-type header if it has not been defined by the backend. Read the technical documentation." +description: "Traefik Proxy's HTTP middleware automatically sets the `Content-Type` header value when it is not set by the backend. Read the technical documentation." --- # ContentType @@ -8,84 +8,59 @@ description: "Traefik Proxy's HTTP middleware can automatically specify the cont Handling Content-Type auto-detection {: .subtitle } -The Content-Type middleware - or rather its `autoDetect` option - -specifies whether to let the `Content-Type` header, -if it has not been defined by the backend, -be automatically set to a value derived from the contents of the response. - -As a proxy, the default behavior should be to leave the header alone, -regardless of what the backend did with it. -However, the historic default was to always auto-detect and set the header if it was not already defined, -and altering this behavior would be a breaking change which would impact many users. - -This middleware exists to enable the correct behavior until at least the default one can be changed in a future version. +The Content-Type middleware sets the `Content-Type` header value to the media type detected from the response content, +when it is not set by the backend. !!! info - As explained above, for compatibility reasons the default behavior on a router (without this middleware), - is still to automatically set the `Content-Type` header. - Therefore, given the default value of the `autoDetect` option (false), - simply enabling this middleware for a router switches the router's behavior. - The scope of the Content-Type middleware is the MIME type detection done by the core of Traefik (the server part). Therefore, it has no effect against any other `Content-Type` header modifications (e.g.: in another middleware such as compress). ## Configuration Examples ```yaml tab="Docker" -# Disable auto-detection +# Enable auto-detection labels: - - "traefik.http.middlewares.autodetect.contenttype.autodetect=false" + - "traefik.http.middlewares.autodetect.contenttype=true" ``` ```yaml tab="Kubernetes" -# Disable auto-detection +# Enable auto-detection apiVersion: traefik.containo.us/v1alpha1 kind: Middleware metadata: name: autodetect spec: - contentType: - autoDetect: false + contentType: {} ``` ```yaml tab="Consul Catalog" -# Disable auto-detection -- "traefik.http.middlewares.autodetect.contenttype.autodetect=false" +# Enable auto-detection +- "traefik.http.middlewares.autodetect.contenttype=true" ``` ```json tab="Marathon" "labels": { - "traefik.http.middlewares.autodetect.contenttype.autodetect": "false" + "traefik.http.middlewares.autodetect.contenttype": "true" } ``` ```yaml tab="Rancher" -# Disable auto-detection +# Enable auto-detection labels: - - "traefik.http.middlewares.autodetect.contenttype.autodetect=false" + - "traefik.http.middlewares.autodetect.contenttype=true" ``` ```yaml tab="File (YAML)" -# Disable auto-detection +# Enable auto-detection http: middlewares: autodetect: - contentType: - autoDetect: false + contentType: {} ``` ```toml tab="File (TOML)" -# Disable auto-detection +# Enable auto-detection [http.middlewares] [http.middlewares.autodetect.contentType] - autoDetect=false -``` - -## Configuration Options - -### `autoDetect` - -`autoDetect` specifies whether to let the `Content-Type` header, -if it has not been set by the backend, -be automatically set to a value derived from the contents of the response. +``` \ No newline at end of file diff --git a/docs/content/migration/v2-to-v3.md b/docs/content/migration/v2-to-v3.md index 4a8a9bf24..a574bf5e7 100644 --- a/docs/content/migration/v2-to-v3.md +++ b/docs/content/migration/v2-to-v3.md @@ -45,3 +45,8 @@ and should be explicitly combined using logical operators to mimic previous beha `Query` can take a single value to match is the query value that has no value (e.g. `/search?mobile`). `HostHeader` has been removed, use `Host` instead. + +## Content-Type Auto-Detection + +In v3, the `Content-Type` header is not auto-detected anymore when it is not set by the backend. +One should use the `ContentType` middleware to enable the `Content-Type` header value auto-detection. diff --git a/docs/content/reference/dynamic-configuration/docker-labels.yml b/docs/content/reference/dynamic-configuration/docker-labels.yml index 37c7917b2..e58a1d46a 100644 --- a/docs/content/reference/dynamic-configuration/docker-labels.yml +++ b/docs/content/reference/dynamic-configuration/docker-labels.yml @@ -17,7 +17,7 @@ - "traefik.http.middlewares.middleware05.compress=true" - "traefik.http.middlewares.middleware05.compress.excludedcontenttypes=foobar, foobar" - "traefik.http.middlewares.middleware05.compress.minresponsebodybytes=42" -- "traefik.http.middlewares.middleware06.contenttype.autodetect=true" +- "traefik.http.middlewares.middleware06.contenttype=true" - "traefik.http.middlewares.middleware07.digestauth.headerfield=foobar" - "traefik.http.middlewares.middleware07.digestauth.realm=foobar" - "traefik.http.middlewares.middleware07.digestauth.removeheader=true" diff --git a/docs/content/reference/dynamic-configuration/file.toml b/docs/content/reference/dynamic-configuration/file.toml index 76d27806f..41994814d 100644 --- a/docs/content/reference/dynamic-configuration/file.toml +++ b/docs/content/reference/dynamic-configuration/file.toml @@ -137,7 +137,6 @@ minResponseBodyBytes = 42 [http.middlewares.Middleware06] [http.middlewares.Middleware06.contentType] - autoDetect = true [http.middlewares.Middleware07] [http.middlewares.Middleware07.digestAuth] users = ["foobar", "foobar"] diff --git a/docs/content/reference/dynamic-configuration/file.yaml b/docs/content/reference/dynamic-configuration/file.yaml index fd04e88d5..bfecd9fd2 100644 --- a/docs/content/reference/dynamic-configuration/file.yaml +++ b/docs/content/reference/dynamic-configuration/file.yaml @@ -141,8 +141,7 @@ http: - foobar minResponseBodyBytes: 42 Middleware06: - contentType: - autoDetect: true + contentType: {} Middleware07: digestAuth: users: diff --git a/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml b/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml index 0681cb3d3..0a8f01823 100644 --- a/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml +++ b/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml @@ -762,19 +762,9 @@ spec: type: object contentType: description: ContentType holds the content-type middleware configuration. - This middleware exists to enable the correct behavior until at least - the default one can be changed in a future version. - properties: - autoDetect: - description: AutoDetect specifies whether to let the `Content-Type` - header, if it has not been set by the backend, be automatically - set to a value derived from the contents of the response. As - a proxy, the default behavior should be to leave the header - alone, regardless of what the backend did with it. However, - the historic default was to always auto-detect and set the header - if it was nil, and it is going to be kept that way in order - to support users currently relying on it. - type: boolean + This middleware sets the `Content-Type` header value to the media + type detected from the response content, when it is not set by the + backend. type: object digestAuth: description: 'DigestAuth holds the digest auth middleware configuration. diff --git a/docs/content/reference/dynamic-configuration/kv-ref.md b/docs/content/reference/dynamic-configuration/kv-ref.md index 2a1310905..ca40ac8a4 100644 --- a/docs/content/reference/dynamic-configuration/kv-ref.md +++ b/docs/content/reference/dynamic-configuration/kv-ref.md @@ -19,7 +19,7 @@ | `traefik/http/middlewares/Middleware05/compress/excludedContentTypes/0` | `foobar` | | `traefik/http/middlewares/Middleware05/compress/excludedContentTypes/1` | `foobar` | | `traefik/http/middlewares/Middleware05/compress/minResponseBodyBytes` | `42` | -| `traefik/http/middlewares/Middleware06/contentType/autoDetect` | `true` | +| `traefik/http/middlewares/Middleware06/contentType` | `` | | `traefik/http/middlewares/Middleware07/digestAuth/headerField` | `foobar` | | `traefik/http/middlewares/Middleware07/digestAuth/realm` | `foobar` | | `traefik/http/middlewares/Middleware07/digestAuth/removeHeader` | `true` | diff --git a/docs/content/reference/dynamic-configuration/marathon-labels.json b/docs/content/reference/dynamic-configuration/marathon-labels.json index 01c2af0fc..99cfaefb9 100644 --- a/docs/content/reference/dynamic-configuration/marathon-labels.json +++ b/docs/content/reference/dynamic-configuration/marathon-labels.json @@ -17,7 +17,7 @@ "traefik.http.middlewares.middleware05.compress": "true", "traefik.http.middlewares.middleware05.compress.excludedcontenttypes": "foobar, foobar", "traefik.http.middlewares.middleware05.compress.minresponsebodybytes": "42", -"traefik.http.middlewares.middleware06.contenttype.autodetect": "true", +"traefik.http.middlewares.middleware06.contenttype": "true", "traefik.http.middlewares.middleware07.digestauth.headerfield": "foobar", "traefik.http.middlewares.middleware07.digestauth.realm": "foobar", "traefik.http.middlewares.middleware07.digestauth.removeheader": "true", diff --git a/docs/content/reference/dynamic-configuration/traefik.containo.us_middlewares.yaml b/docs/content/reference/dynamic-configuration/traefik.containo.us_middlewares.yaml index f332eb5f6..6b9a4cac9 100644 --- a/docs/content/reference/dynamic-configuration/traefik.containo.us_middlewares.yaml +++ b/docs/content/reference/dynamic-configuration/traefik.containo.us_middlewares.yaml @@ -185,19 +185,9 @@ spec: type: object contentType: description: ContentType holds the content-type middleware configuration. - This middleware exists to enable the correct behavior until at least - the default one can be changed in a future version. - properties: - autoDetect: - description: AutoDetect specifies whether to let the `Content-Type` - header, if it has not been set by the backend, be automatically - set to a value derived from the contents of the response. As - a proxy, the default behavior should be to leave the header - alone, regardless of what the backend did with it. However, - the historic default was to always auto-detect and set the header - if it was nil, and it is going to be kept that way in order - to support users currently relying on it. - type: boolean + This middleware sets the `Content-Type` header value to the media + type detected from the response content, when it is not set by the + backend. type: object digestAuth: description: 'DigestAuth holds the digest auth middleware configuration. diff --git a/integration/fixtures/k8s/01-traefik-crd.yml b/integration/fixtures/k8s/01-traefik-crd.yml index 0681cb3d3..0a8f01823 100644 --- a/integration/fixtures/k8s/01-traefik-crd.yml +++ b/integration/fixtures/k8s/01-traefik-crd.yml @@ -762,19 +762,9 @@ spec: type: object contentType: description: ContentType holds the content-type middleware configuration. - This middleware exists to enable the correct behavior until at least - the default one can be changed in a future version. - properties: - autoDetect: - description: AutoDetect specifies whether to let the `Content-Type` - header, if it has not been set by the backend, be automatically - set to a value derived from the contents of the response. As - a proxy, the default behavior should be to leave the header - alone, regardless of what the backend did with it. However, - the historic default was to always auto-detect and set the header - if it was nil, and it is going to be kept that way in order - to support users currently relying on it. - type: boolean + This middleware sets the `Content-Type` header value to the media + type detected from the response content, when it is not set by the + backend. type: object digestAuth: description: 'DigestAuth holds the digest auth middleware configuration. diff --git a/integration/fixtures/simple_contenttype.toml b/integration/fixtures/simple_contenttype.toml index 2743cd659..0c68e3788 100644 --- a/integration/fixtures/simple_contenttype.toml +++ b/integration/fixtures/simple_contenttype.toml @@ -21,32 +21,12 @@ [http.routers] [http.routers.router1] service = "service1" - rule = "PathPrefix(`/css/ct/nomiddleware`) || PathPrefix(`/pdf/ct/nomiddleware`)" + rule = "PathPrefix(`/`)" [http.routers.router2] service = "service1" middlewares = ["autodetect"] - rule = "PathPrefix(`/css/ct/middlewareauto`) || PathPrefix(`/pdf/ct/middlewareauto`)" - - [http.routers.router3] - service = "service1" - middlewares = ["noautodetect"] - rule = "PathPrefix(`/css/ct/middlewarenoauto`) || PathPrefix(`/pdf/ct/middlewarenoauto`)" - - [http.routers.router4] - service = "service1" - rule = "PathPrefix(`/css/noct/nomiddleware`) || PathPrefix(`/pdf/noct/nomiddleware`)" - - [http.routers.router5] - service = "service1" - middlewares = ["autodetect"] - rule = "PathPrefix(`/css/noct/middlewareauto`) || PathPrefix(`/pdf/noct/middlewareauto`)" - - [http.routers.router6] - service = "service1" - middlewares = ["noautodetect"] - rule = "PathPrefix(`/css/noct/middlewarenoauto`) || PathPrefix(`/pdf/noct/middlewarenoauto`)" - + rule = "PathPrefix(`/autodetect`)" [http.services] [http.services.service1] @@ -56,7 +36,3 @@ url = "{{ .Server }}" [http.middlewares.autodetect.contentType] -autoDetect=true - -[http.middlewares.noautodetect.contentType] -autoDetect=false diff --git a/integration/simple_test.go b/integration/simple_test.go index 8738699a1..22327142f 100644 --- a/integration/simple_test.go +++ b/integration/simple_test.go @@ -1166,9 +1166,10 @@ func (s *SimpleSuite) TestSecureAPI(c *check.C) { func (s *SimpleSuite) TestContentTypeDisableAutoDetect(c *check.C) { srv1 := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { rw.Header()["Content-Type"] = nil - switch req.URL.Path[:4] { + path := strings.TrimPrefix(req.URL.Path, "/autodetect") + switch path[:4] { case "/css": - if !strings.Contains(req.URL.Path, "noct") { + if strings.Contains(req.URL.Path, "/ct") { rw.Header().Set("Content-Type", "text/css") } @@ -1177,7 +1178,7 @@ func (s *SimpleSuite) TestContentTypeDisableAutoDetect(c *check.C) { _, err := rw.Write([]byte(".testcss { }")) c.Assert(err, checker.IsNil) case "/pdf": - if !strings.Contains(req.URL.Path, "noct") { + if strings.Contains(req.URL.Path, "/ct") { rw.Header().Set("Content-Type", "application/pdf") } @@ -1211,37 +1212,13 @@ func (s *SimpleSuite) TestContentTypeDisableAutoDetect(c *check.C) { err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 10*time.Second, try.BodyContains("127.0.0.1")) c.Assert(err, checker.IsNil) - err = try.GetRequest("http://127.0.0.1:8000/css/ct/nomiddleware", time.Second, try.HasHeaderValue("Content-Type", "text/css", false)) + err = try.GetRequest("http://127.0.0.1:8000/css/ct", time.Second, try.HasHeaderValue("Content-Type", "text/css", false)) c.Assert(err, checker.IsNil) - err = try.GetRequest("http://127.0.0.1:8000/pdf/ct/nomiddleware", time.Second, try.HasHeaderValue("Content-Type", "application/pdf", false)) + err = try.GetRequest("http://127.0.0.1:8000/pdf/ct", time.Second, try.HasHeaderValue("Content-Type", "application/pdf", false)) c.Assert(err, checker.IsNil) - err = try.GetRequest("http://127.0.0.1:8000/css/ct/middlewareauto", time.Second, try.HasHeaderValue("Content-Type", "text/css", false)) - c.Assert(err, checker.IsNil) - - err = try.GetRequest("http://127.0.0.1:8000/pdf/ct/nomiddlewareauto", time.Second, try.HasHeaderValue("Content-Type", "application/pdf", false)) - c.Assert(err, checker.IsNil) - - err = try.GetRequest("http://127.0.0.1:8000/css/ct/middlewarenoauto", time.Second, try.HasHeaderValue("Content-Type", "text/css", false)) - c.Assert(err, checker.IsNil) - - err = try.GetRequest("http://127.0.0.1:8000/pdf/ct/nomiddlewarenoauto", time.Second, try.HasHeaderValue("Content-Type", "application/pdf", false)) - c.Assert(err, checker.IsNil) - - err = try.GetRequest("http://127.0.0.1:8000/css/noct/nomiddleware", time.Second, try.HasHeaderValue("Content-Type", "text/plain; charset=utf-8", false)) - c.Assert(err, checker.IsNil) - - err = try.GetRequest("http://127.0.0.1:8000/pdf/noct/nomiddleware", time.Second, try.HasHeaderValue("Content-Type", "application/pdf", false)) - c.Assert(err, checker.IsNil) - - err = try.GetRequest("http://127.0.0.1:8000/css/noct/middlewareauto", time.Second, try.HasHeaderValue("Content-Type", "text/plain; charset=utf-8", false)) - c.Assert(err, checker.IsNil) - - err = try.GetRequest("http://127.0.0.1:8000/pdf/noct/nomiddlewareauto", time.Second, try.HasHeaderValue("Content-Type", "application/pdf", false)) - c.Assert(err, checker.IsNil) - - err = try.GetRequest("http://127.0.0.1:8000/css/noct/middlewarenoauto", time.Second, func(res *http.Response) error { + err = try.GetRequest("http://127.0.0.1:8000/css/noct", time.Second, func(res *http.Response) error { if ct, ok := res.Header["Content-Type"]; ok { return fmt.Errorf("should have no content type and %s is present", ct) } @@ -1249,13 +1226,25 @@ func (s *SimpleSuite) TestContentTypeDisableAutoDetect(c *check.C) { }) c.Assert(err, checker.IsNil) - err = try.GetRequest("http://127.0.0.1:8000/pdf/noct/middlewarenoauto", time.Second, func(res *http.Response) error { + err = try.GetRequest("http://127.0.0.1:8000/pdf/noct", time.Second, func(res *http.Response) error { if ct, ok := res.Header["Content-Type"]; ok { return fmt.Errorf("should have no content type and %s is present", ct) } return nil }) c.Assert(err, checker.IsNil) + + err = try.GetRequest("http://127.0.0.1:8000/autodetect/css/ct", time.Second, try.HasHeaderValue("Content-Type", "text/css", false)) + c.Assert(err, checker.IsNil) + + err = try.GetRequest("http://127.0.0.1:8000/autodetect/pdf/ct", time.Second, try.HasHeaderValue("Content-Type", "application/pdf", false)) + c.Assert(err, checker.IsNil) + + err = try.GetRequest("http://127.0.0.1:8000/autodetect/css/noct", time.Second, try.HasHeaderValue("Content-Type", "text/plain; charset=utf-8", false)) + c.Assert(err, checker.IsNil) + + err = try.GetRequest("http://127.0.0.1:8000/autodetect/pdf/noct", time.Second, try.HasHeaderValue("Content-Type", "application/pdf", false)) + c.Assert(err, checker.IsNil) } func (s *SimpleSuite) TestMuxer(c *check.C) { diff --git a/pkg/config/dynamic/middlewares.go b/pkg/config/dynamic/middlewares.go index e8d2a1491..52d349c4d 100644 --- a/pkg/config/dynamic/middlewares.go +++ b/pkg/config/dynamic/middlewares.go @@ -33,7 +33,7 @@ type Middleware struct { Compress *Compress `json:"compress,omitempty" toml:"compress,omitempty" yaml:"compress,omitempty" label:"allowEmpty" file:"allowEmpty" kv:"allowEmpty" export:"true"` PassTLSClientCert *PassTLSClientCert `json:"passTLSClientCert,omitempty" toml:"passTLSClientCert,omitempty" yaml:"passTLSClientCert,omitempty" export:"true"` Retry *Retry `json:"retry,omitempty" toml:"retry,omitempty" yaml:"retry,omitempty" export:"true"` - ContentType *ContentType `json:"contentType,omitempty" toml:"contentType,omitempty" yaml:"contentType,omitempty" export:"true"` + ContentType *ContentType `json:"contentType,omitempty" toml:"contentType,omitempty" yaml:"contentType,omitempty" label:"allowEmpty" file:"allowEmpty" kv:"allowEmpty" export:"true"` GrpcWeb *GrpcWeb `json:"grpcWeb,omitempty" toml:"grpcWeb,omitempty" yaml:"grpcWeb,omitempty" export:"true"` Plugin map[string]PluginConf `json:"plugin,omitempty" toml:"plugin,omitempty" yaml:"plugin,omitempty" export:"true"` @@ -52,15 +52,9 @@ type GrpcWeb struct { // +k8s:deepcopy-gen=true // ContentType holds the content-type middleware configuration. -// This middleware exists to enable the correct behavior until at least the default one can be changed in a future version. -type ContentType struct { - // AutoDetect specifies whether to let the `Content-Type` header, if it has not been set by the backend, - // be automatically set to a value derived from the contents of the response. - // As a proxy, the default behavior should be to leave the header alone, regardless of what the backend did with it. - // However, the historic default was to always auto-detect and set the header if it was nil, - // and it is going to be kept that way in order to support users currently relying on it. - AutoDetect bool `json:"autoDetect,omitempty" toml:"autoDetect,omitempty" yaml:"autoDetect,omitempty" export:"true"` -} +// This middleware sets the `Content-Type` header value to the media type detected from the response content, +// when it is not set by the backend. +type ContentType struct{} // +k8s:deepcopy-gen=true diff --git a/pkg/middlewares/contenttype/content_type.go b/pkg/middlewares/contenttype/content_type.go new file mode 100644 index 000000000..bfe5a5c48 --- /dev/null +++ b/pkg/middlewares/contenttype/content_type.go @@ -0,0 +1,46 @@ +package contenttype + +import ( + "context" + "net/http" + + "github.com/traefik/traefik/v2/pkg/middlewares" +) + +const ( + typeName = "ContentType" +) + +// ContentType is a middleware used to activate Content-Type auto-detection. +type contentType struct { + next http.Handler + name string +} + +// New creates a new handler. +func New(ctx context.Context, next http.Handler, name string) (http.Handler, error) { + middlewares.GetLogger(ctx, name, typeName).Debug().Msg("Creating middleware") + return &contentType{next: next, name: name}, nil +} + +func (c *contentType) ServeHTTP(rw http.ResponseWriter, req *http.Request) { + // Re-enable auto-detection. + if ct, ok := rw.Header()["Content-Type"]; ok && ct == nil { + middlewares.GetLogger(req.Context(), c.name, typeName). + Debug().Msg("Enable Content-Type auto-detection.") + delete(rw.Header(), "Content-Type") + } + + c.next.ServeHTTP(rw, req) +} + +func DisableAutoDetection(next http.Handler) http.HandlerFunc { + return func(rw http.ResponseWriter, req *http.Request) { + // Prevent Content-Type auto-detection. + if _, ok := rw.Header()["Content-Type"]; !ok { + rw.Header()["Content-Type"] = nil + } + + next.ServeHTTP(rw, req) + } +} diff --git a/pkg/middlewares/contenttype/content_type_test.go b/pkg/middlewares/contenttype/content_type_test.go new file mode 100644 index 000000000..f134994c0 --- /dev/null +++ b/pkg/middlewares/contenttype/content_type_test.go @@ -0,0 +1,79 @@ +package contenttype + +import ( + "context" + "net/http" + "net/http/httptest" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/traefik/traefik/v2/pkg/testhelpers" +) + +func TestAutoDetection(t *testing.T) { + testCases := []struct { + desc string + autoDetect bool + contentType string + wantContentType string + }{ + { + desc: "Keep the Content-Type returned by the server", + autoDetect: false, + contentType: "application/json", + wantContentType: "application/json", + }, + { + desc: "Don't auto-detect Content-Type header by default when not set by the server", + autoDetect: false, + contentType: "", + wantContentType: "", + }, + { + desc: "Keep the Content-Type returned by the server with auto-detection middleware", + autoDetect: true, + contentType: "application/json", + wantContentType: "application/json", + }, + { + desc: "Auto-detect when Content-Type header is not already set by the server with auto-detection middleware", + autoDetect: true, + contentType: "", + wantContentType: "text/plain; charset=utf-8", + }, + } + + for _, test := range testCases { + test := test + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + var next http.Handler + next = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if test.contentType != "" { + w.Header().Set("Content-Type", test.contentType) + } + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte("Test")) + }) + + if test.autoDetect { + var err error + next, err = New(context.Background(), next, "foo-content-type") + require.NoError(t, err) + } + + server := httptest.NewServer( + DisableAutoDetection(next), + ) + t.Cleanup(server.Close) + + req := testhelpers.MustNewRequest(http.MethodGet, server.URL, nil) + res, err := server.Client().Do(req) + require.NoError(t, err) + + assert.Equal(t, test.wantContentType, res.Header.Get("Content-Type")) + }) + } +} diff --git a/pkg/redactor/redactor_config_test.go b/pkg/redactor/redactor_config_test.go index 88b507252..beb7ed5b1 100644 --- a/pkg/redactor/redactor_config_test.go +++ b/pkg/redactor/redactor_config_test.go @@ -337,9 +337,7 @@ func init() { Attempts: 42, InitialInterval: 42, }, - ContentType: &dynamic.ContentType{ - AutoDetect: true, - }, + ContentType: &dynamic.ContentType{}, Plugin: map[string]dynamic.PluginConf{ "foo": { "answer": struct{ Answer int }{ diff --git a/pkg/redactor/testdata/anonymized-dynamic-config.json b/pkg/redactor/testdata/anonymized-dynamic-config.json index 4a7d86e2c..f5ce9308f 100644 --- a/pkg/redactor/testdata/anonymized-dynamic-config.json +++ b/pkg/redactor/testdata/anonymized-dynamic-config.json @@ -302,9 +302,7 @@ "attempts": 42, "initialInterval": "42ns" }, - "contentType": { - "autoDetect": true - }, + "contentType": {}, "plugin": { "foo": { "answer": {} diff --git a/pkg/redactor/testdata/secured-dynamic-config.json b/pkg/redactor/testdata/secured-dynamic-config.json index 02547cbb9..05efe1420 100644 --- a/pkg/redactor/testdata/secured-dynamic-config.json +++ b/pkg/redactor/testdata/secured-dynamic-config.json @@ -305,9 +305,7 @@ "attempts": 42, "initialInterval": "42ns" }, - "contentType": { - "autoDetect": true - }, + "contentType": {}, "plugin": { "foo": { "answer": {} diff --git a/pkg/server/middleware/middlewares.go b/pkg/server/middleware/middlewares.go index 18c1f25ba..6d1790838 100644 --- a/pkg/server/middleware/middlewares.go +++ b/pkg/server/middleware/middlewares.go @@ -16,6 +16,7 @@ import ( "github.com/traefik/traefik/v2/pkg/middlewares/chain" "github.com/traefik/traefik/v2/pkg/middlewares/circuitbreaker" "github.com/traefik/traefik/v2/pkg/middlewares/compress" + "github.com/traefik/traefik/v2/pkg/middlewares/contenttype" "github.com/traefik/traefik/v2/pkg/middlewares/customerrors" "github.com/traefik/traefik/v2/pkg/middlewares/grpcweb" "github.com/traefik/traefik/v2/pkg/middlewares/headers" @@ -181,12 +182,7 @@ func (b *Builder) buildConstructor(ctx context.Context, middlewareName string) ( return nil, badConf } middleware = func(next http.Handler) (http.Handler, error) { - return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { - if !config.ContentType.AutoDetect { - rw.Header()["Content-Type"] = nil - } - next.ServeHTTP(rw, req) - }), nil + return contenttype.New(ctx, next, middlewareName) } } diff --git a/pkg/server/server_entrypoint_tcp.go b/pkg/server/server_entrypoint_tcp.go index 2fd4380f8..87c0a77ef 100644 --- a/pkg/server/server_entrypoint_tcp.go +++ b/pkg/server/server_entrypoint_tcp.go @@ -22,6 +22,7 @@ import ( "github.com/traefik/traefik/v2/pkg/ip" "github.com/traefik/traefik/v2/pkg/logs" "github.com/traefik/traefik/v2/pkg/middlewares" + "github.com/traefik/traefik/v2/pkg/middlewares/contenttype" "github.com/traefik/traefik/v2/pkg/middlewares/forwardedheaders" "github.com/traefik/traefik/v2/pkg/middlewares/requestdecorator" "github.com/traefik/traefik/v2/pkg/safe" @@ -537,6 +538,8 @@ func createHTTPServer(ctx context.Context, ln net.Listener, configuration *stati handler = http.AllowQuerySemicolons(handler) + handler = contenttype.DisableAutoDetection(handler) + if withH2c { handler = h2c.NewHandler(handler, &http2.Server{ MaxConcurrentStreams: uint32(configuration.HTTP2.MaxConcurrentStreams), From 0d81fac3fc03f2c3182ea12064df369c59361659 Mon Sep 17 00:00:00 2001 From: Tom Moulard Date: Tue, 29 Nov 2022 15:34:05 +0100 Subject: [PATCH 05/19] Add OpenTelemetry tracing and metrics support --- README.md | 2 +- cmd/traefik/traefik.go | 47 +- .../observability/metrics/opentelemetry.md | 353 ++++++++++++++ .../observability/tracing/opentelemetry.md | 246 ++++++++++ .../reference/static-configuration/cli-ref.md | 75 +++ .../reference/static-configuration/env-ref.md | 75 +++ .../reference/static-configuration/file.toml | 33 ++ .../reference/static-configuration/file.yaml | 36 ++ docs/mkdocs.yml | 2 + go.mod | 88 ++-- go.sum | 197 ++++++-- pkg/config/static/static_config.go | 18 +- pkg/metrics/opentelemetry.go | 422 +++++++++++++++++ pkg/metrics/opentelemetry_test.go | 446 ++++++++++++++++++ pkg/server/server.go | 1 + pkg/tracing/opentelemetry/opentelemetry.go | 138 ++++++ .../opentelemetry/opentelemetry_test.go | 67 +++ pkg/tracing/operation_name_test.go | 6 +- pkg/types/metrics.go | 37 +- 19 files changed, 2199 insertions(+), 90 deletions(-) create mode 100644 docs/content/observability/metrics/opentelemetry.md create mode 100644 docs/content/observability/tracing/opentelemetry.md create mode 100644 pkg/metrics/opentelemetry.go create mode 100644 pkg/metrics/opentelemetry_test.go create mode 100644 pkg/tracing/opentelemetry/opentelemetry.go create mode 100644 pkg/tracing/opentelemetry/opentelemetry_test.go diff --git a/README.md b/README.md index 318a05569..544eddb92 100644 --- a/README.md +++ b/README.md @@ -57,7 +57,7 @@ _(But if you'd rather configure some of your routes manually, Traefik supports t - Provides HTTPS to your microservices by leveraging [Let's Encrypt](https://letsencrypt.org) (wildcard certificates support) - Circuit breakers, retry - See the magic through its clean web UI -- Websocket, HTTP/2, GRPC ready +- Websocket, HTTP/2, gRPC ready - Provides metrics (Rest, Prometheus, Datadog, Statsd, InfluxDB) - Keeps access logs (JSON, CLF) - Fast diff --git a/cmd/traefik/traefik.go b/cmd/traefik/traefik.go index 62d15e78d..0559fbb20 100644 --- a/cmd/traefik/traefik.go +++ b/cmd/traefik/traefik.go @@ -504,24 +504,30 @@ func registerMetricClients(metricsConfig *types.Metrics) []metrics.Registry { logger := log.With().Str(logs.MetricsProviderName, "datadog").Logger() registries = append(registries, metrics.RegisterDatadog(logger.WithContext(context.Background()), metricsConfig.Datadog)) - logger.Debug().Msgf("Configured Datadog metrics: pushing to %s once every %s", - metricsConfig.Datadog.Address, metricsConfig.Datadog.PushInterval) + logger.Debug(). + Str("address", metricsConfig.Datadog.Address). + Str("pushInterval", metricsConfig.Datadog.PushInterval.String()). + Msgf("Configured Datadog metrics") } if metricsConfig.StatsD != nil { logger := log.With().Str(logs.MetricsProviderName, "statsd").Logger() registries = append(registries, metrics.RegisterStatsd(logger.WithContext(context.Background()), metricsConfig.StatsD)) - logger.Debug().Msgf("Configured StatsD metrics: pushing to %s once every %s", - metricsConfig.StatsD.Address, metricsConfig.StatsD.PushInterval) + logger.Debug(). + Str("address", metricsConfig.StatsD.Address). + Str("pushInterval", metricsConfig.StatsD.PushInterval.String()). + Msg("Configured StatsD metrics") } if metricsConfig.InfluxDB != nil { logger := log.With().Str(logs.MetricsProviderName, "influxdb").Logger() registries = append(registries, metrics.RegisterInfluxDB(logger.WithContext(context.Background()), metricsConfig.InfluxDB)) - logger.Debug().Msgf("Configured InfluxDB metrics: pushing to %s once every %s", - metricsConfig.InfluxDB.Address, metricsConfig.InfluxDB.PushInterval) + logger.Debug(). + Str("address", metricsConfig.InfluxDB.Address). + Str("pushInterval", metricsConfig.InfluxDB.PushInterval.String()). + Msg("Configured InfluxDB metrics") } if metricsConfig.InfluxDB2 != nil { @@ -530,8 +536,25 @@ func registerMetricClients(metricsConfig *types.Metrics) []metrics.Registry { influxDB2Register := metrics.RegisterInfluxDB2(logger.WithContext(context.Background()), metricsConfig.InfluxDB2) if influxDB2Register != nil { registries = append(registries, influxDB2Register) - logger.Debug().Msgf("Configured InfluxDB v2 metrics: pushing to %s (%s org/%s bucket) once every %s", - metricsConfig.InfluxDB2.Address, metricsConfig.InfluxDB2.Org, metricsConfig.InfluxDB2.Bucket, metricsConfig.InfluxDB2.PushInterval) + logger.Debug(). + Str("address", metricsConfig.InfluxDB2.Address). + Str("bucket", metricsConfig.InfluxDB2.Bucket). + Str("organization", metricsConfig.InfluxDB2.Org). + Str("pushInterval", metricsConfig.InfluxDB2.PushInterval.String()). + Msg("Configured InfluxDB v2 metrics") + } + } + + if metricsConfig.OpenTelemetry != nil { + logger := log.With().Str(logs.MetricsProviderName, "openTelemetry").Logger() + + openTelemetryRegistry := metrics.RegisterOpenTelemetry(logger.WithContext(context.Background()), metricsConfig.OpenTelemetry) + if openTelemetryRegistry != nil { + registries = append(registries, openTelemetryRegistry) + logger.Debug(). + Str("address", metricsConfig.OpenTelemetry.Address). + Str("pushInterval", metricsConfig.OpenTelemetry.PushInterval.String()). + Msg("Configured OpenTelemetry metrics") } } @@ -617,6 +640,14 @@ func setupTracing(conf *static.Tracing) *tracing.Tracing { } } + if conf.OpenTelemetry != nil { + if backend != nil { + log.Error().Msg("Tracing backends are all mutually exclusive: cannot create OpenTelemetry backend.") + } else { + backend = conf.OpenTelemetry + } + } + if backend == nil { log.Debug().Msg("Could not initialize tracing, using Jaeger by default") defaultBackend := &jaeger.Config{} diff --git a/docs/content/observability/metrics/opentelemetry.md b/docs/content/observability/metrics/opentelemetry.md new file mode 100644 index 000000000..e3abed40a --- /dev/null +++ b/docs/content/observability/metrics/opentelemetry.md @@ -0,0 +1,353 @@ +--- +title: "Traefik OpenTelemetry Documentation" +description: "Traefik supports several metrics backends, including OpenTelemetry. Learn how to implement it for observability in Traefik Proxy. Read the technical documentation." +--- + +# OpenTelemetry + +To enable the OpenTelemetry: + +```yaml tab="File (YAML)" +metrics: + openTelemetry: {} +``` + +```toml tab="File (TOML)" +[metrics] + [metrics.openTelemetry] +``` + +```bash tab="CLI" +--metrics.openTelemetry=true +``` + +!!! info "The OpenTelemetry exporter will export metrics to the collector by using HTTP by default, see the [gRPC Section](#grpc-configuration) to use gRPC." + +#### `address` + +_Required, Default="localhost:4318", Format="`:`"_ + +Address of the OpenTelemetry Collector to send metrics to. + +```yaml tab="File (YAML)" +metrics: + openTelemetry: + address: localhost:4318 +``` + +```toml tab="File (TOML)" +[metrics] + [metrics.openTelemetry] + address = "localhost:4318" +``` + +```bash tab="CLI" +--metrics.openTelemetry.address=localhost:4318 +``` + +#### `addEntryPointsLabels` + +_Optional, Default=true_ + +Enable metrics on entry points. + +```yaml tab="File (YAML)" +metrics: + openTelemetry: + addEntryPointsLabels: true +``` + +```toml tab="File (TOML)" +[metrics] + [metrics.openTelemetry] + addEntryPointsLabels = true +``` + +```bash tab="CLI" +--metrics.openTelemetry.addEntryPointsLabels=true +``` + +#### `addRoutersLabels` + +_Optional, Default=false_ + +Enable metrics on routers. + +```yaml tab="File (YAML)" +metrics: + openTelemetry: + addRoutersLabels: true +``` + +```toml tab="File (TOML)" +[metrics] + [metrics.openTelemetry] + addRoutersLabels = true +``` + +```bash tab="CLI" +--metrics.openTelemetry.addRoutersLabels=true +``` + +#### `addServicesLabels` + +_Optional, Default=true_ + +Enable metrics on services. + +```yaml tab="File (YAML)" +metrics: + openTelemetry: + addServicesLabels: true +``` + +```toml tab="File (TOML)" +[metrics] + [metrics.openTelemetry] + addServicesLabels = true +``` + +```bash tab="CLI" +--metrics.openTelemetry.addServicesLabels=true +``` + +#### `explicitBoundaries` + +_Optional, Default=".005, .01, .025, .05, .1, .25, .5, 1, 2.5, 5, 10"_ + +Explicit boundaries for Histogram data points. + +```yaml tab="File (YAML)" +metrics: + openTelemetry: + explicitBoundaries: + - 0.1 + - 0.3 + - 1.2 + - 5.0 +``` + +```toml tab="File (TOML)" +[metrics] + [metrics.openTelemetry] + explicitBoundaries = [0.1,0.3,1.2,5.0] +``` + +```bash tab="CLI" +--metrics.openTelemetry.explicitBoundaries=0.1,0.3,1.2,5.0 +``` + +#### `headers` + +_Optional, Default={}_ + +Additional headers sent with metrics by the reporter to the OpenTelemetry Collector. + +```yaml tab="File (YAML)" +metrics: + openTelemetry: + headers: + foo: bar + baz: buz +``` + +```toml tab="File (TOML)" +[metrics] + [metrics.openTelemetry.headers] + foo = "bar" + baz = "buz" +``` + +```bash tab="CLI" +--metrics.openTelemetry.headers.foo=bar --metrics.openTelemetry.headers.baz=buz +``` + +#### `insecure` + +_Optional, Default=false_ + +Allows reporter to send metrics to the OpenTelemetry Collector without using a secured protocol. + +```yaml tab="File (YAML)" +metrics: + openTelemetry: + insecure: true +``` + +```toml tab="File (TOML)" +[metrics] + [metrics.openTelemetry] + insecure = true +``` + +```bash tab="CLI" +--metrics.openTelemetry.insecure=true +``` + +#### `pushInterval` + +_Optional, Default=10s_ + +Interval at which metrics are sent to the OpenTelemetry Collector. + +```yaml tab="File (YAML)" +metrics: + openTelemetry: + pushInterval: 10s +``` + +```toml tab="File (TOML)" +[metrics] + [metrics.openTelemetry] + pushInterval = "10s" +``` + +```bash tab="CLI" +--metrics.openTelemetry.pushInterval=10s +``` + +#### `path` + +_Required, Default="/v1/traces"_ + +Allows to override the default URL path used for sending metrics. +This option has no effect when using gRPC transport. + +```yaml tab="File (YAML)" +metrics: + openTelemetry: + path: /foo/v1/traces +``` + +```toml tab="File (TOML)" +[metrics] + [metrics.openTelemetry] + path = "/foo/v1/traces" +``` + +```bash tab="CLI" +--metrics.openTelemetry.path=/foo/v1/traces +``` + +#### `tls` + +_Optional_ + +Defines the TLS configuration used by the reporter to send metrics to the OpenTelemetry Collector. + +##### `ca` + +_Optional_ + +`ca` is the path to the certificate authority used for the secure connection to the OpenTelemetry Collector, +it defaults to the system bundle. + +```yaml tab="File (YAML)" +metrics: + openTelemetry: + tls: + ca: path/to/ca.crt +``` + +```toml tab="File (TOML)" +[metrics.openTelemetry.tls] + ca = "path/to/ca.crt" +``` + +```bash tab="CLI" +--metrics.openTelemetry.tls.ca=path/to/ca.crt +``` + +##### `cert` + +_Optional_ + +`cert` is the path to the public certificate used for the secure connection to the OpenTelemetry Collector. +When using this option, setting the `key` option is required. + +```yaml tab="File (YAML)" +metrics: + openTelemetry: + tls: + cert: path/to/foo.cert + key: path/to/foo.key +``` + +```toml tab="File (TOML)" +[metrics.openTelemetry.tls] + cert = "path/to/foo.cert" + key = "path/to/foo.key" +``` + +```bash tab="CLI" +--metrics.openTelemetry.tls.cert=path/to/foo.cert +--metrics.openTelemetry.tls.key=path/to/foo.key +``` + +##### `key` + +_Optional_ + +`key` is the path to the private key used for the secure connection to the OpenTelemetry Collector. +When using this option, setting the `cert` option is required. + +```yaml tab="File (YAML)" +metrics: + openTelemetry: + tls: + cert: path/to/foo.cert + key: path/to/foo.key +``` + +```toml tab="File (TOML)" +[metrics.openTelemetry.tls] + cert = "path/to/foo.cert" + key = "path/to/foo.key" +``` + +```bash tab="CLI" +--metrics.openTelemetry.tls.cert=path/to/foo.cert +--metrics.openTelemetry.tls.key=path/to/foo.key +``` + +##### `insecureSkipVerify` + +_Optional, Default=false_ + +If `insecureSkipVerify` is `true`, +the TLS connection to the OpenTelemetry Collector accepts any certificate presented by the server regardless of the hostnames it covers. + +```yaml tab="File (YAML)" +metrics: + openTelemetry: + tls: + insecureSkipVerify: true +``` + +```toml tab="File (TOML)" +[metrics.openTelemetry.tls] + insecureSkipVerify = true +``` + +```bash tab="CLI" +--metrics.openTelemetry.tls.insecureSkipVerify=true +``` + +#### gRPC configuration + +This instructs the reporter to send metrics to the OpenTelemetry Collector using gRPC. + +```yaml tab="File (YAML)" +metrics: + openTelemetry: + grpc: {} +``` + +```toml tab="File (TOML)" +[metrics] + [metrics.openTelemetry.grpc] +``` + +```bash tab="CLI" +--metrics.openTelemetry.grpc=true +``` diff --git a/docs/content/observability/tracing/opentelemetry.md b/docs/content/observability/tracing/opentelemetry.md new file mode 100644 index 000000000..d39e09b58 --- /dev/null +++ b/docs/content/observability/tracing/opentelemetry.md @@ -0,0 +1,246 @@ +--- +title: "Traefik OpenTelemetry Documentation" +description: "Traefik supports several tracing backends, including OpenTelemetry. Learn how to implement it for observability in Traefik Proxy. Read the technical documentation." +--- + +# OpenTelemetry + +To enable the OpenTelemetry tracer: + +```yaml tab="File (YAML)" +tracing: + openTelemetry: {} +``` + +```toml tab="File (TOML)" +[tracing] + [tracing.openTelemetry] +``` + +```bash tab="CLI" +--tracing.openTelemetry=true +``` + +!!! info "The OpenTelemetry trace reporter will export traces to the collector using HTTP by default, see the [gRPC Section](#grpc-configuration) to use gRPC." + +!!! info "Trace sampling" + + By default, the OpenTelemetry trace reporter will sample 100% of traces. + See [OpenTelemetry's SDK configuration](https://opentelemetry.io/docs/reference/specification/sdk-environment-variables/#general-sdk-configuration) to customize the sampling strategy. + +#### `address` + +_Required, Default="localhost:4318", Format="`:`"_ + +Address of the OpenTelemetry Collector to send spans to. + +```yaml tab="File (YAML)" +tracing: + openTelemetry: + address: localhost:4318 +``` + +```toml tab="File (TOML)" +[tracing] + [tracing.openTelemetry] + address = "localhost:4318" +``` + +```bash tab="CLI" +--tracing.openTelemetry.address=localhost:4318 +``` + +#### `headers` + +_Optional, Default={}_ + +Additional headers sent with spans by the reporter to the OpenTelemetry Collector. + +```yaml tab="File (YAML)" +tracing: + openTelemetry: + headers: + foo: bar + baz: buz +``` + +```toml tab="File (TOML)" +[tracing] + [tracing.openTelemetry.headers] + foo = "bar" + baz = "buz" +``` + +```bash tab="CLI" +--tracing.openTelemetry.headers.foo=bar --tracing.openTelemetry.headers.baz=buz +``` + +#### `insecure` + +_Optional, Default=false_ + +Allows reporter to send spans to the OpenTelemetry Collector without using a secured protocol. + +```yaml tab="File (YAML)" +tracing: + openTelemetry: + insecure: true +``` + +```toml tab="File (TOML)" +[tracing] + [tracing.openTelemetry] + insecure = true +``` + +```bash tab="CLI" +--tracing.openTelemetry.insecure=true +``` + +#### `path` + +_Required, Default="/v1/traces"_ + +Allows to override the default URL path used for sending traces. +This option has no effect when using gRPC transport. + +```yaml tab="File (YAML)" +tracing: + openTelemetry: + path: /foo/v1/traces +``` + +```toml tab="File (TOML)" +[tracing] + [tracing.openTelemetry] + path = "/foo/v1/traces" +``` + +```bash tab="CLI" +--tracing.openTelemetry.path=/foo/v1/traces +``` + +#### `tls` + +_Optional_ + +Defines the TLS configuration used by the reporter to send spans to the OpenTelemetry Collector. + +##### `ca` + +_Optional_ + +`ca` is the path to the certificate authority used for the secure connection to the OpenTelemetry Collector, +it defaults to the system bundle. + +```yaml tab="File (YAML)" +tracing: + openTelemetry: + tls: + ca: path/to/ca.crt +``` + +```toml tab="File (TOML)" +[tracing.openTelemetry.tls] + ca = "path/to/ca.crt" +``` + +```bash tab="CLI" +--tracing.openTelemetry.tls.ca=path/to/ca.crt +``` + +##### `cert` + +_Optional_ + +`cert` is the path to the public certificate used for the secure connection to the OpenTelemetry Collector. +When using this option, setting the `key` option is required. + +```yaml tab="File (YAML)" +tracing: + openTelemetry: + tls: + cert: path/to/foo.cert + key: path/to/foo.key +``` + +```toml tab="File (TOML)" +[tracing.openTelemetry.tls] + cert = "path/to/foo.cert" + key = "path/to/foo.key" +``` + +```bash tab="CLI" +--tracing.openTelemetry.tls.cert=path/to/foo.cert +--tracing.openTelemetry.tls.key=path/to/foo.key +``` + +##### `key` + +_Optional_ + +`key` is the path to the private key used for the secure connection to the OpenTelemetry Collector. +When using this option, setting the `cert` option is required. + +```yaml tab="File (YAML)" +tracing: + openTelemetry: + tls: + cert: path/to/foo.cert + key: path/to/foo.key +``` + +```toml tab="File (TOML)" +[tracing.openTelemetry.tls] + cert = "path/to/foo.cert" + key = "path/to/foo.key" +``` + +```bash tab="CLI" +--tracing.openTelemetry.tls.cert=path/to/foo.cert +--tracing.openTelemetry.tls.key=path/to/foo.key +``` + +##### `insecureSkipVerify` + +_Optional, Default=false_ + +If `insecureSkipVerify` is `true`, +the TLS connection to the OpenTelemetry Collector accepts any certificate presented by the server regardless of the hostnames it covers. + +```yaml tab="File (YAML)" +tracing: + openTelemetry: + tls: + insecureSkipVerify: true +``` + +```toml tab="File (TOML)" +[tracing.openTelemetry.tls] + insecureSkipVerify = true +``` + +```bash tab="CLI" +--tracing.openTelemetry.tls.insecureSkipVerify=true +``` + +#### gRPC configuration + +_Optional_ + +This instructs the reporter to send spans to the OpenTelemetry Collector using gRPC. + +```yaml tab="File (YAML)" +tracing: + openTelemetry: + grpc: {} +``` + +```toml tab="File (TOML)" +[tracing] + [tracing.openTelemetry.grpc] +``` + +```bash tab="CLI" +--tracing.openTelemetry.grpc=true +``` diff --git a/docs/content/reference/static-configuration/cli-ref.md b/docs/content/reference/static-configuration/cli-ref.md index 3b40890e7..a79447131 100644 --- a/docs/content/reference/static-configuration/cli-ref.md +++ b/docs/content/reference/static-configuration/cli-ref.md @@ -357,6 +357,51 @@ InfluxDB v2 push interval. (Default: ```10```) `--metrics.influxdb2.token`: InfluxDB v2 access token. +`--metrics.opentelemetry`: +OpenTelemetry metrics exporter type. (Default: ```false```) + +`--metrics.opentelemetry.addentrypointslabels`: +Enable metrics on entry points. (Default: ```true```) + +`--metrics.opentelemetry.address`: +Address (host:port) of the collector endpoint. (Default: ```localhost:4318```) + +`--metrics.opentelemetry.addrouterslabels`: +Enable metrics on routers. (Default: ```false```) + +`--metrics.opentelemetry.addserviceslabels`: +Enable metrics on services. (Default: ```true```) + +`--metrics.opentelemetry.explicitboundaries`: +Boundaries for latency metrics. (Default: ```0.005000, 0.010000, 0.025000, 0.050000, 0.100000, 0.250000, 0.500000, 1.000000, 2.500000, 5.000000, 10.000000```) + +`--metrics.opentelemetry.grpc`: +gRPC specific configuration for the OpenTelemetry collector. (Default: ```true```) + +`--metrics.opentelemetry.headers.`: +Headers sent with payload. + +`--metrics.opentelemetry.insecure`: +Disables client transport security for the exporter. (Default: ```false```) + +`--metrics.opentelemetry.path`: +Set the URL path of the collector endpoint. + +`--metrics.opentelemetry.pushinterval`: +Period between calls to collect a checkpoint. (Default: ```10```) + +`--metrics.opentelemetry.tls.ca`: +TLS CA + +`--metrics.opentelemetry.tls.cert`: +TLS cert + +`--metrics.opentelemetry.tls.insecureskipverify`: +TLS insecure skip verify (Default: ```false```) + +`--metrics.opentelemetry.tls.key`: +TLS key + `--metrics.prometheus`: Prometheus metrics exporter type. (Default: ```false```) @@ -1095,6 +1140,36 @@ Sets the sampling type. (Default: ```const```) `--tracing.jaeger.tracecontextheadername`: Sets the header name used to store the trace ID. (Default: ```uber-trace-id```) +`--tracing.opentelemetry`: +Settings for OpenTelemetry. (Default: ```false```) + +`--tracing.opentelemetry.address`: +Sets the address (host:port) of the collector endpoint. (Default: ```localhost:4318```) + +`--tracing.opentelemetry.grpc`: +gRPC specific configuration for the OpenTelemetry collector. (Default: ```true```) + +`--tracing.opentelemetry.headers.`: +Defines additional headers to be sent with the payloads. + +`--tracing.opentelemetry.insecure`: +Disables client transport security for the exporter. (Default: ```false```) + +`--tracing.opentelemetry.path`: +Sets the URL path of the collector endpoint. + +`--tracing.opentelemetry.tls.ca`: +TLS CA + +`--tracing.opentelemetry.tls.cert`: +TLS cert + +`--tracing.opentelemetry.tls.insecureskipverify`: +TLS insecure skip verify (Default: ```false```) + +`--tracing.opentelemetry.tls.key`: +TLS key + `--tracing.servicename`: Set the name for this service. (Default: ```traefik```) diff --git a/docs/content/reference/static-configuration/env-ref.md b/docs/content/reference/static-configuration/env-ref.md index bbcf2459d..41bf8c6bf 100644 --- a/docs/content/reference/static-configuration/env-ref.md +++ b/docs/content/reference/static-configuration/env-ref.md @@ -357,6 +357,51 @@ InfluxDB retention policy used when protocol is http. `TRAEFIK_METRICS_INFLUXDB_USERNAME`: InfluxDB username (only with http). +`TRAEFIK_METRICS_OPENTELEMETRY`: +OpenTelemetry metrics exporter type. (Default: ```false```) + +`TRAEFIK_METRICS_OPENTELEMETRY_ADDENTRYPOINTSLABELS`: +Enable metrics on entry points. (Default: ```true```) + +`TRAEFIK_METRICS_OPENTELEMETRY_ADDRESS`: +Address (host:port) of the collector endpoint. (Default: ```localhost:4318```) + +`TRAEFIK_METRICS_OPENTELEMETRY_ADDROUTERSLABELS`: +Enable metrics on routers. (Default: ```false```) + +`TRAEFIK_METRICS_OPENTELEMETRY_ADDSERVICESLABELS`: +Enable metrics on services. (Default: ```true```) + +`TRAEFIK_METRICS_OPENTELEMETRY_EXPLICITBOUNDARIES`: +Boundaries for latency metrics. (Default: ```0.005000, 0.010000, 0.025000, 0.050000, 0.100000, 0.250000, 0.500000, 1.000000, 2.500000, 5.000000, 10.000000```) + +`TRAEFIK_METRICS_OPENTELEMETRY_GRPC`: +gRPC specific configuration for the OpenTelemetry collector. (Default: ```true```) + +`TRAEFIK_METRICS_OPENTELEMETRY_HEADERS_`: +Headers sent with payload. + +`TRAEFIK_METRICS_OPENTELEMETRY_INSECURE`: +Disables client transport security for the exporter. (Default: ```false```) + +`TRAEFIK_METRICS_OPENTELEMETRY_PATH`: +Set the URL path of the collector endpoint. + +`TRAEFIK_METRICS_OPENTELEMETRY_PUSHINTERVAL`: +Period between calls to collect a checkpoint. (Default: ```10```) + +`TRAEFIK_METRICS_OPENTELEMETRY_TLS_CA`: +TLS CA + +`TRAEFIK_METRICS_OPENTELEMETRY_TLS_CERT`: +TLS cert + +`TRAEFIK_METRICS_OPENTELEMETRY_TLS_INSECURESKIPVERIFY`: +TLS insecure skip verify (Default: ```false```) + +`TRAEFIK_METRICS_OPENTELEMETRY_TLS_KEY`: +TLS key + `TRAEFIK_METRICS_PROMETHEUS`: Prometheus metrics exporter type. (Default: ```false```) @@ -1095,6 +1140,36 @@ Sets the sampling type. (Default: ```const```) `TRAEFIK_TRACING_JAEGER_TRACECONTEXTHEADERNAME`: Sets the header name used to store the trace ID. (Default: ```uber-trace-id```) +`TRAEFIK_TRACING_OPENTELEMETRY`: +Settings for OpenTelemetry. (Default: ```false```) + +`TRAEFIK_TRACING_OPENTELEMETRY_ADDRESS`: +Sets the address (host:port) of the collector endpoint. (Default: ```localhost:4318```) + +`TRAEFIK_TRACING_OPENTELEMETRY_GRPC`: +gRPC specific configuration for the OpenTelemetry collector. (Default: ```true```) + +`TRAEFIK_TRACING_OPENTELEMETRY_HEADERS_`: +Defines additional headers to be sent with the payloads. + +`TRAEFIK_TRACING_OPENTELEMETRY_INSECURE`: +Disables client transport security for the exporter. (Default: ```false```) + +`TRAEFIK_TRACING_OPENTELEMETRY_PATH`: +Sets the URL path of the collector endpoint. + +`TRAEFIK_TRACING_OPENTELEMETRY_TLS_CA`: +TLS CA + +`TRAEFIK_TRACING_OPENTELEMETRY_TLS_CERT`: +TLS cert + +`TRAEFIK_TRACING_OPENTELEMETRY_TLS_INSECURESKIPVERIFY`: +TLS insecure skip verify (Default: ```false```) + +`TRAEFIK_TRACING_OPENTELEMETRY_TLS_KEY`: +TLS key + `TRAEFIK_TRACING_SERVICENAME`: Set the name for this service. (Default: ```traefik```) diff --git a/docs/content/reference/static-configuration/file.toml b/docs/content/reference/static-configuration/file.toml index 2c800f808..73bf8f838 100644 --- a/docs/content/reference/static-configuration/file.toml +++ b/docs/content/reference/static-configuration/file.toml @@ -307,6 +307,25 @@ [metrics.influxDB2.additionalLabels] name0 = "foobar" name1 = "foobar" + [metrics.openTelemetry] + address = "foobar" + addEntryPointsLabels = true + addRoutersLabels = true + addServicesLabels = true + pushInterval = "42s" + path = "foobar" + explicitBoundaries = [42.0, 42.0] + insecure = true + [metrics.openTelemetry.headers] + name0 = "foobar" + name1 = "foobar" + [metrics.openTelemetry.tls] + ca = "foobar" + caOptional = true + cert = "foobar" + insecureSkipVerify = true + key = "foobar" + [metrics.openTelemetry.grpc] [ping] entryPoint = "foobar" @@ -391,6 +410,20 @@ serverURL = "foobar" secretToken = "foobar" serviceEnvironment = "foobar" + [tracing.openTelemetry] + address = "foobar" + insecure = true + path = "foobar" + [tracing.openTelemetry.headers] + name0 = "foobar" + name1 = "foobar" + [tracing.openTelemetry.tls] + ca = "foobar" + caOptional = true + cert = "foobar" + key = "foobar" + insecureSkipVerify = true + [tracing.openTelemetry.grpc] [hostResolver] cnameFlattening = true diff --git a/docs/content/reference/static-configuration/file.yaml b/docs/content/reference/static-configuration/file.yaml index a7a611e27..e84e553d9 100644 --- a/docs/content/reference/static-configuration/file.yaml +++ b/docs/content/reference/static-configuration/file.yaml @@ -335,6 +335,28 @@ metrics: additionalLabels: name0: foobar name1: foobar + openTelemetry: + address: foobar + addEntryPointsLabels: true + addRoutersLabels: true + addServicesLabels: true + explicitBoundaries: + - 42 + - 42 + headers: + name0: foobar + name1: foobar + insecure: true + path: foobar + pushInterval: 42s + tls: + ca: foobar + caOptional: true + cert: foobar + insecureSkipVerify: true + key: foobar + grpc: {} + ping: entryPoint: foobar manualRouting: true @@ -417,6 +439,20 @@ tracing: serverURL: foobar secretToken: foobar serviceEnvironment: foobar + openTelemetry: + address: foobar + headers: + name0: foobar + name1: foobar + insecure: true + path: foobar + tls: + ca: foobar + caOptional: true + cert: foobar + key: foobar + insecureSkipVerify: true + grpc: {} hostResolver: cnameFlattening: true resolvConfig: foobar diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index 8090daa7a..91b163347 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -157,6 +157,7 @@ nav: - 'Datadog': 'observability/metrics/datadog.md' - 'InfluxDB': 'observability/metrics/influxdb.md' - 'InfluxDB2': 'observability/metrics/influxdb2.md' + - 'OpenTelemetry': 'observability/metrics/opentelemetry.md' - 'Prometheus': 'observability/metrics/prometheus.md' - 'StatsD': 'observability/metrics/statsd.md' - 'Tracing': @@ -167,6 +168,7 @@ nav: - 'Instana': 'observability/tracing/instana.md' - 'Haystack': 'observability/tracing/haystack.md' - 'Elastic': 'observability/tracing/elastic.md' + - 'OpenTelemetry': 'observability/tracing/opentelemetry.md' - 'User Guides': - 'Kubernetes and Let''s Encrypt': 'user-guides/crd-acme/index.md' - 'gRPC Examples': 'user-guides/grpc.md' diff --git a/go.mod b/go.mod index 86872dde8..1ecb9fcdc 100644 --- a/go.mod +++ b/go.mod @@ -47,7 +47,7 @@ require ( github.com/lucas-clemente/quic-go v0.28.1 github.com/mailgun/ttlmap v0.0.0-20170619185759-c1c17f74874f github.com/miekg/dns v1.1.50 - github.com/mitchellh/copystructure v1.0.0 + github.com/mitchellh/copystructure v1.2.0 github.com/mitchellh/hashstructure v1.0.0 github.com/mitchellh/mapstructure v1.5.0 github.com/natefinch/lumberjack v0.0.0-20201021141957-47ffae23317c @@ -77,33 +77,45 @@ require ( github.com/vulcand/predicate v1.2.0 go.elastic.co/apm v1.13.1 go.elastic.co/apm/module/apmot v1.13.1 + go.opentelemetry.io/collector/pdata v0.64.1 + go.opentelemetry.io/otel v1.11.1 + go.opentelemetry.io/otel/bridge/opentracing v1.11.1 + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.33.0 + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.33.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.11.1 + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.11.1 + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.11.1 + go.opentelemetry.io/otel/metric v0.33.0 + go.opentelemetry.io/otel/sdk v1.11.1 + go.opentelemetry.io/otel/sdk/metric v0.33.0 + go.opentelemetry.io/otel/trace v1.11.1 golang.org/x/exp v0.0.0-20221114191408-850992195362 golang.org/x/mod v0.6.0 golang.org/x/net v0.1.0 golang.org/x/text v0.4.0 - golang.org/x/time v0.0.0-20220224211638-0e9765cccd65 + golang.org/x/time v0.0.0-20220609170525-579cf78fd858 golang.org/x/tools v0.2.0 - google.golang.org/grpc v1.46.0 + google.golang.org/grpc v1.50.1 gopkg.in/DataDog/dd-trace-go.v1 v1.43.1 gopkg.in/fsnotify.v1 v1.4.7 gopkg.in/yaml.v3 v3.0.1 - k8s.io/api v0.22.1 - k8s.io/apiextensions-apiserver v0.21.3 - k8s.io/apimachinery v0.22.1 - k8s.io/client-go v0.22.1 - k8s.io/utils v0.0.0-20210820185131-d34e5cb4466e + k8s.io/api v0.25.0 + k8s.io/apiextensions-apiserver v0.25.0 + k8s.io/apimachinery v0.25.0 + k8s.io/client-go v0.25.0 + k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed mvdan.cc/xurls/v2 v2.1.0 sigs.k8s.io/gateway-api v0.4.0 ) require ( - cloud.google.com/go v0.81.0 // indirect + cloud.google.com/go v0.97.0 // indirect github.com/AlecAivazis/survey/v2 v2.2.3 // indirect github.com/Azure/azure-sdk-for-go v40.3.0+incompatible // indirect github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect github.com/Azure/go-autorest v14.2.0+incompatible // indirect - github.com/Azure/go-autorest/autorest v0.11.24 // indirect - github.com/Azure/go-autorest/autorest/adal v0.9.18 // indirect + github.com/Azure/go-autorest/autorest v0.11.27 // indirect + github.com/Azure/go-autorest/autorest/adal v0.9.20 // indirect github.com/Azure/go-autorest/autorest/azure/auth v0.5.11 // indirect github.com/Azure/go-autorest/autorest/azure/cli v0.4.5 // indirect github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect @@ -121,6 +133,8 @@ require ( github.com/Microsoft/go-winio v0.5.2 // indirect github.com/Microsoft/hcsshim v0.8.23 // indirect github.com/OpenDNS/vegadns2client v0.0.0-20180418235048-a3fa4a771d87 // indirect + github.com/PuerkitoBio/purell v1.1.1 // indirect + github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect github.com/Shopify/sarama v1.23.1 // indirect github.com/VividCortex/gohistogram v1.0.0 // indirect github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412 // indirect @@ -165,7 +179,8 @@ require ( github.com/elastic/go-licenser v0.3.1 // indirect github.com/elastic/go-sysinfo v1.1.1 // indirect github.com/elastic/go-windows v1.0.0 // indirect - github.com/evanphx/json-patch v4.11.0+incompatible // indirect + github.com/emicklei/go-restful/v3 v3.8.0 // indirect + github.com/evanphx/json-patch v4.12.0+incompatible // indirect github.com/exoscale/egoscale v0.90.0 // indirect github.com/fatih/color v1.13.0 // indirect github.com/fsnotify/fsnotify v1.5.4 // indirect @@ -173,7 +188,11 @@ require ( github.com/ghodss/yaml v1.0.0 // indirect github.com/go-errors/errors v1.0.1 // indirect github.com/go-logfmt/logfmt v0.5.1 // indirect - github.com/go-logr/logr v0.4.0 // indirect + github.com/go-logr/logr v1.2.3 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-openapi/jsonpointer v0.19.5 // indirect + github.com/go-openapi/jsonreference v0.19.5 // indirect + github.com/go-openapi/swag v0.19.14 // indirect github.com/go-redis/redis/v8 v8.11.5 // indirect github.com/go-resty/resty/v2 v2.1.1-0.20191201195748-d7b97669fe48 // indirect github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect @@ -182,21 +201,22 @@ require ( github.com/gogo/googleapis v1.4.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang-jwt/jwt/v4 v4.2.0 // indirect - github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b // indirect + github.com/golang/glog v1.0.0 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/mock v1.6.0 // indirect github.com/google/btree v1.0.1 // indirect - github.com/google/go-cmp v0.5.8 // indirect + github.com/google/gnostic v0.5.7-v3refs // indirect + github.com/google/go-cmp v0.5.9 // indirect github.com/google/go-querystring v1.1.0 // indirect github.com/google/gofuzz v1.2.0 // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect github.com/google/uuid v1.3.0 // indirect - github.com/googleapis/gax-go/v2 v2.0.5 // indirect - github.com/googleapis/gnostic v0.5.5 // indirect + github.com/googleapis/gax-go/v2 v2.1.0 // indirect github.com/gophercloud/gophercloud v1.0.0 // indirect github.com/gophercloud/utils v0.0.0-20210216074907-f6de111f2eae // indirect github.com/gravitational/trace v1.1.16-0.20220114165159-14a9a7dd6aaf // indirect - github.com/grpc-ecosystem/go-grpc-middleware v1.2.2 // indirect + github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0 // indirect github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645 // indirect github.com/hashicorp/consul/sdk v0.10.0 // indirect github.com/hashicorp/cronexpr v1.1.1 // indirect @@ -237,6 +257,7 @@ require ( github.com/liquidweb/liquidweb-cli v0.6.9 // indirect github.com/liquidweb/liquidweb-go v1.6.3 // indirect github.com/looplab/fsm v0.1.0 // indirect + github.com/magiconair/properties v1.8.6 // indirect github.com/mailgun/minheap v0.0.0-20170619185613-3dbe6c6bf55f // indirect github.com/mailgun/multibuf v0.1.2 // indirect github.com/mailgun/timetools v0.0.0-20141028012446-7e6055773c51 // indirect @@ -256,7 +277,7 @@ require ( github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/go-ps v1.0.0 // indirect github.com/mitchellh/go-testing-interface v1.14.1 // indirect - github.com/mitchellh/reflectwalk v1.0.1 // indirect + github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/moby/buildkit v0.8.2-0.20210401015549-df49b648c8bf // indirect github.com/moby/locker v1.0.1 // indirect github.com/moby/sys/mount v0.2.0 // indirect @@ -265,6 +286,7 @@ require ( github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/morikuni/aec v1.0.0 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04 // indirect github.com/nrdcg/auroradns v1.1.0 // indirect github.com/nrdcg/desec v0.6.0 // indirect @@ -300,7 +322,7 @@ require ( github.com/softlayer/softlayer-go v1.0.6 // indirect github.com/softlayer/xmlrpc v0.0.0-20200409220501-5f089df7cb7e // indirect github.com/spf13/cast v1.3.1 // indirect - github.com/spf13/cobra v1.2.1 // indirect + github.com/spf13/cobra v1.4.0 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/stretchr/objx v0.5.0 // indirect github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.490 // indirect @@ -325,10 +347,13 @@ require ( go.etcd.io/etcd/client/pkg/v3 v3.5.4 // indirect go.etcd.io/etcd/client/v3 v3.5.4 // indirect go.opencensus.io v0.23.0 // indirect - go.uber.org/atomic v1.7.0 // indirect - go.uber.org/multierr v1.6.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.11.1 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.33.0 // indirect + go.opentelemetry.io/proto/otlp v0.19.0 // indirect + go.uber.org/atomic v1.9.0 // indirect + go.uber.org/multierr v1.8.0 // indirect go.uber.org/ratelimit v0.2.0 // indirect - go.uber.org/zap v1.18.1 // indirect + go.uber.org/zap v1.21.0 // indirect go4.org/intern v0.0.0-20211027215823-ae77deb06f29 // indirect go4.org/unsafe/assume-no-moving-gc v0.0.0-20220617031537-928513b29760 // indirect golang.org/x/crypto v0.1.0 // indirect @@ -338,10 +363,10 @@ require ( golang.org/x/sys v0.1.0 // indirect golang.org/x/term v0.1.0 // indirect golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect - google.golang.org/api v0.44.0 // indirect + google.golang.org/api v0.57.0 // indirect google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto v0.0.0-20211021150943-2b146023228c // indirect - google.golang.org/protobuf v1.28.0 // indirect + google.golang.org/genproto v0.0.0-20220502173005-c8bf987b8c21 // indirect + google.golang.org/protobuf v1.28.1 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/ini.v1 v1.66.6 // indirect gopkg.in/ns1/ns1-go.v2 v2.6.5 // indirect @@ -350,11 +375,12 @@ require ( gopkg.in/yaml.v2 v2.4.0 // indirect howett.net/plist v0.0.0-20181124034731-591f970eefbb // indirect inet.af/netaddr v0.0.0-20220617031823-097006376321 // indirect - k8s.io/klog/v2 v2.10.0 // indirect - k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e // indirect + k8s.io/klog/v2 v2.70.1 // indirect + k8s.io/kube-openapi v0.0.0-20220803162953-67bda5d908f1 // indirect nhooyr.io/websocket v1.8.7 // indirect - sigs.k8s.io/structured-merge-diff/v4 v4.1.2 // indirect - sigs.k8s.io/yaml v1.2.0 // indirect + sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect + sigs.k8s.io/yaml v1.3.0 // indirect ) // Containous forks @@ -371,3 +397,5 @@ replace github.com/jaguilar/vt100 => github.com/tonistiigi/vt100 v0.0.0-20190402 // ambiguous import: found package github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/http in multiple modules // tencentcloud uses monorepo with multimodule but the go.mod files are incomplete. exclude github.com/tencentcloud/tencentcloud-sdk-go v3.0.83+incompatible + +// replace github.com/go-logr/logr => github.com/go-logr/logr v0.4.0 diff --git a/go.sum b/go.sum index 25a719ffa..20e5707d6 100644 --- a/go.sum +++ b/go.sum @@ -24,8 +24,15 @@ cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKP cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= -cloud.google.com/go v0.81.0 h1:at8Tk2zUz63cLPR0JPWm5vp77pEZmzxEQBEfRKn1VV8= cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= +cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY= +cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM= +cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY= +cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ= +cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI= +cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4= +cloud.google.com/go v0.97.0 h1:3DXvAyifywvq64LfkKaMOmkWPS1CikIQdMe2lY9vxU8= +cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= @@ -70,8 +77,9 @@ github.com/Azure/go-autorest/autorest v0.10.0/go.mod h1:/FALq9T/kS7b5J5qsQ+RSTUd github.com/Azure/go-autorest/autorest v0.11.1/go.mod h1:JFgpikqFJ/MleTTxwepExTKnFUKKszPS8UavbQYUMuw= github.com/Azure/go-autorest/autorest v0.11.12/go.mod h1:eipySxLmqSyC5s5k1CLupqet0PSENBEDP93LQ9a8QYw= github.com/Azure/go-autorest/autorest v0.11.18/go.mod h1:dSiJPy22c3u0OtOKDNttNgqpNFY/GeWa7GH/Pz56QRA= -github.com/Azure/go-autorest/autorest v0.11.24 h1:1fIGgHKqVm54KIPT+q8Zmd1QlVsmHqeUGso5qm2BqqE= github.com/Azure/go-autorest/autorest v0.11.24/go.mod h1:G6kyRlFnTuSbEYkQGawPfsCswgme4iYf6rfSKUDzbCc= +github.com/Azure/go-autorest/autorest v0.11.27 h1:F3R3q42aWytozkV8ihzcgMO4OA4cuqr3bNlsEuF6//A= +github.com/Azure/go-autorest/autorest v0.11.27/go.mod h1:7l8ybrIdUmGqZMTD0sRtAr8NvbHjfofbf8RSP2q7w7U= github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0= github.com/Azure/go-autorest/autorest/adal v0.8.0/go.mod h1:Z6vX6WXXuyieHAXwMj0S6HY6e6wcHn37qQMBQlvY3lc= github.com/Azure/go-autorest/autorest/adal v0.8.1/go.mod h1:ZjhuQClTqx435SRJ2iMlOxPYt3d2C/T/7TiQCVZSn3Q= @@ -79,8 +87,9 @@ github.com/Azure/go-autorest/autorest/adal v0.8.2/go.mod h1:ZjhuQClTqx435SRJ2iMl github.com/Azure/go-autorest/autorest/adal v0.9.0/go.mod h1:/c022QCutn2P7uY+/oQWWNcK9YU+MH96NgK+jErpbcg= github.com/Azure/go-autorest/autorest/adal v0.9.5/go.mod h1:B7KF7jKIeC9Mct5spmyCB/A8CG/sEz1vwIRGv/bbw7A= github.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M= -github.com/Azure/go-autorest/autorest/adal v0.9.18 h1:kLnPsRjzZZUF3K5REu/Kc+qMQrvuza2bwSnNdhmzLfQ= github.com/Azure/go-autorest/autorest/adal v0.9.18/go.mod h1:XVVeme+LZwABT8K5Lc3hA4nAe8LDBVle26gTrguhhPQ= +github.com/Azure/go-autorest/autorest/adal v0.9.20 h1:gJ3E98kMpFB1MFqQCvA1yFab8vthOeD4VlFRQULxahg= +github.com/Azure/go-autorest/autorest/adal v0.9.20/go.mod h1:XVVeme+LZwABT8K5Lc3hA4nAe8LDBVle26gTrguhhPQ= github.com/Azure/go-autorest/autorest/azure/auth v0.4.2/go.mod h1:90gmfKdlmKgfjUpnCEpOJzsUEjrWDSLwHIG73tSXddM= github.com/Azure/go-autorest/autorest/azure/auth v0.5.11 h1:P6bYXFoao05z5uhOQzbC3Qd8JqF3jUoocoTeIxkp2cA= github.com/Azure/go-autorest/autorest/azure/auth v0.5.11/go.mod h1:84w/uV8E37feW2NCJ08uT9VBfjfUHpgLVnG2InYD6cg= @@ -95,8 +104,9 @@ github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxB github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= github.com/Azure/go-autorest/autorest/mocks v0.3.0/go.mod h1:a8FDP3DYzQ4RYfVAxAN3SVSiiO77gL2j2ronKKP0syM= github.com/Azure/go-autorest/autorest/mocks v0.4.0/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= -github.com/Azure/go-autorest/autorest/mocks v0.4.1 h1:K0laFcLE6VLTOwNgSxaGbUcLPuGXlNkbVvq4cW4nIHk= github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= +github.com/Azure/go-autorest/autorest/mocks v0.4.2 h1:PGN4EDXnuQbojHbU0UWoNvmu9AGVwYHG9/fkDYhtAfw= +github.com/Azure/go-autorest/autorest/mocks v0.4.2/go.mod h1:Vy7OitM9Kei0i1Oj+LvyAWMXJHeKH1MVlzFugfVrmyU= github.com/Azure/go-autorest/autorest/to v0.3.0/go.mod h1:MgwOyqaIuKdG4TL/2ywSsIWKAfJfgHDo8ObuUk3t5sA= github.com/Azure/go-autorest/autorest/to v0.4.0 h1:oXVqrxakqqV1UZdSazDOPOLvOIz+XA683u8EctwboHk= github.com/Azure/go-autorest/autorest/to v0.4.0/go.mod h1:fE8iZBn7LQR7zH/9XU2NcPR4o9jEImooCeWJcYV/zLE= @@ -175,8 +185,10 @@ github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAE github.com/OpenDNS/vegadns2client v0.0.0-20180418235048-a3fa4a771d87 h1:xPMsUicZ3iosVPSIP7bW5EcGUzjiiMl1OYTe14y/R24= github.com/OpenDNS/vegadns2client v0.0.0-20180418235048-a3fa4a771d87/go.mod h1:iGLljf5n9GjT6kc0HBvyI1nOKnGQbNB66VzSNbK5iks= github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d h1:UrqY+r/OJnIp5u0s1SbQ8dVfLCZJsnvazdBP5hS4iRs= github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ= @@ -480,6 +492,7 @@ github.com/cpu/goacmedns v0.1.1/go.mod h1:MuaouqEhPAHxsbqjgnck5zeghuwBP1dLnPoobe github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.11 h1:07n33Z8lZxZ2qwegKbObQohDhXDQxiMMz1NOUGYlesw= @@ -608,6 +621,8 @@ github.com/elazarl/goproxy v0.0.0-20191011121108-aa519ddbe484/go.mod h1:Ro8st/El github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2/go.mod h1:gNh8nYJoAm43RfaxurUnxr+N1PwuFV3ZMl/efxlIlY8= github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= +github.com/emicklei/go-restful/v3 v3.8.0 h1:eCZ8ulSerjdAiaNpF7GxXIE7ZCMo1moN1qX+S609eVw= +github.com/emicklei/go-restful/v3 v3.8.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= @@ -624,8 +639,9 @@ github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ= github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= -github.com/evanphx/json-patch v4.11.0+incompatible h1:glyUF9yIYtMHzn8xaKw5rMhdWcwsYV8dZHIq5567/xs= github.com/evanphx/json-patch v4.11.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/evanphx/json-patch v4.12.0+incompatible h1:4onqiflcdA9EOZ4RxV643DvftH5pOlLGNtQ5lPWQu84= +github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/exoscale/egoscale v0.90.0 h1:DZBXVU3iHqu5Ju5lQ5jWVlPo0IpI98SUo8Aa1UQVrmo= github.com/exoscale/egoscale v0.90.0/go.mod h1:wyXE5zrnFynMXA0jMhwQqSe24CfUhmBk2WI5wFZcq6Y= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= @@ -695,17 +711,24 @@ github.com/go-logfmt/logfmt v0.5.1 h1:otpy5pqBCBZ1ng9RQ0dPu4PN7ba75Y/aA+UpowDyNV github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= -github.com/go-logr/logr v0.4.0 h1:K7/B1jt6fIBQVd4Owv2MqGQClcgf0R266+7C/QjRcLc= 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.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= +github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +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= github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM= github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0= github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg= github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc= github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= +github.com/go-openapi/jsonreference v0.19.5 h1:1WJP/wi4OjB4iV8KVbH73rQaoialJrqv8gitZLxGLtM= github.com/go-openapi/jsonreference v0.19.5/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg= github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc= github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo= @@ -713,6 +736,7 @@ github.com/go-openapi/spec v0.19.5/go.mod h1:Hm2Jr4jv8G1ciIAo+frC/Ft+rR2kQDh8JHK github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I= github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.19.14 h1:gm3vOOXfiuw5i9p5N9xJvfjvuofpyvLA9Wr6QfK5Fng= github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= @@ -784,8 +808,9 @@ github.com/golang-jwt/jwt/v4 v4.1.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzw github.com/golang-jwt/jwt/v4 v4.2.0 h1:besgBTC8w8HjP6NzQdxwKH9Z5oQMZ24ThTrHp3cZ8eU= github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/glog v1.0.0 h1:nfP3RFugxnNRyKgeWd4oI1nYvXpxrx8ck8ZrcizshdQ= +github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -826,6 +851,7 @@ github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golangci/lint-1 v0.0.0-20181222135242-d2cdd8c08219/go.mod h1:/X8TswGSh1pIozq4ZwCfxS0WA5JGXguxk94ar/4c87Y= github.com/gomodule/redigo v1.8.2/go.mod h1:P9dn9mFrCBvWhGE1wpxx6fgq7BAeLBk+UUUzlpkBYO0= @@ -836,6 +862,8 @@ github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4= github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= github.com/google/certificate-transparency-go v1.0.21 h1:Yf1aXowfZ2nuboBsg7iYGLmwsOARdV86pfH3g95wXmE= github.com/google/certificate-transparency-go v1.0.21/go.mod h1:QeJfpSbVSfYc7RgB3gJFj9cbuQMMchQxrWXz8Ruopmg= +github.com/google/gnostic v0.5.7-v3refs h1:FhTMOKj2VhjpouxvWJAV1TL304uMlb9zcDqkl6cEI54= +github.com/google/gnostic v0.5.7-v3refs/go.mod h1:73MKFl6jIHelAJNaBGFzt3SPtZULs9dYrGFt8OiIsHQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -849,8 +877,8 @@ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= -github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= -github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-containerregistry v0.0.0-20191015185424-71da34e4d9b3/go.mod h1:ZXFeSndFcK4vB1NR4voH1Zm38K7ViUNiYtfIBDxrwf0= github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= github.com/google/go-github/v28 v28.1.1 h1:kORf5ekX5qwXO2mGzXXOjMe/g6ap8ahVe0sBEulhSxo= @@ -869,6 +897,7 @@ github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/ github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= @@ -882,6 +911,8 @@ github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLe 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-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= @@ -895,15 +926,15 @@ github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+ github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY= github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= -github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/googleapis/gax-go/v2 v2.1.0 h1:6DWmvNpomjL1+3liNSZbVns3zsYzzCjm6pRBO1tLeso= +github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= github.com/googleapis/gnostic v0.0.0-20170426233943-68f4ded48ba9/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= github.com/googleapis/gnostic v0.2.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= github.com/googleapis/gnostic v0.2.2/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg= github.com/googleapis/gnostic v0.5.1/go.mod h1:6U4PtQXGIEt/Z3h5MAT7FNofLnw9vXk2cUuW7uA/OeU= -github.com/googleapis/gnostic v0.5.5 h1:9fHAtK0uDfpveeqqo1hkEZJcFvYXAiCN3UutL8F9xHw= github.com/googleapis/gnostic v0.5.5/go.mod h1:7+EbHbldMins07ALC74bsA81Ovc97DwqyJO1AENw9kA= github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8= github.com/gophercloud/gophercloud v0.15.1-0.20210202035223-633d73521055/go.mod h1:wRtmUelyIIv3CSSDI47aUwbs075O6i+LY+pXsKCBsb4= @@ -929,13 +960,16 @@ github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:Fecb github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-middleware v1.2.0/go.mod h1:mJzapYve32yjrKlk9GbyCZHuPgZsrbyIbyKhSzOpg6s= -github.com/grpc-ecosystem/go-grpc-middleware v1.2.2 h1:FlFbCRLd5Jr4iYXZufAvgWN6Ao0JrI5chLINnUXDDr0= github.com/grpc-ecosystem/go-grpc-middleware v1.2.2/go.mod h1:EaizFBKfUKtMIF5iaDEhniwNedqGo9FuLFzppDr3uwI= +github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 h1:+9834+KizmvFV7pXQGSXQTsaWhq2GjuNUt0aUU0YBYw= +github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw= github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0 h1:BZHcxBETFHIdVyhyEfOvn/RdU/QGdLI4y34qQGjGWO0= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0/go.mod h1:hgWBS7lorOAVIJEQMi4ZsPv9hVvWI6+ch50m39Pf2Ks= github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645 h1:MJG/KsmcqMwFAkh8mTnAwhyKoB+sTAnY4CACC110tbU= github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645/go.mod h1:6iZfnjpejD4L/4DwD7NryNaJyCQdzwWwH2MWhCA90Kw= github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw= @@ -1249,8 +1283,9 @@ github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0Q github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.4/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= -github.com/magiconair/properties v1.8.5 h1:b6kJs+EmPFMYGkow9GiUyCyOvIwYetYJ3fSaWak/Gls= github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= +github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo= +github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/mailgun/multibuf v0.1.2 h1:QE9kE27lK6LFZB4aYNVtUPlWVHVCT0zpgUr2uoq/+jk= github.com/mailgun/multibuf v0.1.2/go.mod h1:E+sUhIy69qgT6EM57kCPdUTlHnjTuxQBO/yf6af9Hes= github.com/mailgun/timetools v0.0.0-20141028012446-7e6055773c51 h1:Kg/NPZLLC3aAFr1YToMs98dbCdhootQ1hZIvZU28hAQ= @@ -1326,8 +1361,9 @@ github.com/mimuret/golang-iij-dpf v0.7.1/go.mod h1:IXWYcQVIHYzuM+W7kDWX0mseHDfUo github.com/mistifyio/go-zfs v2.1.2-0.20190413222219-f784269be439+incompatible/go.mod h1:8AuVvqP/mXw1px98n46wfvcGfQ4ci2FwoAjKYxuo3Z4= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI= -github.com/mitchellh/copystructure v1.0.0 h1:Laisrj+bAB6b/yJwB5Bt3ITZhGJdqmxquMKeZ+mmkFQ= github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= +github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= +github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= @@ -1358,8 +1394,9 @@ github.com/mitchellh/osext v0.0.0-20151018003038-5e2d6d41470f/go.mod h1:OkQIRizQ github.com/mitchellh/pointerstructure v1.0.0 h1:ATSdz4NWrmWPOF1CeCBU4sMCno2hgqdbSrRPFWQSVZI= github.com/mitchellh/pointerstructure v1.0.0/go.mod h1:k4XwG94++jLVsSiTxo7qdIfXA9pj9EAeo0QsNNJOLZ8= github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= -github.com/mitchellh/reflectwalk v1.0.1 h1:FVzMWA5RllMAKIdUSC8mdWo3XtwoecrH79BY70sEEpE= github.com/mitchellh/reflectwalk v1.0.1/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= +github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/moby/buildkit v0.8.2-0.20210401015549-df49b648c8bf h1:dHwWBX8OhYb69qVcT27rFSwzKsn4CRbg0HInQyVh33c= github.com/moby/buildkit v0.8.2-0.20210401015549-df49b648c8bf/go.mod h1:GJcrUlTGFAPlEmPQtbrTsJYn+cy+Jwl7vTZS7jYAoow= github.com/moby/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg= @@ -1393,6 +1430,7 @@ github.com/mreiferson/go-httpclient v0.0.0-20160630210159-31f0106b4474/go.mod h1 github.com/mrunalp/fileutils v0.0.0-20200520151820-abd8a0e76976/go.mod h1:x8F1gnqOkIEiO4rqoeEEEqQbo7HjGMTvyoq3gej4iT0= github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ= github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f h1:KUppIJq7/+SVif2QVs3tOP0zanoHgBEVAwHxUSIzRqU= @@ -1456,6 +1494,7 @@ 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.1.4 h1:GNapqRSid3zijZ9H77KrgVG4/8KqiyRsxcSxe+7ApXY= github.com/onsi/gomega v0.0.0-20151007035656-2152b45fa28a/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= 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= @@ -1751,8 +1790,9 @@ github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tL github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI= github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo= -github.com/spf13/cobra v1.2.1 h1:+KmjbUw1hriSNMF55oPrkZcb27aECyrj8V2ytv7kWDw= github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk= +github.com/spf13/cobra v1.4.0 h1:y+wJpx64xcgO1V+RcnwW0LEHxTKRi2ZDPSBjWnrg88Q= +github.com/spf13/cobra v1.4.0/go.mod h1:Wo4iy3BUC+X2Fybo0PDqwJIv3dNRiZLHQymsfxlB84g= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= @@ -1956,26 +1996,60 @@ go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= +go.opentelemetry.io/collector/pdata v0.64.1 h1:8E06uHr0nnenGftFwhwdenA88QhVnF4dJam+qVXgdVg= +go.opentelemetry.io/collector/pdata v0.64.1/go.mod h1:IzvXUGQml2mrnvdb8zIlEW3qQs9oFLdD2hLwJdZ+pek= +go.opentelemetry.io/otel v1.11.1 h1:4WLLAmcfkmDk2ukNXJyq3/kiz/3UzCaYq6PskJsaou4= +go.opentelemetry.io/otel v1.11.1/go.mod h1:1nNhXBbWSD0nsL38H6btgnFN2k4i0sNLHNNMZMSbUGE= +go.opentelemetry.io/otel/bridge/opentracing v1.11.1 h1:/ZBsgjXWUpiZ5M9zm+Ft3kuDUGErIGcEJbKRIsFN6jA= +go.opentelemetry.io/otel/bridge/opentracing v1.11.1/go.mod h1:vw9hN4H+G0ek+XQtxP+Mm1McLcmdx2FXHNrWn2bBqxU= +go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.11.1 h1:X2GndnMCsUPh6CiY2a+frAbNsXaPLbB0soHRYhAZ5Ig= +go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.11.1/go.mod h1:i8vjiSzbiUC7wOQplijSXMYUpNM93DtlS5CbUT+C6oQ= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.33.0 h1:OT/UjHcjog4A1s1UMCtyehIKS+vpjM5Du0r7KGsH6TE= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.33.0/go.mod h1:0XctNDHEWmiSDIU8NPbJElrK05gBJFcYlGP4FMGo4g4= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.33.0 h1:1SVtGtRsNyGgv1fRfNXfh+sJowIwzF0gkf+61lvTgdg= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.33.0/go.mod h1:ryB27ubOBXsiqfh6MwtSdx5knzbSZtjvPnMMmt3AykQ= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.33.0 h1:NoG4v01cdLZfOeNGBQmSe4f4SeP+fx8I/0qzRgTKsGI= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.33.0/go.mod h1:6anbDXBcTp3Qit87pfFmT0paxTJ8sWRccTNYVywN/H8= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.11.1 h1:MEQNafcNCB0uQIti/oHgU7CZpUMYQ7qigBwMVKycHvc= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.11.1/go.mod h1:19O5I2U5iys38SsmT2uDJja/300woyzE1KPIQxEUBUc= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.11.1 h1:LYyG/f1W/jzAix16jbksJfMQFpOH/Ma6T639pVPMgfI= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.11.1/go.mod h1:QrRRQiY3kzAoYPNLP0W/Ikg0gR6V3LMc+ODSxr7yyvg= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.11.1 h1:tFl63cpAAcD9TOU6U8kZU7KyXuSRYAZlbx1C61aaB74= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.11.1/go.mod h1:X620Jww3RajCJXw/unA+8IRTgxkdS7pi+ZwK9b7KUJk= +go.opentelemetry.io/otel/metric v0.33.0 h1:xQAyl7uGEYvrLAiV/09iTJlp1pZnQ9Wl793qbVvED1E= +go.opentelemetry.io/otel/metric v0.33.0/go.mod h1:QlTYc+EnYNq/M2mNk1qDDMRLpqCOj2f/r5c7Fd5FYaI= +go.opentelemetry.io/otel/sdk v1.11.1 h1:F7KmQgoHljhUuJyA+9BiU+EkJfyX5nVVF4wyzWZpKxs= +go.opentelemetry.io/otel/sdk v1.11.1/go.mod h1:/l3FE4SupHJ12TduVjUkZtlfFqDCQJlOlithYrdktys= +go.opentelemetry.io/otel/sdk/metric v0.33.0 h1:oTqyWfksgKoJmbrs2q7O7ahkJzt+Ipekihf8vhpa9qo= +go.opentelemetry.io/otel/sdk/metric v0.33.0/go.mod h1:xdypMeA21JBOvjjzDUtD0kzIcHO/SPez+a8HOzJPGp0= +go.opentelemetry.io/otel/trace v1.11.1 h1:ofxdnzsNrGBYXbP7t7zpUK281+go5rF7dvdIZXF8gdQ= +go.opentelemetry.io/otel/trace v1.11.1/go.mod h1:f/Q9G7vzk5u91PhbmKbg1Qn0rzH1LJ4vbPHFGkTPtOk= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +go.opentelemetry.io/proto/otlp v0.19.0 h1:IVN6GR+mhC4s5yfcTbmzHYODqvWAp3ZedA2SJPI1Nnw= +go.opentelemetry.io/proto/otlp v0.19.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= -go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/goleak v1.1.10 h1:z+mqJhf6ss6BSfSM671tgKyZBFPTTJM+HLxnhPC3wu0= +go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= +go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= +go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= +go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= -go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/multierr v1.8.0 h1:dg6GjLku4EH+249NNmoIciG9N/jURbDG+pFlTkhzIC8= +go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= go.uber.org/ratelimit v0.2.0 h1:UQE2Bgi7p2B85uP5dC2bbRtig0C+OeNRnNEafLjsLPA= go.uber.org/ratelimit v0.2.0/go.mod h1:YYBV4e4naJvhpitQrWJu1vCpgB7CboMe0qhltKt6mUg= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= -go.uber.org/zap v1.18.1 h1:CSUJ2mjFszzEWt4CdKISEuChVIXGBn3lAPwkRGyVrc4= go.uber.org/zap v1.18.1/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= +go.uber.org/zap v1.21.0 h1:WefMeulhovoZ2sYXz7st6K0sLj7bBhpiFaud4r4zST8= +go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw= go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE= go4.org/intern v0.0.0-20211027215823-ae77deb06f29 h1:UXLjNohABv4S58tHmeuIZDO6e3mHpW2Dx33gaNt03LE= go4.org/intern v0.0.0-20211027215823-ae77deb06f29/go.mod h1:cS2ma+47FKrLPdXFpr7CuxiTW3eyJbWew4qx0qtQWDA= @@ -2139,6 +2213,7 @@ golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLd golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= +golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210520170846-37e1c6afe023/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= @@ -2167,6 +2242,10 @@ golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1 h1:lxqLZaMad/dJHMFZH0NiNpiEZI/nhgWhe4wgzpE+MuA= golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= @@ -2297,12 +2376,16 @@ golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210426230700-d19ff857e887/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -2341,8 +2424,8 @@ golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxb golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20220224211638-0e9765cccd65 h1:M73Iuj3xbbb9Uk1DYhzydthsj6oOd6l9bpuFcNoUvTs= -golang.org/x/time v0.0.0-20220224211638-0e9765cccd65/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20220609170525-579cf78fd858 h1:Dpdu/EMxGMFgq0CeYMh4fazTD2vtlZRYE7wyynxJb9U= +golang.org/x/time v0.0.0-20220609170525-579cf78fd858/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -2416,6 +2499,8 @@ golang.org/x/tools v0.0.0-20210114065538-d78b04bdf963/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.2.0 h1:G6AHpWxTMGY1KyEYoAQ5WTtIekUUvDNjan3ugu60JvE= @@ -2458,8 +2543,15 @@ google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34q google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= -google.golang.org/api v0.44.0 h1:URs6qR1lAxDsqWITsQXI4ZkGiYJ5dHtRNiCpfs2OeKA= google.golang.org/api v0.44.0/go.mod h1:EBOGZqzyhtvMDoxwS97ctnh0zUmYY6CxqXsc1AvkYD8= +google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo= +google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4= +google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw= +google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU= +google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k= +google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= +google.golang.org/api v0.57.0 h1:4t9zuDlHLcIx0ZEhmXEeFVCRsiOgpgn2QOH9N0MNjPI= +google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -2526,9 +2618,25 @@ google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= +google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A= google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= -google.golang.org/genproto v0.0.0-20211021150943-2b146023228c h1:FqrtZMB5Wr+/RecOM3uPJNPfWR8Upb5hAPnt7PU6i4k= +google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24= +google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= +google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= +google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= +google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= +google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w= +google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20211021150943-2b146023228c/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20220502173005-c8bf987b8c21 h1:hrbNEivu7Zn1pxvHk6MBrq9iE22woVILTHqexqBxe6I= +google.golang.org/genproto v0.0.0-20220502173005-c8bf987b8c21/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= google.golang.org/grpc v0.0.0-20160317175043-d3ddb4469d5a/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio= @@ -2559,11 +2667,18 @@ google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA5 google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= +google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= google.golang.org/grpc v1.41.0/go.mod h1:U3l9uK9J0sini8mHphKoXyaqDA/8VyGnDee1zzIUK6k= -google.golang.org/grpc v1.46.0 h1:oCjezcn6g6A75TGoKYBPgKmVBLexhYLM6MebdrPApP8= +google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= +google.golang.org/grpc v1.50.1 h1:DS/BukOZWp8s6p4Dt/tOaJaTQyPyOoCcrjroHuCeLzY= +google.golang.org/grpc v1.50.1/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= +google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/grpc/examples v0.0.0-20201130180447-c456688b1860/go.mod h1:Ly7ZA/ARzg8fnPU9TyZIxoz33sEUuWX7txiqs8lPTgE= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= @@ -2578,8 +2693,9 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 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 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw= google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= +google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/DataDog/dd-trace-go.v1 v1.43.1 h1:Dez4VzRQWAI5YXJRBx58BiC0gONGuW/oY4l8fWKzOXY= gopkg.in/DataDog/dd-trace-go.v1 v1.43.1/go.mod h1:YL9g+nlUY7ByCffD5pDytAqy99GNbytRV0EBpKuldM4= gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U= @@ -2677,10 +2793,12 @@ k8s.io/api v0.20.4/go.mod h1:++lNL1AJMkDymriNniQsWRkMDzRaX2Y/POTUi8yvqYQ= k8s.io/api v0.20.6/go.mod h1:X9e8Qag6JV/bL5G6bU8sdVRltWKmdHsFUGS3eVndqE8= k8s.io/api v0.21.0/go.mod h1:+YbrhBBGgsxbF6o6Kj4KJPJnBmAKuXDeS3E18bgHNVU= k8s.io/api v0.21.3/go.mod h1:hUgeYHUbBp23Ue4qdX9tR8/ANi/g3ehylAqDn9NWVOg= -k8s.io/api v0.22.1 h1:ISu3tD/jRhYfSW8jI/Q1e+lRxkR7w9UwQEZ7FgslrwY= k8s.io/api v0.22.1/go.mod h1:bh13rkTp3F1XEaLGykbyRD2QaTTzPm0e/BMd8ptFONY= -k8s.io/apiextensions-apiserver v0.21.3 h1:+B6biyUWpqt41kz5x6peIsljlsuwvNAp/oFax/j2/aY= +k8s.io/api v0.25.0 h1:H+Q4ma2U/ww0iGB78ijZx6DRByPz6/733jIuFpX70e0= +k8s.io/api v0.25.0/go.mod h1:ttceV1GyV1i1rnmvzT3BST08N6nGt+dudGrquzVQWPk= k8s.io/apiextensions-apiserver v0.21.3/go.mod h1:kl6dap3Gd45+21Jnh6utCx8Z2xxLm8LGDkprcd+KbsE= +k8s.io/apiextensions-apiserver v0.25.0 h1:CJ9zlyXAbq0FIW8CD7HHyozCMBpDSiH7EdrSTCZcZFY= +k8s.io/apiextensions-apiserver v0.25.0/go.mod h1:3pAjZiN4zw7R8aZC5gR0y3/vCkGlAjCazcg1me8iB/E= k8s.io/apimachinery v0.0.0-20180904193909-def12e63c512/go.mod h1:ccL7Eh7zubPUSh9A3USN90/OzHNSVN6zxzde07TDCL0= k8s.io/apimachinery v0.0.0-20190806215851-162a2dabc72f/go.mod h1:+ntn62igV2hyNj7/0brOvXSMONE2KxcePkSxK7/9FFQ= k8s.io/apimachinery v0.0.0-20191004115801-a2eda9f80ab8/go.mod h1:llRdnznGEAqC3DcNm6yEj472xaFVfLM7hnYofMb12tQ= @@ -2690,8 +2808,9 @@ k8s.io/apimachinery v0.20.4/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRp k8s.io/apimachinery v0.20.6/go.mod h1:ejZXtW1Ra6V1O5H8xPBGz+T3+4gfkTCeExAHKU57MAc= k8s.io/apimachinery v0.21.0/go.mod h1:jbreFvJo3ov9rj7eWT7+sYiRx+qZuCYXwWT1bcDswPY= k8s.io/apimachinery v0.21.3/go.mod h1:H/IM+5vH9kZRNJ4l3x/fXP/5bOPJaVP/guptnZPeCFI= -k8s.io/apimachinery v0.22.1 h1:DTARnyzmdHMz7bFWFDDm22AM4pLWTQECMpRTFu2d2OM= k8s.io/apimachinery v0.22.1/go.mod h1:O3oNtNadZdeOMxHFVxOreoznohCpy0z6mocxbZr7oJ0= +k8s.io/apimachinery v0.25.0 h1:MlP0r6+3XbkUG2itd6vp3oxbtdQLQI94fD5gCS+gnoU= +k8s.io/apimachinery v0.25.0/go.mod h1:qMx9eAk0sZQGsXGu86fab8tZdffHbwUfsvzqKn4mfB0= k8s.io/apiserver v0.20.1/go.mod h1:ro5QHeQkgMS7ZGpvf4tSMx6bBOgPfE+f52KwvXfScaU= k8s.io/apiserver v0.20.4/go.mod h1:Mc80thBKOyy7tbvFtB4kJv1kbdD0eIH8k8vianJcbFM= k8s.io/apiserver v0.20.6/go.mod h1:QIJXNt6i6JB+0YQRNcS0hdRHJlMhflFmsBDeSgT1r8Q= @@ -2704,8 +2823,9 @@ k8s.io/client-go v0.20.4/go.mod h1:LiMv25ND1gLUdBeYxBIwKpkSC5IsozMMmOOeSJboP+k= k8s.io/client-go v0.20.6/go.mod h1:nNQMnOvEUEsOzRRFIIkdmYOjAZrC8bgq0ExboWSU1I0= k8s.io/client-go v0.21.0/go.mod h1:nNBytTF9qPFDEhoqgEPaarobC8QPae13bElIVHzIglA= k8s.io/client-go v0.21.3/go.mod h1:+VPhCgTsaFmGILxR/7E1N0S+ryO010QBeNCv5JwRGYU= -k8s.io/client-go v0.22.1 h1:jW0ZSHi8wW260FvcXHkIa0NLxFBQszTlhiAVsU5mopw= k8s.io/client-go v0.22.1/go.mod h1:BquC5A4UOo4qVDUtoc04/+Nxp1MeHcVc1HJm1KmG8kk= +k8s.io/client-go v0.25.0 h1:CVWIaCETLMBNiTUta3d5nzRbXvY5Hy9Dpl+VvREpu5E= +k8s.io/client-go v0.25.0/go.mod h1:lxykvypVfKilxhTklov0wz1FoaUZ8X4EwbhS6rpRfN8= k8s.io/code-generator v0.21.3/go.mod h1:K3y0Bv9Cz2cOW2vXUrNZlFbflhuPvuadW6JdnN6gGKo= k8s.io/code-generator v0.22.0/go.mod h1:eV77Y09IopzeXOJzndrDyCI88UBok2h6WxAlBwpxa+o= k8s.io/component-base v0.20.1/go.mod h1:guxkoJnNoh8LNrbtiQOlyp2Y2XFCZQmrcg2n/DeYNLk= @@ -2731,23 +2851,26 @@ k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= k8s.io/klog/v2 v2.4.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= k8s.io/klog/v2 v2.8.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec= k8s.io/klog/v2 v2.9.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec= -k8s.io/klog/v2 v2.10.0 h1:R2HDMDJsHVTHA2n4RjwbeYXdOcBymXdX/JRb1v0VGhE= k8s.io/klog/v2 v2.10.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec= +k8s.io/klog/v2 v2.70.1 h1:7aaoSdahviPmR+XkS7FyxlkkXs6tHISSG03RxleQAVQ= +k8s.io/klog/v2 v2.70.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= k8s.io/kube-openapi v0.0.0-20180731170545-e3762e86a74c/go.mod h1:BXM9ceUBTj2QnfH2MK1odQs778ajze1RxcmP6S8RVVc= k8s.io/kube-openapi v0.0.0-20190709113604-33be087ad058/go.mod h1:nfDlWeOsu3pUf4yWGL+ERqohP4YsZcBJXWMK+gkzOA4= k8s.io/kube-openapi v0.0.0-20190816220812-743ec37842bf/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E= k8s.io/kube-openapi v0.0.0-20201113171705-d219536bb9fd/go.mod h1:WOJ3KddDSol4tAGcJo0Tvi+dK12EcqSLqcWsryKMpfM= k8s.io/kube-openapi v0.0.0-20210305001622-591a79e4bda7/go.mod h1:wXW5VT87nVfh/iLV8FpR2uDvrFyomxbtb1KivDbvPTE= -k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e h1:KLHHjkdQFomZy8+06csTWZ0m1343QqxZhR2LJ1OxCYM= k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e/go.mod h1:vHXdDvt9+2spS2Rx9ql3I8tycm3H9FDfdUoIuKCefvw= +k8s.io/kube-openapi v0.0.0-20220803162953-67bda5d908f1 h1:MQ8BAZPZlWk3S9K4a9NCkIFQtZShWqoha7snGixVgEA= +k8s.io/kube-openapi v0.0.0-20220803162953-67bda5d908f1/go.mod h1:C/N6wCaBHeBHkHUesQOQy2/MZqGgMAFPqGsGQLdbZBU= k8s.io/kubernetes v1.11.10/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk= k8s.io/kubernetes v1.13.0/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk= k8s.io/utils v0.0.0-20190801114015-581e00157fb1/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20210707171843-4b05e18ac7d9/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= k8s.io/utils v0.0.0-20210722164352-7f3ee0f31471/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= -k8s.io/utils v0.0.0-20210820185131-d34e5cb4466e h1:ldQh+neBabomh7+89dTpiFAB8tGdfVmuIzAHbvtl+9I= k8s.io/utils v0.0.0-20210820185131-d34e5cb4466e/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed h1:jAne/RjBTyawwAy0utX5eqigAwz/lQhTmy+Hr/Cpue4= +k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= launchpad.net/gocheck v0.0.0-20140225173054-000000000087/go.mod h1:hj7XX3B/0A+80Vse0e+BUHsHMTEhd0O4cpUHr/e/BUM= mvdan.cc/xurls/v2 v2.1.0 h1:KaMb5GLhlcSX+e+qhbRJODnUUBvlw01jt4yrjFIHAuA= mvdan.cc/xurls/v2 v2.1.0/go.mod h1:5GrSd9rOnKOpZaji1OZLYL/yeAAtGDlo/cFe+8K5n8E= @@ -2765,15 +2888,19 @@ sigs.k8s.io/controller-runtime v0.9.6/go.mod h1:q6PpkM5vqQubEKUKOM6qr06oXGzOBcCb sigs.k8s.io/controller-tools v0.6.2/go.mod h1:oaeGpjXn6+ZSEIQkUe/+3I40PNiDYp9aeawbt3xTgJ8= sigs.k8s.io/gateway-api v0.4.0 h1:07IJkTt21NetZTHtPKJk2I4XIgDN4BAlTIq1wK7V11o= sigs.k8s.io/gateway-api v0.4.0/go.mod h1:r3eiNP+0el+NTLwaTfOrCNXy8TukC+dIM3ggc+fbNWk= +sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 h1:iXTIw73aPyC+oRdyqqvVJuloN1p0AC/kzH07hu3NE+k= +sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI= sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= sigs.k8s.io/structured-merge-diff/v4 v4.0.3/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= sigs.k8s.io/structured-merge-diff/v4 v4.1.0/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= -sigs.k8s.io/structured-merge-diff/v4 v4.1.2 h1:Hr/htKFmJEbtMgS/UD0N+gtgctAqz81t3nu+sPzynno= sigs.k8s.io/structured-merge-diff/v4 v4.1.2/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4= +sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE= +sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E= sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= -sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q= sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= +sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= +sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU= sourcegraph.com/sourcegraph/go-diff v0.5.0/go.mod h1:kuch7UrkMzY0X+p9CRK03kfuPQ2zzQcaEFbx8wA8rck= sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0= diff --git a/pkg/config/static/static_config.go b/pkg/config/static/static_config.go index 3ab86cdff..ce17087c0 100644 --- a/pkg/config/static/static_config.go +++ b/pkg/config/static/static_config.go @@ -35,6 +35,7 @@ import ( "github.com/traefik/traefik/v2/pkg/tracing/haystack" "github.com/traefik/traefik/v2/pkg/tracing/instana" "github.com/traefik/traefik/v2/pkg/tracing/jaeger" + "github.com/traefik/traefik/v2/pkg/tracing/opentelemetry" "github.com/traefik/traefik/v2/pkg/tracing/zipkin" "github.com/traefik/traefik/v2/pkg/types" ) @@ -169,14 +170,15 @@ func (a *LifeCycle) SetDefaults() { // Tracing holds the tracing configuration. type Tracing struct { - ServiceName string `description:"Set the name for this service." json:"serviceName,omitempty" toml:"serviceName,omitempty" yaml:"serviceName,omitempty" export:"true"` - SpanNameLimit int `description:"Set the maximum character limit for Span names (default 0 = no limit)." json:"spanNameLimit,omitempty" toml:"spanNameLimit,omitempty" yaml:"spanNameLimit,omitempty" export:"true"` - Jaeger *jaeger.Config `description:"Settings for Jaeger." json:"jaeger,omitempty" toml:"jaeger,omitempty" yaml:"jaeger,omitempty" export:"true" label:"allowEmpty" file:"allowEmpty"` - Zipkin *zipkin.Config `description:"Settings for Zipkin." json:"zipkin,omitempty" toml:"zipkin,omitempty" yaml:"zipkin,omitempty" export:"true" label:"allowEmpty" file:"allowEmpty"` - Datadog *datadog.Config `description:"Settings for Datadog." json:"datadog,omitempty" toml:"datadog,omitempty" yaml:"datadog,omitempty" export:"true" label:"allowEmpty" file:"allowEmpty"` - Instana *instana.Config `description:"Settings for Instana." json:"instana,omitempty" toml:"instana,omitempty" yaml:"instana,omitempty" export:"true" label:"allowEmpty" file:"allowEmpty"` - Haystack *haystack.Config `description:"Settings for Haystack." json:"haystack,omitempty" toml:"haystack,omitempty" yaml:"haystack,omitempty" export:"true" label:"allowEmpty" file:"allowEmpty"` - Elastic *elastic.Config `description:"Settings for Elastic." json:"elastic,omitempty" toml:"elastic,omitempty" yaml:"elastic,omitempty" export:"true" label:"allowEmpty" file:"allowEmpty"` + ServiceName string `description:"Set the name for this service." json:"serviceName,omitempty" toml:"serviceName,omitempty" yaml:"serviceName,omitempty" export:"true"` + SpanNameLimit int `description:"Set the maximum character limit for Span names (default 0 = no limit)." json:"spanNameLimit,omitempty" toml:"spanNameLimit,omitempty" yaml:"spanNameLimit,omitempty" export:"true"` + Jaeger *jaeger.Config `description:"Settings for Jaeger." json:"jaeger,omitempty" toml:"jaeger,omitempty" yaml:"jaeger,omitempty" export:"true" label:"allowEmpty" file:"allowEmpty"` + Zipkin *zipkin.Config `description:"Settings for Zipkin." json:"zipkin,omitempty" toml:"zipkin,omitempty" yaml:"zipkin,omitempty" export:"true" label:"allowEmpty" file:"allowEmpty"` + Datadog *datadog.Config `description:"Settings for Datadog." json:"datadog,omitempty" toml:"datadog,omitempty" yaml:"datadog,omitempty" export:"true" label:"allowEmpty" file:"allowEmpty"` + Instana *instana.Config `description:"Settings for Instana." json:"instana,omitempty" toml:"instana,omitempty" yaml:"instana,omitempty" export:"true" label:"allowEmpty" file:"allowEmpty"` + Haystack *haystack.Config `description:"Settings for Haystack." json:"haystack,omitempty" toml:"haystack,omitempty" yaml:"haystack,omitempty" export:"true" label:"allowEmpty" file:"allowEmpty"` + Elastic *elastic.Config `description:"Settings for Elastic." json:"elastic,omitempty" toml:"elastic,omitempty" yaml:"elastic,omitempty" export:"true" label:"allowEmpty" file:"allowEmpty"` + OpenTelemetry *opentelemetry.Config `description:"Settings for OpenTelemetry." json:"openTelemetry,omitempty" toml:"openTelemetry,omitempty" yaml:"openTelemetry,omitempty" export:"true" label:"allowEmpty" file:"allowEmpty"` } // SetDefaults sets the default values. diff --git a/pkg/metrics/opentelemetry.go b/pkg/metrics/opentelemetry.go new file mode 100644 index 000000000..328f6fc54 --- /dev/null +++ b/pkg/metrics/opentelemetry.go @@ -0,0 +1,422 @@ +package metrics + +import ( + "context" + "fmt" + "net" + "strings" + "sync" + "time" + + "github.com/go-kit/kit/metrics" + "github.com/rs/zerolog/log" + "github.com/traefik/traefik/v2/pkg/types" + "github.com/traefik/traefik/v2/pkg/version" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc" + "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp" + "go.opentelemetry.io/otel/metric" + "go.opentelemetry.io/otel/metric/global" + "go.opentelemetry.io/otel/metric/instrument" + "go.opentelemetry.io/otel/metric/instrument/asyncfloat64" + "go.opentelemetry.io/otel/metric/instrument/syncfloat64" + "go.opentelemetry.io/otel/metric/unit" + sdkmetric "go.opentelemetry.io/otel/sdk/metric" + "go.opentelemetry.io/otel/sdk/metric/aggregation" + "go.opentelemetry.io/otel/sdk/metric/view" + "google.golang.org/grpc/credentials" + "google.golang.org/grpc/encoding/gzip" +) + +var ( + openTelemetryMeterProvider *sdkmetric.MeterProvider + openTelemetryGaugeCollector *gaugeCollector +) + +// RegisterOpenTelemetry registers all OpenTelemetry metrics. +func RegisterOpenTelemetry(ctx context.Context, config *types.OpenTelemetry) Registry { + if openTelemetryMeterProvider == nil { + var err error + if openTelemetryMeterProvider, err = newOpenTelemetryMeterProvider(ctx, config); err != nil { + log.Ctx(ctx).Err(err).Msg("Unable to create OpenTelemetry meter provider") + + return nil + } + } + + if openTelemetryGaugeCollector == nil { + openTelemetryGaugeCollector = newOpenTelemetryGaugeCollector() + } + + meter := global.Meter("github.com/traefik/traefik", + metric.WithInstrumentationVersion(version.Version)) + + reg := &standardRegistry{ + epEnabled: config.AddEntryPointsLabels, + routerEnabled: config.AddRoutersLabels, + svcEnabled: config.AddServicesLabels, + configReloadsCounter: newOTLPCounterFrom(meter, configReloadsTotalName, "Config reloads"), + configReloadsFailureCounter: newOTLPCounterFrom(meter, configReloadsFailuresTotalName, "Config reload failures"), + lastConfigReloadSuccessGauge: newOTLPGaugeFrom(meter, configLastReloadSuccessName, "Last config reload success", unit.Milliseconds), + lastConfigReloadFailureGauge: newOTLPGaugeFrom(meter, configLastReloadFailureName, "Last config reload failure", unit.Milliseconds), + tlsCertsNotAfterTimestampGauge: newOTLPGaugeFrom(meter, tlsCertsNotAfterTimestamp, "Certificate expiration timestamp", unit.Milliseconds), + } + + if config.AddEntryPointsLabels { + reg.entryPointReqsCounter = newOTLPCounterFrom(meter, entryPointReqsTotalName, + "How many HTTP requests processed on an entrypoint, partitioned by status code, protocol, and method.") + reg.entryPointReqsTLSCounter = newOTLPCounterFrom(meter, entryPointReqsTLSTotalName, + "How many HTTP requests with TLS processed on an entrypoint, partitioned by TLS Version and TLS cipher Used.") + reg.entryPointReqDurationHistogram, _ = NewHistogramWithScale(newOTLPHistogramFrom(meter, entryPointReqDurationName, + "How long it took to process the request on an entrypoint, partitioned by status code, protocol, and method.", + unit.Milliseconds), time.Second) + reg.entryPointOpenConnsGauge = newOTLPGaugeFrom(meter, entryPointOpenConnsName, + "How many open connections exist on an entrypoint, partitioned by method and protocol.", + unit.Dimensionless) + } + + if config.AddRoutersLabels { + reg.routerReqsCounter = newOTLPCounterFrom(meter, routerReqsTotalName, + "How many HTTP requests are processed on a router, partitioned by service, status code, protocol, and method.") + reg.routerReqsTLSCounter = newOTLPCounterFrom(meter, routerReqsTLSTotalName, + "How many HTTP requests with TLS are processed on a router, partitioned by service, TLS Version, and TLS cipher Used.") + reg.routerReqDurationHistogram, _ = NewHistogramWithScale(newOTLPHistogramFrom(meter, routerReqDurationName, + "How long it took to process the request on a router, partitioned by service, status code, protocol, and method.", + unit.Milliseconds), time.Second) + reg.routerOpenConnsGauge = newOTLPGaugeFrom(meter, routerOpenConnsName, + "How many open connections exist on a router, partitioned by service, method, and protocol.", + unit.Dimensionless) + } + + if config.AddServicesLabels { + reg.serviceReqsCounter = newOTLPCounterFrom(meter, serviceReqsTotalName, + "How many HTTP requests processed on a service, partitioned by status code, protocol, and method.") + reg.serviceReqsTLSCounter = newOTLPCounterFrom(meter, serviceReqsTLSTotalName, + "How many HTTP requests with TLS processed on a service, partitioned by TLS version and TLS cipher.") + reg.serviceReqDurationHistogram, _ = NewHistogramWithScale(newOTLPHistogramFrom(meter, serviceReqDurationName, + "How long it took to process the request on a service, partitioned by status code, protocol, and method.", + unit.Milliseconds), time.Second) + reg.serviceOpenConnsGauge = newOTLPGaugeFrom(meter, serviceOpenConnsName, + "How many open connections exist on a service, partitioned by method and protocol.", + unit.Dimensionless) + reg.serviceRetriesCounter = newOTLPCounterFrom(meter, serviceRetriesTotalName, + "How many request retries happened on a service.") + reg.serviceServerUpGauge = newOTLPGaugeFrom(meter, serviceServerUpName, + "service server is up, described by gauge value of 0 or 1.", + unit.Dimensionless) + } + + return reg +} + +// StopOpenTelemetry stops and resets Open-Telemetry client. +func StopOpenTelemetry() { + if openTelemetryMeterProvider == nil { + return + } + + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + if err := openTelemetryMeterProvider.Shutdown(ctx); err != nil { + log.Err(err).Msg("Unable to shutdown OpenTelemetry meter provider") + } + + openTelemetryMeterProvider = nil +} + +// newOpenTelemetryMeterProvider creates a new controller.Controller. +func newOpenTelemetryMeterProvider(ctx context.Context, config *types.OpenTelemetry) (*sdkmetric.MeterProvider, error) { + var ( + exporter sdkmetric.Exporter + err error + ) + if config.GRPC != nil { + exporter, err = newGRPCExporter(ctx, config) + } else { + exporter, err = newHTTPExporter(ctx, config) + } + if err != nil { + return nil, fmt.Errorf("creating exporter: %w", err) + } + + opts := []sdkmetric.PeriodicReaderOption{ + sdkmetric.WithInterval(time.Duration(config.PushInterval)), + } + + // View to customize histogram buckets and rename a single histogram instrument. + customBucketsView, err := view.New( + // Match* to match instruments + view.MatchInstrumentName("traefik_*_request_duration_seconds"), + + view.WithSetAggregation(aggregation.ExplicitBucketHistogram{ + Boundaries: config.ExplicitBoundaries, + }), + ) + if err != nil { + return nil, fmt.Errorf("creating histogram view: %w", err) + } + + meterProvider := sdkmetric.NewMeterProvider(sdkmetric.WithReader( + sdkmetric.NewPeriodicReader(exporter, opts...), + customBucketsView, + )) + + global.SetMeterProvider(meterProvider) + + return meterProvider, nil +} + +func newHTTPExporter(ctx context.Context, config *types.OpenTelemetry) (sdkmetric.Exporter, error) { + host, port, err := net.SplitHostPort(config.Address) + if err != nil { + return nil, fmt.Errorf("invalid collector address %q: %w", config.Address, err) + } + + opts := []otlpmetrichttp.Option{ + otlpmetrichttp.WithEndpoint(fmt.Sprintf("%s:%s", host, port)), + otlpmetrichttp.WithHeaders(config.Headers), + otlpmetrichttp.WithCompression(otlpmetrichttp.GzipCompression), + } + + if config.Insecure { + opts = append(opts, otlpmetrichttp.WithInsecure()) + } + + if config.Path != "" { + opts = append(opts, otlpmetrichttp.WithURLPath(config.Path)) + } + + if config.TLS != nil { + tlsConfig, err := config.TLS.CreateTLSConfig(ctx) + if err != nil { + return nil, fmt.Errorf("creating TLS client config: %w", err) + } + + opts = append(opts, otlpmetrichttp.WithTLSClientConfig(tlsConfig)) + } + + return otlpmetrichttp.New(ctx, opts...) +} + +func newGRPCExporter(ctx context.Context, config *types.OpenTelemetry) (sdkmetric.Exporter, error) { + host, port, err := net.SplitHostPort(config.Address) + if err != nil { + return nil, fmt.Errorf("invalid collector address %q: %w", config.Address, err) + } + + opts := []otlpmetricgrpc.Option{ + otlpmetricgrpc.WithEndpoint(fmt.Sprintf("%s:%s", host, port)), + otlpmetricgrpc.WithHeaders(config.Headers), + otlpmetricgrpc.WithCompressor(gzip.Name), + } + + if config.Insecure { + opts = append(opts, otlpmetricgrpc.WithInsecure()) + } + + if config.TLS != nil { + tlsConfig, err := config.TLS.CreateTLSConfig(ctx) + if err != nil { + return nil, fmt.Errorf("creating TLS client config: %w", err) + } + + opts = append(opts, otlpmetricgrpc.WithTLSCredentials(credentials.NewTLS(tlsConfig))) + } + + return otlpmetricgrpc.New(ctx, opts...) +} + +func newOTLPCounterFrom(meter metric.Meter, name, desc string) *otelCounter { + c, _ := meter.SyncFloat64().Counter(name, + instrument.WithDescription(desc), + instrument.WithUnit(unit.Dimensionless), + ) + + return &otelCounter{ + ip: c, + } +} + +type otelCounter struct { + labelNamesValues otelLabelNamesValues + ip syncfloat64.Counter +} + +func (c *otelCounter) With(labelValues ...string) metrics.Counter { + return &otelCounter{ + labelNamesValues: c.labelNamesValues.With(labelValues...), + ip: c.ip, + } +} + +func (c *otelCounter) Add(delta float64) { + c.ip.Add(context.Background(), delta, c.labelNamesValues.ToLabels()...) +} + +type gaugeValue struct { + attributes otelLabelNamesValues + value float64 +} + +type gaugeCollector struct { + mu sync.Mutex + values map[string]map[string]gaugeValue +} + +func newOpenTelemetryGaugeCollector() *gaugeCollector { + return &gaugeCollector{ + values: make(map[string]map[string]gaugeValue), + } +} + +func (c *gaugeCollector) add(name string, delta float64, attributes otelLabelNamesValues) { + c.mu.Lock() + defer c.mu.Unlock() + + str := strings.Join(attributes, "") + + if _, exists := c.values[name]; !exists { + c.values[name] = map[string]gaugeValue{ + str: { + attributes: attributes, + value: delta, + }, + } + return + } + + v, exists := c.values[name][str] + if !exists { + c.values[name][str] = gaugeValue{ + attributes: attributes, + value: delta, + } + return + } + + c.values[name][str] = gaugeValue{ + attributes: attributes, + value: v.value + delta, + } +} + +func (c *gaugeCollector) set(name string, value float64, attributes otelLabelNamesValues) { + c.mu.Lock() + defer c.mu.Unlock() + + if _, exists := c.values[name]; !exists { + c.values[name] = make(map[string]gaugeValue) + } + + c.values[name][strings.Join(attributes, "")] = gaugeValue{ + attributes: attributes, + value: value, + } +} + +func newOTLPGaugeFrom(meter metric.Meter, name, desc string, u unit.Unit) *otelGauge { + openTelemetryGaugeCollector.values[name] = make(map[string]gaugeValue) + + c, _ := meter.AsyncFloat64().Gauge(name, + instrument.WithDescription(desc), + instrument.WithUnit(u), + ) + + err := meter.RegisterCallback([]instrument.Asynchronous{c}, func(ctx context.Context) { + openTelemetryGaugeCollector.mu.Lock() + defer openTelemetryGaugeCollector.mu.Unlock() + + values, exists := openTelemetryGaugeCollector.values[name] + if !exists { + return + } + + for _, value := range values { + c.Observe(ctx, value.value, value.attributes.ToLabels()...) + } + }) + if err != nil { + log.Err(err).Msg("Unable to register OpenTelemetry meter callback") + } + + return &otelGauge{ + ip: c, + name: name, + } +} + +type otelGauge struct { + labelNamesValues otelLabelNamesValues + ip asyncfloat64.Gauge + name string +} + +func (g *otelGauge) With(labelValues ...string) metrics.Gauge { + return &otelGauge{ + labelNamesValues: g.labelNamesValues.With(labelValues...), + ip: g.ip, + name: g.name, + } +} + +func (g *otelGauge) Add(delta float64) { + openTelemetryGaugeCollector.add(g.name, delta, g.labelNamesValues) +} + +func (g *otelGauge) Set(value float64) { + openTelemetryGaugeCollector.set(g.name, value, g.labelNamesValues) +} + +func newOTLPHistogramFrom(meter metric.Meter, name, desc string, u unit.Unit) *otelHistogram { + c, _ := meter.SyncFloat64().Histogram(name, + instrument.WithDescription(desc), + instrument.WithUnit(u), + ) + + return &otelHistogram{ + ip: c, + } +} + +type otelHistogram struct { + labelNamesValues otelLabelNamesValues + ip syncfloat64.Histogram +} + +func (h *otelHistogram) With(labelValues ...string) metrics.Histogram { + return &otelHistogram{ + labelNamesValues: h.labelNamesValues.With(labelValues...), + ip: h.ip, + } +} + +func (h *otelHistogram) Observe(incr float64) { + h.ip.Record(context.Background(), incr, h.labelNamesValues.ToLabels()...) +} + +// otelLabelNamesValues is the equivalent of prometheus' labelNamesValues +// but adapted to OpenTelemetry. +// otelLabelNamesValues is a type alias that provides validation on its With +// method. +// Metrics may include it as a member to help them satisfy With semantics and +// save some code duplication. +type otelLabelNamesValues []string + +// With validates the input, and returns a new aggregate otelLabelNamesValues. +func (lvs otelLabelNamesValues) With(labelValues ...string) otelLabelNamesValues { + if len(labelValues)%2 != 0 { + labelValues = append(labelValues, "unknown") + } + return append(lvs, labelValues...) +} + +// ToLabels is a convenience method to convert a otelLabelNamesValues +// to the native attribute.KeyValue. +func (lvs otelLabelNamesValues) ToLabels() []attribute.KeyValue { + labels := make([]attribute.KeyValue, len(lvs)/2) + for i := 0; i < len(labels); i++ { + labels[i] = attribute.String(lvs[2*i], lvs[2*i+1]) + } + return labels +} diff --git a/pkg/metrics/opentelemetry_test.go b/pkg/metrics/opentelemetry_test.go new file mode 100644 index 000000000..16890b6ba --- /dev/null +++ b/pkg/metrics/opentelemetry_test.go @@ -0,0 +1,446 @@ +package metrics + +import ( + "compress/gzip" + "context" + "encoding/json" + "fmt" + "io" + "net/http" + "net/http/httptest" + "net/url" + "strconv" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + ptypes "github.com/traefik/paerser/types" + "github.com/traefik/traefik/v2/pkg/types" + "go.opentelemetry.io/collector/pdata/pmetric/pmetricotlp" + "go.opentelemetry.io/otel/attribute" +) + +func TestOpenTelemetry_labels(t *testing.T) { + tests := []struct { + desc string + values otelLabelNamesValues + with []string + expect []attribute.KeyValue + }{ + { + desc: "with no starting value", + values: otelLabelNamesValues{}, + expect: []attribute.KeyValue{}, + }, + { + desc: "with one starting value", + values: otelLabelNamesValues{"foo"}, + expect: []attribute.KeyValue{}, + }, + { + desc: "with two starting value", + values: otelLabelNamesValues{"foo", "bar"}, + expect: []attribute.KeyValue{attribute.String("foo", "bar")}, + }, + { + desc: "with no starting value, and with one other value", + values: otelLabelNamesValues{}, + with: []string{"baz"}, + expect: []attribute.KeyValue{attribute.String("baz", "unknown")}, + }, + { + desc: "with no starting value, and with two other value", + values: otelLabelNamesValues{}, + with: []string{"baz", "buz"}, + expect: []attribute.KeyValue{attribute.String("baz", "buz")}, + }, + { + desc: "with one starting value, and with one other value", + values: otelLabelNamesValues{"foo"}, + with: []string{"baz"}, + expect: []attribute.KeyValue{attribute.String("foo", "baz")}, + }, + { + desc: "with one starting value, and with two other value", + values: otelLabelNamesValues{"foo"}, + with: []string{"baz", "buz"}, + expect: []attribute.KeyValue{attribute.String("foo", "baz")}, + }, + { + desc: "with two starting value, and with one other value", + values: otelLabelNamesValues{"foo", "bar"}, + with: []string{"baz"}, + expect: []attribute.KeyValue{ + attribute.String("foo", "bar"), + attribute.String("baz", "unknown"), + }, + }, + { + desc: "with two starting value, and with two other value", + values: otelLabelNamesValues{"foo", "bar"}, + with: []string{"baz", "buz"}, + expect: []attribute.KeyValue{ + attribute.String("foo", "bar"), + attribute.String("baz", "buz"), + }, + }, + } + + for _, test := range tests { + test := test + + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + assert.Equal(t, test.expect, test.values.With(test.with...).ToLabels()) + }) + } +} + +func TestOpenTelemetry_GaugeCollectorAdd(t *testing.T) { + tests := []struct { + desc string + gc *gaugeCollector + delta float64 + name string + attributes otelLabelNamesValues + expect map[string]map[string]gaugeValue + }{ + { + desc: "empty collector", + gc: newOpenTelemetryGaugeCollector(), + delta: 1, + name: "foo", + expect: map[string]map[string]gaugeValue{ + "foo": {"": {value: 1}}, + }, + }, + { + desc: "initialized collector", + gc: &gaugeCollector{ + values: map[string]map[string]gaugeValue{ + "foo": {"": {value: 1}}, + }, + }, + delta: 1, + name: "foo", + expect: map[string]map[string]gaugeValue{ + "foo": {"": {value: 2}}, + }, + }, + { + desc: "initialized collector, values with label (only the last one counts)", + gc: &gaugeCollector{ + values: map[string]map[string]gaugeValue{ + "foo": { + "bar": { + attributes: otelLabelNamesValues{"bar"}, + value: 1, + }, + }, + }, + }, + delta: 1, + name: "foo", + expect: map[string]map[string]gaugeValue{ + "foo": { + "": { + value: 1, + }, + "bar": { + attributes: otelLabelNamesValues{"bar"}, + value: 1, + }, + }, + }, + }, + { + desc: "initialized collector, values with label on set", + gc: &gaugeCollector{ + values: map[string]map[string]gaugeValue{ + "foo": {"bar": {value: 1}}, + }, + }, + delta: 1, + name: "foo", + attributes: otelLabelNamesValues{"baz"}, + expect: map[string]map[string]gaugeValue{ + "foo": { + "bar": { + value: 1, + }, + "baz": { + value: 1, + attributes: otelLabelNamesValues{"baz"}, + }, + }, + }, + }, + } + + for _, test := range tests { + test := test + + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + test.gc.add(test.name, test.delta, test.attributes) + + assert.Equal(t, test.expect, test.gc.values) + }) + } +} + +func TestOpenTelemetry_GaugeCollectorSet(t *testing.T) { + tests := []struct { + desc string + gc *gaugeCollector + value float64 + name string + attributes otelLabelNamesValues + expect map[string]map[string]gaugeValue + }{ + { + desc: "empty collector", + gc: newOpenTelemetryGaugeCollector(), + value: 1, + name: "foo", + expect: map[string]map[string]gaugeValue{ + "foo": {"": {value: 1}}, + }, + }, + { + desc: "initialized collector", + gc: &gaugeCollector{ + values: map[string]map[string]gaugeValue{ + "foo": {"": {value: 1}}, + }, + }, + value: 1, + name: "foo", + expect: map[string]map[string]gaugeValue{ + "foo": {"": {value: 1}}, + }, + }, + { + desc: "initialized collector, values with label", + gc: &gaugeCollector{ + values: map[string]map[string]gaugeValue{ + "foo": { + "bar": { + attributes: otelLabelNamesValues{"bar"}, + value: 1, + }, + }, + }, + }, + value: 1, + name: "foo", + expect: map[string]map[string]gaugeValue{ + "foo": { + "": { + value: 1, + }, + "bar": { + attributes: otelLabelNamesValues{"bar"}, + value: 1, + }, + }, + }, + }, + { + desc: "initialized collector, values with label on set", + gc: &gaugeCollector{ + values: map[string]map[string]gaugeValue{ + "foo": {"": {value: 1}}, + }, + }, + value: 1, + name: "foo", + attributes: otelLabelNamesValues{"bar"}, + expect: map[string]map[string]gaugeValue{ + "foo": { + "": { + value: 1, + }, + "bar": { + value: 1, + attributes: otelLabelNamesValues{"bar"}, + }, + }, + }, + }, + } + + for _, test := range tests { + test := test + + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + test.gc.set(test.name, test.value, test.attributes) + + assert.Equal(t, test.expect, test.gc.values) + }) + } +} + +func TestOpenTelemetry(t *testing.T) { + t.Parallel() + + c := make(chan *string) + defer close(c) + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + gzr, err := gzip.NewReader(r.Body) + require.NoError(t, err) + + body, err := io.ReadAll(gzr) + require.NoError(t, err) + + req := pmetricotlp.NewExportRequest() + err = req.UnmarshalProto(body) + require.NoError(t, err) + + marshalledReq, err := json.Marshal(req) + require.NoError(t, err) + + bodyStr := string(marshalledReq) + c <- &bodyStr + + _, err = fmt.Fprintln(w, "ok") + require.NoError(t, err) + })) + defer ts.Close() + + sURL, err := url.Parse(ts.URL) + require.NoError(t, err) + + var cfg types.OpenTelemetry + (&cfg).SetDefaults() + cfg.AddRoutersLabels = true + cfg.Address = sURL.Host + cfg.Insecure = true + cfg.PushInterval = ptypes.Duration(10 * time.Millisecond) + + registry := RegisterOpenTelemetry(context.Background(), &cfg) + require.NotNil(t, registry) + + if !registry.IsEpEnabled() || !registry.IsRouterEnabled() || !registry.IsSvcEnabled() { + t.Fatalf("registry should return true for IsEnabled(), IsRouterEnabled() and IsSvcEnabled()") + } + + // TODO: the len of startUnixNano is no supposed to be 20, it should be 19 + expectedServer := []string{ + `({"name":"traefik_config_reloads_total","description":"Config reloads","unit":"1","sum":{"dataPoints":\[{"startTimeUnixNano":"[\d]{19}","timeUnixNano":"[\d]{19}","asDouble":1}\],"aggregationTemporality":2,"isMonotonic":true}})`, + `({"name":"traefik_config_reloads_failure_total","description":"Config reload failures","unit":"1","sum":{"dataPoints":\[{"startTimeUnixNano":"[\d]{19}","timeUnixNano":"[\d]{19}","asDouble":1}\],"aggregationTemporality":2,"isMonotonic":true}})`, + `({"name":"traefik_config_last_reload_success","description":"Last config reload success","unit":"ms","gauge":{"dataPoints":\[{"startTimeUnixNano":"[\d]{20}","timeUnixNano":"[\d]{19}","asDouble":1}\]}})`, + `({"name":"traefik_config_last_reload_failure","description":"Last config reload failure","unit":"ms","gauge":{"dataPoints":\[{"startTimeUnixNano":"[\d]{20}","timeUnixNano":"[\d]{19}","asDouble":1}\]}})`, + } + + registry.ConfigReloadsCounter().Add(1) + registry.ConfigReloadsFailureCounter().Add(1) + registry.LastConfigReloadSuccessGauge().Set(1) + registry.LastConfigReloadFailureGauge().Set(1) + msgServer := <-c + + assertMessage(t, *msgServer, expectedServer) + + expectedTLS := []string{ + `({"name":"traefik_tls_certs_not_after","description":"Certificate expiration timestamp","unit":"ms","gauge":{"dataPoints":\[{"attributes":\[{"key":"key","value":{"stringValue":"value"}}\],"startTimeUnixNano":"[\d]{20}","timeUnixNano":"[\d]{19}","asDouble":1}\]}})`, + } + + registry.TLSCertsNotAfterTimestampGauge().With("key", "value").Set(1) + msgTLS := <-c + + assertMessage(t, *msgTLS, expectedTLS) + + expectedEntrypoint := []string{ + `({"name":"traefik_entrypoint_requests_total","description":"How many HTTP requests processed on an entrypoint, partitioned by status code, protocol, and method.","unit":"1","sum":{"dataPoints":\[{"attributes":\[{"key":"code","value":{"stringValue":"200"}},{"key":"entrypoint","value":{"stringValue":"test1"}},{"key":"method","value":{"stringValue":"GET"}}\],"startTimeUnixNano":"[\d]{19}","timeUnixNano":"[\d]{19}","asDouble":1}\],"aggregationTemporality":2,"isMonotonic":true}})`, + `({"name":"traefik_entrypoint_requests_tls_total","description":"How many HTTP requests with TLS processed on an entrypoint, partitioned by TLS Version and TLS cipher Used.","unit":"1","sum":{"dataPoints":\[{"attributes":\[{"key":"entrypoint","value":{"stringValue":"test2"}},{"key":"tls_cipher","value":{"stringValue":"bar"}},{"key":"tls_version","value":{"stringValue":"foo"}}\],"startTimeUnixNano":"[\d]{19}","timeUnixNano":"[\d]{19}","asDouble":1}\],"aggregationTemporality":2,"isMonotonic":true}})`, + `({"name":"traefik_entrypoint_request_duration_seconds","description":"How long it took to process the request on an entrypoint, partitioned by status code, protocol, and method.","unit":"ms","histogram":{"dataPoints":\[{"attributes":\[{"key":"entrypoint","value":{"stringValue":"test3"}}\],"startTimeUnixNano":"[\d]{19}","timeUnixNano":"[\d]{19}","count":"1","sum":10000,"bucketCounts":\["0","0","0","0","0","0","0","0","0","0","0","1"\],"explicitBounds":\[0.005,0.01,0.025,0.05,0.1,0.25,0.5,1,2.5,5,10\],"min":10000,"max":10000}\],"aggregationTemporality":2}})`, + `({"name":"traefik_entrypoint_open_connections","description":"How many open connections exist on an entrypoint, partitioned by method and protocol.","unit":"1","gauge":{"dataPoints":\[{"attributes":\[{"key":"entrypoint","value":{"stringValue":"test4"}}\],"startTimeUnixNano":"[\d]{20}","timeUnixNano":"[\d]{19}","asDouble":1}\]}})`, + } + + registry.EntryPointReqsCounter().With("entrypoint", "test1", "code", strconv.Itoa(http.StatusOK), "method", http.MethodGet).Add(1) + registry.EntryPointReqsTLSCounter().With("entrypoint", "test2", "tls_version", "foo", "tls_cipher", "bar").Add(1) + registry.EntryPointReqDurationHistogram().With("entrypoint", "test3").Observe(10000) + registry.EntryPointOpenConnsGauge().With("entrypoint", "test4").Set(1) + msgEntrypoint := <-c + + assertMessage(t, *msgEntrypoint, expectedEntrypoint) + + expectedRouter := []string{ + `({"name":"traefik_router_requests_total","description":"How many HTTP requests are processed on a router, partitioned by service, status code, protocol, and method.","unit":"1","sum":{"dataPoints":\[{"attributes":\[{"key":"code","value":{"stringValue":"(?:200|404)"}},{"key":"method","value":{"stringValue":"GET"}},{"key":"router","value":{"stringValue":"RouterReqsCounter"}},{"key":"service","value":{"stringValue":"test"}}\],"startTimeUnixNano":"[\d]{19}","timeUnixNano":"[\d]{19}","asDouble":1},{"attributes":\[{"key":"code","value":{"stringValue":"(?:200|404)"}},{"key":"method","value":{"stringValue":"GET"}},{"key":"router","value":{"stringValue":"RouterReqsCounter"}},{"key":"service","value":{"stringValue":"test"}}\],"startTimeUnixNano":"[\d]{19}","timeUnixNano":"[\d]{19}","asDouble":1}\],"aggregationTemporality":2,"isMonotonic":true}})`, + `({"name":"traefik_router_requests_tls_total","description":"How many HTTP requests with TLS are processed on a router, partitioned by service, TLS Version, and TLS cipher Used.","unit":"1","sum":{"dataPoints":\[{"attributes":\[{"key":"router","value":{"stringValue":"demo"}},{"key":"service","value":{"stringValue":"test"}},{"key":"tls_cipher","value":{"stringValue":"bar"}},{"key":"tls_version","value":{"stringValue":"foo"}}\],"startTimeUnixNano":"[\d]{19}","timeUnixNano":"[\d]{19}","asDouble":1}\],"aggregationTemporality":2,"isMonotonic":true}})`, + `({"name":"traefik_router_request_duration_seconds","description":"How long it took to process the request on a router, partitioned by service, status code, protocol, and method.","unit":"ms","histogram":{"dataPoints":\[{"attributes":\[{"key":"code","value":{"stringValue":"200"}},{"key":"router","value":{"stringValue":"demo"}},{"key":"service","value":{"stringValue":"test"}}\],"startTimeUnixNano":"[\d]{19}","timeUnixNano":"[\d]{19}","count":"1","sum":10000,"bucketCounts":\["0","0","0","0","0","0","0","0","0","0","0","1"\],"explicitBounds":\[0.005,0.01,0.025,0.05,0.1,0.25,0.5,1,2.5,5,10\],"min":10000,"max":10000}\],"aggregationTemporality":2}})`, + `({"name":"traefik_router_open_connections","description":"How many open connections exist on a router, partitioned by service, method, and protocol.","unit":"1","gauge":{"dataPoints":\[{"attributes":\[{"key":"router","value":{"stringValue":"demo"}},{"key":"service","value":{"stringValue":"test"}}\],"startTimeUnixNano":"[\d]{20}","timeUnixNano":"[\d]{19}","asDouble":1}\]}})`, + } + + registry.RouterReqsCounter().With("router", "RouterReqsCounter", "service", "test", "code", strconv.Itoa(http.StatusNotFound), "method", http.MethodGet).Add(1) + registry.RouterReqsCounter().With("router", "RouterReqsCounter", "service", "test", "code", strconv.Itoa(http.StatusOK), "method", http.MethodGet).Add(1) + registry.RouterReqsTLSCounter().With("router", "demo", "service", "test", "tls_version", "foo", "tls_cipher", "bar").Add(1) + registry.RouterReqDurationHistogram().With("router", "demo", "service", "test", "code", strconv.Itoa(http.StatusOK)).Observe(10000) + registry.RouterOpenConnsGauge().With("router", "demo", "service", "test").Set(1) + msgRouter := <-c + + assertMessage(t, *msgRouter, expectedRouter) + + expectedService := []string{ + `({"name":"traefik_service_requests_total","description":"How many HTTP requests processed on a service, partitioned by status code, protocol, and method.","unit":"1","sum":{"dataPoints":\[{"attributes":\[{"key":"code","value":{"stringValue":"(?:200|404)"}},{"key":"method","value":{"stringValue":"GET"}},{"key":"service","value":{"stringValue":"ServiceReqsCounter"}}\],"startTimeUnixNano":"[\d]{19}","timeUnixNano":"[\d]{19}","asDouble":1},{"attributes":\[{"key":"code","value":{"stringValue":"(?:200|404)"}},{"key":"method","value":{"stringValue":"GET"}},{"key":"service","value":{"stringValue":"ServiceReqsCounter"}}\],"startTimeUnixNano":"[\d]{19}","timeUnixNano":"[\d]{19}","asDouble":1}\],"aggregationTemporality":2,"isMonotonic":true}})`, + `({"name":"traefik_service_requests_tls_total","description":"How many HTTP requests with TLS processed on a service, partitioned by TLS version and TLS cipher.","unit":"1","sum":{"dataPoints":\[{"attributes":\[{"key":"service","value":{"stringValue":"test"}},{"key":"tls_cipher","value":{"stringValue":"bar"}},{"key":"tls_version","value":{"stringValue":"foo"}}\],"startTimeUnixNano":"[\d]{19}","timeUnixNano":"[\d]{19}","asDouble":1}\],"aggregationTemporality":2,"isMonotonic":true}})`, + `({"name":"traefik_service_request_duration_seconds","description":"How long it took to process the request on a service, partitioned by status code, protocol, and method.","unit":"ms","histogram":{"dataPoints":\[{"attributes":\[{"key":"code","value":{"stringValue":"200"}},{"key":"service","value":{"stringValue":"test"}}\],"startTimeUnixNano":"[\d]{19}","timeUnixNano":"[\d]{19}","count":"1","sum":10000,"bucketCounts":\["0","0","0","0","0","0","0","0","0","0","0","1"\],"explicitBounds":\[0.005,0.01,0.025,0.05,0.1,0.25,0.5,1,2.5,5,10\],"min":10000,"max":10000}\],"aggregationTemporality":2}})`, + `({"name":"traefik_service_server_up","description":"service server is up, described by gauge value of 0 or 1.","unit":"1","gauge":{"dataPoints":\[{"attributes":\[{"key":"service","value":{"stringValue":"test"}},{"key":"url","value":{"stringValue":"http://127.0.0.1"}}\],"startTimeUnixNano":"[\d]{20}","timeUnixNano":"[\d]{19}","asDouble":1}\]}})`, + } + + registry.ServiceReqsCounter().With("service", "ServiceReqsCounter", "code", strconv.Itoa(http.StatusOK), "method", http.MethodGet).Add(1) + registry.ServiceReqsCounter().With("service", "ServiceReqsCounter", "code", strconv.Itoa(http.StatusNotFound), "method", http.MethodGet).Add(1) + registry.ServiceReqsTLSCounter().With("service", "test", "tls_version", "foo", "tls_cipher", "bar").Add(1) + registry.ServiceReqDurationHistogram().With("service", "test", "code", strconv.Itoa(http.StatusOK)).Observe(10000) + registry.ServiceServerUpGauge().With("service", "test", "url", "http://127.0.0.1").Set(1) + msgService := <-c + + assertMessage(t, *msgService, expectedService) + + expectedServiceRetries := []string{ + `({"attributes":\[{"key":"service","value":{"stringValue":"foobar"}}\],"startTimeUnixNano":"[\d]{19}","timeUnixNano":"[\d]{19}","asDouble":1})`, + `({"attributes":\[{"key":"service","value":{"stringValue":"test"}}\],"startTimeUnixNano":"[\d]{19}","timeUnixNano":"[\d]{19}","asDouble":2})`, + } + + registry.ServiceRetriesCounter().With("service", "test").Add(1) + registry.ServiceRetriesCounter().With("service", "test").Add(1) + registry.ServiceRetriesCounter().With("service", "foobar").Add(1) + msgServiceRetries := <-c + + assertMessage(t, *msgServiceRetries, expectedServiceRetries) + + expectedServiceOpenConns := []string{ + `({"attributes":\[{"key":"service","value":{"stringValue":"test"}}\],"startTimeUnixNano":"[\d]{20}","timeUnixNano":"[\d]{19}","asDouble":3})`, + `({"attributes":\[{"key":"service","value":{"stringValue":"foobar"}}\],"startTimeUnixNano":"[\d]{20}","timeUnixNano":"[\d]{19}","asDouble":1})`, + } + + registry.ServiceOpenConnsGauge().With("service", "test").Set(1) + registry.ServiceOpenConnsGauge().With("service", "test").Add(1) + registry.ServiceOpenConnsGauge().With("service", "test").Add(1) + registry.ServiceOpenConnsGauge().With("service", "foobar").Add(1) + msgServiceOpenConns := <-c + + assertMessage(t, *msgServiceOpenConns, expectedServiceOpenConns) + + expectedEntryPointReqDuration := []string{ + `({"attributes":\[{"key":"entrypoint","value":{"stringValue":"myEntrypoint"}}\],"startTimeUnixNano":"[\d]{19}","timeUnixNano":"[\d]{19}","count":"2","sum":30000,"bucketCounts":\["0","0","0","0","0","0","0","0","0","0","0","2"\],"explicitBounds":\[0.005,0.01,0.025,0.05,0.1,0.25,0.5,1,2.5,5,10\],"min":10000,"max":20000})`, + } + + registry.EntryPointReqDurationHistogram().With("entrypoint", "myEntrypoint").Observe(10000) + registry.EntryPointReqDurationHistogram().With("entrypoint", "myEntrypoint").Observe(20000) + msgEntryPointReqDurationHistogram := <-c + + assertMessage(t, *msgEntryPointReqDurationHistogram, expectedEntryPointReqDuration) + + // We need to unlock the HTTP Server for the last export call when stopping + // OpenTelemetry. + go func() { + <-c + }() + StopOpenTelemetry() +} diff --git a/pkg/server/server.go b/pkg/server/server.go index edd0cdb02..421437a38 100644 --- a/pkg/server/server.go +++ b/pkg/server/server.go @@ -113,4 +113,5 @@ func stopMetricsClients() { metrics.StopStatsd() metrics.StopInfluxDB() metrics.StopInfluxDB2() + metrics.StopOpenTelemetry() } diff --git a/pkg/tracing/opentelemetry/opentelemetry.go b/pkg/tracing/opentelemetry/opentelemetry.go new file mode 100644 index 000000000..d42770ae3 --- /dev/null +++ b/pkg/tracing/opentelemetry/opentelemetry.go @@ -0,0 +1,138 @@ +package opentelemetry + +import ( + "context" + "fmt" + "io" + "net" + + "github.com/opentracing/opentracing-go" + "github.com/rs/zerolog/log" + "github.com/traefik/traefik/v2/pkg/types" + "github.com/traefik/traefik/v2/pkg/version" + "go.opentelemetry.io/otel" + oteltracer "go.opentelemetry.io/otel/bridge/opentracing" + "go.opentelemetry.io/otel/exporters/otlp/otlptrace" + "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc" + "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp" + "go.opentelemetry.io/otel/propagation" + sdktrace "go.opentelemetry.io/otel/sdk/trace" + "go.opentelemetry.io/otel/trace" + "google.golang.org/grpc/credentials" + "google.golang.org/grpc/encoding/gzip" +) + +// Config provides configuration settings for the open-telemetry tracer. +type Config struct { + // NOTE: as no gRPC option is implemented yet, the type is empty and is used as a boolean for upward compatibility purposes. + GRPC *struct{} `description:"gRPC specific configuration for the OpenTelemetry collector." json:"grpc,omitempty" toml:"grpc,omitempty" yaml:"grpc,omitempty" export:"true" label:"allowEmpty" file:"allowEmpty"` + + Address string `description:"Sets the address (host:port) of the collector endpoint." json:"address,omitempty" toml:"address,omitempty" yaml:"address,omitempty"` + Path string `description:"Sets the URL path of the collector endpoint." json:"path,omitempty" toml:"path,omitempty" yaml:"path,omitempty" export:"true"` + Insecure bool `description:"Disables client transport security for the exporter." json:"insecure,omitempty" toml:"insecure,omitempty" yaml:"insecure,omitempty" export:"true"` + Headers map[string]string `description:"Defines additional headers to be sent with the payloads." json:"headers,omitempty" toml:"headers,omitempty" yaml:"headers,omitempty" export:"true"` + TLS *types.ClientTLS `description:"Defines client transport security parameters." json:"tls,omitempty" toml:"tls,omitempty" yaml:"tls,omitempty" export:"true"` +} + +// SetDefaults sets the default values. +func (c *Config) SetDefaults() { + c.Address = "localhost:4318" +} + +// Setup sets up the tracer. +func (c *Config) Setup(componentName string) (opentracing.Tracer, io.Closer, error) { + var ( + err error + exporter *otlptrace.Exporter + ) + if c.GRPC != nil { + exporter, err = c.setupGRPCExporter() + } else { + exporter, err = c.setupHTTPExporter() + } + if err != nil { + return nil, nil, fmt.Errorf("setting up exporter: %w", err) + } + + bt := oteltracer.NewBridgeTracer() + bt.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{})) + + bt.SetOpenTelemetryTracer(otel.Tracer(componentName, trace.WithInstrumentationVersion(version.Version))) + opentracing.SetGlobalTracer(bt) + + tracerProvider := sdktrace.NewTracerProvider(sdktrace.WithBatcher(exporter)) + otel.SetTracerProvider(tracerProvider) + + log.Debug().Msg("OpenTelemetry tracer configured") + + return bt, tpCloser{provider: tracerProvider}, err +} + +func (c *Config) setupHTTPExporter() (*otlptrace.Exporter, error) { + host, port, err := net.SplitHostPort(c.Address) + if err != nil { + return nil, fmt.Errorf("invalid collector address %q: %w", c.Address, err) + } + + opts := []otlptracehttp.Option{ + otlptracehttp.WithEndpoint(fmt.Sprintf("%s:%s", host, port)), + otlptracehttp.WithHeaders(c.Headers), + otlptracehttp.WithCompression(otlptracehttp.GzipCompression), + } + + if c.Insecure { + opts = append(opts, otlptracehttp.WithInsecure()) + } + + if c.Path != "" { + opts = append(opts, otlptracehttp.WithURLPath(c.Path)) + } + + if c.TLS != nil { + tlsConfig, err := c.TLS.CreateTLSConfig(context.Background()) + if err != nil { + return nil, fmt.Errorf("creating TLS client config: %w", err) + } + + opts = append(opts, otlptracehttp.WithTLSClientConfig(tlsConfig)) + } + + return otlptrace.New(context.Background(), otlptracehttp.NewClient(opts...)) +} + +func (c *Config) setupGRPCExporter() (*otlptrace.Exporter, error) { + host, port, err := net.SplitHostPort(c.Address) + if err != nil { + return nil, fmt.Errorf("invalid collector address %q: %w", c.Address, err) + } + + opts := []otlptracegrpc.Option{ + otlptracegrpc.WithEndpoint(fmt.Sprintf("%s:%s", host, port)), + otlptracegrpc.WithHeaders(c.Headers), + otlptracegrpc.WithCompressor(gzip.Name), + } + + if c.Insecure { + opts = append(opts, otlptracegrpc.WithInsecure()) + } + + if c.TLS != nil { + tlsConfig, err := c.TLS.CreateTLSConfig(context.Background()) + if err != nil { + return nil, fmt.Errorf("creating TLS client config: %w", err) + } + + opts = append(opts, otlptracegrpc.WithTLSCredentials(credentials.NewTLS(tlsConfig))) + } + + return otlptrace.New(context.Background(), otlptracegrpc.NewClient(opts...)) +} + +// tpCloser converts a TraceProvider into an io.Closer. +type tpCloser struct { + provider *sdktrace.TracerProvider +} + +func (t tpCloser) Close() error { + return t.provider.Shutdown(context.Background()) +} diff --git a/pkg/tracing/opentelemetry/opentelemetry_test.go b/pkg/tracing/opentelemetry/opentelemetry_test.go new file mode 100644 index 000000000..9d76f71d3 --- /dev/null +++ b/pkg/tracing/opentelemetry/opentelemetry_test.go @@ -0,0 +1,67 @@ +package opentelemetry + +import ( + "compress/gzip" + "context" + "encoding/json" + "io" + "net/http" + "net/http/httptest" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + mtracing "github.com/traefik/traefik/v2/pkg/middlewares/tracing" + "github.com/traefik/traefik/v2/pkg/tracing" + "go.opentelemetry.io/collector/pdata/ptrace/ptraceotlp" +) + +func TestTraceContextPropagation(t *testing.T) { + t.Parallel() + + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + gzr, err := gzip.NewReader(r.Body) + require.NoError(t, err) + + body, err := io.ReadAll(gzr) + require.NoError(t, err) + + req := ptraceotlp.NewExportRequest() + err = req.UnmarshalProto(body) + require.NoError(t, err) + + marshalledReq, err := json.Marshal(req) + require.NoError(t, err) + + bodyStr := string(marshalledReq) + assert.Regexp(t, `("traceId":"00000000000000000000000000000001")`, bodyStr) + assert.Regexp(t, `("parentSpanId":"0000000000000001")`, bodyStr) + assert.Regexp(t, `("traceState":"foo=bar")`, bodyStr) + })) + defer ts.Close() + + cfg := Config{ + Address: strings.TrimPrefix(ts.URL, "http://"), + Insecure: true, + } + + newTracing, err := tracing.NewTracing("", 0, &cfg) + require.NoError(t, err) + defer newTracing.Close() + + req := httptest.NewRequest(http.MethodGet, "http://www.test.com", nil) + req.Header.Set("traceparent", "00-00000000000000000000000000000001-0000000000000001-00") + req.Header.Set("tracestate", "foo=bar") + rw := httptest.NewRecorder() + + var forwarded bool + next := http.HandlerFunc(func(http.ResponseWriter, *http.Request) { + forwarded = true + }) + + handler := mtracing.NewEntryPoint(context.Background(), newTracing, "test", next) + handler.ServeHTTP(rw, req) + + require.True(t, forwarded) +} diff --git a/pkg/tracing/operation_name_test.go b/pkg/tracing/operation_name_test.go index dd9542c67..599151098 100644 --- a/pkg/tracing/operation_name_test.go +++ b/pkg/tracing/operation_name_test.go @@ -72,8 +72,8 @@ func TestComputeHash(t *testing.T) { }{ { desc: "hashing", - text: "some very long pice of text", - expected: "0258ea1c", + text: "some very long piece of text", + expected: "0c6e798b", }, { desc: "short text less than limit 10", @@ -109,7 +109,7 @@ func TestTruncateString(t *testing.T) { }, { desc: "basic truncate with limit 10", - text: "some very long pice of text", + text: "some very long piece of text", limit: 10, expected: "some ve...", }, diff --git a/pkg/types/metrics.go b/pkg/types/metrics.go index de3b85a06..ddcf71d96 100644 --- a/pkg/types/metrics.go +++ b/pkg/types/metrics.go @@ -10,11 +10,12 @@ import ( // Metrics provides options to expose and send Traefik metrics to different third party monitoring systems. type Metrics struct { - Prometheus *Prometheus `description:"Prometheus metrics exporter type." json:"prometheus,omitempty" toml:"prometheus,omitempty" yaml:"prometheus,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` - Datadog *Datadog `description:"Datadog metrics exporter type." json:"datadog,omitempty" toml:"datadog,omitempty" yaml:"datadog,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` - StatsD *Statsd `description:"StatsD metrics exporter type." json:"statsD,omitempty" toml:"statsD,omitempty" yaml:"statsD,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` - InfluxDB *InfluxDB `description:"InfluxDB metrics exporter type." json:"influxDB,omitempty" toml:"influxDB,omitempty" yaml:"influxDB,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` - InfluxDB2 *InfluxDB2 `description:"InfluxDB v2 metrics exporter type." json:"influxDB2,omitempty" toml:"influxDB2,omitempty" yaml:"influxDB2,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` + Prometheus *Prometheus `description:"Prometheus metrics exporter type." json:"prometheus,omitempty" toml:"prometheus,omitempty" yaml:"prometheus,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` + Datadog *Datadog `description:"Datadog metrics exporter type." json:"datadog,omitempty" toml:"datadog,omitempty" yaml:"datadog,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` + StatsD *Statsd `description:"StatsD metrics exporter type." json:"statsD,omitempty" toml:"statsD,omitempty" yaml:"statsD,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` + InfluxDB *InfluxDB `description:"InfluxDB metrics exporter type." json:"influxDB,omitempty" toml:"influxDB,omitempty" yaml:"influxDB,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` + InfluxDB2 *InfluxDB2 `description:"InfluxDB v2 metrics exporter type." json:"influxDB2,omitempty" toml:"influxDB2,omitempty" yaml:"influxDB2,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` + OpenTelemetry *OpenTelemetry `description:"OpenTelemetry metrics exporter type." json:"openTelemetry,omitempty" toml:"openTelemetry,omitempty" yaml:"openTelemetry,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` } // Prometheus can contain specific configuration used by the Prometheus Metrics exporter. @@ -127,6 +128,32 @@ func (i *InfluxDB2) SetDefaults() { i.AddServicesLabels = true } +// OpenTelemetry contains specific configuration used by the OpenTelemetry Metrics exporter. +type OpenTelemetry struct { + // NOTE: as no gRPC option is implemented yet, the type is empty and is used as a boolean for upward compatibility purposes. + GRPC *struct{} `description:"gRPC specific configuration for the OpenTelemetry collector." json:"grpc,omitempty" toml:"grpc,omitempty" yaml:"grpc,omitempty" export:"true" label:"allowEmpty" file:"allowEmpty"` + + Address string `description:"Address (host:port) of the collector endpoint." json:"address,omitempty" toml:"address,omitempty" yaml:"address,omitempty"` + AddEntryPointsLabels bool `description:"Enable metrics on entry points." json:"addEntryPointsLabels,omitempty" toml:"addEntryPointsLabels,omitempty" yaml:"addEntryPointsLabels,omitempty" export:"true"` + AddRoutersLabels bool `description:"Enable metrics on routers." json:"addRoutersLabels,omitempty" toml:"addRoutersLabels,omitempty" yaml:"addRoutersLabels,omitempty" export:"true"` + AddServicesLabels bool `description:"Enable metrics on services." json:"addServicesLabels,omitempty" toml:"addServicesLabels,omitempty" yaml:"addServicesLabels,omitempty" export:"true"` + ExplicitBoundaries []float64 `description:"Boundaries for latency metrics." json:"explicitBoundaries,omitempty" toml:"explicitBoundaries,omitempty" yaml:"explicitBoundaries,omitempty" export:"true"` + Headers map[string]string `description:"Headers sent with payload." json:"headers,omitempty" toml:"headers,omitempty" yaml:"headers,omitempty" export:"true"` + Insecure bool `description:"Disables client transport security for the exporter." json:"insecure,omitempty" toml:"insecure,omitempty" yaml:"insecure,omitempty" export:"true"` + Path string `description:"Set the URL path of the collector endpoint." json:"path,omitempty" toml:"path,omitempty" yaml:"path,omitempty" export:"true"` + PushInterval types.Duration `description:"Period between calls to collect a checkpoint." json:"pushInterval,omitempty" toml:"pushInterval,omitempty" yaml:"pushInterval,omitempty" export:"true"` + TLS *ClientTLS `description:"Enable TLS support." json:"tls,omitempty" toml:"tls,omitempty" yaml:"tls,omitempty" export:"true"` +} + +// SetDefaults sets the default values. +func (o *OpenTelemetry) SetDefaults() { + o.Address = "localhost:4318" + o.AddEntryPointsLabels = true + o.AddServicesLabels = true + o.ExplicitBoundaries = []float64{.005, .01, .025, .05, .1, .25, .5, 1, 2.5, 5, 10} + o.PushInterval = types.Duration(10 * time.Second) +} + // Statistics provides options for monitoring request and response stats. type Statistics struct { RecentErrors int `description:"Number of recent errors logged." json:"recentErrors,omitempty" toml:"recentErrors,omitempty" yaml:"recentErrors,omitempty" export:"true"` From c57876c116ebc375becaff476fbcf6f25c4db7f3 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Wed, 30 Nov 2022 09:50:05 +0100 Subject: [PATCH 06/19] Improve provider logs --- pkg/provider/consulcatalog/consul_catalog.go | 4 ++-- pkg/provider/docker/docker.go | 4 ++-- pkg/provider/ecs/ecs.go | 4 ++-- pkg/provider/http/http.go | 4 ++-- pkg/provider/http/http_test.go | 2 +- pkg/provider/kubernetes/crd/kubernetes.go | 4 ++-- pkg/provider/kubernetes/gateway/kubernetes.go | 4 ++-- pkg/provider/kubernetes/ingress/kubernetes.go | 4 ++-- pkg/provider/kv/kv.go | 10 +++------- pkg/provider/marathon/marathon.go | 4 ++-- pkg/provider/rancher/rancher.go | 4 ++-- 11 files changed, 22 insertions(+), 26 deletions(-) diff --git a/pkg/provider/consulcatalog/consul_catalog.go b/pkg/provider/consulcatalog/consul_catalog.go index 3f0815f1a..af8c7e2f7 100644 --- a/pkg/provider/consulcatalog/consul_catalog.go +++ b/pkg/provider/consulcatalog/consul_catalog.go @@ -233,12 +233,12 @@ func (p *Provider) Provide(configurationChan chan<- dynamic.Message, pool *safe. } notify := func(err error, time time.Duration) { - logger.Error().Err(err).Msgf("Provider connection error, retrying in %s", time) + logger.Error().Err(err).Msgf("Provider error, retrying in %s", time) } err := backoff.RetryNotify(safe.OperationWithRecover(operation), backoff.WithContext(job.NewBackOff(backoff.NewExponentialBackOff()), ctxLog), notify) if err != nil { - logger.Error().Err(err).Msg("Cannot connect to consul catalog server") + logger.Error().Err(err).Msg("Cannot retrieve data") } }) diff --git a/pkg/provider/docker/docker.go b/pkg/provider/docker/docker.go index eefd737ad..2e902cf8a 100644 --- a/pkg/provider/docker/docker.go +++ b/pkg/provider/docker/docker.go @@ -329,11 +329,11 @@ func (p *Provider) Provide(configurationChan chan<- dynamic.Message, pool *safe. } notify := func(err error, time time.Duration) { - logger.Error().Err(err).Msgf("Provider connection error, retrying in %s", time) + logger.Error().Err(err).Msgf("Provider error, retrying in %s", time) } err := backoff.RetryNotify(safe.OperationWithRecover(operation), backoff.WithContext(job.NewBackOff(backoff.NewExponentialBackOff()), ctxLog), notify) if err != nil { - logger.Error().Err(err).Msg("Cannot connect to docker server") + logger.Error().Err(err).Msg("Cannot retrieve data") } }) diff --git a/pkg/provider/ecs/ecs.go b/pkg/provider/ecs/ecs.go index ac78b5d6f..22e3e5695 100644 --- a/pkg/provider/ecs/ecs.go +++ b/pkg/provider/ecs/ecs.go @@ -183,11 +183,11 @@ func (p *Provider) Provide(configurationChan chan<- dynamic.Message, pool *safe. } notify := func(err error, time time.Duration) { - logger.Error().Err(err).Msgf("Provider connection error, retrying in %s", time) + logger.Error().Err(err).Msgf("Provider error, retrying in %s", time) } err := backoff.RetryNotify(safe.OperationWithRecover(operation), backoff.WithContext(job.NewBackOff(backoff.NewExponentialBackOff()), routineCtx), notify) if err != nil { - logger.Error().Err(err).Msg("Cannot connect to Provider api") + logger.Error().Err(err).Msg("Cannot retrieve data") } }) diff --git a/pkg/provider/http/http.go b/pkg/provider/http/http.go index 776c9de1b..e8ad5b8e7 100644 --- a/pkg/provider/http/http.go +++ b/pkg/provider/http/http.go @@ -97,11 +97,11 @@ func (p *Provider) Provide(configurationChan chan<- dynamic.Message, pool *safe. } notify := func(err error, time time.Duration) { - logger.Error().Err(err).Msgf("Provider connection error, retrying in %s", time) + logger.Error().Err(err).Msgf("Provider error, retrying in %s", time) } err := backoff.RetryNotify(safe.OperationWithRecover(operation), backoff.WithContext(job.NewBackOff(backoff.NewExponentialBackOff()), ctxLog), notify) if err != nil { - logger.Error().Err(err).Msg("Cannot connect to server endpoint") + logger.Error().Err(err).Msg("Cannot retrieve data") } }) diff --git a/pkg/provider/http/http_test.go b/pkg/provider/http/http_test.go index 7a0477dd0..97f020c3b 100644 --- a/pkg/provider/http/http_test.go +++ b/pkg/provider/http/http_test.go @@ -146,7 +146,7 @@ func TestProvider_decodeConfiguration(t *testing.T) { }, { desc: "should return the decoded dynamic configuration", - configData: []byte("{\"tcp\":{\"routers\":{\"foo\":{}}}}"), + configData: []byte(`{"tcp":{"routers":{"foo":{}}}}`), expConfig: &dynamic.Configuration{ HTTP: &dynamic.HTTPConfiguration{ Routers: make(map[string]*dynamic.Router), diff --git a/pkg/provider/kubernetes/crd/kubernetes.go b/pkg/provider/kubernetes/crd/kubernetes.go index 299f3f803..f2054d09e 100644 --- a/pkg/provider/kubernetes/crd/kubernetes.go +++ b/pkg/provider/kubernetes/crd/kubernetes.go @@ -168,11 +168,11 @@ func (p *Provider) Provide(configurationChan chan<- dynamic.Message, pool *safe. } notify := func(err error, time time.Duration) { - logger.Error().Err(err).Msgf("Provider connection error, retrying in %s", time) + logger.Error().Err(err).Msgf("Provider error, retrying in %s", time) } err := backoff.RetryNotify(safe.OperationWithRecover(operation), backoff.WithContext(job.NewBackOff(backoff.NewExponentialBackOff()), ctxPool), notify) if err != nil { - logger.Error().Err(err).Msg("Cannot connect to Provider") + logger.Error().Err(err).Msg("Cannot retrieve data") } }) diff --git a/pkg/provider/kubernetes/gateway/kubernetes.go b/pkg/provider/kubernetes/gateway/kubernetes.go index f06bdac9b..3f62e98a2 100644 --- a/pkg/provider/kubernetes/gateway/kubernetes.go +++ b/pkg/provider/kubernetes/gateway/kubernetes.go @@ -162,11 +162,11 @@ func (p *Provider) Provide(configurationChan chan<- dynamic.Message, pool *safe. } notify := func(err error, time time.Duration) { - logger.Error().Err(err).Msgf("Provider connection error, retrying in %s", time) + logger.Error().Err(err).Msgf("Provider error, retrying in %s", time) } err := backoff.RetryNotify(safe.OperationWithRecover(operation), backoff.WithContext(job.NewBackOff(backoff.NewExponentialBackOff()), ctxPool), notify) if err != nil { - logger.Error().Err(err).Msg("Cannot connect to Provider") + logger.Error().Err(err).Msg("Cannot retrieve data") } }) diff --git a/pkg/provider/kubernetes/ingress/kubernetes.go b/pkg/provider/kubernetes/ingress/kubernetes.go index 206f37d50..95a71c12d 100644 --- a/pkg/provider/kubernetes/ingress/kubernetes.go +++ b/pkg/provider/kubernetes/ingress/kubernetes.go @@ -169,12 +169,12 @@ func (p *Provider) Provide(configurationChan chan<- dynamic.Message, pool *safe. } notify := func(err error, time time.Duration) { - logger.Error().Err(err).Msgf("Provider connection error, retrying in %s", time) + logger.Error().Err(err).Msgf("Provider error, retrying in %s", time) } err := backoff.RetryNotify(safe.OperationWithRecover(operation), backoff.WithContext(job.NewBackOff(backoff.NewExponentialBackOff()), ctxPool), notify) if err != nil { - logger.Error().Err(err).Msg("Cannot connect to Provider") + logger.Error().Err(err).Msg("Cannot retrieve data") } }) diff --git a/pkg/provider/kv/kv.go b/pkg/provider/kv/kv.go index abc25a84d..f6697b821 100644 --- a/pkg/provider/kv/kv.go +++ b/pkg/provider/kv/kv.go @@ -85,7 +85,7 @@ func (p *Provider) Provide(configurationChan chan<- dynamic.Message, pool *safe. err := p.watchKv(ctxLog, configurationChan) if err != nil { - logger.Error().Err(err).Msg("Cannot watch KV store") + logger.Error().Err(err).Msg("Cannot retrieve data") } }) @@ -124,15 +124,11 @@ func (p *Provider) watchKv(ctx context.Context, configurationChan chan<- dynamic } notify := func(err error, time time.Duration) { - log.Ctx(ctx).Error().Err(err).Msgf("KV connection error, retrying in %s", time) + log.Ctx(ctx).Error().Err(err).Msgf("Provider error, retrying in %s", time) } - err := backoff.RetryNotify(safe.OperationWithRecover(operation), + return backoff.RetryNotify(safe.OperationWithRecover(operation), backoff.WithContext(job.NewBackOff(backoff.NewExponentialBackOff()), ctx), notify) - if err != nil { - return fmt.Errorf("cannot connect to KV server: %w", err) - } - return nil } func (p *Provider) buildConfiguration(ctx context.Context) (*dynamic.Configuration, error) { diff --git a/pkg/provider/marathon/marathon.go b/pkg/provider/marathon/marathon.go index 423573e66..d4e8cccd8 100644 --- a/pkg/provider/marathon/marathon.go +++ b/pkg/provider/marathon/marathon.go @@ -192,11 +192,11 @@ func (p *Provider) Provide(configurationChan chan<- dynamic.Message, pool *safe. } notify := func(err error, time time.Duration) { - logger.Error().Err(err).Msgf("Provider connection error, retrying in %s", time) + logger.Error().Err(err).Msgf("Provider error, retrying in %s", time) } err := backoff.RetryNotify(safe.OperationWithRecover(operation), backoff.WithContext(job.NewBackOff(backoff.NewExponentialBackOff()), ctx), notify) if err != nil { - logger.Error().Err(err).Msg("Cannot connect to Provider server") + logger.Error().Err(err).Msg("Cannot retrieve data") } return nil } diff --git a/pkg/provider/rancher/rancher.go b/pkg/provider/rancher/rancher.go index 8f74dcc21..212c6607c 100644 --- a/pkg/provider/rancher/rancher.go +++ b/pkg/provider/rancher/rancher.go @@ -140,11 +140,11 @@ func (p *Provider) Provide(configurationChan chan<- dynamic.Message, pool *safe. } notify := func(err error, time time.Duration) { - logger.Error().Err(err).Msgf("Provider connection error, retrying in %s", time) + logger.Error().Err(err).Msgf("Provider error, retrying in %s", time) } err := backoff.RetryNotify(safe.OperationWithRecover(operation), backoff.WithContext(job.NewBackOff(backoff.NewExponentialBackOff()), ctxLog), notify) if err != nil { - logger.Error().Err(err).Msg("Cannot connect to Provider server") + logger.Error().Err(err).Msg("Cannot retrieve data") } }) From af71443b6189182acbe1ec7c3132e9c6d2d9d538 Mon Sep 17 00:00:00 2001 From: Janik <80165193+Janik-Haag@users.noreply.github.com> Date: Wed, 30 Nov 2022 15:04:05 +0100 Subject: [PATCH 07/19] Added networking example --- docs/content/getting-started/quick-start.md | 7 ++++- .../docker-compose/basic-example/index.md | 29 +++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/docs/content/getting-started/quick-start.md b/docs/content/getting-started/quick-start.md index 5bddfc2ae..6a411666e 100644 --- a/docs/content/getting-started/quick-start.md +++ b/docs/content/getting-started/quick-start.md @@ -50,7 +50,12 @@ Now that we have a Traefik instance up and running, we will deploy new services. Edit your `docker-compose.yml` file and add the following at the end of your file. ```yaml -# ... +version: '3' + +services: + + ... + whoami: # A container that exposes an API to show its IP address image: traefik/whoami diff --git a/docs/content/user-guides/docker-compose/basic-example/index.md b/docs/content/user-guides/docker-compose/basic-example/index.md index 643df4505..bda9ccca6 100644 --- a/docs/content/user-guides/docker-compose/basic-example/index.md +++ b/docs/content/user-guides/docker-compose/basic-example/index.md @@ -16,6 +16,35 @@ This will also be used as a starting point for the other docker-compose guides. --8<-- "content/user-guides/docker-compose/basic-example/docker-compose.yml" ``` +??? Networking + + The Traefik container has to be attached to the same network as the containers to be exposed. + If no networks are specified in the docker-compose file, Docker creates a default one that allows Traefik to reach the containers defined in the same file. + You can [customize the network](https://docs.docker.com/compose/networking/#specify-custom-networks) as described in the example below. + You can use a [pre-existing network](https://docs.docker.com/compose/networking/#use-a-pre-existing-network) too. + + ```yaml + version: "3.3" + + networks: + traefiknet: {} + + services: + + traefik: + image: "traefik:v2.9" + ... + networks: + - traefiknet + + whoami: + image: "traefik/whoami" + ... + networks: + - traefiknet + + ``` + - Replace `whoami.localhost` by your **own domain** within the `traefik.http.routers.whoami.rule` label of the `whoami` service. - Run `docker-compose up -d` within the folder where you created the previous file. - Wait a bit and visit `http://your_own_domain` to confirm everything went fine. From c7647b49380317d27c4f27d1f0eb70dfb7e6fef0 Mon Sep 17 00:00:00 2001 From: mloiseleur <97035654+mloiseleur@users.noreply.github.com> Date: Thu, 1 Dec 2022 10:10:05 +0100 Subject: [PATCH 08/19] doc: Update Helm installation section --- docs/content/getting-started/install-traefik.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/docs/content/getting-started/install-traefik.md b/docs/content/getting-started/install-traefik.md index 15aa3733a..dfc592b81 100644 --- a/docs/content/getting-started/install-traefik.md +++ b/docs/content/getting-started/install-traefik.md @@ -44,10 +44,10 @@ Traefik can be installed in Kubernetes using the Helm chart from Date: Thu, 1 Dec 2022 14:30:06 +0100 Subject: [PATCH 09/19] Add information about the Hub Agent --- docs/content/traefik-hub/index.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/content/traefik-hub/index.md b/docs/content/traefik-hub/index.md index f1c46035d..a6c57cee9 100644 --- a/docs/content/traefik-hub/index.md +++ b/docs/content/traefik-hub/index.md @@ -29,6 +29,12 @@ This agent can: * The Traefik Hub Agent must be installed to connect to the Traefik Hub platform. * Activate this feature in the experimental section of the static configuration. +!!! information "Configuration Discovery" + + According to installation options, the Traefik Hub Agent listens to the Docker or Kubernetes API to discover containers/services. + + It doesn't support the routers discovered by Traefik Proxy using other providers, e.g., using the File provider. + !!! example "Minimal Static Configuration to Activate Traefik Hub for Docker" ```yaml tab="File (YAML)" From 778188ed34e886b55d1d5c3c669fce4cc49795ef Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Mon, 5 Dec 2022 11:30:05 +0100 Subject: [PATCH 10/19] fix: remove logs of the request --- cmd/traefik/traefik.go | 2 +- go.mod | 16 ++++----- go.sum | 27 +++++++++------ integration/fixtures/simple_debug_log.toml | 15 ++++++++ integration/simple_test.go | 34 +++++++++++++++++++ pkg/healthcheck/healthcheck.go | 2 +- pkg/healthcheck/healthcheck_test.go | 2 +- pkg/middlewares/accesslog/field_middleware.go | 2 +- pkg/middlewares/auth/forward.go | 4 +-- pkg/middlewares/auth/forward_test.go | 2 +- pkg/middlewares/buffering/buffering.go | 4 +-- .../circuitbreaker/circuit_breaker.go | 4 +-- pkg/middlewares/customerrors/custom_errors.go | 2 +- .../empty_backend_handler_test.go | 2 +- pkg/middlewares/extractor.go | 2 +- pkg/middlewares/inflightreq/inflight_req.go | 2 +- pkg/middlewares/ratelimiter/rate_limiter.go | 2 +- .../ratelimiter/rate_limiter_test.go | 2 +- pkg/middlewares/redirect/redirect.go | 2 +- pkg/server/service/service.go | 4 +-- 20 files changed, 94 insertions(+), 38 deletions(-) create mode 100644 integration/fixtures/simple_debug_log.toml diff --git a/cmd/traefik/traefik.go b/cmd/traefik/traefik.go index 125025251..ca685d575 100644 --- a/cmd/traefik/traefik.go +++ b/cmd/traefik/traefik.go @@ -44,7 +44,7 @@ import ( "github.com/traefik/traefik/v2/pkg/tracing/jaeger" "github.com/traefik/traefik/v2/pkg/types" "github.com/traefik/traefik/v2/pkg/version" - "github.com/vulcand/oxy/roundrobin" + "github.com/vulcand/oxy/v2/roundrobin" ) func main() { diff --git a/go.mod b/go.mod index cb9086d78..f8e560207 100644 --- a/go.mod +++ b/go.mod @@ -56,8 +56,8 @@ require ( github.com/prometheus/client_golang v1.12.2-0.20220704083116-e8f91604d835 github.com/prometheus/client_model v0.2.0 github.com/rancher/go-rancher-metadata v0.0.0-20200311180630-7f4c936a06ac - github.com/sirupsen/logrus v1.8.1 - github.com/stretchr/testify v1.8.0 + github.com/sirupsen/logrus v1.9.0 + github.com/stretchr/testify v1.8.1 github.com/stvp/go-udp-testing v0.0.0-20191102171040-06b61409b154 github.com/traefik/paerser v0.1.9 github.com/traefik/yaegi v0.14.3 @@ -66,13 +66,13 @@ require ( github.com/unrolled/render v1.0.2 github.com/unrolled/secure v1.0.9 github.com/vdemeester/shakers v0.1.0 - github.com/vulcand/oxy v1.4.2 + github.com/vulcand/oxy/v2 v2.0.0-20221121151423-d5cb734e4467 github.com/vulcand/predicate v1.2.0 go.elastic.co/apm v1.13.1 go.elastic.co/apm/module/apmot v1.13.1 golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 - golang.org/x/net v0.0.0-20220927171203-f486391704dc - golang.org/x/text v0.3.7 + golang.org/x/net v0.1.0 + golang.org/x/text v0.4.0 golang.org/x/time v0.0.0-20220224211638-0e9765cccd65 golang.org/x/tools v0.1.12 google.golang.org/grpc v1.41.0 @@ -293,7 +293,7 @@ require ( github.com/spf13/cast v1.3.1 // indirect github.com/spf13/cobra v1.2.1 // indirect github.com/spf13/pflag v1.0.5 // indirect - github.com/stretchr/objx v0.4.0 // indirect + github.com/stretchr/objx v0.5.0 // indirect github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.490 // indirect github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.490 // indirect github.com/theupdateframework/notary v0.6.1 // indirect @@ -325,8 +325,8 @@ require ( golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 // indirect golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1 // indirect golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 // indirect - golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab // indirect - golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect + golang.org/x/sys v0.1.0 // indirect + golang.org/x/term v0.1.0 // indirect golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect google.golang.org/api v0.44.0 // indirect google.golang.org/appengine v1.6.7 // indirect diff --git a/go.sum b/go.sum index 8c1a6e5a6..00253253e 100644 --- a/go.sum +++ b/go.sum @@ -1672,8 +1672,9 @@ github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMB github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= -github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= +github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/assertions v1.0.1 h1:voD4ITNjPL5jjBfgR/r8fPIIBrliWrWHeiJApdr3r4w= github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9 h1:hp2CYQUINdZMHdvTdXtPOY2ainKl4IoMcpAXEf2xj3Q= @@ -1733,8 +1734,9 @@ github.com/stretchr/objx v0.0.0-20180129172003-8a3f7159479f/go.mod h1:HFkY916IF+ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= -github.com/stretchr/objx v0.4.0 h1:M2gUjqZET1qApGOWNSnZ49BAIMX4F/1plDv3+l31EJ4= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v0.0.0-20151208002404-e3a8ff8ce365/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v0.0.0-20180303142811-b89eecf5ca5d/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= @@ -1745,8 +1747,9 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stvp/go-udp-testing v0.0.0-20191102171040-06b61409b154 h1:XGopsea1Dw7ecQ8JscCNQXDGYAKDiWjDeXnpN/+BY9g= github.com/stvp/go-udp-testing v0.0.0-20191102171040-06b61409b154/go.mod h1:7jxmlfBCDBXRzr0eAQJ48XC1hBu1np4CS5+cHEYfwpc= github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= @@ -1821,8 +1824,8 @@ github.com/vmihailenco/msgpack v3.3.3+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6Ac github.com/vmihailenco/msgpack/v4 v4.3.12/go.mod h1:gborTTJjAo/GWTqqRjrLCn9pgNN+NXzzngzBKDPIqw4= github.com/vmihailenco/tagparser v0.1.1/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI= github.com/vmware/govmomi v0.18.0/go.mod h1:URlwyTFZX72RmxtxuaFL2Uj3fD1JTvZdx59bHWk6aFU= -github.com/vulcand/oxy v1.4.2 h1:KibUVdKrwy7eXR3uHS2pYoZ9dCzKVcgDNHD2jkPZmxU= -github.com/vulcand/oxy v1.4.2/go.mod h1:Yq8OBb0XWU/7nPSglwUH5LS2Pcp4yvad8SVayobZbSo= +github.com/vulcand/oxy/v2 v2.0.0-20221121151423-d5cb734e4467 h1:Dbv3KJLgwtDKLpCZzTf1ISeG5ZYudPaLfTdYi4O2dSU= +github.com/vulcand/oxy/v2 v2.0.0-20221121151423-d5cb734e4467/go.mod h1:0kOEB8mKzSeGHknF53gTM47UEvQnPoAPnM+58baqn2o= github.com/vulcand/predicate v1.2.0 h1:uFsW1gcnnR7R+QTID+FVcs0sSYlIGntoGOTb3rQJt50= github.com/vulcand/predicate v1.2.0/go.mod h1:VipoNYXny6c8N381zGUWkjuuNHiRbeAZhE7Qm9c+2GA= github.com/vultr/govultr/v2 v2.17.2 h1:gej/rwr91Puc/tgh+j33p/BLR16UrIPnSr+AIwYWZQs= @@ -2090,8 +2093,8 @@ golang.org/x/net v0.0.0-20211216030914-fe4d6282115f/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.0.0-20220927171203-f486391704dc h1:FxpXZdoBqT8RjqTy6i1E8nXHhW21wK7ptQ/EPIGxzPQ= -golang.org/x/net v0.0.0-20220927171203-f486391704dc/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= +golang.org/x/net v0.1.0 h1:hZ/3BUoy5aId7sCpA/Tc5lt8DkFgdVS2onTpJsZ/fl0= +golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/oauth2 v0.0.0-20180724155351-3d292e4d0cdc/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -2249,13 +2252,16 @@ golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab h1:2QkjZIsXupsJbJIdSjjUOgWK3aEtzyuh2mPt3l/CkeU= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 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= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.1.0 h1:g6Z6vPFA9dYBAF7DWcH6sCcOntplXsDKcliusYijMlw= +golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -2265,8 +2271,9 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 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= diff --git a/integration/fixtures/simple_debug_log.toml b/integration/fixtures/simple_debug_log.toml new file mode 100644 index 000000000..5f6419a52 --- /dev/null +++ b/integration/fixtures/simple_debug_log.toml @@ -0,0 +1,15 @@ +[global] + checkNewVersion = false + sendAnonymousUsage = false + +[log] + level = "DEBUG" + +[api] + insecure = true + +[providers.docker] + +[entryPoints] + [entryPoints.webHost] + address = ":8000" diff --git a/integration/simple_test.go b/integration/simple_test.go index 28e5291fd..e25f25cfb 100644 --- a/integration/simple_test.go +++ b/integration/simple_test.go @@ -11,6 +11,7 @@ import ( "net/http" "net/http/httptest" "os" + "regexp" "strings" "sync/atomic" "syscall" @@ -1378,3 +1379,36 @@ func (s *SimpleSuite) TestMuxer(c *check.C) { } } } + +func (s *SimpleSuite) TestDebugLog(c *check.C) { + s.createComposeProject(c, "base") + + s.composeUp(c) + defer s.composeDown(c) + + file := s.adaptFile(c, "fixtures/simple_debug_log.toml", struct{}{}) + defer os.Remove(file) + + cmd, output := s.cmdTraefik(withConfigFile(file)) + + err := cmd.Start() + c.Assert(err, checker.IsNil) + defer s.killCmd(cmd) + + err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("PathPrefix(`/whoami`)")) + c.Assert(err, checker.IsNil) + + req, err := http.NewRequest(http.MethodGet, "http://localhost:8000/whoami", http.NoBody) + c.Assert(err, checker.IsNil) + req.Header.Set("Autorization", "Bearer ThisIsABearerToken") + + response, err := http.DefaultClient.Do(req) + c.Assert(err, checker.IsNil) + c.Assert(response.StatusCode, checker.Equals, http.StatusOK) + + if regexp.MustCompile("ThisIsABearerToken").MatchReader(output) { + c.Logf("Traefik Logs: %s", output.String()) + c.Log("Found Authorization Header in Traefik DEBUG logs") + c.Fail() + } +} diff --git a/pkg/healthcheck/healthcheck.go b/pkg/healthcheck/healthcheck.go index 77a7743bb..68210e821 100644 --- a/pkg/healthcheck/healthcheck.go +++ b/pkg/healthcheck/healthcheck.go @@ -18,7 +18,7 @@ import ( "github.com/traefik/traefik/v2/pkg/log" "github.com/traefik/traefik/v2/pkg/metrics" "github.com/traefik/traefik/v2/pkg/safe" - "github.com/vulcand/oxy/roundrobin" + "github.com/vulcand/oxy/v2/roundrobin" ) const ( diff --git a/pkg/healthcheck/healthcheck_test.go b/pkg/healthcheck/healthcheck_test.go index 914f48291..4c30f8f31 100644 --- a/pkg/healthcheck/healthcheck_test.go +++ b/pkg/healthcheck/healthcheck_test.go @@ -13,7 +13,7 @@ import ( "github.com/stretchr/testify/require" "github.com/traefik/traefik/v2/pkg/config/runtime" "github.com/traefik/traefik/v2/pkg/testhelpers" - "github.com/vulcand/oxy/roundrobin" + "github.com/vulcand/oxy/v2/roundrobin" ) const ( diff --git a/pkg/middlewares/accesslog/field_middleware.go b/pkg/middlewares/accesslog/field_middleware.go index c4182ab78..17ed9d2ff 100644 --- a/pkg/middlewares/accesslog/field_middleware.go +++ b/pkg/middlewares/accesslog/field_middleware.go @@ -6,7 +6,7 @@ import ( "github.com/traefik/traefik/v2/pkg/log" "github.com/traefik/traefik/v2/pkg/middlewares/capture" - "github.com/vulcand/oxy/utils" + "github.com/vulcand/oxy/v2/utils" ) // FieldApply function hook to add data in accesslog. diff --git a/pkg/middlewares/auth/forward.go b/pkg/middlewares/auth/forward.go index a0cee86ce..92f8b95ff 100644 --- a/pkg/middlewares/auth/forward.go +++ b/pkg/middlewares/auth/forward.go @@ -17,8 +17,8 @@ import ( "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/forward" - "github.com/vulcand/oxy/utils" + "github.com/vulcand/oxy/v2/forward" + "github.com/vulcand/oxy/v2/utils" ) const ( diff --git a/pkg/middlewares/auth/forward_test.go b/pkg/middlewares/auth/forward_test.go index 1aeb5ef9a..4e17f7ff5 100644 --- a/pkg/middlewares/auth/forward_test.go +++ b/pkg/middlewares/auth/forward_test.go @@ -16,7 +16,7 @@ import ( tracingMiddleware "github.com/traefik/traefik/v2/pkg/middlewares/tracing" "github.com/traefik/traefik/v2/pkg/testhelpers" "github.com/traefik/traefik/v2/pkg/tracing" - "github.com/vulcand/oxy/forward" + "github.com/vulcand/oxy/v2/forward" ) func TestForwardAuthFail(t *testing.T) { diff --git a/pkg/middlewares/buffering/buffering.go b/pkg/middlewares/buffering/buffering.go index 5f6ea6b47..34ae428f8 100644 --- a/pkg/middlewares/buffering/buffering.go +++ b/pkg/middlewares/buffering/buffering.go @@ -9,7 +9,7 @@ import ( "github.com/traefik/traefik/v2/pkg/log" "github.com/traefik/traefik/v2/pkg/middlewares" "github.com/traefik/traefik/v2/pkg/tracing" - oxybuffer "github.com/vulcand/oxy/buffer" + oxybuffer "github.com/vulcand/oxy/v2/buffer" ) const ( @@ -34,7 +34,7 @@ func New(ctx context.Context, next http.Handler, config dynamic.Buffering, name oxybuffer.MaxRequestBodyBytes(config.MaxRequestBodyBytes), oxybuffer.MemResponseBodyBytes(config.MemResponseBodyBytes), oxybuffer.MaxResponseBodyBytes(config.MaxResponseBodyBytes), - oxybuffer.CondSetter(len(config.RetryExpression) > 0, oxybuffer.Retry(config.RetryExpression)), + oxybuffer.Cond(len(config.RetryExpression) > 0, oxybuffer.Retry(config.RetryExpression)), ) if err != nil { return nil, err diff --git a/pkg/middlewares/circuitbreaker/circuit_breaker.go b/pkg/middlewares/circuitbreaker/circuit_breaker.go index 77b489e7b..52ce02fea 100644 --- a/pkg/middlewares/circuitbreaker/circuit_breaker.go +++ b/pkg/middlewares/circuitbreaker/circuit_breaker.go @@ -10,7 +10,7 @@ import ( "github.com/traefik/traefik/v2/pkg/log" "github.com/traefik/traefik/v2/pkg/middlewares" "github.com/traefik/traefik/v2/pkg/tracing" - "github.com/vulcand/oxy/cbreaker" + "github.com/vulcand/oxy/v2/cbreaker" ) const typeName = "CircuitBreaker" @@ -28,7 +28,7 @@ func New(ctx context.Context, next http.Handler, confCircuitBreaker dynamic.Circ logger.Debug("Creating middleware") logger.Debugf("Setting up with expression: %s", expression) - cbOpts := []cbreaker.CircuitBreakerOption{ + cbOpts := []cbreaker.Option{ cbreaker.Fallback(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { tracing.SetErrorWithEvent(req, "blocked by circuit-breaker (%q)", expression) rw.WriteHeader(http.StatusServiceUnavailable) diff --git a/pkg/middlewares/customerrors/custom_errors.go b/pkg/middlewares/customerrors/custom_errors.go index dd28aeccd..58b36d115 100644 --- a/pkg/middlewares/customerrors/custom_errors.go +++ b/pkg/middlewares/customerrors/custom_errors.go @@ -16,7 +16,7 @@ import ( "github.com/traefik/traefik/v2/pkg/middlewares" "github.com/traefik/traefik/v2/pkg/tracing" "github.com/traefik/traefik/v2/pkg/types" - "github.com/vulcand/oxy/utils" + "github.com/vulcand/oxy/v2/utils" ) // Compile time validation that the response recorder implements http interfaces correctly. diff --git a/pkg/middlewares/emptybackendhandler/empty_backend_handler_test.go b/pkg/middlewares/emptybackendhandler/empty_backend_handler_test.go index b0d9714d2..657be8201 100644 --- a/pkg/middlewares/emptybackendhandler/empty_backend_handler_test.go +++ b/pkg/middlewares/emptybackendhandler/empty_backend_handler_test.go @@ -9,7 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/traefik/traefik/v2/pkg/testhelpers" - "github.com/vulcand/oxy/roundrobin" + "github.com/vulcand/oxy/v2/roundrobin" ) func TestEmptyBackendHandler(t *testing.T) { diff --git a/pkg/middlewares/extractor.go b/pkg/middlewares/extractor.go index 559ab4ff7..1fc1e9a83 100644 --- a/pkg/middlewares/extractor.go +++ b/pkg/middlewares/extractor.go @@ -8,7 +8,7 @@ import ( "github.com/traefik/traefik/v2/pkg/config/dynamic" "github.com/traefik/traefik/v2/pkg/log" - "github.com/vulcand/oxy/utils" + "github.com/vulcand/oxy/v2/utils" ) // GetSourceExtractor returns the SourceExtractor function corresponding to the given sourceMatcher. diff --git a/pkg/middlewares/inflightreq/inflight_req.go b/pkg/middlewares/inflightreq/inflight_req.go index fb235dd85..757994907 100644 --- a/pkg/middlewares/inflightreq/inflight_req.go +++ b/pkg/middlewares/inflightreq/inflight_req.go @@ -10,7 +10,7 @@ import ( "github.com/traefik/traefik/v2/pkg/log" "github.com/traefik/traefik/v2/pkg/middlewares" "github.com/traefik/traefik/v2/pkg/tracing" - "github.com/vulcand/oxy/connlimit" + "github.com/vulcand/oxy/v2/connlimit" ) const ( diff --git a/pkg/middlewares/ratelimiter/rate_limiter.go b/pkg/middlewares/ratelimiter/rate_limiter.go index 403c97199..312595bfc 100644 --- a/pkg/middlewares/ratelimiter/rate_limiter.go +++ b/pkg/middlewares/ratelimiter/rate_limiter.go @@ -14,7 +14,7 @@ import ( "github.com/traefik/traefik/v2/pkg/log" "github.com/traefik/traefik/v2/pkg/middlewares" "github.com/traefik/traefik/v2/pkg/tracing" - "github.com/vulcand/oxy/utils" + "github.com/vulcand/oxy/v2/utils" "golang.org/x/time/rate" ) diff --git a/pkg/middlewares/ratelimiter/rate_limiter_test.go b/pkg/middlewares/ratelimiter/rate_limiter_test.go index 26d18580b..ac73fc2ec 100644 --- a/pkg/middlewares/ratelimiter/rate_limiter_test.go +++ b/pkg/middlewares/ratelimiter/rate_limiter_test.go @@ -14,7 +14,7 @@ import ( ptypes "github.com/traefik/paerser/types" "github.com/traefik/traefik/v2/pkg/config/dynamic" "github.com/traefik/traefik/v2/pkg/testhelpers" - "github.com/vulcand/oxy/utils" + "github.com/vulcand/oxy/v2/utils" ) func TestNewRateLimiter(t *testing.T) { diff --git a/pkg/middlewares/redirect/redirect.go b/pkg/middlewares/redirect/redirect.go index e09611e37..a48053f8a 100644 --- a/pkg/middlewares/redirect/redirect.go +++ b/pkg/middlewares/redirect/redirect.go @@ -7,7 +7,7 @@ import ( "github.com/opentracing/opentracing-go/ext" "github.com/traefik/traefik/v2/pkg/tracing" - "github.com/vulcand/oxy/utils" + "github.com/vulcand/oxy/v2/utils" ) const ( diff --git a/pkg/server/service/service.go b/pkg/server/service/service.go index 67c2d97d1..a29208aa6 100644 --- a/pkg/server/service/service.go +++ b/pkg/server/service/service.go @@ -27,8 +27,8 @@ import ( "github.com/traefik/traefik/v2/pkg/server/service/loadbalancer/failover" "github.com/traefik/traefik/v2/pkg/server/service/loadbalancer/mirror" "github.com/traefik/traefik/v2/pkg/server/service/loadbalancer/wrr" - "github.com/vulcand/oxy/roundrobin" - "github.com/vulcand/oxy/roundrobin/stickycookie" + "github.com/vulcand/oxy/v2/roundrobin" + "github.com/vulcand/oxy/v2/roundrobin/stickycookie" ) const ( From 519ed8bde58e9ebc6d9618a38a351208820cf273 Mon Sep 17 00:00:00 2001 From: Romain Date: Mon, 5 Dec 2022 16:58:04 +0100 Subject: [PATCH 11/19] Prepare release v3.0.0-beta1 --- .github/PULL_REQUEST_TEMPLATE.md | 8 +- .semaphore/semaphore.yml | 2 +- CHANGELOG.md | 46 +++++++ .../getting-started/configuration-overview.md | 2 +- .../getting-started/install-traefik.md | 4 +- .../quick-start-with-kubernetes.md | 2 +- docs/content/getting-started/quick-start.md | 2 +- docs/content/observability/access-logs.md | 2 +- docs/content/providers/docker.md | 2 +- .../kubernetes-crd-definition-v1.yml | 130 +++++++++--------- .../kubernetes-gateway-traefik-lb-svc.yml | 2 +- .../traefik.containo.us_ingressroutes.yaml | 26 ++-- .../traefik.containo.us_ingressroutetcps.yaml | 16 +-- .../traefik.containo.us_ingressrouteudps.yaml | 2 +- .../traefik.containo.us_middlewares.yaml | 62 ++++----- .../traefik.containo.us_middlewaretcps.yaml | 2 +- ...traefik.containo.us_serverstransports.yaml | 2 +- .../traefik.containo.us_tlsoptions.yaml | 8 +- .../traefik.containo.us_tlsstores.yaml | 2 +- .../traefik.containo.us_traefikservices.yaml | 10 +- .../routing/providers/kubernetes-crd.md | 2 +- .../routing/providers/kubernetes-ingress.md | 6 +- .../user-guides/crd-acme/03-deployments.yml | 2 +- docs/content/user-guides/crd-acme/k3s.yml | 2 +- .../acme-dns/docker-compose.yml | 2 +- .../acme-dns/docker-compose_secrets.yml | 2 +- .../acme-http/docker-compose.yml | 2 +- .../acme-tls/docker-compose.yml | 2 +- .../basic-example/docker-compose.yml | 2 +- .../docker-compose/basic-example/index.md | 2 +- integration/fixtures/k8s/01-traefik-crd.yml | 130 +++++++++--------- pkg/config/dynamic/middlewares.go | 48 +++---- pkg/config/dynamic/tcp_config.go | 2 +- pkg/config/dynamic/tcp_middlewares.go | 2 +- .../crd/traefik/v1alpha1/ingressroute.go | 28 ++-- .../crd/traefik/v1alpha1/ingressroutetcp.go | 18 +-- .../crd/traefik/v1alpha1/ingressrouteudp.go | 2 +- .../crd/traefik/v1alpha1/middleware.go | 24 ++-- .../crd/traefik/v1alpha1/middlewaretcp.go | 2 +- .../crd/traefik/v1alpha1/serverstransport.go | 2 +- .../crd/traefik/v1alpha1/service.go | 8 +- .../crd/traefik/v1alpha1/tlsoption.go | 8 +- .../crd/traefik/v1alpha1/tlsstore.go | 2 +- script/gcg/traefik-rc-first.toml | 6 +- 44 files changed, 342 insertions(+), 296 deletions(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 31100ab46..0df46760d 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -2,16 +2,16 @@ PLEASE READ THIS MESSAGE. Documentation fixes or enhancements: -- for Traefik v1: use branch v1.7 - for Traefik v2: use branch v2.9 +- for Traefik v3: use branch master Bug fixes: -- for Traefik v1: use branch v1.7 - for Traefik v2: use branch v2.9 +- for Traefik v3: use branch master Enhancements: -- for Traefik v1: we only accept bug fixes -- for Traefik v2: use branch master +- for Traefik v2: we only accept bug fixes +- for Traefik v3: use branch master HOW TO WRITE A GOOD PULL REQUEST? https://doc.traefik.io/traefik/contributing/submitting-pull-requests/ diff --git a/.semaphore/semaphore.yml b/.semaphore/semaphore.yml index 1d1b9a3a4..2aab6ab3e 100644 --- a/.semaphore/semaphore.yml +++ b/.semaphore/semaphore.yml @@ -64,7 +64,7 @@ blocks: - name: GH_VERSION value: 1.12.1 - name: CODENAME - value: "banon" + value: "beaufort" - name: IN_DOCKER value: "" prologue: diff --git a/CHANGELOG.md b/CHANGELOG.md index 2660f4888..a114f5c4f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,49 @@ +## [v3.0.0-beta1](https://github.com/traefik/traefik/tree/v3.0.0-beta1) (2022-12-05) +[All Commits](https://github.com/traefik/traefik/compare/v2.9.0-rc1...v3.0.0-beta1) + +**Enhancements:** +- **[ecs]** Add option to keep only healthy ECS tasks ([#8027](https://github.com/traefik/traefik/pull/8027) by [Michampt](https://github.com/Michampt)) +- **[healthcheck]** Support gRPC healthcheck ([#8583](https://github.com/traefik/traefik/pull/8583) by [jjacque](https://github.com/jjacque)) +- **[healthcheck]** Add a status option to the service health check ([#9463](https://github.com/traefik/traefik/pull/9463) by [guoard](https://github.com/guoard)) +- **[http]** Support custom headers when fetching configuration through HTTP ([#9421](https://github.com/traefik/traefik/pull/9421) by [kevinpollet](https://github.com/kevinpollet)) +- **[logs,performance]** New logger for the Traefik logs ([#9515](https://github.com/traefik/traefik/pull/9515) by [ldez](https://github.com/ldez)) +- **[logs,plugins]** Retry on plugin API calls ([#9530](https://github.com/traefik/traefik/pull/9530) by [ldez](https://github.com/ldez)) +- **[logs,provider]** Improve provider logs ([#9562](https://github.com/traefik/traefik/pull/9562) by [ldez](https://github.com/ldez)) +- **[logs]** Improve test logger assertions ([#9533](https://github.com/traefik/traefik/pull/9533) by [ldez](https://github.com/ldez)) +- **[metrics]** Support gRPC and gRPC-Web protocol in metrics ([#9483](https://github.com/traefik/traefik/pull/9483) by [longit644](https://github.com/longit644)) +- **[middleware,accesslogs]** Log TLS client subject ([#9285](https://github.com/traefik/traefik/pull/9285) by [xmessi](https://github.com/xmessi)) +- **[middleware,metrics,tracing]** Add OpenTelemetry tracing and metrics support ([#8999](https://github.com/traefik/traefik/pull/8999) by [tomMoulard](https://github.com/tomMoulard)) +- **[middleware]** Disable Content-Type auto-detection by default ([#9546](https://github.com/traefik/traefik/pull/9546) by [sdelicata](https://github.com/sdelicata)) +- **[middleware]** Add gRPC-Web middleware ([#9451](https://github.com/traefik/traefik/pull/9451) by [juliens](https://github.com/juliens)) +- **[middleware]** Add support for Brotli ([#9387](https://github.com/traefik/traefik/pull/9387) by [glinton](https://github.com/glinton)) +- **[middleware]** Renaming IPWhiteList to IPAllowList ([#9457](https://github.com/traefik/traefik/pull/9457) by [wxmbugu](https://github.com/wxmbugu)) +- **[nomad]** Support multiple namespaces in the Nomad Provider ([#9332](https://github.com/traefik/traefik/pull/9332) by [0teh](https://github.com/0teh)) +- **[rules]** Update routing syntax ([#9531](https://github.com/traefik/traefik/pull/9531) by [skwair](https://github.com/skwair)) +- **[server]** Rework servers load-balancer to use the WRR ([#9431](https://github.com/traefik/traefik/pull/9431) by [juliens](https://github.com/juliens)) +- **[server]** Allow default entrypoints definition ([#9100](https://github.com/traefik/traefik/pull/9100) by [jilleJr](https://github.com/jilleJr)) +- **[tls,service]** Support SPIFFE mTLS between Traefik and Backend servers ([#9394](https://github.com/traefik/traefik/pull/9394) by [jlevesy](https://github.com/jlevesy)) +- **[tls]** Add Tailscale certificate resolver ([#9237](https://github.com/traefik/traefik/pull/9237) by [kevinpollet](https://github.com/kevinpollet)) +- **[tls]** Support SNI routing with Postgres STARTTLS connections ([#9377](https://github.com/traefik/traefik/pull/9377) by [rtribotte](https://github.com/rtribotte)) +- Remove deprecated options ([#9527](https://github.com/traefik/traefik/pull/9527) by [sdelicata](https://github.com/sdelicata)) + +**Bug fixes:** +- **[logs]** Fix log level ([#9545](https://github.com/traefik/traefik/pull/9545) by [ldez](https://github.com/ldez)) +- **[metrics]** Fix ServerUp metric ([#9534](https://github.com/traefik/traefik/pull/9534) by [kevinpollet](https://github.com/kevinpollet)) +- **[tls,service]** Enforce default servers transport SPIFFE config ([#9444](https://github.com/traefik/traefik/pull/9444) by [jlevesy](https://github.com/jlevesy)) + +**Documentation:** +- **[metrics]** Update and publish official Grafana Dashboard ([#9493](https://github.com/traefik/traefik/pull/9493) by [mloiseleur](https://github.com/mloiseleur)) + +**Misc:** +- Merge branch v2.9 into master ([#9554](https://github.com/traefik/traefik/pull/9554) by [ldez](https://github.com/ldez)) +- Merge branch v2.9 into master ([#9536](https://github.com/traefik/traefik/pull/9536) by [ldez](https://github.com/ldez)) +- Merge branch v2.9 into master ([#9532](https://github.com/traefik/traefik/pull/9532) by [ldez](https://github.com/ldez)) +- Merge branch v2.9 into master ([#9482](https://github.com/traefik/traefik/pull/9482) by [kevinpollet](https://github.com/kevinpollet)) +- Merge branch v2.9 into master ([#9464](https://github.com/traefik/traefik/pull/9464) by [ldez](https://github.com/ldez)) +- Merge branch v2.9 into master ([#9449](https://github.com/traefik/traefik/pull/9449) by [kevinpollet](https://github.com/kevinpollet)) +- Merge branch v2.9 into master ([#9419](https://github.com/traefik/traefik/pull/9419) by [kevinpollet](https://github.com/kevinpollet)) +- Merge branch v2.9 into master ([#9351](https://github.com/traefik/traefik/pull/9351) by [rtribotte](https://github.com/rtribotte)) + ## [v2.9.5](https://github.com/traefik/traefik/tree/v2.9.5) (2022-11-17) [All Commits](https://github.com/traefik/traefik/compare/v2.9.4...v2.9.5) diff --git a/docs/content/getting-started/configuration-overview.md b/docs/content/getting-started/configuration-overview.md index f99c06925..261103b87 100644 --- a/docs/content/getting-started/configuration-overview.md +++ b/docs/content/getting-started/configuration-overview.md @@ -79,7 +79,7 @@ traefik --help # or docker run traefik[:version] --help -# ex: docker run traefik:v2.9 --help +# ex: docker run traefik:v3.0 --help ``` All available arguments can also be found [here](../reference/static-configuration/cli.md). diff --git a/docs/content/getting-started/install-traefik.md b/docs/content/getting-started/install-traefik.md index dfc592b81..9e4926fc0 100644 --- a/docs/content/getting-started/install-traefik.md +++ b/docs/content/getting-started/install-traefik.md @@ -21,7 +21,7 @@ Choose one of the [official Docker images](https://hub.docker.com/_/traefik) and ```bash docker run -d -p 8080:8080 -p 80:80 \ - -v $PWD/traefik.yml:/etc/traefik/traefik.yml traefik:v2.9 + -v $PWD/traefik.yml:/etc/traefik/traefik.yml traefik:v3.0 ``` For more details, go to the [Docker provider documentation](../providers/docker.md) @@ -29,7 +29,7 @@ For more details, go to the [Docker provider documentation](../providers/docker. !!! tip * Prefer a fixed version than the latest that could be an unexpected version. - ex: `traefik:v2.9` + ex: `traefik:v3.0` * Docker images are based from the [Alpine Linux Official image](https://hub.docker.com/_/alpine). * Any orchestrator using docker images can fetch the official Traefik docker image. diff --git a/docs/content/getting-started/quick-start-with-kubernetes.md b/docs/content/getting-started/quick-start-with-kubernetes.md index 8626278b5..fb4472270 100644 --- a/docs/content/getting-started/quick-start-with-kubernetes.md +++ b/docs/content/getting-started/quick-start-with-kubernetes.md @@ -130,7 +130,7 @@ spec: serviceAccountName: traefik-account containers: - name: traefik - image: traefik:v2.9 + image: traefik:v3.0 args: - --api.insecure - --providers.kubernetesingress diff --git a/docs/content/getting-started/quick-start.md b/docs/content/getting-started/quick-start.md index 6a411666e..e1883040d 100644 --- a/docs/content/getting-started/quick-start.md +++ b/docs/content/getting-started/quick-start.md @@ -20,7 +20,7 @@ version: '3' services: reverse-proxy: # The official v2 Traefik docker image - image: traefik:v2.9 + image: traefik:v3.0 # Enables the web UI and tells Traefik to listen to docker command: --api.insecure=true --providers.docker ports: diff --git a/docs/content/observability/access-logs.md b/docs/content/observability/access-logs.md index 98fca36ef..9b210b636 100644 --- a/docs/content/observability/access-logs.md +++ b/docs/content/observability/access-logs.md @@ -255,7 +255,7 @@ version: "3.7" services: traefik: - image: traefik:v2.9 + image: traefik:v3.0 environment: - TZ=US/Alaska command: diff --git a/docs/content/providers/docker.md b/docs/content/providers/docker.md index b34752eec..8fd567356 100644 --- a/docs/content/providers/docker.md +++ b/docs/content/providers/docker.md @@ -265,7 +265,7 @@ See the sections [Docker API Access](#docker-api-access) and [Docker Swarm API A services: traefik: - image: traefik:v2.9 # The official v2 Traefik docker image + image: traefik:v3.0 # The official v2 Traefik docker image ports: - "80:80" volumes: diff --git a/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml b/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml index 0a8f01823..4a7f19a66 100644 --- a/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml +++ b/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml @@ -39,7 +39,7 @@ spec: entryPoints: description: 'EntryPoints defines the list of entry point names to bind to. Entry points have to be configured in the static configuration. - More info: https://doc.traefik.io/traefik/v2.9/routing/entrypoints/ + More info: https://doc.traefik.io/traefik/v3.0/routing/entrypoints/ Default: all.' items: type: string @@ -56,11 +56,11 @@ spec: - Rule type: string match: - description: 'Match defines the router''s rule. More info: https://doc.traefik.io/traefik/v2.9/routing/routers/#rule' + description: 'Match defines the router''s rule. More info: https://doc.traefik.io/traefik/v3.0/routing/routers/#rule' type: string middlewares: description: 'Middlewares defines the list of references to - Middleware resources. More info: https://doc.traefik.io/traefik/v2.9/routing/providers/kubernetes-crd/#kind-middleware' + Middleware resources. More info: https://doc.traefik.io/traefik/v3.0/routing/providers/kubernetes-crd/#kind-middleware' items: description: MiddlewareRef is a reference to a Middleware resource. @@ -79,7 +79,7 @@ spec: type: array priority: description: 'Priority defines the router''s priority. More - info: https://doc.traefik.io/traefik/v2.9/routing/routers/#priority' + info: https://doc.traefik.io/traefik/v3.0/routing/routers/#priority' type: integer services: description: Services defines the list of Service. It can contain @@ -145,7 +145,7 @@ spec: type: string sticky: description: 'Sticky defines the sticky sessions configuration. - More info: https://doc.traefik.io/traefik/v2.9/routing/services/#sticky-sessions' + More info: https://doc.traefik.io/traefik/v3.0/routing/services/#sticky-sessions' properties: cookie: description: Cookie defines the sticky cookie configuration. @@ -190,16 +190,16 @@ spec: type: object type: array tls: - description: 'TLS defines the TLS configuration. More info: https://doc.traefik.io/traefik/v2.9/routing/routers/#tls' + description: 'TLS defines the TLS configuration. More info: https://doc.traefik.io/traefik/v3.0/routing/routers/#tls' properties: certResolver: description: 'CertResolver defines the name of the certificate resolver to use. Cert resolvers have to be configured in the - static configuration. More info: https://doc.traefik.io/traefik/v2.9/https/acme/#certificate-resolvers' + static configuration. More info: https://doc.traefik.io/traefik/v3.0/https/acme/#certificate-resolvers' type: string domains: description: 'Domains defines the list of domains that will be - used to issue certificates. More info: https://doc.traefik.io/traefik/v2.9/routing/routers/#domains' + used to issue certificates. More info: https://doc.traefik.io/traefik/v3.0/routing/routers/#domains' items: description: Domain holds a domain name with SANs. properties: @@ -217,15 +217,15 @@ spec: options: description: 'Options defines the reference to a TLSOption, that specifies the parameters of the TLS connection. If not defined, - the `default` TLSOption is used. More info: https://doc.traefik.io/traefik/v2.9/https/tls/#tls-options' + the `default` TLSOption is used. More info: https://doc.traefik.io/traefik/v3.0/https/tls/#tls-options' properties: name: description: 'Name defines the name of the referenced TLSOption. - More info: https://doc.traefik.io/traefik/v2.9/routing/providers/kubernetes-crd/#kind-tlsoption' + More info: https://doc.traefik.io/traefik/v3.0/routing/providers/kubernetes-crd/#kind-tlsoption' type: string namespace: description: 'Namespace defines the namespace of the referenced - TLSOption. More info: https://doc.traefik.io/traefik/v2.9/routing/providers/kubernetes-crd/#kind-tlsoption' + TLSOption. More info: https://doc.traefik.io/traefik/v3.0/routing/providers/kubernetes-crd/#kind-tlsoption' type: string required: - name @@ -241,11 +241,11 @@ spec: properties: name: description: 'Name defines the name of the referenced TLSStore. - More info: https://doc.traefik.io/traefik/v2.9/routing/providers/kubernetes-crd/#kind-tlsstore' + More info: https://doc.traefik.io/traefik/v3.0/routing/providers/kubernetes-crd/#kind-tlsstore' type: string namespace: description: 'Namespace defines the namespace of the referenced - TLSStore. More info: https://doc.traefik.io/traefik/v2.9/routing/providers/kubernetes-crd/#kind-tlsstore' + TLSStore. More info: https://doc.traefik.io/traefik/v3.0/routing/providers/kubernetes-crd/#kind-tlsstore' type: string required: - name @@ -307,7 +307,7 @@ spec: entryPoints: description: 'EntryPoints defines the list of entry point names to bind to. Entry points have to be configured in the static configuration. - More info: https://doc.traefik.io/traefik/v2.9/routing/entrypoints/ + More info: https://doc.traefik.io/traefik/v3.0/routing/entrypoints/ Default: all.' items: type: string @@ -318,7 +318,7 @@ spec: description: RouteTCP holds the TCP route configuration. properties: match: - description: 'Match defines the router''s rule. More info: https://doc.traefik.io/traefik/v2.9/routing/routers/#rule_1' + description: 'Match defines the router''s rule. More info: https://doc.traefik.io/traefik/v3.0/routing/routers/#rule_1' type: string middlewares: description: Middlewares defines the list of references to MiddlewareTCP @@ -341,7 +341,7 @@ spec: type: array priority: description: 'Priority defines the router''s priority. More - info: https://doc.traefik.io/traefik/v2.9/routing/routers/#priority_1' + info: https://doc.traefik.io/traefik/v3.0/routing/routers/#priority_1' type: integer services: description: Services defines the list of TCP services. @@ -366,7 +366,7 @@ spec: x-kubernetes-int-or-string: true proxyProtocol: description: 'ProxyProtocol defines the PROXY protocol - configuration. More info: https://doc.traefik.io/traefik/v2.9/routing/services/#proxy-protocol' + configuration. More info: https://doc.traefik.io/traefik/v3.0/routing/services/#proxy-protocol' properties: version: description: Version defines the PROXY Protocol version @@ -397,16 +397,16 @@ spec: type: array tls: description: 'TLS defines the TLS configuration on a layer 4 / TCP - Route. More info: https://doc.traefik.io/traefik/v2.9/routing/routers/#tls_1' + Route. More info: https://doc.traefik.io/traefik/v3.0/routing/routers/#tls_1' properties: certResolver: description: 'CertResolver defines the name of the certificate resolver to use. Cert resolvers have to be configured in the - static configuration. More info: https://doc.traefik.io/traefik/v2.9/https/acme/#certificate-resolvers' + static configuration. More info: https://doc.traefik.io/traefik/v3.0/https/acme/#certificate-resolvers' type: string domains: description: 'Domains defines the list of domains that will be - used to issue certificates. More info: https://doc.traefik.io/traefik/v2.9/routing/routers/#domains' + used to issue certificates. More info: https://doc.traefik.io/traefik/v3.0/routing/routers/#domains' items: description: Domain holds a domain name with SANs. properties: @@ -424,7 +424,7 @@ spec: options: description: 'Options defines the reference to a TLSOption, that specifies the parameters of the TLS connection. If not defined, - the `default` TLSOption is used. More info: https://doc.traefik.io/traefik/v2.9/https/tls/#tls-options' + the `default` TLSOption is used. More info: https://doc.traefik.io/traefik/v3.0/https/tls/#tls-options' properties: name: description: Name defines the name of the referenced Traefik @@ -518,7 +518,7 @@ spec: entryPoints: description: 'EntryPoints defines the list of entry point names to bind to. Entry points have to be configured in the static configuration. - More info: https://doc.traefik.io/traefik/v2.9/routing/entrypoints/ + More info: https://doc.traefik.io/traefik/v3.0/routing/entrypoints/ Default: all.' items: type: string @@ -597,7 +597,7 @@ spec: schema: openAPIV3Schema: description: 'Middleware is the CRD implementation of a Traefik Middleware. - More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/overview/' + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/overview/' properties: apiVersion: description: 'APIVersion defines the versioned schema of this representation @@ -617,7 +617,7 @@ spec: addPrefix: description: 'AddPrefix holds the add prefix middleware configuration. This middleware updates the path of a request before forwarding - it. More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/addprefix/' + it. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/addprefix/' properties: prefix: description: Prefix is the string to add before the current path @@ -627,11 +627,11 @@ spec: basicAuth: description: 'BasicAuth holds the basic auth middleware configuration. This middleware restricts access to your services to known users. - More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/basicauth/' + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/basicauth/' properties: headerField: description: 'HeaderField defines a header field to store the - authenticated user. More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/basicauth/#headerfield' + authenticated user. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/basicauth/#headerfield' type: string realm: description: 'Realm allows the protected resources on a server @@ -651,7 +651,7 @@ spec: buffering: description: 'Buffering holds the buffering middleware configuration. This middleware retries or limits the size of requests that can - be forwarded to backends. More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/buffering/#maxrequestbodybytes' + be forwarded to backends. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/buffering/#maxrequestbodybytes' properties: maxRequestBodyBytes: description: 'MaxRequestBodyBytes defines the maximum allowed @@ -684,13 +684,13 @@ spec: retryExpression: description: 'RetryExpression defines the retry conditions. It is a logical combination of functions with operators AND (&&) - and OR (||). More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/buffering/#retryexpression' + and OR (||). More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/buffering/#retryexpression' type: string type: object chain: description: 'Chain holds the configuration of the chain middleware. This middleware enables to define reusable combinations of other - pieces of middleware. More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/chain/' + pieces of middleware. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/chain/' properties: middlewares: description: Middlewares is the list of MiddlewareRef which composes @@ -744,7 +744,7 @@ spec: compress: description: 'Compress holds the compress middleware configuration. This middleware compresses responses before sending them to the - client, using gzip compression. More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/compress/' + client, using gzip compression. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/compress/' properties: excludedContentTypes: description: ExcludedContentTypes defines the list of content @@ -769,11 +769,11 @@ spec: digestAuth: description: 'DigestAuth holds the digest auth middleware configuration. This middleware restricts access to your services to known users. - More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/digestauth/' + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/digestauth/' properties: headerField: description: 'HeaderField defines a header field to store the - authenticated user. More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/basicauth/#headerfield' + authenticated user. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/basicauth/#headerfield' type: string realm: description: 'Realm allows the protected resources on a server @@ -792,7 +792,7 @@ spec: errors: description: 'ErrorPage holds the custom error middleware configuration. This middleware returns a custom page in lieu of the default, according - to configured ranges of HTTP Status codes. More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/errorpages/' + to configured ranges of HTTP Status codes. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/errorpages/' properties: query: description: Query defines the URL for the error page (hosted @@ -801,7 +801,7 @@ spec: type: string service: description: 'Service defines the reference to a Kubernetes Service - that will serve the error page. More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/errorpages/#service' + that will serve the error page. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/errorpages/#service' properties: kind: description: Kind defines the kind of the Service. @@ -858,7 +858,7 @@ spec: type: string sticky: description: 'Sticky defines the sticky sessions configuration. - More info: https://doc.traefik.io/traefik/v2.9/routing/services/#sticky-sessions' + More info: https://doc.traefik.io/traefik/v3.0/routing/services/#sticky-sessions' properties: cookie: description: Cookie defines the sticky cookie configuration. @@ -907,7 +907,7 @@ spec: forwardAuth: description: 'ForwardAuth holds the forward auth middleware configuration. This middleware delegates the request authentication to a Service. - More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/forwardauth/' + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/forwardauth/' properties: address: description: Address defines the authentication server address. @@ -930,7 +930,7 @@ spec: description: 'AuthResponseHeadersRegex defines the regex to match headers to copy from the authentication server response and set on forwarded request, after stripping all headers that match - the regex. More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/forwardauth/#authresponseheadersregex' + the regex. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/forwardauth/#authresponseheadersregex' type: string tls: description: TLS defines the configuration used to secure the @@ -970,7 +970,7 @@ spec: headers: description: 'Headers holds the headers middleware configuration. This middleware manages the requests and responses headers. More - info: https://doc.traefik.io/traefik/v2.9/middlewares/http/headers/#customrequestheaders' + info: https://doc.traefik.io/traefik/v3.0/middlewares/http/headers/#customrequestheaders' properties: accessControlAllowCredentials: description: AccessControlAllowCredentials defines whether the @@ -1114,7 +1114,7 @@ spec: inFlightReq: description: 'InFlightReq holds the in-flight request middleware configuration. This middleware limits the number of requests being processed and - served concurrently. More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/inflightreq/' + served concurrently. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/inflightreq/' properties: amount: description: Amount defines the maximum amount of allowed simultaneous @@ -1128,11 +1128,11 @@ spec: group requests as originating from a common source. If several strategies are defined at the same time, an error will be raised. If none are set, the default is to use the requestHost. More - info: https://doc.traefik.io/traefik/v2.9/middlewares/http/inflightreq/#sourcecriterion' + info: https://doc.traefik.io/traefik/v3.0/middlewares/http/inflightreq/#sourcecriterion' properties: ipStrategy: description: 'IPStrategy holds the IP strategy configuration - used by Traefik to determine the client IP. More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/ipallowlist/#ipstrategy' + used by Traefik to determine the client IP. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/ipallowlist/#ipstrategy' properties: depth: description: Depth tells Traefik to use the X-Forwarded-For @@ -1160,11 +1160,11 @@ spec: ipAllowList: description: 'IPAllowList holds the IP allowlist middleware configuration. This middleware accepts / refuses requests based on the client IP. - More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/ipallowlist/' + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/ipallowlist/' properties: ipStrategy: description: 'IPStrategy holds the IP strategy configuration used - by Traefik to determine the client IP. More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/ipallowlist/#ipstrategy' + by Traefik to determine the client IP. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/ipallowlist/#ipstrategy' properties: depth: description: Depth tells Traefik to use the X-Forwarded-For @@ -1188,7 +1188,7 @@ spec: passTLSClientCert: description: 'PassTLSClientCert holds the pass TLS client cert middleware configuration. This middleware adds the selected data from the passed - client TLS certificate to a header. More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/passtlsclientcert/' + client TLS certificate to a header. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/passtlsclientcert/' properties: info: description: Info selects the specific client certificate details @@ -1295,7 +1295,7 @@ spec: rateLimit: description: 'RateLimit holds the rate limit configuration. This middleware ensures that services will receive a fair amount of requests, and - allows one to define what fair is. More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/ratelimit/' + allows one to define what fair is. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/ratelimit/' properties: average: description: Average is the maximum rate, by default in requests/s, @@ -1328,7 +1328,7 @@ spec: properties: ipStrategy: description: 'IPStrategy holds the IP strategy configuration - used by Traefik to determine the client IP. More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/ipallowlist/#ipstrategy' + used by Traefik to determine the client IP. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/ipallowlist/#ipstrategy' properties: depth: description: Depth tells Traefik to use the X-Forwarded-For @@ -1356,7 +1356,7 @@ spec: redirectRegex: description: 'RedirectRegex holds the redirect regex middleware configuration. This middleware redirects a request using regex matching and replacement. - More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/redirectregex/#regex' + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/redirectregex/#regex' properties: permanent: description: Permanent defines whether the redirection is permanent @@ -1374,7 +1374,7 @@ spec: redirectScheme: description: 'RedirectScheme holds the redirect scheme middleware configuration. This middleware redirects requests from a scheme/port - to another. More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/redirectscheme/' + to another. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/redirectscheme/' properties: permanent: description: Permanent defines whether the redirection is permanent @@ -1390,7 +1390,7 @@ spec: replacePath: description: 'ReplacePath holds the replace path middleware configuration. This middleware replaces the path of the request URL and store the - original path in an X-Replaced-Path header. More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/replacepath/' + original path in an X-Replaced-Path header. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/replacepath/' properties: path: description: Path defines the path to use as replacement in the @@ -1400,7 +1400,7 @@ spec: replacePathRegex: description: 'ReplacePathRegex holds the replace path regex middleware configuration. This middleware replaces the path of a URL using - regex matching and replacement. More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/replacepathregex/' + regex matching and replacement. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/replacepathregex/' properties: regex: description: Regex defines the regular expression used to match @@ -1416,7 +1416,7 @@ spec: middleware reissues requests a given number of times to a backend server if that server does not reply. As soon as the server answers, the middleware stops retrying, regardless of the response status. - More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/retry/' + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/retry/' properties: attempts: description: Attempts defines how many times the request should @@ -1436,7 +1436,7 @@ spec: stripPrefix: description: 'StripPrefix holds the strip prefix middleware configuration. This middleware removes the specified prefixes from the URL path. - More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/stripprefix/' + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/stripprefix/' properties: prefixes: description: Prefixes defines the prefixes to strip from the request @@ -1448,7 +1448,7 @@ spec: stripPrefixRegex: description: 'StripPrefixRegex holds the strip prefix regex middleware configuration. This middleware removes the matching prefixes from - the URL path. More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/stripprefixregex/' + the URL path. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/stripprefixregex/' properties: regex: description: Regex defines the regular expression to match the @@ -1492,7 +1492,7 @@ spec: schema: openAPIV3Schema: description: 'MiddlewareTCP is the CRD implementation of a Traefik TCP middleware. - More info: https://doc.traefik.io/traefik/v2.9/middlewares/overview/' + More info: https://doc.traefik.io/traefik/v3.0/middlewares/overview/' properties: apiVersion: description: 'APIVersion defines the versioned schema of this representation @@ -1566,7 +1566,7 @@ spec: description: 'ServersTransport is the CRD implementation of a ServersTransport. If no serversTransport is specified, the default@internal will be used. The default@internal serversTransport is created from the static configuration. - More info: https://doc.traefik.io/traefik/v2.9/routing/services/#serverstransport_1' + More info: https://doc.traefik.io/traefik/v3.0/routing/services/#serverstransport_1' properties: apiVersion: description: 'APIVersion defines the versioned schema of this representation @@ -1706,7 +1706,7 @@ spec: openAPIV3Schema: description: 'TLSOption is the CRD implementation of a Traefik TLS Option, allowing to configure some parameters of the TLS connection. More info: - https://doc.traefik.io/traefik/v2.9/https/tls/#tls-options' + https://doc.traefik.io/traefik/v3.0/https/tls/#tls-options' properties: apiVersion: description: 'APIVersion defines the versioned schema of this representation @@ -1726,13 +1726,13 @@ spec: alpnProtocols: description: 'ALPNProtocols defines the list of supported application level protocols for the TLS handshake, in order of preference. More - info: https://doc.traefik.io/traefik/v2.9/https/tls/#alpn-protocols' + info: https://doc.traefik.io/traefik/v3.0/https/tls/#alpn-protocols' items: type: string type: array cipherSuites: description: 'CipherSuites defines the list of supported cipher suites - for TLS versions up to TLS 1.2. More info: https://doc.traefik.io/traefik/v2.9/https/tls/#cipher-suites' + for TLS versions up to TLS 1.2. More info: https://doc.traefik.io/traefik/v3.0/https/tls/#cipher-suites' items: type: string type: array @@ -1759,7 +1759,7 @@ spec: type: object curvePreferences: description: 'CurvePreferences defines the preferred elliptic curves - in a specific order. More info: https://doc.traefik.io/traefik/v2.9/https/tls/#curve-preferences' + in a specific order. More info: https://doc.traefik.io/traefik/v3.0/https/tls/#curve-preferences' items: type: string type: array @@ -1814,7 +1814,7 @@ spec: description: 'TLSStore is the CRD implementation of a Traefik TLS Store. For the time being, only the TLSStore named default is supported. This means that you cannot have two stores that are named default in different Kubernetes - namespaces. More info: https://doc.traefik.io/traefik/v2.9/https/tls/#certificates-stores' + namespaces. More info: https://doc.traefik.io/traefik/v3.0/https/tls/#certificates-stores' properties: apiVersion: description: 'APIVersion defines the versioned schema of this representation @@ -1912,7 +1912,7 @@ spec: openAPIV3Schema: description: 'TraefikService is the CRD implementation of a Traefik Service. TraefikService object allows to: - Apply weight to Services on load-balancing - - Mirror traffic on services More info: https://doc.traefik.io/traefik/v2.9/routing/providers/kubernetes-crd/#kind-traefikservice' + - Mirror traffic on services More info: https://doc.traefik.io/traefik/v3.0/routing/providers/kubernetes-crd/#kind-traefikservice' properties: apiVersion: description: 'APIVersion defines the versioned schema of this representation @@ -2011,7 +2011,7 @@ spec: type: string sticky: description: 'Sticky defines the sticky sessions configuration. - More info: https://doc.traefik.io/traefik/v2.9/routing/services/#sticky-sessions' + More info: https://doc.traefik.io/traefik/v3.0/routing/services/#sticky-sessions' properties: cookie: description: Cookie defines the sticky cookie configuration. @@ -2095,7 +2095,7 @@ spec: type: string sticky: description: 'Sticky defines the sticky sessions configuration. - More info: https://doc.traefik.io/traefik/v2.9/routing/services/#sticky-sessions' + More info: https://doc.traefik.io/traefik/v3.0/routing/services/#sticky-sessions' properties: cookie: description: Cookie defines the sticky cookie configuration. @@ -2195,7 +2195,7 @@ spec: type: string sticky: description: 'Sticky defines the sticky sessions configuration. - More info: https://doc.traefik.io/traefik/v2.9/routing/services/#sticky-sessions' + More info: https://doc.traefik.io/traefik/v3.0/routing/services/#sticky-sessions' properties: cookie: description: Cookie defines the sticky cookie configuration. @@ -2234,7 +2234,7 @@ spec: type: array sticky: description: 'Sticky defines whether sticky sessions are enabled. - More info: https://doc.traefik.io/traefik/v2.9/routing/providers/kubernetes-crd/#stickiness-and-load-balancing' + More info: https://doc.traefik.io/traefik/v3.0/routing/providers/kubernetes-crd/#stickiness-and-load-balancing' properties: cookie: description: Cookie defines the sticky cookie configuration. diff --git a/docs/content/reference/dynamic-configuration/kubernetes-gateway-traefik-lb-svc.yml b/docs/content/reference/dynamic-configuration/kubernetes-gateway-traefik-lb-svc.yml index e4b477e78..c9f30f444 100644 --- a/docs/content/reference/dynamic-configuration/kubernetes-gateway-traefik-lb-svc.yml +++ b/docs/content/reference/dynamic-configuration/kubernetes-gateway-traefik-lb-svc.yml @@ -25,7 +25,7 @@ spec: serviceAccountName: traefik-controller containers: - name: traefik - image: traefik:v2.9 + image: traefik:v3.0 args: - --entrypoints.web.address=:80 - --entrypoints.websecure.address=:443 diff --git a/docs/content/reference/dynamic-configuration/traefik.containo.us_ingressroutes.yaml b/docs/content/reference/dynamic-configuration/traefik.containo.us_ingressroutes.yaml index 9550d645e..b6c0eff29 100644 --- a/docs/content/reference/dynamic-configuration/traefik.containo.us_ingressroutes.yaml +++ b/docs/content/reference/dynamic-configuration/traefik.containo.us_ingressroutes.yaml @@ -39,7 +39,7 @@ spec: entryPoints: description: 'EntryPoints defines the list of entry point names to bind to. Entry points have to be configured in the static configuration. - More info: https://doc.traefik.io/traefik/v2.9/routing/entrypoints/ + More info: https://doc.traefik.io/traefik/v3.0/routing/entrypoints/ Default: all.' items: type: string @@ -56,11 +56,11 @@ spec: - Rule type: string match: - description: 'Match defines the router''s rule. More info: https://doc.traefik.io/traefik/v2.9/routing/routers/#rule' + description: 'Match defines the router''s rule. More info: https://doc.traefik.io/traefik/v3.0/routing/routers/#rule' type: string middlewares: description: 'Middlewares defines the list of references to - Middleware resources. More info: https://doc.traefik.io/traefik/v2.9/routing/providers/kubernetes-crd/#kind-middleware' + Middleware resources. More info: https://doc.traefik.io/traefik/v3.0/routing/providers/kubernetes-crd/#kind-middleware' items: description: MiddlewareRef is a reference to a Middleware resource. @@ -79,7 +79,7 @@ spec: type: array priority: description: 'Priority defines the router''s priority. More - info: https://doc.traefik.io/traefik/v2.9/routing/routers/#priority' + info: https://doc.traefik.io/traefik/v3.0/routing/routers/#priority' type: integer services: description: Services defines the list of Service. It can contain @@ -145,7 +145,7 @@ spec: type: string sticky: description: 'Sticky defines the sticky sessions configuration. - More info: https://doc.traefik.io/traefik/v2.9/routing/services/#sticky-sessions' + More info: https://doc.traefik.io/traefik/v3.0/routing/services/#sticky-sessions' properties: cookie: description: Cookie defines the sticky cookie configuration. @@ -190,16 +190,16 @@ spec: type: object type: array tls: - description: 'TLS defines the TLS configuration. More info: https://doc.traefik.io/traefik/v2.9/routing/routers/#tls' + description: 'TLS defines the TLS configuration. More info: https://doc.traefik.io/traefik/v3.0/routing/routers/#tls' properties: certResolver: description: 'CertResolver defines the name of the certificate resolver to use. Cert resolvers have to be configured in the - static configuration. More info: https://doc.traefik.io/traefik/v2.9/https/acme/#certificate-resolvers' + static configuration. More info: https://doc.traefik.io/traefik/v3.0/https/acme/#certificate-resolvers' type: string domains: description: 'Domains defines the list of domains that will be - used to issue certificates. More info: https://doc.traefik.io/traefik/v2.9/routing/routers/#domains' + used to issue certificates. More info: https://doc.traefik.io/traefik/v3.0/routing/routers/#domains' items: description: Domain holds a domain name with SANs. properties: @@ -217,15 +217,15 @@ spec: options: description: 'Options defines the reference to a TLSOption, that specifies the parameters of the TLS connection. If not defined, - the `default` TLSOption is used. More info: https://doc.traefik.io/traefik/v2.9/https/tls/#tls-options' + the `default` TLSOption is used. More info: https://doc.traefik.io/traefik/v3.0/https/tls/#tls-options' properties: name: description: 'Name defines the name of the referenced TLSOption. - More info: https://doc.traefik.io/traefik/v2.9/routing/providers/kubernetes-crd/#kind-tlsoption' + More info: https://doc.traefik.io/traefik/v3.0/routing/providers/kubernetes-crd/#kind-tlsoption' type: string namespace: description: 'Namespace defines the namespace of the referenced - TLSOption. More info: https://doc.traefik.io/traefik/v2.9/routing/providers/kubernetes-crd/#kind-tlsoption' + TLSOption. More info: https://doc.traefik.io/traefik/v3.0/routing/providers/kubernetes-crd/#kind-tlsoption' type: string required: - name @@ -241,11 +241,11 @@ spec: properties: name: description: 'Name defines the name of the referenced TLSStore. - More info: https://doc.traefik.io/traefik/v2.9/routing/providers/kubernetes-crd/#kind-tlsstore' + More info: https://doc.traefik.io/traefik/v3.0/routing/providers/kubernetes-crd/#kind-tlsstore' type: string namespace: description: 'Namespace defines the namespace of the referenced - TLSStore. More info: https://doc.traefik.io/traefik/v2.9/routing/providers/kubernetes-crd/#kind-tlsstore' + TLSStore. More info: https://doc.traefik.io/traefik/v3.0/routing/providers/kubernetes-crd/#kind-tlsstore' type: string required: - name diff --git a/docs/content/reference/dynamic-configuration/traefik.containo.us_ingressroutetcps.yaml b/docs/content/reference/dynamic-configuration/traefik.containo.us_ingressroutetcps.yaml index 37da83b34..a73fde965 100644 --- a/docs/content/reference/dynamic-configuration/traefik.containo.us_ingressroutetcps.yaml +++ b/docs/content/reference/dynamic-configuration/traefik.containo.us_ingressroutetcps.yaml @@ -39,7 +39,7 @@ spec: entryPoints: description: 'EntryPoints defines the list of entry point names to bind to. Entry points have to be configured in the static configuration. - More info: https://doc.traefik.io/traefik/v2.9/routing/entrypoints/ + More info: https://doc.traefik.io/traefik/v3.0/routing/entrypoints/ Default: all.' items: type: string @@ -50,7 +50,7 @@ spec: description: RouteTCP holds the TCP route configuration. properties: match: - description: 'Match defines the router''s rule. More info: https://doc.traefik.io/traefik/v2.9/routing/routers/#rule_1' + description: 'Match defines the router''s rule. More info: https://doc.traefik.io/traefik/v3.0/routing/routers/#rule_1' type: string middlewares: description: Middlewares defines the list of references to MiddlewareTCP @@ -73,7 +73,7 @@ spec: type: array priority: description: 'Priority defines the router''s priority. More - info: https://doc.traefik.io/traefik/v2.9/routing/routers/#priority_1' + info: https://doc.traefik.io/traefik/v3.0/routing/routers/#priority_1' type: integer services: description: Services defines the list of TCP services. @@ -98,7 +98,7 @@ spec: x-kubernetes-int-or-string: true proxyProtocol: description: 'ProxyProtocol defines the PROXY protocol - configuration. More info: https://doc.traefik.io/traefik/v2.9/routing/services/#proxy-protocol' + configuration. More info: https://doc.traefik.io/traefik/v3.0/routing/services/#proxy-protocol' properties: version: description: Version defines the PROXY Protocol version @@ -129,16 +129,16 @@ spec: type: array tls: description: 'TLS defines the TLS configuration on a layer 4 / TCP - Route. More info: https://doc.traefik.io/traefik/v2.9/routing/routers/#tls_1' + Route. More info: https://doc.traefik.io/traefik/v3.0/routing/routers/#tls_1' properties: certResolver: description: 'CertResolver defines the name of the certificate resolver to use. Cert resolvers have to be configured in the - static configuration. More info: https://doc.traefik.io/traefik/v2.9/https/acme/#certificate-resolvers' + static configuration. More info: https://doc.traefik.io/traefik/v3.0/https/acme/#certificate-resolvers' type: string domains: description: 'Domains defines the list of domains that will be - used to issue certificates. More info: https://doc.traefik.io/traefik/v2.9/routing/routers/#domains' + used to issue certificates. More info: https://doc.traefik.io/traefik/v3.0/routing/routers/#domains' items: description: Domain holds a domain name with SANs. properties: @@ -156,7 +156,7 @@ spec: options: description: 'Options defines the reference to a TLSOption, that specifies the parameters of the TLS connection. If not defined, - the `default` TLSOption is used. More info: https://doc.traefik.io/traefik/v2.9/https/tls/#tls-options' + the `default` TLSOption is used. More info: https://doc.traefik.io/traefik/v3.0/https/tls/#tls-options' properties: name: description: Name defines the name of the referenced Traefik diff --git a/docs/content/reference/dynamic-configuration/traefik.containo.us_ingressrouteudps.yaml b/docs/content/reference/dynamic-configuration/traefik.containo.us_ingressrouteudps.yaml index 2ba4dade6..50a7e6298 100644 --- a/docs/content/reference/dynamic-configuration/traefik.containo.us_ingressrouteudps.yaml +++ b/docs/content/reference/dynamic-configuration/traefik.containo.us_ingressrouteudps.yaml @@ -39,7 +39,7 @@ spec: entryPoints: description: 'EntryPoints defines the list of entry point names to bind to. Entry points have to be configured in the static configuration. - More info: https://doc.traefik.io/traefik/v2.9/routing/entrypoints/ + More info: https://doc.traefik.io/traefik/v3.0/routing/entrypoints/ Default: all.' items: type: string diff --git a/docs/content/reference/dynamic-configuration/traefik.containo.us_middlewares.yaml b/docs/content/reference/dynamic-configuration/traefik.containo.us_middlewares.yaml index 6b9a4cac9..9994d3469 100644 --- a/docs/content/reference/dynamic-configuration/traefik.containo.us_middlewares.yaml +++ b/docs/content/reference/dynamic-configuration/traefik.containo.us_middlewares.yaml @@ -20,7 +20,7 @@ spec: schema: openAPIV3Schema: description: 'Middleware is the CRD implementation of a Traefik Middleware. - More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/overview/' + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/overview/' properties: apiVersion: description: 'APIVersion defines the versioned schema of this representation @@ -40,7 +40,7 @@ spec: addPrefix: description: 'AddPrefix holds the add prefix middleware configuration. This middleware updates the path of a request before forwarding - it. More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/addprefix/' + it. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/addprefix/' properties: prefix: description: Prefix is the string to add before the current path @@ -50,11 +50,11 @@ spec: basicAuth: description: 'BasicAuth holds the basic auth middleware configuration. This middleware restricts access to your services to known users. - More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/basicauth/' + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/basicauth/' properties: headerField: description: 'HeaderField defines a header field to store the - authenticated user. More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/basicauth/#headerfield' + authenticated user. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/basicauth/#headerfield' type: string realm: description: 'Realm allows the protected resources on a server @@ -74,7 +74,7 @@ spec: buffering: description: 'Buffering holds the buffering middleware configuration. This middleware retries or limits the size of requests that can - be forwarded to backends. More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/buffering/#maxrequestbodybytes' + be forwarded to backends. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/buffering/#maxrequestbodybytes' properties: maxRequestBodyBytes: description: 'MaxRequestBodyBytes defines the maximum allowed @@ -107,13 +107,13 @@ spec: retryExpression: description: 'RetryExpression defines the retry conditions. It is a logical combination of functions with operators AND (&&) - and OR (||). More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/buffering/#retryexpression' + and OR (||). More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/buffering/#retryexpression' type: string type: object chain: description: 'Chain holds the configuration of the chain middleware. This middleware enables to define reusable combinations of other - pieces of middleware. More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/chain/' + pieces of middleware. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/chain/' properties: middlewares: description: Middlewares is the list of MiddlewareRef which composes @@ -167,7 +167,7 @@ spec: compress: description: 'Compress holds the compress middleware configuration. This middleware compresses responses before sending them to the - client, using gzip compression. More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/compress/' + client, using gzip compression. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/compress/' properties: excludedContentTypes: description: ExcludedContentTypes defines the list of content @@ -192,11 +192,11 @@ spec: digestAuth: description: 'DigestAuth holds the digest auth middleware configuration. This middleware restricts access to your services to known users. - More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/digestauth/' + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/digestauth/' properties: headerField: description: 'HeaderField defines a header field to store the - authenticated user. More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/basicauth/#headerfield' + authenticated user. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/basicauth/#headerfield' type: string realm: description: 'Realm allows the protected resources on a server @@ -215,7 +215,7 @@ spec: errors: description: 'ErrorPage holds the custom error middleware configuration. This middleware returns a custom page in lieu of the default, according - to configured ranges of HTTP Status codes. More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/errorpages/' + to configured ranges of HTTP Status codes. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/errorpages/' properties: query: description: Query defines the URL for the error page (hosted @@ -224,7 +224,7 @@ spec: type: string service: description: 'Service defines the reference to a Kubernetes Service - that will serve the error page. More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/errorpages/#service' + that will serve the error page. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/errorpages/#service' properties: kind: description: Kind defines the kind of the Service. @@ -281,7 +281,7 @@ spec: type: string sticky: description: 'Sticky defines the sticky sessions configuration. - More info: https://doc.traefik.io/traefik/v2.9/routing/services/#sticky-sessions' + More info: https://doc.traefik.io/traefik/v3.0/routing/services/#sticky-sessions' properties: cookie: description: Cookie defines the sticky cookie configuration. @@ -330,7 +330,7 @@ spec: forwardAuth: description: 'ForwardAuth holds the forward auth middleware configuration. This middleware delegates the request authentication to a Service. - More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/forwardauth/' + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/forwardauth/' properties: address: description: Address defines the authentication server address. @@ -353,7 +353,7 @@ spec: description: 'AuthResponseHeadersRegex defines the regex to match headers to copy from the authentication server response and set on forwarded request, after stripping all headers that match - the regex. More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/forwardauth/#authresponseheadersregex' + the regex. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/forwardauth/#authresponseheadersregex' type: string tls: description: TLS defines the configuration used to secure the @@ -393,7 +393,7 @@ spec: headers: description: 'Headers holds the headers middleware configuration. This middleware manages the requests and responses headers. More - info: https://doc.traefik.io/traefik/v2.9/middlewares/http/headers/#customrequestheaders' + info: https://doc.traefik.io/traefik/v3.0/middlewares/http/headers/#customrequestheaders' properties: accessControlAllowCredentials: description: AccessControlAllowCredentials defines whether the @@ -537,7 +537,7 @@ spec: inFlightReq: description: 'InFlightReq holds the in-flight request middleware configuration. This middleware limits the number of requests being processed and - served concurrently. More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/inflightreq/' + served concurrently. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/inflightreq/' properties: amount: description: Amount defines the maximum amount of allowed simultaneous @@ -551,11 +551,11 @@ spec: group requests as originating from a common source. If several strategies are defined at the same time, an error will be raised. If none are set, the default is to use the requestHost. More - info: https://doc.traefik.io/traefik/v2.9/middlewares/http/inflightreq/#sourcecriterion' + info: https://doc.traefik.io/traefik/v3.0/middlewares/http/inflightreq/#sourcecriterion' properties: ipStrategy: description: 'IPStrategy holds the IP strategy configuration - used by Traefik to determine the client IP. More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/ipallowlist/#ipstrategy' + used by Traefik to determine the client IP. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/ipallowlist/#ipstrategy' properties: depth: description: Depth tells Traefik to use the X-Forwarded-For @@ -583,11 +583,11 @@ spec: ipAllowList: description: 'IPAllowList holds the IP allowlist middleware configuration. This middleware accepts / refuses requests based on the client IP. - More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/ipallowlist/' + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/ipallowlist/' properties: ipStrategy: description: 'IPStrategy holds the IP strategy configuration used - by Traefik to determine the client IP. More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/ipallowlist/#ipstrategy' + by Traefik to determine the client IP. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/ipallowlist/#ipstrategy' properties: depth: description: Depth tells Traefik to use the X-Forwarded-For @@ -611,7 +611,7 @@ spec: passTLSClientCert: description: 'PassTLSClientCert holds the pass TLS client cert middleware configuration. This middleware adds the selected data from the passed - client TLS certificate to a header. More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/passtlsclientcert/' + client TLS certificate to a header. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/passtlsclientcert/' properties: info: description: Info selects the specific client certificate details @@ -718,7 +718,7 @@ spec: rateLimit: description: 'RateLimit holds the rate limit configuration. This middleware ensures that services will receive a fair amount of requests, and - allows one to define what fair is. More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/ratelimit/' + allows one to define what fair is. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/ratelimit/' properties: average: description: Average is the maximum rate, by default in requests/s, @@ -751,7 +751,7 @@ spec: properties: ipStrategy: description: 'IPStrategy holds the IP strategy configuration - used by Traefik to determine the client IP. More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/ipallowlist/#ipstrategy' + used by Traefik to determine the client IP. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/ipallowlist/#ipstrategy' properties: depth: description: Depth tells Traefik to use the X-Forwarded-For @@ -779,7 +779,7 @@ spec: redirectRegex: description: 'RedirectRegex holds the redirect regex middleware configuration. This middleware redirects a request using regex matching and replacement. - More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/redirectregex/#regex' + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/redirectregex/#regex' properties: permanent: description: Permanent defines whether the redirection is permanent @@ -797,7 +797,7 @@ spec: redirectScheme: description: 'RedirectScheme holds the redirect scheme middleware configuration. This middleware redirects requests from a scheme/port - to another. More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/redirectscheme/' + to another. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/redirectscheme/' properties: permanent: description: Permanent defines whether the redirection is permanent @@ -813,7 +813,7 @@ spec: replacePath: description: 'ReplacePath holds the replace path middleware configuration. This middleware replaces the path of the request URL and store the - original path in an X-Replaced-Path header. More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/replacepath/' + original path in an X-Replaced-Path header. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/replacepath/' properties: path: description: Path defines the path to use as replacement in the @@ -823,7 +823,7 @@ spec: replacePathRegex: description: 'ReplacePathRegex holds the replace path regex middleware configuration. This middleware replaces the path of a URL using - regex matching and replacement. More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/replacepathregex/' + regex matching and replacement. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/replacepathregex/' properties: regex: description: Regex defines the regular expression used to match @@ -839,7 +839,7 @@ spec: middleware reissues requests a given number of times to a backend server if that server does not reply. As soon as the server answers, the middleware stops retrying, regardless of the response status. - More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/retry/' + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/retry/' properties: attempts: description: Attempts defines how many times the request should @@ -859,7 +859,7 @@ spec: stripPrefix: description: 'StripPrefix holds the strip prefix middleware configuration. This middleware removes the specified prefixes from the URL path. - More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/stripprefix/' + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/stripprefix/' properties: prefixes: description: Prefixes defines the prefixes to strip from the request @@ -871,7 +871,7 @@ spec: stripPrefixRegex: description: 'StripPrefixRegex holds the strip prefix regex middleware configuration. This middleware removes the matching prefixes from - the URL path. More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/stripprefixregex/' + the URL path. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/stripprefixregex/' properties: regex: description: Regex defines the regular expression to match the diff --git a/docs/content/reference/dynamic-configuration/traefik.containo.us_middlewaretcps.yaml b/docs/content/reference/dynamic-configuration/traefik.containo.us_middlewaretcps.yaml index 78a449562..071b49cf8 100644 --- a/docs/content/reference/dynamic-configuration/traefik.containo.us_middlewaretcps.yaml +++ b/docs/content/reference/dynamic-configuration/traefik.containo.us_middlewaretcps.yaml @@ -20,7 +20,7 @@ spec: schema: openAPIV3Schema: description: 'MiddlewareTCP is the CRD implementation of a Traefik TCP middleware. - More info: https://doc.traefik.io/traefik/v2.9/middlewares/overview/' + More info: https://doc.traefik.io/traefik/v3.0/middlewares/overview/' properties: apiVersion: description: 'APIVersion defines the versioned schema of this representation diff --git a/docs/content/reference/dynamic-configuration/traefik.containo.us_serverstransports.yaml b/docs/content/reference/dynamic-configuration/traefik.containo.us_serverstransports.yaml index f23d345ce..362db4f49 100644 --- a/docs/content/reference/dynamic-configuration/traefik.containo.us_serverstransports.yaml +++ b/docs/content/reference/dynamic-configuration/traefik.containo.us_serverstransports.yaml @@ -22,7 +22,7 @@ spec: description: 'ServersTransport is the CRD implementation of a ServersTransport. If no serversTransport is specified, the default@internal will be used. The default@internal serversTransport is created from the static configuration. - More info: https://doc.traefik.io/traefik/v2.9/routing/services/#serverstransport_1' + More info: https://doc.traefik.io/traefik/v3.0/routing/services/#serverstransport_1' properties: apiVersion: description: 'APIVersion defines the versioned schema of this representation diff --git a/docs/content/reference/dynamic-configuration/traefik.containo.us_tlsoptions.yaml b/docs/content/reference/dynamic-configuration/traefik.containo.us_tlsoptions.yaml index 098a0068f..ffbd86ec1 100644 --- a/docs/content/reference/dynamic-configuration/traefik.containo.us_tlsoptions.yaml +++ b/docs/content/reference/dynamic-configuration/traefik.containo.us_tlsoptions.yaml @@ -21,7 +21,7 @@ spec: openAPIV3Schema: description: 'TLSOption is the CRD implementation of a Traefik TLS Option, allowing to configure some parameters of the TLS connection. More info: - https://doc.traefik.io/traefik/v2.9/https/tls/#tls-options' + https://doc.traefik.io/traefik/v3.0/https/tls/#tls-options' properties: apiVersion: description: 'APIVersion defines the versioned schema of this representation @@ -41,13 +41,13 @@ spec: alpnProtocols: description: 'ALPNProtocols defines the list of supported application level protocols for the TLS handshake, in order of preference. More - info: https://doc.traefik.io/traefik/v2.9/https/tls/#alpn-protocols' + info: https://doc.traefik.io/traefik/v3.0/https/tls/#alpn-protocols' items: type: string type: array cipherSuites: description: 'CipherSuites defines the list of supported cipher suites - for TLS versions up to TLS 1.2. More info: https://doc.traefik.io/traefik/v2.9/https/tls/#cipher-suites' + for TLS versions up to TLS 1.2. More info: https://doc.traefik.io/traefik/v3.0/https/tls/#cipher-suites' items: type: string type: array @@ -74,7 +74,7 @@ spec: type: object curvePreferences: description: 'CurvePreferences defines the preferred elliptic curves - in a specific order. More info: https://doc.traefik.io/traefik/v2.9/https/tls/#curve-preferences' + in a specific order. More info: https://doc.traefik.io/traefik/v3.0/https/tls/#curve-preferences' items: type: string type: array diff --git a/docs/content/reference/dynamic-configuration/traefik.containo.us_tlsstores.yaml b/docs/content/reference/dynamic-configuration/traefik.containo.us_tlsstores.yaml index f6dfc6c8f..9e35d6897 100644 --- a/docs/content/reference/dynamic-configuration/traefik.containo.us_tlsstores.yaml +++ b/docs/content/reference/dynamic-configuration/traefik.containo.us_tlsstores.yaml @@ -22,7 +22,7 @@ spec: description: 'TLSStore is the CRD implementation of a Traefik TLS Store. For the time being, only the TLSStore named default is supported. This means that you cannot have two stores that are named default in different Kubernetes - namespaces. More info: https://doc.traefik.io/traefik/v2.9/https/tls/#certificates-stores' + namespaces. More info: https://doc.traefik.io/traefik/v3.0/https/tls/#certificates-stores' properties: apiVersion: description: 'APIVersion defines the versioned schema of this representation diff --git a/docs/content/reference/dynamic-configuration/traefik.containo.us_traefikservices.yaml b/docs/content/reference/dynamic-configuration/traefik.containo.us_traefikservices.yaml index 358fdc1ea..3289806f9 100644 --- a/docs/content/reference/dynamic-configuration/traefik.containo.us_traefikservices.yaml +++ b/docs/content/reference/dynamic-configuration/traefik.containo.us_traefikservices.yaml @@ -21,7 +21,7 @@ spec: openAPIV3Schema: description: 'TraefikService is the CRD implementation of a Traefik Service. TraefikService object allows to: - Apply weight to Services on load-balancing - - Mirror traffic on services More info: https://doc.traefik.io/traefik/v2.9/routing/providers/kubernetes-crd/#kind-traefikservice' + - Mirror traffic on services More info: https://doc.traefik.io/traefik/v3.0/routing/providers/kubernetes-crd/#kind-traefikservice' properties: apiVersion: description: 'APIVersion defines the versioned schema of this representation @@ -120,7 +120,7 @@ spec: type: string sticky: description: 'Sticky defines the sticky sessions configuration. - More info: https://doc.traefik.io/traefik/v2.9/routing/services/#sticky-sessions' + More info: https://doc.traefik.io/traefik/v3.0/routing/services/#sticky-sessions' properties: cookie: description: Cookie defines the sticky cookie configuration. @@ -204,7 +204,7 @@ spec: type: string sticky: description: 'Sticky defines the sticky sessions configuration. - More info: https://doc.traefik.io/traefik/v2.9/routing/services/#sticky-sessions' + More info: https://doc.traefik.io/traefik/v3.0/routing/services/#sticky-sessions' properties: cookie: description: Cookie defines the sticky cookie configuration. @@ -304,7 +304,7 @@ spec: type: string sticky: description: 'Sticky defines the sticky sessions configuration. - More info: https://doc.traefik.io/traefik/v2.9/routing/services/#sticky-sessions' + More info: https://doc.traefik.io/traefik/v3.0/routing/services/#sticky-sessions' properties: cookie: description: Cookie defines the sticky cookie configuration. @@ -343,7 +343,7 @@ spec: type: array sticky: description: 'Sticky defines whether sticky sessions are enabled. - More info: https://doc.traefik.io/traefik/v2.9/routing/providers/kubernetes-crd/#stickiness-and-load-balancing' + More info: https://doc.traefik.io/traefik/v3.0/routing/providers/kubernetes-crd/#stickiness-and-load-balancing' properties: cookie: description: Cookie defines the sticky cookie configuration. diff --git a/docs/content/routing/providers/kubernetes-crd.md b/docs/content/routing/providers/kubernetes-crd.md index f342fc87f..f39ef599e 100644 --- a/docs/content/routing/providers/kubernetes-crd.md +++ b/docs/content/routing/providers/kubernetes-crd.md @@ -48,7 +48,7 @@ The Kubernetes Ingress Controller, The Custom Resource Way. serviceAccountName: traefik-ingress-controller containers: - name: traefik - image: traefik:v2.9 + image: traefik:v3.0 args: - --log.level=DEBUG - --api diff --git a/docs/content/routing/providers/kubernetes-ingress.md b/docs/content/routing/providers/kubernetes-ingress.md index c7c8ab914..5720889d8 100644 --- a/docs/content/routing/providers/kubernetes-ingress.md +++ b/docs/content/routing/providers/kubernetes-ingress.md @@ -147,7 +147,7 @@ which in turn will create the resulting routers, services, handlers, etc. serviceAccountName: traefik-ingress-controller containers: - name: traefik - image: traefik:v2.9 + image: traefik:v3.0 args: - --entrypoints.web.address=:80 - --providers.kubernetesingress @@ -539,7 +539,7 @@ This way, any Ingress attached to this Entrypoint will have TLS termination by d serviceAccountName: traefik-ingress-controller containers: - name: traefik - image: traefik:v2.9 + image: traefik:v3.0 args: - --entrypoints.websecure.address=:443 - --entrypoints.websecure.http.tls @@ -749,7 +749,7 @@ For more options, please refer to the available [annotations](#on-ingress). serviceAccountName: traefik-ingress-controller containers: - name: traefik - image: traefik:v2.9 + image: traefik:v3.0 args: - --entrypoints.websecure.address=:443 - --providers.kubernetesingress diff --git a/docs/content/user-guides/crd-acme/03-deployments.yml b/docs/content/user-guides/crd-acme/03-deployments.yml index 2f3fd526d..57f6da6d0 100644 --- a/docs/content/user-guides/crd-acme/03-deployments.yml +++ b/docs/content/user-guides/crd-acme/03-deployments.yml @@ -26,7 +26,7 @@ spec: serviceAccountName: traefik-ingress-controller containers: - name: traefik - image: traefik:v2.9 + image: traefik:v3.0 args: - --api.insecure - --accesslog diff --git a/docs/content/user-guides/crd-acme/k3s.yml b/docs/content/user-guides/crd-acme/k3s.yml index 495887054..445e6a4ba 100644 --- a/docs/content/user-guides/crd-acme/k3s.yml +++ b/docs/content/user-guides/crd-acme/k3s.yml @@ -26,5 +26,5 @@ node: - K3S_CLUSTER_SECRET=somethingtotallyrandom volumes: # this is where you would place a alternative traefik image (saved as a .tar file with - # 'docker save'), if you want to use it, instead of the traefik:v2.9 image. + # 'docker save'), if you want to use it, instead of the traefik:v3.0 image. - /somewhere/on/your/host/custom-image:/var/lib/rancher/k3s/agent/images diff --git a/docs/content/user-guides/docker-compose/acme-dns/docker-compose.yml b/docs/content/user-guides/docker-compose/acme-dns/docker-compose.yml index 9ce36865a..9a6307ad3 100644 --- a/docs/content/user-guides/docker-compose/acme-dns/docker-compose.yml +++ b/docs/content/user-guides/docker-compose/acme-dns/docker-compose.yml @@ -3,7 +3,7 @@ version: "3.3" services: traefik: - image: "traefik:v2.9" + image: "traefik:v3.0" container_name: "traefik" command: #- "--log.level=DEBUG" diff --git a/docs/content/user-guides/docker-compose/acme-dns/docker-compose_secrets.yml b/docs/content/user-guides/docker-compose/acme-dns/docker-compose_secrets.yml index ae22e954b..46e04acbe 100644 --- a/docs/content/user-guides/docker-compose/acme-dns/docker-compose_secrets.yml +++ b/docs/content/user-guides/docker-compose/acme-dns/docker-compose_secrets.yml @@ -13,7 +13,7 @@ secrets: services: traefik: - image: "traefik:v2.9" + image: "traefik:v3.0" container_name: "traefik" command: #- "--log.level=DEBUG" diff --git a/docs/content/user-guides/docker-compose/acme-http/docker-compose.yml b/docs/content/user-guides/docker-compose/acme-http/docker-compose.yml index 97256c0a4..3d888789c 100644 --- a/docs/content/user-guides/docker-compose/acme-http/docker-compose.yml +++ b/docs/content/user-guides/docker-compose/acme-http/docker-compose.yml @@ -3,7 +3,7 @@ version: "3.3" services: traefik: - image: "traefik:v2.9" + image: "traefik:v3.0" container_name: "traefik" command: #- "--log.level=DEBUG" diff --git a/docs/content/user-guides/docker-compose/acme-tls/docker-compose.yml b/docs/content/user-guides/docker-compose/acme-tls/docker-compose.yml index fc619b09a..52e4ff869 100644 --- a/docs/content/user-guides/docker-compose/acme-tls/docker-compose.yml +++ b/docs/content/user-guides/docker-compose/acme-tls/docker-compose.yml @@ -3,7 +3,7 @@ version: "3.3" services: traefik: - image: "traefik:v2.9" + image: "traefik:v3.0" container_name: "traefik" command: #- "--log.level=DEBUG" diff --git a/docs/content/user-guides/docker-compose/basic-example/docker-compose.yml b/docs/content/user-guides/docker-compose/basic-example/docker-compose.yml index 0eae788e3..a17cddc9f 100644 --- a/docs/content/user-guides/docker-compose/basic-example/docker-compose.yml +++ b/docs/content/user-guides/docker-compose/basic-example/docker-compose.yml @@ -3,7 +3,7 @@ version: "3.3" services: traefik: - image: "traefik:v2.9" + image: "traefik:v3.0" container_name: "traefik" command: #- "--log.level=DEBUG" diff --git a/docs/content/user-guides/docker-compose/basic-example/index.md b/docs/content/user-guides/docker-compose/basic-example/index.md index bda9ccca6..0e2bf4964 100644 --- a/docs/content/user-guides/docker-compose/basic-example/index.md +++ b/docs/content/user-guides/docker-compose/basic-example/index.md @@ -32,7 +32,7 @@ This will also be used as a starting point for the other docker-compose guides. services: traefik: - image: "traefik:v2.9" + image: "traefik:v3.0" ... networks: - traefiknet diff --git a/integration/fixtures/k8s/01-traefik-crd.yml b/integration/fixtures/k8s/01-traefik-crd.yml index 0a8f01823..4a7f19a66 100644 --- a/integration/fixtures/k8s/01-traefik-crd.yml +++ b/integration/fixtures/k8s/01-traefik-crd.yml @@ -39,7 +39,7 @@ spec: entryPoints: description: 'EntryPoints defines the list of entry point names to bind to. Entry points have to be configured in the static configuration. - More info: https://doc.traefik.io/traefik/v2.9/routing/entrypoints/ + More info: https://doc.traefik.io/traefik/v3.0/routing/entrypoints/ Default: all.' items: type: string @@ -56,11 +56,11 @@ spec: - Rule type: string match: - description: 'Match defines the router''s rule. More info: https://doc.traefik.io/traefik/v2.9/routing/routers/#rule' + description: 'Match defines the router''s rule. More info: https://doc.traefik.io/traefik/v3.0/routing/routers/#rule' type: string middlewares: description: 'Middlewares defines the list of references to - Middleware resources. More info: https://doc.traefik.io/traefik/v2.9/routing/providers/kubernetes-crd/#kind-middleware' + Middleware resources. More info: https://doc.traefik.io/traefik/v3.0/routing/providers/kubernetes-crd/#kind-middleware' items: description: MiddlewareRef is a reference to a Middleware resource. @@ -79,7 +79,7 @@ spec: type: array priority: description: 'Priority defines the router''s priority. More - info: https://doc.traefik.io/traefik/v2.9/routing/routers/#priority' + info: https://doc.traefik.io/traefik/v3.0/routing/routers/#priority' type: integer services: description: Services defines the list of Service. It can contain @@ -145,7 +145,7 @@ spec: type: string sticky: description: 'Sticky defines the sticky sessions configuration. - More info: https://doc.traefik.io/traefik/v2.9/routing/services/#sticky-sessions' + More info: https://doc.traefik.io/traefik/v3.0/routing/services/#sticky-sessions' properties: cookie: description: Cookie defines the sticky cookie configuration. @@ -190,16 +190,16 @@ spec: type: object type: array tls: - description: 'TLS defines the TLS configuration. More info: https://doc.traefik.io/traefik/v2.9/routing/routers/#tls' + description: 'TLS defines the TLS configuration. More info: https://doc.traefik.io/traefik/v3.0/routing/routers/#tls' properties: certResolver: description: 'CertResolver defines the name of the certificate resolver to use. Cert resolvers have to be configured in the - static configuration. More info: https://doc.traefik.io/traefik/v2.9/https/acme/#certificate-resolvers' + static configuration. More info: https://doc.traefik.io/traefik/v3.0/https/acme/#certificate-resolvers' type: string domains: description: 'Domains defines the list of domains that will be - used to issue certificates. More info: https://doc.traefik.io/traefik/v2.9/routing/routers/#domains' + used to issue certificates. More info: https://doc.traefik.io/traefik/v3.0/routing/routers/#domains' items: description: Domain holds a domain name with SANs. properties: @@ -217,15 +217,15 @@ spec: options: description: 'Options defines the reference to a TLSOption, that specifies the parameters of the TLS connection. If not defined, - the `default` TLSOption is used. More info: https://doc.traefik.io/traefik/v2.9/https/tls/#tls-options' + the `default` TLSOption is used. More info: https://doc.traefik.io/traefik/v3.0/https/tls/#tls-options' properties: name: description: 'Name defines the name of the referenced TLSOption. - More info: https://doc.traefik.io/traefik/v2.9/routing/providers/kubernetes-crd/#kind-tlsoption' + More info: https://doc.traefik.io/traefik/v3.0/routing/providers/kubernetes-crd/#kind-tlsoption' type: string namespace: description: 'Namespace defines the namespace of the referenced - TLSOption. More info: https://doc.traefik.io/traefik/v2.9/routing/providers/kubernetes-crd/#kind-tlsoption' + TLSOption. More info: https://doc.traefik.io/traefik/v3.0/routing/providers/kubernetes-crd/#kind-tlsoption' type: string required: - name @@ -241,11 +241,11 @@ spec: properties: name: description: 'Name defines the name of the referenced TLSStore. - More info: https://doc.traefik.io/traefik/v2.9/routing/providers/kubernetes-crd/#kind-tlsstore' + More info: https://doc.traefik.io/traefik/v3.0/routing/providers/kubernetes-crd/#kind-tlsstore' type: string namespace: description: 'Namespace defines the namespace of the referenced - TLSStore. More info: https://doc.traefik.io/traefik/v2.9/routing/providers/kubernetes-crd/#kind-tlsstore' + TLSStore. More info: https://doc.traefik.io/traefik/v3.0/routing/providers/kubernetes-crd/#kind-tlsstore' type: string required: - name @@ -307,7 +307,7 @@ spec: entryPoints: description: 'EntryPoints defines the list of entry point names to bind to. Entry points have to be configured in the static configuration. - More info: https://doc.traefik.io/traefik/v2.9/routing/entrypoints/ + More info: https://doc.traefik.io/traefik/v3.0/routing/entrypoints/ Default: all.' items: type: string @@ -318,7 +318,7 @@ spec: description: RouteTCP holds the TCP route configuration. properties: match: - description: 'Match defines the router''s rule. More info: https://doc.traefik.io/traefik/v2.9/routing/routers/#rule_1' + description: 'Match defines the router''s rule. More info: https://doc.traefik.io/traefik/v3.0/routing/routers/#rule_1' type: string middlewares: description: Middlewares defines the list of references to MiddlewareTCP @@ -341,7 +341,7 @@ spec: type: array priority: description: 'Priority defines the router''s priority. More - info: https://doc.traefik.io/traefik/v2.9/routing/routers/#priority_1' + info: https://doc.traefik.io/traefik/v3.0/routing/routers/#priority_1' type: integer services: description: Services defines the list of TCP services. @@ -366,7 +366,7 @@ spec: x-kubernetes-int-or-string: true proxyProtocol: description: 'ProxyProtocol defines the PROXY protocol - configuration. More info: https://doc.traefik.io/traefik/v2.9/routing/services/#proxy-protocol' + configuration. More info: https://doc.traefik.io/traefik/v3.0/routing/services/#proxy-protocol' properties: version: description: Version defines the PROXY Protocol version @@ -397,16 +397,16 @@ spec: type: array tls: description: 'TLS defines the TLS configuration on a layer 4 / TCP - Route. More info: https://doc.traefik.io/traefik/v2.9/routing/routers/#tls_1' + Route. More info: https://doc.traefik.io/traefik/v3.0/routing/routers/#tls_1' properties: certResolver: description: 'CertResolver defines the name of the certificate resolver to use. Cert resolvers have to be configured in the - static configuration. More info: https://doc.traefik.io/traefik/v2.9/https/acme/#certificate-resolvers' + static configuration. More info: https://doc.traefik.io/traefik/v3.0/https/acme/#certificate-resolvers' type: string domains: description: 'Domains defines the list of domains that will be - used to issue certificates. More info: https://doc.traefik.io/traefik/v2.9/routing/routers/#domains' + used to issue certificates. More info: https://doc.traefik.io/traefik/v3.0/routing/routers/#domains' items: description: Domain holds a domain name with SANs. properties: @@ -424,7 +424,7 @@ spec: options: description: 'Options defines the reference to a TLSOption, that specifies the parameters of the TLS connection. If not defined, - the `default` TLSOption is used. More info: https://doc.traefik.io/traefik/v2.9/https/tls/#tls-options' + the `default` TLSOption is used. More info: https://doc.traefik.io/traefik/v3.0/https/tls/#tls-options' properties: name: description: Name defines the name of the referenced Traefik @@ -518,7 +518,7 @@ spec: entryPoints: description: 'EntryPoints defines the list of entry point names to bind to. Entry points have to be configured in the static configuration. - More info: https://doc.traefik.io/traefik/v2.9/routing/entrypoints/ + More info: https://doc.traefik.io/traefik/v3.0/routing/entrypoints/ Default: all.' items: type: string @@ -597,7 +597,7 @@ spec: schema: openAPIV3Schema: description: 'Middleware is the CRD implementation of a Traefik Middleware. - More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/overview/' + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/overview/' properties: apiVersion: description: 'APIVersion defines the versioned schema of this representation @@ -617,7 +617,7 @@ spec: addPrefix: description: 'AddPrefix holds the add prefix middleware configuration. This middleware updates the path of a request before forwarding - it. More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/addprefix/' + it. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/addprefix/' properties: prefix: description: Prefix is the string to add before the current path @@ -627,11 +627,11 @@ spec: basicAuth: description: 'BasicAuth holds the basic auth middleware configuration. This middleware restricts access to your services to known users. - More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/basicauth/' + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/basicauth/' properties: headerField: description: 'HeaderField defines a header field to store the - authenticated user. More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/basicauth/#headerfield' + authenticated user. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/basicauth/#headerfield' type: string realm: description: 'Realm allows the protected resources on a server @@ -651,7 +651,7 @@ spec: buffering: description: 'Buffering holds the buffering middleware configuration. This middleware retries or limits the size of requests that can - be forwarded to backends. More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/buffering/#maxrequestbodybytes' + be forwarded to backends. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/buffering/#maxrequestbodybytes' properties: maxRequestBodyBytes: description: 'MaxRequestBodyBytes defines the maximum allowed @@ -684,13 +684,13 @@ spec: retryExpression: description: 'RetryExpression defines the retry conditions. It is a logical combination of functions with operators AND (&&) - and OR (||). More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/buffering/#retryexpression' + and OR (||). More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/buffering/#retryexpression' type: string type: object chain: description: 'Chain holds the configuration of the chain middleware. This middleware enables to define reusable combinations of other - pieces of middleware. More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/chain/' + pieces of middleware. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/chain/' properties: middlewares: description: Middlewares is the list of MiddlewareRef which composes @@ -744,7 +744,7 @@ spec: compress: description: 'Compress holds the compress middleware configuration. This middleware compresses responses before sending them to the - client, using gzip compression. More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/compress/' + client, using gzip compression. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/compress/' properties: excludedContentTypes: description: ExcludedContentTypes defines the list of content @@ -769,11 +769,11 @@ spec: digestAuth: description: 'DigestAuth holds the digest auth middleware configuration. This middleware restricts access to your services to known users. - More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/digestauth/' + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/digestauth/' properties: headerField: description: 'HeaderField defines a header field to store the - authenticated user. More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/basicauth/#headerfield' + authenticated user. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/basicauth/#headerfield' type: string realm: description: 'Realm allows the protected resources on a server @@ -792,7 +792,7 @@ spec: errors: description: 'ErrorPage holds the custom error middleware configuration. This middleware returns a custom page in lieu of the default, according - to configured ranges of HTTP Status codes. More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/errorpages/' + to configured ranges of HTTP Status codes. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/errorpages/' properties: query: description: Query defines the URL for the error page (hosted @@ -801,7 +801,7 @@ spec: type: string service: description: 'Service defines the reference to a Kubernetes Service - that will serve the error page. More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/errorpages/#service' + that will serve the error page. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/errorpages/#service' properties: kind: description: Kind defines the kind of the Service. @@ -858,7 +858,7 @@ spec: type: string sticky: description: 'Sticky defines the sticky sessions configuration. - More info: https://doc.traefik.io/traefik/v2.9/routing/services/#sticky-sessions' + More info: https://doc.traefik.io/traefik/v3.0/routing/services/#sticky-sessions' properties: cookie: description: Cookie defines the sticky cookie configuration. @@ -907,7 +907,7 @@ spec: forwardAuth: description: 'ForwardAuth holds the forward auth middleware configuration. This middleware delegates the request authentication to a Service. - More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/forwardauth/' + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/forwardauth/' properties: address: description: Address defines the authentication server address. @@ -930,7 +930,7 @@ spec: description: 'AuthResponseHeadersRegex defines the regex to match headers to copy from the authentication server response and set on forwarded request, after stripping all headers that match - the regex. More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/forwardauth/#authresponseheadersregex' + the regex. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/forwardauth/#authresponseheadersregex' type: string tls: description: TLS defines the configuration used to secure the @@ -970,7 +970,7 @@ spec: headers: description: 'Headers holds the headers middleware configuration. This middleware manages the requests and responses headers. More - info: https://doc.traefik.io/traefik/v2.9/middlewares/http/headers/#customrequestheaders' + info: https://doc.traefik.io/traefik/v3.0/middlewares/http/headers/#customrequestheaders' properties: accessControlAllowCredentials: description: AccessControlAllowCredentials defines whether the @@ -1114,7 +1114,7 @@ spec: inFlightReq: description: 'InFlightReq holds the in-flight request middleware configuration. This middleware limits the number of requests being processed and - served concurrently. More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/inflightreq/' + served concurrently. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/inflightreq/' properties: amount: description: Amount defines the maximum amount of allowed simultaneous @@ -1128,11 +1128,11 @@ spec: group requests as originating from a common source. If several strategies are defined at the same time, an error will be raised. If none are set, the default is to use the requestHost. More - info: https://doc.traefik.io/traefik/v2.9/middlewares/http/inflightreq/#sourcecriterion' + info: https://doc.traefik.io/traefik/v3.0/middlewares/http/inflightreq/#sourcecriterion' properties: ipStrategy: description: 'IPStrategy holds the IP strategy configuration - used by Traefik to determine the client IP. More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/ipallowlist/#ipstrategy' + used by Traefik to determine the client IP. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/ipallowlist/#ipstrategy' properties: depth: description: Depth tells Traefik to use the X-Forwarded-For @@ -1160,11 +1160,11 @@ spec: ipAllowList: description: 'IPAllowList holds the IP allowlist middleware configuration. This middleware accepts / refuses requests based on the client IP. - More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/ipallowlist/' + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/ipallowlist/' properties: ipStrategy: description: 'IPStrategy holds the IP strategy configuration used - by Traefik to determine the client IP. More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/ipallowlist/#ipstrategy' + by Traefik to determine the client IP. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/ipallowlist/#ipstrategy' properties: depth: description: Depth tells Traefik to use the X-Forwarded-For @@ -1188,7 +1188,7 @@ spec: passTLSClientCert: description: 'PassTLSClientCert holds the pass TLS client cert middleware configuration. This middleware adds the selected data from the passed - client TLS certificate to a header. More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/passtlsclientcert/' + client TLS certificate to a header. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/passtlsclientcert/' properties: info: description: Info selects the specific client certificate details @@ -1295,7 +1295,7 @@ spec: rateLimit: description: 'RateLimit holds the rate limit configuration. This middleware ensures that services will receive a fair amount of requests, and - allows one to define what fair is. More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/ratelimit/' + allows one to define what fair is. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/ratelimit/' properties: average: description: Average is the maximum rate, by default in requests/s, @@ -1328,7 +1328,7 @@ spec: properties: ipStrategy: description: 'IPStrategy holds the IP strategy configuration - used by Traefik to determine the client IP. More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/ipallowlist/#ipstrategy' + used by Traefik to determine the client IP. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/ipallowlist/#ipstrategy' properties: depth: description: Depth tells Traefik to use the X-Forwarded-For @@ -1356,7 +1356,7 @@ spec: redirectRegex: description: 'RedirectRegex holds the redirect regex middleware configuration. This middleware redirects a request using regex matching and replacement. - More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/redirectregex/#regex' + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/redirectregex/#regex' properties: permanent: description: Permanent defines whether the redirection is permanent @@ -1374,7 +1374,7 @@ spec: redirectScheme: description: 'RedirectScheme holds the redirect scheme middleware configuration. This middleware redirects requests from a scheme/port - to another. More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/redirectscheme/' + to another. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/redirectscheme/' properties: permanent: description: Permanent defines whether the redirection is permanent @@ -1390,7 +1390,7 @@ spec: replacePath: description: 'ReplacePath holds the replace path middleware configuration. This middleware replaces the path of the request URL and store the - original path in an X-Replaced-Path header. More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/replacepath/' + original path in an X-Replaced-Path header. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/replacepath/' properties: path: description: Path defines the path to use as replacement in the @@ -1400,7 +1400,7 @@ spec: replacePathRegex: description: 'ReplacePathRegex holds the replace path regex middleware configuration. This middleware replaces the path of a URL using - regex matching and replacement. More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/replacepathregex/' + regex matching and replacement. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/replacepathregex/' properties: regex: description: Regex defines the regular expression used to match @@ -1416,7 +1416,7 @@ spec: middleware reissues requests a given number of times to a backend server if that server does not reply. As soon as the server answers, the middleware stops retrying, regardless of the response status. - More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/retry/' + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/retry/' properties: attempts: description: Attempts defines how many times the request should @@ -1436,7 +1436,7 @@ spec: stripPrefix: description: 'StripPrefix holds the strip prefix middleware configuration. This middleware removes the specified prefixes from the URL path. - More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/stripprefix/' + More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/stripprefix/' properties: prefixes: description: Prefixes defines the prefixes to strip from the request @@ -1448,7 +1448,7 @@ spec: stripPrefixRegex: description: 'StripPrefixRegex holds the strip prefix regex middleware configuration. This middleware removes the matching prefixes from - the URL path. More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/stripprefixregex/' + the URL path. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/stripprefixregex/' properties: regex: description: Regex defines the regular expression to match the @@ -1492,7 +1492,7 @@ spec: schema: openAPIV3Schema: description: 'MiddlewareTCP is the CRD implementation of a Traefik TCP middleware. - More info: https://doc.traefik.io/traefik/v2.9/middlewares/overview/' + More info: https://doc.traefik.io/traefik/v3.0/middlewares/overview/' properties: apiVersion: description: 'APIVersion defines the versioned schema of this representation @@ -1566,7 +1566,7 @@ spec: description: 'ServersTransport is the CRD implementation of a ServersTransport. If no serversTransport is specified, the default@internal will be used. The default@internal serversTransport is created from the static configuration. - More info: https://doc.traefik.io/traefik/v2.9/routing/services/#serverstransport_1' + More info: https://doc.traefik.io/traefik/v3.0/routing/services/#serverstransport_1' properties: apiVersion: description: 'APIVersion defines the versioned schema of this representation @@ -1706,7 +1706,7 @@ spec: openAPIV3Schema: description: 'TLSOption is the CRD implementation of a Traefik TLS Option, allowing to configure some parameters of the TLS connection. More info: - https://doc.traefik.io/traefik/v2.9/https/tls/#tls-options' + https://doc.traefik.io/traefik/v3.0/https/tls/#tls-options' properties: apiVersion: description: 'APIVersion defines the versioned schema of this representation @@ -1726,13 +1726,13 @@ spec: alpnProtocols: description: 'ALPNProtocols defines the list of supported application level protocols for the TLS handshake, in order of preference. More - info: https://doc.traefik.io/traefik/v2.9/https/tls/#alpn-protocols' + info: https://doc.traefik.io/traefik/v3.0/https/tls/#alpn-protocols' items: type: string type: array cipherSuites: description: 'CipherSuites defines the list of supported cipher suites - for TLS versions up to TLS 1.2. More info: https://doc.traefik.io/traefik/v2.9/https/tls/#cipher-suites' + for TLS versions up to TLS 1.2. More info: https://doc.traefik.io/traefik/v3.0/https/tls/#cipher-suites' items: type: string type: array @@ -1759,7 +1759,7 @@ spec: type: object curvePreferences: description: 'CurvePreferences defines the preferred elliptic curves - in a specific order. More info: https://doc.traefik.io/traefik/v2.9/https/tls/#curve-preferences' + in a specific order. More info: https://doc.traefik.io/traefik/v3.0/https/tls/#curve-preferences' items: type: string type: array @@ -1814,7 +1814,7 @@ spec: description: 'TLSStore is the CRD implementation of a Traefik TLS Store. For the time being, only the TLSStore named default is supported. This means that you cannot have two stores that are named default in different Kubernetes - namespaces. More info: https://doc.traefik.io/traefik/v2.9/https/tls/#certificates-stores' + namespaces. More info: https://doc.traefik.io/traefik/v3.0/https/tls/#certificates-stores' properties: apiVersion: description: 'APIVersion defines the versioned schema of this representation @@ -1912,7 +1912,7 @@ spec: openAPIV3Schema: description: 'TraefikService is the CRD implementation of a Traefik Service. TraefikService object allows to: - Apply weight to Services on load-balancing - - Mirror traffic on services More info: https://doc.traefik.io/traefik/v2.9/routing/providers/kubernetes-crd/#kind-traefikservice' + - Mirror traffic on services More info: https://doc.traefik.io/traefik/v3.0/routing/providers/kubernetes-crd/#kind-traefikservice' properties: apiVersion: description: 'APIVersion defines the versioned schema of this representation @@ -2011,7 +2011,7 @@ spec: type: string sticky: description: 'Sticky defines the sticky sessions configuration. - More info: https://doc.traefik.io/traefik/v2.9/routing/services/#sticky-sessions' + More info: https://doc.traefik.io/traefik/v3.0/routing/services/#sticky-sessions' properties: cookie: description: Cookie defines the sticky cookie configuration. @@ -2095,7 +2095,7 @@ spec: type: string sticky: description: 'Sticky defines the sticky sessions configuration. - More info: https://doc.traefik.io/traefik/v2.9/routing/services/#sticky-sessions' + More info: https://doc.traefik.io/traefik/v3.0/routing/services/#sticky-sessions' properties: cookie: description: Cookie defines the sticky cookie configuration. @@ -2195,7 +2195,7 @@ spec: type: string sticky: description: 'Sticky defines the sticky sessions configuration. - More info: https://doc.traefik.io/traefik/v2.9/routing/services/#sticky-sessions' + More info: https://doc.traefik.io/traefik/v3.0/routing/services/#sticky-sessions' properties: cookie: description: Cookie defines the sticky cookie configuration. @@ -2234,7 +2234,7 @@ spec: type: array sticky: description: 'Sticky defines whether sticky sessions are enabled. - More info: https://doc.traefik.io/traefik/v2.9/routing/providers/kubernetes-crd/#stickiness-and-load-balancing' + More info: https://doc.traefik.io/traefik/v3.0/routing/providers/kubernetes-crd/#stickiness-and-load-balancing' properties: cookie: description: Cookie defines the sticky cookie configuration. diff --git a/pkg/config/dynamic/middlewares.go b/pkg/config/dynamic/middlewares.go index 52d349c4d..97981ae8a 100644 --- a/pkg/config/dynamic/middlewares.go +++ b/pkg/config/dynamic/middlewares.go @@ -60,7 +60,7 @@ type ContentType struct{} // AddPrefix holds the add prefix middleware configuration. // This middleware updates the path of a request before forwarding it. -// More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/addprefix/ +// More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/addprefix/ type AddPrefix struct { // Prefix is the string to add before the current path in the requested URL. // It should include a leading slash (/). @@ -71,7 +71,7 @@ type AddPrefix struct { // BasicAuth holds the basic auth middleware configuration. // This middleware restricts access to your services to known users. -// More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/basicauth/ +// More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/basicauth/ type BasicAuth struct { // Users is an array of authorized users. // Each user must be declared using the name:hashed-password format. @@ -86,7 +86,7 @@ type BasicAuth struct { // Default: false. RemoveHeader bool `json:"removeHeader,omitempty" toml:"removeHeader,omitempty" yaml:"removeHeader,omitempty" export:"true"` // HeaderField defines a header field to store the authenticated user. - // More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/basicauth/#headerfield + // More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/basicauth/#headerfield HeaderField string `json:"headerField,omitempty" toml:"headerField,omitempty" yaml:"headerField,omitempty" export:"true"` } @@ -94,7 +94,7 @@ type BasicAuth struct { // Buffering holds the buffering middleware configuration. // This middleware retries or limits the size of requests that can be forwarded to backends. -// More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/buffering/#maxrequestbodybytes +// More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/buffering/#maxrequestbodybytes type Buffering struct { // MaxRequestBodyBytes defines the maximum allowed body size for the request (in bytes). // If the request exceeds the allowed size, it is not forwarded to the service, and the client gets a 413 (Request Entity Too Large) response. @@ -112,7 +112,7 @@ type Buffering struct { MemResponseBodyBytes int64 `json:"memResponseBodyBytes,omitempty" toml:"memResponseBodyBytes,omitempty" yaml:"memResponseBodyBytes,omitempty" export:"true"` // RetryExpression defines the retry conditions. // It is a logical combination of functions with operators AND (&&) and OR (||). - // More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/buffering/#retryexpression + // More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/buffering/#retryexpression RetryExpression string `json:"retryExpression,omitempty" toml:"retryExpression,omitempty" yaml:"retryExpression,omitempty" export:"true"` } @@ -129,7 +129,7 @@ type Chain struct { // CircuitBreaker holds the circuit breaker middleware configuration. // This middleware protects the system from stacking requests to unhealthy services, resulting in cascading failures. -// More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/circuitbreaker/ +// More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/circuitbreaker/ type CircuitBreaker struct { // Expression defines the expression that, once matched, opens the circuit breaker and applies the fallback mechanism instead of calling the services. Expression string `json:"expression,omitempty" toml:"expression,omitempty" yaml:"expression,omitempty" export:"true"` @@ -152,7 +152,7 @@ func (c *CircuitBreaker) SetDefaults() { // Compress holds the compress middleware configuration. // This middleware compresses responses before sending them to the client, using gzip compression. -// More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/compress/ +// More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/compress/ type Compress struct { // ExcludedContentTypes defines the list of content types to compare the Content-Type header of the incoming requests and responses before compressing. // `application/grpc` is always excluded. @@ -166,7 +166,7 @@ type Compress struct { // DigestAuth holds the digest auth middleware configuration. // This middleware restricts access to your services to known users. -// More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/digestauth/ +// More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/digestauth/ type DigestAuth struct { // Users defines the authorized users. // Each user should be declared using the name:realm:encoded-password format. @@ -179,7 +179,7 @@ type DigestAuth struct { // Default: traefik. Realm string `json:"realm,omitempty" toml:"realm,omitempty" yaml:"realm,omitempty"` // HeaderField defines a header field to store the authenticated user. - // More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/basicauth/#headerfield + // More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/basicauth/#headerfield HeaderField string `json:"headerField,omitempty" toml:"headerField,omitempty" yaml:"headerField,omitempty" export:"true"` } @@ -205,7 +205,7 @@ type ErrorPage struct { // ForwardAuth holds the forward auth middleware configuration. // This middleware delegates the request authentication to a Service. -// More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/forwardauth/ +// More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/forwardauth/ type ForwardAuth struct { // Address defines the authentication server address. Address string `json:"address,omitempty" toml:"address,omitempty" yaml:"address,omitempty"` @@ -216,7 +216,7 @@ type ForwardAuth struct { // AuthResponseHeaders defines the list of headers to copy from the authentication server response and set on forwarded request, replacing any existing conflicting headers. AuthResponseHeaders []string `json:"authResponseHeaders,omitempty" toml:"authResponseHeaders,omitempty" yaml:"authResponseHeaders,omitempty" export:"true"` // AuthResponseHeadersRegex defines the regex to match headers to copy from the authentication server response and set on forwarded request, after stripping all headers that match the regex. - // More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/forwardauth/#authresponseheadersregex + // More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/forwardauth/#authresponseheadersregex AuthResponseHeadersRegex string `json:"authResponseHeadersRegex,omitempty" toml:"authResponseHeadersRegex,omitempty" yaml:"authResponseHeadersRegex,omitempty" export:"true"` // AuthRequestHeaders defines the list of the headers to copy from the request to the authentication server. // If not set or empty then all request headers are passed. @@ -227,7 +227,7 @@ type ForwardAuth struct { // Headers holds the headers middleware configuration. // This middleware manages the requests and responses headers. -// More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/headers/#customrequestheaders +// More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/headers/#customrequestheaders type Headers struct { // CustomRequestHeaders defines the header names and values to apply to the request. CustomRequestHeaders map[string]string `json:"customRequestHeaders,omitempty" toml:"customRequestHeaders,omitempty" yaml:"customRequestHeaders,omitempty" export:"true"` @@ -337,7 +337,7 @@ func (h *Headers) HasSecureHeadersDefined() bool { // +k8s:deepcopy-gen=true // IPStrategy holds the IP strategy configuration used by Traefik to determine the client IP. -// More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/ipallowlist/#ipstrategy +// More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/ipallowlist/#ipstrategy type IPStrategy struct { // Depth tells Traefik to use the X-Forwarded-For header and take the IP located at the depth position (starting from the right). Depth int `json:"depth,omitempty" toml:"depth,omitempty" yaml:"depth,omitempty" export:"true"` @@ -378,7 +378,7 @@ func (s *IPStrategy) Get() (ip.Strategy, error) { // IPAllowList holds the IP allowlist middleware configuration. // This middleware accepts / refuses requests based on the client IP. -// More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/ipallowlist/ +// More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/ipallowlist/ type IPAllowList struct { // SourceRange defines the set of allowed IPs (or ranges of allowed IPs by using CIDR notation). SourceRange []string `json:"sourceRange,omitempty" toml:"sourceRange,omitempty" yaml:"sourceRange,omitempty"` @@ -389,7 +389,7 @@ type IPAllowList struct { // InFlightReq holds the in-flight request middleware configuration. // This middleware limits the number of requests being processed and served concurrently. -// More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/inflightreq/ +// More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/inflightreq/ type InFlightReq struct { // Amount defines the maximum amount of allowed simultaneous in-flight request. // The middleware responds with HTTP 429 Too Many Requests if there are already amount requests in progress (based on the same sourceCriterion strategy). @@ -397,7 +397,7 @@ type InFlightReq struct { // SourceCriterion defines what criterion is used to group requests as originating from a common source. // If several strategies are defined at the same time, an error will be raised. // If none are set, the default is to use the requestHost. - // More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/inflightreq/#sourcecriterion + // More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/inflightreq/#sourcecriterion SourceCriterion *SourceCriterion `json:"sourceCriterion,omitempty" toml:"sourceCriterion,omitempty" yaml:"sourceCriterion,omitempty" export:"true"` } @@ -405,7 +405,7 @@ type InFlightReq struct { // PassTLSClientCert holds the pass TLS client cert middleware configuration. // This middleware adds the selected data from the passed client TLS certificate to a header. -// More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/passtlsclientcert/ +// More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/passtlsclientcert/ type PassTLSClientCert struct { // PEM sets the X-Forwarded-Tls-Client-Cert header with the certificate. PEM bool `json:"pem,omitempty" toml:"pem,omitempty" yaml:"pem,omitempty" export:"true"` @@ -461,7 +461,7 @@ func (r *RateLimit) SetDefaults() { // RedirectRegex holds the redirect regex middleware configuration. // This middleware redirects a request using regex matching and replacement. -// More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/redirectregex/#regex +// More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/redirectregex/#regex type RedirectRegex struct { // Regex defines the regex used to match and capture elements from the request URL. Regex string `json:"regex,omitempty" toml:"regex,omitempty" yaml:"regex,omitempty"` @@ -475,7 +475,7 @@ type RedirectRegex struct { // RedirectScheme holds the redirect scheme middleware configuration. // This middleware redirects requests from a scheme/port to another. -// More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/redirectscheme/ +// More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/redirectscheme/ type RedirectScheme struct { // Scheme defines the scheme of the new URL. Scheme string `json:"scheme,omitempty" toml:"scheme,omitempty" yaml:"scheme,omitempty" export:"true"` @@ -489,7 +489,7 @@ type RedirectScheme struct { // ReplacePath holds the replace path middleware configuration. // This middleware replaces the path of the request URL and store the original path in an X-Replaced-Path header. -// More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/replacepath/ +// More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/replacepath/ type ReplacePath struct { // Path defines the path to use as replacement in the request URL. Path string `json:"path,omitempty" toml:"path,omitempty" yaml:"path,omitempty" export:"true"` @@ -499,7 +499,7 @@ type ReplacePath struct { // ReplacePathRegex holds the replace path regex middleware configuration. // This middleware replaces the path of a URL using regex matching and replacement. -// More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/replacepathregex/ +// More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/replacepathregex/ type ReplacePathRegex struct { // Regex defines the regular expression used to match and capture the path from the request URL. Regex string `json:"regex,omitempty" toml:"regex,omitempty" yaml:"regex,omitempty" export:"true"` @@ -512,7 +512,7 @@ type ReplacePathRegex struct { // Retry holds the retry middleware configuration. // This middleware reissues requests a given number of times to a backend server if that server does not reply. // As soon as the server answers, the middleware stops retrying, regardless of the response status. -// More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/retry/ +// More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/retry/ type Retry struct { // Attempts defines how many times the request should be retried. Attempts int `json:"attempts,omitempty" toml:"attempts,omitempty" yaml:"attempts,omitempty" export:"true"` @@ -528,7 +528,7 @@ type Retry struct { // StripPrefix holds the strip prefix middleware configuration. // This middleware removes the specified prefixes from the URL path. -// More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/stripprefix/ +// More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/stripprefix/ type StripPrefix struct { // Prefixes defines the prefixes to strip from the request URL. Prefixes []string `json:"prefixes,omitempty" toml:"prefixes,omitempty" yaml:"prefixes,omitempty" export:"true"` @@ -538,7 +538,7 @@ type StripPrefix struct { // StripPrefixRegex holds the strip prefix regex middleware configuration. // This middleware removes the matching prefixes from the URL path. -// More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/stripprefixregex/ +// More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/stripprefixregex/ type StripPrefixRegex struct { // Regex defines the regular expression to match the path prefix from the request URL. Regex []string `json:"regex,omitempty" toml:"regex,omitempty" yaml:"regex,omitempty" export:"true"` diff --git a/pkg/config/dynamic/tcp_config.go b/pkg/config/dynamic/tcp_config.go index 9c84d3e5d..19007fa8c 100644 --- a/pkg/config/dynamic/tcp_config.go +++ b/pkg/config/dynamic/tcp_config.go @@ -114,7 +114,7 @@ type TCPServer struct { // +k8s:deepcopy-gen=true // ProxyProtocol holds the PROXY Protocol configuration. -// More info: https://doc.traefik.io/traefik/v2.9/routing/services/#proxy-protocol +// More info: https://doc.traefik.io/traefik/v3.0/routing/services/#proxy-protocol type ProxyProtocol struct { // Version defines the PROXY Protocol version to use. Version int `json:"version,omitempty" toml:"version,omitempty" yaml:"version,omitempty" export:"true"` diff --git a/pkg/config/dynamic/tcp_middlewares.go b/pkg/config/dynamic/tcp_middlewares.go index a9e191287..a688d4bd9 100644 --- a/pkg/config/dynamic/tcp_middlewares.go +++ b/pkg/config/dynamic/tcp_middlewares.go @@ -13,7 +13,7 @@ type TCPMiddleware struct { // TCPInFlightConn holds the TCP InFlightConn middleware configuration. // This middleware prevents services from being overwhelmed with high load, // by limiting the number of allowed simultaneous connections for one IP. -// More info: https://doc.traefik.io/traefik/v2.9/middlewares/tcp/inflightconn/ +// More info: https://doc.traefik.io/traefik/v3.0/middlewares/tcp/inflightconn/ type TCPInFlightConn struct { // Amount defines the maximum amount of allowed simultaneous connections. // The middleware closes the connection if there are already amount connections opened. diff --git a/pkg/provider/kubernetes/crd/traefik/v1alpha1/ingressroute.go b/pkg/provider/kubernetes/crd/traefik/v1alpha1/ingressroute.go index a4dbe2bfd..93f57998c 100644 --- a/pkg/provider/kubernetes/crd/traefik/v1alpha1/ingressroute.go +++ b/pkg/provider/kubernetes/crd/traefik/v1alpha1/ingressroute.go @@ -13,72 +13,72 @@ type IngressRouteSpec struct { Routes []Route `json:"routes"` // EntryPoints defines the list of entry point names to bind to. // Entry points have to be configured in the static configuration. - // More info: https://doc.traefik.io/traefik/v2.9/routing/entrypoints/ + // More info: https://doc.traefik.io/traefik/v3.0/routing/entrypoints/ // Default: all. EntryPoints []string `json:"entryPoints,omitempty"` // TLS defines the TLS configuration. - // More info: https://doc.traefik.io/traefik/v2.9/routing/routers/#tls + // More info: https://doc.traefik.io/traefik/v3.0/routing/routers/#tls TLS *TLS `json:"tls,omitempty"` } // Route holds the HTTP route configuration. type Route struct { // Match defines the router's rule. - // More info: https://doc.traefik.io/traefik/v2.9/routing/routers/#rule + // More info: https://doc.traefik.io/traefik/v3.0/routing/routers/#rule Match string `json:"match"` // Kind defines the kind of the route. // Rule is the only supported kind. // +kubebuilder:validation:Enum=Rule Kind string `json:"kind"` // Priority defines the router's priority. - // More info: https://doc.traefik.io/traefik/v2.9/routing/routers/#priority + // More info: https://doc.traefik.io/traefik/v3.0/routing/routers/#priority Priority int `json:"priority,omitempty"` // Services defines the list of Service. // It can contain any combination of TraefikService and/or reference to a Kubernetes Service. Services []Service `json:"services,omitempty"` // Middlewares defines the list of references to Middleware resources. - // More info: https://doc.traefik.io/traefik/v2.9/routing/providers/kubernetes-crd/#kind-middleware + // More info: https://doc.traefik.io/traefik/v3.0/routing/providers/kubernetes-crd/#kind-middleware Middlewares []MiddlewareRef `json:"middlewares,omitempty"` } // TLS holds the TLS configuration. -// More info: https://doc.traefik.io/traefik/v2.9/routing/routers/#tls +// More info: https://doc.traefik.io/traefik/v3.0/routing/routers/#tls type TLS struct { // SecretName is the name of the referenced Kubernetes Secret to specify the certificate details. SecretName string `json:"secretName,omitempty"` // Options defines the reference to a TLSOption, that specifies the parameters of the TLS connection. // If not defined, the `default` TLSOption is used. - // More info: https://doc.traefik.io/traefik/v2.9/https/tls/#tls-options + // More info: https://doc.traefik.io/traefik/v3.0/https/tls/#tls-options Options *TLSOptionRef `json:"options,omitempty"` // Store defines the reference to the TLSStore, that will be used to store certificates. // Please note that only `default` TLSStore can be used. Store *TLSStoreRef `json:"store,omitempty"` // CertResolver defines the name of the certificate resolver to use. // Cert resolvers have to be configured in the static configuration. - // More info: https://doc.traefik.io/traefik/v2.9/https/acme/#certificate-resolvers + // More info: https://doc.traefik.io/traefik/v3.0/https/acme/#certificate-resolvers CertResolver string `json:"certResolver,omitempty"` // Domains defines the list of domains that will be used to issue certificates. - // More info: https://doc.traefik.io/traefik/v2.9/routing/routers/#domains + // More info: https://doc.traefik.io/traefik/v3.0/routing/routers/#domains Domains []types.Domain `json:"domains,omitempty"` } // TLSOptionRef is a reference to a TLSOption resource. type TLSOptionRef struct { // Name defines the name of the referenced TLSOption. - // More info: https://doc.traefik.io/traefik/v2.9/routing/providers/kubernetes-crd/#kind-tlsoption + // More info: https://doc.traefik.io/traefik/v3.0/routing/providers/kubernetes-crd/#kind-tlsoption Name string `json:"name"` // Namespace defines the namespace of the referenced TLSOption. - // More info: https://doc.traefik.io/traefik/v2.9/routing/providers/kubernetes-crd/#kind-tlsoption + // More info: https://doc.traefik.io/traefik/v3.0/routing/providers/kubernetes-crd/#kind-tlsoption Namespace string `json:"namespace,omitempty"` } // TLSStoreRef is a reference to a TLSStore resource. type TLSStoreRef struct { // Name defines the name of the referenced TLSStore. - // More info: https://doc.traefik.io/traefik/v2.9/routing/providers/kubernetes-crd/#kind-tlsstore + // More info: https://doc.traefik.io/traefik/v3.0/routing/providers/kubernetes-crd/#kind-tlsstore Name string `json:"name"` // Namespace defines the namespace of the referenced TLSStore. - // More info: https://doc.traefik.io/traefik/v2.9/routing/providers/kubernetes-crd/#kind-tlsstore + // More info: https://doc.traefik.io/traefik/v3.0/routing/providers/kubernetes-crd/#kind-tlsstore Namespace string `json:"namespace,omitempty"` } @@ -95,7 +95,7 @@ type LoadBalancerSpec struct { // Namespace defines the namespace of the referenced Kubernetes Service or TraefikService. Namespace string `json:"namespace,omitempty"` // Sticky defines the sticky sessions configuration. - // More info: https://doc.traefik.io/traefik/v2.9/routing/services/#sticky-sessions + // More info: https://doc.traefik.io/traefik/v3.0/routing/services/#sticky-sessions Sticky *dynamic.Sticky `json:"sticky,omitempty"` // Port defines the port of a Kubernetes Service. // This can be a reference to a named port. diff --git a/pkg/provider/kubernetes/crd/traefik/v1alpha1/ingressroutetcp.go b/pkg/provider/kubernetes/crd/traefik/v1alpha1/ingressroutetcp.go index 64b6c1872..4d89c7fdb 100644 --- a/pkg/provider/kubernetes/crd/traefik/v1alpha1/ingressroutetcp.go +++ b/pkg/provider/kubernetes/crd/traefik/v1alpha1/ingressroutetcp.go @@ -13,21 +13,21 @@ type IngressRouteTCPSpec struct { Routes []RouteTCP `json:"routes"` // EntryPoints defines the list of entry point names to bind to. // Entry points have to be configured in the static configuration. - // More info: https://doc.traefik.io/traefik/v2.9/routing/entrypoints/ + // More info: https://doc.traefik.io/traefik/v3.0/routing/entrypoints/ // Default: all. EntryPoints []string `json:"entryPoints,omitempty"` // TLS defines the TLS configuration on a layer 4 / TCP Route. - // More info: https://doc.traefik.io/traefik/v2.9/routing/routers/#tls_1 + // More info: https://doc.traefik.io/traefik/v3.0/routing/routers/#tls_1 TLS *TLSTCP `json:"tls,omitempty"` } // RouteTCP holds the TCP route configuration. type RouteTCP struct { // Match defines the router's rule. - // More info: https://doc.traefik.io/traefik/v2.9/routing/routers/#rule_1 + // More info: https://doc.traefik.io/traefik/v3.0/routing/routers/#rule_1 Match string `json:"match"` // Priority defines the router's priority. - // More info: https://doc.traefik.io/traefik/v2.9/routing/routers/#priority_1 + // More info: https://doc.traefik.io/traefik/v3.0/routing/routers/#priority_1 Priority int `json:"priority,omitempty"` // Services defines the list of TCP services. Services []ServiceTCP `json:"services,omitempty"` @@ -36,7 +36,7 @@ type RouteTCP struct { } // TLSTCP holds the TLS configuration for an IngressRouteTCP. -// More info: https://doc.traefik.io/traefik/v2.9/routing/routers/#tls_1 +// More info: https://doc.traefik.io/traefik/v3.0/routing/routers/#tls_1 type TLSTCP struct { // SecretName is the name of the referenced Kubernetes Secret to specify the certificate details. SecretName string `json:"secretName,omitempty"` @@ -44,17 +44,17 @@ type TLSTCP struct { Passthrough bool `json:"passthrough,omitempty"` // Options defines the reference to a TLSOption, that specifies the parameters of the TLS connection. // If not defined, the `default` TLSOption is used. - // More info: https://doc.traefik.io/traefik/v2.9/https/tls/#tls-options + // More info: https://doc.traefik.io/traefik/v3.0/https/tls/#tls-options Options *ObjectReference `json:"options,omitempty"` // Store defines the reference to the TLSStore, that will be used to store certificates. // Please note that only `default` TLSStore can be used. Store *ObjectReference `json:"store,omitempty"` // CertResolver defines the name of the certificate resolver to use. // Cert resolvers have to be configured in the static configuration. - // More info: https://doc.traefik.io/traefik/v2.9/https/acme/#certificate-resolvers + // More info: https://doc.traefik.io/traefik/v3.0/https/acme/#certificate-resolvers CertResolver string `json:"certResolver,omitempty"` // Domains defines the list of domains that will be used to issue certificates. - // More info: https://doc.traefik.io/traefik/v2.9/routing/routers/#domains + // More info: https://doc.traefik.io/traefik/v3.0/routing/routers/#domains Domains []types.Domain `json:"domains,omitempty"` } @@ -76,7 +76,7 @@ type ServiceTCP struct { // A negative value means an infinite deadline (i.e. the reading capability is never closed). TerminationDelay *int `json:"terminationDelay,omitempty"` // ProxyProtocol defines the PROXY protocol configuration. - // More info: https://doc.traefik.io/traefik/v2.9/routing/services/#proxy-protocol + // More info: https://doc.traefik.io/traefik/v3.0/routing/services/#proxy-protocol ProxyProtocol *dynamic.ProxyProtocol `json:"proxyProtocol,omitempty"` } diff --git a/pkg/provider/kubernetes/crd/traefik/v1alpha1/ingressrouteudp.go b/pkg/provider/kubernetes/crd/traefik/v1alpha1/ingressrouteudp.go index 4d313ad9c..374461346 100644 --- a/pkg/provider/kubernetes/crd/traefik/v1alpha1/ingressrouteudp.go +++ b/pkg/provider/kubernetes/crd/traefik/v1alpha1/ingressrouteudp.go @@ -11,7 +11,7 @@ type IngressRouteUDPSpec struct { Routes []RouteUDP `json:"routes"` // EntryPoints defines the list of entry point names to bind to. // Entry points have to be configured in the static configuration. - // More info: https://doc.traefik.io/traefik/v2.9/routing/entrypoints/ + // More info: https://doc.traefik.io/traefik/v3.0/routing/entrypoints/ // Default: all. EntryPoints []string `json:"entryPoints,omitempty"` } diff --git a/pkg/provider/kubernetes/crd/traefik/v1alpha1/middleware.go b/pkg/provider/kubernetes/crd/traefik/v1alpha1/middleware.go index e3f388ac1..c0d55d28f 100644 --- a/pkg/provider/kubernetes/crd/traefik/v1alpha1/middleware.go +++ b/pkg/provider/kubernetes/crd/traefik/v1alpha1/middleware.go @@ -12,7 +12,7 @@ import ( // +kubebuilder:storageversion // Middleware is the CRD implementation of a Traefik Middleware. -// More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/overview/ +// More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/overview/ type Middleware struct { metav1.TypeMeta `json:",inline"` // Standard object's metadata. @@ -58,7 +58,7 @@ type MiddlewareSpec struct { // ErrorPage holds the custom error middleware configuration. // This middleware returns a custom page in lieu of the default, according to configured ranges of HTTP Status codes. -// More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/errorpages/ +// More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/errorpages/ type ErrorPage struct { // Status defines which status or range of statuses should result in an error page. // It can be either a status code as a number (500), @@ -67,7 +67,7 @@ type ErrorPage struct { // or a combination of the two (404,418,500-599). Status []string `json:"status,omitempty"` // Service defines the reference to a Kubernetes Service that will serve the error page. - // More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/errorpages/#service + // More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/errorpages/#service Service Service `json:"service,omitempty"` // Query defines the URL for the error page (hosted by service). // The {status} variable can be used in order to insert the status code in the URL. @@ -92,7 +92,7 @@ type CircuitBreaker struct { // Chain holds the configuration of the chain middleware. // This middleware enables to define reusable combinations of other pieces of middleware. -// More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/chain/ +// More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/chain/ type Chain struct { // Middlewares is the list of MiddlewareRef which composes the chain. Middlewares []MiddlewareRef `json:"middlewares,omitempty"` @@ -102,7 +102,7 @@ type Chain struct { // BasicAuth holds the basic auth middleware configuration. // This middleware restricts access to your services to known users. -// More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/basicauth/ +// More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/basicauth/ type BasicAuth struct { // Secret is the name of the referenced Kubernetes Secret containing user credentials. Secret string `json:"secret,omitempty"` @@ -113,7 +113,7 @@ type BasicAuth struct { // Default: false. RemoveHeader bool `json:"removeHeader,omitempty"` // HeaderField defines a header field to store the authenticated user. - // More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/basicauth/#headerfield + // More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/basicauth/#headerfield HeaderField string `json:"headerField,omitempty"` } @@ -121,7 +121,7 @@ type BasicAuth struct { // DigestAuth holds the digest auth middleware configuration. // This middleware restricts access to your services to known users. -// More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/digestauth/ +// More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/digestauth/ type DigestAuth struct { // Secret is the name of the referenced Kubernetes Secret containing user credentials. Secret string `json:"secret,omitempty"` @@ -131,7 +131,7 @@ type DigestAuth struct { // Default: traefik. Realm string `json:"realm,omitempty"` // HeaderField defines a header field to store the authenticated user. - // More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/basicauth/#headerfield + // More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/basicauth/#headerfield HeaderField string `json:"headerField,omitempty"` } @@ -139,7 +139,7 @@ type DigestAuth struct { // ForwardAuth holds the forward auth middleware configuration. // This middleware delegates the request authentication to a Service. -// More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/forwardauth/ +// More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/forwardauth/ type ForwardAuth struct { // Address defines the authentication server address. Address string `json:"address,omitempty"` @@ -148,7 +148,7 @@ type ForwardAuth struct { // AuthResponseHeaders defines the list of headers to copy from the authentication server response and set on forwarded request, replacing any existing conflicting headers. AuthResponseHeaders []string `json:"authResponseHeaders,omitempty"` // AuthResponseHeadersRegex defines the regex to match headers to copy from the authentication server response and set on forwarded request, after stripping all headers that match the regex. - // More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/forwardauth/#authresponseheadersregex + // More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/forwardauth/#authresponseheadersregex AuthResponseHeadersRegex string `json:"authResponseHeadersRegex,omitempty"` // AuthRequestHeaders defines the list of the headers to copy from the request to the authentication server. // If not set or empty then all request headers are passed. @@ -173,7 +173,7 @@ type ClientTLS struct { // RateLimit holds the rate limit configuration. // This middleware ensures that services will receive a fair amount of requests, and allows one to define what fair is. -// More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/ratelimit/ +// More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/ratelimit/ type RateLimit struct { // Average is the maximum rate, by default in requests/s, allowed for the given source. // It defaults to 0, which means no rate limiting. @@ -197,7 +197,7 @@ type RateLimit struct { // Retry holds the retry middleware configuration. // This middleware reissues requests a given number of times to a backend server if that server does not reply. // As soon as the server answers, the middleware stops retrying, regardless of the response status. -// More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/retry/ +// More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/retry/ type Retry struct { // Attempts defines how many times the request should be retried. Attempts int `json:"attempts,omitempty"` diff --git a/pkg/provider/kubernetes/crd/traefik/v1alpha1/middlewaretcp.go b/pkg/provider/kubernetes/crd/traefik/v1alpha1/middlewaretcp.go index 2845b5357..655bf0e26 100644 --- a/pkg/provider/kubernetes/crd/traefik/v1alpha1/middlewaretcp.go +++ b/pkg/provider/kubernetes/crd/traefik/v1alpha1/middlewaretcp.go @@ -9,7 +9,7 @@ import ( // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // MiddlewareTCP is the CRD implementation of a Traefik TCP middleware. -// More info: https://doc.traefik.io/traefik/v2.9/middlewares/overview/ +// More info: https://doc.traefik.io/traefik/v3.0/middlewares/overview/ type MiddlewareTCP struct { metav1.TypeMeta `json:",inline"` // Standard object's metadata. diff --git a/pkg/provider/kubernetes/crd/traefik/v1alpha1/serverstransport.go b/pkg/provider/kubernetes/crd/traefik/v1alpha1/serverstransport.go index 10486a426..68a3438d2 100644 --- a/pkg/provider/kubernetes/crd/traefik/v1alpha1/serverstransport.go +++ b/pkg/provider/kubernetes/crd/traefik/v1alpha1/serverstransport.go @@ -13,7 +13,7 @@ import ( // ServersTransport is the CRD implementation of a ServersTransport. // If no serversTransport is specified, the default@internal will be used. // The default@internal serversTransport is created from the static configuration. -// More info: https://doc.traefik.io/traefik/v2.9/routing/services/#serverstransport_1 +// More info: https://doc.traefik.io/traefik/v3.0/routing/services/#serverstransport_1 type ServersTransport struct { metav1.TypeMeta `json:",inline"` // Standard object's metadata. diff --git a/pkg/provider/kubernetes/crd/traefik/v1alpha1/service.go b/pkg/provider/kubernetes/crd/traefik/v1alpha1/service.go index ea4549fe7..09fafb11b 100644 --- a/pkg/provider/kubernetes/crd/traefik/v1alpha1/service.go +++ b/pkg/provider/kubernetes/crd/traefik/v1alpha1/service.go @@ -13,7 +13,7 @@ import ( // TraefikService object allows to: // - Apply weight to Services on load-balancing // - Mirror traffic on services -// More info: https://doc.traefik.io/traefik/v2.9/routing/providers/kubernetes-crd/#kind-traefikservice +// More info: https://doc.traefik.io/traefik/v3.0/routing/providers/kubernetes-crd/#kind-traefikservice type TraefikService struct { metav1.TypeMeta `json:",inline"` // Standard object's metadata. @@ -49,7 +49,7 @@ type TraefikServiceSpec struct { // +k8s:deepcopy-gen=true // Mirroring holds the mirroring service configuration. -// More info: https://doc.traefik.io/traefik/v2.9/routing/services/#mirroring-service +// More info: https://doc.traefik.io/traefik/v3.0/routing/services/#mirroring-service type Mirroring struct { LoadBalancerSpec `json:",inline"` @@ -75,11 +75,11 @@ type MirrorService struct { // +k8s:deepcopy-gen=true // WeightedRoundRobin holds the weighted round-robin configuration. -// More info: https://doc.traefik.io/traefik/v2.9/routing/services/#weighted-round-robin-service +// More info: https://doc.traefik.io/traefik/v3.0/routing/services/#weighted-round-robin-service type WeightedRoundRobin struct { // Services defines the list of Kubernetes Service and/or TraefikService to load-balance, with weight. Services []Service `json:"services,omitempty"` // Sticky defines whether sticky sessions are enabled. - // More info: https://doc.traefik.io/traefik/v2.9/routing/providers/kubernetes-crd/#stickiness-and-load-balancing + // More info: https://doc.traefik.io/traefik/v3.0/routing/providers/kubernetes-crd/#stickiness-and-load-balancing Sticky *dynamic.Sticky `json:"sticky,omitempty"` } diff --git a/pkg/provider/kubernetes/crd/traefik/v1alpha1/tlsoption.go b/pkg/provider/kubernetes/crd/traefik/v1alpha1/tlsoption.go index 41908e7c9..f8132c138 100644 --- a/pkg/provider/kubernetes/crd/traefik/v1alpha1/tlsoption.go +++ b/pkg/provider/kubernetes/crd/traefik/v1alpha1/tlsoption.go @@ -9,7 +9,7 @@ import ( // +kubebuilder:storageversion // TLSOption is the CRD implementation of a Traefik TLS Option, allowing to configure some parameters of the TLS connection. -// More info: https://doc.traefik.io/traefik/v2.9/https/tls/#tls-options +// More info: https://doc.traefik.io/traefik/v3.0/https/tls/#tls-options type TLSOption struct { metav1.TypeMeta `json:",inline"` // Standard object's metadata. @@ -32,17 +32,17 @@ type TLSOptionSpec struct { // Default: None. MaxVersion string `json:"maxVersion,omitempty"` // CipherSuites defines the list of supported cipher suites for TLS versions up to TLS 1.2. - // More info: https://doc.traefik.io/traefik/v2.9/https/tls/#cipher-suites + // More info: https://doc.traefik.io/traefik/v3.0/https/tls/#cipher-suites CipherSuites []string `json:"cipherSuites,omitempty"` // CurvePreferences defines the preferred elliptic curves in a specific order. - // More info: https://doc.traefik.io/traefik/v2.9/https/tls/#curve-preferences + // More info: https://doc.traefik.io/traefik/v3.0/https/tls/#curve-preferences CurvePreferences []string `json:"curvePreferences,omitempty"` // ClientAuth defines the server's policy for TLS Client Authentication. ClientAuth ClientAuth `json:"clientAuth,omitempty"` // SniStrict defines whether Traefik allows connections from clients connections that do not specify a server_name extension. SniStrict bool `json:"sniStrict,omitempty"` // ALPNProtocols defines the list of supported application level protocols for the TLS handshake, in order of preference. - // More info: https://doc.traefik.io/traefik/v2.9/https/tls/#alpn-protocols + // More info: https://doc.traefik.io/traefik/v3.0/https/tls/#alpn-protocols ALPNProtocols []string `json:"alpnProtocols,omitempty"` } diff --git a/pkg/provider/kubernetes/crd/traefik/v1alpha1/tlsstore.go b/pkg/provider/kubernetes/crd/traefik/v1alpha1/tlsstore.go index c55884210..2af463df1 100644 --- a/pkg/provider/kubernetes/crd/traefik/v1alpha1/tlsstore.go +++ b/pkg/provider/kubernetes/crd/traefik/v1alpha1/tlsstore.go @@ -12,7 +12,7 @@ import ( // TLSStore is the CRD implementation of a Traefik TLS Store. // For the time being, only the TLSStore named default is supported. // This means that you cannot have two stores that are named default in different Kubernetes namespaces. -// More info: https://doc.traefik.io/traefik/v2.9/https/tls/#certificates-stores +// More info: https://doc.traefik.io/traefik/v3.0/https/tls/#certificates-stores type TLSStore struct { metav1.TypeMeta `json:",inline"` // Standard object's metadata. diff --git a/script/gcg/traefik-rc-first.toml b/script/gcg/traefik-rc-first.toml index 67793a9c0..c7c2eab61 100644 --- a/script/gcg/traefik-rc-first.toml +++ b/script/gcg/traefik-rc-first.toml @@ -4,11 +4,11 @@ RepositoryName = "traefik" OutputType = "file" FileName = "traefik_changelog.md" -# example RC1 of v2.9.0 +# example RC1 of v3.0.0-beta1 CurrentRef = "master" -PreviousRef = "v2.8.0-rc1" +PreviousRef = "v2.9.0-rc1" BaseBranch = "master" -FutureCurrentRefName = "v2.9.0-rc1" +FutureCurrentRefName = "v3.0.0-beta1" ThresholdPreviousRef = 10 ThresholdCurrentRef = 10 From 8cf9385938ec7510b815737c3b5beacbdd2bf485 Mon Sep 17 00:00:00 2001 From: Tom Moulard Date: Tue, 6 Dec 2022 10:40:06 +0100 Subject: [PATCH 12/19] Rework Host and HostRegexp matchers Co-authored-by: Simon Delicata --- docs/content/routing/routers/index.md | 88 ++++++++++++++------------- pkg/muxer/http/matcher.go | 27 +++----- pkg/muxer/http/matcher_test.go | 31 +++++++++- 3 files changed, 80 insertions(+), 66 deletions(-) diff --git a/docs/content/routing/routers/index.md b/docs/content/routing/routers/index.md index 14acc4448..51328317b 100644 --- a/docs/content/routing/routers/index.md +++ b/docs/content/routing/routers/index.md @@ -244,14 +244,14 @@ The table below lists all the available matchers: The usual AND (`&&`) and OR (`||`) logical operators can be used, with the expected precedence rules, as well as parentheses. - + One can invert a matcher by using the NOT (`!`) operator. - + The following rule matches requests where: - + - either host is `example.com` OR, - host is `example.org` AND path is NOT `/traefik` - + ```yaml Host(`example.com`) || (Host(`example.org`) && !Path(`/traefik`)) ``` @@ -261,21 +261,21 @@ The table below lists all the available matchers: The `Header` and `HeaderRegexp` matchers allow to match requests that contain specific header. !!! example "Examples" - + Match requests with a `Content-Type` header set to `application/yaml`: - + ```yaml Header(`Content-Type`, `application/yaml`) ``` - + Match requests with a `Content-Type` header set to either `application/json` or `application/yaml`: - + ```yaml HeaderRegexp(`Content-Type`, `^application/(json|yaml)$`) ``` - + To match headers [case-insensitively](https://en.wikipedia.org/wiki/Case_sensitivity), use the `(?i)` option: - + ```yaml HeaderRegexp(`Content-Type`, `(?i)^application/(json|yaml)$`) ``` @@ -288,22 +288,24 @@ These matchers do not support non-ASCII characters, use punycode encoded values If no Host is set in the request URL (e.g., it's an IP address), these matchers will look at the `Host` header. +These matchers will match the request's host in lowercase. + !!! example "Examples" Match requests with `Host` set to `example.com`: - + ```yaml Host(`example.com`) ``` - + Match requests sent to any subdomain of `example.com`: - + ```yaml HostRegexp(`^.+\.example\.com$`) ``` Match requests with `Host` set to either `example.com` or `example.org`: - + ```yaml HostRegexp(`^example\.(com|org)$`) ``` @@ -321,7 +323,7 @@ The `Method` matchers allows to match requests sent with the given method. !!! example "Example" Match `OPTIONS` requests: - + ```yaml Method(`OPTIONS`) ``` @@ -337,14 +339,14 @@ Path are always starting with a `/`, except for `PathRegexp`. !!! example "Examples" Match `/products` but neither `/products/shoes` nor `/products/`: - + ```yaml Path(`/products`) ``` - + Match `/products` as well as everything under `/products`, such as `/products/shoes`, `/products/` but also `/products-for-sale`: - + ```yaml PathPrefix(`/products`) ``` @@ -376,7 +378,7 @@ The `Query` and `QueryRegexp` matchers allow to match requests based on query pa !!! example "Examples" Match requests with a `mobile` query parameter set to `true`, such as in `/search?mobile=true`: - + ```yaml Query(`mobile`, `true`) ``` @@ -388,13 +390,13 @@ The `Query` and `QueryRegexp` matchers allow to match requests based on query pa ``` Match requests with a `mobile` query parameter set to either `true` or `yes`: - + ```yaml QueryRegexp(`mobile`, `^(true|yes)$`) ``` Match requests with a `mobile` query parameter set to any value (including the empty value): - + ```yaml QueryRegexp(`mobile`, `^.*$`) ``` @@ -414,15 +416,15 @@ It only matches the request client IP and does not use the `X-Forwarded-For` hea !!! example "Examples" Match requests coming from a given IP: - + ```yaml tab="IPv4" ClientIP(`10.76.105.11`) ``` - + ```yaml tab="IPv6" ClientIP(`::1`) ``` - + Match requests coming from a given subnet: ```yaml tab="IPv4" @@ -831,9 +833,9 @@ If you want to limit the router scope to a set of entry points, set the entry po a situation where both sides are waiting for data and the connection appears to have hanged. - The only way that Traefik can deal with such a case, is to make - sure that on the concerned entry point, there is no TLS router - whatsoever (neither TCP nor HTTP), and there is at least one + The only way that Traefik can deal with such a case, is to make + sure that on the concerned entry point, there is no TLS router + whatsoever (neither TCP nor HTTP), and there is at least one non-TLS TCP router that leads to the server in question. ??? example "Listens to Every Entry Point" @@ -990,14 +992,14 @@ The table below lists all the available matchers: The usual AND (`&&`) and OR (`||`) logical operators can be used, with the expected precedence rules, as well as parentheses. - + One can invert a matcher by using the NOT (`!`) operator. - + The following rule matches connections where: - + - either Server Name Indication is `example.com` OR, - Server Name Indication is `example.org` AND ALPN protocol is NOT `h2` - + ```yaml HostSNI(`example.com`) || (HostSNI(`example.org`) && !ALPN(`h2`)) ``` @@ -1019,23 +1021,23 @@ These matchers do not support non-ASCII characters, use punycode encoded values !!! example "Examples" Match all connections: - + ```yaml tab="HostSNI" HostSNI(`*`) ``` - + ```yaml tab="HostSNIRegexp" HostSNIRegexp(`^.*$`) ``` Match TCP connections sent to `example.com`: - + ```yaml HostSNI(`example.com`) ``` Match TCP connections openned on any subdomain of `example.com`: - + ```yaml HostSNIRegexp(`^.+\.example\.com$`) ``` @@ -1047,17 +1049,17 @@ The `ClientIP` matcher allows matching connections opened by a client with the g !!! example "Examples" Match connections opened by a given IP: - + ```yaml tab="IPv4" ClientIP(`10.76.105.11`) ``` - + ```yaml tab="IPv6" ClientIP(`::1`) ``` - + Match connections coming from a given subnet: - + ```yaml tab="IPv4" ClientIP(`192.168.1.0/24`) ``` @@ -1078,14 +1080,14 @@ protocol, and Traefik returns an error if this is attempted. !!! example "Example" Match connections using the ALPN protocol `h2`: - + ```yaml ALPN(`h2`) ``` ### Priority -To avoid path overlap, routes are sorted, by default, in descending order using rules length. +To avoid path overlap, routes are sorted, by default, in descending order using rules length. The priority is directly equal to the length of the rule, and so the longest length has the highest priority. A value of `0` for the priority is ignored: `priority = 0` means that the default rules length sorting is used. @@ -1415,8 +1417,8 @@ So UDP "routers" at this time are pretty much only load-balancers in one form or It basically means that some state is kept about an ongoing communication between a client and a backend, notably so that the proxy knows where to forward a response packet from a backend. As expected, a `timeout` is associated to each of these sessions, - so that they get cleaned out if they go through a period of inactivity longer than a given duration. - Timeout can be configured using the `entryPoints.name.udp.timeout` option as described + so that they get cleaned out if they go through a period of inactivity longer than a given duration. + Timeout can be configured using the `entryPoints.name.udp.timeout` option as described under [EntryPoints](../entrypoints/#udp-options). ### EntryPoints diff --git a/pkg/muxer/http/matcher.go b/pkg/muxer/http/matcher.go index ceafbd6b4..d30e6df61 100644 --- a/pkg/muxer/http/matcher.go +++ b/pkg/muxer/http/matcher.go @@ -75,25 +75,6 @@ func host(route *mux.Route, hosts ...string) error { route.MatcherFunc(func(req *http.Request, _ *mux.RouteMatch) bool { reqHost := requestdecorator.GetCanonizedHost(req.Context()) if len(reqHost) == 0 { - // If the request is an HTTP/1.0 request, then a Host may not be defined. - if req.ProtoAtLeast(1, 1) { - log.Ctx(req.Context()).Warn().Str("host", req.Host).Msg("Could not retrieve CanonizedHost, rejecting") - } - - return false - } - - flatH := requestdecorator.GetCNAMEFlatten(req.Context()) - if len(flatH) > 0 { - if strings.EqualFold(reqHost, host) || strings.EqualFold(flatH, host) { - return true - } - - log.Ctx(req.Context()).Debug(). - Str("host", reqHost). - Str("flattenHost", flatH). - Str("matcher", host). - Msg("CNAMEFlattening: resolved Host does not match") return false } @@ -101,6 +82,11 @@ func host(route *mux.Route, hosts ...string) error { return true } + flatH := requestdecorator.GetCNAMEFlatten(req.Context()) + if len(flatH) > 0 { + return strings.EqualFold(flatH, host) + } + // Check for match on trailing period on host if last := len(host) - 1; last >= 0 && host[last] == '.' { h := host[:last] @@ -136,7 +122,8 @@ func hostRegexp(route *mux.Route, hosts ...string) error { } route.MatcherFunc(func(req *http.Request, _ *mux.RouteMatch) bool { - return re.MatchString(req.Host) + return re.MatchString(requestdecorator.GetCanonizedHost(req.Context())) || + re.MatchString(requestdecorator.GetCNAMEFlatten(req.Context())) }) return nil diff --git a/pkg/muxer/http/matcher_test.go b/pkg/muxer/http/matcher_test.go index 231d34f91..2d31d8dc8 100644 --- a/pkg/muxer/http/matcher_test.go +++ b/pkg/muxer/http/matcher_test.go @@ -198,6 +198,7 @@ func TestHostMatcher(t *testing.T) { rule: "Host(`example.com`)", expected: map[string]int{ "https://example.com": http.StatusOK, + "https://example.com:8080": http.StatusOK, "https://example.com/path": http.StatusOK, "https://example.org": http.StatusNotFound, "https://example.org/path": http.StatusNotFound, @@ -227,6 +228,16 @@ func TestHostMatcher(t *testing.T) { "https://example.org./path": http.StatusNotFound, }, }, + { + desc: "valid Host matcher - matcher with UPPER case", + rule: "Host(`EXAMPLE.COM`)", + expected: map[string]int{ + "https://example.com": http.StatusOK, + "https://example.com/path": http.StatusOK, + "https://example.org": http.StatusNotFound, + "https://example.org/path": http.StatusNotFound, + }, + }, { desc: "valid Host matcher - puny-coded emoji", rule: "Host(`xn--9t9h.com`)", @@ -258,7 +269,7 @@ func TestHostMatcher(t *testing.T) { require.NoError(t, err) - // RequestDecorator is necessary for the host rule + // RequestDecorator is necessary for the Host matcher reqHost := requestdecorator.New(nil) results := make(map[string]int) @@ -312,11 +323,23 @@ func TestHostRegexpMatcher(t *testing.T) { rule: "HostRegexp(`^[a-zA-Z-]+\\.com$`)", expected: map[string]int{ "https://example.com": http.StatusOK, + "https://example.com:8080": http.StatusOK, "https://example.com/path": http.StatusOK, "https://example.org": http.StatusNotFound, "https://example.org/path": http.StatusNotFound, }, }, + { + desc: "valid HostRegexp matcher with case sensitive regexp", + rule: "HostRegexp(`^[A-Z]+\\.com$`)", + expected: map[string]int{ + "https://example.com": http.StatusNotFound, + "https://EXAMPLE.com": http.StatusNotFound, + "https://example.com/path": http.StatusNotFound, + "https://example.org": http.StatusNotFound, + "https://example.org/path": http.StatusNotFound, + }, + }, { desc: "valid HostRegexp matcher with Traefik v2 syntax", rule: "HostRegexp(`{domain:[a-zA-Z-]+\\.com}`)", @@ -343,16 +366,18 @@ func TestHostRegexpMatcher(t *testing.T) { require.Error(t, err) return } - require.NoError(t, err) + // RequestDecorator is necessary for the HostRegexp matcher + reqHost := requestdecorator.New(nil) + results := make(map[string]int) for calledURL := range test.expected { w := httptest.NewRecorder() req := httptest.NewRequest(http.MethodGet, calledURL, http.NoBody) - muxer.ServeHTTP(w, req) + reqHost.ServeHTTP(w, req, muxer.ServeHTTP) results[calledURL] = w.Code } assert.Equal(t, test.expected, results) From 7e3fe48b80083b41e9ff82a474a36484cabc701a Mon Sep 17 00:00:00 2001 From: mpl Date: Tue, 6 Dec 2022 18:28:05 +0100 Subject: [PATCH 13/19] Handle broken TLS conf better Co-authored-by: Jean-Baptiste Doumenjou <925513+jbdoumenjou@users.noreply.github.com> Co-authored-by: Romain --- .../https/https_invalid_tls_options.toml | 60 +++++++ .../fixtures/tcp/multi-tls-options.toml | 11 ++ integration/https_test.go | 50 ++++++ integration/tcp_test.go | 8 + pkg/server/router/router.go | 19 ++- pkg/server/router/router_test.go | 88 ++++++++++- pkg/server/router/tcp/manager.go | 146 ++++++++++++------ pkg/server/router/tcp/router.go | 98 ++++++------ pkg/server/routerfactory.go | 2 +- pkg/tls/tlsmanager.go | 32 ++-- pkg/tls/tlsmanager_test.go | 49 ++---- 11 files changed, 404 insertions(+), 159 deletions(-) create mode 100644 integration/fixtures/https/https_invalid_tls_options.toml diff --git a/integration/fixtures/https/https_invalid_tls_options.toml b/integration/fixtures/https/https_invalid_tls_options.toml new file mode 100644 index 000000000..cc915c4ca --- /dev/null +++ b/integration/fixtures/https/https_invalid_tls_options.toml @@ -0,0 +1,60 @@ +[global] + checkNewVersion = false + sendAnonymousUsage = false + +[log] + level = "DEBUG" + +[entryPoints.websecure] + address = ":4443" + +[api] + insecure = true + +[providers.file] + filename = "{{ .SelfFilename }}" + +## dynamic configuration ## + +[http.routers] + + [http.routers.router1] + entryPoints = ["websecure"] + service = "service1" + rule = "Host(`snitest.com`)" + [http.routers.router1.tls] + options = "invalidTLSOptions" + + [http.routers.router2] + entryPoints = ["websecure"] + service = "service1" + rule = "Host(`snitest.org`)" + [http.routers.router2.tls] + + # fallback router + [http.routers.router3] + entryPoints = ["websecure"] + service = "service1" + rule = "Path(`/`)" + [http.routers.router3.tls] + +[[http.services.service1.loadBalancer.servers]] + url = "http://127.0.0.1:9010" + +[[tls.certificates]] + certFile = "fixtures/https/snitest.com.cert" + keyFile = "fixtures/https/snitest.com.key" + +[[tls.certificates]] + certFile = "fixtures/https/snitest.org.cert" + keyFile = "fixtures/https/snitest.org.key" + +[tls.options] + + [tls.options.default.clientAuth] + # Missing caFile to have an invalid mTLS configuration. + clientAuthType = "RequireAndVerifyClientCert" + + [tls.options.invalidTLSOptions.clientAuth] + # Missing caFile to have an invalid mTLS configuration. + clientAuthType = "RequireAndVerifyClientCert" diff --git a/integration/fixtures/tcp/multi-tls-options.toml b/integration/fixtures/tcp/multi-tls-options.toml index f0b4a931c..480df2d60 100644 --- a/integration/fixtures/tcp/multi-tls-options.toml +++ b/integration/fixtures/tcp/multi-tls-options.toml @@ -33,6 +33,13 @@ [tcp.routers.to-whoami-sni-strict.tls] options = "bar" + [tcp.routers.to-whoami-invalid-tls] + rule = "HostSNI(`whoami-i.test`)" + service = "whoami-no-cert" + entryPoints = [ "tcp" ] + [tcp.routers.to-whoami-invalid-tls.tls] + options = "invalid" + [tcp.services.whoami-no-cert] [tcp.services.whoami-no-cert.loadBalancer] [[tcp.services.whoami-no-cert.loadBalancer.servers]] @@ -45,3 +52,7 @@ [tls.options.bar] minVersion = "VersionTLS13" + + [tls.options.invalid.clientAuth] + # Missing CA files to have an invalid mTLS configuration. + clientAuthType = "RequireAndVerifyClientCert" diff --git a/integration/https_test.go b/integration/https_test.go index 868d31aff..19983faeb 100644 --- a/integration/https_test.go +++ b/integration/https_test.go @@ -1226,3 +1226,53 @@ func (s *HTTPSSuite) TestWithDomainFronting(c *check.C) { c.Assert(err, checker.IsNil) } } + +// TestWithInvalidTLSOption verifies the behavior when using an invalid tlsOption configuration. +func (s *HTTPSSuite) TestWithInvalidTLSOption(c *check.C) { + backend := startTestServer("9010", http.StatusOK, "server1") + defer backend.Close() + + file := s.adaptFile(c, "fixtures/https/https_invalid_tls_options.toml", struct{}{}) + defer os.Remove(file) + cmd, display := s.traefikCmd(withConfigFile(file)) + defer display(c) + err := cmd.Start() + c.Assert(err, checker.IsNil) + defer s.killCmd(cmd) + + // wait for Traefik + err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 500*time.Millisecond, try.BodyContains("Host(`snitest.com`)")) + c.Assert(err, checker.IsNil) + + testCases := []struct { + desc string + serverName string + }{ + { + desc: "With invalid TLS Options specified", + serverName: "snitest.com", + }, + { + desc: "With invalid Default TLS Options", + serverName: "snitest.org", + }, + { + desc: "With TLS Options without servername (fallback to default)", + }, + } + + for _, test := range testCases { + test := test + + tlsConfig := &tls.Config{ + InsecureSkipVerify: true, + } + if test.serverName != "" { + tlsConfig.ServerName = test.serverName + } + + conn, err := tls.Dial("tcp", "127.0.0.1:4443", tlsConfig) + c.Assert(err, checker.NotNil, check.Commentf("connected to server successfully")) + c.Assert(conn, checker.IsNil) + } +} diff --git a/integration/tcp_test.go b/integration/tcp_test.go index 52a3813c9..0a60ae5b4 100644 --- a/integration/tcp_test.go +++ b/integration/tcp_test.go @@ -116,6 +116,14 @@ func (s *TCPSuite) TestTLSOptions(c *check.C) { _, err = guessWhoTLSMaxVersion("127.0.0.1:8093", "whoami-d.test", true, tls.VersionTLS12) c.Assert(err, checker.NotNil) c.Assert(err.Error(), checker.Contains, "protocol version not supported") + + // Check that we can't reach a route with an invalid mTLS configuration. + conn, err := tls.Dial("tcp", "127.0.0.1:8093", &tls.Config{ + ServerName: "whoami-i.test", + InsecureSkipVerify: true, + }) + c.Assert(conn, checker.IsNil) + c.Assert(err, checker.NotNil) } func (s *TCPSuite) TestNonTLSFallback(c *check.C) { diff --git a/pkg/server/router/router.go b/pkg/server/router/router.go index 6f1819dbe..6359102de 100644 --- a/pkg/server/router/router.go +++ b/pkg/server/router/router.go @@ -3,6 +3,7 @@ package router import ( "context" "errors" + "fmt" "net/http" "github.com/containous/alice" @@ -16,6 +17,7 @@ import ( httpmuxer "github.com/traefik/traefik/v2/pkg/muxer/http" "github.com/traefik/traefik/v2/pkg/server/middleware" "github.com/traefik/traefik/v2/pkg/server/provider" + "github.com/traefik/traefik/v2/pkg/tls" ) type middlewareBuilder interface { @@ -35,10 +37,11 @@ type Manager struct { middlewaresBuilder middlewareBuilder chainBuilder *middleware.ChainBuilder conf *runtime.Configuration + tlsManager *tls.Manager } -// NewManager Creates a new Manager. -func NewManager(conf *runtime.Configuration, serviceManager serviceManager, middlewaresBuilder middlewareBuilder, chainBuilder *middleware.ChainBuilder, metricsRegistry metrics.Registry) *Manager { +// NewManager creates a new Manager. +func NewManager(conf *runtime.Configuration, serviceManager serviceManager, middlewaresBuilder middlewareBuilder, chainBuilder *middleware.ChainBuilder, metricsRegistry metrics.Registry, tlsManager *tls.Manager) *Manager { return &Manager{ routerHandlers: make(map[string]http.Handler), serviceManager: serviceManager, @@ -46,6 +49,7 @@ func NewManager(conf *runtime.Configuration, serviceManager serviceManager, midd middlewaresBuilder: middlewaresBuilder, chainBuilder: chainBuilder, conf: conf, + tlsManager: tlsManager, } } @@ -141,6 +145,17 @@ func (m *Manager) buildRouterHandler(ctx context.Context, routerName string, rou return handler, nil } + if routerConfig.TLS != nil { + // Don't build the router if the TLSOptions configuration is invalid. + tlsOptionsName := tls.DefaultTLSConfigName + if len(routerConfig.TLS.Options) > 0 && routerConfig.TLS.Options != tls.DefaultTLSConfigName { + tlsOptionsName = provider.GetQualifiedName(ctx, routerConfig.TLS.Options) + } + if _, err := m.tlsManager.Get(tls.DefaultTLSStoreName, tlsOptionsName); err != nil { + return nil, fmt.Errorf("building router handler: %w", err) + } + } + handler, err := m.buildHTTPHandler(ctx, routerConfig, routerName) if err != nil { return nil, err diff --git a/pkg/server/router/router_test.go b/pkg/server/router/router_test.go index 26e8a68a3..0ba85b527 100644 --- a/pkg/server/router/router_test.go +++ b/pkg/server/router/router_test.go @@ -20,6 +20,7 @@ import ( "github.com/traefik/traefik/v2/pkg/server/middleware" "github.com/traefik/traefik/v2/pkg/server/service" "github.com/traefik/traefik/v2/pkg/testhelpers" + "github.com/traefik/traefik/v2/pkg/tls" "github.com/traefik/traefik/v2/pkg/types" ) @@ -317,8 +318,9 @@ func TestRouterManager_Get(t *testing.T) { serviceManager := service.NewManager(rtConf.Services, nil, nil, roundTripperManager) middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager, nil) chainBuilder := middleware.NewChainBuilder(nil, nil, nil) + tlsManager := tls.NewManager() - routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, chainBuilder, metrics.NewVoidRegistry()) + routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, chainBuilder, metrics.NewVoidRegistry(), tlsManager) handlers := routerManager.BuildHandlers(context.Background(), test.entryPoints, false) @@ -423,8 +425,9 @@ func TestAccessLog(t *testing.T) { serviceManager := service.NewManager(rtConf.Services, nil, nil, roundTripperManager) middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager, nil) chainBuilder := middleware.NewChainBuilder(nil, nil, nil) + tlsManager := tls.NewManager() - routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, chainBuilder, metrics.NewVoidRegistry()) + routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, chainBuilder, metrics.NewVoidRegistry(), tlsManager) handlers := routerManager.BuildHandlers(context.Background(), test.entryPoints, false) @@ -462,6 +465,7 @@ func TestRuntimeConfiguration(t *testing.T) { serviceConfig map[string]*dynamic.Service routerConfig map[string]*dynamic.Router middlewareConfig map[string]*dynamic.Middleware + tlsOptions map[string]tls.Options expectedError int }{ { @@ -665,7 +669,6 @@ func TestRuntimeConfiguration(t *testing.T) { }, expectedError: 1, }, - { desc: "Router with broken middleware", serviceConfig: map[string]*dynamic.Service{ @@ -696,8 +699,71 @@ func TestRuntimeConfiguration(t *testing.T) { }, expectedError: 2, }, + { + desc: "Router with broken tlsOption", + serviceConfig: map[string]*dynamic.Service{ + "foo-service": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + Servers: []dynamic.Server{ + { + URL: "http://127.0.0.1", + }, + }, + }, + }, + }, + middlewareConfig: map[string]*dynamic.Middleware{}, + routerConfig: map[string]*dynamic.Router{ + "bar": { + EntryPoints: []string{"web"}, + Service: "foo-service", + Rule: "Host(`foo.bar`)", + TLS: &dynamic.RouterTLSConfig{ + Options: "broken-tlsOption", + }, + }, + }, + tlsOptions: map[string]tls.Options{ + "broken-tlsOption": { + ClientAuth: tls.ClientAuth{ + ClientAuthType: "foobar", + }, + }, + }, + expectedError: 1, + }, + { + desc: "Router with broken default tlsOption", + serviceConfig: map[string]*dynamic.Service{ + "foo-service": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + Servers: []dynamic.Server{ + { + URL: "http://127.0.0.1", + }, + }, + }, + }, + }, + middlewareConfig: map[string]*dynamic.Middleware{}, + routerConfig: map[string]*dynamic.Router{ + "bar": { + EntryPoints: []string{"web"}, + Service: "foo-service", + Rule: "Host(`foo.bar`)", + TLS: &dynamic.RouterTLSConfig{}, + }, + }, + tlsOptions: map[string]tls.Options{ + "default": { + ClientAuth: tls.ClientAuth{ + ClientAuthType: "foobar", + }, + }, + }, + expectedError: 1, + }, } - for _, test := range testCases { test := test t.Run(test.desc, func(t *testing.T) { @@ -711,6 +777,9 @@ func TestRuntimeConfiguration(t *testing.T) { Routers: test.routerConfig, Middlewares: test.middlewareConfig, }, + TLS: &dynamic.TLSConfiguration{ + Options: test.tlsOptions, + }, }) roundTripperManager := service.NewRoundTripperManager() @@ -718,10 +787,13 @@ func TestRuntimeConfiguration(t *testing.T) { serviceManager := service.NewManager(rtConf.Services, nil, nil, roundTripperManager) middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager, nil) chainBuilder := middleware.NewChainBuilder(nil, nil, nil) + tlsManager := tls.NewManager() + tlsManager.UpdateConfigs(context.Background(), nil, test.tlsOptions, nil) - routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, chainBuilder, metrics.NewVoidRegistry()) + routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, chainBuilder, metrics.NewVoidRegistry(), tlsManager) _ = routerManager.BuildHandlers(context.Background(), entryPoints, false) + _ = routerManager.BuildHandlers(context.Background(), entryPoints, true) // even though rtConf was passed by argument to the manager builders above, // it's ok to use it as the result we check, because everything worth checking @@ -793,8 +865,9 @@ func TestProviderOnMiddlewares(t *testing.T) { serviceManager := service.NewManager(rtConf.Services, nil, nil, roundTripperManager) middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager, nil) chainBuilder := middleware.NewChainBuilder(nil, nil, nil) + tlsManager := tls.NewManager() - routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, chainBuilder, metrics.NewVoidRegistry()) + routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, chainBuilder, metrics.NewVoidRegistry(), tlsManager) _ = routerManager.BuildHandlers(context.Background(), entryPoints, false) @@ -861,8 +934,9 @@ func BenchmarkRouterServe(b *testing.B) { serviceManager := service.NewManager(rtConf.Services, nil, nil, staticRoundTripperGetter{res}) middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager, nil) chainBuilder := middleware.NewChainBuilder(nil, nil, nil) + tlsManager := tls.NewManager() - routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, chainBuilder, metrics.NewVoidRegistry()) + routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, chainBuilder, metrics.NewVoidRegistry(), tlsManager) handlers := routerManager.BuildHandlers(context.Background(), entryPoints, false) diff --git a/pkg/server/router/tcp/manager.go b/pkg/server/router/tcp/manager.go index ff92f3112..b149f5f0b 100644 --- a/pkg/server/router/tcp/manager.go +++ b/pkg/server/router/tcp/manager.go @@ -103,18 +103,21 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string router.SetHTTPHandler(handlerHTTP) + // Even though the error is seemingly ignored (aside from logging it), + // we actually rely later on the fact that a tls config is nil (which happens when an error is returned) to take special steps + // when assigning a handler to a route. defaultTLSConf, err := m.tlsManager.Get(traefiktls.DefaultTLSStoreName, traefiktls.DefaultTLSConfigName) if err != nil { log.FromContext(ctx).Errorf("Error during the build of the default TLS configuration: %v", err) } - // Keyed by domain. The source of truth for doing SNI checking, and for what TLS - // options will actually be used for the connection. + // Keyed by domain. The source of truth for doing SNI checking (domain fronting). // As soon as there's (at least) two different tlsOptions found for the same domain, // we set the value to the default TLS conf. tlsOptionsForHost := map[string]string{} // Keyed by domain, then by options reference. + // The actual source of truth for what TLS options will actually be used for the connection. // As opposed to tlsOptionsForHost, it keeps track of all the (different) TLS // options that occur for a given host name, so that later on we can set relevant // errors and logging for all the routers concerned (i.e. wrongly configured). @@ -142,21 +145,20 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string } if len(domains) == 0 { - // Extra Host(*) rule, for HTTPS routers with no Host rule, and for requests for - // which the SNI does not match _any_ of the other existing routers Host. This is - // only about choosing the TLS configuration. The actual routing will be done - // further on by the HTTPS handler. See examples below. + // Extra Host(*) rule, for HTTPS routers with no Host rule, + // and for requests for which the SNI does not match _any_ of the other existing routers Host. + // This is only about choosing the TLS configuration. + // The actual routing will be done further on by the HTTPS handler. + // See examples below. router.AddHTTPTLSConfig("*", defaultTLSConf) - // The server name (from a Host(SNI) rule) is the only parameter (available in - // HTTP routing rules) on which we can map a TLS config, because it is the only one - // accessible before decryption (we obtain it during the ClientHello). Therefore, - // when a router has no Host rule, it does not make any sense to specify some TLS - // options. Consequently, when it comes to deciding what TLS config will be used, - // for a request that will match an HTTPS router with no Host rule, the result will - // depend on the _others_ existing routers (their Host rule, to be precise), and - // the TLS options associated with them, even though they don't match the incoming - // request. Consider the following examples: + // The server name (from a Host(SNI) rule) is the only parameter (available in HTTP routing rules) on which we can map a TLS config, + // because it is the only one accessible before decryption (we obtain it during the ClientHello). + // Therefore, when a router has no Host rule, it does not make any sense to specify some TLS options. + // Consequently, when it comes to deciding what TLS config will be used, + // for a request that will match an HTTPS router with no Host rule, + // the result will depend on the _others_ existing routers (their Host rule, to be precise), and the TLS options associated with them, + // even though they don't match the incoming request. Consider the following examples: // # conf1 // httpRouter1: @@ -170,17 +172,19 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string // httpRouter2: // rule: Host("foo.com") && PathPrefix("/bar") // tlsoptions: myTLSOptions - // # When a request for "/foo" comes, even though it won't be routed by - // httpRouter2, if its SNI is set to foo.com, myTLSOptions will be used for the TLS - // connection. Otherwise, it will fallback to the default TLS config. + // # When a request for "/foo" comes, even though it won't be routed by httpRouter2, + // # if its SNI is set to foo.com, myTLSOptions will be used for the TLS connection. + // # Otherwise, it will fallback to the default TLS config. logger.Warnf("No domain found in rule %v, the TLS options applied for this router will depend on the SNI of each request", routerHTTPConfig.Rule) } - tlsConf, err := m.tlsManager.Get(traefiktls.DefaultTLSStoreName, tlsOptionsName) - if err != nil { - routerHTTPConfig.AddError(err, true) - logger.Error(err) - continue + // Even though the error is seemingly ignored (aside from logging it), + // we actually rely later on the fact that a tls config is nil (which happens when an error is returned) to take special steps + // when assigning a handler to a route. + tlsConf, tlsConfErr := m.tlsManager.Get(traefiktls.DefaultTLSStoreName, tlsOptionsName) + if tlsConfErr != nil { + // Note: we do not call AddError here because we already did so when buildRouterHandler errored for the same reason. + logger.Error(tlsConfErr) } for _, domain := range domains { @@ -204,6 +208,7 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string sniCheck := snicheck.New(tlsOptionsForHost, handlerHTTPS) + // Keep in mind that defaultTLSConf might be nil here. router.SetHTTPSHandler(sniCheck, defaultTLSConf) logger := log.FromContext(ctx) @@ -217,22 +222,42 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string break } - logger.Debugf("Adding route for %s with TLS options %s", hostSNI, optionsName) - - router.AddHTTPTLSConfig(hostSNI, config) - } else { - routers := make([]string, 0, len(tlsConfigs)) - for _, v := range tlsConfigs { - configsHTTP[v.routerName].AddError(fmt.Errorf("found different TLS options for routers on the same host %v, so using the default TLS options instead", hostSNI), false) - routers = append(routers, v.routerName) + if config == nil { + // we use nil config as a signal to insert a handler + // that enforces that TLS connection attempts to the corresponding (broken) router should fail. + logger.Debugf("Adding special closing route for %s because broken TLS options %s", hostSNI, optionsName) + router.AddHTTPTLSConfig(hostSNI, nil) + continue } - logger.Warnf("Found different TLS options for routers on the same host %v, so using the default TLS options instead for these routers: %#v", hostSNI, routers) - - router.AddHTTPTLSConfig(hostSNI, defaultTLSConf) + logger.Debugf("Adding route for %s with TLS options %s", hostSNI, optionsName) + router.AddHTTPTLSConfig(hostSNI, config) + continue } + + // multiple tlsConfigs + + routers := make([]string, 0, len(tlsConfigs)) + for _, v := range tlsConfigs { + configsHTTP[v.routerName].AddError(fmt.Errorf("found different TLS options for routers on the same host %v, so using the default TLS options instead", hostSNI), false) + routers = append(routers, v.routerName) + } + + logger.Warnf("Found different TLS options for routers on the same host %v, so using the default TLS options instead for these routers: %#v", hostSNI, routers) + if defaultTLSConf == nil { + logger.Debugf("Adding special closing route for %s because broken default TLS options", hostSNI) + } + + router.AddHTTPTLSConfig(hostSNI, defaultTLSConf) } + m.addTCPHandlers(ctx, configs, router) + + return router, nil +} + +// addTCPHandlers creates the TCP handlers defined in configs, and adds them to router. +func (m *Manager) addTCPHandlers(ctx context.Context, configs map[string]*runtime.TCPRouterInfo, router *Router) { for routerName, routerConfig := range configs { ctxRouter := log.With(provider.AddInContext(ctx, routerName), log.Str(log.RouterName, routerName)) logger := log.FromContext(ctxRouter) @@ -251,13 +276,6 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string continue } - handler, err := m.buildTCPHandler(ctxRouter, routerConfig) - if err != nil { - routerConfig.AddError(err, true) - logger.Error(err) - continue - } - domains, err := tcpmuxer.ParseHostSNI(routerConfig.Rule) if err != nil { routerErr := fmt.Errorf("invalid rule: %q , %w", routerConfig.Rule, err) @@ -274,6 +292,16 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string logger.Error(routerErr) } + var handler tcp.Handler + if routerConfig.TLS == nil || routerConfig.TLS.Passthrough { + handler, err = m.buildTCPHandler(ctxRouter, routerConfig) + if err != nil { + routerConfig.AddError(err, true) + logger.Error(err) + continue + } + } + if routerConfig.TLS == nil { logger.Debugf("Adding route for %q", routerConfig.Rule) if err := router.AddRoute(routerConfig.Rule, routerConfig.Priority, handler); err != nil { @@ -285,7 +313,7 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string if routerConfig.TLS.Passthrough { logger.Debugf("Adding Passthrough route for %q", routerConfig.Rule) - if err := router.AddRouteTLS(routerConfig.Rule, routerConfig.Priority, handler, nil); err != nil { + if err := router.muxerTCPTLS.AddRoute(routerConfig.Rule, routerConfig.Priority, handler); err != nil { routerConfig.AddError(err, true) logger.Error(err) } @@ -315,7 +343,15 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string tlsConf, err := m.tlsManager.Get(traefiktls.DefaultTLSStoreName, tlsOptionsName) if err != nil { routerConfig.AddError(err, true) + logger.Error(err) + logger.Debugf("Adding special TLS closing route for %q because broken TLS options %s", routerConfig.Rule, tlsOptionsName) + + err = router.muxerTCPTLS.AddRoute(routerConfig.Rule, routerConfig.Priority, &brokenTLSRouter{}) + if err != nil { + routerConfig.AddError(err, true) + logger.Error(err) + } continue } @@ -327,20 +363,30 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string // rule: HostSNI(foo.com) && ClientIP(IP2) // tlsOption: tlsTwo // i.e. same HostSNI but different tlsOptions - // This is only applicable if the muxer can decide about the routing _before_ - // telling the client about the tlsConf (i.e. before the TLS HandShake). This seems - // to be the case so far with the existing matchers (HostSNI, and ClientIP), so - // it's all good. Otherwise, we would have to do as for HTTPS, i.e. disallow - // different TLS configs for the same HostSNIs. + // This is only applicable if the muxer can decide about the routing _before_ telling the client about the tlsConf (i.e. before the TLS HandShake). + // This seems to be the case so far with the existing matchers (HostSNI, and ClientIP), so it's all good. + // Otherwise, we would have to do as for HTTPS, i.e. disallow different TLS configs for the same HostSNIs. + + handler, err = m.buildTCPHandler(ctxRouter, routerConfig) + if err != nil { + routerConfig.AddError(err, true) + logger.Error(err) + continue + } + + handler = &tcp.TLSHandler{ + Next: handler, + Config: tlsConf, + } logger.Debugf("Adding TLS route for %q", routerConfig.Rule) - if err := router.AddRouteTLS(routerConfig.Rule, routerConfig.Priority, handler, tlsConf); err != nil { + + err = router.muxerTCPTLS.AddRoute(routerConfig.Rule, routerConfig.Priority, handler) + if err != nil { routerConfig.AddError(err, true) logger.Error(err) } } - - return router, nil } func (m *Manager) buildTCPHandler(ctx context.Context, router *runtime.TCPRouterInfo) (tcp.Handler, error) { diff --git a/pkg/server/router/tcp/router.go b/pkg/server/router/tcp/router.go index 0a41e80e2..dca4e6fae 100644 --- a/pkg/server/router/tcp/router.go +++ b/pkg/server/router/tcp/router.go @@ -27,19 +27,20 @@ type Router struct { muxerHTTPS tcpmuxer.Muxer // Forwarder handlers. - // Handles all HTTP requests. + // httpForwarder handles all HTTP requests. httpForwarder tcp.Handler - // Handles (indirectly through muxerHTTPS, or directly) all HTTPS requests. + // httpsForwarder handles (indirectly through muxerHTTPS, or directly) all HTTPS requests. httpsForwarder tcp.Handler - // Neither is used directly, but they are held here, and recreated on config - // reload, so that they can be passed to the Switcher at the end of the config - // reload phase. + // Neither is used directly, but they are held here, and recreated on config reload, + // so that they can be passed to the Switcher at the end of the config reload phase. httpHandler http.Handler httpsHandler http.Handler // TLS configs. - httpsTLSConfig *tls.Config // default TLS config + httpsTLSConfig *tls.Config // default TLS config + // hostHTTPTLSConfig contains TLS configs keyed by SNI. + // A nil config is the hint to set up a brokenTLSRouter. hostHTTPTLSConfig map[string]*tls.Config // TLS configs keyed by SNI } @@ -80,11 +81,11 @@ func (r *Router) GetTLSGetClientInfo() func(info *tls.ClientHelloInfo) (*tls.Con // ServeTCP forwards the connection to the right TCP/HTTP handler. func (r *Router) ServeTCP(conn tcp.WriteCloser) { - // Handling Non-TLS TCP connection early if there is neither HTTP(S) nor TLS - // routers on the entryPoint, and if there is at least one non-TLS TCP router. - // In the case of a non-TLS TCP client (that does not "send" first), we would - // block forever on clientHelloInfo, which is why we want to detect and - // handle that case first and foremost. + // Handling Non-TLS TCP connection early if there is neither HTTP(S) nor TLS routers on the entryPoint, + // and if there is at least one non-TLS TCP router. + // In the case of a non-TLS TCP client (that does not "send" first), + // we would block forever on clientHelloInfo, + // which is why we want to detect and handle that case first and foremost. if r.muxerTCP.HasRoutes() && !r.muxerTCPTLS.HasRoutes() && !r.muxerHTTPS.HasRoutes() { connData, err := tcpmuxer.NewConnData("", conn, nil) if err != nil { @@ -152,9 +153,9 @@ func (r *Router) ServeTCP(conn tcp.WriteCloser) { // (wrapped inside the returned handler) requested for the given HostSNI. handlerHTTPS, catchAllHTTPS := r.muxerHTTPS.Match(connData) if handlerHTTPS != nil && !catchAllHTTPS { - // In order not to depart from the behavior in 2.6, we only allow an HTTPS router - // to take precedence over a TCP-TLS router if it is _not_ an HostSNI(*) router (so - // basically any router that has a specific HostSNI based rule). + // In order not to depart from the behavior in 2.6, + // we only allow an HTTPS router to take precedence over a TCP-TLS router if it is _not_ an HostSNI(*) router + // (so basically any router that has a specific HostSNI based rule). handlerHTTPS.ServeTCP(r.GetConn(conn, hello.peeked)) return } @@ -180,7 +181,7 @@ func (r *Router) ServeTCP(conn tcp.WriteCloser) { return } - // needed to handle 404s for HTTPS, as well as all non-Host (e.g. PathPrefix) matches. + // To handle 404s for HTTPS. if r.httpsForwarder != nil { r.httpsForwarder.ServeTCP(r.GetConn(conn, hello.peeked)) return @@ -194,19 +195,6 @@ func (r *Router) AddRoute(rule string, priority int, target tcp.Handler) error { return r.muxerTCP.AddRoute(rule, priority, target) } -// AddRouteTLS defines a handler for a given rule and sets the matching tlsConfig. -func (r *Router) AddRouteTLS(rule string, priority int, target tcp.Handler, config *tls.Config) error { - // TLS PassThrough - if config == nil { - return r.muxerTCPTLS.AddRoute(rule, priority, target) - } - - return r.muxerTCPTLS.AddRoute(rule, priority, &tcp.TLSHandler{ - Next: target, - Config: config, - }) -} - // AddHTTPTLSConfig defines a handler for a given sniHost and sets the matching tlsConfig. func (r *Router) AddHTTPTLSConfig(sniHost string, config *tls.Config) { if r.hostHTTPTLSConfig == nil { @@ -242,20 +230,44 @@ func (r *Router) SetHTTPForwarder(handler tcp.Handler) { r.httpForwarder = handler } -// SetHTTPSForwarder sets the tcp handler that will forward the TLS connections to an http handler. +// brokenTLSRouter is associated to a Host(SNI) rule for which we know the TLS conf is broken. +// It is used to make sure any attempt to connect to that hostname is closed, +// since we cannot proceed with the intended TLS conf. +type brokenTLSRouter struct{} + +// ServeTCP instantly closes the connection. +func (t *brokenTLSRouter) ServeTCP(conn tcp.WriteCloser) { + _ = conn.Close() +} + +// SetHTTPSForwarder sets the tcp handler that will forward the TLS connections to an HTTP handler. +// It also sets up each TLS handler (with its TLS config) for each Host(SNI) rule we previously kept track of. +// It sets up a special handler that closes the connection if a TLS config is nil. func (r *Router) SetHTTPSForwarder(handler tcp.Handler) { for sniHost, tlsConf := range r.hostHTTPTLSConfig { + var tcpHandler tcp.Handler + if tlsConf == nil { + tcpHandler = &brokenTLSRouter{} + } else { + tcpHandler = &tcp.TLSHandler{ + Next: handler, + Config: tlsConf, + } + } + // muxerHTTPS only contains single HostSNI rules (and no other kind of rules), // so there's no need for specifying a priority for them. - err := r.muxerHTTPS.AddRoute("HostSNI(`"+sniHost+"`)", 0, &tcp.TLSHandler{ - Next: handler, - Config: tlsConf, - }) + err := r.muxerHTTPS.AddRoute("HostSNI(`"+sniHost+"`)", 0, tcpHandler) if err != nil { log.WithoutContext().Errorf("Error while adding route for host: %v", err) } } + if r.httpsTLSConfig == nil { + r.httpsForwarder = &brokenTLSRouter{} + return + } + r.httpsForwarder = &tcp.TLSHandler{ Next: handler, Config: r.httpsTLSConfig, @@ -275,15 +287,14 @@ func (r *Router) SetHTTPSHandler(handler http.Handler, config *tls.Config) { // 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, but have not yet been consumed - // by Read calls. It set to nil by Read when fully consumed. + // Peeked are the bytes that have been read from Conn for the purposes of route matching, + // but have not yet been consumed by Read calls. + // It set to nil by Read when fully consumed. Peeked []byte // Conn is the underlying connection. - // It can be type asserted against *net.TCPConn or other types - // as needed. It should not be read from directly unless - // Peeked is nil. + // It can be type asserted against *net.TCPConn or other types as needed. + // It should not be read from directly unless Peeked is nil. tcp.WriteCloser } @@ -320,15 +331,14 @@ func clientHelloInfo(br *bufio.Reader) (*clientHello, error) { return nil, err } - // No valid TLS record has a type of 0x80, however SSLv2 handshakes - // start with a uint16 length where the MSB is set and the first record - // is always < 256 bytes long. Therefore typ == 0x80 strongly suggests - // an SSLv2 client. + // No valid TLS record has a type of 0x80, however SSLv2 handshakes start with an uint16 length + // where the MSB is set and the first record is always < 256 bytes long. + // Therefore, typ == 0x80 strongly suggests an SSLv2 client. const recordTypeSSLv2 = 0x80 const recordTypeHandshake = 0x16 if hdr[0] != recordTypeHandshake { if hdr[0] == recordTypeSSLv2 { - // we consider SSLv2 as TLS and it will be refused by real TLS handshake. + // we consider SSLv2 as TLS, and it will be refused by real TLS handshake. return &clientHello{ isTLS: true, peeked: getPeeked(br), diff --git a/pkg/server/routerfactory.go b/pkg/server/routerfactory.go index fff7e3eea..6b7b80ff4 100644 --- a/pkg/server/routerfactory.go +++ b/pkg/server/routerfactory.go @@ -72,7 +72,7 @@ func (f *RouterFactory) CreateRouters(rtConf *runtime.Configuration) (map[string middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager, f.pluginBuilder) - routerManager := router.NewManager(rtConf, serviceManager, middlewaresBuilder, f.chainBuilder, f.metricsRegistry) + routerManager := router.NewManager(rtConf, serviceManager, middlewaresBuilder, f.chainBuilder, f.metricsRegistry, f.tlsManager) handlersNonTLS := routerManager.BuildHandlers(ctx, f.entryPointsTCP, false) handlersTLS := routerManager.BuildHandlers(ctx, f.entryPointsTCP, true) diff --git a/pkg/tls/tlsmanager.go b/pkg/tls/tlsmanager.go index 0c7daa992..91fd6402f 100644 --- a/pkg/tls/tlsmanager.go +++ b/pkg/tls/tlsmanager.go @@ -157,19 +157,16 @@ func (m *Manager) Get(storeName, configName string) (*tls.Config, error) { m.lock.RLock() defer m.lock.RUnlock() - var tlsConfig *tls.Config - var err error - sniStrict := false config, ok := m.configs[configName] - if ok { - sniStrict = config.SniStrict - tlsConfig, err = buildTLSConfig(config) - } else { - err = fmt.Errorf("unknown TLS options: %s", configName) + if !ok { + return nil, fmt.Errorf("unknown TLS options: %s", configName) } + + sniStrict = config.SniStrict + tlsConfig, err := buildTLSConfig(config) if err != nil { - tlsConfig = &tls.Config{} + return nil, fmt.Errorf("building TLS config: %w", err) } store := m.getStore(storeName) @@ -177,7 +174,7 @@ func (m *Manager) Get(storeName, configName string) (*tls.Config, error) { err = fmt.Errorf("TLS store %s not found", storeName) } acmeTLSStore := m.getStore(tlsalpn01.ACMETLS1Protocol) - if acmeTLSStore == nil { + if acmeTLSStore == nil && err == nil { err = fmt.Errorf("ACME TLS store %s not found", tlsalpn01.ACMETLS1Protocol) } @@ -188,15 +185,12 @@ func (m *Manager) Get(storeName, configName string) (*tls.Config, error) { certificate := acmeTLSStore.GetBestCertificate(clientHello) if certificate == nil { log.WithoutContext().Debugf("TLS: no certificate for TLSALPN challenge: %s", domainToCheck) - // We want the user to eventually get the (alertUnrecognizedName) "unrecognized - // name" error. - // Unfortunately, if we returned an error here, since we can't use - // the unexported error (errNoCertificates) that our caller (config.getCertificate - // in crypto/tls) uses as a sentinel, it would report an (alertInternalError) - // "internal error" instead of an alertUnrecognizedName. - // Which is why we return no error, and we let the caller detect that there's - // actually no certificate, and fall back into the flow that will report - // the desired error. + // We want the user to eventually get the (alertUnrecognizedName) "unrecognized name" error. + // Unfortunately, if we returned an error here, + // since we can't use the unexported error (errNoCertificates) that our caller (config.getCertificate in crypto/tls) uses as a sentinel, + // it would report an (alertInternalError) "internal error" instead of an alertUnrecognizedName. + // Which is why we return no error, and we let the caller detect that there's actually no certificate, + // and fall back into the flow that will report the desired error. // https://cs.opensource.google/go/go/+/dev.boringcrypto.go1.17:src/crypto/tls/common.go;l=1058 return nil, nil } diff --git a/pkg/tls/tlsmanager_test.go b/pkg/tls/tlsmanager_test.go index 7f3853299..08acf04a2 100644 --- a/pkg/tls/tlsmanager_test.go +++ b/pkg/tls/tlsmanager_test.go @@ -119,8 +119,9 @@ func TestManager_Get(t *testing.T) { }} tlsConfigs := map[string]Options{ - "foo": {MinVersion: "VersionTLS12"}, - "bar": {MinVersion: "VersionTLS11"}, + "foo": {MinVersion: "VersionTLS12"}, + "bar": {MinVersion: "VersionTLS11"}, + "invalid": {CurvePreferences: []string{"42"}}, } testCases := []struct { @@ -140,15 +141,20 @@ func TestManager_Get(t *testing.T) { expectedMinVersion: uint16(tls.VersionTLS11), }, { - desc: "Get an tls config from an invalid name", + desc: "Get a tls config from an invalid name", tlsOptionsName: "unknown", expectedError: true, }, { - desc: "Get an tls config from unexisting 'default' name", + desc: "Get a tls config from unexisting 'default' name", tlsOptionsName: "default", expectedError: true, }, + { + desc: "Get an invalid tls config", + tlsOptionsName: "invalid", + expectedError: true, + }, } tlsManager := NewManager() @@ -161,42 +167,13 @@ func TestManager_Get(t *testing.T) { config, err := tlsManager.Get("default", test.tlsOptionsName) if test.expectedError { - assert.Error(t, err) + require.Nil(t, config) + require.Error(t, err) return } - assert.NoError(t, err) - assert.Equal(t, config.MinVersion, test.expectedMinVersion) - }) - } -} - -func TestManager_Get_GetCertificate(t *testing.T) { - testCases := []struct { - desc string - expectedGetConfigErr require.ErrorAssertionFunc - expectedCertificate assert.ValueAssertionFunc - }{ - { - desc: "Get a default certificate from non-existing store", - expectedGetConfigErr: require.Error, - expectedCertificate: assert.Nil, - }, - } - - tlsManager := NewManager() - - for _, test := range testCases { - test := test - t.Run(test.desc, func(t *testing.T) { - t.Parallel() - - config, err := tlsManager.Get("default", "foo") - test.expectedGetConfigErr(t, err) - - certificate, err := config.GetCertificate(&tls.ClientHelloInfo{}) require.NoError(t, err) - test.expectedCertificate(t, certificate) + assert.Equal(t, config.MinVersion, test.expectedMinVersion) }) } } From abd569701f1d10872e970ac3bd753b3f2957491f Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Wed, 7 Dec 2022 10:02:04 +0100 Subject: [PATCH 14/19] fix: update golang.org/x/net --- go.mod | 8 ++++---- go.sum | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index f8e560207..a0d3c1fbf 100644 --- a/go.mod +++ b/go.mod @@ -71,8 +71,8 @@ require ( go.elastic.co/apm v1.13.1 go.elastic.co/apm/module/apmot v1.13.1 golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 - golang.org/x/net v0.1.0 - golang.org/x/text v0.4.0 + golang.org/x/net v0.3.1-0.20221206200815-1e63c2f08a10 + golang.org/x/text v0.5.0 golang.org/x/time v0.0.0-20220224211638-0e9765cccd65 golang.org/x/tools v0.1.12 google.golang.org/grpc v1.41.0 @@ -325,8 +325,8 @@ require ( golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 // indirect golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1 // indirect golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 // indirect - golang.org/x/sys v0.1.0 // indirect - golang.org/x/term v0.1.0 // indirect + golang.org/x/sys v0.3.0 // indirect + golang.org/x/term v0.3.0 // indirect golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect google.golang.org/api v0.44.0 // indirect google.golang.org/appengine v1.6.7 // indirect diff --git a/go.sum b/go.sum index 00253253e..0020ab332 100644 --- a/go.sum +++ b/go.sum @@ -2093,8 +2093,8 @@ golang.org/x/net v0.0.0-20211216030914-fe4d6282115f/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.1.0 h1:hZ/3BUoy5aId7sCpA/Tc5lt8DkFgdVS2onTpJsZ/fl0= -golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= +golang.org/x/net v0.3.1-0.20221206200815-1e63c2f08a10 h1:Frnccbp+ok2GkUS2tC84yAq/U9Vg+0sIO7aRL3T4Xnc= +golang.org/x/net v0.3.1-0.20221206200815-1e63c2f08a10/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= golang.org/x/oauth2 v0.0.0-20180724155351-3d292e4d0cdc/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -2254,14 +2254,14 @@ golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U= -golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ= +golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 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= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.1.0 h1:g6Z6vPFA9dYBAF7DWcH6sCcOntplXsDKcliusYijMlw= -golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.3.0 h1:qoo4akIqOcDME5bhc/NgxUdovd6BSS2uMsVjB56q1xI= +golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -2272,8 +2272,8 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg= -golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.5.0 h1:OLmvp0KP+FVG99Ct/qFiL/Fhk4zp4QQnZ7b2U+5piUM= +golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 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= From a8df674dcf77909e6e0f9c0452abf7a7c6857571 Mon Sep 17 00:00:00 2001 From: Ludovic Fernandez Date: Wed, 7 Dec 2022 10:56:05 +0100 Subject: [PATCH 15/19] fix: flaky tests --- pkg/server/configurationwatcher_test.go | 78 +++++++++++++----------- pkg/server/server_entrypoint_tcp_test.go | 22 +++---- pkg/server/server_entrypoint_udp_test.go | 32 +++++----- 3 files changed, 71 insertions(+), 61 deletions(-) diff --git a/pkg/server/configurationwatcher_test.go b/pkg/server/configurationwatcher_test.go index afd886e84..da8d39c4d 100644 --- a/pkg/server/configurationwatcher_test.go +++ b/pkg/server/configurationwatcher_test.go @@ -58,7 +58,7 @@ func (p *mockProvider) Init() error { func TestNewConfigurationWatcher(t *testing.T) { routinesPool := safe.NewPool(context.Background()) - defer routinesPool.Stop() + t.Cleanup(routinesPool.Stop) pvd := &mockProvider{ messages: []dynamic.Message{{ @@ -115,7 +115,6 @@ func TestNewConfigurationWatcher(t *testing.T) { func TestWaitForRequiredProvider(t *testing.T) { routinesPool := safe.NewPool(context.Background()) - defer routinesPool.Stop() pvdAggregator := &mockProvider{ wait: 5 * time.Millisecond, @@ -151,7 +150,9 @@ func TestWaitForRequiredProvider(t *testing.T) { }) watcher.Start() - defer watcher.Stop() + + t.Cleanup(watcher.Stop) + t.Cleanup(routinesPool.Stop) // give some time so that the configuration can be processed time.Sleep(20 * time.Millisecond) @@ -162,7 +163,6 @@ func TestWaitForRequiredProvider(t *testing.T) { func TestIgnoreTransientConfiguration(t *testing.T) { routinesPool := safe.NewPool(context.Background()) - defer routinesPool.Stop() config := &dynamic.Configuration{ HTTP: th.BuildConfiguration( @@ -190,7 +190,9 @@ func TestIgnoreTransientConfiguration(t *testing.T) { }) watcher.Start() - defer watcher.Stop() + + t.Cleanup(watcher.Stop) + t.Cleanup(routinesPool.Stop) watcher.allProvidersConfigs <- dynamic.Message{ ProviderName: "mock", @@ -243,7 +245,6 @@ func TestIgnoreTransientConfiguration(t *testing.T) { func TestListenProvidersThrottleProviderConfigReload(t *testing.T) { routinesPool := safe.NewPool(context.Background()) - defer routinesPool.Stop() pvd := &mockProvider{ wait: 10 * time.Millisecond, @@ -274,7 +275,9 @@ func TestListenProvidersThrottleProviderConfigReload(t *testing.T) { }) watcher.Start() - defer watcher.Stop() + + t.Cleanup(watcher.Stop) + t.Cleanup(routinesPool.Stop) // Give some time so that the configuration can be processed. time.Sleep(100 * time.Millisecond) @@ -287,7 +290,6 @@ func TestListenProvidersThrottleProviderConfigReload(t *testing.T) { func TestListenProvidersSkipsEmptyConfigs(t *testing.T) { routinesPool := safe.NewPool(context.Background()) - defer routinesPool.Stop() pvd := &mockProvider{ messages: []dynamic.Message{{ProviderName: "mock"}}, @@ -299,7 +301,9 @@ func TestListenProvidersSkipsEmptyConfigs(t *testing.T) { }) watcher.Start() - defer watcher.Stop() + + t.Cleanup(watcher.Stop) + t.Cleanup(routinesPool.Stop) // give some time so that the configuration can be processed time.Sleep(100 * time.Millisecond) @@ -307,7 +311,6 @@ func TestListenProvidersSkipsEmptyConfigs(t *testing.T) { func TestListenProvidersSkipsSameConfigurationForProvider(t *testing.T) { routinesPool := safe.NewPool(context.Background()) - defer routinesPool.Stop() message := dynamic.Message{ ProviderName: "mock", @@ -331,7 +334,9 @@ func TestListenProvidersSkipsSameConfigurationForProvider(t *testing.T) { }) watcher.Start() - defer watcher.Stop() + + t.Cleanup(watcher.Stop) + t.Cleanup(routinesPool.Stop) // give some time so that the configuration can be processed time.Sleep(100 * time.Millisecond) @@ -340,7 +345,6 @@ func TestListenProvidersSkipsSameConfigurationForProvider(t *testing.T) { func TestListenProvidersDoesNotSkipFlappingConfiguration(t *testing.T) { routinesPool := safe.NewPool(context.Background()) - defer routinesPool.Stop() configuration := &dynamic.Configuration{ HTTP: th.BuildConfiguration( @@ -374,7 +378,9 @@ func TestListenProvidersDoesNotSkipFlappingConfiguration(t *testing.T) { }) watcher.Start() - defer watcher.Stop() + + t.Cleanup(watcher.Stop) + t.Cleanup(routinesPool.Stop) // give some time so that the configuration can be processed time.Sleep(100 * time.Millisecond) @@ -407,7 +413,6 @@ func TestListenProvidersDoesNotSkipFlappingConfiguration(t *testing.T) { func TestListenProvidersIgnoreSameConfig(t *testing.T) { routinesPool := safe.NewPool(context.Background()) - defer routinesPool.Stop() configuration := &dynamic.Configuration{ HTTP: th.BuildConfiguration( @@ -453,8 +458,7 @@ func TestListenProvidersIgnoreSameConfig(t *testing.T) { configurationReloads++ lastConfig = conf - // Allows next configurations to be sent by the mock provider - // as soon as the first configuration message is applied. + // Allows next configurations to be sent by the mock provider as soon as the first configuration message is applied. once.Do(func() { pvd.first <- struct{}{} // Wait for all configuration messages to pile in @@ -463,7 +467,9 @@ func TestListenProvidersIgnoreSameConfig(t *testing.T) { }) watcher.Start() - defer watcher.Stop() + + t.Cleanup(watcher.Stop) + t.Cleanup(routinesPool.Stop) // Wait long enough time.Sleep(50 * time.Millisecond) @@ -498,7 +504,6 @@ func TestListenProvidersIgnoreSameConfig(t *testing.T) { func TestApplyConfigUnderStress(t *testing.T) { routinesPool := safe.NewPool(context.Background()) - defer routinesPool.Stop() watcher := NewConfigurationWatcher(routinesPool, &mockProvider{}, []string{"defaultEP"}, "") @@ -525,15 +530,16 @@ func TestApplyConfigUnderStress(t *testing.T) { }) watcher.Start() - defer watcher.Stop() + + t.Cleanup(watcher.Stop) + t.Cleanup(routinesPool.Stop) time.Sleep(100 * time.Millisecond) // Ensure that at least two configurations have been applied - // if we simulate being spammed configuration changes by the - // provider(s). - // In theory, checking at least one would be sufficient, but - // checking for two also ensures that we're looping properly, + // if we simulate being spammed configuration changes by the provider(s). + // In theory, checking at least one would be sufficient, + // but checking for two also ensures that we're looping properly, // and that the whole algo holds, etc. t.Log(configurationReloads) assert.GreaterOrEqual(t, configurationReloads, 2) @@ -541,7 +547,6 @@ func TestApplyConfigUnderStress(t *testing.T) { func TestListenProvidersIgnoreIntermediateConfigs(t *testing.T) { routinesPool := safe.NewPool(context.Background()) - defer routinesPool.Stop() configuration := &dynamic.Configuration{ HTTP: th.BuildConfiguration( @@ -596,7 +601,9 @@ func TestListenProvidersIgnoreIntermediateConfigs(t *testing.T) { }) watcher.Start() - defer watcher.Stop() + + t.Cleanup(watcher.Stop) + t.Cleanup(routinesPool.Stop) // Wait long enough time.Sleep(500 * time.Millisecond) @@ -631,7 +638,6 @@ func TestListenProvidersIgnoreIntermediateConfigs(t *testing.T) { func TestListenProvidersPublishesConfigForEachProvider(t *testing.T) { routinesPool := safe.NewPool(context.Background()) - defer routinesPool.Stop() configuration := &dynamic.Configuration{ HTTP: th.BuildConfiguration( @@ -656,7 +662,9 @@ func TestListenProvidersPublishesConfigForEachProvider(t *testing.T) { }) watcher.Start() - defer watcher.Stop() + + t.Cleanup(watcher.Stop) + t.Cleanup(routinesPool.Stop) // give some time so that the configuration can be processed time.Sleep(100 * time.Millisecond) @@ -695,7 +703,6 @@ func TestListenProvidersPublishesConfigForEachProvider(t *testing.T) { func TestPublishConfigUpdatedByProvider(t *testing.T) { routinesPool := safe.NewPool(context.Background()) - defer routinesPool.Stop() pvdConfiguration := dynamic.Configuration{ TCP: &dynamic.TCPConfiguration{ @@ -725,12 +732,14 @@ func TestPublishConfigUpdatedByProvider(t *testing.T) { watcher.AddListener(func(configuration dynamic.Configuration) { publishedConfigCount++ - // Update the provider configuration published in next dynamic Message which should trigger a new publish. + // Update the provider configuration published in next dynamic Message which should trigger a new publishing. pvdConfiguration.TCP.Routers["bar"] = &dynamic.TCPRouter{} }) watcher.Start() - defer watcher.Stop() + + t.Cleanup(watcher.Stop) + t.Cleanup(routinesPool.Stop) // give some time so that the configuration can be processed. time.Sleep(100 * time.Millisecond) @@ -740,7 +749,6 @@ func TestPublishConfigUpdatedByProvider(t *testing.T) { func TestPublishConfigUpdatedByConfigWatcherListener(t *testing.T) { routinesPool := safe.NewPool(context.Background()) - defer routinesPool.Stop() pvd := &mockProvider{ wait: 10 * time.Millisecond, @@ -774,13 +782,15 @@ func TestPublishConfigUpdatedByConfigWatcherListener(t *testing.T) { watcher.AddListener(func(configuration dynamic.Configuration) { publishedConfigCount++ - // Modify the provided configuration. This should not modify the configuration stored in the configuration - // watcher and cause a new publish. + // Modify the provided configuration. + // This should not modify the configuration stored in the configuration watcher and therefore there will be no new publishing. configuration.TCP.Routers["foo@mock"].Rule = "bar" }) watcher.Start() - defer watcher.Stop() + + t.Cleanup(watcher.Stop) + t.Cleanup(routinesPool.Stop) // give some time so that the configuration can be processed. time.Sleep(100 * time.Millisecond) diff --git a/pkg/server/server_entrypoint_tcp_test.go b/pkg/server/server_entrypoint_tcp_test.go index 73cc9c28b..342d7d9fa 100644 --- a/pkg/server/server_entrypoint_tcp_test.go +++ b/pkg/server/server_entrypoint_tcp_test.go @@ -48,18 +48,13 @@ func TestShutdownTCP(t *testing.T) { require.NoError(t, err) err = router.AddRoute("HostSNI(`*`)", 0, tcp.HandlerFunc(func(conn tcp.WriteCloser) { - for { - _, err := http.ReadRequest(bufio.NewReader(conn)) - - if errors.Is(err, io.EOF) || (err != nil && errors.Is(err, net.ErrClosed)) { - return - } - require.NoError(t, err) - - resp := http.Response{StatusCode: http.StatusOK} - err = resp.Write(conn) - require.NoError(t, err) + _, err := http.ReadRequest(bufio.NewReader(conn)) + if err != nil { + return } + + resp := http.Response{StatusCode: http.StatusOK} + _ = resp.Write(conn) })) require.NoError(t, err) @@ -89,6 +84,7 @@ func testShutdown(t *testing.T, router *tcprouter.Router) { conn, err := startEntrypoint(entryPoint, router) require.NoError(t, err) + t.Cleanup(func() { _ = conn.Close() }) epAddr := entryPoint.listener.Addr().String() @@ -97,14 +93,14 @@ func testShutdown(t *testing.T, router *tcprouter.Router) { time.Sleep(100 * time.Millisecond) - // We need to do a write on the conn before the shutdown to make it "exist". + // We need to do a write on conn before the shutdown to make it "exist". // Because the connection indeed exists as far as TCP is concerned, // but since we only pass it along to the HTTP server after at least one byte is peeked, // the HTTP server (and hence its shutdown) does not know about the connection until that first byte peeked. err = request.Write(conn) require.NoError(t, err) - reader := bufio.NewReader(conn) + reader := bufio.NewReaderSize(conn, 1) // Wait for first byte in response. _, err = reader.Peek(1) require.NoError(t, err) diff --git a/pkg/server/server_entrypoint_udp_test.go b/pkg/server/server_entrypoint_udp_test.go index f219cd98f..0396f434f 100644 --- a/pkg/server/server_entrypoint_udp_test.go +++ b/pkg/server/server_entrypoint_udp_test.go @@ -32,16 +32,19 @@ func TestShutdownUDPConn(t *testing.T) { for { b := make([]byte, 1024*1024) n, err := conn.Read(b) - require.NoError(t, err) - // We control the termination, otherwise we would block on the Read above, until - // conn is closed by a timeout. Which means we would get an error, and even though - // we are in a goroutine and the current test might be over, go test would still - // yell at us if this happens while other tests are still running. + if err != nil { + return + } + + // We control the termination, otherwise we would block on the Read above, + // until conn is closed by a timeout. + // Which means we would get an error, + // and even though we are in a goroutine and the current test might be over, + // go test would still yell at us if this happens while other tests are still running. if string(b[:n]) == "CLOSE" { return } - _, err = conn.Write(b[:n]) - require.NoError(t, err) + _, _ = conn.Write(b[:n]) } })) @@ -68,9 +71,9 @@ func TestShutdownUDPConn(t *testing.T) { // Packet is accepted, but dropped require.NoError(t, err) - // Make sure that our session is yet again still live. This is specifically to - // make sure we don't create a regression in listener's readLoop, i.e. that we only - // terminate the listener's readLoop goroutine by closing its pConn. + // Make sure that our session is yet again still live. + // This is specifically to make sure we don't create a regression in listener's readLoop, + // i.e. that we only terminate the listener's readLoop goroutine by closing its pConn. requireEcho(t, "TEST3", conn, time.Second) done := make(chan bool) @@ -101,10 +104,11 @@ func TestShutdownUDPConn(t *testing.T) { } } -// requireEcho tests that the conn session is live and functional, by writing -// data through it, and expecting the same data as a response when reading on it. -// It fatals if the read blocks longer than timeout, which is useful to detect -// regressions that would make a test wait forever. +// requireEcho tests that conn session is live and functional, +// by writing data through it, +// and expecting the same data as a response when reading on it. +// It fatals if the read blocks longer than timeout, +// which is useful to detect regressions that would make a test wait forever. func requireEcho(t *testing.T, data string, conn io.ReadWriter, timeout time.Duration) { t.Helper() From 6c75052a1383e7acfaf28ab569000c57f37f59f3 Mon Sep 17 00:00:00 2001 From: Tom Moulard Date: Wed, 7 Dec 2022 11:34:06 +0100 Subject: [PATCH 16/19] Change traefik cmd error log to error level --- cmd/traefik/traefik.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/traefik/traefik.go b/cmd/traefik/traefik.go index 0559fbb20..9d0256795 100644 --- a/cmd/traefik/traefik.go +++ b/cmd/traefik/traefik.go @@ -79,7 +79,7 @@ Complete documentation is available at https://traefik.io`, err = cli.Execute(cmdTraefik) if err != nil { - stdlog.Println(err) + log.Error().Err(err).Msg("Command error") logrus.Exit(1) } From d97d3a6726ad6e50110dd2ff2b552a8b17eeed55 Mon Sep 17 00:00:00 2001 From: Tom Moulard Date: Wed, 7 Dec 2022 15:14:05 +0100 Subject: [PATCH 17/19] Prepare release v2.9.6 --- CHANGELOG.md | 19 +++++++++++++++++++ script/gcg/traefik-bugfix.toml | 6 +++--- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2660f4888..76675b738 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,22 @@ +## [v2.9.6](https://github.com/traefik/traefik/tree/v2.9.6) (2022-12-07) +[All Commits](https://github.com/traefik/traefik/compare/v2.9.5...v2.9.6) + +**Bug fixes:** +- **[acme]** Update go-acme/lego to v4.9.1 ([#9550](https://github.com/traefik/traefik/pull/9550) by [ldez](https://github.com/ldez)) +- **[k8s/crd]** Support of allowEmptyServices in TraefikService ([#9424](https://github.com/traefik/traefik/pull/9424) by [jeromeguiard](https://github.com/jeromeguiard)) +- **[logs]** Remove logs of the request ([#9574](https://github.com/traefik/traefik/pull/9574) by [ldez](https://github.com/ldez)) +- **[plugins]** Increase the timeout on plugin download ([#9529](https://github.com/traefik/traefik/pull/9529) by [ldez](https://github.com/ldez)) +- **[server]** Update golang.org/x/net ([#9582](https://github.com/traefik/traefik/pull/9582) by [ldez](https://github.com/ldez)) +- **[tls]** Handle broken TLS conf better ([#9572](https://github.com/traefik/traefik/pull/9572) by [mpl](https://github.com/mpl)) +- **[tracing]** Update DataDog tracing dependency to v1.43.1 ([#9526](https://github.com/traefik/traefik/pull/9526) by [rtribotte](https://github.com/rtribotte)) +- **[webui]** Add missing serialNumber passTLSClientCert option to middleware panel ([#9539](https://github.com/traefik/traefik/pull/9539) by [rtribotte](https://github.com/rtribotte)) + +**Documentation:** +- **[docker]** Add networking example ([#9542](https://github.com/traefik/traefik/pull/9542) by [Janik-Haag](https://github.com/Janik-Haag)) +- **[hub]** Add information about the Hub Agent ([#9560](https://github.com/traefik/traefik/pull/9560) by [nmengin](https://github.com/nmengin)) +- **[k8s/helm]** Update Helm installation section ([#9564](https://github.com/traefik/traefik/pull/9564) by [mloiseleur](https://github.com/mloiseleur)) +- **[middleware]** Clarify PathPrefix matcher greediness ([#9519](https://github.com/traefik/traefik/pull/9519) by [mpl](https://github.com/mpl)) + ## [v2.9.5](https://github.com/traefik/traefik/tree/v2.9.5) (2022-11-17) [All Commits](https://github.com/traefik/traefik/compare/v2.9.4...v2.9.5) diff --git a/script/gcg/traefik-bugfix.toml b/script/gcg/traefik-bugfix.toml index 95aa2b1ab..9db9d689c 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.9.5 +# example new bugfix v2.9.6 CurrentRef = "v2.9" -PreviousRef = "v2.9.4" +PreviousRef = "v2.9.5" BaseBranch = "v2.9" -FutureCurrentRefName = "v2.9.5" +FutureCurrentRefName = "v2.9.6" ThresholdPreviousRef = 10 ThresholdCurrentRef = 10 From fdd3f2abef64ff058b9ec849f2a46e9a3128432f Mon Sep 17 00:00:00 2001 From: Simon Delicata Date: Wed, 7 Dec 2022 17:02:05 +0100 Subject: [PATCH 18/19] Moves HTTP/3 outside the experimental section --- docs/content/migration/v2-to-v3.md | 5 ++ .../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 | 60 +++++++------------ pkg/config/static/experimental.go | 1 - pkg/config/static/static_config.go | 10 ---- 8 files changed, 28 insertions(+), 56 deletions(-) diff --git a/docs/content/migration/v2-to-v3.md b/docs/content/migration/v2-to-v3.md index a574bf5e7..27d38e953 100644 --- a/docs/content/migration/v2-to-v3.md +++ b/docs/content/migration/v2-to-v3.md @@ -50,3 +50,8 @@ and should be explicitly combined using logical operators to mimic previous beha In v3, the `Content-Type` header is not auto-detected anymore when it is not set by the backend. One should use the `ContentType` middleware to enable the `Content-Type` header value auto-detection. + +## HTTP/3 + +In v3, HTTP/3 is no longer an experimental feature. +The `experimental.http3` option has been removed from the static configuration. diff --git a/docs/content/reference/static-configuration/cli-ref.md b/docs/content/reference/static-configuration/cli-ref.md index a79447131..39cb5d102 100644 --- a/docs/content/reference/static-configuration/cli-ref.md +++ b/docs/content/reference/static-configuration/cli-ref.md @@ -189,9 +189,6 @@ WriteTimeout is the maximum duration before timing out writes of the response. I `--entrypoints..udp.timeout`: Timeout defines how long to wait on an idle session before releasing the related resources. (Default: ```3```) -`--experimental.http3`: -Enable HTTP3. (Default: ```false```) - `--experimental.hub`: Enable the Traefik Hub provider. (Default: ```false```) diff --git a/docs/content/reference/static-configuration/env-ref.md b/docs/content/reference/static-configuration/env-ref.md index 41bf8c6bf..08fb6bd7d 100644 --- a/docs/content/reference/static-configuration/env-ref.md +++ b/docs/content/reference/static-configuration/env-ref.md @@ -189,9 +189,6 @@ WriteTimeout is the maximum duration before timing out writes of the response. I `TRAEFIK_ENTRYPOINTS__UDP_TIMEOUT`: Timeout defines how long to wait on an idle session before releasing the related resources. (Default: ```3```) -`TRAEFIK_EXPERIMENTAL_HTTP3`: -Enable HTTP3. (Default: ```false```) - `TRAEFIK_EXPERIMENTAL_HUB`: Enable the Traefik Hub provider. (Default: ```false```) diff --git a/docs/content/reference/static-configuration/file.toml b/docs/content/reference/static-configuration/file.toml index 73bf8f838..22420a806 100644 --- a/docs/content/reference/static-configuration/file.toml +++ b/docs/content/reference/static-configuration/file.toml @@ -461,7 +461,6 @@ [experimental] kubernetesGateway = true - http3 = true hub = true [experimental.plugins] [experimental.plugins.Descriptor0] diff --git a/docs/content/reference/static-configuration/file.yaml b/docs/content/reference/static-configuration/file.yaml index e84e553d9..596f5ab4d 100644 --- a/docs/content/reference/static-configuration/file.yaml +++ b/docs/content/reference/static-configuration/file.yaml @@ -489,7 +489,6 @@ hub: key: foobar experimental: kubernetesGateway: true - http3: true hub: true plugins: Descriptor0: diff --git a/docs/content/routing/entrypoints.md b/docs/content/routing/entrypoints.md index 119fa60e3..ece0f2c0b 100644 --- a/docs/content/routing/entrypoints.md +++ b/docs/content/routing/entrypoints.md @@ -312,39 +312,32 @@ entryPoints: #### `http3` `http3` enables HTTP/3 protocol on the entryPoint. -HTTP/3 requires a TCP entryPoint, as HTTP/3 always starts as a TCP connection that then gets upgraded to UDP. -In most scenarios, this entryPoint is the same as the one used for TLS traffic. +HTTP/3 requires a TCP entryPoint, +as HTTP/3 always starts as a TCP connection that then gets upgraded to UDP. +In most scenarios, +this entryPoint is the same as the one used for TLS traffic. + +```yaml tab="File (YAML)" +entryPoints: + name: + http3: {} +``` + +```toml tab="File (TOML)" +[entryPoints.name.http3] +``` + +```bash tab="CLI" +--entrypoints.name.http3 +``` ??? info "HTTP/3 uses UDP+TLS" - As HTTP/3 uses UDP, you can't have a TCP entryPoint with HTTP/3 on the same port as a UDP entryPoint. - Since HTTP/3 requires the use of TLS, only routers with TLS enabled will be usable with HTTP/3. - -!!! warning "Enabling Experimental HTTP/3" - - As the HTTP/3 spec is still in draft, HTTP/3 support in Traefik is an experimental feature and needs to be activated - in the experimental section of the static configuration. - - ```yaml tab="File (YAML)" - experimental: - http3: true - - entryPoints: - name: - http3: {} - ``` - - ```toml tab="File (TOML)" - [experimental] - http3 = true - - [entryPoints.name.http3] - ``` - - ```bash tab="CLI" - --experimental.http3=true - --entrypoints.name.http3 - ``` + As HTTP/3 actually uses UDP, when traefik is configured with a TCP entryPoint on port N with HTTP/3 enabled, + the underlying HTTP/3 server that is started automatically listens on UDP port N too. As a consequence, + it means port N cannot be used by another UDP entryPoint. + Since HTTP/3 requires the use of TLS, + only routers with TLS enabled will be usable with HTTP/3. #### `advertisedPort` @@ -355,9 +348,6 @@ It can be used to override the authority in the `alt-svc` header, for example if !!! info "http3.advertisedPort" ```yaml tab="File (YAML)" - experimental: - http3: true - entryPoints: name: http3: @@ -365,15 +355,11 @@ It can be used to override the authority in the `alt-svc` header, for example if ``` ```toml tab="File (TOML)" - [experimental] - http3 = true - [entryPoints.name.http3] advertisedPort = 443 ``` ```bash tab="CLI" - --experimental.http3=true --entrypoints.name.http3.advertisedport=443 ``` diff --git a/pkg/config/static/experimental.go b/pkg/config/static/experimental.go index 7f0c813ac..5f16f9898 100644 --- a/pkg/config/static/experimental.go +++ b/pkg/config/static/experimental.go @@ -8,6 +8,5 @@ type Experimental struct { LocalPlugins map[string]plugins.LocalDescriptor `description:"Local plugins configuration." json:"localPlugins,omitempty" toml:"localPlugins,omitempty" yaml:"localPlugins,omitempty" export:"true"` KubernetesGateway bool `description:"Allow the Kubernetes gateway api provider usage." json:"kubernetesGateway,omitempty" toml:"kubernetesGateway,omitempty" yaml:"kubernetesGateway,omitempty" export:"true"` - HTTP3 bool `description:"Enable HTTP3." json:"http3,omitempty" toml:"http3,omitempty" yaml:"http3,omitempty" export:"true"` Hub bool `description:"Enable the Traefik Hub provider." json:"hub,omitempty" toml:"hub,omitempty" yaml:"hub,omitempty" export:"true"` } diff --git a/pkg/config/static/static_config.go b/pkg/config/static/static_config.go index ce17087c0..da32b76f9 100644 --- a/pkg/config/static/static_config.go +++ b/pkg/config/static/static_config.go @@ -268,16 +268,6 @@ func (c *Configuration) SetEffectiveConfiguration() { c.Providers.KubernetesGateway = nil } - if c.Experimental == nil || !c.Experimental.HTTP3 { - for epName, ep := range c.EntryPoints { - if ep.HTTP3 != nil { - ep.HTTP3 = nil - log.Debug().Str(logs.EntryPointName, epName). - Msgf("Disabling HTTP3 configuration for entryPoint %q: HTTP3 is disabled in the experimental configuration section", epName) - } - } - } - // Configure Gateway API provider if c.Providers.KubernetesGateway != nil { log.Debug().Msg("Experimental Kubernetes Gateway provider has been activated") From e54ee89330a800d509da7b11b46a6ecbb331e791 Mon Sep 17 00:00:00 2001 From: Tom Moulard Date: Wed, 7 Dec 2022 17:26:04 +0100 Subject: [PATCH 19/19] Prepare release v3.0.0-beta2 --- CHANGELOG.md | 13 +++++++++++++ script/gcg/traefik-rc-new.toml | 10 +++++----- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fcea4a780..9a393f217 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,16 @@ +## [v3.0.0-beta2](https://github.com/traefik/traefik/tree/v3.0.0-beta2) (2022-12-07) +[All Commits](https://github.com/traefik/traefik/compare/v3.0.0-beta1...v3.0.0-beta2) + +**Enhancements:** +- **[http3]** Moves HTTP/3 outside the experimental section ([#9570](https://github.com/traefik/traefik/pull/9570) by [sdelicata](https://github.com/sdelicata)) + +**Bug fixes:** +- **[logs]** Change traefik cmd error log to error level ([#9569](https://github.com/traefik/traefik/pull/9569) by [tomMoulard](https://github.com/tomMoulard)) +- **[rules]** Rework Host and HostRegexp matchers ([#9559](https://github.com/traefik/traefik/pull/9559) by [tomMoulard](https://github.com/tomMoulard)) + +**Misc:** +- Merge current v2.9 into master ([#9586](https://github.com/traefik/traefik/pull/9586) by [tomMoulard](https://github.com/tomMoulard)) + ## [v2.9.6](https://github.com/traefik/traefik/tree/v2.9.6) (2022-12-07) [All Commits](https://github.com/traefik/traefik/compare/v2.9.5...v2.9.6) diff --git a/script/gcg/traefik-rc-new.toml b/script/gcg/traefik-rc-new.toml index a48ac8b2b..5c4090849 100644 --- a/script/gcg/traefik-rc-new.toml +++ b/script/gcg/traefik-rc-new.toml @@ -4,11 +4,11 @@ RepositoryName = "traefik" OutputType = "file" FileName = "traefik_changelog.md" -# example RC5 of v2.9.0 -CurrentRef = "v2.9" -PreviousRef = "v2.9.0-rc4" -BaseBranch = "v2.9" -FutureCurrentRefName = "v2.9.0-rc5" +# example beta2 of v3.0.0 +CurrentRef = "master" +PreviousRef = "v3.0.0-beta1" +BaseBranch = "master" +FutureCurrentRefName = "v3.0.0-beta2" ThresholdPreviousRef = 10 ThresholdCurrentRef = 10