diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml deleted file mode 100644 index b920e1793..000000000 --- a/.github/FUNDING.yml +++ /dev/null @@ -1,3 +0,0 @@ -# These are supported funding model platforms - -github: traefik diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md deleted file mode 100644 index 8d124b341..000000000 --- a/.github/ISSUE_TEMPLATE.md +++ /dev/null @@ -1,77 +0,0 @@ - - -### Do you want to request a *feature* or report a *bug*? - - - -Bug - - - -### What did you do? - - - -### What did you expect to see? - - - -### What did you see instead? - - - -### Output of `traefik version`: (_What version of Traefik are you using?_) - - - -``` -(paste your output here) -``` - -### What is your environment & configuration (arguments, toml, provider, platform, ...)? - -```toml -# (paste your configuration here) -``` - - - - -### If applicable, please paste the log output in DEBUG level (`--log.level=DEBUG` switch) - -``` -(paste your output here) -``` diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml deleted file mode 100644 index ce21d35ee..000000000 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ /dev/null @@ -1,82 +0,0 @@ -name: Bug Report (Traefik) -description: Create a report to help us improve. -body: - - type: checkboxes - id: terms - attributes: - label: Welcome! - description: | - The issue tracker is for reporting bugs and feature requests only. - For end-user related support questions, please use the [Traefik community forum](https://community.traefik.io/). - - All new/updated issues are triaged regularly by the maintainers. - All issues closed by a bot are subsequently double-checked by the maintainers. - - DO NOT FILE ISSUES FOR GENERAL SUPPORT QUESTIONS. - - options: - - label: Yes, I've searched similar issues on [GitHub](https://github.com/traefik/traefik/issues) and didn't find any. - required: true - - label: Yes, I've searched similar issues on the [Traefik community forum](https://community.traefik.io) and didn't find any. - required: true - - - type: textarea - attributes: - label: What did you do? - description: | - How to write a good bug report? - - - Respect the issue template as much as possible. - - The title should be short and descriptive. - - Explain the conditions which led you to report this issue: the context. - - The context should lead to something, an idea or a problem that you’re facing. - - Remain clear and concise. - - Format your messages to help the reader focus on what matters and understand the structure of your message, use [Markdown syntax](https://help.github.com/articles/github-flavored-markdown) - placeholder: What did you do? - validations: - required: true - - - type: textarea - attributes: - label: What did you see instead? - placeholder: What did you see instead? - validations: - required: true - - - type: textarea - attributes: - label: What version of Traefik are you using? - description: | - `latest` is not considered as a valid version. - - Output of `traefik version`. - - For the Traefik Docker image (`docker run [IMAGE] version`), example: - ```console - $ docker run traefik version - ``` - placeholder: Paste your output here. - validations: - required: true - - - type: textarea - attributes: - label: What is your environment & configuration? - description: arguments, toml, provider, platform, ... - placeholder: Add information here. - value: | - ```yaml - # (paste your configuration here) - ``` - - Add more configuration information here. - validations: - required: true - - - type: textarea - attributes: - label: If applicable, please paste the log output in DEBUG level - description: "`--log.level=DEBUG` switch." - placeholder: Paste your output here. - validations: - required: false diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml deleted file mode 100644 index 80c8c86b5..000000000 --- a/.github/ISSUE_TEMPLATE/config.yml +++ /dev/null @@ -1,8 +0,0 @@ -blank_issues_enabled: false -contact_links: - - name: Traefik Community Support - url: https://community.traefik.io/ - about: If you have a question, or are looking for advice, please post on our Discuss forum! The community loves to chime in to help. Happy Coding! - - name: Traefik Helm Chart Issues - url: https://github.com/traefik/traefik-helm-chart - about: Are you submitting an issue or feature enhancement for the Traefik helm chart? Please post in the traefik-helm-chart GitHub Issues. diff --git a/.github/ISSUE_TEMPLATE/feature-request.yml b/.github/ISSUE_TEMPLATE/feature-request.yml deleted file mode 100644 index 5a092594d..000000000 --- a/.github/ISSUE_TEMPLATE/feature-request.yml +++ /dev/null @@ -1,33 +0,0 @@ -name: Feature Request (Traefik) -description: Suggest an idea for this project. -body: - - type: checkboxes - id: terms - attributes: - label: Welcome! - description: | - The issue tracker is for reporting bugs and feature requests only. For end-user related support questions, please refer to one of the following: - - the Traefik community forum: https://community.traefik.io/ - - DO NOT FILE ISSUES FOR GENERAL SUPPORT QUESTIONS. - options: - - label: Yes, I've searched similar issues on [GitHub](https://github.com/traefik/traefik/issues) and didn't find any. - required: true - - label: Yes, I've searched similar issues on the [Traefik community forum](https://community.traefik.io) and didn't find any. - required: true - - - type: textarea - attributes: - label: What did you expect to see? - description: | - How to write a good issue? - - - Respect the issue template as much as possible. - - The title should be short and descriptive. - - Explain the conditions which led you to report this issue: the context. - - The context should lead to something, an idea or a problem that you’re facing. - - Remain clear and concise. - - Format your messages to help the reader focus on what matters and understand the structure of your message, use [Markdown syntax](https://help.github.com/articles/github-flavored-markdown) - placeholder: What did you expect to see? - validations: - required: true diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 2594684f6..b4717dc18 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,17 +1,16 @@ -| `traefik/http/middlewares/Middleware01/addPrefix/prefix` | `foobar` | -| `traefik/http/middlewares/Middleware02/basicAuth/headerField` | `foobar` | -| `traefik/http/middlewares/Middleware02/basicAuth/realm` | `foobar` | -| `traefik/http/middlewares/Middleware02/basicAuth/removeHeader` | `true` | -| `traefik/http/middlewares/Middleware02/basicAuth/users/0` | `foobar` | -| `traefik/http/middlewares/Middleware02/basicAuth/users/1` | `foobar` | -| `traefik/http/middlewares/Middleware02/basicAuth/usersFile` | `foobar` | -| `traefik/http/middlewares/Middleware03/buffering/maxRequestBodyBytes` | `42` | -| `traefik/http/middlewares/Middleware03/buffering/maxResponseBodyBytes` | `42` | -| `traefik/http/middlewares/Middleware03/buffering/memRequestBodyBytes` | `42` | -| `traefik/http/middlewares/Middleware03/buffering/memResponseBodyBytes` | `42` | -| `traefik/http/middlewares/Middleware03/buffering/retryExpression` | `foobar` | -| `traefik/http/middlewares/Middleware04/chain/middlewares/0` | `foobar` | -| `traefik/http/middlewares/Middleware04/chain/middlewares/1` | `foobar` | -| `traefik/http/middlewares/Middleware05/circuitBreaker/checkPeriod` | `42s` | -| `traefik/http/middlewares/Middleware05/circuitBreaker/expression` | `foobar` | -| `traefik/http/middlewares/Middleware05/circuitBreaker/fallbackDuration` | `42s` | -| `traefik/http/middlewares/Middleware05/circuitBreaker/recoveryDuration` | `42s` | -| `traefik/http/middlewares/Middleware05/circuitBreaker/responseCode` | `42` | -| `traefik/http/middlewares/Middleware06/compress/defaultEncoding` | `foobar` | -| `traefik/http/middlewares/Middleware06/compress/encodings/0` | `foobar` | -| `traefik/http/middlewares/Middleware06/compress/encodings/1` | `foobar` | -| `traefik/http/middlewares/Middleware06/compress/excludedContentTypes/0` | `foobar` | -| `traefik/http/middlewares/Middleware06/compress/excludedContentTypes/1` | `foobar` | -| `traefik/http/middlewares/Middleware06/compress/includedContentTypes/0` | `foobar` | -| `traefik/http/middlewares/Middleware06/compress/includedContentTypes/1` | `foobar` | -| `traefik/http/middlewares/Middleware06/compress/minResponseBodyBytes` | `42` | -| `traefik/http/middlewares/Middleware07/contentType/autoDetect` | `true` | -| `traefik/http/middlewares/Middleware08/digestAuth/headerField` | `foobar` | -| `traefik/http/middlewares/Middleware08/digestAuth/realm` | `foobar` | -| `traefik/http/middlewares/Middleware08/digestAuth/removeHeader` | `true` | -| `traefik/http/middlewares/Middleware08/digestAuth/users/0` | `foobar` | -| `traefik/http/middlewares/Middleware08/digestAuth/users/1` | `foobar` | -| `traefik/http/middlewares/Middleware08/digestAuth/usersFile` | `foobar` | -| `traefik/http/middlewares/Middleware09/errors/query` | `foobar` | -| `traefik/http/middlewares/Middleware09/errors/service` | `foobar` | -| `traefik/http/middlewares/Middleware09/errors/status/0` | `foobar` | -| `traefik/http/middlewares/Middleware09/errors/status/1` | `foobar` | -| `traefik/http/middlewares/Middleware09/errors/statusRewrites/name0` | `42` | -| `traefik/http/middlewares/Middleware09/errors/statusRewrites/name1` | `42` | -| `traefik/http/middlewares/Middleware10/forwardAuth/addAuthCookiesToResponse/0` | `foobar` | -| `traefik/http/middlewares/Middleware10/forwardAuth/addAuthCookiesToResponse/1` | `foobar` | -| `traefik/http/middlewares/Middleware10/forwardAuth/address` | `foobar` | -| `traefik/http/middlewares/Middleware10/forwardAuth/authRequestHeaders/0` | `foobar` | -| `traefik/http/middlewares/Middleware10/forwardAuth/authRequestHeaders/1` | `foobar` | -| `traefik/http/middlewares/Middleware10/forwardAuth/authResponseHeaders/0` | `foobar` | -| `traefik/http/middlewares/Middleware10/forwardAuth/authResponseHeaders/1` | `foobar` | -| `traefik/http/middlewares/Middleware10/forwardAuth/authResponseHeadersRegex` | `foobar` | -| `traefik/http/middlewares/Middleware10/forwardAuth/forwardBody` | `true` | -| `traefik/http/middlewares/Middleware10/forwardAuth/headerField` | `foobar` | -| `traefik/http/middlewares/Middleware10/forwardAuth/maxBodySize` | `42` | -| `traefik/http/middlewares/Middleware10/forwardAuth/preserveLocationHeader` | `true` | -| `traefik/http/middlewares/Middleware10/forwardAuth/preserveRequestMethod` | `true` | -| `traefik/http/middlewares/Middleware10/forwardAuth/tls/ca` | `foobar` | -| `traefik/http/middlewares/Middleware10/forwardAuth/tls/caOptional` | `true` | -| `traefik/http/middlewares/Middleware10/forwardAuth/tls/cert` | `foobar` | -| `traefik/http/middlewares/Middleware10/forwardAuth/tls/insecureSkipVerify` | `true` | -| `traefik/http/middlewares/Middleware10/forwardAuth/tls/key` | `foobar` | -| `traefik/http/middlewares/Middleware10/forwardAuth/trustForwardHeader` | `true` | -| `traefik/http/middlewares/Middleware11/grpcWeb/allowOrigins/0` | `foobar` | -| `traefik/http/middlewares/Middleware11/grpcWeb/allowOrigins/1` | `foobar` | -| `traefik/http/middlewares/Middleware12/headers/accessControlAllowCredentials` | `true` | -| `traefik/http/middlewares/Middleware12/headers/accessControlAllowHeaders/0` | `foobar` | -| `traefik/http/middlewares/Middleware12/headers/accessControlAllowHeaders/1` | `foobar` | -| `traefik/http/middlewares/Middleware12/headers/accessControlAllowMethods/0` | `foobar` | -| `traefik/http/middlewares/Middleware12/headers/accessControlAllowMethods/1` | `foobar` | -| `traefik/http/middlewares/Middleware12/headers/accessControlAllowOriginList/0` | `foobar` | -| `traefik/http/middlewares/Middleware12/headers/accessControlAllowOriginList/1` | `foobar` | -| `traefik/http/middlewares/Middleware12/headers/accessControlAllowOriginListRegex/0` | `foobar` | -| `traefik/http/middlewares/Middleware12/headers/accessControlAllowOriginListRegex/1` | `foobar` | -| `traefik/http/middlewares/Middleware12/headers/accessControlExposeHeaders/0` | `foobar` | -| `traefik/http/middlewares/Middleware12/headers/accessControlExposeHeaders/1` | `foobar` | -| `traefik/http/middlewares/Middleware12/headers/accessControlMaxAge` | `42` | -| `traefik/http/middlewares/Middleware12/headers/addVaryHeader` | `true` | -| `traefik/http/middlewares/Middleware12/headers/allowedHosts/0` | `foobar` | -| `traefik/http/middlewares/Middleware12/headers/allowedHosts/1` | `foobar` | -| `traefik/http/middlewares/Middleware12/headers/browserXssFilter` | `true` | -| `traefik/http/middlewares/Middleware12/headers/contentSecurityPolicy` | `foobar` | -| `traefik/http/middlewares/Middleware12/headers/contentSecurityPolicyReportOnly` | `foobar` | -| `traefik/http/middlewares/Middleware12/headers/contentTypeNosniff` | `true` | -| `traefik/http/middlewares/Middleware12/headers/customBrowserXSSValue` | `foobar` | -| `traefik/http/middlewares/Middleware12/headers/customFrameOptionsValue` | `foobar` | -| `traefik/http/middlewares/Middleware12/headers/customRequestHeaders/name0` | `foobar` | -| `traefik/http/middlewares/Middleware12/headers/customRequestHeaders/name1` | `foobar` | -| `traefik/http/middlewares/Middleware12/headers/customResponseHeaders/name0` | `foobar` | -| `traefik/http/middlewares/Middleware12/headers/customResponseHeaders/name1` | `foobar` | -| `traefik/http/middlewares/Middleware12/headers/featurePolicy` | `foobar` | -| `traefik/http/middlewares/Middleware12/headers/forceSTSHeader` | `true` | -| `traefik/http/middlewares/Middleware12/headers/frameDeny` | `true` | -| `traefik/http/middlewares/Middleware12/headers/hostsProxyHeaders/0` | `foobar` | -| `traefik/http/middlewares/Middleware12/headers/hostsProxyHeaders/1` | `foobar` | -| `traefik/http/middlewares/Middleware12/headers/isDevelopment` | `true` | -| `traefik/http/middlewares/Middleware12/headers/permissionsPolicy` | `foobar` | -| `traefik/http/middlewares/Middleware12/headers/publicKey` | `foobar` | -| `traefik/http/middlewares/Middleware12/headers/referrerPolicy` | `foobar` | -| `traefik/http/middlewares/Middleware12/headers/sslForceHost` | `true` | -| `traefik/http/middlewares/Middleware12/headers/sslHost` | `foobar` | -| `traefik/http/middlewares/Middleware12/headers/sslProxyHeaders/name0` | `foobar` | -| `traefik/http/middlewares/Middleware12/headers/sslProxyHeaders/name1` | `foobar` | -| `traefik/http/middlewares/Middleware12/headers/sslRedirect` | `true` | -| `traefik/http/middlewares/Middleware12/headers/sslTemporaryRedirect` | `true` | -| `traefik/http/middlewares/Middleware12/headers/stsIncludeSubdomains` | `true` | -| `traefik/http/middlewares/Middleware12/headers/stsPreload` | `true` | -| `traefik/http/middlewares/Middleware12/headers/stsSeconds` | `42` | -| `traefik/http/middlewares/Middleware13/ipAllowList/ipStrategy/depth` | `42` | -| `traefik/http/middlewares/Middleware13/ipAllowList/ipStrategy/excludedIPs/0` | `foobar` | -| `traefik/http/middlewares/Middleware13/ipAllowList/ipStrategy/excludedIPs/1` | `foobar` | -| `traefik/http/middlewares/Middleware13/ipAllowList/ipStrategy/ipv6Subnet` | `42` | -| `traefik/http/middlewares/Middleware13/ipAllowList/rejectStatusCode` | `42` | -| `traefik/http/middlewares/Middleware13/ipAllowList/sourceRange/0` | `foobar` | -| `traefik/http/middlewares/Middleware13/ipAllowList/sourceRange/1` | `foobar` | -| `traefik/http/middlewares/Middleware14/ipWhiteList/ipStrategy/depth` | `42` | -| `traefik/http/middlewares/Middleware14/ipWhiteList/ipStrategy/excludedIPs/0` | `foobar` | -| `traefik/http/middlewares/Middleware14/ipWhiteList/ipStrategy/excludedIPs/1` | `foobar` | -| `traefik/http/middlewares/Middleware14/ipWhiteList/ipStrategy/ipv6Subnet` | `42` | -| `traefik/http/middlewares/Middleware14/ipWhiteList/sourceRange/0` | `foobar` | -| `traefik/http/middlewares/Middleware14/ipWhiteList/sourceRange/1` | `foobar` | -| `traefik/http/middlewares/Middleware15/inFlightReq/amount` | `42` | -| `traefik/http/middlewares/Middleware15/inFlightReq/sourceCriterion/ipStrategy/depth` | `42` | -| `traefik/http/middlewares/Middleware15/inFlightReq/sourceCriterion/ipStrategy/excludedIPs/0` | `foobar` | -| `traefik/http/middlewares/Middleware15/inFlightReq/sourceCriterion/ipStrategy/excludedIPs/1` | `foobar` | -| `traefik/http/middlewares/Middleware15/inFlightReq/sourceCriterion/ipStrategy/ipv6Subnet` | `42` | -| `traefik/http/middlewares/Middleware15/inFlightReq/sourceCriterion/requestHeaderName` | `foobar` | -| `traefik/http/middlewares/Middleware15/inFlightReq/sourceCriterion/requestHost` | `true` | -| `traefik/http/middlewares/Middleware16/passTLSClientCert/info/issuer/commonName` | `true` | -| `traefik/http/middlewares/Middleware16/passTLSClientCert/info/issuer/country` | `true` | -| `traefik/http/middlewares/Middleware16/passTLSClientCert/info/issuer/domainComponent` | `true` | -| `traefik/http/middlewares/Middleware16/passTLSClientCert/info/issuer/locality` | `true` | -| `traefik/http/middlewares/Middleware16/passTLSClientCert/info/issuer/organization` | `true` | -| `traefik/http/middlewares/Middleware16/passTLSClientCert/info/issuer/province` | `true` | -| `traefik/http/middlewares/Middleware16/passTLSClientCert/info/issuer/serialNumber` | `true` | -| `traefik/http/middlewares/Middleware16/passTLSClientCert/info/notAfter` | `true` | -| `traefik/http/middlewares/Middleware16/passTLSClientCert/info/notBefore` | `true` | -| `traefik/http/middlewares/Middleware16/passTLSClientCert/info/sans` | `true` | -| `traefik/http/middlewares/Middleware16/passTLSClientCert/info/serialNumber` | `true` | -| `traefik/http/middlewares/Middleware16/passTLSClientCert/info/subject/commonName` | `true` | -| `traefik/http/middlewares/Middleware16/passTLSClientCert/info/subject/country` | `true` | -| `traefik/http/middlewares/Middleware16/passTLSClientCert/info/subject/domainComponent` | `true` | -| `traefik/http/middlewares/Middleware16/passTLSClientCert/info/subject/locality` | `true` | -| `traefik/http/middlewares/Middleware16/passTLSClientCert/info/subject/organization` | `true` | -| `traefik/http/middlewares/Middleware16/passTLSClientCert/info/subject/organizationalUnit` | `true` | -| `traefik/http/middlewares/Middleware16/passTLSClientCert/info/subject/province` | `true` | -| `traefik/http/middlewares/Middleware16/passTLSClientCert/info/subject/serialNumber` | `true` | -| `traefik/http/middlewares/Middleware16/passTLSClientCert/pem` | `true` | -| `traefik/http/middlewares/Middleware17/plugin/PluginConf0/name0` | `foobar` | -| `traefik/http/middlewares/Middleware17/plugin/PluginConf0/name1` | `foobar` | -| `traefik/http/middlewares/Middleware17/plugin/PluginConf1/name0` | `foobar` | -| `traefik/http/middlewares/Middleware17/plugin/PluginConf1/name1` | `foobar` | -| `traefik/http/middlewares/Middleware18/rateLimit/average` | `42` | -| `traefik/http/middlewares/Middleware18/rateLimit/burst` | `42` | -| `traefik/http/middlewares/Middleware18/rateLimit/period` | `42s` | -| `traefik/http/middlewares/Middleware18/rateLimit/redis/db` | `42` | -| `traefik/http/middlewares/Middleware18/rateLimit/redis/dialTimeout` | `42s` | -| `traefik/http/middlewares/Middleware18/rateLimit/redis/endpoints/0` | `foobar` | -| `traefik/http/middlewares/Middleware18/rateLimit/redis/endpoints/1` | `foobar` | -| `traefik/http/middlewares/Middleware18/rateLimit/redis/maxActiveConns` | `42` | -| `traefik/http/middlewares/Middleware18/rateLimit/redis/minIdleConns` | `42` | -| `traefik/http/middlewares/Middleware18/rateLimit/redis/password` | `foobar` | -| `traefik/http/middlewares/Middleware18/rateLimit/redis/poolSize` | `42` | -| `traefik/http/middlewares/Middleware18/rateLimit/redis/readTimeout` | `42s` | -| `traefik/http/middlewares/Middleware18/rateLimit/redis/tls/ca` | `foobar` | -| `traefik/http/middlewares/Middleware18/rateLimit/redis/tls/cert` | `foobar` | -| `traefik/http/middlewares/Middleware18/rateLimit/redis/tls/insecureSkipVerify` | `true` | -| `traefik/http/middlewares/Middleware18/rateLimit/redis/tls/key` | `foobar` | -| `traefik/http/middlewares/Middleware18/rateLimit/redis/username` | `foobar` | -| `traefik/http/middlewares/Middleware18/rateLimit/redis/writeTimeout` | `42s` | -| `traefik/http/middlewares/Middleware18/rateLimit/sourceCriterion/ipStrategy/depth` | `42` | -| `traefik/http/middlewares/Middleware18/rateLimit/sourceCriterion/ipStrategy/excludedIPs/0` | `foobar` | -| `traefik/http/middlewares/Middleware18/rateLimit/sourceCriterion/ipStrategy/excludedIPs/1` | `foobar` | -| `traefik/http/middlewares/Middleware18/rateLimit/sourceCriterion/ipStrategy/ipv6Subnet` | `42` | -| `traefik/http/middlewares/Middleware18/rateLimit/sourceCriterion/requestHeaderName` | `foobar` | -| `traefik/http/middlewares/Middleware18/rateLimit/sourceCriterion/requestHost` | `true` | -| `traefik/http/middlewares/Middleware19/redirectRegex/permanent` | `true` | -| `traefik/http/middlewares/Middleware19/redirectRegex/regex` | `foobar` | -| `traefik/http/middlewares/Middleware19/redirectRegex/replacement` | `foobar` | -| `traefik/http/middlewares/Middleware20/redirectScheme/permanent` | `true` | -| `traefik/http/middlewares/Middleware20/redirectScheme/port` | `foobar` | -| `traefik/http/middlewares/Middleware20/redirectScheme/scheme` | `foobar` | -| `traefik/http/middlewares/Middleware21/replacePath/path` | `foobar` | -| `traefik/http/middlewares/Middleware22/replacePathRegex/regex` | `foobar` | -| `traefik/http/middlewares/Middleware22/replacePathRegex/replacement` | `foobar` | -| `traefik/http/middlewares/Middleware23/retry/attempts` | `42` | -| `traefik/http/middlewares/Middleware23/retry/initialInterval` | `42s` | -| `traefik/http/middlewares/Middleware24/stripPrefix/forceSlash` | `true` | -| `traefik/http/middlewares/Middleware24/stripPrefix/prefixes/0` | `foobar` | -| `traefik/http/middlewares/Middleware24/stripPrefix/prefixes/1` | `foobar` | -| `traefik/http/middlewares/Middleware25/stripPrefixRegex/regex/0` | `foobar` | -| `traefik/http/middlewares/Middleware25/stripPrefixRegex/regex/1` | `foobar` | -| `traefik/http/routers/Router0/entryPoints/0` | `foobar` | -| `traefik/http/routers/Router0/entryPoints/1` | `foobar` | -| `traefik/http/routers/Router0/middlewares/0` | `foobar` | -| `traefik/http/routers/Router0/middlewares/1` | `foobar` | -| `traefik/http/routers/Router0/observability/accessLogs` | `true` | -| `traefik/http/routers/Router0/observability/metrics` | `true` | -| `traefik/http/routers/Router0/observability/tracing` | `true` | -| `traefik/http/routers/Router0/priority` | `42` | -| `traefik/http/routers/Router0/rule` | `foobar` | -| `traefik/http/routers/Router0/ruleSyntax` | `foobar` | -| `traefik/http/routers/Router0/service` | `foobar` | -| `traefik/http/routers/Router0/tls/certResolver` | `foobar` | -| `traefik/http/routers/Router0/tls/domains/0/main` | `foobar` | -| `traefik/http/routers/Router0/tls/domains/0/sans/0` | `foobar` | -| `traefik/http/routers/Router0/tls/domains/0/sans/1` | `foobar` | -| `traefik/http/routers/Router0/tls/domains/1/main` | `foobar` | -| `traefik/http/routers/Router0/tls/domains/1/sans/0` | `foobar` | -| `traefik/http/routers/Router0/tls/domains/1/sans/1` | `foobar` | -| `traefik/http/routers/Router0/tls/options` | `foobar` | -| `traefik/http/routers/Router1/entryPoints/0` | `foobar` | -| `traefik/http/routers/Router1/entryPoints/1` | `foobar` | -| `traefik/http/routers/Router1/middlewares/0` | `foobar` | -| `traefik/http/routers/Router1/middlewares/1` | `foobar` | -| `traefik/http/routers/Router1/observability/accessLogs` | `true` | -| `traefik/http/routers/Router1/observability/metrics` | `true` | -| `traefik/http/routers/Router1/observability/tracing` | `true` | -| `traefik/http/routers/Router1/priority` | `42` | -| `traefik/http/routers/Router1/rule` | `foobar` | -| `traefik/http/routers/Router1/ruleSyntax` | `foobar` | -| `traefik/http/routers/Router1/service` | `foobar` | -| `traefik/http/routers/Router1/tls/certResolver` | `foobar` | -| `traefik/http/routers/Router1/tls/domains/0/main` | `foobar` | -| `traefik/http/routers/Router1/tls/domains/0/sans/0` | `foobar` | -| `traefik/http/routers/Router1/tls/domains/0/sans/1` | `foobar` | -| `traefik/http/routers/Router1/tls/domains/1/main` | `foobar` | -| `traefik/http/routers/Router1/tls/domains/1/sans/0` | `foobar` | -| `traefik/http/routers/Router1/tls/domains/1/sans/1` | `foobar` | -| `traefik/http/routers/Router1/tls/options` | `foobar` | -| `traefik/http/serversTransports/ServersTransport0/certificates/0/certFile` | `foobar` | -| `traefik/http/serversTransports/ServersTransport0/certificates/0/keyFile` | `foobar` | -| `traefik/http/serversTransports/ServersTransport0/certificates/1/certFile` | `foobar` | -| `traefik/http/serversTransports/ServersTransport0/certificates/1/keyFile` | `foobar` | -| `traefik/http/serversTransports/ServersTransport0/disableHTTP2` | `true` | -| `traefik/http/serversTransports/ServersTransport0/forwardingTimeouts/dialTimeout` | `42s` | -| `traefik/http/serversTransports/ServersTransport0/forwardingTimeouts/idleConnTimeout` | `42s` | -| `traefik/http/serversTransports/ServersTransport0/forwardingTimeouts/pingTimeout` | `42s` | -| `traefik/http/serversTransports/ServersTransport0/forwardingTimeouts/readIdleTimeout` | `42s` | -| `traefik/http/serversTransports/ServersTransport0/forwardingTimeouts/responseHeaderTimeout` | `42s` | -| `traefik/http/serversTransports/ServersTransport0/insecureSkipVerify` | `true` | -| `traefik/http/serversTransports/ServersTransport0/maxIdleConnsPerHost` | `42` | -| `traefik/http/serversTransports/ServersTransport0/peerCertURI` | `foobar` | -| `traefik/http/serversTransports/ServersTransport0/rootCAs/0` | `foobar` | -| `traefik/http/serversTransports/ServersTransport0/rootCAs/1` | `foobar` | -| `traefik/http/serversTransports/ServersTransport0/serverName` | `foobar` | -| `traefik/http/serversTransports/ServersTransport0/spiffe/ids/0` | `foobar` | -| `traefik/http/serversTransports/ServersTransport0/spiffe/ids/1` | `foobar` | -| `traefik/http/serversTransports/ServersTransport0/spiffe/trustDomain` | `foobar` | -| `traefik/http/serversTransports/ServersTransport1/certificates/0/certFile` | `foobar` | -| `traefik/http/serversTransports/ServersTransport1/certificates/0/keyFile` | `foobar` | -| `traefik/http/serversTransports/ServersTransport1/certificates/1/certFile` | `foobar` | -| `traefik/http/serversTransports/ServersTransport1/certificates/1/keyFile` | `foobar` | -| `traefik/http/serversTransports/ServersTransport1/disableHTTP2` | `true` | -| `traefik/http/serversTransports/ServersTransport1/forwardingTimeouts/dialTimeout` | `42s` | -| `traefik/http/serversTransports/ServersTransport1/forwardingTimeouts/idleConnTimeout` | `42s` | -| `traefik/http/serversTransports/ServersTransport1/forwardingTimeouts/pingTimeout` | `42s` | -| `traefik/http/serversTransports/ServersTransport1/forwardingTimeouts/readIdleTimeout` | `42s` | -| `traefik/http/serversTransports/ServersTransport1/forwardingTimeouts/responseHeaderTimeout` | `42s` | -| `traefik/http/serversTransports/ServersTransport1/insecureSkipVerify` | `true` | -| `traefik/http/serversTransports/ServersTransport1/maxIdleConnsPerHost` | `42` | -| `traefik/http/serversTransports/ServersTransport1/peerCertURI` | `foobar` | -| `traefik/http/serversTransports/ServersTransport1/rootCAs/0` | `foobar` | -| `traefik/http/serversTransports/ServersTransport1/rootCAs/1` | `foobar` | -| `traefik/http/serversTransports/ServersTransport1/serverName` | `foobar` | -| `traefik/http/serversTransports/ServersTransport1/spiffe/ids/0` | `foobar` | -| `traefik/http/serversTransports/ServersTransport1/spiffe/ids/1` | `foobar` | -| `traefik/http/serversTransports/ServersTransport1/spiffe/trustDomain` | `foobar` | -| `traefik/http/services/Service01/failover/fallback` | `foobar` | -| `traefik/http/services/Service01/failover/healthCheck` | `` | -| `traefik/http/services/Service01/failover/service` | `foobar` | -| `traefik/http/services/Service02/loadBalancer/healthCheck/followRedirects` | `true` | -| `traefik/http/services/Service02/loadBalancer/healthCheck/headers/name0` | `foobar` | -| `traefik/http/services/Service02/loadBalancer/healthCheck/headers/name1` | `foobar` | -| `traefik/http/services/Service02/loadBalancer/healthCheck/hostname` | `foobar` | -| `traefik/http/services/Service02/loadBalancer/healthCheck/interval` | `42s` | -| `traefik/http/services/Service02/loadBalancer/healthCheck/method` | `foobar` | -| `traefik/http/services/Service02/loadBalancer/healthCheck/mode` | `foobar` | -| `traefik/http/services/Service02/loadBalancer/healthCheck/path` | `foobar` | -| `traefik/http/services/Service02/loadBalancer/healthCheck/port` | `42` | -| `traefik/http/services/Service02/loadBalancer/healthCheck/scheme` | `foobar` | -| `traefik/http/services/Service02/loadBalancer/healthCheck/status` | `42` | -| `traefik/http/services/Service02/loadBalancer/healthCheck/timeout` | `42s` | -| `traefik/http/services/Service02/loadBalancer/passHostHeader` | `true` | -| `traefik/http/services/Service02/loadBalancer/responseForwarding/flushInterval` | `42s` | -| `traefik/http/services/Service02/loadBalancer/servers/0/preservePath` | `true` | -| `traefik/http/services/Service02/loadBalancer/servers/0/url` | `foobar` | -| `traefik/http/services/Service02/loadBalancer/servers/0/weight` | `42` | -| `traefik/http/services/Service02/loadBalancer/servers/1/preservePath` | `true` | -| `traefik/http/services/Service02/loadBalancer/servers/1/url` | `foobar` | -| `traefik/http/services/Service02/loadBalancer/servers/1/weight` | `42` | -| `traefik/http/services/Service02/loadBalancer/serversTransport` | `foobar` | -| `traefik/http/services/Service02/loadBalancer/sticky/cookie/domain` | `foobar` | -| `traefik/http/services/Service02/loadBalancer/sticky/cookie/httpOnly` | `true` | -| `traefik/http/services/Service02/loadBalancer/sticky/cookie/maxAge` | `42` | -| `traefik/http/services/Service02/loadBalancer/sticky/cookie/name` | `foobar` | -| `traefik/http/services/Service02/loadBalancer/sticky/cookie/path` | `foobar` | -| `traefik/http/services/Service02/loadBalancer/sticky/cookie/sameSite` | `foobar` | -| `traefik/http/services/Service02/loadBalancer/sticky/cookie/secure` | `true` | -| `traefik/http/services/Service02/loadBalancer/strategy` | `foobar` | -| `traefik/http/services/Service03/mirroring/healthCheck` | `` | -| `traefik/http/services/Service03/mirroring/maxBodySize` | `42` | -| `traefik/http/services/Service03/mirroring/mirrorBody` | `true` | -| `traefik/http/services/Service03/mirroring/mirrors/0/name` | `foobar` | -| `traefik/http/services/Service03/mirroring/mirrors/0/percent` | `42` | -| `traefik/http/services/Service03/mirroring/mirrors/1/name` | `foobar` | -| `traefik/http/services/Service03/mirroring/mirrors/1/percent` | `42` | -| `traefik/http/services/Service03/mirroring/service` | `foobar` | -| `traefik/http/services/Service04/weighted/healthCheck` | `` | -| `traefik/http/services/Service04/weighted/services/0/name` | `foobar` | -| `traefik/http/services/Service04/weighted/services/0/weight` | `42` | -| `traefik/http/services/Service04/weighted/services/1/name` | `foobar` | -| `traefik/http/services/Service04/weighted/services/1/weight` | `42` | -| `traefik/http/services/Service04/weighted/sticky/cookie/domain` | `foobar` | -| `traefik/http/services/Service04/weighted/sticky/cookie/httpOnly` | `true` | -| `traefik/http/services/Service04/weighted/sticky/cookie/maxAge` | `42` | -| `traefik/http/services/Service04/weighted/sticky/cookie/name` | `foobar` | -| `traefik/http/services/Service04/weighted/sticky/cookie/path` | `foobar` | -| `traefik/http/services/Service04/weighted/sticky/cookie/sameSite` | `foobar` | -| `traefik/http/services/Service04/weighted/sticky/cookie/secure` | `true` | -| `traefik/tcp/middlewares/TCPMiddleware01/ipAllowList/sourceRange/0` | `foobar` | -| `traefik/tcp/middlewares/TCPMiddleware01/ipAllowList/sourceRange/1` | `foobar` | -| `traefik/tcp/middlewares/TCPMiddleware02/ipWhiteList/sourceRange/0` | `foobar` | -| `traefik/tcp/middlewares/TCPMiddleware02/ipWhiteList/sourceRange/1` | `foobar` | -| `traefik/tcp/middlewares/TCPMiddleware03/inFlightConn/amount` | `42` | -| `traefik/tcp/routers/TCPRouter0/entryPoints/0` | `foobar` | -| `traefik/tcp/routers/TCPRouter0/entryPoints/1` | `foobar` | -| `traefik/tcp/routers/TCPRouter0/middlewares/0` | `foobar` | -| `traefik/tcp/routers/TCPRouter0/middlewares/1` | `foobar` | -| `traefik/tcp/routers/TCPRouter0/priority` | `42` | -| `traefik/tcp/routers/TCPRouter0/rule` | `foobar` | -| `traefik/tcp/routers/TCPRouter0/ruleSyntax` | `foobar` | -| `traefik/tcp/routers/TCPRouter0/service` | `foobar` | -| `traefik/tcp/routers/TCPRouter0/tls/certResolver` | `foobar` | -| `traefik/tcp/routers/TCPRouter0/tls/domains/0/main` | `foobar` | -| `traefik/tcp/routers/TCPRouter0/tls/domains/0/sans/0` | `foobar` | -| `traefik/tcp/routers/TCPRouter0/tls/domains/0/sans/1` | `foobar` | -| `traefik/tcp/routers/TCPRouter0/tls/domains/1/main` | `foobar` | -| `traefik/tcp/routers/TCPRouter0/tls/domains/1/sans/0` | `foobar` | -| `traefik/tcp/routers/TCPRouter0/tls/domains/1/sans/1` | `foobar` | -| `traefik/tcp/routers/TCPRouter0/tls/options` | `foobar` | -| `traefik/tcp/routers/TCPRouter0/tls/passthrough` | `true` | -| `traefik/tcp/routers/TCPRouter1/entryPoints/0` | `foobar` | -| `traefik/tcp/routers/TCPRouter1/entryPoints/1` | `foobar` | -| `traefik/tcp/routers/TCPRouter1/middlewares/0` | `foobar` | -| `traefik/tcp/routers/TCPRouter1/middlewares/1` | `foobar` | -| `traefik/tcp/routers/TCPRouter1/priority` | `42` | -| `traefik/tcp/routers/TCPRouter1/rule` | `foobar` | -| `traefik/tcp/routers/TCPRouter1/ruleSyntax` | `foobar` | -| `traefik/tcp/routers/TCPRouter1/service` | `foobar` | -| `traefik/tcp/routers/TCPRouter1/tls/certResolver` | `foobar` | -| `traefik/tcp/routers/TCPRouter1/tls/domains/0/main` | `foobar` | -| `traefik/tcp/routers/TCPRouter1/tls/domains/0/sans/0` | `foobar` | -| `traefik/tcp/routers/TCPRouter1/tls/domains/0/sans/1` | `foobar` | -| `traefik/tcp/routers/TCPRouter1/tls/domains/1/main` | `foobar` | -| `traefik/tcp/routers/TCPRouter1/tls/domains/1/sans/0` | `foobar` | -| `traefik/tcp/routers/TCPRouter1/tls/domains/1/sans/1` | `foobar` | -| `traefik/tcp/routers/TCPRouter1/tls/options` | `foobar` | -| `traefik/tcp/routers/TCPRouter1/tls/passthrough` | `true` | -| `traefik/tcp/serversTransports/TCPServersTransport0/dialKeepAlive` | `42s` | -| `traefik/tcp/serversTransports/TCPServersTransport0/dialTimeout` | `42s` | -| `traefik/tcp/serversTransports/TCPServersTransport0/terminationDelay` | `42s` | -| `traefik/tcp/serversTransports/TCPServersTransport0/tls/certificates/0/certFile` | `foobar` | -| `traefik/tcp/serversTransports/TCPServersTransport0/tls/certificates/0/keyFile` | `foobar` | -| `traefik/tcp/serversTransports/TCPServersTransport0/tls/certificates/1/certFile` | `foobar` | -| `traefik/tcp/serversTransports/TCPServersTransport0/tls/certificates/1/keyFile` | `foobar` | -| `traefik/tcp/serversTransports/TCPServersTransport0/tls/insecureSkipVerify` | `true` | -| `traefik/tcp/serversTransports/TCPServersTransport0/tls/peerCertURI` | `foobar` | -| `traefik/tcp/serversTransports/TCPServersTransport0/tls/rootCAs/0` | `foobar` | -| `traefik/tcp/serversTransports/TCPServersTransport0/tls/rootCAs/1` | `foobar` | -| `traefik/tcp/serversTransports/TCPServersTransport0/tls/serverName` | `foobar` | -| `traefik/tcp/serversTransports/TCPServersTransport0/tls/spiffe/ids/0` | `foobar` | -| `traefik/tcp/serversTransports/TCPServersTransport0/tls/spiffe/ids/1` | `foobar` | -| `traefik/tcp/serversTransports/TCPServersTransport0/tls/spiffe/trustDomain` | `foobar` | -| `traefik/tcp/serversTransports/TCPServersTransport1/dialKeepAlive` | `42s` | -| `traefik/tcp/serversTransports/TCPServersTransport1/dialTimeout` | `42s` | -| `traefik/tcp/serversTransports/TCPServersTransport1/terminationDelay` | `42s` | -| `traefik/tcp/serversTransports/TCPServersTransport1/tls/certificates/0/certFile` | `foobar` | -| `traefik/tcp/serversTransports/TCPServersTransport1/tls/certificates/0/keyFile` | `foobar` | -| `traefik/tcp/serversTransports/TCPServersTransport1/tls/certificates/1/certFile` | `foobar` | -| `traefik/tcp/serversTransports/TCPServersTransport1/tls/certificates/1/keyFile` | `foobar` | -| `traefik/tcp/serversTransports/TCPServersTransport1/tls/insecureSkipVerify` | `true` | -| `traefik/tcp/serversTransports/TCPServersTransport1/tls/peerCertURI` | `foobar` | -| `traefik/tcp/serversTransports/TCPServersTransport1/tls/rootCAs/0` | `foobar` | -| `traefik/tcp/serversTransports/TCPServersTransport1/tls/rootCAs/1` | `foobar` | -| `traefik/tcp/serversTransports/TCPServersTransport1/tls/serverName` | `foobar` | -| `traefik/tcp/serversTransports/TCPServersTransport1/tls/spiffe/ids/0` | `foobar` | -| `traefik/tcp/serversTransports/TCPServersTransport1/tls/spiffe/ids/1` | `foobar` | -| `traefik/tcp/serversTransports/TCPServersTransport1/tls/spiffe/trustDomain` | `foobar` | -| `traefik/tcp/services/TCPService01/loadBalancer/proxyProtocol/version` | `42` | -| `traefik/tcp/services/TCPService01/loadBalancer/servers/0/address` | `foobar` | -| `traefik/tcp/services/TCPService01/loadBalancer/servers/0/tls` | `true` | -| `traefik/tcp/services/TCPService01/loadBalancer/servers/1/address` | `foobar` | -| `traefik/tcp/services/TCPService01/loadBalancer/servers/1/tls` | `true` | -| `traefik/tcp/services/TCPService01/loadBalancer/serversTransport` | `foobar` | -| `traefik/tcp/services/TCPService01/loadBalancer/terminationDelay` | `42` | -| `traefik/tcp/services/TCPService02/weighted/services/0/name` | `foobar` | -| `traefik/tcp/services/TCPService02/weighted/services/0/weight` | `42` | -| `traefik/tcp/services/TCPService02/weighted/services/1/name` | `foobar` | -| `traefik/tcp/services/TCPService02/weighted/services/1/weight` | `42` | -| `traefik/tls/certificates/0/certFile` | `foobar` | -| `traefik/tls/certificates/0/keyFile` | `foobar` | -| `traefik/tls/certificates/0/stores/0` | `foobar` | -| `traefik/tls/certificates/0/stores/1` | `foobar` | -| `traefik/tls/certificates/1/certFile` | `foobar` | -| `traefik/tls/certificates/1/keyFile` | `foobar` | -| `traefik/tls/certificates/1/stores/0` | `foobar` | -| `traefik/tls/certificates/1/stores/1` | `foobar` | -| `traefik/tls/options/Options0/alpnProtocols/0` | `foobar` | -| `traefik/tls/options/Options0/alpnProtocols/1` | `foobar` | -| `traefik/tls/options/Options0/cipherSuites/0` | `foobar` | -| `traefik/tls/options/Options0/cipherSuites/1` | `foobar` | -| `traefik/tls/options/Options0/clientAuth/caFiles/0` | `foobar` | -| `traefik/tls/options/Options0/clientAuth/caFiles/1` | `foobar` | -| `traefik/tls/options/Options0/clientAuth/clientAuthType` | `foobar` | -| `traefik/tls/options/Options0/curvePreferences/0` | `foobar` | -| `traefik/tls/options/Options0/curvePreferences/1` | `foobar` | -| `traefik/tls/options/Options0/disableSessionTickets` | `true` | -| `traefik/tls/options/Options0/maxVersion` | `foobar` | -| `traefik/tls/options/Options0/minVersion` | `foobar` | -| `traefik/tls/options/Options0/preferServerCipherSuites` | `true` | -| `traefik/tls/options/Options0/sniStrict` | `true` | -| `traefik/tls/options/Options1/alpnProtocols/0` | `foobar` | -| `traefik/tls/options/Options1/alpnProtocols/1` | `foobar` | -| `traefik/tls/options/Options1/cipherSuites/0` | `foobar` | -| `traefik/tls/options/Options1/cipherSuites/1` | `foobar` | -| `traefik/tls/options/Options1/clientAuth/caFiles/0` | `foobar` | -| `traefik/tls/options/Options1/clientAuth/caFiles/1` | `foobar` | -| `traefik/tls/options/Options1/clientAuth/clientAuthType` | `foobar` | -| `traefik/tls/options/Options1/curvePreferences/0` | `foobar` | -| `traefik/tls/options/Options1/curvePreferences/1` | `foobar` | -| `traefik/tls/options/Options1/disableSessionTickets` | `true` | -| `traefik/tls/options/Options1/maxVersion` | `foobar` | -| `traefik/tls/options/Options1/minVersion` | `foobar` | -| `traefik/tls/options/Options1/preferServerCipherSuites` | `true` | -| `traefik/tls/options/Options1/sniStrict` | `true` | -| `traefik/tls/stores/Store0/defaultCertificate/certFile` | `foobar` | -| `traefik/tls/stores/Store0/defaultCertificate/keyFile` | `foobar` | -| `traefik/tls/stores/Store0/defaultGeneratedCert/domain/main` | `foobar` | -| `traefik/tls/stores/Store0/defaultGeneratedCert/domain/sans/0` | `foobar` | -| `traefik/tls/stores/Store0/defaultGeneratedCert/domain/sans/1` | `foobar` | -| `traefik/tls/stores/Store0/defaultGeneratedCert/resolver` | `foobar` | -| `traefik/tls/stores/Store1/defaultCertificate/certFile` | `foobar` | -| `traefik/tls/stores/Store1/defaultCertificate/keyFile` | `foobar` | -| `traefik/tls/stores/Store1/defaultGeneratedCert/domain/main` | `foobar` | -| `traefik/tls/stores/Store1/defaultGeneratedCert/domain/sans/0` | `foobar` | -| `traefik/tls/stores/Store1/defaultGeneratedCert/domain/sans/1` | `foobar` | -| `traefik/tls/stores/Store1/defaultGeneratedCert/resolver` | `foobar` | -| `traefik/udp/routers/UDPRouter0/entryPoints/0` | `foobar` | -| `traefik/udp/routers/UDPRouter0/entryPoints/1` | `foobar` | -| `traefik/udp/routers/UDPRouter0/service` | `foobar` | -| `traefik/udp/routers/UDPRouter1/entryPoints/0` | `foobar` | -| `traefik/udp/routers/UDPRouter1/entryPoints/1` | `foobar` | -| `traefik/udp/routers/UDPRouter1/service` | `foobar` | -| `traefik/udp/services/UDPService01/loadBalancer/servers/0/address` | `foobar` | -| `traefik/udp/services/UDPService01/loadBalancer/servers/1/address` | `foobar` | -| `traefik/udp/services/UDPService02/weighted/services/0/name` | `foobar` | -| `traefik/udp/services/UDPService02/weighted/services/0/weight` | `42` | -| `traefik/udp/services/UDPService02/weighted/services/1/name` | `foobar` | -| `traefik/udp/services/UDPService02/weighted/services/1/weight` | `42` | + +| Key (Path) | Value | +|------------|-------| +| `traefik/http/middlewares/Middleware01/addPrefix/prefix` | `foobar` | +| `traefik/http/middlewares/Middleware02/basicAuth/headerField` | `foobar` | +| `traefik/http/middlewares/Middleware02/basicAuth/realm` | `foobar` | +| `traefik/http/middlewares/Middleware02/basicAuth/removeHeader` | `true` | +| `traefik/http/middlewares/Middleware02/basicAuth/users/0` | `foobar` | +| `traefik/http/middlewares/Middleware02/basicAuth/users/1` | `foobar` | +| `traefik/http/middlewares/Middleware02/basicAuth/usersFile` | `foobar` | +| `traefik/http/middlewares/Middleware03/buffering/maxRequestBodyBytes` | `42` | +| `traefik/http/middlewares/Middleware03/buffering/maxResponseBodyBytes` | `42` | +| `traefik/http/middlewares/Middleware03/buffering/memRequestBodyBytes` | `42` | +| `traefik/http/middlewares/Middleware03/buffering/memResponseBodyBytes` | `42` | +| `traefik/http/middlewares/Middleware03/buffering/retryExpression` | `foobar` | +| `traefik/http/middlewares/Middleware04/chain/middlewares/0` | `foobar` | +| `traefik/http/middlewares/Middleware04/chain/middlewares/1` | `foobar` | +| `traefik/http/middlewares/Middleware05/circuitBreaker/checkPeriod` | `42s` | +| `traefik/http/middlewares/Middleware05/circuitBreaker/expression` | `foobar` | +| `traefik/http/middlewares/Middleware05/circuitBreaker/fallbackDuration` | `42s` | +| `traefik/http/middlewares/Middleware05/circuitBreaker/recoveryDuration` | `42s` | +| `traefik/http/middlewares/Middleware05/circuitBreaker/responseCode` | `42` | +| `traefik/http/middlewares/Middleware06/compress/defaultEncoding` | `foobar` | +| `traefik/http/middlewares/Middleware06/compress/encodings/0` | `foobar` | +| `traefik/http/middlewares/Middleware06/compress/encodings/1` | `foobar` | +| `traefik/http/middlewares/Middleware06/compress/excludedContentTypes/0` | `foobar` | +| `traefik/http/middlewares/Middleware06/compress/excludedContentTypes/1` | `foobar` | +| `traefik/http/middlewares/Middleware06/compress/includedContentTypes/0` | `foobar` | +| `traefik/http/middlewares/Middleware06/compress/includedContentTypes/1` | `foobar` | +| `traefik/http/middlewares/Middleware06/compress/minResponseBodyBytes` | `42` | +| `traefik/http/middlewares/Middleware07/contentType/autoDetect` | `true` | +| `traefik/http/middlewares/Middleware08/digestAuth/headerField` | `foobar` | +| `traefik/http/middlewares/Middleware08/digestAuth/realm` | `foobar` | +| `traefik/http/middlewares/Middleware08/digestAuth/removeHeader` | `true` | +| `traefik/http/middlewares/Middleware08/digestAuth/users/0` | `foobar` | +| `traefik/http/middlewares/Middleware08/digestAuth/users/1` | `foobar` | +| `traefik/http/middlewares/Middleware08/digestAuth/usersFile` | `foobar` | +| `traefik/http/middlewares/Middleware09/errors/query` | `foobar` | +| `traefik/http/middlewares/Middleware09/errors/service` | `foobar` | +| `traefik/http/middlewares/Middleware09/errors/status/0` | `foobar` | +| `traefik/http/middlewares/Middleware09/errors/status/1` | `foobar` | +| `traefik/http/middlewares/Middleware09/errors/statusRewrites/name0` | `42` | +| `traefik/http/middlewares/Middleware09/errors/statusRewrites/name1` | `42` | +| `traefik/http/middlewares/Middleware10/forwardAuth/addAuthCookiesToResponse/0` | `foobar` | +| `traefik/http/middlewares/Middleware10/forwardAuth/addAuthCookiesToResponse/1` | `foobar` | +| `traefik/http/middlewares/Middleware10/forwardAuth/address` | `foobar` | +| `traefik/http/middlewares/Middleware10/forwardAuth/authRequestHeaders/0` | `foobar` | +| `traefik/http/middlewares/Middleware10/forwardAuth/authRequestHeaders/1` | `foobar` | +| `traefik/http/middlewares/Middleware10/forwardAuth/authResponseHeaders/0` | `foobar` | +| `traefik/http/middlewares/Middleware10/forwardAuth/authResponseHeaders/1` | `foobar` | +| `traefik/http/middlewares/Middleware10/forwardAuth/authResponseHeadersRegex` | `foobar` | +| `traefik/http/middlewares/Middleware10/forwardAuth/forwardBody` | `true` | +| `traefik/http/middlewares/Middleware10/forwardAuth/headerField` | `foobar` | +| `traefik/http/middlewares/Middleware10/forwardAuth/maxBodySize` | `42` | +| `traefik/http/middlewares/Middleware10/forwardAuth/preserveLocationHeader` | `true` | +| `traefik/http/middlewares/Middleware10/forwardAuth/preserveRequestMethod` | `true` | +| `traefik/http/middlewares/Middleware10/forwardAuth/tls/ca` | `foobar` | +| `traefik/http/middlewares/Middleware10/forwardAuth/tls/caOptional` | `true` | +| `traefik/http/middlewares/Middleware10/forwardAuth/tls/cert` | `foobar` | +| `traefik/http/middlewares/Middleware10/forwardAuth/tls/insecureSkipVerify` | `true` | +| `traefik/http/middlewares/Middleware10/forwardAuth/tls/key` | `foobar` | +| `traefik/http/middlewares/Middleware10/forwardAuth/trustForwardHeader` | `true` | +| `traefik/http/middlewares/Middleware11/grpcWeb/allowOrigins/0` | `foobar` | +| `traefik/http/middlewares/Middleware11/grpcWeb/allowOrigins/1` | `foobar` | +| `traefik/http/middlewares/Middleware12/headers/accessControlAllowCredentials` | `true` | +| `traefik/http/middlewares/Middleware12/headers/accessControlAllowHeaders/0` | `foobar` | +| `traefik/http/middlewares/Middleware12/headers/accessControlAllowHeaders/1` | `foobar` | +| `traefik/http/middlewares/Middleware12/headers/accessControlAllowMethods/0` | `foobar` | +| `traefik/http/middlewares/Middleware12/headers/accessControlAllowMethods/1` | `foobar` | +| `traefik/http/middlewares/Middleware12/headers/accessControlAllowOriginList/0` | `foobar` | +| `traefik/http/middlewares/Middleware12/headers/accessControlAllowOriginList/1` | `foobar` | +| `traefik/http/middlewares/Middleware12/headers/accessControlAllowOriginListRegex/0` | `foobar` | +| `traefik/http/middlewares/Middleware12/headers/accessControlAllowOriginListRegex/1` | `foobar` | +| `traefik/http/middlewares/Middleware12/headers/accessControlExposeHeaders/0` | `foobar` | +| `traefik/http/middlewares/Middleware12/headers/accessControlExposeHeaders/1` | `foobar` | +| `traefik/http/middlewares/Middleware12/headers/accessControlMaxAge` | `42` | +| `traefik/http/middlewares/Middleware12/headers/addVaryHeader` | `true` | +| `traefik/http/middlewares/Middleware12/headers/allowedHosts/0` | `foobar` | +| `traefik/http/middlewares/Middleware12/headers/allowedHosts/1` | `foobar` | +| `traefik/http/middlewares/Middleware12/headers/browserXssFilter` | `true` | +| `traefik/http/middlewares/Middleware12/headers/contentSecurityPolicy` | `foobar` | +| `traefik/http/middlewares/Middleware12/headers/contentSecurityPolicyReportOnly` | `foobar` | +| `traefik/http/middlewares/Middleware12/headers/contentTypeNosniff` | `true` | +| `traefik/http/middlewares/Middleware12/headers/customBrowserXSSValue` | `foobar` | +| `traefik/http/middlewares/Middleware12/headers/customFrameOptionsValue` | `foobar` | +| `traefik/http/middlewares/Middleware12/headers/customRequestHeaders/name0` | `foobar` | +| `traefik/http/middlewares/Middleware12/headers/customRequestHeaders/name1` | `foobar` | +| `traefik/http/middlewares/Middleware12/headers/customResponseHeaders/name0` | `foobar` | +| `traefik/http/middlewares/Middleware12/headers/customResponseHeaders/name1` | `foobar` | +| `traefik/http/middlewares/Middleware12/headers/featurePolicy` | `foobar` | +| `traefik/http/middlewares/Middleware12/headers/forceSTSHeader` | `true` | +| `traefik/http/middlewares/Middleware12/headers/frameDeny` | `true` | +| `traefik/http/middlewares/Middleware12/headers/hostsProxyHeaders/0` | `foobar` | +| `traefik/http/middlewares/Middleware12/headers/hostsProxyHeaders/1` | `foobar` | +| `traefik/http/middlewares/Middleware12/headers/isDevelopment` | `true` | +| `traefik/http/middlewares/Middleware12/headers/permissionsPolicy` | `foobar` | +| `traefik/http/middlewares/Middleware12/headers/publicKey` | `foobar` | +| `traefik/http/middlewares/Middleware12/headers/referrerPolicy` | `foobar` | +| `traefik/http/middlewares/Middleware12/headers/sslForceHost` | `true` | +| `traefik/http/middlewares/Middleware12/headers/sslHost` | `foobar` | +| `traefik/http/middlewares/Middleware12/headers/sslProxyHeaders/name0` | `foobar` | +| `traefik/http/middlewares/Middleware12/headers/sslProxyHeaders/name1` | `foobar` | +| `traefik/http/middlewares/Middleware12/headers/sslRedirect` | `true` | +| `traefik/http/middlewares/Middleware12/headers/sslTemporaryRedirect` | `true` | +| `traefik/http/middlewares/Middleware12/headers/stsIncludeSubdomains` | `true` | +| `traefik/http/middlewares/Middleware12/headers/stsPreload` | `true` | +| `traefik/http/middlewares/Middleware12/headers/stsSeconds` | `42` | +| `traefik/http/middlewares/Middleware13/ipAllowList/ipStrategy/depth` | `42` | +| `traefik/http/middlewares/Middleware13/ipAllowList/ipStrategy/excludedIPs/0` | `foobar` | +| `traefik/http/middlewares/Middleware13/ipAllowList/ipStrategy/excludedIPs/1` | `foobar` | +| `traefik/http/middlewares/Middleware13/ipAllowList/ipStrategy/ipv6Subnet` | `42` | +| `traefik/http/middlewares/Middleware13/ipAllowList/rejectStatusCode` | `42` | +| `traefik/http/middlewares/Middleware13/ipAllowList/sourceRange/0` | `foobar` | +| `traefik/http/middlewares/Middleware13/ipAllowList/sourceRange/1` | `foobar` | +| `traefik/http/middlewares/Middleware14/ipWhiteList/ipStrategy/depth` | `42` | +| `traefik/http/middlewares/Middleware14/ipWhiteList/ipStrategy/excludedIPs/0` | `foobar` | +| `traefik/http/middlewares/Middleware14/ipWhiteList/ipStrategy/excludedIPs/1` | `foobar` | +| `traefik/http/middlewares/Middleware14/ipWhiteList/ipStrategy/ipv6Subnet` | `42` | +| `traefik/http/middlewares/Middleware14/ipWhiteList/sourceRange/0` | `foobar` | +| `traefik/http/middlewares/Middleware14/ipWhiteList/sourceRange/1` | `foobar` | +| `traefik/http/middlewares/Middleware15/inFlightReq/amount` | `42` | +| `traefik/http/middlewares/Middleware15/inFlightReq/sourceCriterion/ipStrategy/depth` | `42` | +| `traefik/http/middlewares/Middleware15/inFlightReq/sourceCriterion/ipStrategy/excludedIPs/0` | `foobar` | +| `traefik/http/middlewares/Middleware15/inFlightReq/sourceCriterion/ipStrategy/excludedIPs/1` | `foobar` | +| `traefik/http/middlewares/Middleware15/inFlightReq/sourceCriterion/ipStrategy/ipv6Subnet` | `42` | +| `traefik/http/middlewares/Middleware15/inFlightReq/sourceCriterion/requestHeaderName` | `foobar` | +| `traefik/http/middlewares/Middleware15/inFlightReq/sourceCriterion/requestHost` | `true` | +| `traefik/http/middlewares/Middleware16/passTLSClientCert/info/issuer/commonName` | `true` | +| `traefik/http/middlewares/Middleware16/passTLSClientCert/info/issuer/country` | `true` | +| `traefik/http/middlewares/Middleware16/passTLSClientCert/info/issuer/domainComponent` | `true` | +| `traefik/http/middlewares/Middleware16/passTLSClientCert/info/issuer/locality` | `true` | +| `traefik/http/middlewares/Middleware16/passTLSClientCert/info/issuer/organization` | `true` | +| `traefik/http/middlewares/Middleware16/passTLSClientCert/info/issuer/province` | `true` | +| `traefik/http/middlewares/Middleware16/passTLSClientCert/info/issuer/serialNumber` | `true` | +| `traefik/http/middlewares/Middleware16/passTLSClientCert/info/notAfter` | `true` | +| `traefik/http/middlewares/Middleware16/passTLSClientCert/info/notBefore` | `true` | +| `traefik/http/middlewares/Middleware16/passTLSClientCert/info/sans` | `true` | +| `traefik/http/middlewares/Middleware16/passTLSClientCert/info/serialNumber` | `true` | +| `traefik/http/middlewares/Middleware16/passTLSClientCert/info/subject/commonName` | `true` | +| `traefik/http/middlewares/Middleware16/passTLSClientCert/info/subject/country` | `true` | +| `traefik/http/middlewares/Middleware16/passTLSClientCert/info/subject/domainComponent` | `true` | +| `traefik/http/middlewares/Middleware16/passTLSClientCert/info/subject/locality` | `true` | +| `traefik/http/middlewares/Middleware16/passTLSClientCert/info/subject/organization` | `true` | +| `traefik/http/middlewares/Middleware16/passTLSClientCert/info/subject/organizationalUnit` | `true` | +| `traefik/http/middlewares/Middleware16/passTLSClientCert/info/subject/province` | `true` | +| `traefik/http/middlewares/Middleware16/passTLSClientCert/info/subject/serialNumber` | `true` | +| `traefik/http/middlewares/Middleware16/passTLSClientCert/pem` | `true` | +| `traefik/http/middlewares/Middleware17/plugin/PluginConf0/name0` | `foobar` | +| `traefik/http/middlewares/Middleware17/plugin/PluginConf0/name1` | `foobar` | +| `traefik/http/middlewares/Middleware17/plugin/PluginConf1/name0` | `foobar` | +| `traefik/http/middlewares/Middleware17/plugin/PluginConf1/name1` | `foobar` | +| `traefik/http/middlewares/Middleware18/rateLimit/average` | `42` | +| `traefik/http/middlewares/Middleware18/rateLimit/burst` | `42` | +| `traefik/http/middlewares/Middleware18/rateLimit/period` | `42s` | +| `traefik/http/middlewares/Middleware18/rateLimit/redis/db` | `42` | +| `traefik/http/middlewares/Middleware18/rateLimit/redis/dialTimeout` | `42s` | +| `traefik/http/middlewares/Middleware18/rateLimit/redis/endpoints/0` | `foobar` | +| `traefik/http/middlewares/Middleware18/rateLimit/redis/endpoints/1` | `foobar` | +| `traefik/http/middlewares/Middleware18/rateLimit/redis/maxActiveConns` | `42` | +| `traefik/http/middlewares/Middleware18/rateLimit/redis/minIdleConns` | `42` | +| `traefik/http/middlewares/Middleware18/rateLimit/redis/password` | `foobar` | +| `traefik/http/middlewares/Middleware18/rateLimit/redis/poolSize` | `42` | +| `traefik/http/middlewares/Middleware18/rateLimit/redis/readTimeout` | `42s` | +| `traefik/http/middlewares/Middleware18/rateLimit/redis/tls/ca` | `foobar` | +| `traefik/http/middlewares/Middleware18/rateLimit/redis/tls/cert` | `foobar` | +| `traefik/http/middlewares/Middleware18/rateLimit/redis/tls/insecureSkipVerify` | `true` | +| `traefik/http/middlewares/Middleware18/rateLimit/redis/tls/key` | `foobar` | +| `traefik/http/middlewares/Middleware18/rateLimit/redis/username` | `foobar` | +| `traefik/http/middlewares/Middleware18/rateLimit/redis/writeTimeout` | `42s` | +| `traefik/http/middlewares/Middleware18/rateLimit/sourceCriterion/ipStrategy/depth` | `42` | +| `traefik/http/middlewares/Middleware18/rateLimit/sourceCriterion/ipStrategy/excludedIPs/0` | `foobar` | +| `traefik/http/middlewares/Middleware18/rateLimit/sourceCriterion/ipStrategy/excludedIPs/1` | `foobar` | +| `traefik/http/middlewares/Middleware18/rateLimit/sourceCriterion/ipStrategy/ipv6Subnet` | `42` | +| `traefik/http/middlewares/Middleware18/rateLimit/sourceCriterion/requestHeaderName` | `foobar` | +| `traefik/http/middlewares/Middleware18/rateLimit/sourceCriterion/requestHost` | `true` | +| `traefik/http/middlewares/Middleware19/redirectRegex/permanent` | `true` | +| `traefik/http/middlewares/Middleware19/redirectRegex/regex` | `foobar` | +| `traefik/http/middlewares/Middleware19/redirectRegex/replacement` | `foobar` | +| `traefik/http/middlewares/Middleware20/redirectScheme/permanent` | `true` | +| `traefik/http/middlewares/Middleware20/redirectScheme/port` | `foobar` | +| `traefik/http/middlewares/Middleware20/redirectScheme/scheme` | `foobar` | +| `traefik/http/middlewares/Middleware21/replacePath/path` | `foobar` | +| `traefik/http/middlewares/Middleware22/replacePathRegex/regex` | `foobar` | +| `traefik/http/middlewares/Middleware22/replacePathRegex/replacement` | `foobar` | +| `traefik/http/middlewares/Middleware23/retry/attempts` | `42` | +| `traefik/http/middlewares/Middleware23/retry/initialInterval` | `42s` | +| `traefik/http/middlewares/Middleware24/stripPrefix/forceSlash` | `true` | +| `traefik/http/middlewares/Middleware24/stripPrefix/prefixes/0` | `foobar` | +| `traefik/http/middlewares/Middleware24/stripPrefix/prefixes/1` | `foobar` | +| `traefik/http/middlewares/Middleware25/stripPrefixRegex/regex/0` | `foobar` | +| `traefik/http/middlewares/Middleware25/stripPrefixRegex/regex/1` | `foobar` | +| `traefik/http/routers/Router0/entryPoints/0` | `foobar` | +| `traefik/http/routers/Router0/entryPoints/1` | `foobar` | +| `traefik/http/routers/Router0/middlewares/0` | `foobar` | +| `traefik/http/routers/Router0/middlewares/1` | `foobar` | +| `traefik/http/routers/Router0/observability/accessLogs` | `true` | +| `traefik/http/routers/Router0/observability/metrics` | `true` | +| `traefik/http/routers/Router0/observability/traceVerbosity` | `foobar` | +| `traefik/http/routers/Router0/observability/tracing` | `true` | +| `traefik/http/routers/Router0/priority` | `42` | +| `traefik/http/routers/Router0/rule` | `foobar` | +| `traefik/http/routers/Router0/ruleSyntax` | `foobar` | +| `traefik/http/routers/Router0/service` | `foobar` | +| `traefik/http/routers/Router0/tls/certResolver` | `foobar` | +| `traefik/http/routers/Router0/tls/domains/0/main` | `foobar` | +| `traefik/http/routers/Router0/tls/domains/0/sans/0` | `foobar` | +| `traefik/http/routers/Router0/tls/domains/0/sans/1` | `foobar` | +| `traefik/http/routers/Router0/tls/domains/1/main` | `foobar` | +| `traefik/http/routers/Router0/tls/domains/1/sans/0` | `foobar` | +| `traefik/http/routers/Router0/tls/domains/1/sans/1` | `foobar` | +| `traefik/http/routers/Router0/tls/options` | `foobar` | +| `traefik/http/routers/Router1/entryPoints/0` | `foobar` | +| `traefik/http/routers/Router1/entryPoints/1` | `foobar` | +| `traefik/http/routers/Router1/middlewares/0` | `foobar` | +| `traefik/http/routers/Router1/middlewares/1` | `foobar` | +| `traefik/http/routers/Router1/observability/accessLogs` | `true` | +| `traefik/http/routers/Router1/observability/metrics` | `true` | +| `traefik/http/routers/Router1/observability/traceVerbosity` | `foobar` | +| `traefik/http/routers/Router1/observability/tracing` | `true` | +| `traefik/http/routers/Router1/priority` | `42` | +| `traefik/http/routers/Router1/rule` | `foobar` | +| `traefik/http/routers/Router1/ruleSyntax` | `foobar` | +| `traefik/http/routers/Router1/service` | `foobar` | +| `traefik/http/routers/Router1/tls/certResolver` | `foobar` | +| `traefik/http/routers/Router1/tls/domains/0/main` | `foobar` | +| `traefik/http/routers/Router1/tls/domains/0/sans/0` | `foobar` | +| `traefik/http/routers/Router1/tls/domains/0/sans/1` | `foobar` | +| `traefik/http/routers/Router1/tls/domains/1/main` | `foobar` | +| `traefik/http/routers/Router1/tls/domains/1/sans/0` | `foobar` | +| `traefik/http/routers/Router1/tls/domains/1/sans/1` | `foobar` | +| `traefik/http/routers/Router1/tls/options` | `foobar` | +| `traefik/http/serversTransports/ServersTransport0/certificates/0/certFile` | `foobar` | +| `traefik/http/serversTransports/ServersTransport0/certificates/0/keyFile` | `foobar` | +| `traefik/http/serversTransports/ServersTransport0/certificates/1/certFile` | `foobar` | +| `traefik/http/serversTransports/ServersTransport0/certificates/1/keyFile` | `foobar` | +| `traefik/http/serversTransports/ServersTransport0/disableHTTP2` | `true` | +| `traefik/http/serversTransports/ServersTransport0/forwardingTimeouts/dialTimeout` | `42s` | +| `traefik/http/serversTransports/ServersTransport0/forwardingTimeouts/idleConnTimeout` | `42s` | +| `traefik/http/serversTransports/ServersTransport0/forwardingTimeouts/pingTimeout` | `42s` | +| `traefik/http/serversTransports/ServersTransport0/forwardingTimeouts/readIdleTimeout` | `42s` | +| `traefik/http/serversTransports/ServersTransport0/forwardingTimeouts/responseHeaderTimeout` | `42s` | +| `traefik/http/serversTransports/ServersTransport0/insecureSkipVerify` | `true` | +| `traefik/http/serversTransports/ServersTransport0/maxIdleConnsPerHost` | `42` | +| `traefik/http/serversTransports/ServersTransport0/peerCertURI` | `foobar` | +| `traefik/http/serversTransports/ServersTransport0/rootCAs/0` | `foobar` | +| `traefik/http/serversTransports/ServersTransport0/rootCAs/1` | `foobar` | +| `traefik/http/serversTransports/ServersTransport0/serverName` | `foobar` | +| `traefik/http/serversTransports/ServersTransport0/spiffe/ids/0` | `foobar` | +| `traefik/http/serversTransports/ServersTransport0/spiffe/ids/1` | `foobar` | +| `traefik/http/serversTransports/ServersTransport0/spiffe/trustDomain` | `foobar` | +| `traefik/http/serversTransports/ServersTransport1/certificates/0/certFile` | `foobar` | +| `traefik/http/serversTransports/ServersTransport1/certificates/0/keyFile` | `foobar` | +| `traefik/http/serversTransports/ServersTransport1/certificates/1/certFile` | `foobar` | +| `traefik/http/serversTransports/ServersTransport1/certificates/1/keyFile` | `foobar` | +| `traefik/http/serversTransports/ServersTransport1/disableHTTP2` | `true` | +| `traefik/http/serversTransports/ServersTransport1/forwardingTimeouts/dialTimeout` | `42s` | +| `traefik/http/serversTransports/ServersTransport1/forwardingTimeouts/idleConnTimeout` | `42s` | +| `traefik/http/serversTransports/ServersTransport1/forwardingTimeouts/pingTimeout` | `42s` | +| `traefik/http/serversTransports/ServersTransport1/forwardingTimeouts/readIdleTimeout` | `42s` | +| `traefik/http/serversTransports/ServersTransport1/forwardingTimeouts/responseHeaderTimeout` | `42s` | +| `traefik/http/serversTransports/ServersTransport1/insecureSkipVerify` | `true` | +| `traefik/http/serversTransports/ServersTransport1/maxIdleConnsPerHost` | `42` | +| `traefik/http/serversTransports/ServersTransport1/peerCertURI` | `foobar` | +| `traefik/http/serversTransports/ServersTransport1/rootCAs/0` | `foobar` | +| `traefik/http/serversTransports/ServersTransport1/rootCAs/1` | `foobar` | +| `traefik/http/serversTransports/ServersTransport1/serverName` | `foobar` | +| `traefik/http/serversTransports/ServersTransport1/spiffe/ids/0` | `foobar` | +| `traefik/http/serversTransports/ServersTransport1/spiffe/ids/1` | `foobar` | +| `traefik/http/serversTransports/ServersTransport1/spiffe/trustDomain` | `foobar` | +| `traefik/http/services/Service01/failover/fallback` | `foobar` | +| `traefik/http/services/Service01/failover/healthCheck` | `` | +| `traefik/http/services/Service01/failover/service` | `foobar` | +| `traefik/http/services/Service02/loadBalancer/healthCheck/followRedirects` | `true` | +| `traefik/http/services/Service02/loadBalancer/healthCheck/headers/name0` | `foobar` | +| `traefik/http/services/Service02/loadBalancer/healthCheck/headers/name1` | `foobar` | +| `traefik/http/services/Service02/loadBalancer/healthCheck/hostname` | `foobar` | +| `traefik/http/services/Service02/loadBalancer/healthCheck/interval` | `42s` | +| `traefik/http/services/Service02/loadBalancer/healthCheck/method` | `foobar` | +| `traefik/http/services/Service02/loadBalancer/healthCheck/mode` | `foobar` | +| `traefik/http/services/Service02/loadBalancer/healthCheck/path` | `foobar` | +| `traefik/http/services/Service02/loadBalancer/healthCheck/port` | `42` | +| `traefik/http/services/Service02/loadBalancer/healthCheck/scheme` | `foobar` | +| `traefik/http/services/Service02/loadBalancer/healthCheck/status` | `42` | +| `traefik/http/services/Service02/loadBalancer/healthCheck/timeout` | `42s` | +| `traefik/http/services/Service02/loadBalancer/healthCheck/unhealthyInterval` | `42s` | +| `traefik/http/services/Service02/loadBalancer/passHostHeader` | `true` | +| `traefik/http/services/Service02/loadBalancer/responseForwarding/flushInterval` | `42s` | +| `traefik/http/services/Service02/loadBalancer/servers/0/preservePath` | `true` | +| `traefik/http/services/Service02/loadBalancer/servers/0/url` | `foobar` | +| `traefik/http/services/Service02/loadBalancer/servers/0/weight` | `42` | +| `traefik/http/services/Service02/loadBalancer/servers/1/preservePath` | `true` | +| `traefik/http/services/Service02/loadBalancer/servers/1/url` | `foobar` | +| `traefik/http/services/Service02/loadBalancer/servers/1/weight` | `42` | +| `traefik/http/services/Service02/loadBalancer/serversTransport` | `foobar` | +| `traefik/http/services/Service02/loadBalancer/sticky/cookie/domain` | `foobar` | +| `traefik/http/services/Service02/loadBalancer/sticky/cookie/httpOnly` | `true` | +| `traefik/http/services/Service02/loadBalancer/sticky/cookie/maxAge` | `42` | +| `traefik/http/services/Service02/loadBalancer/sticky/cookie/name` | `foobar` | +| `traefik/http/services/Service02/loadBalancer/sticky/cookie/path` | `foobar` | +| `traefik/http/services/Service02/loadBalancer/sticky/cookie/sameSite` | `foobar` | +| `traefik/http/services/Service02/loadBalancer/sticky/cookie/secure` | `true` | +| `traefik/http/services/Service02/loadBalancer/strategy` | `foobar` | +| `traefik/http/services/Service03/mirroring/healthCheck` | `` | +| `traefik/http/services/Service03/mirroring/maxBodySize` | `42` | +| `traefik/http/services/Service03/mirroring/mirrorBody` | `true` | +| `traefik/http/services/Service03/mirroring/mirrors/0/name` | `foobar` | +| `traefik/http/services/Service03/mirroring/mirrors/0/percent` | `42` | +| `traefik/http/services/Service03/mirroring/mirrors/1/name` | `foobar` | +| `traefik/http/services/Service03/mirroring/mirrors/1/percent` | `42` | +| `traefik/http/services/Service03/mirroring/service` | `foobar` | +| `traefik/http/services/Service04/weighted/healthCheck` | `` | +| `traefik/http/services/Service04/weighted/services/0/name` | `foobar` | +| `traefik/http/services/Service04/weighted/services/0/weight` | `42` | +| `traefik/http/services/Service04/weighted/services/1/name` | `foobar` | +| `traefik/http/services/Service04/weighted/services/1/weight` | `42` | +| `traefik/http/services/Service04/weighted/sticky/cookie/domain` | `foobar` | +| `traefik/http/services/Service04/weighted/sticky/cookie/httpOnly` | `true` | +| `traefik/http/services/Service04/weighted/sticky/cookie/maxAge` | `42` | +| `traefik/http/services/Service04/weighted/sticky/cookie/name` | `foobar` | +| `traefik/http/services/Service04/weighted/sticky/cookie/path` | `foobar` | +| `traefik/http/services/Service04/weighted/sticky/cookie/sameSite` | `foobar` | +| `traefik/http/services/Service04/weighted/sticky/cookie/secure` | `true` | +| `traefik/tcp/middlewares/TCPMiddleware01/ipAllowList/sourceRange/0` | `foobar` | +| `traefik/tcp/middlewares/TCPMiddleware01/ipAllowList/sourceRange/1` | `foobar` | +| `traefik/tcp/middlewares/TCPMiddleware02/ipWhiteList/sourceRange/0` | `foobar` | +| `traefik/tcp/middlewares/TCPMiddleware02/ipWhiteList/sourceRange/1` | `foobar` | +| `traefik/tcp/middlewares/TCPMiddleware03/inFlightConn/amount` | `42` | +| `traefik/tcp/routers/TCPRouter0/entryPoints/0` | `foobar` | +| `traefik/tcp/routers/TCPRouter0/entryPoints/1` | `foobar` | +| `traefik/tcp/routers/TCPRouter0/middlewares/0` | `foobar` | +| `traefik/tcp/routers/TCPRouter0/middlewares/1` | `foobar` | +| `traefik/tcp/routers/TCPRouter0/priority` | `42` | +| `traefik/tcp/routers/TCPRouter0/rule` | `foobar` | +| `traefik/tcp/routers/TCPRouter0/ruleSyntax` | `foobar` | +| `traefik/tcp/routers/TCPRouter0/service` | `foobar` | +| `traefik/tcp/routers/TCPRouter0/tls/certResolver` | `foobar` | +| `traefik/tcp/routers/TCPRouter0/tls/domains/0/main` | `foobar` | +| `traefik/tcp/routers/TCPRouter0/tls/domains/0/sans/0` | `foobar` | +| `traefik/tcp/routers/TCPRouter0/tls/domains/0/sans/1` | `foobar` | +| `traefik/tcp/routers/TCPRouter0/tls/domains/1/main` | `foobar` | +| `traefik/tcp/routers/TCPRouter0/tls/domains/1/sans/0` | `foobar` | +| `traefik/tcp/routers/TCPRouter0/tls/domains/1/sans/1` | `foobar` | +| `traefik/tcp/routers/TCPRouter0/tls/options` | `foobar` | +| `traefik/tcp/routers/TCPRouter0/tls/passthrough` | `true` | +| `traefik/tcp/routers/TCPRouter1/entryPoints/0` | `foobar` | +| `traefik/tcp/routers/TCPRouter1/entryPoints/1` | `foobar` | +| `traefik/tcp/routers/TCPRouter1/middlewares/0` | `foobar` | +| `traefik/tcp/routers/TCPRouter1/middlewares/1` | `foobar` | +| `traefik/tcp/routers/TCPRouter1/priority` | `42` | +| `traefik/tcp/routers/TCPRouter1/rule` | `foobar` | +| `traefik/tcp/routers/TCPRouter1/ruleSyntax` | `foobar` | +| `traefik/tcp/routers/TCPRouter1/service` | `foobar` | +| `traefik/tcp/routers/TCPRouter1/tls/certResolver` | `foobar` | +| `traefik/tcp/routers/TCPRouter1/tls/domains/0/main` | `foobar` | +| `traefik/tcp/routers/TCPRouter1/tls/domains/0/sans/0` | `foobar` | +| `traefik/tcp/routers/TCPRouter1/tls/domains/0/sans/1` | `foobar` | +| `traefik/tcp/routers/TCPRouter1/tls/domains/1/main` | `foobar` | +| `traefik/tcp/routers/TCPRouter1/tls/domains/1/sans/0` | `foobar` | +| `traefik/tcp/routers/TCPRouter1/tls/domains/1/sans/1` | `foobar` | +| `traefik/tcp/routers/TCPRouter1/tls/options` | `foobar` | +| `traefik/tcp/routers/TCPRouter1/tls/passthrough` | `true` | +| `traefik/tcp/serversTransports/TCPServersTransport0/dialKeepAlive` | `42s` | +| `traefik/tcp/serversTransports/TCPServersTransport0/dialTimeout` | `42s` | +| `traefik/tcp/serversTransports/TCPServersTransport0/proxyProtocol/version` | `42` | +| `traefik/tcp/serversTransports/TCPServersTransport0/terminationDelay` | `42s` | +| `traefik/tcp/serversTransports/TCPServersTransport0/tls/certificates/0/certFile` | `foobar` | +| `traefik/tcp/serversTransports/TCPServersTransport0/tls/certificates/0/keyFile` | `foobar` | +| `traefik/tcp/serversTransports/TCPServersTransport0/tls/certificates/1/certFile` | `foobar` | +| `traefik/tcp/serversTransports/TCPServersTransport0/tls/certificates/1/keyFile` | `foobar` | +| `traefik/tcp/serversTransports/TCPServersTransport0/tls/insecureSkipVerify` | `true` | +| `traefik/tcp/serversTransports/TCPServersTransport0/tls/peerCertURI` | `foobar` | +| `traefik/tcp/serversTransports/TCPServersTransport0/tls/rootCAs/0` | `foobar` | +| `traefik/tcp/serversTransports/TCPServersTransport0/tls/rootCAs/1` | `foobar` | +| `traefik/tcp/serversTransports/TCPServersTransport0/tls/serverName` | `foobar` | +| `traefik/tcp/serversTransports/TCPServersTransport0/tls/spiffe/ids/0` | `foobar` | +| `traefik/tcp/serversTransports/TCPServersTransport0/tls/spiffe/ids/1` | `foobar` | +| `traefik/tcp/serversTransports/TCPServersTransport0/tls/spiffe/trustDomain` | `foobar` | +| `traefik/tcp/serversTransports/TCPServersTransport1/dialKeepAlive` | `42s` | +| `traefik/tcp/serversTransports/TCPServersTransport1/dialTimeout` | `42s` | +| `traefik/tcp/serversTransports/TCPServersTransport1/proxyProtocol/version` | `42` | +| `traefik/tcp/serversTransports/TCPServersTransport1/terminationDelay` | `42s` | +| `traefik/tcp/serversTransports/TCPServersTransport1/tls/certificates/0/certFile` | `foobar` | +| `traefik/tcp/serversTransports/TCPServersTransport1/tls/certificates/0/keyFile` | `foobar` | +| `traefik/tcp/serversTransports/TCPServersTransport1/tls/certificates/1/certFile` | `foobar` | +| `traefik/tcp/serversTransports/TCPServersTransport1/tls/certificates/1/keyFile` | `foobar` | +| `traefik/tcp/serversTransports/TCPServersTransport1/tls/insecureSkipVerify` | `true` | +| `traefik/tcp/serversTransports/TCPServersTransport1/tls/peerCertURI` | `foobar` | +| `traefik/tcp/serversTransports/TCPServersTransport1/tls/rootCAs/0` | `foobar` | +| `traefik/tcp/serversTransports/TCPServersTransport1/tls/rootCAs/1` | `foobar` | +| `traefik/tcp/serversTransports/TCPServersTransport1/tls/serverName` | `foobar` | +| `traefik/tcp/serversTransports/TCPServersTransport1/tls/spiffe/ids/0` | `foobar` | +| `traefik/tcp/serversTransports/TCPServersTransport1/tls/spiffe/ids/1` | `foobar` | +| `traefik/tcp/serversTransports/TCPServersTransport1/tls/spiffe/trustDomain` | `foobar` | +| `traefik/tcp/services/TCPService01/loadBalancer/proxyProtocol/version` | `42` | +| `traefik/tcp/services/TCPService01/loadBalancer/servers/0/address` | `foobar` | +| `traefik/tcp/services/TCPService01/loadBalancer/servers/0/tls` | `true` | +| `traefik/tcp/services/TCPService01/loadBalancer/servers/1/address` | `foobar` | +| `traefik/tcp/services/TCPService01/loadBalancer/servers/1/tls` | `true` | +| `traefik/tcp/services/TCPService01/loadBalancer/serversTransport` | `foobar` | +| `traefik/tcp/services/TCPService01/loadBalancer/terminationDelay` | `42` | +| `traefik/tcp/services/TCPService02/weighted/services/0/name` | `foobar` | +| `traefik/tcp/services/TCPService02/weighted/services/0/weight` | `42` | +| `traefik/tcp/services/TCPService02/weighted/services/1/name` | `foobar` | +| `traefik/tcp/services/TCPService02/weighted/services/1/weight` | `42` | +| `traefik/tls/certificates/0/certFile` | `foobar` | +| `traefik/tls/certificates/0/keyFile` | `foobar` | +| `traefik/tls/certificates/0/stores/0` | `foobar` | +| `traefik/tls/certificates/0/stores/1` | `foobar` | +| `traefik/tls/certificates/1/certFile` | `foobar` | +| `traefik/tls/certificates/1/keyFile` | `foobar` | +| `traefik/tls/certificates/1/stores/0` | `foobar` | +| `traefik/tls/certificates/1/stores/1` | `foobar` | +| `traefik/tls/options/Options0/alpnProtocols/0` | `foobar` | +| `traefik/tls/options/Options0/alpnProtocols/1` | `foobar` | +| `traefik/tls/options/Options0/cipherSuites/0` | `foobar` | +| `traefik/tls/options/Options0/cipherSuites/1` | `foobar` | +| `traefik/tls/options/Options0/clientAuth/caFiles/0` | `foobar` | +| `traefik/tls/options/Options0/clientAuth/caFiles/1` | `foobar` | +| `traefik/tls/options/Options0/clientAuth/clientAuthType` | `foobar` | +| `traefik/tls/options/Options0/curvePreferences/0` | `foobar` | +| `traefik/tls/options/Options0/curvePreferences/1` | `foobar` | +| `traefik/tls/options/Options0/disableSessionTickets` | `true` | +| `traefik/tls/options/Options0/maxVersion` | `foobar` | +| `traefik/tls/options/Options0/minVersion` | `foobar` | +| `traefik/tls/options/Options0/preferServerCipherSuites` | `true` | +| `traefik/tls/options/Options0/sniStrict` | `true` | +| `traefik/tls/options/Options1/alpnProtocols/0` | `foobar` | +| `traefik/tls/options/Options1/alpnProtocols/1` | `foobar` | +| `traefik/tls/options/Options1/cipherSuites/0` | `foobar` | +| `traefik/tls/options/Options1/cipherSuites/1` | `foobar` | +| `traefik/tls/options/Options1/clientAuth/caFiles/0` | `foobar` | +| `traefik/tls/options/Options1/clientAuth/caFiles/1` | `foobar` | +| `traefik/tls/options/Options1/clientAuth/clientAuthType` | `foobar` | +| `traefik/tls/options/Options1/curvePreferences/0` | `foobar` | +| `traefik/tls/options/Options1/curvePreferences/1` | `foobar` | +| `traefik/tls/options/Options1/disableSessionTickets` | `true` | +| `traefik/tls/options/Options1/maxVersion` | `foobar` | +| `traefik/tls/options/Options1/minVersion` | `foobar` | +| `traefik/tls/options/Options1/preferServerCipherSuites` | `true` | +| `traefik/tls/options/Options1/sniStrict` | `true` | +| `traefik/tls/stores/Store0/defaultCertificate/certFile` | `foobar` | +| `traefik/tls/stores/Store0/defaultCertificate/keyFile` | `foobar` | +| `traefik/tls/stores/Store0/defaultGeneratedCert/domain/main` | `foobar` | +| `traefik/tls/stores/Store0/defaultGeneratedCert/domain/sans/0` | `foobar` | +| `traefik/tls/stores/Store0/defaultGeneratedCert/domain/sans/1` | `foobar` | +| `traefik/tls/stores/Store0/defaultGeneratedCert/resolver` | `foobar` | +| `traefik/tls/stores/Store1/defaultCertificate/certFile` | `foobar` | +| `traefik/tls/stores/Store1/defaultCertificate/keyFile` | `foobar` | +| `traefik/tls/stores/Store1/defaultGeneratedCert/domain/main` | `foobar` | +| `traefik/tls/stores/Store1/defaultGeneratedCert/domain/sans/0` | `foobar` | +| `traefik/tls/stores/Store1/defaultGeneratedCert/domain/sans/1` | `foobar` | +| `traefik/tls/stores/Store1/defaultGeneratedCert/resolver` | `foobar` | +| `traefik/udp/routers/UDPRouter0/entryPoints/0` | `foobar` | +| `traefik/udp/routers/UDPRouter0/entryPoints/1` | `foobar` | +| `traefik/udp/routers/UDPRouter0/service` | `foobar` | +| `traefik/udp/routers/UDPRouter1/entryPoints/0` | `foobar` | +| `traefik/udp/routers/UDPRouter1/entryPoints/1` | `foobar` | +| `traefik/udp/routers/UDPRouter1/service` | `foobar` | +| `traefik/udp/services/UDPService01/loadBalancer/servers/0/address` | `foobar` | +| `traefik/udp/services/UDPService01/loadBalancer/servers/1/address` | `foobar` | +| `traefik/udp/services/UDPService02/weighted/services/0/name` | `foobar` | +| `traefik/udp/services/UDPService02/weighted/services/0/weight` | `42` | +| `traefik/udp/services/UDPService02/weighted/services/1/name` | `foobar` | +| `traefik/udp/services/UDPService02/weighted/services/1/weight` | `42` | diff --git a/docs/content/reference/dynamic-configuration/kv.md b/docs/content/reference/dynamic-configuration/kv.md deleted file mode 100644 index 8db8415e5..000000000 --- a/docs/content/reference/dynamic-configuration/kv.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -title: "Traefik Dynamic Configuration with KV stores" -description: "Read the technical documentation to learn the Traefik Dynamic Configuration with KV stores." ---- - -# KV Configuration Reference - -Dynamic configuration with KV stores. -{: .subtitle } - -| Key (Path) | Value | -|----------------------------------------------------------------------------------------------|-------------| ---8<-- "content/reference/dynamic-configuration/kv-ref.md" diff --git a/docs/content/reference/dynamic-configuration/nomad.md b/docs/content/reference/dynamic-configuration/nomad.md deleted file mode 100644 index 680e621b4..000000000 --- a/docs/content/reference/dynamic-configuration/nomad.md +++ /dev/null @@ -1,16 +0,0 @@ ---- -title: "Traefik Nomad Service Discovery Configuration Documentation" -description: "View the reference for performing dynamic configurations with Traefik Proxy and Nomad Service Discovery. Read the technical documentation." ---- - -# Nomad Service Discovery Configuration Reference - -Dynamic configuration with Nomad Service Discovery -{: .subtitle } - -The labels are case-insensitive. - -```yaml ---8<-- "content/reference/dynamic-configuration/nomad.yml" ---8<-- "content/reference/dynamic-configuration/docker-labels.yml" -``` diff --git a/docs/content/reference/dynamic-configuration/nomad.yml b/docs/content/reference/dynamic-configuration/nomad.yml deleted file mode 100644 index 23efc00c6..000000000 --- a/docs/content/reference/dynamic-configuration/nomad.yml +++ /dev/null @@ -1 +0,0 @@ -- "traefik.enable=true" diff --git a/docs/content/reference/dynamic-configuration/rancher.md b/docs/content/reference/dynamic-configuration/rancher.md deleted file mode 100644 index e69de29bb..000000000 diff --git a/docs/content/reference/dynamic-configuration/swarm.md b/docs/content/reference/dynamic-configuration/swarm.md deleted file mode 100644 index 67fec341c..000000000 --- a/docs/content/reference/dynamic-configuration/swarm.md +++ /dev/null @@ -1,17 +0,0 @@ ---- -title: "Traefik Docker Swarm Configuration Documentation" -description: "Reference dynamic configuration with Docker Swarm labels in Traefik Proxy. Read the technical documentation." ---- - -# Docker Swarm Configuration Reference - -Dynamic configuration with Docker Labels -{: .subtitle } - -The labels are case-insensitive. - -```yaml -labels: - --8<-- "content/reference/dynamic-configuration/swarm.yml" - --8<-- "content/reference/dynamic-configuration/docker-labels.yml" -``` diff --git a/docs/content/reference/dynamic-configuration/swarm.yml b/docs/content/reference/dynamic-configuration/swarm.yml deleted file mode 100644 index 1b40b4483..000000000 --- a/docs/content/reference/dynamic-configuration/swarm.yml +++ /dev/null @@ -1,3 +0,0 @@ -- "traefik.enable=true" -- "traefik.swarm.network=foobar" -- "traefik.swarm.lbswarm=true" diff --git a/docs/content/reference/dynamic-configuration/traefik.containo.us_tlsoptions.yaml b/docs/content/reference/dynamic-configuration/traefik.containo.us_tlsoptions.yaml new file mode 100644 index 000000000..6c7fdc914 --- /dev/null +++ b/docs/content/reference/dynamic-configuration/traefik.containo.us_tlsoptions.yaml @@ -0,0 +1,114 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.16.1 + name: tlsoptions.traefik.containo.us +spec: + group: traefik.containo.us + names: + kind: TLSOption + listKind: TLSOptionList + plural: tlsoptions + singular: tlsoption + scope: Namespaced + versions: + - name: v1alpha1 + schema: + 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.11/https/tls/#tls-options + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: TLSOptionSpec defines the desired state of a TLSOption. + properties: + 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.11/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.11/https/tls/#cipher-suites + items: + type: string + type: array + clientAuth: + description: ClientAuth defines the server's policy for TLS Client + Authentication. + properties: + clientAuthType: + description: ClientAuthType defines the client authentication + type to apply. + enum: + - NoClientCert + - RequestClientCert + - RequireAnyClientCert + - VerifyClientCertIfGiven + - RequireAndVerifyClientCert + type: string + secretNames: + description: SecretNames defines the names of the referenced Kubernetes + Secret storing certificate details. + items: + type: string + type: array + type: object + curvePreferences: + description: |- + CurvePreferences defines the preferred elliptic curves. + More info: https://doc.traefik.io/traefik/v2.11/https/tls/#curve-preferences + items: + type: string + type: array + maxVersion: + description: |- + MaxVersion defines the maximum TLS version that Traefik will accept. + Possible values: VersionTLS10, VersionTLS11, VersionTLS12, VersionTLS13. + Default: None. + type: string + minVersion: + description: |- + MinVersion defines the minimum TLS version that Traefik will accept. + Possible values: VersionTLS10, VersionTLS11, VersionTLS12, VersionTLS13. + Default: VersionTLS10. + type: string + preferServerCipherSuites: + description: |- + PreferServerCipherSuites defines whether the server chooses a cipher suite among his own instead of among the client's. + It is enabled automatically when minVersion or maxVersion is set. + Deprecated: https://github.com/golang/go/issues/45430 + type: boolean + sniStrict: + description: SniStrict defines whether Traefik allows connections + from clients connections that do not specify a server_name extension. + type: boolean + type: object + required: + - metadata + - spec + type: object + served: true + storage: true diff --git a/docs/content/reference/dynamic-configuration/traefik.io_ingressroutes.yaml b/docs/content/reference/dynamic-configuration/traefik.io_ingressroutes.yaml index 160703eb8..7c40866ea 100644 --- a/docs/content/reference/dynamic-configuration/traefik.io_ingressroutes.yaml +++ b/docs/content/reference/dynamic-configuration/traefik.io_ingressroutes.yaml @@ -43,7 +43,7 @@ spec: 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/v3.4/routing/entrypoints/ + More info: https://doc.traefik.io/traefik/v3.5/reference/install-configuration/entrypoints/ Default: all. items: type: string @@ -64,12 +64,12 @@ spec: match: description: |- Match defines the router's rule. - More info: https://doc.traefik.io/traefik/v3.4/routing/routers/#rule + More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/routing/rules-and-priority/ type: string middlewares: description: |- Middlewares defines the list of references to Middleware resources. - More info: https://doc.traefik.io/traefik/v3.4/routing/providers/kubernetes-crd/#kind-middleware + More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/kubernetes/crd/http/middleware/ items: description: MiddlewareRef is a reference to a Middleware resource. @@ -89,19 +89,30 @@ spec: observability: description: |- Observability defines the observability configuration for a router. - More info: https://doc.traefik.io/traefik/v3.4/routing/routers/#observability + More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/routing/observability/ properties: accessLogs: + description: AccessLogs enables access logs for this router. type: boolean metrics: + description: Metrics enables metrics for this router. type: boolean + traceVerbosity: + default: minimal + description: TraceVerbosity defines the verbosity level + of the tracing for this router. + enum: + - minimal + - detailed + type: string tracing: + description: Tracing enables tracing for this router. type: boolean type: object priority: description: |- Priority defines the router's priority. - More info: https://doc.traefik.io/traefik/v3.4/routing/routers/#priority + More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/routing/rules-and-priority/#priority maximum: 9223372036854775000 type: integer services: @@ -136,7 +147,7 @@ spec: - type: integer - type: string description: |- - Interval defines the frequency of the health check calls. + Interval defines the frequency of the health check calls for healthy targets. Default: 30s x-kubernetes-int-or-string: true method: @@ -172,6 +183,15 @@ spec: Timeout defines the maximum duration Traefik will wait for a health check request before considering the server unhealthy. Default: 5s x-kubernetes-int-or-string: true + unhealthyInterval: + anyOf: + - type: integer + - type: string + description: |- + UnhealthyInterval defines the frequency of the health check calls for unhealthy targets. + When UnhealthyInterval is not defined, it defaults to the Interval value. + Default: 30s + x-kubernetes-int-or-string: true type: object kind: description: Kind defines the kind of the Service. @@ -243,7 +263,7 @@ spec: sticky: description: |- Sticky defines the sticky sessions configuration. - More info: https://doc.traefik.io/traefik/v3.4/routing/services/#sticky-sessions + More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/load-balancing/service/#sticky-sessions properties: cookie: description: Cookie defines the sticky cookie configuration. @@ -312,7 +332,7 @@ spec: syntax: description: |- Syntax defines the router's rule syntax. - More info: https://doc.traefik.io/traefik/v3.4/routing/routers/#rulesyntax + More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/routing/rules-and-priority/#rulesyntax Deprecated: Please do not use this field and rewrite the router rules to use the v3 syntax. type: string required: @@ -322,18 +342,18 @@ spec: tls: description: |- TLS defines the TLS configuration. - More info: https://doc.traefik.io/traefik/v3.4/routing/routers/#tls + More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/routing/router/#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/v3.4/https/acme/#certificate-resolvers + More info: https://doc.traefik.io/traefik/v3.5/reference/install-configuration/tls/certificate-resolvers/acme/ type: string domains: description: |- Domains defines the list of domains that will be used to issue certificates. - More info: https://doc.traefik.io/traefik/v3.4/routing/routers/#domains + More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/tls/tls-certificates/#domains items: description: Domain holds a domain name with SANs. properties: @@ -352,17 +372,17 @@ spec: 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/v3.4/https/tls/#tls-options + More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/tls/tls-options/ properties: name: description: |- Name defines the name of the referenced TLSOption. - More info: https://doc.traefik.io/traefik/v3.4/routing/providers/kubernetes-crd/#kind-tlsoption + More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/kubernetes/crd/http/tlsoption/ type: string namespace: description: |- Namespace defines the namespace of the referenced TLSOption. - More info: https://doc.traefik.io/traefik/v3.4/routing/providers/kubernetes-crd/#kind-tlsoption + More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/kubernetes/crd/http/tlsoption/ type: string required: - name @@ -379,12 +399,12 @@ spec: name: description: |- Name defines the name of the referenced TLSStore. - More info: https://doc.traefik.io/traefik/v3.4/routing/providers/kubernetes-crd/#kind-tlsstore + More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/kubernetes/crd/http/tlsstore/ type: string namespace: description: |- Namespace defines the namespace of the referenced TLSStore. - More info: https://doc.traefik.io/traefik/v3.4/routing/providers/kubernetes-crd/#kind-tlsstore + More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/kubernetes/crd/http/tlsstore/ type: string required: - name diff --git a/docs/content/reference/dynamic-configuration/traefik.io_ingressroutetcps.yaml b/docs/content/reference/dynamic-configuration/traefik.io_ingressroutetcps.yaml index a42968a0c..37a02f2fb 100644 --- a/docs/content/reference/dynamic-configuration/traefik.io_ingressroutetcps.yaml +++ b/docs/content/reference/dynamic-configuration/traefik.io_ingressroutetcps.yaml @@ -43,7 +43,7 @@ spec: 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/v3.4/routing/entrypoints/ + More info: https://doc.traefik.io/traefik/v3.5/reference/install-configuration/entrypoints/ Default: all. items: type: string @@ -56,7 +56,7 @@ spec: match: description: |- Match defines the router's rule. - More info: https://doc.traefik.io/traefik/v3.4/routing/routers/#rule_1 + More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/tcp/routing/rules-and-priority/ type: string middlewares: description: Middlewares defines the list of references to MiddlewareTCP @@ -80,7 +80,7 @@ spec: priority: description: |- Priority defines the router's priority. - More info: https://doc.traefik.io/traefik/v3.4/routing/routers/#priority_1 + More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/tcp/routing/rules-and-priority/#priority maximum: 9223372036854775000 type: integer services: @@ -122,7 +122,8 @@ spec: proxyProtocol: description: |- ProxyProtocol defines the PROXY protocol configuration. - More info: https://doc.traefik.io/traefik/v3.4/routing/services/#proxy-protocol + More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/tcp/service/#proxy-protocol + Deprecated: ProxyProtocol will not be supported in future APIVersions, please use ServersTransport to configure ProxyProtocol instead. properties: version: description: Version defines the PROXY Protocol version @@ -163,7 +164,7 @@ spec: syntax: description: |- Syntax defines the router's rule syntax. - More info: https://doc.traefik.io/traefik/v3.4/routing/routers/#rulesyntax_1 + More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/tcp/routing/rules-and-priority/#rulesyntax Deprecated: Please do not use this field and rewrite the router rules to use the v3 syntax. enum: - v3 @@ -176,18 +177,18 @@ spec: tls: description: |- TLS defines the TLS configuration on a layer 4 / TCP Route. - More info: https://doc.traefik.io/traefik/v3.4/routing/routers/#tls_1 + More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/tcp/routing/router/#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/v3.4/https/acme/#certificate-resolvers + More info: https://doc.traefik.io/traefik/v3.5/reference/install-configuration/tls/certificate-resolvers/acme/ type: string domains: description: |- Domains defines the list of domains that will be used to issue certificates. - More info: https://doc.traefik.io/traefik/v3.4/routing/routers/#domains + More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/tcp/tls/#domains items: description: Domain holds a domain name with SANs. properties: @@ -206,7 +207,7 @@ spec: 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/v3.4/https/tls/#tls-options + More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/tcp/tls/#tls-options properties: name: description: Name defines the name of the referenced Traefik diff --git a/docs/content/reference/dynamic-configuration/traefik.io_ingressrouteudps.yaml b/docs/content/reference/dynamic-configuration/traefik.io_ingressrouteudps.yaml index d23d7e851..dc08ce018 100644 --- a/docs/content/reference/dynamic-configuration/traefik.io_ingressrouteudps.yaml +++ b/docs/content/reference/dynamic-configuration/traefik.io_ingressrouteudps.yaml @@ -43,7 +43,7 @@ spec: 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/v3.4/routing/entrypoints/ + More info: https://doc.traefik.io/traefik/v3.5/reference/install-configuration/entrypoints/ Default: all. items: type: string diff --git a/docs/content/reference/dynamic-configuration/traefik.io_middlewares.yaml b/docs/content/reference/dynamic-configuration/traefik.io_middlewares.yaml index f45b0b68f..5321da0b0 100644 --- a/docs/content/reference/dynamic-configuration/traefik.io_middlewares.yaml +++ b/docs/content/reference/dynamic-configuration/traefik.io_middlewares.yaml @@ -19,7 +19,7 @@ spec: openAPIV3Schema: description: |- Middleware is the CRD implementation of a Traefik Middleware. - More info: https://doc.traefik.io/traefik/v3.4/middlewares/http/overview/ + More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/middlewares/overview/ properties: apiVersion: description: |- @@ -45,7 +45,7 @@ spec: 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/v3.4/middlewares/http/addprefix/ + More info: https://doc.traefik.io/traefik/v3.5/middlewares/http/addprefix/ properties: prefix: description: |- @@ -60,12 +60,12 @@ spec: 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/v3.4/middlewares/http/basicauth/ + More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/middlewares/basicauth/ properties: headerField: description: |- HeaderField defines a header field to store the authenticated user. - More info: https://doc.traefik.io/traefik/v3.4/middlewares/http/basicauth/#headerfield + More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/middlewares/basicauth/#headerfield type: string realm: description: |- @@ -86,7 +86,7 @@ spec: 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/v3.4/middlewares/http/buffering/#maxrequestbodybytes + More info: https://doc.traefik.io/traefik/v3.5/middlewares/http/buffering/#maxrequestbodybytes properties: maxRequestBodyBytes: description: |- @@ -118,14 +118,14 @@ spec: 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/v3.4/middlewares/http/buffering/#retryexpression + More info: https://doc.traefik.io/traefik/v3.5/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/v3.4/middlewares/http/chain/ + More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/middlewares/chain/ properties: middlewares: description: Middlewares is the list of MiddlewareRef which composes @@ -188,7 +188,7 @@ spec: description: |- Compress holds the compress middleware configuration. This middleware compresses responses before sending them to the client, using gzip, brotli, or zstd compression. - More info: https://doc.traefik.io/traefik/v3.4/middlewares/http/compress/ + More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/middlewares/compress/ properties: defaultEncoding: description: DefaultEncoding specifies the default encoding if @@ -238,12 +238,12 @@ spec: 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/v3.4/middlewares/http/digestauth/ + More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/middlewares/digestauth/ properties: headerField: description: |- HeaderField defines a header field to store the authenticated user. - More info: https://doc.traefik.io/traefik/v3.4/middlewares/http/basicauth/#headerfield + More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/middlewares/digestauth/#headerfield type: string realm: description: |- @@ -263,7 +263,7 @@ spec: 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/v3.4/middlewares/http/errorpages/ + More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/middlewares/errorpages/ properties: query: description: |- @@ -275,7 +275,7 @@ spec: service: description: |- Service defines the reference to a Kubernetes Service that will serve the error page. - More info: https://doc.traefik.io/traefik/v3.4/middlewares/http/errorpages/#service + More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/middlewares/errorpages/#service properties: healthCheck: description: Healthcheck defines health checks for ExternalName @@ -301,7 +301,7 @@ spec: - type: integer - type: string description: |- - Interval defines the frequency of the health check calls. + Interval defines the frequency of the health check calls for healthy targets. Default: 30s x-kubernetes-int-or-string: true method: @@ -337,6 +337,15 @@ spec: Timeout defines the maximum duration Traefik will wait for a health check request before considering the server unhealthy. Default: 5s x-kubernetes-int-or-string: true + unhealthyInterval: + anyOf: + - type: integer + - type: string + description: |- + UnhealthyInterval defines the frequency of the health check calls for unhealthy targets. + When UnhealthyInterval is not defined, it defaults to the Interval value. + Default: 30s + x-kubernetes-int-or-string: true type: object kind: description: Kind defines the kind of the Service. @@ -408,7 +417,7 @@ spec: sticky: description: |- Sticky defines the sticky sessions configuration. - More info: https://doc.traefik.io/traefik/v3.4/routing/services/#sticky-sessions + More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/load-balancing/service/#sticky-sessions properties: cookie: description: Cookie defines the sticky cookie configuration. @@ -495,7 +504,7 @@ spec: description: |- ForwardAuth holds the forward auth middleware configuration. This middleware delegates the request authentication to a Service. - More info: https://doc.traefik.io/traefik/v3.4/middlewares/http/forwardauth/ + More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/middlewares/forwardauth/ properties: addAuthCookiesToResponse: description: AddAuthCookiesToResponse defines the list of cookies @@ -523,7 +532,7 @@ spec: authResponseHeadersRegex: 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/v3.4/middlewares/http/forwardauth/#authresponseheadersregex + More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/middlewares/forwardauth/#authresponseheadersregex type: string forwardBody: description: ForwardBody defines whether to send the request body @@ -532,7 +541,7 @@ spec: headerField: description: |- HeaderField defines a header field to store the authenticated user. - More info: https://doc.traefik.io/traefik/v3.4/middlewares/http/forwardauth/#headerfield + More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/middlewares/forwardauth/#headerfield type: string maxBodySize: description: MaxBodySize defines the maximum body size in bytes @@ -594,7 +603,7 @@ spec: description: |- Headers holds the headers middleware configuration. This middleware manages the requests and responses headers. - More info: https://doc.traefik.io/traefik/v3.4/middlewares/http/headers/#customrequestheaders + More info: https://doc.traefik.io/traefik/v3.5/middlewares/http/headers/#customrequestheaders properties: accessControlAllowCredentials: description: AccessControlAllowCredentials defines whether the @@ -766,7 +775,7 @@ spec: 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/v3.4/middlewares/http/inflightreq/ + More info: https://doc.traefik.io/traefik/v3.5/middlewares/http/inflightreq/ properties: amount: description: |- @@ -780,12 +789,12 @@ spec: 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/v3.4/middlewares/http/inflightreq/#sourcecriterion + More info: https://doc.traefik.io/traefik/v3.5/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/v3.4/middlewares/http/ipallowlist/#ipstrategy + More info: https://doc.traefik.io/traefik/v3.5/middlewares/http/ipallowlist/#ipstrategy properties: depth: description: Depth tells Traefik to use the X-Forwarded-For @@ -821,12 +830,12 @@ spec: description: |- IPAllowList holds the IP allowlist middleware configuration. This middleware limits allowed requests based on the client IP. - More info: https://doc.traefik.io/traefik/v3.4/middlewares/http/ipallowlist/ + More info: https://doc.traefik.io/traefik/v3.5/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/v3.4/middlewares/http/ipallowlist/#ipstrategy + More info: https://doc.traefik.io/traefik/v3.5/middlewares/http/ipallowlist/#ipstrategy properties: depth: description: Depth tells Traefik to use the X-Forwarded-For @@ -864,7 +873,7 @@ spec: ipStrategy: description: |- IPStrategy holds the IP strategy configuration used by Traefik to determine the client IP. - More info: https://doc.traefik.io/traefik/v3.4/middlewares/http/ipallowlist/#ipstrategy + More info: https://doc.traefik.io/traefik/v3.5/middlewares/http/ipallowlist/#ipstrategy properties: depth: description: Depth tells Traefik to use the X-Forwarded-For @@ -895,7 +904,7 @@ spec: 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/v3.4/middlewares/http/passtlsclientcert/ + More info: https://doc.traefik.io/traefik/v3.5/middlewares/http/passtlsclientcert/ properties: info: description: Info selects the specific client certificate details @@ -998,13 +1007,13 @@ spec: x-kubernetes-preserve-unknown-fields: true description: |- Plugin defines the middleware plugin configuration. - More info: https://doc.traefik.io/traefik/plugins/ + More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/middlewares/overview/#community-middlewares type: object 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/v3.4/middlewares/http/ratelimit/ + More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/middlewares/ratelimit/ properties: average: description: |- @@ -1123,7 +1132,7 @@ spec: ipStrategy: description: |- IPStrategy holds the IP strategy configuration used by Traefik to determine the client IP. - More info: https://doc.traefik.io/traefik/v3.4/middlewares/http/ipallowlist/#ipstrategy + More info: https://doc.traefik.io/traefik/v3.5/middlewares/http/ipallowlist/#ipstrategy properties: depth: description: Depth tells Traefik to use the X-Forwarded-For @@ -1159,11 +1168,11 @@ spec: 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/v3.4/middlewares/http/redirectregex/#regex + More info: https://doc.traefik.io/traefik/v3.5/middlewares/http/redirectregex/#regex properties: permanent: description: Permanent defines whether the redirection is permanent - (301). + (308). type: boolean regex: description: Regex defines the regex used to match and capture @@ -1178,11 +1187,11 @@ spec: 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/v3.4/middlewares/http/redirectscheme/ + More info: https://doc.traefik.io/traefik/v3.5/middlewares/http/redirectscheme/ properties: permanent: description: Permanent defines whether the redirection is permanent - (301). + (308). type: boolean port: description: Port defines the port of the new URL. @@ -1195,7 +1204,7 @@ spec: 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/v3.4/middlewares/http/replacepath/ + More info: https://doc.traefik.io/traefik/v3.5/middlewares/http/replacepath/ properties: path: description: Path defines the path to use as replacement in the @@ -1206,7 +1215,7 @@ spec: 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/v3.4/middlewares/http/replacepathregex/ + More info: https://doc.traefik.io/traefik/v3.5/middlewares/http/replacepathregex/ properties: regex: description: Regex defines the regular expression used to match @@ -1222,7 +1231,7 @@ spec: 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/v3.4/middlewares/http/retry/ + More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/middlewares/retry/ properties: attempts: description: Attempts defines how many times the request should @@ -1246,7 +1255,7 @@ spec: 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/v3.4/middlewares/http/stripprefix/ + More info: https://doc.traefik.io/traefik/v3.5/middlewares/http/stripprefix/ properties: forceSlash: description: |- @@ -1265,7 +1274,7 @@ spec: 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/v3.4/middlewares/http/stripprefixregex/ + More info: https://doc.traefik.io/traefik/v3.5/middlewares/http/stripprefixregex/ properties: regex: description: Regex defines the regular expression to match the diff --git a/docs/content/reference/dynamic-configuration/traefik.io_middlewaretcps.yaml b/docs/content/reference/dynamic-configuration/traefik.io_middlewaretcps.yaml index 26d9ba184..5f7604923 100644 --- a/docs/content/reference/dynamic-configuration/traefik.io_middlewaretcps.yaml +++ b/docs/content/reference/dynamic-configuration/traefik.io_middlewaretcps.yaml @@ -19,7 +19,7 @@ spec: openAPIV3Schema: description: |- MiddlewareTCP is the CRD implementation of a Traefik TCP middleware. - More info: https://doc.traefik.io/traefik/v3.4/middlewares/overview/ + More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/tcp/middlewares/overview/ properties: apiVersion: description: |- @@ -56,7 +56,7 @@ spec: description: |- IPAllowList defines the IPAllowList middleware configuration. This middleware accepts/refuses connections based on the client IP. - More info: https://doc.traefik.io/traefik/v3.4/middlewares/tcp/ipallowlist/ + More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/tcp/middlewares/ipallowlist/ properties: sourceRange: description: SourceRange defines the allowed IPs (or ranges of @@ -70,7 +70,7 @@ spec: IPWhiteList defines the IPWhiteList middleware configuration. This middleware accepts/refuses connections based on the client IP. Deprecated: please use IPAllowList instead. - More info: https://doc.traefik.io/traefik/v3.4/middlewares/tcp/ipwhitelist/ + More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/tcp/middlewares/ipwhitelist/ properties: sourceRange: description: SourceRange defines the allowed IPs (or ranges of diff --git a/docs/content/reference/dynamic-configuration/traefik.io_serverstransports.yaml b/docs/content/reference/dynamic-configuration/traefik.io_serverstransports.yaml index 99d820da2..0828291ee 100644 --- a/docs/content/reference/dynamic-configuration/traefik.io_serverstransports.yaml +++ b/docs/content/reference/dynamic-configuration/traefik.io_serverstransports.yaml @@ -21,7 +21,7 @@ spec: 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/v3.4/routing/services/#serverstransport_1 + More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/load-balancing/serverstransport/ properties: apiVersion: description: |- @@ -107,7 +107,7 @@ spec: maxIdleConnsPerHost: description: MaxIdleConnsPerHost controls the maximum idle (keep-alive) to keep per-host. - minimum: 0 + minimum: -1 type: integer peerCertURI: description: PeerCertURI defines the peer cert URI used to match against diff --git a/docs/content/reference/dynamic-configuration/traefik.io_serverstransporttcps.yaml b/docs/content/reference/dynamic-configuration/traefik.io_serverstransporttcps.yaml index 35f5dab93..f8be2b1d9 100644 --- a/docs/content/reference/dynamic-configuration/traefik.io_serverstransporttcps.yaml +++ b/docs/content/reference/dynamic-configuration/traefik.io_serverstransporttcps.yaml @@ -21,7 +21,7 @@ spec: ServersTransportTCP is the CRD implementation of a TCPServersTransport. If no tcpServersTransport is specified, a default one named default@internal will be used. The default@internal tcpServersTransport can be configured in the static configuration. - More info: https://doc.traefik.io/traefik/v3.4/routing/services/#serverstransport_3 + More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/tcp/serverstransport/ properties: apiVersion: description: |- @@ -63,6 +63,15 @@ spec: to a backend server can be established. pattern: ^([0-9]+(ns|us|µs|ms|s|m|h)?)+$ x-kubernetes-int-or-string: true + proxyProtocol: + description: ProxyProtocol holds the PROXY Protocol configuration. + properties: + version: + description: Version defines the PROXY Protocol version to use. + maximum: 2 + minimum: 1 + type: integer + type: object terminationDelay: anyOf: - type: integer diff --git a/docs/content/reference/dynamic-configuration/traefik.io_tlsoptions.yaml b/docs/content/reference/dynamic-configuration/traefik.io_tlsoptions.yaml index 758a0ab96..c32974fae 100644 --- a/docs/content/reference/dynamic-configuration/traefik.io_tlsoptions.yaml +++ b/docs/content/reference/dynamic-configuration/traefik.io_tlsoptions.yaml @@ -19,7 +19,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/v3.4/https/tls/#tls-options + More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/tls/tls-certificates/#certificates-stores#tls-options properties: apiVersion: description: |- @@ -44,14 +44,14 @@ 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/v3.4/https/tls/#alpn-protocols + More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/tls/tls-certificates/#certificates-stores#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/v3.4/https/tls/#cipher-suites + More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/tls/tls-certificates/#certificates-stores#cipher-suites items: type: string type: array @@ -78,8 +78,8 @@ spec: type: object curvePreferences: description: |- - CurvePreferences defines the preferred elliptic curves in a specific order. - More info: https://doc.traefik.io/traefik/v3.4/https/tls/#curve-preferences + CurvePreferences defines the preferred elliptic curves. + More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/tls/tls-certificates/#certificates-stores#curve-preferences items: type: string type: array diff --git a/docs/content/reference/dynamic-configuration/traefik.io_tlsstores.yaml b/docs/content/reference/dynamic-configuration/traefik.io_tlsstores.yaml index bdf4a93d6..779c93908 100644 --- a/docs/content/reference/dynamic-configuration/traefik.io_tlsstores.yaml +++ b/docs/content/reference/dynamic-configuration/traefik.io_tlsstores.yaml @@ -21,7 +21,7 @@ spec: 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/v3.4/https/tls/#certificates-stores + More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/tls/tls-certificates/#certificates-stores#certificates-stores properties: apiVersion: description: |- diff --git a/docs/content/reference/dynamic-configuration/traefik.io_traefikservices.yaml b/docs/content/reference/dynamic-configuration/traefik.io_traefikservices.yaml index 6715233b3..77e97156b 100644 --- a/docs/content/reference/dynamic-configuration/traefik.io_traefikservices.yaml +++ b/docs/content/reference/dynamic-configuration/traefik.io_traefikservices.yaml @@ -22,7 +22,7 @@ spec: TraefikService object allows to: - Apply weight to Services on load-balancing - Mirror traffic on services - More info: https://doc.traefik.io/traefik/v3.4/routing/providers/kubernetes-crd/#kind-traefikservice + More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/kubernetes/crd/http/traefikservice/ properties: apiVersion: description: |- @@ -71,7 +71,7 @@ spec: - type: integer - type: string description: |- - Interval defines the frequency of the health check calls. + Interval defines the frequency of the health check calls for healthy targets. Default: 30s x-kubernetes-int-or-string: true method: @@ -107,6 +107,15 @@ spec: Timeout defines the maximum duration Traefik will wait for a health check request before considering the server unhealthy. Default: 5s x-kubernetes-int-or-string: true + unhealthyInterval: + anyOf: + - type: integer + - type: string + description: |- + UnhealthyInterval defines the frequency of the health check calls for unhealthy targets. + When UnhealthyInterval is not defined, it defaults to the Interval value. + Default: 30s + x-kubernetes-int-or-string: true type: object kind: description: Kind defines the kind of the Service. @@ -156,7 +165,7 @@ spec: - type: integer - type: string description: |- - Interval defines the frequency of the health check calls. + Interval defines the frequency of the health check calls for healthy targets. Default: 30s x-kubernetes-int-or-string: true method: @@ -192,6 +201,15 @@ spec: Timeout defines the maximum duration Traefik will wait for a health check request before considering the server unhealthy. Default: 5s x-kubernetes-int-or-string: true + unhealthyInterval: + anyOf: + - type: integer + - type: string + description: |- + UnhealthyInterval defines the frequency of the health check calls for unhealthy targets. + When UnhealthyInterval is not defined, it defaults to the Interval value. + Default: 30s + x-kubernetes-int-or-string: true type: object kind: description: Kind defines the kind of the Service. @@ -268,7 +286,7 @@ spec: sticky: description: |- Sticky defines the sticky sessions configuration. - More info: https://doc.traefik.io/traefik/v3.4/routing/services/#sticky-sessions + More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/load-balancing/service/#sticky-sessions properties: cookie: description: Cookie defines the sticky cookie configuration. @@ -396,7 +414,7 @@ spec: sticky: description: |- Sticky defines the sticky sessions configuration. - More info: https://doc.traefik.io/traefik/v3.4/routing/services/#sticky-sessions + More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/load-balancing/service/#sticky-sessions properties: cookie: description: Cookie defines the sticky cookie configuration. @@ -493,7 +511,7 @@ spec: - type: integer - type: string description: |- - Interval defines the frequency of the health check calls. + Interval defines the frequency of the health check calls for healthy targets. Default: 30s x-kubernetes-int-or-string: true method: @@ -529,6 +547,15 @@ spec: Timeout defines the maximum duration Traefik will wait for a health check request before considering the server unhealthy. Default: 5s x-kubernetes-int-or-string: true + unhealthyInterval: + anyOf: + - type: integer + - type: string + description: |- + UnhealthyInterval defines the frequency of the health check calls for unhealthy targets. + When UnhealthyInterval is not defined, it defaults to the Interval value. + Default: 30s + x-kubernetes-int-or-string: true type: object kind: description: Kind defines the kind of the Service. @@ -600,7 +627,7 @@ spec: sticky: description: |- Sticky defines the sticky sessions configuration. - More info: https://doc.traefik.io/traefik/v3.4/routing/services/#sticky-sessions + More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/load-balancing/service/#sticky-sessions properties: cookie: description: Cookie defines the sticky cookie configuration. @@ -668,7 +695,7 @@ spec: sticky: description: |- Sticky defines whether sticky sessions are enabled. - More info: https://doc.traefik.io/traefik/v3.4/routing/providers/kubernetes-crd/#stickiness-and-load-balancing + More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/kubernetes/crd/http/traefikservice/#stickiness-and-load-balancing properties: cookie: description: Cookie defines the sticky cookie configuration. diff --git a/docs/content/reference/install-configuration/api-dashboard.md b/docs/content/reference/install-configuration/api-dashboard.md index ed485d499..9f03454ca 100644 --- a/docs/content/reference/install-configuration/api-dashboard.md +++ b/docs/content/reference/install-configuration/api-dashboard.md @@ -155,11 +155,12 @@ enabing the dashboard [here](https://github.com/traefik/traefik-helm-chart/blob/ | Field | Description | Default | Required | |:-----------|:---------------------------------|:--------|:---------| -| `api` | Enable api/dashboard. When set to `true`, its sub option `api.dashboard` is also set to true.| false | No | -| `api.dashboard` | Enable dashboard. | false | No | -| `api.debug` | Enable additional endpoints for debugging and profiling. | false | No | -| `api.disabledashboardad` | Disable the advertisement from the dashboard. | false | No | -| `api.insecure` | Enable the API and the dashboard on the entryPoint named traefik.| false | No | +| `api` | Enable api/dashboard. When set to `true`, its sub option `api.dashboard` is also set to true.| false | No | +| api.basepath | Defines the base path where the API and Dashboard will be exposed. | / | No | +| `api.dashboard` | Enable dashboard. | false | No | +| `api.debug` | Enable additional endpoints for debugging and profiling. | false | No | +| `api.disabledashboardad` | Disable the advertisement from the dashboard. | false | No | +| `api.insecure` | Enable the API and the dashboard on the entryPoint named traefik.| false | No | ## Endpoints @@ -167,37 +168,42 @@ All the following endpoints must be accessed with a `GET` HTTP request. | Path | Description | |--------------------------------|---------------------------------------------------------------------------------------------| -| `/api/http/routers` | Lists all the HTTP routers information. | -| `/api/http/routers/{name}` | Returns the information of the HTTP router specified by `name`. | -| `/api/http/services` | Lists all the HTTP services information. | -| `/api/http/services/{name}` | Returns the information of the HTTP service specified by `name`. | -| `/api/http/middlewares` | Lists all the HTTP middlewares information. | -| `/api/http/middlewares/{name}` | Returns the information of the HTTP middleware specified by `name`. | -| `/api/tcp/routers` | Lists all the TCP routers information. | -| `/api/tcp/routers/{name}` | Returns the information of the TCP router specified by `name`. | -| `/api/tcp/services` | Lists all the TCP services information. | -| `/api/tcp/services/{name}` | Returns the information of the TCP service specified by `name`. | -| `/api/tcp/middlewares` | Lists all the TCP middlewares information. | -| `/api/tcp/middlewares/{name}` | Returns the information of the TCP middleware specified by `name`. | -| `/api/udp/routers` | Lists all the UDP routers information. | -| `/api/udp/routers/{name}` | Returns the information of the UDP router specified by `name`. | -| `/api/udp/services` | Lists all the UDP services information. | -| `/api/udp/services/{name}` | Returns the information of the UDP service specified by `name`. | -| `/api/entrypoints` | Lists all the entry points information. | -| `/api/entrypoints/{name}` | Returns the information of the entry point specified by `name`. | -| `/api/overview` | Returns statistic information about HTTP, TCP and about enabled features and providers. | -| `/api/rawdata` | Returns information about dynamic configurations, errors, status and dependency relations. | -| `/api/version` | Returns information about Traefik version. | -| `/debug/vars` | See the [expvar](https://golang.org/pkg/expvar/) Go documentation. | -| `/debug/pprof/` | See the [pprof Index](https://golang.org/pkg/net/http/pprof/#Index) Go documentation. | -| `/debug/pprof/cmdline` | See the [pprof Cmdline](https://golang.org/pkg/net/http/pprof/#Cmdline) Go documentation. | -| `/debug/pprof/profile` | See the [pprof Profile](https://golang.org/pkg/net/http/pprof/#Profile) Go documentation. | -| `/debug/pprof/symbol` | See the [pprof Symbol](https://golang.org/pkg/net/http/pprof/#Symbol) Go documentation. | -| `/debug/pprof/trace` | See the [pprof Trace](https://golang.org/pkg/net/http/pprof/#Trace) Go documentation. | +| `/api/http/routers` | Lists all the HTTP routers information. | +| `/api/http/routers/{name}` | Returns the information of the HTTP router specified by `name`. | +| `/api/http/services` | Lists all the HTTP services information. | +| `/api/http/services/{name}` | Returns the information of the HTTP service specified by `name`. | +| `/api/http/middlewares` | Lists all the HTTP middlewares information. | +| `/api/http/middlewares/{name}` | Returns the information of the HTTP middleware specified by `name`. | +| `/api/tcp/routers` | Lists all the TCP routers information. | +| `/api/tcp/routers/{name}` | Returns the information of the TCP router specified by `name`. | +| `/api/tcp/services` | Lists all the TCP services information. | +| `/api/tcp/services/{name}` | Returns the information of the TCP service specified by `name`. | +| `/api/tcp/middlewares` | Lists all the TCP middlewares information. | +| `/api/tcp/middlewares/{name}` | Returns the information of the TCP middleware specified by `name`. | +| `/api/udp/routers` | Lists all the UDP routers information. | +| `/api/udp/routers/{name}` | Returns the information of the UDP router specified by `name`. | +| `/api/udp/services` | Lists all the UDP services information. | +| `/api/udp/services/{name}` | Returns the information of the UDP service specified by `name`. | +| `/api/entrypoints` | Lists all the entry points information. | +| `/api/entrypoints/{name}` | Returns the information of the entry point specified by `name`. | +| `/api/overview` | Returns statistic information about HTTP, TCP and about enabled features and providers. | +| `/api/rawdata` | Returns information about dynamic configurations, errors, status and dependency relations. | +| `/api/version` | Returns information about Traefik version. | +| `/debug/vars` | See the [expvar](https://golang.org/pkg/expvar/) Go documentation. | +| `/debug/pprof/` | See the [pprof Index](https://golang.org/pkg/net/http/pprof/#Index) Go documentation. | +| `/debug/pprof/cmdline` | See the [pprof Cmdline](https://golang.org/pkg/net/http/pprof/#Cmdline) Go documentation. | +| `/debug/pprof/profile` | See the [pprof Profile](https://golang.org/pkg/net/http/pprof/#Profile) Go documentation. | +| `/debug/pprof/symbol` | See the [pprof Symbol](https://golang.org/pkg/net/http/pprof/#Symbol) Go documentation. | +| `/debug/pprof/trace` | See the [pprof Trace](https://golang.org/pkg/net/http/pprof/#Trace) Go documentation. | + + +!!! note "Base Path Configuration" + + By default, Traefik exposes its API and Dashboard under the `/` base path. It's possible to configure it with `api.basepath`. When configured, all endpoints (api, dashboard, debug) are using it. ## Dashboard -The dashboard is available at the same location as the [API](../../operations/api.md), but by default on the path `/dashboard/`. +The dashboard is available at the same location as the API, but by default on the path `/dashboard/`. !!! note diff --git a/docs/content/reference/install-configuration/boot-environment.md b/docs/content/reference/install-configuration/boot-environment.md index 223977752..ca4f01c59 100644 --- a/docs/content/reference/install-configuration/boot-environment.md +++ b/docs/content/reference/install-configuration/boot-environment.md @@ -7,14 +7,14 @@ description: "Read the official Traefik documentation to get started with config Traefik Proxy’s configuration is divided into two main categories: -- **Static Configuration**: Defines parameters that require Traefik to restart when changed. This includes entry points, providers, API/dashboard settings, and logging levels. -- **Dynamic Configuration**: Involves elements that can be updated without restarting Traefik, such as routers, services, and middlewares. +- **Install Configuration**: (formerly known as the static configuration) Defines parameters that require Traefik to restart when changed. This includes entry points, providers, API/dashboard settings, and logging levels. +- **Routing Configuration**: (formerly known as the dynamic configuration) Involves elements that can be updated without restarting Traefik, such as routers, services, and middlewares. -This section focuses on setting up the static configuration, which is essential for Traefik’s initial boot. +This section focuses on setting up the install configuration, which is essential for Traefik’s initial boot. ## Configuration Methods -Traefik offers multiple methods to define static configuration. +Traefik offers multiple methods to define install configuration. !!! warning "Note" It’s crucial to choose one method and stick to it, as mixing different configuration options is not supported and can lead to unexpected behavior. @@ -28,7 +28,7 @@ Here are the methods available for configuring the Traefik proxy: ## File -You can define the static configuration in a file using formats like YAML or TOML. +You can define the install configuration in a file using formats like YAML or TOML. ### Configuration Example @@ -69,7 +69,7 @@ log: ### Configuration File -At startup, Traefik searches for static configuration in a file named `traefik.yml` (or `traefik.yaml` or `traefik.toml`) in the following directories: +At startup, Traefik searches for install configuration in a file named `traefik.yml` (or `traefik.yaml` or `traefik.toml`) in the following directories: - `/etc/traefik/` - `$XDG_CONFIG_HOME/` @@ -84,7 +84,7 @@ traefik --configFile=foo/bar/myconfigfile.yml ## CLI -Using the CLI, you can pass static configuration directly as command-line arguments when starting Traefik. +Using the CLI, you can pass install configuration directly as command-line arguments when starting Traefik. ### Configuration Example @@ -99,7 +99,7 @@ traefik \ ## Environment Variables -You can also set the static configuration using environment variables. Each option corresponds to an environment variable prefixed with `TRAEFIK_`. +You can also set the install configuration using environment variables. Each option corresponds to an environment variable prefixed with `TRAEFIK_`. ### Configuration Example @@ -109,7 +109,7 @@ TRAEFIK_ENTRYPOINTS_WEB_ADDRESS=":80" TRAEFIK_ENTRYPOINTS_WEBSECURE_ADDRESS=":44 ## Helm -When deploying Traefik Proxy using Helm in a Kubernetes cluster, the static configuration is defined in a `values.yaml` file. +When deploying Traefik Proxy using Helm in a Kubernetes cluster, the install configuration is defined in a `values.yaml` file. You can find the official Traefik Helm chart on [GitHub](https://github.com/traefik/traefik-helm-chart/blob/master/traefik/VALUES.md) diff --git a/docs/content/reference/install-configuration/configuration-options.md b/docs/content/reference/install-configuration/configuration-options.md new file mode 100644 index 000000000..596c69283 --- /dev/null +++ b/docs/content/reference/install-configuration/configuration-options.md @@ -0,0 +1,491 @@ + +# Install Configuration Options +## Configuration Options + +| Field | Description | Default | +|:-------|:------------|:-------| +| accesslog | Access log settings. | false | +| accesslog.addinternals | Enables access log for internal services (ping, dashboard, etc...). | false | +| accesslog.bufferingsize | Number of access log lines to process in a buffered way. | 0 | +| accesslog.fields.defaultmode | Default mode for fields: keep | drop | keep | +| accesslog.fields.headers.defaultmode | Default mode for fields: keep | drop | redact | drop | +| accesslog.fields.headers.names._name_ | Override mode for headers | | +| accesslog.fields.names._name_ | Override mode for fields | | +| accesslog.filepath | Access log file path. Stdout is used when omitted or empty. | | +| accesslog.filters.minduration | Keep access logs when request took longer than the specified duration. | 0 | +| accesslog.filters.retryattempts | Keep access logs when at least one retry happened. | false | +| accesslog.filters.statuscodes | Keep access logs with status codes in the specified range. | | +| accesslog.format | Access log format: json, common, or genericCLF | common | +| accesslog.otlp | Settings for OpenTelemetry. | false | +| accesslog.otlp.grpc | gRPC configuration for the OpenTelemetry collector. | false | +| accesslog.otlp.grpc.endpoint | Sets the gRPC endpoint (host:port) of the collector. | localhost:4317 | +| accesslog.otlp.grpc.headers._name_ | Headers sent with payload. | | +| accesslog.otlp.grpc.insecure | Disables client transport security for the exporter. | false | +| accesslog.otlp.grpc.tls.ca | TLS CA | | +| accesslog.otlp.grpc.tls.cert | TLS cert | | +| accesslog.otlp.grpc.tls.insecureskipverify | TLS insecure skip verify | false | +| accesslog.otlp.grpc.tls.key | TLS key | | +| accesslog.otlp.http | HTTP configuration for the OpenTelemetry collector. | false | +| accesslog.otlp.http.endpoint | Sets the HTTP endpoint (scheme://host:port/path) of the collector. | https://localhost:4318 | +| accesslog.otlp.http.headers._name_ | Headers sent with payload. | | +| accesslog.otlp.http.tls.ca | TLS CA | | +| accesslog.otlp.http.tls.cert | TLS cert | | +| accesslog.otlp.http.tls.insecureskipverify | TLS insecure skip verify | false | +| accesslog.otlp.http.tls.key | TLS key | | +| accesslog.otlp.resourceattributes._name_ | Defines additional resource attributes (key:value). | | +| accesslog.otlp.servicename | Defines the service name resource attribute. | traefik | +| api | Enable api/dashboard. | false | +| api.basepath | Defines the base path where the API and Dashboard will be exposed. | / | +| api.dashboard | Activate dashboard. | true | +| api.debug | Enable additional endpoints for debugging and profiling. | false | +| api.disabledashboardad | Disable ad in the dashboard. | false | +| api.insecure | Activate API directly on the entryPoint named traefik. | false | +| certificatesresolvers._name_ | Certificates resolvers configuration. | false | +| certificatesresolvers._name_.acme.cacertificates | Specify the paths to PEM encoded CA Certificates that can be used to authenticate an ACME server with an HTTPS certificate not issued by a CA in the system-wide trusted root list. | | +| certificatesresolvers._name_.acme.caserver | CA server to use. | https://acme-v02.api.letsencrypt.org/directory | +| certificatesresolvers._name_.acme.caservername | Specify the CA server name that can be used to authenticate an ACME server with an HTTPS certificate not issued by a CA in the system-wide trusted root list. | | +| certificatesresolvers._name_.acme.casystemcertpool | Define if the certificates pool must use a copy of the system cert pool. | false | +| certificatesresolvers._name_.acme.certificatesduration | Certificates' duration in hours. | 2160 | +| certificatesresolvers._name_.acme.clientresponseheadertimeout | Timeout for receiving the response headers when communicating with the ACME server. | 30 | +| certificatesresolvers._name_.acme.clienttimeout | Timeout for a complete HTTP transaction with the ACME server. | 120 | +| certificatesresolvers._name_.acme.dnschallenge | Activate DNS-01 Challenge. | false | +| certificatesresolvers._name_.acme.dnschallenge.delaybeforecheck | (Deprecated) Assume DNS propagates after a delay in seconds rather than finding and querying nameservers. | 0 | +| certificatesresolvers._name_.acme.dnschallenge.disablepropagationcheck | (Deprecated) Disable the DNS propagation checks before notifying ACME that the DNS challenge is ready. [not recommended] | false | +| certificatesresolvers._name_.acme.dnschallenge.propagation | DNS propagation checks configuration | false | +| certificatesresolvers._name_.acme.dnschallenge.propagation.delaybeforechecks | Defines the delay before checking the challenge TXT record propagation. | 0 | +| certificatesresolvers._name_.acme.dnschallenge.propagation.disableanschecks | Disables the challenge TXT record propagation checks against authoritative nameservers. | false | +| certificatesresolvers._name_.acme.dnschallenge.propagation.disablechecks | Disables the challenge TXT record propagation checks (not recommended). | false | +| certificatesresolvers._name_.acme.dnschallenge.propagation.requireallrns | Requires the challenge TXT record to be propagated to all recursive nameservers. | false | +| certificatesresolvers._name_.acme.dnschallenge.provider | Use a DNS-01 based challenge provider rather than HTTPS. | | +| certificatesresolvers._name_.acme.dnschallenge.resolvers | Use following DNS servers to resolve the FQDN authority. | | +| certificatesresolvers._name_.acme.eab.hmacencoded | Base64 encoded HMAC key from External CA. | | +| certificatesresolvers._name_.acme.eab.kid | Key identifier from External CA. | | +| certificatesresolvers._name_.acme.email | Email address used for registration. | | +| certificatesresolvers._name_.acme.emailaddresses | CSR email addresses to use. | | +| certificatesresolvers._name_.acme.httpchallenge | Activate HTTP-01 Challenge. | false | +| certificatesresolvers._name_.acme.httpchallenge.delay | Delay between the creation of the challenge and the validation. | 0 | +| certificatesresolvers._name_.acme.httpchallenge.entrypoint | HTTP challenge EntryPoint | | +| certificatesresolvers._name_.acme.keytype | KeyType used for generating certificate private key. Allow value 'EC256', 'EC384', 'RSA2048', 'RSA4096', 'RSA8192'. | RSA4096 | +| certificatesresolvers._name_.acme.preferredchain | Preferred chain to use. | | +| certificatesresolvers._name_.acme.profile | Certificate profile to use. | | +| certificatesresolvers._name_.acme.storage | Storage to use. | acme.json | +| certificatesresolvers._name_.acme.tlschallenge | Activate TLS-ALPN-01 Challenge. | true | +| certificatesresolvers._name_.tailscale | Enables Tailscale certificate resolution. | true | +| core.defaultrulesyntax | Defines the rule parser default syntax (v2 or v3) | v3 | +| entrypoints._name_ | Entry points definition. | false | +| entrypoints._name_.address | Entry point address. | | +| entrypoints._name_.allowacmebypass | Enables handling of ACME TLS and HTTP challenges with custom routers. | false | +| entrypoints._name_.asdefault | Adds this EntryPoint to the list of default EntryPoints to be used on routers that don't have any Entrypoint defined. | false | +| entrypoints._name_.forwardedheaders.connection | List of Connection headers that are allowed to pass through the middleware chain before being removed. | | +| entrypoints._name_.forwardedheaders.insecure | Trust all forwarded headers. | false | +| entrypoints._name_.forwardedheaders.trustedips | Trust only forwarded headers from selected IPs. | | +| entrypoints._name_.http | HTTP configuration. | | +| entrypoints._name_.http.encodequerysemicolons | Defines whether request query semicolons should be URLEncoded. | false | +| entrypoints._name_.http.maxheaderbytes | Maximum size of request headers in bytes. | 1048576 | +| entrypoints._name_.http.middlewares | Default middlewares for the routers linked to the entry point. | | +| entrypoints._name_.http.redirections.entrypoint.permanent | Applies a permanent redirection. | true | +| entrypoints._name_.http.redirections.entrypoint.priority | Priority of the generated router. | 9223372036854775806 | +| entrypoints._name_.http.redirections.entrypoint.scheme | Scheme used for the redirection. | https | +| entrypoints._name_.http.redirections.entrypoint.to | Targeted entry point of the redirection. | | +| entrypoints._name_.http.sanitizepath | Defines whether to enable request path sanitization (removal of /./, /../ and multiple slash sequences). | true | +| entrypoints._name_.http.tls | Default TLS configuration for the routers linked to the entry point. | false | +| entrypoints._name_.http.tls.certresolver | Default certificate resolver for the routers linked to the entry point. | | +| entrypoints._name_.http.tls.domains | Default TLS domains for the routers linked to the entry point. | | +| entrypoints._name_.http.tls.domains[0].main | Default subject name. | | +| entrypoints._name_.http.tls.domains[0].sans | Subject alternative names. | | +| entrypoints._name_.http.tls.options | Default TLS options for the routers linked to the entry point. | | +| entrypoints._name_.http2.maxconcurrentstreams | Specifies the number of concurrent streams per connection that each client is allowed to initiate. | 250 | +| entrypoints._name_.http3 | HTTP/3 configuration. | false | +| entrypoints._name_.http3.advertisedport | UDP port to advertise, on which HTTP/3 is available. | 0 | +| entrypoints._name_.observability.accesslogs | Enables access-logs for this entryPoint. | true | +| entrypoints._name_.observability.metrics | Enables metrics for this entryPoint. | true | +| entrypoints._name_.observability.traceverbosity | Defines the tracing verbosity level for this entryPoint. | minimal | +| entrypoints._name_.observability.tracing | Enables tracing for this entryPoint. | true | +| entrypoints._name_.proxyprotocol | Proxy-Protocol configuration. | false | +| entrypoints._name_.proxyprotocol.insecure | Trust all. | false | +| entrypoints._name_.proxyprotocol.trustedips | Trust only selected IPs. | | +| entrypoints._name_.reuseport | Enables EntryPoints from the same or different processes listening on the same TCP/UDP port. | false | +| entrypoints._name_.transport.keepalivemaxrequests | Maximum number of requests before closing a keep-alive connection. | 0 | +| entrypoints._name_.transport.keepalivemaxtime | Maximum duration before closing a keep-alive connection. | 0 | +| entrypoints._name_.transport.lifecycle.gracetimeout | Duration to give active requests a chance to finish before Traefik stops. | 10 | +| entrypoints._name_.transport.lifecycle.requestacceptgracetimeout | Duration to keep accepting requests before Traefik initiates the graceful shutdown procedure. | 0 | +| entrypoints._name_.transport.respondingtimeouts.idletimeout | IdleTimeout is the maximum amount duration an idle (keep-alive) connection will remain idle before closing itself. If zero, no timeout is set. | 180 | +| entrypoints._name_.transport.respondingtimeouts.readtimeout | ReadTimeout is the maximum duration for reading the entire request, including the body. If zero, no timeout is set. | 60 | +| entrypoints._name_.transport.respondingtimeouts.writetimeout | WriteTimeout is the maximum duration before timing out writes of the response. If zero, no timeout is set. | 0 | +| entrypoints._name_.udp.timeout | Timeout defines how long to wait on an idle session before releasing the related resources. | 3 | +| experimental.abortonpluginfailure | Defines whether all plugins must be loaded successfully for Traefik to start. | false | +| experimental.fastproxy | Enables the FastProxy implementation. | false | +| experimental.fastproxy.debug | Enable debug mode for the FastProxy implementation. | false | +| experimental.kubernetesgateway | (Deprecated) Allow the Kubernetes gateway api provider usage. | false | +| experimental.kubernetesingressnginx | Allow the Kubernetes Ingress NGINX provider usage. | false | +| experimental.localplugins._name_ | Local plugins configuration. | false | +| experimental.localplugins._name_.modulename | Plugin's module name. | | +| experimental.localplugins._name_.settings | Plugin's settings (works only for wasm plugins). | | +| experimental.localplugins._name_.settings.envs | Environment variables to forward to the wasm guest. | | +| experimental.localplugins._name_.settings.mounts | Directory to mount to the wasm guest. | | +| experimental.localplugins._name_.settings.useunsafe | Allow the plugin to use unsafe package. | false | +| experimental.otlplogs | Enables the OpenTelemetry logs integration. | false | +| experimental.plugins._name_.hash | plugin's hash to validate' | | +| experimental.plugins._name_.modulename | plugin's module name. | | +| experimental.plugins._name_.settings | Plugin's settings (works only for wasm plugins). | | +| experimental.plugins._name_.settings.envs | Environment variables to forward to the wasm guest. | | +| experimental.plugins._name_.settings.mounts | Directory to mount to the wasm guest. | | +| experimental.plugins._name_.settings.useunsafe | Allow the plugin to use unsafe package. | false | +| experimental.plugins._name_.version | plugin's version. | | +| global.checknewversion | Periodically check if a new version has been released. | true | +| global.sendanonymoususage | Periodically send anonymous usage statistics. If the option is not specified, it will be disabled by default. | false | +| global.updatercallbacks | Callback urls for updater script (example: https://localhost:8080/callback) | | +| hostresolver | Enable CNAME Flattening. | false | +| hostresolver.cnameflattening | A flag to enable/disable CNAME flattening | false | +| hostresolver.resolvconfig | resolv.conf used for DNS resolving | /etc/resolv.conf | +| hostresolver.resolvdepth | The maximal depth of DNS recursive resolving | 5 | +| log | Traefik log settings. | false | +| log.compress | Determines if the rotated log files should be compressed using gzip. | false | +| log.filepath | Traefik log file path. Stdout is used when omitted or empty. | | +| log.format | Traefik log format: json | common | common | +| log.level | Log level set to traefik logs. | ERROR | +| log.maxage | Maximum number of days to retain old log files based on the timestamp encoded in their filename. | 0 | +| log.maxbackups | Maximum number of old log files to retain. | 0 | +| log.maxsize | Maximum size in megabytes of the log file before it gets rotated. | 0 | +| log.nocolor | When using the 'common' format, disables the colorized output. | false | +| log.otlp | Settings for OpenTelemetry. | false | +| log.otlp.grpc | gRPC configuration for the OpenTelemetry collector. | false | +| log.otlp.grpc.endpoint | Sets the gRPC endpoint (host:port) of the collector. | localhost:4317 | +| log.otlp.grpc.headers._name_ | Headers sent with payload. | | +| log.otlp.grpc.insecure | Disables client transport security for the exporter. | false | +| log.otlp.grpc.tls.ca | TLS CA | | +| log.otlp.grpc.tls.cert | TLS cert | | +| log.otlp.grpc.tls.insecureskipverify | TLS insecure skip verify | false | +| log.otlp.grpc.tls.key | TLS key | | +| log.otlp.http | HTTP configuration for the OpenTelemetry collector. | false | +| log.otlp.http.endpoint | Sets the HTTP endpoint (scheme://host:port/path) of the collector. | https://localhost:4318 | +| log.otlp.http.headers._name_ | Headers sent with payload. | | +| log.otlp.http.tls.ca | TLS CA | | +| log.otlp.http.tls.cert | TLS cert | | +| log.otlp.http.tls.insecureskipverify | TLS insecure skip verify | false | +| log.otlp.http.tls.key | TLS key | | +| log.otlp.resourceattributes._name_ | Defines additional resource attributes (key:value). | | +| log.otlp.servicename | Defines the service name resource attribute. | traefik | +| metrics.addinternals | Enables metrics for internal services (ping, dashboard, etc...). | false | +| metrics.datadog | Datadog metrics exporter type. | false | +| metrics.datadog.addentrypointslabels | Enable metrics on entry points. | true | +| metrics.datadog.address | Datadog's address. | localhost:8125 | +| metrics.datadog.addrouterslabels | Enable metrics on routers. | false | +| metrics.datadog.addserviceslabels | Enable metrics on services. | true | +| metrics.datadog.prefix | Prefix to use for metrics collection. | traefik | +| metrics.datadog.pushinterval | Datadog push interval. | 10 | +| metrics.influxdb2 | InfluxDB v2 metrics exporter type. | false | +| metrics.influxdb2.addentrypointslabels | Enable metrics on entry points. | true | +| metrics.influxdb2.additionallabels._name_ | Additional labels (influxdb tags) on all metrics | | +| metrics.influxdb2.address | InfluxDB v2 address. | http://localhost:8086 | +| metrics.influxdb2.addrouterslabels | Enable metrics on routers. | false | +| metrics.influxdb2.addserviceslabels | Enable metrics on services. | true | +| metrics.influxdb2.bucket | InfluxDB v2 bucket ID. | | +| metrics.influxdb2.org | InfluxDB v2 org ID. | | +| metrics.influxdb2.pushinterval | InfluxDB v2 push interval. | 10 | +| metrics.influxdb2.token | InfluxDB v2 access token. | | +| metrics.otlp | OpenTelemetry metrics exporter type. | false | +| metrics.otlp.addentrypointslabels | Enable metrics on entry points. | true | +| metrics.otlp.addrouterslabels | Enable metrics on routers. | false | +| metrics.otlp.addserviceslabels | Enable metrics on services. | true | +| metrics.otlp.explicitboundaries | Boundaries for latency metrics. | 0.005000, 0.010000, 0.025000, 0.050000, 0.075000, 0.100000, 0.250000, 0.500000, 0.750000, 1.000000, 2.500000, 5.000000, 7.500000, 10.000000 | +| metrics.otlp.grpc | gRPC configuration for the OpenTelemetry collector. | false | +| metrics.otlp.grpc.endpoint | Sets the gRPC endpoint (host:port) of the collector. | localhost:4317 | +| metrics.otlp.grpc.headers._name_ | Headers sent with payload. | | +| metrics.otlp.grpc.insecure | Disables client transport security for the exporter. | false | +| metrics.otlp.grpc.tls.ca | TLS CA | | +| metrics.otlp.grpc.tls.cert | TLS cert | | +| metrics.otlp.grpc.tls.insecureskipverify | TLS insecure skip verify | false | +| metrics.otlp.grpc.tls.key | TLS key | | +| metrics.otlp.http | HTTP configuration for the OpenTelemetry collector. | false | +| metrics.otlp.http.endpoint | Sets the HTTP endpoint (scheme://host:port/path) of the collector. | https://localhost:4318 | +| metrics.otlp.http.headers._name_ | Headers sent with payload. | | +| metrics.otlp.http.tls.ca | TLS CA | | +| metrics.otlp.http.tls.cert | TLS cert | | +| metrics.otlp.http.tls.insecureskipverify | TLS insecure skip verify | false | +| metrics.otlp.http.tls.key | TLS key | | +| metrics.otlp.pushinterval | Period between calls to collect a checkpoint. | 10 | +| metrics.otlp.resourceattributes._name_ | Defines additional resource attributes (key:value). | | +| metrics.otlp.servicename | Defines the service name resource attribute. | traefik | +| metrics.prometheus | Prometheus metrics exporter type. | false | +| metrics.prometheus.addentrypointslabels | Enable metrics on entry points. | true | +| metrics.prometheus.addrouterslabels | Enable metrics on routers. | false | +| metrics.prometheus.addserviceslabels | Enable metrics on services. | true | +| metrics.prometheus.buckets | Buckets for latency metrics. | 0.100000, 0.300000, 1.200000, 5.000000 | +| metrics.prometheus.entrypoint | EntryPoint | traefik | +| metrics.prometheus.headerlabels._name_ | Defines the extra labels for the requests_total metrics, and for each of them, the request header containing the value for this label. | | +| metrics.prometheus.manualrouting | Manual routing | false | +| metrics.statsd | StatsD metrics exporter type. | false | +| metrics.statsd.addentrypointslabels | Enable metrics on entry points. | true | +| metrics.statsd.address | StatsD address. | localhost:8125 | +| metrics.statsd.addrouterslabels | Enable metrics on routers. | false | +| metrics.statsd.addserviceslabels | Enable metrics on services. | true | +| metrics.statsd.prefix | Prefix to use for metrics collection. | traefik | +| metrics.statsd.pushinterval | StatsD push interval. | 10 | +| ocsp | OCSP configuration. | false | +| ocsp.responderoverrides._name_ | Defines a map of OCSP responders to replace for querying OCSP servers. | | +| ping | Enable ping. | false | +| ping.entrypoint | EntryPoint | traefik | +| ping.manualrouting | Manual routing | false | +| ping.terminatingstatuscode | Terminating status code | 503 | +| providers.consul | Enables Consul provider. | false | +| providers.consul.endpoints | KV store endpoints. | 127.0.0.1:8500 | +| providers.consul.namespaces | Sets the namespaces used to discover the configuration (Consul Enterprise only). | | +| providers.consul.rootkey | Root key used for KV store. | traefik | +| providers.consul.tls.ca | TLS CA | | +| providers.consul.tls.cert | TLS cert | | +| providers.consul.tls.insecureskipverify | TLS insecure skip verify | false | +| providers.consul.tls.key | TLS key | | +| providers.consul.token | Per-request ACL token. | | +| providers.consulcatalog | Enables Consul Catalog provider. | false | +| providers.consulcatalog.cache | Use local agent caching for catalog reads. | false | +| providers.consulcatalog.connectaware | Enable Consul Connect support. | false | +| providers.consulcatalog.connectbydefault | Consider every service as Connect capable by default. | false | +| providers.consulcatalog.constraints | Constraints is an expression that Traefik matches against the container's labels to determine whether to create any route for that container. | | +| providers.consulcatalog.defaultrule | Default rule. | Host(`{{ normalize .Name }}`) | +| providers.consulcatalog.endpoint.address | The address of the Consul server | | +| providers.consulcatalog.endpoint.datacenter | Data center to use. If not provided, the default agent data center is used | | +| providers.consulcatalog.endpoint.endpointwaittime | WaitTime limits how long a Watch will block. If not provided, the agent default values will be used | 0 | +| providers.consulcatalog.endpoint.httpauth.password | Basic Auth password | | +| providers.consulcatalog.endpoint.httpauth.username | Basic Auth username | | +| providers.consulcatalog.endpoint.scheme | The URI scheme for the Consul server | | +| providers.consulcatalog.endpoint.tls.ca | TLS CA | | +| providers.consulcatalog.endpoint.tls.cert | TLS cert | | +| providers.consulcatalog.endpoint.tls.insecureskipverify | TLS insecure skip verify | false | +| providers.consulcatalog.endpoint.tls.key | TLS key | | +| providers.consulcatalog.endpoint.token | Token is used to provide a per-request ACL token which overrides the agent's default token | | +| providers.consulcatalog.exposedbydefault | Expose containers by default. | true | +| providers.consulcatalog.namespaces | Sets the namespaces used to discover services (Consul Enterprise only). | | +| providers.consulcatalog.prefix | Prefix for consul service tags. | traefik | +| providers.consulcatalog.refreshinterval | Interval for check Consul API. | 15 | +| providers.consulcatalog.requireconsistent | Forces the read to be fully consistent. | false | +| providers.consulcatalog.servicename | Name of the Traefik service in Consul Catalog (needs to be registered via the orchestrator or manually). | traefik | +| providers.consulcatalog.stale | Use stale consistency for catalog reads. | false | +| providers.consulcatalog.strictchecks | A list of service health statuses to allow taking traffic. | passing, warning | +| providers.consulcatalog.watch | Watch Consul API events. | false | +| providers.docker | Enables Docker provider. | false | +| providers.docker.allowemptyservices | Disregards the Docker containers health checks with respect to the creation or removal of the corresponding services. | false | +| providers.docker.constraints | Constraints is an expression that Traefik matches against the container's labels to determine whether to create any route for that container. | | +| providers.docker.defaultrule | Default rule. | Host(`{{ normalize .Name }}`) | +| providers.docker.endpoint | Docker server endpoint. Can be a TCP or a Unix socket endpoint. | unix:///var/run/docker.sock | +| providers.docker.exposedbydefault | Expose containers by default. | true | +| providers.docker.httpclienttimeout | Client timeout for HTTP connections. | 0 | +| providers.docker.labelmap | Label shorthands. | | +| providers.docker.labelmap[0].from | Shorthand label. | | +| providers.docker.labelmap[0].to | Full label with templates. | | +| providers.docker.labelmap[0].value | Optional override; used instead of user input if set. | | +| providers.docker.network | Default Docker network used. | | +| providers.docker.password | Password for Basic HTTP authentication. | | +| providers.docker.tls.ca | TLS CA | | +| providers.docker.tls.cert | TLS cert | | +| providers.docker.tls.insecureskipverify | TLS insecure skip verify | false | +| providers.docker.tls.key | TLS key | | +| providers.docker.usebindportip | Use the ip address from the bound port, rather than from the inner network. | false | +| providers.docker.username | Username for Basic HTTP authentication. | | +| providers.docker.watch | Watch Docker events. | true | +| providers.ecs | Enables AWS ECS provider. | false | +| providers.ecs.accesskeyid | AWS credentials access key ID to use for making requests. | | +| providers.ecs.autodiscoverclusters | Auto discover cluster. | false | +| providers.ecs.clusters | ECS Cluster names. | default | +| providers.ecs.constraints | Constraints is an expression that Traefik matches against the container's labels to determine whether to create any route for that container. | | +| providers.ecs.defaultrule | Default rule. | Host(`{{ normalize .Name }}`) | +| providers.ecs.ecsanywhere | Enable ECS Anywhere support. | false | +| providers.ecs.exposedbydefault | Expose services by default. | true | +| providers.ecs.healthytasksonly | Determines whether to discover only healthy tasks. | false | +| providers.ecs.refreshseconds | Polling interval (in seconds). | 15 | +| providers.ecs.region | AWS region to use for requests. | | +| providers.ecs.secretaccesskey | AWS credentials access key to use for making requests. | | +| providers.etcd | Enables Etcd provider. | false | +| providers.etcd.endpoints | KV store endpoints. | 127.0.0.1:2379 | +| providers.etcd.password | Password for authentication. | | +| providers.etcd.rootkey | Root key used for KV store. | traefik | +| providers.etcd.tls.ca | TLS CA | | +| providers.etcd.tls.cert | TLS cert | | +| providers.etcd.tls.insecureskipverify | TLS insecure skip verify | false | +| providers.etcd.tls.key | TLS key | | +| providers.etcd.username | Username for authentication. | | +| providers.file.debugloggeneratedtemplate | Enable debug logging of generated configuration template. | false | +| providers.file.directory | Load dynamic configuration from one or more .yml or .toml files in a directory. | | +| providers.file.filename | Load dynamic configuration from a file. | | +| providers.file.watch | Watch provider. | true | +| providers.http | Enables HTTP provider. | false | +| providers.http.endpoint | Load configuration from this endpoint. | | +| providers.http.headers._name_ | Define custom headers to be sent to the endpoint. | | +| providers.http.pollinterval | Polling interval for endpoint. | 5 | +| providers.http.polltimeout | Polling timeout for endpoint. | 5 | +| providers.http.tls.ca | TLS CA | | +| providers.http.tls.cert | TLS cert | | +| providers.http.tls.insecureskipverify | TLS insecure skip verify | false | +| providers.http.tls.key | TLS key | | +| providers.kubernetescrd | Enables Kubernetes CRD provider. | false | +| providers.kubernetescrd.allowcrossnamespace | Allow cross namespace resource reference. | false | +| providers.kubernetescrd.allowemptyservices | Allow the creation of services without endpoints. | false | +| providers.kubernetescrd.allowexternalnameservices | Allow ExternalName services. | false | +| providers.kubernetescrd.certauthfilepath | Kubernetes certificate authority file path (not needed for in-cluster client). | | +| providers.kubernetescrd.disableclusterscoperesources | Disables the lookup of cluster scope resources (incompatible with IngressClasses and NodePortLB enabled services). | false | +| providers.kubernetescrd.endpoint | Kubernetes server endpoint (required for external cluster client). | | +| providers.kubernetescrd.ingressclass | Value of kubernetes.io/ingress.class annotation to watch for. | | +| providers.kubernetescrd.labelselector | Kubernetes label selector to use. | | +| providers.kubernetescrd.namespaces | Kubernetes namespaces. | | +| providers.kubernetescrd.nativelbbydefault | Defines whether to use Native Kubernetes load-balancing mode by default. | false | +| providers.kubernetescrd.throttleduration | Ingress refresh throttle duration | 0 | +| providers.kubernetescrd.token | Kubernetes bearer token (not needed for in-cluster client). It accepts either a token value or a file path to the token. | | +| providers.kubernetesgateway | Enables Kubernetes Gateway API provider. | false | +| providers.kubernetesgateway.certauthfilepath | Kubernetes certificate authority file path (not needed for in-cluster client). | | +| providers.kubernetesgateway.endpoint | Kubernetes server endpoint (required for external cluster client). | | +| providers.kubernetesgateway.experimentalchannel | Toggles Experimental Channel resources support (TCPRoute, TLSRoute...). | false | +| providers.kubernetesgateway.labelselector | Kubernetes label selector to select specific GatewayClasses. | | +| providers.kubernetesgateway.namespaces | Kubernetes namespaces. | | +| providers.kubernetesgateway.nativelbbydefault | Defines whether to use Native Kubernetes load-balancing by default. | false | +| providers.kubernetesgateway.statusaddress.hostname | Hostname used for Kubernetes Gateway status address. | | +| providers.kubernetesgateway.statusaddress.ip | IP used to set Kubernetes Gateway status address. | | +| providers.kubernetesgateway.statusaddress.service | Published Kubernetes Service to copy status addresses from. | | +| providers.kubernetesgateway.statusaddress.service.name | Name of the Kubernetes service. | | +| providers.kubernetesgateway.statusaddress.service.namespace | Namespace of the Kubernetes service. | | +| providers.kubernetesgateway.throttleduration | Kubernetes refresh throttle duration | 0 | +| providers.kubernetesgateway.token | Kubernetes bearer token (not needed for in-cluster client). It accepts either a token value or a file path to the token. | | +| providers.kubernetesingress | Enables Kubernetes Ingress provider. | false | +| providers.kubernetesingress.allowemptyservices | Allow creation of services without endpoints. | false | +| providers.kubernetesingress.allowexternalnameservices | Allow ExternalName services. | false | +| providers.kubernetesingress.certauthfilepath | Kubernetes certificate authority file path (not needed for in-cluster client). | | +| providers.kubernetesingress.disableclusterscoperesources | Disables the lookup of cluster scope resources (incompatible with IngressClasses and NodePortLB enabled services). | false | +| providers.kubernetesingress.disableingressclasslookup | Disables the lookup of IngressClasses (Deprecated, please use DisableClusterScopeResources). | false | +| providers.kubernetesingress.endpoint | Kubernetes server endpoint (required for external cluster client). | | +| providers.kubernetesingress.ingressclass | Value of kubernetes.io/ingress.class annotation or IngressClass name to watch for. | | +| providers.kubernetesingress.ingressendpoint.hostname | Hostname used for Kubernetes Ingress endpoints. | | +| providers.kubernetesingress.ingressendpoint.ip | IP used for Kubernetes Ingress endpoints. | | +| providers.kubernetesingress.ingressendpoint.publishedservice | Published Kubernetes Service to copy status from. | | +| providers.kubernetesingress.labelselector | Kubernetes Ingress label selector to use. | | +| providers.kubernetesingress.namespaces | Kubernetes namespaces. | | +| providers.kubernetesingress.nativelbbydefault | Defines whether to use Native Kubernetes load-balancing mode by default. | false | +| providers.kubernetesingress.strictprefixmatching | Make prefix matching strictly comply with the Kubernetes Ingress specification (path-element-wise matching instead of character-by-character string matching). | false | +| providers.kubernetesingress.throttleduration | Ingress refresh throttle duration | 0 | +| providers.kubernetesingress.token | Kubernetes bearer token (not needed for in-cluster client). It accepts either a token value or a file path to the token. | | +| providers.kubernetesingressnginx | Enables Kubernetes Ingress NGINX provider. | false | +| providers.kubernetesingressnginx.certauthfilepath | Kubernetes certificate authority file path (not needed for in-cluster client). | | +| providers.kubernetesingressnginx.controllerclass | Ingress Class Controller value this controller satisfies. | k8s.io/ingress-nginx | +| providers.kubernetesingressnginx.defaultbackendservice | Service used to serve HTTP requests not matching any known server name (catch-all). Takes the form 'namespace/name'. | | +| providers.kubernetesingressnginx.disablesvcexternalname | Disable support for Services of type ExternalName. | false | +| providers.kubernetesingressnginx.endpoint | Kubernetes server endpoint (required for external cluster client). | | +| providers.kubernetesingressnginx.ingressclass | Name of the ingress class this controller satisfies. | nginx | +| providers.kubernetesingressnginx.ingressclassbyname | Define if Ingress Controller should watch for Ingress Class by Name together with Controller Class. | false | +| providers.kubernetesingressnginx.publishservice | Service fronting the Ingress controller. Takes the form 'namespace/name'. | | +| providers.kubernetesingressnginx.publishstatusaddress | Customized address (or addresses, separated by comma) to set as the load-balancer status of Ingress objects this controller satisfies. | | +| providers.kubernetesingressnginx.throttleduration | Ingress refresh throttle duration. | 0 | +| providers.kubernetesingressnginx.token | Kubernetes bearer token (not needed for in-cluster client). It accepts either a token value or a file path to the token. | | +| providers.kubernetesingressnginx.watchingresswithoutclass | Define if Ingress Controller should also watch for Ingresses without an IngressClass or the annotation specified. | false | +| providers.kubernetesingressnginx.watchnamespace | Namespace the controller watches for updates to Kubernetes objects. All namespaces are watched if this parameter is left empty. | | +| providers.kubernetesingressnginx.watchnamespaceselector | Selector selects namespaces the controller watches for updates to Kubernetes objects. | | +| providers.nomad | Enables Nomad provider. | false | +| providers.nomad.allowemptyservices | Allow the creation of services without endpoints. | false | +| providers.nomad.constraints | Constraints is an expression that Traefik matches against the Nomad service's tags to determine whether to create route(s) for that service. | | +| providers.nomad.defaultrule | Default rule. | Host(`{{ normalize .Name }}`) | +| providers.nomad.endpoint.address | The address of the Nomad server, including scheme and port. | http://127.0.0.1:4646 | +| providers.nomad.endpoint.endpointwaittime | WaitTime limits how long a Watch will block. If not provided, the agent default values will be used | 0 | +| providers.nomad.endpoint.region | Nomad region to use. If not provided, the local agent region is used. | | +| providers.nomad.endpoint.tls.ca | TLS CA | | +| providers.nomad.endpoint.tls.cert | TLS cert | | +| providers.nomad.endpoint.tls.insecureskipverify | TLS insecure skip verify | false | +| providers.nomad.endpoint.tls.key | TLS key | | +| providers.nomad.endpoint.token | Token is used to provide a per-request ACL token. | | +| providers.nomad.exposedbydefault | Expose Nomad services by default. | true | +| providers.nomad.namespaces | Sets the Nomad namespaces used to discover services. | | +| providers.nomad.prefix | Prefix for nomad service tags. | traefik | +| providers.nomad.refreshinterval | Interval for polling Nomad API. | 15 | +| providers.nomad.stale | Use stale consistency for catalog reads. | false | +| providers.nomad.throttleduration | Watch throttle duration. | 0 | +| providers.nomad.watch | Watch Nomad Service events. | false | +| providers.plugin._name_ | Plugins configuration. | | +| providers.providersthrottleduration | Backends throttle duration: minimum duration between 2 events from providers before applying a new configuration. It avoids unnecessary reloads if multiples events are sent in a short amount of time. | 2 | +| providers.redis | Enables Redis provider. | false | +| providers.redis.db | Database to be selected after connecting to the server. | 0 | +| providers.redis.endpoints | KV store endpoints. | 127.0.0.1:6379 | +| providers.redis.password | Password for authentication. | | +| providers.redis.rootkey | Root key used for KV store. | traefik | +| providers.redis.sentinel.latencystrategy | Defines whether to route commands to the closest master or replica nodes (mutually exclusive with RandomStrategy and ReplicaStrategy). | false | +| providers.redis.sentinel.mastername | Name of the master. | | +| providers.redis.sentinel.password | Password for Sentinel authentication. | | +| providers.redis.sentinel.randomstrategy | Defines whether to route commands randomly to master or replica nodes (mutually exclusive with LatencyStrategy and ReplicaStrategy). | false | +| providers.redis.sentinel.replicastrategy | Defines whether to route all commands to replica nodes (mutually exclusive with LatencyStrategy and RandomStrategy). | false | +| providers.redis.sentinel.usedisconnectedreplicas | Use replicas disconnected with master when cannot get connected replicas. | false | +| providers.redis.sentinel.username | Username for Sentinel authentication. | | +| providers.redis.tls.ca | TLS CA | | +| providers.redis.tls.cert | TLS cert | | +| providers.redis.tls.insecureskipverify | TLS insecure skip verify | false | +| providers.redis.tls.key | TLS key | | +| providers.redis.username | Username for authentication. | | +| providers.rest | Enables Rest provider. | false | +| providers.rest.insecure | Activate REST Provider directly on the entryPoint named traefik. | false | +| providers.swarm | Enables Docker Swarm provider. | false | +| providers.swarm.allowemptyservices | Disregards the Docker containers health checks with respect to the creation or removal of the corresponding services. | false | +| providers.swarm.constraints | Constraints is an expression that Traefik matches against the container's labels to determine whether to create any route for that container. | | +| providers.swarm.defaultrule | Default rule. | Host(`{{ normalize .Name }}`) | +| providers.swarm.endpoint | Docker server endpoint. Can be a TCP or a Unix socket endpoint. | unix:///var/run/docker.sock | +| providers.swarm.exposedbydefault | Expose containers by default. | true | +| providers.swarm.httpclienttimeout | Client timeout for HTTP connections. | 0 | +| providers.swarm.labelmap | Label shorthands. | | +| providers.swarm.labelmap[0].from | Shorthand label. | | +| providers.swarm.labelmap[0].to | Full label with templates. | | +| providers.swarm.labelmap[0].value | Optional override; used instead of user input if set. | | +| providers.swarm.network | Default Docker network used. | | +| providers.swarm.password | Password for Basic HTTP authentication. | | +| providers.swarm.refreshseconds | Polling interval for swarm mode. | 15 | +| providers.swarm.tls.ca | TLS CA | | +| providers.swarm.tls.cert | TLS cert | | +| providers.swarm.tls.insecureskipverify | TLS insecure skip verify | false | +| providers.swarm.tls.key | TLS key | | +| providers.swarm.usebindportip | Use the ip address from the bound port, rather than from the inner network. | false | +| providers.swarm.username | Username for Basic HTTP authentication. | | +| providers.swarm.watch | Watch Docker events. | true | +| providers.zookeeper | Enables ZooKeeper provider. | false | +| providers.zookeeper.endpoints | KV store endpoints. | 127.0.0.1:2181 | +| providers.zookeeper.password | Password for authentication. | | +| providers.zookeeper.rootkey | Root key used for KV store. | traefik | +| providers.zookeeper.username | Username for authentication. | | +| serverstransport.forwardingtimeouts.dialtimeout | The amount of time to wait until a connection to a backend server can be established. If zero, no timeout exists. | 30 | +| serverstransport.forwardingtimeouts.idleconntimeout | The maximum period for which an idle HTTP keep-alive connection will remain open before closing itself | 90 | +| serverstransport.forwardingtimeouts.responseheadertimeout | The amount of time to wait for a server's response headers after fully writing the request (including its body, if any). If zero, no timeout exists. | 0 | +| serverstransport.insecureskipverify | Disable SSL certificate verification. | false | +| serverstransport.maxidleconnsperhost | If non-zero, controls the maximum idle (keep-alive) to keep per-host. If zero, DefaultMaxIdleConnsPerHost is used. If negative, disables connection reuse. | 200 | +| serverstransport.rootcas | Add cert file for self-signed certificate. | | +| serverstransport.spiffe | Defines the SPIFFE configuration. | false | +| serverstransport.spiffe.ids | Defines the allowed SPIFFE IDs (takes precedence over the SPIFFE TrustDomain). | | +| serverstransport.spiffe.trustdomain | Defines the allowed SPIFFE trust domain. | | +| spiffe.workloadapiaddr | Defines the workload API address. | | +| tcpserverstransport.dialkeepalive | Defines the interval between keep-alive probes for an active network connection. If zero, keep-alive probes are sent with a default value (currently 15 seconds), if supported by the protocol and operating system. Network protocols or operating systems that do not support keep-alives ignore this field. If negative, keep-alive probes are disabled | 15 | +| tcpserverstransport.dialtimeout | Defines the amount of time to wait until a connection to a backend server can be established. If zero, no timeout exists. | 30 | +| tcpserverstransport.terminationdelay | Defines the delay to wait before fully terminating the connection, after one connected peer has closed its writing capability. | 0 | +| tcpserverstransport.tls | Defines the TLS configuration. | false | +| tcpserverstransport.tls.insecureskipverify | Disables SSL certificate verification. | false | +| tcpserverstransport.tls.rootcas | Defines a list of CA secret used to validate self-signed certificate | | +| tcpserverstransport.tls.spiffe | Defines the SPIFFE TLS configuration. | false | +| tcpserverstransport.tls.spiffe.ids | Defines the allowed SPIFFE IDs (takes precedence over the SPIFFE TrustDomain). | | +| tcpserverstransport.tls.spiffe.trustdomain | Defines the allowed SPIFFE trust domain. | | +| tracing | Tracing configuration. | false | +| tracing.addinternals | Enables tracing for internal services (ping, dashboard, etc...). | false | +| tracing.capturedrequestheaders | Request headers to add as attributes for server and client spans. | | +| tracing.capturedresponseheaders | Response headers to add as attributes for server and client spans. | | +| tracing.globalattributes._name_ | (Deprecated) Defines additional resource attributes (key:value). | | +| tracing.otlp | Settings for OpenTelemetry. | false | +| tracing.otlp.grpc | gRPC configuration for the OpenTelemetry collector. | false | +| tracing.otlp.grpc.endpoint | Sets the gRPC endpoint (host:port) of the collector. | localhost:4317 | +| tracing.otlp.grpc.headers._name_ | Headers sent with payload. | | +| tracing.otlp.grpc.insecure | Disables client transport security for the exporter. | false | +| tracing.otlp.grpc.tls.ca | TLS CA | | +| tracing.otlp.grpc.tls.cert | TLS cert | | +| tracing.otlp.grpc.tls.insecureskipverify | TLS insecure skip verify | false | +| tracing.otlp.grpc.tls.key | TLS key | | +| tracing.otlp.http | HTTP configuration for the OpenTelemetry collector. | false | +| tracing.otlp.http.endpoint | Sets the HTTP endpoint (scheme://host:port/path) of the collector. | https://localhost:4318 | +| tracing.otlp.http.headers._name_ | Headers sent with payload. | | +| tracing.otlp.http.tls.ca | TLS CA | | +| tracing.otlp.http.tls.cert | TLS cert | | +| tracing.otlp.http.tls.insecureskipverify | TLS insecure skip verify | false | +| tracing.otlp.http.tls.key | TLS key | | +| tracing.resourceattributes._name_ | Defines additional resource attributes (key:value). | | +| tracing.safequeryparams | Query params to not redact. | | +| tracing.samplerate | Sets the rate between 0.0 and 1.0 of requests to trace. | 1.000000 | +| tracing.servicename | Defines the service name resource attribute. | traefik | diff --git a/docs/content/reference/install-configuration/entrypoints.md b/docs/content/reference/install-configuration/entrypoints.md index 5b313f58b..9a41e3bc3 100644 --- a/docs/content/reference/install-configuration/entrypoints.md +++ b/docs/content/reference/install-configuration/entrypoints.md @@ -18,11 +18,38 @@ entryPoints: to: websecure scheme: https permanent: true + observability: + accessLogs: false + metrics: false + tracing: false websecure: address: :443 - tls: {} - middlewares: + http: + tls: {} + middlewares: + - auth@kubernetescrd + - strip@kubernetescrd +``` + +```toml tab="File (TOML)" +[entryPoints] + [entryPoints.web] + address = ":80" + [entryPoints.web.http] + [entryPoints.web.http.redirections] + entryPoint = "websecure" + scheme = "https" + permanent = true + [entryPoints.web.observability] + accessLogs = false + metrics = false + tracing = false + + [entryPoints.websecure] + address = ":443" + [entryPoints.websecure.tls] + [entryPoints.websecure.middlewares] - auth@kubernetescrd - strip@kubernetescrd ``` @@ -43,6 +70,9 @@ additionalArguments: - --entryPoints.web.http.redirections.to=websecure - --entryPoints.web.http.redirections.scheme=https - --entryPoints.web.http.redirections.permanent=true + - --entryPoints.web.observability.accessLogs=false + - --entryPoints.web.observability.metrics=false + - --entryPoints.web.observability.tracing=false ``` !!! tip @@ -54,39 +84,40 @@ additionalArguments: ## Configuration Options -| Field | Description | Default | Required | -|:----------------------------------------------------------------|:------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:--------|:---------| -| `address` | Define the port, and optionally the hostname, on which to listen for incoming connections and packets.
It also defines the protocol to use (TCP or UDP).
If no protocol is specified, the default is TCP. The format is:`[host]:port[/tcp\|/udp]`. | - | Yes | -| `accessLogs` | Defines whether a router attached to this EntryPoint produces access-logs by default. Nonetheless, a router defining its own observability configuration will opt-out from this default. | true | No | -| `asDefault` | Mark the `entryPoint` to be in the list of default `entryPoints`.
`entryPoints`in this list are used (by default) on HTTP and TCP routers that do not define their own `entryPoints` option.
More information [here](#asdefault). | false | No | -| `forwardedHeaders.trustedIPs` | Set the IPs or CIDR from where Traefik trusts the forwarded headers information (`X-Forwarded-*`). | - | No | -| `forwardedHeaders.insecure` | Set the insecure mode to always trust the forwarded headers information (`X-Forwarded-*`).
We recommend to use this option only for tests purposes, not in production. | false | No | -| `http.redirections.`
`entryPoint.to` | The target element to enable (permanent) redirecting of all incoming requests on an entry point to another one.
The target element can be an entry point name (ex: `websecure`), or a port (`:443`). | - | Yes | -| `http.redirections.`
`entryPoint.scheme` | The target scheme to use for (permanent) redirection of all incoming requests. | https | No | -| `http.redirections.`
`entryPoint.permanent` | Enable permanent redirecting of all incoming requests on an entry point to another one changing the scheme.
The target element, it can be an entry point name (ex: `websecure`), or a port (`:443`). | false | No | -| `http.redirections.`
`entryPoint.priority` | Default priority applied to the routers attached to the `entryPoint`. | MaxInt32-1 (2147483646) | No | -| `http.encodeQuerySemicolons` | Enable query semicolons encoding.
Use this option to avoid non-encoded semicolons to be interpreted as query parameter separators by Traefik.
When using this option, the non-encoded semicolons characters in query will be transmitted encoded to the backend.
More information [here](#encodequerysemicolons). | false | No | -| `http.sanitizePath` | Defines whether to enable the request path sanitization.
More information [here](#sanitizepath). | false | No | -| `http.middlewares` | Set the list of middlewares that are prepended by default to the list of middlewares of each router associated to the named entry point.
More information [here](#httpmiddlewares). | - | No | -| `http.tls` | Enable TLS on every router attached to the `entryPoint`.
If no certificate are set, a default self-signed certificate is generates by Traefik.
We recommend to not use self signed certificates in production. | - | No | -| `http.tls.options` | Apply TLS options on every router attached to the `entryPoint`.
The TLS options can be overidden per router.
More information in the [dedicated section](../../routing/providers/kubernetes-crd.md#kind-tlsoption). | - | No | -| `http.tls.certResolver` | Apply a certificate resolver on every router attached to the `entryPoint`.
The TLS options can be overidden per router.
More information in the [dedicated section](../install-configuration/tls/certificate-resolvers/overview.md). | - | No | -| `http2.maxConcurrentStreams` | Set the number of concurrent streams per connection that each client is allowed to initiate.
The value must be greater than zero. | 250 | No | -| `http3` | Enable 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.
More information [here](#http3. | - | No | -| `http3.advertisedPort` | Set the UDP port to advertise as the HTTP/3 authority.
It defaults to the entryPoint's address port.
It can be used to override the authority in the `alt-svc` header, for example if the public facing port is different from where Traefik is listening. | - | No | -| `metrics` | Defines whether a router attached to this EntryPoint produces metrics by default. Nonetheless, a router defining its own observability configuration will opt-out from this default. | true | No | -| `proxyProtocol.trustedIPs` | Enable PROXY protocol with Trusted IPs.
Traefik supports [PROXY protocol](https://www.haproxy.org/download/2.0/doc/proxy-protocol.txt) version 1 and 2.
If PROXY protocol header parsing is enabled for the entry point, this entry point can accept connections with or without PROXY protocol headers.
If the PROXY protocol header is passed, then the version is determined automatically.
More information [here](#proxyprotocol-and-load-balancers). | - | No | -| `proxyProtocol.insecure` | Enable PROXY protocol trusting every incoming connection.
Every remote client address will be replaced (`trustedIPs`) won't have any effect).
Traefik supports [PROXY protocol](https://www.haproxy.org/download/2.0/doc/proxy-protocol.txt) version 1 and 2.
If PROXY protocol header parsing is enabled for the entry point, this entry point can accept connections with or without PROXY protocol headers.
If the PROXY protocol header is passed, then the version is determined automatically.
We recommend to use this option only for tests purposes, not in production.
More information [here](#proxyprotocol-and-load-balancers). | - | No | -| `reusePort` | Enable `entryPoints` from the same or different processes listening on the same TCP/UDP port by utilizing the `SO_REUSEPORT` socket option.
It also allows the kernel to act like a load balancer to distribute incoming connections between entry points..
More information [here](#reuseport). | false | No | -| `tracing` | Defines whether a router attached to this EntryPoint produces traces by default. Nonetheless, a router defining its own observability configuration will opt-out from this default. | true | No | -| `transport.`
`respondingTimeouts.`
`readTimeout` | Set the timeouts for incoming requests to the Traefik instance. This is the maximum duration for reading the entire request, including the body. Setting them has no effect for UDP `entryPoints`.
If zero, no timeout exists.
Can be provided in a format supported by [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration) or as raw values (digits).
If no units are provided, the value is parsed assuming seconds. | 60s (seconds) | No | -| `transport.`
`respondingTimeouts.`
`writeTimeout` | Maximum duration before timing out writes of the response.
It covers the time from the end of the request header read to the end of the response write.
If zero, no timeout exists.
Can be provided in a format supported by [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration) or as raw values (digits).
If no units are provided, the value is parsed assuming seconds. | 0s (seconds) | No | -| `transport.`
`respondingTimeouts.`
`idleTimeout` | Maximum duration an idle (keep-alive) connection will remain idle before closing itself.
If zero, no timeout exists
Can be provided in a format supported by [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration) or as raw values (digits).
If no units are provided, the value is parsed assuming seconds | 180s (seconds) | No | -| `transport.`
`lifeCycle.`
`graceTimeOut` | Set the duration to give active requests a chance to finish before Traefik stops.
Can be provided in a format supported by [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration) or as raw values (digits).
If no units are provided, the value is parsed assuming seconds
In this time frame no new requests are accepted. | 10s (seconds) | No | -| `transport.`
`lifeCycle.`
`requestAcceptGraceTimeout` | Set the duration to keep accepting requests prior to initiating the graceful termination period (as defined by the `transportlifeCycle.graceTimeOut` option).
This option is meant to give downstream load-balancers sufficient time to take Traefik out of rotation.
Can be provided in a format supported by [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration) or as raw values (digits).
If no units are provided, the value is parsed assuming seconds | 0s (seconds) | No | -| `transport.`
`keepAliveMaxRequests` | Set the maximum number of requests Traefik can handle before sending a `Connection: Close` header to the client (for HTTP2, Traefik sends a GOAWAY).
Zero means no limit. | 0 | No | -| `transport.`
`keepAliveMaxTime` | Set the maximum duration Traefik can handle requests before sending a `Connection: Close` header to the client (for HTTP2, Traefik sends a GOAWAY). Zero means no limit. | 0s (seconds) | No | -| `udp.timeout` | Define how long to wait on an idle session before releasing the related resources.
The Timeout value must be greater than zero. | 3s (seconds)| No | +| Field | Description | Default | Required | +|:----------------------------------------------------------------|:------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:------------------------|:---------| +| `address` | Define the port, and optionally the hostname, on which to listen for incoming connections and packets.
It also defines the protocol to use (TCP or UDP).
If no protocol is specified, the default is TCP. The format is:`[host]:port[/tcp\|/udp] | - | Yes | +| `asDefault` | Mark the `entryPoint` to be in the list of default `entryPoints`.
`entryPoints`in this list are used (by default) on HTTP and TCP routers that do not define their own `entryPoints` option.
More information [here](#asdefault). | false | No | +| `forwardedHeaders.trustedIPs` | Set the IPs or CIDR from where Traefik trusts the forwarded headers information (`X-Forwarded-*`). | - | No | +| `forwardedHeaders.insecure` | Set the insecure mode to always trust the forwarded headers information (`X-Forwarded-*`).
We recommend to use this option only for tests purposes, not in production. | false | No | +| `http.redirections.`
`entryPoint.to`
| The target element to enable (permanent) redirecting of all incoming requests on an entry point to another one.
The target element can be an entry point name (ex: `websecure`), or a port (`:443`). | - | Yes | +| `http.redirections.`
`entryPoint.scheme`
| The target scheme to use for (permanent) redirection of all incoming requests. | https | No | +| `http.redirections.`
`entryPoint.permanent`
| Enable permanent redirecting of all incoming requests on an entry point to another one changing the scheme.
The target element, it can be an entry point name (ex: `websecure`), or a port (`:443`). | false | No | +| `http.redirections.`
`entryPoint.priority`
| Default priority applied to the routers attached to the `entryPoint`. | MaxInt32-1 (2147483646) | No | +| `http.encodeQuerySemicolons` | Enable query semicolons encoding.
Use this option to avoid non-encoded semicolons to be interpreted as query parameter separators by Traefik.
When using this option, the non-encoded semicolons characters in query will be transmitted encoded to the backend.
More information [here](#encodequerysemicolons). | false | No | +| `http.sanitizePath` | Defines whether to enable the request path sanitization.
More information [here](#sanitizepath). | false | No | +| `http.middlewares` | Set the list of middlewares that are prepended by default to the list of middlewares of each router associated to the named entry point.
More information [here](#httpmiddlewares). | - | No | +| `http.tls` | Enable TLS on every router attached to the `entryPoint`.
If no certificate are set, a default self-signed certificate is generated by Traefik.
We recommend to not use self signed certificates in production. | - | No | +| `http.tls.options` | Apply TLS options on every router attached to the `entryPoint`.
The TLS options can be overidden per router.
More information in the [dedicated section](../../routing/providers/kubernetes-crd.md#kind-tlsoption). | - | No | +| `http.tls.certResolver` | Apply a certificate resolver on every router attached to the `entryPoint`.
The TLS options can be overidden per router.
More information in the [dedicated section](../install-configuration/tls/certificate-resolvers/overview.md). | - | No | +| `http2.maxConcurrentStreams` | Set the number of concurrent streams per connection that each client is allowed to initiate.
The value must be greater than zero. | 250 | No | +| `http3` | Enable 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.
More information [here](#http3). | - | No | +| `http3.advertisedPort` | Set the UDP port to advertise as the HTTP/3 authority.
It defaults to the entryPoint's address port.
It can be used to override the authority in the `alt-svc` header, for example if the public facing port is different from where Traefik is listening. | - | No | +| `observability.accessLogs` | Defines whether a router attached to this EntryPoint produces access-logs by default. Nonetheless, a router defining its own observability configuration will opt-out from this default. | true | No | +| `observability.metrics` | Defines whether a router attached to this EntryPoint produces metrics by default. Nonetheless, a router defining its own observability configuration will opt-out from this default. | true | No | +| `observability.tracing` | Defines whether a router attached to this EntryPoint produces traces by default. Nonetheless, a router defining its own observability configuration will opt-out from this default. | true | No | +| `observability.traceVerbosity` | Defines the tracing verbosity level for routers attached to this EntryPoint. Possible values: `minimal` (default), `detailed`. Routers can override this value in their own observability configuration.
More information [here](#traceverbosity). | minimal | No | +| `proxyProtocol.trustedIPs` | Enable PROXY protocol with Trusted IPs.
Traefik supports [PROXY protocol](https://www.haproxy.org/download/2.0/doc/proxy-protocol.txt) version 1 and 2.
If PROXY protocol header parsing is enabled for the entry point, this entry point can accept connections with or without PROXY protocol headers.
If the PROXY protocol header is passed, then the version is determined automatically.
More information [here](#proxyprotocol-and-load-balancers). | - | No | +| `proxyProtocol.insecure` | Enable PROXY protocol trusting every incoming connection.
Every remote client address will be replaced (`trustedIPs`) won't have any effect).
Traefik supports [PROXY protocol](https://www.haproxy.org/download/2.0/doc/proxy-protocol.txt) version 1 and 2.
If PROXY protocol header parsing is enabled for the entry point, this entry point can accept connections with or without PROXY protocol headers.
If the PROXY protocol header is passed, then the version is determined automatically.
We recommend to use this option only for tests purposes, not in production.
More information [here](#proxyprotocol-and-load-balancers). | - | No | +| `reusePort` | Enable `entryPoints` from the same or different processes listening on the same TCP/UDP port by utilizing the `SO_REUSEPORT` socket option.
It also allows the kernel to act like a load balancer to distribute incoming connections between entry points.
More information [here](#reuseport). | false | No | +| `transport.`
`respondingTimeouts.`
`readTimeout`
| Set the timeouts for incoming requests to the Traefik instance. This is the maximum duration for reading the entire request, including the body. Setting them has no effect for UDP `entryPoints`.
If zero, no timeout exists.
Can be provided in a format supported by [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration) or as raw values (digits).
If no units are provided, the value is parsed assuming seconds. | 60s (seconds) | No | +| `transport.`
`respondingTimeouts.`
`writeTimeout`
| Maximum duration before timing out writes of the response.
It covers the time from the end of the request header read to the end of the response write.
If zero, no timeout exists.
Can be provided in a format supported by [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration) or as raw values (digits).
If no units are provided, the value is parsed assuming seconds. | 0s (seconds) | No | +| `transport.`
`respondingTimeouts.`
`idleTimeout`
| Maximum duration an idle (keep-alive) connection will remain idle before closing itself.
If zero, no timeout exists
Can be provided in a format supported by [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration) or as raw values (digits).
If no units are provided, the value is parsed assuming seconds | 180s (seconds) | No | +| `transport.`
`lifeCycle.`
`graceTimeOut`
| Set the duration to give active requests a chance to finish before Traefik stops.
Can be provided in a format supported by [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration) or as raw values (digits).
If no units are provided, the value is parsed assuming seconds
In this time frame no new requests are accepted. | 10s (seconds) | No | +| `transport.`
`lifeCycle.`
`requestAcceptGraceTimeout`
| Set the duration to keep accepting requests prior to initiating the graceful termination period (as defined by the `transportlifeCycle.graceTimeOut` option).
This option is meant to give downstream load-balancers sufficient time to take Traefik out of rotation.
Can be provided in a format supported by [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration) or as raw values (digits).
If no units are provided, the value is parsed assuming seconds | 0s (seconds) | No | +| `transport.`
`keepAliveMaxRequests`
| Set the maximum number of requests Traefik can handle before sending a `Connection: Close` header to the client (for HTTP2, Traefik sends a GOAWAY).
Zero means no limit. | 0 | No | +| `transport.`
`keepAliveMaxTime`
| Set the maximum duration Traefik can handle requests before sending a `Connection: Close` header to the client (for HTTP2, Traefik sends a GOAWAY). Zero means no limit. | 0s (seconds) | No | +| `udp.timeout` | Define how long to wait on an idle session before releasing the related resources.
The Timeout value must be greater than zero. | 3s (seconds) | No | ### asDefault @@ -121,18 +152,20 @@ are applied after the ones declared on the Entrypoint) entryPoints: web: address: :80 - middlewares: - - auth@kubernetescrd - - strip@file + http: + middlewares: + - auth@kubernetescrd + - strip@file ``` ```yaml tab="Helm Chart Values" ports: web: port: :80 - middlewares: - - auth@kubernetescrd - - strip@file + http: + middlewares: + - auth@kubernetescrd + - strip@file ``` ### encodeQuerySemicolons @@ -141,10 +174,10 @@ Behavior examples: | EncodeQuerySemicolons | Request Query | Resulting Request Query | |-----------------------|---------------------|-------------------------| -| false | foo=bar;baz=bar | foo=bar&baz=bar | -| true | foo=bar;baz=bar | foo=bar%3Bbaz=bar | -| false | foo=bar&baz=bar;foo | foo=bar&baz=bar&foo | -| true | foo=bar&baz=bar;foo | foo=bar&baz=bar%3Bfoo | +| false | foo=bar;baz=bar | foo=bar&baz=bar | +| true | foo=bar;baz=bar | foo=bar%3Bbaz=bar | +| false | foo=bar&baz=bar;foo | foo=bar&baz=bar&foo | +| true | foo=bar&baz=bar;foo | foo=bar&baz=bar%3Bfoo | ### SanitizePath @@ -164,14 +197,14 @@ it can lead to unsafe routing when the `sanitizePath` option is set to `false`. | SanitizePath | Request Path | Resulting Request Path | |--------------|-----------------|------------------------| -| false | /./foo/bar | /./foo/bar | -| true | /./foo/bar | /foo/bar | -| false | /foo/../bar | /foo/../bar | -| true | /foo/../bar | /bar | -| false | /foo/bar// | /foo/bar// | -| true | /foo/bar// | /foo/bar/ | -| false | /./foo/../bar// | /./foo/../bar// | -| true | /./foo/../bar// | /bar/ | +| false | /./foo/bar | /./foo/bar | +| true | /./foo/bar | /foo/bar | +| false | /foo/../bar | /foo/../bar | +| true | /foo/../bar | /bar | +| false | /foo/bar// | /foo/bar// | +| true | /foo/bar// | /foo/bar/ | +| false | /./foo/../bar// | /./foo/../bar// | +| true | /./foo/../bar// | /bar/ | ### HTTP3 @@ -184,7 +217,7 @@ only routers with TLS enabled will be usable with HTTP/3. ### ProxyProtocol and Load-Balancers -The replacement of the remote client address will occur only for IP addresses listed in `trustedIPs`. This is where yoåu specify your load balancer IPs or CIDR ranges. +The replacement of the remote client address will occur only for IP addresses listed in `trustedIPs`. This is where you specify your load balancer IPs or CIDR ranges. When queuing Traefik behind another load-balancer, make sure to configure PROXY protocol on both sides. @@ -242,3 +275,13 @@ Use the `reusePort` option with the other option `transport.lifeCycle.gracetimeo to do canary deployments against Traefik itself. Like upgrading Traefik version or reloading the static configuration without any service downtime. + +#### Trace Verbosity + +`observability.traceVerbosity` defines the tracing verbosity level for routers attached to this EntryPoint. +Routers can override this value in their own observability configuration. + +Possible values are: + +- `minimal`: produces a single server span and one client span for each request processed by a router. +- `detailed`: enables the creation of additional spans for each middleware executed for each request processed by a router. diff --git a/docs/content/reference/install-configuration/observability/healthcheck.md b/docs/content/reference/install-configuration/observability/healthcheck.md index 742480286..d207095c1 100644 --- a/docs/content/reference/install-configuration/observability/healthcheck.md +++ b/docs/content/reference/install-configuration/observability/healthcheck.md @@ -36,7 +36,7 @@ whose default value is `traefik` (port `8080`). | Path | Method | Description | |---------|---------------|-----------------------------------------------------------------------------------------------------| -| `/ping` | `GET`, `HEAD` | An endpoint to check for Traefik process liveness. Return a code `200` with the content: `OK` | +| `/ping` | `GET`, `HEAD` | An endpoint to check for Traefik process liveness. Return a code `200` with the content: `OK` | ### Configuration Example @@ -58,9 +58,9 @@ ping: {} | Field | Description | Default | Required | |:------|:----------------------------------------------------------|:---------------------|:---------| -| `ping.entryPoint` | Enables `/ping` on a dedicated EntryPoint. | traefik | No | -| `ping.manualRouting` | Disables the default internal router in order to allow one to create a custom router for the `ping@internal` service when set to `true`. | false | No | -| `ping.terminatingStatusCode` | Defines the status code for the ping handler during a graceful shut down. See more information [here](#terminatingstatuscode) | 503 | No | +| `ping.entryPoint` | Enables `/ping` on a dedicated EntryPoint. | traefik | No | +| `ping.manualRouting` | Disables the default internal router in order to allow one to create a custom router for the `ping@internal` service when set to `true`. | false | No | +| `ping.terminatingStatusCode` | Defines the status code for the ping handler during a graceful shut down. See more information [here](#terminatingstatuscode) | 503 | No | #### `terminatingStatusCode` diff --git a/docs/content/reference/install-configuration/observability/healthcheck/cli.md b/docs/content/reference/install-configuration/observability/healthcheck/cli.md new file mode 100644 index 000000000..a68f80d83 --- /dev/null +++ b/docs/content/reference/install-configuration/observability/healthcheck/cli.md @@ -0,0 +1,28 @@ +--- +title: "Traefik Health Check CLI Command Documentation" +description: "In Traefik Proxy, the healthcheck CLI command lets you check the health of your Traefik instances. Read the technical documentation for configuration examples and options." +--- + +# Healthcheck Command + +Checking the Health of your Traefik Instances. +{: .subtitle } + +## Usage + +The healthcheck command allows you to make a request to the `/ping` endpoint (defined in the install (static) configuration) to check the health of Traefik. Its exit status is `0` if Traefik is healthy and `1` otherwise. + +This can be used with [HEALTHCHECK](https://docs.docker.com/engine/reference/builder/#healthcheck) instruction or any other health check orchestration mechanism. + +```sh +traefik healthcheck [command] [flags] [arguments] +``` + +Example: + +```sh +$ traefik healthcheck +OK: http://:8082/ping +``` + +The command uses the [ping](./ping.md) endpoint that is defined in the Traefik install (static) configuration. diff --git a/docs/content/reference/install-configuration/observability/healthcheck/ping.md b/docs/content/reference/install-configuration/observability/healthcheck/ping.md new file mode 100644 index 000000000..7b8372129 --- /dev/null +++ b/docs/content/reference/install-configuration/observability/healthcheck/ping.md @@ -0,0 +1,66 @@ +--- +title: "Traefik Ping Option Documentation" +description: "In Traefik Proxy, the option Ping lets you check the health of your Traefik instances. Read the technical documentation for configuration examples and options." +--- + +# Ping + +Checking the Health of your Traefik Instances +{: .subtitle } + +The `ping` options allows you to enable the ping endpoint to check Traefik liveness. + +The ping endpoint is reachable using the path `/ping` and the methods `GET`and `HEAD`. + +If the Traefik instance is alive, it returns the `200` HTTP code with the content: `OK`. + +## Configuration Example + +To enable the API handler: + +```yaml tab="File (YAML)" +ping: {} +``` + +```toml tab="File (TOML)" +[ping] +``` + +```bash tab="CLI" +--ping=true +``` + +## Configuration Options + +The `ping` option is defined in the install (static) configuration. +You can define it using the same [configuration methods](../../boot-environment.md#configuration-methods) as Traefik. + +| Field | Description | Default | Required | +|:------|:----------------------------------------------------------|:---------------------|:---------| +| `ping.entryPoint` | Enables `/ping` on a dedicated EntryPoint. | traefik | No | +| `ping.manualRouting` | Disables the default internal router in order to allow one to create a custom router for the `ping@internal` service when set to `true`. | false | No | +| `ping.terminatingStatusCode` | Defines the status code for the ping handler during a graceful shut down. See more information [here](#terminatingstatuscode) | 503 | No | + +### `terminatingStatusCode` + +During the period in which Traefik is gracefully shutting down, the ping handler +returns a `503` status code by default. +If Traefik is behind, for example a load-balancer +doing health checks (such as the Kubernetes LivenessProbe), another code might +be expected as the signal for graceful termination. +In that case, the terminatingStatusCode can be used to set the code returned by the ping +handler during termination. + +```yaml tab="File (YAML)" +ping: + terminatingStatusCode: 204 +``` + +```toml tab="File (TOML)" +[ping] + terminatingStatusCode = 204 +``` + +```bash tab="CLI" +--ping.terminatingStatusCode=204 +``` diff --git a/docs/content/reference/install-configuration/observability/logs-and-accesslogs.md b/docs/content/reference/install-configuration/observability/logs-and-accesslogs.md index d13226ad4..7fab52d14 100644 --- a/docs/content/reference/install-configuration/observability/logs-and-accesslogs.md +++ b/docs/content/reference/install-configuration/observability/logs-and-accesslogs.md @@ -35,17 +35,112 @@ The section below describe how to configure Traefik logs using the static config | Field | Description | Default | Required | |:-----------|:----------------------------|:--------|:---------| -| `log.filePath` | By default, the logs are written to the standard output.
You can configure a file path instead using the `filePath` option.| - | No | -| `log.format` | Log format (`common`or `json`).
The fields displayed with the format `common` cannot be customized. | "common" | No | -| `log.level` | Log level (`TRACE`, `DEBUG`, `INFO`, `WARN`, `ERROR`, `FATAL`, and `PANIC`)| ERROR | No | -| `log.noColor` | When using the format `common`, disables the colorized output. | false | No | -| `log.maxSize` | Maximum size in megabytes of the log file before it gets rotated. | 100MB | No | -| `log.maxAge` | Maximum number of days to retain old log files based on the timestamp encoded in their filename.
A day is defined as 24 hours and may not exactly correspond to calendar days due to daylight savings, leap seconds, etc.
By default files are not removed based on their age. | 0 | No | -| `log.maxBackups` | Maximum number of old log files to retain.
The default is to retain all old log files. | 0 | No | -| `log.compress` | Compress log files in gzip after rotation. | false | No | +| `log.filePath` | By default, the logs are written to the standard output.
You can configure a file path instead using the `filePath` option. When `filePath` is specified, Traefik will write logs only to that file (not to standard output).| - | No | +| `log.format` | Log format (`common`or `json`).
The fields displayed with the format `common` cannot be customized. | "common" | No | +| `log.level` | Log level (`TRACE`, `DEBUG`, `INFO`, `WARN`, `ERROR`, `FATAL`, and `PANIC`)| ERROR | No | +| `log.noColor` | When using the format `common`, disables the colorized output. | false | No | +| `log.maxSize` | Maximum size in megabytes of the log file before it gets rotated. | 100MB | No | +| `log.maxAge` | Maximum number of days to retain old log files based on the timestamp encoded in their filename.
A day is defined as 24 hours and may not exactly correspond to calendar days due to daylight savings, leap seconds, etc.
By default files are not removed based on their age. | 0 | No | +| `log.maxBackups` | Maximum number of old log files to retain.
The default is to retain all old log files. | 0 | No | +| `log.compress` | Compress log files in gzip after rotation. | false | No | + +### OpenTelemetry + +Traefik supports OpenTelemetry for logging. To enable OpenTelemetry, you need to set the following in the static configuration: + +```yaml tab="File (YAML)" +experimental: + otlpLogs: true +``` + +```toml tab="File (TOML)" +[experimental] + otlpLogs = true +``` + +```sh tab="CLI" +--experimental.otlpLogs=true +``` + +!!! warning + This is an experimental feature. + +!!! note "Stdio logs remain available" + When OTLP logging is enabled, standard output (stdio) logs are still available and will continue to be written alongside OTLP exports. + +#### Configuration Example + +```yaml tab="File (YAML)" +experimental: + otlpLogs: true + +log: + otlp: + http: + endpoint: https://collector:4318/v1/logs + headers: + Authorization: Bearer auth_asKXRhIMplM7El1JENjrotGouS1LYRdL +``` + +```toml tab="File (TOML)" +[experimental] + otlpLogs = true + +[log.otlp] + http.endpoint = "https://collector:4318/v1/logs" + http.headers.Authorization = "Bearer auth_asKXRhIMplM7El1JENjrotGouS1LYRdL" +``` + +```sh tab="CLI" +--experimental.otlpLogs=true +--log.otlp.http.endpoint=https://collector:4318/v1/logs +--log.otlp.http.headers.Authorization=Bearer auth_asKXRhIMplM7El1JENjrotGouS1LYRdL +``` + +#### Configuration Options + +| Field | Description | Default | Required | +|:---------------------------------------|:---------------------------------------------------------------------------------------------------------------------------------------|:---------------------------------|:---------| +| `log.otlp.serviceName` | Service name used in selected backend. | "traefik" | No | +| `log.otlp.resourceAttributes` | Defines additional resource attributes to be sent to the collector. See [resourceAttributes](#resourceattributes) for details. | [] | No | +| `log.otlp.http` | This instructs the exporter to send logs to the OpenTelemetry Collector using HTTP. | | No | +| `log.otlp.http.endpoint` | The endpoint of the OpenTelemetry Collector. (format=`://:`) | `https://localhost:4318/v1/logs` | No | +| `log.otlp.http.headers` | Additional headers sent with logs by the exporter to the OpenTelemetry Collector. | [ ] | No | +| `log.otlp.http.tls` | Defines the Client TLS configuration used by the exporter to send logs to the OpenTelemetry Collector. | | No | +| `log.otlp.http.tls.ca` | The path to the certificate authority used for the secure connection to the OpenTelemetry Collector, it defaults to the system bundle. | | No | +| `log.otlp.http.tls.cert` | The path to the certificate to use for the OpenTelemetry Collector. | | No | +| `log.otlp.http.tls.key` | The path to the key to use for the OpenTelemetry Collector. | | No | +| `log.otlp.http.tls.insecureSkipVerify` | Instructs the OpenTelemetry Collector to accept any certificate presented by the server regardless of the hostname in the certificate. | false | No | +| `log.otlp.grpc` | This instructs the exporter to send logs to the OpenTelemetry Collector using gRPC. | | No | +| `log.otlp.grpc.endpoint` | The endpoint of the OpenTelemetry Collector. (format=`:`) | `localhost:4317` | No | +| `log.otlp.grpc.headers` | Additional headers sent with logs by the exporter to the OpenTelemetry Collector. | [ ] | No | +| `log.otlp.grpc.insecure` | Instructs the exporter to send logs to the OpenTelemetry Collector using an insecure protocol. | false | No | +| `log.otlp.grpc.tls` | Defines the Client TLS configuration used by the exporter to send logs to the OpenTelemetry Collector. | | No | +| `log.otlp.grpc.tls.ca` | The path to the certificate authority used for the secure connection to the OpenTelemetry Collector, it defaults to the system bundle. | | No | +| `log.otlp.grpc.tls.cert` | The path to the certificate to use for the OpenTelemetry Collector. | | No | +| `log.otlp.grpc.tls.key` | The path to the key to use for the OpenTelemetry Collector. | | No | +| `log.otlp.grpc.tls.insecureSkipVerify` | Instructs the OpenTelemetry Collector to accept any certificate presented by the server regardless of the hostname in the certificate. | false | No | + +#### resourceAttributes + +The `resourceAttributes` option allows setting the resource attributes sent along the traces. +Traefik also supports the `OTEL_RESOURCE_ATTRIBUTES` env variable to set up the resource attributes. + +!!! info "Kubernetes Resource Attributes Detection" + + Additionally, Traefik automatically discovers the following [Kubernetes resource attributes](https://opentelemetry.io/docs/specs/semconv/non-normative/k8s-attributes/) when running in a Kubernetes cluster: + + - `k8s.namespace.name` + - `k8s.pod.uid` + - `k8s.pod.name` + + Note that this automatic detection can fail, like if the Traefik pod is running in host network mode. + In this case, you should provide the attributes with the option or the env variable. ## AccessLogs +Access logs concern everything that happens to the requests handled by Traefik. + ### Configuration Example ```yaml tab="File (YAML)" @@ -117,21 +212,112 @@ The section below describes how to configure Traefik access logs using the stati | Field | Description | Default | Required | |:-----------|:--------------------------|:--------|:---------| -| `accesslog.filePath` | By default, the access logs are written to the standard output.
You can configure a file path instead using the `filePath` option.| | No | -| `accesslog.format` | By default, logs are written using the Common Log Format (CLF).
To write logs in JSON, use `json` in the `format` option.
If the given format is unsupported, the default (CLF) is used instead.
More information about CLF fields [here](#clf-format-fields). | "common" | No | -| `accesslog.bufferingSize` | To write the logs in an asynchronous fashion, specify a `bufferingSize` option.
This option represents the number of log lines Traefik will keep in memory before writing them to the selected output.
In some cases, this option can greatly help performances.| 0 | No | -| `accesslog.addInternals` | Enables access logs for internal resources (e.g.: `ping@internal`). | false | No | -| `accesslog.filters.statusCodes` | Limit the access logs to requests with a status codes in the specified range. | false | No | -| `accesslog.filters.retryAttempts` | Keep the access logs when at least one retry has happened. | false | No | -| `accesslog.filters.minDuration` | Keep access logs when requests take longer than the specified duration (provided in seconds or as a valid duration format, see [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration)). | 0 | No | -| `accesslog.fields.defaultMode` | Mode to apply by default to the access logs fields (`keep`, `redact` or `drop`). | keep | No | -| `accesslog.fields.names` | Set the fields list to display in the access logs (format `name:mode`).
Available fields list [here](#available-fields). | - | No | -| `accesslog.headers.defaultMode` | Mode to apply by default to the access logs headers (`keep`, `redact` or `drop`). | drop | No | -| `accesslog.headers.names` | Set the headers list to display in the access logs (format `name:mode`). | - | No | +| `accesslog.filePath` | By default, the access logs are written to the standard output.
You can configure a file path instead using the `filePath` option.| | No | +| `accesslog.format` | By default, logs are written using the Traefik Common Log Format (CLF).
Available formats: [`common`](#traefik-clf-format-fields) (Traefik extended CLF), [`genericCLF`](#generic-clf-format-fields) (standard CLF compatible with analyzers), or [`json`](#json-format-fields).
If the given format is unsupported, the default (`common`) is used instead. | "common" | No | +| `accesslog.bufferingSize` | To write the logs in an asynchronous fashion, specify a `bufferingSize` option.
This option represents the number of log lines Traefik will keep in memory before writing them to the selected output.
In some cases, this option can greatly help performances.| 0 | No | +| `accesslog.addInternals` | Enables access logs for internal resources (e.g.: `ping@internal`). | false | No | +| `accesslog.filters.statusCodes` | Limit the access logs to requests with a status codes in the specified range. | [ ] | No | +| `accesslog.filters.retryAttempts` | Keep the access logs when at least one retry has happened. | false | No | +| `accesslog.filters.minDuration` | Keep access logs when requests take longer than the specified duration (provided in seconds or as a valid duration format, see [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration)). | 0 | No | +| `accesslog.fields.defaultMode` | Mode to apply by default to the access logs fields (`keep`, `redact` or `drop`). | keep | No | +| `accesslog.fields.names` | Set the fields list to display in the access logs (format `name:mode`).
Available fields list [here](#json-format-fields). | [ ] | No | +| `accesslog.fields.headers.defaultMode` | Mode to apply by default to the access logs headers (`keep`, `redact` or `drop`). | drop | No | +| `accesslog.fields.headers.names` | Set the headers list to display in the access logs (format `name:mode`). | [ ] | No | -#### CLF format fields +### OpenTelemetry -Below the fields displayed with the CLF format: +Traefik supports OpenTelemetry for access logs. To enable OpenTelemetry, you need to set the following in the static configuration: + +```yaml tab="File (YAML)" +experimental: + otlpLogs: true +``` + +```toml tab="File (TOML)" +[experimental] + otlpLogs = true +``` + +```sh tab="CLI" +--experimental.otlpLogs=true +``` + +!!! warning + This is an experimental feature. + +#### Configuration Example + +```yaml tab="File (YAML)" +experimental: + otlpLogs: true + +accesslog: + otlp: + http: + endpoint: https://collector:4318/v1/logs + headers: + Authorization: Bearer auth_asKXRhIMplM7El1JENjrotGouS1LYRdL +``` + +```toml tab="File (TOML)" +[experimental] + otlpLogs = true + +[accesslog.otlp] + http.endpoint = "https://collector:4318/v1/logs" + http.headers.Authorization = "Bearer auth_asKXRhIMplM7El1JENjrotGouS1LYRdL" +``` + +```yaml tab="CLI" +--experimental.otlpLogs=true +--accesslog.otlp.http.endpoint=https://collector:4318/v1/logs +--accesslog.otlp.http.headers.Authorization=Bearer auth_asKXRhIMplM7El1JENjrotGouS1LYRdL +``` + +#### Configuration Options + +| Field | Description | Default | Required | +|:---------------------------------------------|:---------------------------------------------------------------------------------------------------------------------------------------|:---------------------------------|:---------| +| `accesslog.otlp.serviceName` | Defines the service name resource attribute. | "traefik" | No | +| `accesslog.otlp.resourceAttributes` | Defines additional resource attributes to be sent to the collector. See [resourceAttributes](#resourceattributes_1) for details. | [] | No | +| `accesslog.otlp.http` | This instructs the exporter to send access logs to the OpenTelemetry Collector using HTTP. | | No | +| `accesslog.otlp.http.endpoint` | The endpoint of the OpenTelemetry Collector. (format=`://:`) | `https://localhost:4318/v1/logs` | No | +| `accesslog.otlp.http.headers` | Additional headers sent with access logs by the exporter to the OpenTelemetry Collector. | [ ] | No | +| `accesslog.otlp.http.tls` | Defines the Client TLS configuration used by the exporter to send access logs to the OpenTelemetry Collector. | | No | +| `accesslog.otlp.http.tls.ca` | The path to the certificate authority used for the secure connection to the OpenTelemetry Collector, it defaults to the system bundle. | | No | +| `accesslog.otlp.http.tls.cert` | The path to the certificate to use for the OpenTelemetry Collector. | | No | +| `accesslog.otlp.http.tls.key` | The path to the key to use for the OpenTelemetry Collector. | | No | +| `accesslog.otlp.http.tls.insecureSkipVerify` | Instructs the OpenTelemetry Collector to accept any certificate presented by the server regardless of the hostname in the certificate. | false | No | +| `accesslog.otlp.grpc` | This instructs the exporter to send access logs to the OpenTelemetry Collector using gRPC. | | No | +| `accesslog.otlp.grpc.endpoint` | The endpoint of the OpenTelemetry Collector. (format=`:`) | `localhost:4317` | No | +| `accesslog.otlp.grpc.headers` | Additional headers sent with access logs by the exporter to the OpenTelemetry Collector. | [ ] | No | +| `accesslog.otlp.grpc.insecure` | Instructs the exporter to send access logs to the OpenTelemetry Collector using an insecure protocol. | false | No | +| `accesslog.otlp.grpc.tls` | Defines the Client TLS configuration used by the exporter to send access logs to the OpenTelemetry Collector. | | No | +| `accesslog.otlp.grpc.tls.ca` | The path to the certificate authority used for the secure connection to the OpenTelemetry Collector, it defaults to the system bundle. | | No | +| `accesslog.otlp.grpc.tls.cert` | The path to the certificate to use for the OpenTelemetry Collector. | | No | +| `accesslog.otlp.grpc.tls.key` | The path to the key to use for the OpenTelemetry Collector. | | No | +| `accesslog.otlp.grpc.tls.insecureSkipVerify` | Instructs the OpenTelemetry Collector to accept any certificate presented by the server regardless of the hostname in the certificate. | false | No | + +#### resourceAttributes + +The `resourceAttributes` option allows setting the resource attributes sent along the traces. +Traefik also supports the `OTEL_RESOURCE_ATTRIBUTES` env variable to set up the resource attributes. + +!!! info "Kubernetes Resource Attributes Detection" + + Additionally, Traefik automatically discovers the following [Kubernetes resource attributes](https://opentelemetry.io/docs/specs/semconv/non-normative/k8s-attributes/) when running in a Kubernetes cluster: + + - `k8s.namespace.name` + - `k8s.pod.uid` + - `k8s.pod.name` + + Note that this automatic detection can fail, like if the Traefik pod is running in host network mode. + In this case, you should provide the attributes with the option or the env variable. + +### Traefik CLF format fields + +It's the default format provided by Traefik. +Below the fields displayed with the Traefik CLF format: ```html - [] @@ -140,46 +326,56 @@ Below the fields displayed with the CLF format: "" "" ms ``` -#### Available Fields +### Generic CLF format fields + +Below the fields displayed with the generic CLF format: + +```html + - [] +" " +"" "" +``` + +### JSON format fields | Field | Description | |-------------------------|------------------| -| `StartUTC` | The time at which request processing started. | -| `StartLocal` | The local time at which request processing started. | -| `Duration` | The total time taken (in nanoseconds) by processing the response, including the origin server's time but not the log writing time. | -| `RouterName` | The name of the Traefik router. | -| `ServiceName` | The name of the Traefik backend. | -| `ServiceURL` | The URL of the Traefik backend. | -| `ServiceAddr` | The IP:port of the Traefik backend (extracted from `ServiceURL`). | -| `ClientAddr` | The remote address in its original form (usually IP:port). | -| `ClientHost` | The remote IP address from which the client request was received. | -| `ClientPort` | The remote TCP port from which the client request was received. | -| `ClientUsername` | The username provided in the URL, if present. | -| `RequestAddr` | The HTTP Host header (usually IP:port). This is treated as not a header by the Go API. | -| `RequestHost` | The HTTP Host server name (not including port). | -| `RequestPort` | The TCP port from the HTTP Host. | -| `RequestMethod` | The HTTP method. | -| `RequestPath` | The HTTP request URI, not including the scheme, host or port. | -| `RequestProtocol` | The version of HTTP requested. | -| `RequestScheme` | The HTTP scheme requested `http` or `https`. | -| `RequestLine` | The `RequestMethod`, + `RequestPath` and `RequestProtocol`. | -| `RequestContentSize` | The number of bytes in the request entity (a.k.a. body) sent by the client. | -| `OriginDuration` | The time taken (in nanoseconds) by the origin server ('upstream') to return its response. | -| `OriginContentSize` | The content length specified by the origin server, or 0 if unspecified. | -| `OriginStatus` | The HTTP status code returned by the origin server. If the request was handled by this Traefik instance (e.g. with a redirect), then this value will be absent (0). | -| `OriginStatusLine` | `OriginStatus` + Status code explanation | -| `DownstreamStatus` | The HTTP status code returned to the client. | -| `DownstreamStatusLine` | The `DownstreamStatus` and status code explanation. | -| `DownstreamContentSize` | The number of bytes in the response entity returned to the client. This is in addition to the "Content-Length" header, which may be present in the origin response. | -| `RequestCount` | The number of requests received since the Traefik instance started. | -| `GzipRatio` | The response body compression ratio achieved. | -| `Overhead` | The processing time overhead (in nanoseconds) caused by Traefik. | -| `RetryAttempts` | The amount of attempts the request was retried. | -| `TLSVersion` | The TLS version used by the connection (e.g. `1.2`) (if connection is TLS). | -| `TLSCipher` | The TLS cipher used by the connection (e.g. `TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA`) (if connection is TLS). | -| `TLSClientSubject` | The string representation of the TLS client certificate's Subject (e.g. `CN=username,O=organization`). | +| `StartUTC` | The time at which request processing started. | +| `StartLocal` | The local time at which request processing started. | +| `Duration` | The total time taken (in nanoseconds) by processing the response, including the origin server's time but not the log writing time. | +| `RouterName` | The name of the Traefik router. | +| `ServiceName` | The name of the Traefik backend. | +| `ServiceURL` | The URL of the Traefik backend. | +| `ServiceAddr` | The IP:port of the Traefik backend (extracted from `ServiceURL`). | +| `ClientAddr` | The remote address in its original form (usually IP:port). | +| `ClientHost` | The remote IP address from which the client request was received. | +| `ClientPort` | The remote TCP port from which the client request was received. | +| `ClientUsername` | The username provided in the URL, if present. | +| `RequestAddr` | The HTTP Host header (usually IP:port). This is treated as not a header by the Go API. | +| `RequestHost` | The HTTP Host server name (not including port). | +| `RequestPort` | The TCP port from the HTTP Host. | +| `RequestMethod` | The HTTP method. | +| `RequestPath` | The HTTP request URI, not including the scheme, host or port. | +| `RequestProtocol` | The version of HTTP requested. | +| `RequestScheme` | The HTTP scheme requested `http` or `https`. | +| `RequestLine` | The `RequestMethod`, + `RequestPath` and `RequestProtocol`. | +| `RequestContentSize` | The number of bytes in the request entity (a.k.a. body) sent by the client. | +| `OriginDuration` | The time taken (in nanoseconds) by the origin server ('upstream') to return its response. | +| `OriginContentSize` | The content length specified by the origin server, or 0 if unspecified. | +| `OriginStatus` | The HTTP status code returned by the origin server. If the request was handled by this Traefik instance (e.g. with a redirect), then this value will be absent (0). | +| `OriginStatusLine` | `OriginStatus` + Status code explanation | +| `DownstreamStatus` | The HTTP status code returned to the client. | +| `DownstreamStatusLine` | The `DownstreamStatus` and status code explanation. | +| `DownstreamContentSize` | The number of bytes in the response entity returned to the client. This is in addition to the "Content-Length" header, which may be present in the origin response. | +| `RequestCount` | The number of requests received since the Traefik instance started. | +| `GzipRatio` | The response body compression ratio achieved. | +| `Overhead` | The processing time overhead (in nanoseconds) caused by Traefik. | +| `RetryAttempts` | The amount of attempts the request was retried. | +| `TLSVersion` | The TLS version used by the connection (e.g. `1.2`) (if connection is TLS). | +| `TLSCipher` | The TLS cipher used by the connection (e.g. `TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA`) (if connection is TLS). | +| `TLSClientSubject` | The string representation of the TLS client certificate's Subject (e.g. `CN=username,O=organization`). | -#### Log Rotation +### Log Rotation Traefik close and reopen its log files, assuming they're configured, on receipt of a USR1 signal. This allows the logs to be rotated and processed by an external program, such as `logrotate`. @@ -187,7 +383,7 @@ This allows the logs to be rotated and processed by an external program, such as !!! warning This does not work on Windows due to the lack of USR signals. -#### Time Zones +### Time Zones Traefik will timestamp each log line in UTC time by default. @@ -199,11 +395,9 @@ It is possible to configure the Traefik to timestamp in a specific timezone by e Example utilizing Docker Compose: ```yaml -version: "3.7" - services: traefik: - image: traefik:v3.4 + image: traefik:v3.5 environment: - TZ=US/Alaska command: diff --git a/docs/content/reference/install-configuration/observability/metrics.md b/docs/content/reference/install-configuration/observability/metrics.md index c2c6f4bd0..979fb230d 100644 --- a/docs/content/reference/install-configuration/observability/metrics.md +++ b/docs/content/reference/install-configuration/observability/metrics.md @@ -60,29 +60,47 @@ metrics: ### Configuration Options -| Field | Description | Default | Required | -|:-----------|---------------|:--------|:---------| -| `metrics.addInternals` | Enables metrics for internal resources (e.g.: `ping@internal`). | false | No | -| `metrics.otlp.addEntryPointsLabels` | Enable metrics on entry points. | true | No | -| `metrics.otlp.addRoutersLabels` | Enable metrics on routers. | false | No | -| `metrics.otlp.addServicesLabels` | Enable metrics on services.| true | No | -| `metrics.otlp.explicitBoundaries` | Explicit boundaries for Histogram data points. | ".005, .01, .025, .05, .1, .25, .5, 1, 2.5, 5, 10" | No | -| `metrics.otlp.pushInterval` | Interval at which metrics are sent to the OpenTelemetry Collector. | 10s | No | -| `metrics.otlp.http` | This instructs the exporter to send the metrics to the OpenTelemetry Collector using HTTP.
Setting the sub-options with their default values. | null/false | No | -| `metrics.otlp.http.endpoint` | URL of the OpenTelemetry Collector to send metrics to.
Format="`://:`" | "http://localhost:4318/v1/metrics" | Yes | -| `metrics.otlp.http.headers` | Additional headers sent with metrics by the exporter to the OpenTelemetry Collector. | - | No | -| `metrics.otlp.http.tls.ca` | Path to the certificate authority used for the secure connection to the OpenTelemetry Collector,
it defaults to the system bundle. | "" | No | -| `metrics.otlp.http.tls.cert` | Path to the public certificate used for the secure connection to the OpenTelemetry Collector.
When using this option, setting the `key` option is required. | "" | No | -| `metrics.otlp.http.tls.key` | This instructs the exporter to send the metrics to the OpenTelemetry Collector using HTTP.
Setting the sub-options with their default values. | null/false | No | -| `metrics.otlp.http.tls.insecureskipverify` | Allow the TLS connection to the OpenTelemetry Collector accepts any certificate presented by the server regardless of the hostnames it covers. | false | Yes | -| `metrics.otlp.grpc` | This instructs the exporter to send metrics to the OpenTelemetry Collector using gRPC. | null/false | No | -| `metrics.otlp.grpc.endpoint` | Address of the OpenTelemetry Collector to send metrics to.
Format="`:`" | "localhost:4317" | Yes | -| `metrics.otlp.grpc.headers` | Additional headers sent with metrics by the exporter to the OpenTelemetry Collector. | - | No | -| `metrics.otlp.http.grpc.insecure` |Allows exporter to send metrics to the OpenTelemetry Collector without using a secured protocol. | false | Yes | -| `metrics.otlp.grpc.tls.ca` | Path to the certificate authority used for the secure connection to the OpenTelemetry Collector,
it defaults to the system bundle. | - | No | -| `metrics.otlp.grpc.tls.cert` | Path to the public certificate used for the secure connection to the OpenTelemetry Collector.
When using this option, setting the `key` option is required. | - | No | -| `metrics.otlp.grpc.tls.key` | This instructs the exporter to send the metrics to the OpenTelemetry Collector using HTTP.
Setting the sub-options with their default values. | null/false | No | -| `metrics.otlp.grpc.tls.insecureskipverify` | Allow the TLS connection to the OpenTelemetry Collector accepts any certificate presented by the server regardless of the hostnames it covers. | false | Yes | +| Field | Description | Default | Required | +|:-------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------|:---------------------------------------------------|:---------| +| `metrics.addInternals` | Enables metrics for internal resources (e.g.: `ping@internal`). | false | No | +| `metrics.otlp.serviceName` | Defines the service name resource attribute. | "traefik" | No | +| `metrics.otlp.resourceAttributes` | Defines additional resource attributes to be sent to the collector. See [resourceAttributes](#resourceattributes) for details. | [] | No | +| `metrics.otlp.addEntryPointsLabels` | Enable metrics on entry points. | true | No | +| `metrics.otlp.addRoutersLabels` | Enable metrics on routers. | false | No | +| `metrics.otlp.addServicesLabels` | Enable metrics on services. | true | No | +| `metrics.otlp.explicitBoundaries` | Explicit boundaries for Histogram data points. | ".005, .01, .025, .05, .1, .25, .5, 1, 2.5, 5, 10" | No | +| `metrics.otlp.pushInterval` | Interval at which metrics are sent to the OpenTelemetry Collector. | 10s | No | +| `metrics.otlp.http` | This instructs the exporter to send the metrics to the OpenTelemetry Collector using HTTP.
Setting the sub-options with their default values. | null/false | No | +| `metrics.otlp.http.endpoint` | URL of the OpenTelemetry Collector to send metrics to.
Format="`://:`" | "https://localhost:4318/v1/metrics" | Yes | +| `metrics.otlp.http.headers` | Additional headers sent with metrics by the exporter to the OpenTelemetry Collector. | - | No | +| `metrics.otlp.http.tls.ca` | Path to the certificate authority used for the secure connection to the OpenTelemetry Collector,
it defaults to the system bundle. | "" | No | +| `metrics.otlp.http.tls.cert` | Path to the public certificate used for the secure connection to the OpenTelemetry Collector.
When using this option, setting the `key` option is required. | "" | No | +| `metrics.otlp.http.tls.key` | This instructs the exporter to send the metrics to the OpenTelemetry Collector using HTTP.
Setting the sub-options with their default values. | null/false | No | +| `metrics.otlp.http.tls.insecureskipverify` | Allow the TLS connection to the OpenTelemetry Collector accepts any certificate presented by the server regardless of the hostnames it covers. | false | Yes | +| `metrics.otlp.grpc` | This instructs the exporter to send metrics to the OpenTelemetry Collector using gRPC. | null/false | No | +| `metrics.otlp.grpc.endpoint` | Address of the OpenTelemetry Collector to send metrics to.
Format="`:`" | "localhost:4317" | Yes | +| `metrics.otlp.grpc.headers` | Additional headers sent with metrics by the exporter to the OpenTelemetry Collector. | - | No | +| `metrics.otlp.grpc.insecure` | Allows exporter to send metrics to the OpenTelemetry Collector without using a secured protocol. | false | Yes | +| `metrics.otlp.grpc.tls.ca` | Path to the certificate authority used for the secure connection to the OpenTelemetry Collector,
it defaults to the system bundle. | - | No | +| `metrics.otlp.grpc.tls.cert` | Path to the public certificate used for the secure connection to the OpenTelemetry Collector.
When using this option, setting the `key` option is required. | - | No | +| `metrics.otlp.grpc.tls.key` | This instructs the exporter to send the metrics to the OpenTelemetry Collector using HTTP.
Setting the sub-options with their default values. | null/false | No | +| `metrics.otlp.grpc.tls.insecureskipverify` | Allow the TLS connection to the OpenTelemetry Collector accepts any certificate presented by the server regardless of the hostnames it covers. | false | Yes | + +### resourceAttributes + +The `resourceAttributes` option allows setting the resource attributes sent along the traces. +Traefik also supports the `OTEL_RESOURCE_ATTRIBUTES` env variable to set up the resource attributes. + +!!! info "Kubernetes Resource Attributes Detection" + + Additionally, Traefik automatically discovers the following [Kubernetes resource attributes](https://opentelemetry.io/docs/specs/semconv/non-normative/k8s-attributes/) when running in a Kubernetes cluster: + + - `k8s.namespace.name` + - `k8s.pod.uid` + - `k8s.pod.name` + + Note that this automatic detection can fail, like if the Traefik pod is running in host network mode. + In this case, you should provide the attributes with the option or the env variable. ## Vendors @@ -110,13 +128,13 @@ metrics: | Field | Description | Default | Required | |:------|:-------------------------------|:---------------------|:---------| -| `metrics.addInternals` | Enables metrics for internal resources (e.g.: `ping@internal`). | false | No | -| `datadog.address` | Defines the address for the exporter to send metrics to datadog-agent. More information [here](#address)| `127.0.0.1:8125` | Yes | -| `datadog.addEntryPointsLabels` | Enable metrics on entry points. | true | No | -| `datadog.addRoutersLabels` | Enable metrics on routers. | false | No | -| `datadog.addServicesLabels` | Enable metrics on services. | true | No | -| `datadog.pushInterval` | Defines the interval used by the exporter to push metrics to datadog-agent. | 10s | No | -| `datadog.prefix` | Defines the prefix to use for metrics collection. | "traefik" | No | +| `metrics.addInternals` | Enables metrics for internal resources (e.g.: `ping@internal`). | false | No | +| `datadog.address` | Defines the address for the exporter to send metrics to datadog-agent. More information [here](#address)| `127.0.0.1:8125` | Yes | +| `datadog.addEntryPointsLabels` | Enable metrics on entry points. | true | No | +| `datadog.addRoutersLabels` | Enable metrics on routers. | false | No | +| `datadog.addServicesLabels` | Enable metrics on services. | true | No | +| `datadog.pushInterval` | Defines the interval used by the exporter to push metrics to datadog-agent. | 10s | No | +| `datadog.prefix` | Defines the prefix to use for metrics collection. | "traefik" | No | ##### `address` @@ -168,16 +186,16 @@ metrics: | Field | Description | Default | Required | |:-----------|-------------------------|:--------|:---------| -| `metrics.addInternal` | Enables metrics for internal resources (e.g.: `ping@internal`). | false | No | -| `metrics.influxDB2.addEntryPointsLabels` | Enable metrics on entry points. | true | No | -| `metrics.influxDB2.addRoutersLabels` | Enable metrics on routers. | false | No | -| `metrics.influxDB2.addServicesLabels` | Enable metrics on services.| true | No | -| `metrics.influxDB2.additionalLabels` | Additional labels (InfluxDB tags) on all metrics. | - | No | -| `metrics.influxDB2.pushInterval` | The interval used by the exporter to push metrics to InfluxDB server. | 10s | No | -| `metrics.influxDB2.address` | Address of the InfluxDB v2 instance. | "http://localhost:8086" | Yes | -| `metrics.influxDB2.token` | Token with which to connect to InfluxDB v2. | - | Yes | -| `metrics.influxDB2.org` | Organisation where metrics will be stored. | - | Yes | -| `metrics.influxDB2.bucket` | Bucket where metrics will be stored. | - | Yes | +| `metrics.addInternal` | Enables metrics for internal resources (e.g.: `ping@internal`). | false | No | +| `metrics.influxDB2.addEntryPointsLabels` | Enable metrics on entry points. | true | No | +| `metrics.influxDB2.addRoutersLabels` | Enable metrics on routers. | false | No | +| `metrics.influxDB2.addServicesLabels` | Enable metrics on services.| true | No | +| `metrics.influxDB2.additionalLabels` | Additional labels (InfluxDB tags) on all metrics. | - | No | +| `metrics.influxDB2.pushInterval` | The interval used by the exporter to push metrics to InfluxDB server. | 10s | No | +| `metrics.influxDB2.address` | Address of the InfluxDB v2 instance. | "http://localhost:8086" | Yes | +| `metrics.influxDB2.token` | Token with which to connect to InfluxDB v2. | - | Yes | +| `metrics.influxDB2.org` | Organisation where metrics will be stored. | - | Yes | +| `metrics.influxDB2.bucket` | Bucket where metrics will be stored. | - | Yes | ### Prometheus @@ -213,14 +231,14 @@ metrics: | Field | Description | Default | Required | |:-----------|---------------------|:--------|:---------| -| `metrics.prometheus.addInternals` | Enables metrics for internal resources (e.g.: `ping@internals`). | false | No | -| `metrics.prometheus.addEntryPointsLabels` | Enable metrics on entry points. | true | No | -| `metrics.prometheus.addRoutersLabels` | Enable metrics on routers. | false | No | -| `metrics.prometheus.addServicesLabels` | Enable metrics on services.| true | No | -| `metrics.prometheus.buckets` | Buckets for latency metrics. |"0.100000, 0.300000, 1.200000, 5.000000" | No | -| `metrics.prometheus.manualRouting` | Set to _true_, it disables the default internal router in order to allow creating a custom router for the `prometheus@internal` service. | false | No | -| `metrics.prometheus.entryPoint` | Traefik Entrypoint name used to expose metrics. | "traefik" | No | -| `metrics.prometheus.headerLabels` | Defines extra labels extracted from request headers for the `requests_total` metrics.
More information [here](#headerlabels). | | Yes | +| `metrics.addInternals` | Enables metrics for internal resources (e.g.: `ping@internals`). | false | No | +| `metrics.prometheus.addEntryPointsLabels` | Enable metrics on entry points. | true | No | +| `metrics.prometheus.addRoutersLabels` | Enable metrics on routers. | false | No | +| `metrics.prometheus.addServicesLabels` | Enable metrics on services.| true | No | +| `metrics.prometheus.buckets` | Buckets for latency metrics. |"0.100000, 0.300000, 1.200000, 5.000000" | No | +| `metrics.prometheus.manualRouting` | Set to _true_, it disables the default internal router in order to allow creating a custom router for the `prometheus@internal` service. | false | No | +| `metrics.prometheus.entryPoint` | Traefik Entrypoint name used to expose metrics. | "traefik" | No | +| `metrics.prometheus.headerLabels` | Defines extra labels extracted from request headers for the `requests_total` metrics.
More information [here](#headerlabels). | | Yes | ##### headerLabels @@ -286,13 +304,13 @@ metrics: | Field | Description | Default | Required | |:-----------|:-------------------------|:--------|:---------| -| `metrics.addInternals` | Enables metrics for internal resources (e.g.: `ping@internals`). | false | No | -| `metrics.statsD.addEntryPointsLabels` | Enable metrics on entry points. | true | No | -| `metrics.statsD.addRoutersLabels` | Enable metrics on routers. | false | No | -| `metrics.statsD.addServicesLabels` | Enable metrics on services.| true | No | -| `metrics.statsD.pushInterval` | The interval used by the exporter to push metrics to DataDog server. | 10s | No | -| `metrics.statsD.address` | Address instructs exporter to send metrics to statsd at this address. | "127.0.0.1:8125" | Yes | -| `metrics.statsD.prefix` | The prefix to use for metrics collection. | "traefik" | No | +| `metrics.addInternals` | Enables metrics for internal resources (e.g.: `ping@internals`). | false | No | +| `metrics.statsD.addEntryPointsLabels` | Enable metrics on entry points. | true | No | +| `metrics.statsD.addRoutersLabels` | Enable metrics on routers. | false | No | +| `metrics.statsD.addServicesLabels` | Enable metrics on services.| true | No | +| `metrics.statsD.pushInterval` | The interval used by the exporter to push metrics to DataDog server. | 10s | No | +| `metrics.statsD.address` | Address instructs exporter to send metrics to statsd at this address. | "127.0.0.1:8125" | Yes | +| `metrics.statsD.prefix` | The prefix to use for metrics collection. | "traefik" | No | ## Metrics Provided @@ -301,42 +319,42 @@ metrics: === "OpenTelemetry" | Metric | Type | [Labels](#labels) | Description | |----------------------------|-------|--------------------------|--------------------------------------------------------------------| - | `traefik_config_reloads_total` | Count | | The total count of configuration reloads. | - | `traefik_config_last_reload_success` | Gauge | | The timestamp of the last configuration reload success. | - | `traefik_open_connections` | Gauge | `entrypoint`, `protocol` | The current count of open connections, by entrypoint and protocol. | - | `traefik_tls_certs_not_after` | Gauge | | The expiration date of certificates. | + | `traefik_config_reloads_total` | Count | | The total count of configuration reloads. | + | `traefik_config_last_reload_success` | Gauge | | The timestamp of the last configuration reload success. | + | `traefik_open_connections` | Gauge | `entrypoint`, `protocol` | The current count of open connections, by entrypoint and protocol. | + | `traefik_tls_certs_not_after` | Gauge | | The expiration date of certificates. | === "Prometheus" | Metric | Type | [Labels](#labels) | Description | |----------------------------|-------|--------------------------|--------------------------------------------------------------------| - | `traefik_config_reloads_total` | Count | | The total count of configuration reloads. | - | `traefik_config_last_reload_success` | Gauge | | The timestamp of the last configuration reload success. | - | `traefik_open_connections` | Gauge | `entrypoint`, `protocol` | The current count of open connections, by entrypoint and protocol. | - | `traefik_tls_certs_not_after` | Gauge | | The expiration date of certificates. | + | `traefik_config_reloads_total` | Count | | The total count of configuration reloads. | + | `traefik_config_last_reload_success` | Gauge | | The timestamp of the last configuration reload success. | + | `traefik_open_connections` | Gauge | `entrypoint`, `protocol` | The current count of open connections, by entrypoint and protocol. | + | `traefik_tls_certs_not_after` | Gauge | | The expiration date of certificates. | === "Datadog" | Metric | Type | [Labels](#labels) | Description | |----------------------------|-------|--------------------------|--------------------------------------------------------------------| - | `config.reload.total` | Count | | The total count of configuration reloads. | - | `config.reload.lastSuccessTimestamp` | Gauge | | The timestamp of the last configuration reload success. | - | `open.connections` | Gauge | `entrypoint`, `protocol` | The current count of open connections, by entrypoint and protocol. | - | `tls.certs.notAfterTimestamp` | Gauge | | The expiration date of certificates. | + | `config.reload.total` | Count | | The total count of configuration reloads. | + | `config.reload.lastSuccessTimestamp` | Gauge | | The timestamp of the last configuration reload success. | + | `open.connections` | Gauge | `entrypoint`, `protocol` | The current count of open connections, by entrypoint and protocol. | + | `tls.certs.notAfterTimestamp` | Gauge | | The expiration date of certificates. | === "InfluxDB2" | Metric | Type | [Labels](#labels) | Description | |----------------------------|-------|--------------------------|--------------------------------------------------------------------| - | `traefik.config.reload.total` | Count | | The total count of configuration reloads. | - | `traefik.config.reload.lastSuccessTimestamp` | Gauge | | The timestamp of the last configuration reload success. | - | `traefik.open.connections` | Gauge | `entrypoint`, `protocol` | The current count of open connections, by entrypoint and protocol. | - | `traefik.tls.certs.notAfterTimestamp` | Gauge | | The expiration date of certificates. | + | `traefik.config.reload.total` | Count | | The total count of configuration reloads. | + | `traefik.config.reload.lastSuccessTimestamp` | Gauge | | The timestamp of the last configuration reload success. | + | `traefik.open.connections` | Gauge | `entrypoint`, `protocol` | The current count of open connections, by entrypoint and protocol. | + | `traefik.tls.certs.notAfterTimestamp` | Gauge | | The expiration date of certificates. | === "StatsD" | Metric | Type | [Labels](#labels) | Description | |----------------------------|-------|--------------------------|--------------------------------------------------------------------| - | `{prefix}.config.reload.total` | Count | | The total count of configuration reloads. | - | `{prefix}.config.reload.lastSuccessTimestamp` | Gauge | | The timestamp of the last configuration reload success. | - | `{prefix}.open.connections` | Gauge | `entrypoint`, `protocol` | The current count of open connections, by entrypoint and protocol. | - | `{prefix}.tls.certs.notAfterTimestamp` | Gauge | | The expiration date of certificates. | + | `{prefix}.config.reload.total` | Count | | The total count of configuration reloads. | + | `{prefix}.config.reload.lastSuccessTimestamp` | Gauge | | The timestamp of the last configuration reload success. | + | `{prefix}.open.connections` | Gauge | `entrypoint`, `protocol` | The current count of open connections, by entrypoint and protocol. | + | `{prefix}.tls.certs.notAfterTimestamp` | Gauge | | The expiration date of certificates. | !!! note "\{prefix\} Default Value" By default, \{prefix\} value is `traefik`. @@ -347,8 +365,8 @@ Here is a comprehensive list of labels that are provided by the global metrics: | Label | Description | example | |--------------|----------------------------------------|----------------------| -| `entrypoint` | Entrypoint that handled the connection | "example_entrypoint" | -| `protocol` | Connection protocol | "TCP" | +| `entrypoint` | Entrypoint that handled the connection | "example_entrypoint" | +| `protocol` | Connection protocol | "TCP" | ### OpenTelemetry Semantic Conventions @@ -358,7 +376,7 @@ Traefik Proxy follows [official OpenTelemetry semantic conventions v1.23.1](http | Metric | Type | [Labels](#labels) | Description | |----------|-----------|-------------------------|------------------| -| `http.server.request.duration` | Histogram | `error.type`, `http.request.method`, `http.response.status_code`, `network.protocol.name`, `server.address`, `server.port`, `url.scheme` | Duration of HTTP server requests | +| `http.server.request.duration` | Histogram | `error.type`, `http.request.method`, `http.response.status_code`, `network.protocol.name`, `server.address`, `server.port`, `url.scheme` | Duration of HTTP server requests | ##### Labels @@ -366,35 +384,35 @@ Here is a comprehensive list of labels that are provided by the metrics: | Label | Description | example | |-----------------------------|--------|---------------| -| `error.type` | Describes a class of error the operation ended with | "500" | -| `http.request.method` | HTTP request method | "GET" | -| `http.response.status_code` | HTTP response status code | "200" | -| `network.protocol.name` | OSI application layer or non-OSI equivalent | "http/1.1" | -| `network.protocol.version` | Version of the protocol specified in `network.protocol.name` | "1.1" | -| `server.address` | Name of the local HTTP server that received the request | "example.com" | -| `server.port` | Port of the local HTTP server that received the request | "80" | -| `url.scheme` | The URI scheme component identifying the used protocol | "http" | +| `error.type` | Describes a class of error the operation ended with | "500" | +| `http.request.method` | HTTP request method | "GET" | +| `http.response.status_code` | HTTP response status code | "200" | +| `network.protocol.name` | OSI application layer or non-OSI equivalent | "http/1.1" | +| `network.protocol.version` | Version of the protocol specified in `network.protocol.name` | "1.1" | +| `server.address` | Name of the local HTTP server that received the request | "example.com" | +| `server.port` | Port of the local HTTP server that received the request | "80" | +| `url.scheme` | The URI scheme component identifying the used protocol | "http" | #### HTTP Client | Metric | Type | [Labels](#labels) | Description | |-------------------------------|-----------|-----------------|--------| -| `http.client.request.duration` | Histogram | `error.type`, `http.request.method`, `http.response.status_code`, `network.protocol.name`, `server.address`, `server.port`, `url.scheme` | Duration of HTTP client requests | +| `http.client.request.duration` | Histogram | `error.type`, `http.request.method`, `http.response.status_code`, `network.protocol.name`, `server.address`, `server.port`, `url.scheme` | Duration of HTTP client requests | ##### Labels Here is a comprehensive list of labels that are provided by the metrics: -| Label | Description | example | -|------ -----|------------|---------------| -| `error.type` | Describes a class of error the operation ended with | "500" | -| `http.request.method` | HTTP request method | "GET" | -| `http.response.status_code` | HTTP response status code | "200" | -| `network.protocol.name` | OSI application layer or non-OSI equivalent | "http/1.1" | -| `network.protocol.version` | Version of the protocol specified in `network.protocol.name` | "1.1" | -| `server.address` | Name of the local HTTP server that received the request | "example.com" | -| `server.port` | Port of the local HTTP server that received the request | "80" | -| `url.scheme` | The URI scheme component identifying the used protocol | "http" | +| Label | Description | example | +|----------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------|---------------| +| `error.type` | Describes a class of error the operation ended with | "500" | +| `http.request.method` | HTTP request method | "GET" | +| `http.response.status_code` | HTTP response status code | "200" | +| `network.protocol.name` | OSI application layer or non-OSI equivalent | "http/1.1" | +| `network.protocol.version` | Version of the protocol specified in `network.protocol.name` | "1.1" | +| `server.address` | Name of the local HTTP server that received the request | "example.com" | +| `server.port` | Port of the local HTTP server that received the request | "80" | +| `url.scheme` | The URI scheme component identifying the used protocol | "http" | ### HTTP Metrics @@ -406,51 +424,51 @@ On top of the official OpenTelemetry semantic conventions, Traefik provides its | Metric | Type | [Labels](#labels) | Description | |-----------------------|-----------|--------------------|--------------------------| - | `traefik_entrypoint_requests_total` | Count | `code`, `method`, `protocol`, `entrypoint` | The total count of HTTP requests received by an entrypoint. | - | `traefik_entrypoint_requests_tls_total` | Count | `tls_version`, `tls_cipher`, `entrypoint` | The total count of HTTPS requests received by an entrypoint. | - | `traefik_entrypoint_request_duration_seconds` | Histogram | `code`, `method`, `protocol`, `entrypoint` | Request processing duration histogram on an entrypoint. | - | `traefik_entrypoint_requests_bytes_total` | Count | `code`, `method`, `protocol`, `entrypoint` | The total size of HTTP requests in bytes handled by an entrypoint. | - | `traefik_entrypoint_responses_bytes_total` | Count | `code`, `method`, `protocol`, `entrypoint` | The total size of HTTP responses in bytes handled by an entrypoint. | + | `traefik_entrypoint_requests_total` | Count | `code`, `method`, `protocol`, `entrypoint` | The total count of HTTP requests received by an entrypoint. | + | `traefik_entrypoint_requests_tls_total` | Count | `tls_version`, `tls_cipher`, `entrypoint` | The total count of HTTPS requests received by an entrypoint. | + | `traefik_entrypoint_request_duration_seconds` | Histogram | `code`, `method`, `protocol`, `entrypoint` | Request processing duration histogram on an entrypoint. | + | `traefik_entrypoint_requests_bytes_total` | Count | `code`, `method`, `protocol`, `entrypoint` | The total size of HTTP requests in bytes handled by an entrypoint. | + | `traefik_entrypoint_responses_bytes_total` | Count | `code`, `method`, `protocol`, `entrypoint` | The total size of HTTP responses in bytes handled by an entrypoint. | === "Prometheus" | Metric | Type | [Labels](#labels) | Description | |-----------------------|-----------|------------------------|-------------------------| - | `traefik_entrypoint_requests_total` | Count | `code`, `method`, `protocol`, `entrypoint` | The total count of HTTP requests received by an entrypoint. | - | `traefik_entrypoint_requests_tls_total` | Count | `tls_version`, `tls_cipher`, `entrypoint` | The total count of HTTPS requests received by an entrypoint. | - | `traefik_entrypoint_request_duration_seconds` | Histogram | `code`, `method`, `protocol`, `entrypoint` | Request processing duration histogram on an entrypoint. | - | `traefik_entrypoint_requests_bytes_total` | Count | `code`, `method`, `protocol`, `entrypoint` | The total size of HTTP requests in bytes handled by an entrypoint. | - | `traefik_entrypoint_responses_bytes_total` | Count | `code`, `method`, `protocol`, `entrypoint` | The total size of HTTP responses in bytes handled by an entrypoint. | + | `traefik_entrypoint_requests_total` | Count | `code`, `method`, `protocol`, `entrypoint` | The total count of HTTP requests received by an entrypoint. | + | `traefik_entrypoint_requests_tls_total` | Count | `tls_version`, `tls_cipher`, `entrypoint` | The total count of HTTPS requests received by an entrypoint. | + | `traefik_entrypoint_request_duration_seconds` | Histogram | `code`, `method`, `protocol`, `entrypoint` | Request processing duration histogram on an entrypoint. | + | `traefik_entrypoint_requests_bytes_total` | Count | `code`, `method`, `protocol`, `entrypoint` | The total size of HTTP requests in bytes handled by an entrypoint. | + | `traefik_entrypoint_responses_bytes_total` | Count | `code`, `method`, `protocol`, `entrypoint` | The total size of HTTP responses in bytes handled by an entrypoint. | === "Datadog" | Metric | Type | [Labels](#labels) | Description | |-----------------------|-----------|------------------|---------------------------| - | `entrypoint.requests.total` | Count | `code`, `method`, `protocol`, `entrypoint` | The total count of HTTP requests received by an entrypoint. | - | `entrypoint.requests.tls.total` | Count | `tls_version`, `tls_cipher`, `entrypoint` | The total count of HTTPS requests received by an entrypoint. | - | `entrypoint.request.duration.seconds` | Histogram | `code`, `method`, `protocol`, `entrypoint` | Request processing duration histogram on an entrypoint. | - | `entrypoint.requests.bytes.total` | Count | `code`, `method`, `protocol`, `entrypoint` | The total size of HTTP requests in bytes handled by an entrypoint. | - | `entrypoint.responses.bytes.total` | Count | `code`, `method`, `protocol`, `entrypoint` | The total size of HTTP responses in bytes handled by an entrypoint. | + | `entrypoint.requests.total` | Count | `code`, `method`, `protocol`, `entrypoint` | The total count of HTTP requests received by an entrypoint. | + | `entrypoint.requests.tls.total` | Count | `tls_version`, `tls_cipher`, `entrypoint` | The total count of HTTPS requests received by an entrypoint. | + | `entrypoint.request.duration.seconds` | Histogram | `code`, `method`, `protocol`, `entrypoint` | Request processing duration histogram on an entrypoint. | + | `entrypoint.requests.bytes.total` | Count | `code`, `method`, `protocol`, `entrypoint` | The total size of HTTP requests in bytes handled by an entrypoint. | + | `entrypoint.responses.bytes.total` | Count | `code`, `method`, `protocol`, `entrypoint` | The total size of HTTP responses in bytes handled by an entrypoint. | === "InfluxDB2" | Metric | Type | [Labels](#labels) | Description | |------------|-----------|-------------------|-----------------| - | `traefik.entrypoint.requests.total` | Count | `code`, `method`, `protocol`, `entrypoint` | The total count of HTTP requests received by an entrypoint. | - | `traefik.entrypoint.requests.tls.total` | Count | `tls_version`, `tls_cipher`, `entrypoint` | The total count of HTTPS requests received by an entrypoint. | - | `traefik.entrypoint.request.duration.seconds` | Histogram | `code`, `method`, `protocol`, `entrypoint` | Request processing duration histogram on an entrypoint. | - | `traefik.entrypoint.requests.bytes.total` | Count | `code`, `method`, `protocol`, `entrypoint` | The total size of HTTP requests in bytes handled by an entrypoint. | - | `traefik.entrypoint.responses.bytes.total` | Count | `code`, `method`, `protocol`, `entrypoint` | The total size of HTTP responses in bytes handled by an entrypoint. | + | `traefik.entrypoint.requests.total` | Count | `code`, `method`, `protocol`, `entrypoint` | The total count of HTTP requests received by an entrypoint. | + | `traefik.entrypoint.requests.tls.total` | Count | `tls_version`, `tls_cipher`, `entrypoint` | The total count of HTTPS requests received by an entrypoint. | + | `traefik.entrypoint.request.duration.seconds` | Histogram | `code`, `method`, `protocol`, `entrypoint` | Request processing duration histogram on an entrypoint. | + | `traefik.entrypoint.requests.bytes.total` | Count | `code`, `method`, `protocol`, `entrypoint` | The total size of HTTP requests in bytes handled by an entrypoint. | + | `traefik.entrypoint.responses.bytes.total` | Count | `code`, `method`, `protocol`, `entrypoint` | The total size of HTTP responses in bytes handled by an entrypoint. | === "StatsD" | Metric | Type | [Labels](#labels) | Description | |----------------------------|-------|--------------------------|--------------------------------------------------------------------| - | `{prefix}.entrypoint.requests.total` | Count | `code`, `method`, `protocol`, `entrypoint` | The total count of HTTP requests received by an entrypoint. | - | `{prefix}.entrypoint.requests.tls.total` | Count | `tls_version`, `tls_cipher`, `entrypoint` | The total count of HTTPS requests received by an entrypoint. | - | `{prefix}.entrypoint.request.duration.seconds` | Histogram | `code`, `method`, `protocol`, `entrypoint` | Request processing duration histogram on an entrypoint. | - | `{prefix}.entrypoint.requests.bytes.total` | Count | `code`, `method`, `protocol`, `entrypoint` | The total size of HTTP requests in bytes handled by an entrypoint. | - | `{prefix}.entrypoint.responses.bytes.total` | Count | `code`, `method`, `protocol`, `entrypoint` | The total size of HTTP responses in bytes handled by an entrypoint. | + | `{prefix}.entrypoint.requests.total` | Count | `code`, `method`, `protocol`, `entrypoint` | The total count of HTTP requests received by an entrypoint. | + | `{prefix}.entrypoint.requests.tls.total` | Count | `tls_version`, `tls_cipher`, `entrypoint` | The total count of HTTPS requests received by an entrypoint. | + | `{prefix}.entrypoint.request.duration.seconds` | Histogram | `code`, `method`, `protocol`, `entrypoint` | Request processing duration histogram on an entrypoint. | + | `{prefix}.entrypoint.requests.bytes.total` | Count | `code`, `method`, `protocol`, `entrypoint` | The total size of HTTP requests in bytes handled by an entrypoint. | + | `{prefix}.entrypoint.responses.bytes.total` | Count | `code`, `method`, `protocol`, `entrypoint` | The total size of HTTP responses in bytes handled by an entrypoint. | !!! note "\{prefix\} Default Value" By default, \{prefix\} value is `traefik`. @@ -461,51 +479,51 @@ On top of the official OpenTelemetry semantic conventions, Traefik provides its | Metric | Type | [Labels](#labels) | Description | |-----------------------|-----------|----------------------|--------------------------------| - | `traefik_router_requests_total` | Count | `code`, `method`, `protocol`, `router`, `service` | The total count of HTTP requests handled by a router. | - | `traefik_router_requests_tls_total` | Count | `tls_version`, `tls_cipher`, `router`, `service` | The total count of HTTPS requests handled by a router. | - | `traefik_router_request_duration_seconds` | Histogram | `code`, `method`, `protocol`, `router`, `service` | Request processing duration histogram on a router. | - | `traefik_router_requests_bytes_total` | Count | `code`, `method`, `protocol`, `router`, `service` | The total size of HTTP requests in bytes handled by a router. | - | `traefik_router_responses_bytes_total` | Count | `code`, `method`, `protocol`, `router`, `service` | The total size of HTTP responses in bytes handled by a router. | + | `traefik_router_requests_total` | Count | `code`, `method`, `protocol`, `router`, `service` | The total count of HTTP requests handled by a router. | + | `traefik_router_requests_tls_total` | Count | `tls_version`, `tls_cipher`, `router`, `service` | The total count of HTTPS requests handled by a router. | + | `traefik_router_request_duration_seconds` | Histogram | `code`, `method`, `protocol`, `router`, `service` | Request processing duration histogram on a router. | + | `traefik_router_requests_bytes_total` | Count | `code`, `method`, `protocol`, `router`, `service` | The total size of HTTP requests in bytes handled by a router. | + | `traefik_router_responses_bytes_total` | Count | `code`, `method`, `protocol`, `router`, `service` | The total size of HTTP responses in bytes handled by a router. | === "Prometheus" | Metric | Type | [Labels](#labels) | Description | |-----------------------|-----------|---------------------------------------------------|----------------------------------------------------------------| - | `traefik_router_requests_total` | Count | `code`, `method`, `protocol`, `router`, `service` | The total count of HTTP requests handled by a router. | - | `traefik_router_requests_tls_total` | Count | `tls_version`, `tls_cipher`, `router`, `service` | The total count of HTTPS requests handled by a router. | - | `traefik_router_request_duration_seconds` | Histogram | `code`, `method`, `protocol`, `router`, `service` | Request processing duration histogram on a router. | - | `traefik_router_requests_bytes_total` | Count | `code`, `method`, `protocol`, `router`, `service` | The total size of HTTP requests in bytes handled by a router. | - | `traefik_router_responses_bytes_total` | Count | `code`, `method`, `protocol`, `router`, `service` | The total size of HTTP responses in bytes handled by a router. | + | `traefik_router_requests_total` | Count | `code`, `method`, `protocol`, `router`, `service` | The total count of HTTP requests handled by a router. | + | `traefik_router_requests_tls_total` | Count | `tls_version`, `tls_cipher`, `router`, `service` | The total count of HTTPS requests handled by a router. | + | `traefik_router_request_duration_seconds` | Histogram | `code`, `method`, `protocol`, `router`, `service` | Request processing duration histogram on a router. | + | `traefik_router_requests_bytes_total` | Count | `code`, `method`, `protocol`, `router`, `service` | The total size of HTTP requests in bytes handled by a router. | + | `traefik_router_responses_bytes_total` | Count | `code`, `method`, `protocol`, `router`, `service` | The total size of HTTP responses in bytes handled by a router. | === "Datadog" | Metric | Type | [Labels](#labels) | Description | |-------------|-----------|---------------|---------------------| - | `router.requests.total` | Count | `code`, `method`, `protocol`, `router`, `service` | The total count of HTTP requests handled by a router. | - | `router.requests.tls.total` | Count | `tls_version`, `tls_cipher`, `router`, `service` | The total count of HTTPS requests handled by a router. | - | `router.request.duration.seconds` | Histogram | `code`, `method`, `protocol`, `router`, `service` | Request processing duration histogram on a router. | - | `router.requests.bytes.total` | Count | `code`, `method`, `protocol`, `router`, `service` | The total size of HTTP requests in bytes handled by a router. | - | `router.responses.bytes.total` | Count | `code`, `method`, `protocol`, `router`, `service` | The total size of HTTP responses in bytes handled by a router. | + | `router.requests.total` | Count | `code`, `method`, `protocol`, `router`, `service` | The total count of HTTP requests handled by a router. | + | `router.requests.tls.total` | Count | `tls_version`, `tls_cipher`, `router`, `service` | The total count of HTTPS requests handled by a router. | + | `router.request.duration.seconds` | Histogram | `code`, `method`, `protocol`, `router`, `service` | Request processing duration histogram on a router. | + | `router.requests.bytes.total` | Count | `code`, `method`, `protocol`, `router`, `service` | The total size of HTTP requests in bytes handled by a router. | + | `router.responses.bytes.total` | Count | `code`, `method`, `protocol`, `router`, `service` | The total size of HTTP responses in bytes handled by a router. | === "InfluxDB2" | Metric | Type | [Labels](#labels) | Description | |-----------------------|-----------|---------------------------------------------------|----------------------------------------------------------------| - | `traefik.router.requests.total` | Count | `code`, `method`, `protocol`, `router`, `service` | The total count of HTTP requests handled by a router. | - | `traefik.router.requests.tls.total` | Count | `tls_version`, `tls_cipher`, `router`, `service` | The total count of HTTPS requests handled by a router. | - | `traefik.router.request.duration.seconds` | Histogram | `code`, `method`, `protocol`, `router`, `service` | Request processing duration histogram on a router. | - | `traefik.router.requests.bytes.total` | Count | `code`, `method`, `protocol`, `router`, `service` | The total size of HTTP requests in bytes handled by a router. | - | `traefik.router.responses.bytes.total` | Count | `code`, `method`, `protocol`, `router`, `service` | The total size of HTTP responses in bytes handled by a router. | + | `traefik.router.requests.total` | Count | `code`, `method`, `protocol`, `router`, `service` | The total count of HTTP requests handled by a router. | + | `traefik.router.requests.tls.total` | Count | `tls_version`, `tls_cipher`, `router`, `service` | The total count of HTTPS requests handled by a router. | + | `traefik.router.request.duration.seconds` | Histogram | `code`, `method`, `protocol`, `router`, `service` | Request processing duration histogram on a router. | + | `traefik.router.requests.bytes.total` | Count | `code`, `method`, `protocol`, `router`, `service` | The total size of HTTP requests in bytes handled by a router. | + | `traefik.router.responses.bytes.total` | Count | `code`, `method`, `protocol`, `router`, `service` | The total size of HTTP responses in bytes handled by a router. | === "StatsD" | Metric | Type | [Labels](#labels) | Description | |-----------------------|-----------|---------------|-------------| - | `{prefix}.router.requests.total` | Count | `code`, `method`, `protocol`, `router`, `service` | The total count of HTTP requests handled by a router. | - | `{prefix}.router.requests.tls.total` | Count | `tls_version`, `tls_cipher`, `router`, `service` | The total count of HTTPS requests handled by a router. | - | `{prefix}.router.request.duration.seconds` | Histogram | `code`, `method`, `protocol`, `router`, `service` | Request processing duration histogram on a router. | - | `{prefix}.router.requests.bytes.total` | Count | `code`, `method`, `protocol`, `router`, `service` | The total size of HTTP requests in bytes handled by a router. | - | `{prefix}.router.responses.bytes.total` | Count | `code`, `method`, `protocol`, `router`, `service` | The total size of HTTP responses in bytes handled by a router. | + | `{prefix}.router.requests.total` | Count | `code`, `method`, `protocol`, `router`, `service` | The total count of HTTP requests handled by a router. | + | `{prefix}.router.requests.tls.total` | Count | `tls_version`, `tls_cipher`, `router`, `service` | The total count of HTTPS requests handled by a router. | + | `{prefix}.router.request.duration.seconds` | Histogram | `code`, `method`, `protocol`, `router`, `service` | Request processing duration histogram on a router. | + | `{prefix}.router.requests.bytes.total` | Count | `code`, `method`, `protocol`, `router`, `service` | The total size of HTTP requests in bytes handled by a router. | + | `{prefix}.router.responses.bytes.total` | Count | `code`, `method`, `protocol`, `router`, `service` | The total size of HTTP responses in bytes handled by a router. | !!! note "\{prefix\} Default Value" By default, \{prefix\} value is `traefik`. @@ -516,61 +534,61 @@ On top of the official OpenTelemetry semantic conventions, Traefik provides its | Metric | Type | Labels | Description | |-----------------------|-----------|------------|------------| - | `traefik_service_requests_total` | Count | `code`, `method`, `protocol`, `service` | The total count of HTTP requests processed on a service. | - | `traefik_service_requests_tls_total` | Count | `tls_version`, `tls_cipher`, `service` | The total count of HTTPS requests processed on a service. | - | `traefik_service_request_duration_seconds` | Histogram | `code`, `method`, `protocol`, `service` | Request processing duration histogram on a service. | - | `traefik_service_retries_total` | Count | `service` | The count of requests retries on a service. | - | `traefik_service_server_up` | Gauge | `service`, `url` | Current service's server status, 0 for a down or 1 for up. | - | `traefik_service_requests_bytes_total` | Count | `code`, `method`, `protocol`, `service` | The total size of requests in bytes received by a service. | - | `traefik_service_responses_bytes_total` | Count | `code`, `method`, `protocol`, `service` | The total size of responses in bytes returned by a service. | + | `traefik_service_requests_total` | Count | `code`, `method`, `protocol`, `service` | The total count of HTTP requests processed on a service. | + | `traefik_service_requests_tls_total` | Count | `tls_version`, `tls_cipher`, `service` | The total count of HTTPS requests processed on a service. | + | `traefik_service_request_duration_seconds` | Histogram | `code`, `method`, `protocol`, `service` | Request processing duration histogram on a service. | + | `traefik_service_retries_total` | Count | `service` | The count of requests retries on a service. | + | `traefik_service_server_up` | Gauge | `service`, `url` | Current service's server status, 0 for a down or 1 for up. Only for services configured with healthcheck. | + | `traefik_service_requests_bytes_total` | Count | `code`, `method`, `protocol`, `service` | The total size of requests in bytes received by a service. | + | `traefik_service_responses_bytes_total` | Count | `code`, `method`, `protocol`, `service` | The total size of responses in bytes returned by a service. | === "Prometheus" | Metric | Type | Labels | Description | |-----------------------|-----------|-------|------------| - | `traefik_service_requests_total` | Count | `code`, `method`, `protocol`, `service` | The total count of HTTP requests processed on a service. | - | `traefik_service_requests_tls_total` | Count | `tls_version`, `tls_cipher`, `service` | The total count of HTTPS requests processed on a service. | - | `traefik_service_request_duration_seconds` | Histogram | `code`, `method`, `protocol`, `service` | Request processing duration histogram on a service. | - | `traefik_service_retries_total` | Count | `service` | The count of requests retries on a service. | - | `traefik_service_server_up` | Gauge | `service`, `url` | Current service's server status, 0 for a down or 1 for up. | - | `traefik_service_requests_bytes_total` | Count | `code`, `method`, `protocol`, `service` | The total size of requests in bytes received by a service. | - | `traefik_service_responses_bytes_total` | Count | `code`, `method`, `protocol`, `service` | The total size of responses in bytes returned by a service. | + | `traefik_service_requests_total` | Count | `code`, `method`, `protocol`, `service` | The total count of HTTP requests processed on a service. | + | `traefik_service_requests_tls_total` | Count | `tls_version`, `tls_cipher`, `service` | The total count of HTTPS requests processed on a service. | + | `traefik_service_request_duration_seconds` | Histogram | `code`, `method`, `protocol`, `service` | Request processing duration histogram on a service. | + | `traefik_service_retries_total` | Count | `service` | The count of requests retries on a service. | + | `traefik_service_server_up` | Gauge | `service`, `url` | Current service's server status, 0 for a down or 1 for up. Only for services configured with healthcheck. | + | `traefik_service_requests_bytes_total` | Count | `code`, `method`, `protocol`, `service` | The total size of requests in bytes received by a service. | + | `traefik_service_responses_bytes_total` | Count | `code`, `method`, `protocol`, `service` | The total size of responses in bytes returned by a service. | === "Datadog" | Metric | Type | Labels | Description | |-----------------------|-----------|--------|------------------| - | `service.requests.total` | Count | `code`, `method`, `protocol`, `service` | The total count of HTTP requests processed on a service. | - | `router.service.tls.total` | Count | `tls_version`, `tls_cipher`, `service` | The total count of HTTPS requests processed on a service. | - | `service.request.duration.seconds` | Histogram | `code`, `method`, `protocol`, `service` | Request processing duration histogram on a service. | - | `service.retries.total` | Count | `service` | The count of requests retries on a service. | - | `service.server.up` | Gauge | `service`, `url` | Current service's server status, 0 for a down or 1 for up. | - | `service.requests.bytes.total` | Count | `code`, `method`, `protocol`, `service` | The total size of requests in bytes received by a service. | - | `service.responses.bytes.total` | Count | `code`, `method`, `protocol`, `service` | The total size of responses in bytes returned by a service. | + | `service.requests.total` | Count | `code`, `method`, `protocol`, `service` | The total count of HTTP requests processed on a service. | + | `router.service.tls.total` | Count | `tls_version`, `tls_cipher`, `service` | The total count of HTTPS requests processed on a service. | + | `service.request.duration.seconds` | Histogram | `code`, `method`, `protocol`, `service` | Request processing duration histogram on a service. | + | `service.retries.total` | Count | `service` | The count of requests retries on a service. | + | `service.server.up` | Gauge | `service`, `url` | Current service's server status, 0 for a down or 1 for up. Only for services configured with healthcheck. | + | `service.requests.bytes.total` | Count | `code`, `method`, `protocol`, `service` | The total size of requests in bytes received by a service. | + | `service.responses.bytes.total` | Count | `code`, `method`, `protocol`, `service` | The total size of responses in bytes returned by a service. | === "InfluxDB2" | Metric | Type | Labels | Description | |-----------------------|-----------|-----------------------------------------|-------------------------------------------------------------| - | `traefik.service.requests.total` | Count | `code`, `method`, `protocol`, `service` | The total count of HTTP requests processed on a service. | - | `traefik.service.requests.tls.total` | Count | `tls_version`, `tls_cipher`, `service` | The total count of HTTPS requests processed on a service. | - | `traefik.service.request.duration.seconds` | Histogram | `code`, `method`, `protocol`, `service` | Request processing duration histogram on a service. | - | `traefik.service.retries.total` | Count | `service` | The count of requests retries on a service. | - | `traefik.service.server.up` | Gauge | `service`, `url` | Current service's server status, 0 for a down or 1 for up. | - | `traefik.service.requests.bytes.total` | Count | `code`, `method`, `protocol`, `service` | The total size of requests in bytes received by a service. | - | `traefik.service.responses.bytes.total` | Count | `code`, `method`, `protocol`, `service` | The total size of responses in bytes returned by a service. | + | `traefik.service.requests.total` | Count | `code`, `method`, `protocol`, `service` | The total count of HTTP requests processed on a service. | + | `traefik.service.requests.tls.total` | Count | `tls_version`, `tls_cipher`, `service` | The total count of HTTPS requests processed on a service. | + | `traefik.service.request.duration.seconds` | Histogram | `code`, `method`, `protocol`, `service` | Request processing duration histogram on a service. | + | `traefik.service.retries.total` | Count | `service` | The count of requests retries on a service. | + | `traefik.service.server.up` | Gauge | `service`, `url` | Current service's server status, 0 for a down or 1 for up. Only for services configured with healthcheck. | + | `traefik.service.requests.bytes.total` | Count | `code`, `method`, `protocol`, `service` | The total size of requests in bytes received by a service. | + | `traefik.service.responses.bytes.total` | Count | `code`, `method`, `protocol`, `service` | The total size of responses in bytes returned by a service. | === "StatsD" | Metric | Type | Labels | Description | |-----------------------|-----------|-----|---------| - | `{prefix}.service.requests.total` | Count | `code`, `method`, `protocol`, `service` | The total count of HTTP requests processed on a service. | - | `{prefix}.service.requests.tls.total` | Count | `tls_version`, `tls_cipher`, `service` | The total count of HTTPS requests processed on a service. | - | `{prefix}.service.request.duration.seconds` | Histogram | `code`, `method`, `protocol`, `service` | Request processing duration histogram on a service. | - | `{prefix}.service.retries.total` | Count | `service` | The count of requests retries on a service. | - | `{prefix}.service.server.up` | Gauge | `service`, `url` | Current service's server status, 0 for a down or 1 for up. | - | `{prefix}.service.requests.bytes.total` | Count | `code`, `method`, `protocol`, `service` | The total size of requests in bytes received by a service. | - | `{prefix}.service.responses.bytes.total` | Count | `code`, `method`, `protocol`, `service` | The total size of responses in bytes returned by a service. | + | `{prefix}.service.requests.total` | Count | `code`, `method`, `protocol`, `service` | The total count of HTTP requests processed on a service. | + | `{prefix}.service.requests.tls.total` | Count | `tls_version`, `tls_cipher`, `service` | The total count of HTTPS requests processed on a service. | + | `{prefix}.service.request.duration.seconds` | Histogram | `code`, `method`, `protocol`, `service` | Request processing duration histogram on a service. | + | `{prefix}.service.retries.total` | Count | `service` | The count of requests retries on a service. | + | `{prefix}.service.server.up` | Gauge | `service`, `url` | Current service's server status, 0 for a down or 1 for up. Only for services configured with healthcheck. | + | `{prefix}.service.requests.bytes.total` | Count | `code`, `method`, `protocol`, `service` | The total size of requests in bytes received by a service. | + | `{prefix}.service.responses.bytes.total` | Count | `code`, `method`, `protocol`, `service` | The total size of responses in bytes returned by a service. | !!! note "\{prefix\} Default Value" By default, \{prefix\} value is `traefik`. @@ -581,18 +599,18 @@ Here is a comprehensive list of labels that are provided by the metrics: | Label | Description | example | |---------------|-------------------|----------------------------| -| `cn` | Certificate Common Name | "example.com" | -| `code` | Request code | "200" | -| `entrypoint` | Entrypoint that handled the request | "example_entrypoint" | -| `method` | Request Method | "GET" | -| `protocol` | Request protocol | "http" | -| `router` | Router that handled the request | "example_router" | -| `sans` | Certificate Subject Alternative NameS | "example.com" | -| `serial` | Certificate Serial Number | "123..." | -| `service` | Service that handled the request | "example_service@provider" | -| `tls_cipher` | TLS cipher used for the request | "TLS_FALLBACK_SCSV" | -| `tls_version` | TLS version used for the request | "1.0" | -| `url` | Service server url | "http://example.com" | +| `cn` | Certificate Common Name | "example.com" | +| `code` | Request code | "200" | +| `entrypoint` | Entrypoint that handled the request | "example_entrypoint" | +| `method` | Request Method | "GET" | +| `protocol` | Request protocol | "http" | +| `router` | Router that handled the request | "example_router" | +| `sans` | Certificate Subject Alternative NameS | "example.com" | +| `serial` | Certificate Serial Number | "123..." | +| `service` | Service that handled the request | "example_service@provider" | +| `tls_cipher` | TLS cipher used for the request | "TLS_FALLBACK_SCSV" | +| `tls_version` | TLS version used for the request | "1.0" | +| `url` | Service server url | "http://example.com" | !!! info "`method` label value" diff --git a/docs/content/reference/install-configuration/observability/tracing.md b/docs/content/reference/install-configuration/observability/tracing.md index f7b4987c1..c3748e5f1 100644 --- a/docs/content/reference/install-configuration/observability/tracing.md +++ b/docs/content/reference/install-configuration/observability/tracing.md @@ -36,27 +36,43 @@ tracing: {} ## Configuration Options -| Field | Description | Default | Required | -|:-------------------------------------------|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:-----------------------------------|:---------| -| `tracing.addInternals` | Enables tracing for internal resources (e.g.: `ping@internal`). | false | No | -| `tracing.serviceName` | Service name used in selected backend. | "traefik" | No | -| `tracing.sampleRate` | The proportion of requests to trace, specified between 0.0 and 1.0. | 1.0 | No | -| `tracing.resourceAttributes` | Defines additional resource attributes to be sent to the collector. | [] | No | -| `tracing.capturedRequestHeaders` | Defines the list of request headers to add as attributes.
It applies to client and server kind spans.| [] | No | -| `tracing.capturedResponseHeaders` | Defines the list of response headers to add as attributes.
It applies to client and server kind spans.| [] |False | -| `tracing.safeQueryParams` | By default, all query parameters are redacted.
Defines the list of query parameters to not redact. | [] | No | -| `tracing.otlp.http` | This instructs the exporter to send the tracing to the OpenTelemetry Collector using HTTP.
Setting the sub-options with their default values. | null/false | No | -| `tracing.otlp.http.endpoint` | URL of the OpenTelemetry Collector to send tracing to.
Format="`://:`" | "http://localhost:4318/v1/tracing" | Yes | -| `tracing.otlp.http.headers` | Additional headers sent with tracing by the exporter to the OpenTelemetry Collector. | | No | -| `tracing.otlp.http.tls.ca` | Path to the certificate authority used for the secure connection to the OpenTelemetry Collector, it defaults to the system bundle. | "" | No | -| `tracing.otlp.http.tls.cert` | Path to the public certificate used for the secure connection to the OpenTelemetry Collector. When using this option, setting the `key` option is required. | "" | No | -| `tracing.otlp.http.tls.key` | This instructs the exporter to send the tracing to the OpenTelemetry Collector using HTTP.
Setting the sub-options with their default values. | ""null/false "" | No | -| `tracing.otlp.http.tls.insecureskipverify` |If `insecureSkipVerify` is `true`, the TLS connection to the OpenTelemetry Collector accepts any certificate presented by the server regardless of the hostnames it covers. | false | Yes | -| `tracing.otlp.grpc` | This instructs the exporter to send tracing to the OpenTelemetry Collector using gRPC. | false | No | -| `tracing.otlp.grpc.endpoint` | Address of the OpenTelemetry Collector to send tracing to.
Format="`:`" | "localhost:4317" | Yes | -| `tracing.otlp.grpc.headers` | Additional headers sent with tracing by the exporter to the OpenTelemetry Collector. | [] | No | -| `tracing.otlp.grpc.insecure` |Allows exporter to send tracing to the OpenTelemetry Collector without using a secured protocol. | false | Yes | -| `tracing.otlp.grpc.tls.ca` | Path to the certificate authority used for the secure connection to the OpenTelemetry Collector, it defaults to the system bundle. | "" | No | -| `tracing.otlp.grpc.tls.cert` | Path to the public certificate used for the secure connection to the OpenTelemetry Collector. When using this option, setting the `key` option is required. | "" | No | -| `tracing.otlp.grpc.tls.key` | This instructs the exporter to send the tracing to the OpenTelemetry Collector using HTTP.
Setting the sub-options with their default values. | ""null/false "" | No | -| `tracing.otlp.grpc.tls.insecureskipverify` |If `insecureSkipVerify` is `true`, the TLS connection to the OpenTelemetry Collector accepts any certificate presented by the server regardless of the hostnames it covers. | false | Yes | +| Field | Description | Default | Required | +|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:----------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:------------------------------------|:---------| +| `tracing.addInternals` | Enables tracing for internal resources (e.g.: `ping@internal`). | false | No | +| `tracing.serviceName` | Defines the service name resource attribute. | "traefik" | No | +| `tracing.resourceAttributes` | Defines additional resource attributes to be sent to the collector. See [resourceAttributes](#resourceattributes) for details. | [] | No | +| `tracing.sampleRate` | The proportion of requests to trace, specified between 0.0 and 1.0. | 1.0 | No | +| `tracing.capturedRequestHeaders` | Defines the list of request headers to add as attributes.
It applies to client and server kind spans. | [] | No | +| `tracing.capturedResponseHeaders` | Defines the list of response headers to add as attributes.
It applies to client and server kind spans. | [] | False | +| `tracing.safeQueryParams` | By default, all query parameters are redacted.
Defines the list of query parameters to not redact. | [] | No | +| `tracing.otlp.http` | This instructs the exporter to send the tracing to the OpenTelemetry Collector using HTTP.
Setting the sub-options with their default values. | null/false | No | +| `tracing.otlp.http.endpoint` | URL of the OpenTelemetry Collector to send tracing to.
Format="`://:`" | "https://localhost:4318/v1/tracing" | Yes | +| `tracing.otlp.http.headers` | Additional headers sent with tracing by the exporter to the OpenTelemetry Collector. | | No | +| `tracing.otlp.http.tls.ca` | Path to the certificate authority used for the secure connection to the OpenTelemetry Collector, it defaults to the system bundle. | "" | No | +| `tracing.otlp.http.tls.cert` | Path to the public certificate used for the secure connection to the OpenTelemetry Collector. When using this option, setting the `key` option is required. | "" | No | +| `tracing.otlp.http.tls.key` | This instructs the exporter to send the tracing to the OpenTelemetry Collector using HTTP.
Setting the sub-options with their default values. | ""null/false "" | No | +| `tracing.otlp.http.tls.insecureskipverify` | If `insecureSkipVerify` is `true`, the TLS connection to the OpenTelemetry Collector accepts any certificate presented by the server regardless of the hostnames it covers. | false | Yes | +| `tracing.otlp.grpc` | This instructs the exporter to send tracing to the OpenTelemetry Collector using gRPC. | false | No | +| `tracing.otlp.grpc.endpoint` | Address of the OpenTelemetry Collector to send tracing to.
Format="`:`" | "localhost:4317" | Yes | +| `tracing.otlp.grpc.headers` | Additional headers sent with tracing by the exporter to the OpenTelemetry Collector. | [] | No | +| `tracing.otlp.grpc.insecure` | Allows exporter to send tracing to the OpenTelemetry Collector without using a secured protocol. | false | Yes | +| `tracing.otlp.grpc.tls.ca` | Path to the certificate authority used for the secure connection to the OpenTelemetry Collector, it defaults to the system bundle. | "" | No | +| `tracing.otlp.grpc.tls.cert` | Path to the public certificate used for the secure connection to the OpenTelemetry Collector. When using this option, setting the `key` option is required. | "" | No | +| `tracing.otlp.grpc.tls.key` | This instructs the exporter to send the tracing to the OpenTelemetry Collector using HTTP.
Setting the sub-options with their default values. | ""null/false "" | No | +| `tracing.otlp.grpc.tls.insecureskipverify` | If `insecureSkipVerify` is `true`, the TLS connection to the OpenTelemetry Collector accepts any certificate presented by the server regardless of the hostnames it covers. | false | Yes | + +## resourceAttributes + +The `resourceAttributes` option allows setting the resource attributes sent along the traces. +Traefik also supports the `OTEL_RESOURCE_ATTRIBUTES` env variable to set up the resource attributes. + +!!! info "Kubernetes Resource Attributes Detection" + + Additionally, Traefik automatically discovers the following [Kubernetes resource attributes](https://opentelemetry.io/docs/specs/semconv/non-normative/k8s-attributes/) when running in a Kubernetes cluster: + + - `k8s.namespace.name` + - `k8s.pod.uid` + - `k8s.pod.name` + + Note that this automatic detection can fail, like if the Traefik pod is running in host network mode. + In this case, you should provide the attributes with the option or the env variable. diff --git a/docs/content/reference/install-configuration/providers/docker.md b/docs/content/reference/install-configuration/providers/docker.md index 74e250ae0..eff2a9297 100644 --- a/docs/content/reference/install-configuration/providers/docker.md +++ b/docs/content/reference/install-configuration/providers/docker.md @@ -29,7 +29,6 @@ providers: Attach labels to containers (in your Docker compose file) ```yaml -version: "3" services: my-container: # ... @@ -41,22 +40,22 @@ services: | Field | Description | Default | Required | |:------|:----------------------------------------------------------|:---------------------|:---------| -| `providers.providersThrottleDuration` | Minimum amount of time to wait for, after a configuration reload, before taking into account any new configuration refresh event.
If multiple events occur within this time, only the most recent one is taken into account, and all others are discarded.
**This option cannot be set per provider, but the throttling algorithm applies to each of them independently.** | 2s | No | -| `providers.docker.endpoint` | Specifies the Docker API endpoint. See [here](#endpoint) for more information| "unix:///var/run/docker.sock" | Yes | -| `providers.docker.username` | Defines the username for Basic HTTP authentication. This should be used when the Docker daemon socket is exposed through an HTTP proxy that requires Basic HTTP authentication.| "" | No | -| `providers.docker.password` | Defines the password for Basic HTTP authentication. This should be used when the Docker daemon socket is exposed through an HTTP proxy that requires Basic HTTP authentication.| "" | No | -| `providers.docker.useBindPortIP` | Instructs Traefik to use the IP/Port attached to the container's binding instead of its inner network IP/Port. See [here](#usebindportip) for more information | false | No | -| `providers.docker.exposedByDefault` | Expose containers by default through Traefik. See [here](./overview.md#restrict-the-scope-of-service-discovery) for additional information | true | No | -| `providers.docker.network` | Defines a default docker network to use for connections to all containers. This option can be overridden on a per-container basis with the `traefik.docker.network` label.| "" | No | -| `providers.docker.defaultRule` | Defines what routing rule to apply to a container if no rule is defined by a label. See [here](#defaultrule) for more information. | ```"Host(`{{ normalize .Name }}`)"``` | No | -| `providers.docker.httpClientTimeout` | Defines the client timeout (in seconds) for HTTP connections. If its value is 0, no timeout is set. | 0 | No | -| `providers.docker.watch` | Instructs Traefik to watch Docker events or not. | True | No | -| `providers.docker.constraints` | Defines an expression that Traefik matches against the container labels to determine whether to create any route for that container. See [here](#constraints) for more information. | "" | No | -| `providers.docker.allowEmptyServices` | Instructs the provider to create any [servers load balancer](../../../routing/services/index.md#servers-load-balancer) defined for Docker containers regardless of the [healthiness](https://docs.docker.com/engine/reference/builder/#healthcheck) of the corresponding containers. | false | No | -| `providers.docker.tls.ca` | Defines the path to the certificate authority used for the secure connection to Docker, it defaults to the system bundle. | "" | No | -| `providers.docker.tls.cert` | Defines the path to the public certificate used for the secure connection to Docker. When using this option, setting the `key` option is required. | "" | Yes | -| `providers.docker.tls.key` | Defines the path to the private key used for the secure connection to Docker. When using this option, setting the `cert` option is required. | "" | Yes | -| `providers.docker.tls.insecureSkipVerify` | Instructs the provider to accept any certificate presented by the Docker server when establishing a TLS connection, regardless of the hostnames the certificate covers. | false | No | +| `providers.providersThrottleDuration` | Minimum amount of time to wait for, after a configuration reload, before taking into account any new configuration refresh event.
If multiple events occur within this time, only the most recent one is taken into account, and all others are discarded.
**This option cannot be set per provider, but the throttling algorithm applies to each of them independently.** | 2s | No | +| `providers.docker.endpoint` | Specifies the Docker API endpoint. See [here](#endpoint) for more information| "unix:///var/run/docker.sock" | Yes | +| `providers.docker.username` | Defines the username for Basic HTTP authentication. This should be used when the Docker daemon socket is exposed through an HTTP proxy that requires Basic HTTP authentication.| "" | No | +| `providers.docker.password` | Defines the password for Basic HTTP authentication. This should be used when the Docker daemon socket is exposed through an HTTP proxy that requires Basic HTTP authentication.| "" | No | +| `providers.docker.useBindPortIP` | Instructs Traefik to use the IP/Port attached to the container's binding instead of its inner network IP/Port. See [here](#usebindportip) for more information | false | No | +| `providers.docker.exposedByDefault` | Expose containers by default through Traefik. See [here](./overview.md#exposedbydefault-and-traefikenable) for additional information | true | No | +| `providers.docker.network` | Defines a default docker network to use for connections to all containers. This option can be overridden on a per-container basis with the `traefik.docker.network` label.| "" | No | +| `providers.docker.defaultRule` | Defines what routing rule to apply to a container if no rule is defined by a label. See [here](#defaultrule) for more information. | ```"Host(`{{ normalize .Name }}`)"``` | No | +| `providers.docker.httpClientTimeout` | Defines the client timeout (in seconds) for HTTP connections. If its value is 0, no timeout is set. | 0 | No | +| `providers.docker.watch` | Instructs Traefik to watch Docker events or not. | True | No | +| `providers.docker.constraints` | Defines an expression that Traefik matches against the container labels to determine whether to create any route for that container. See [here](#constraints) for more information. | "" | No | +| `providers.docker.allowEmptyServices` | Instructs the provider to create any [servers load balancer](../../../routing/services/index.md#servers-load-balancer) defined for Docker containers regardless of the [healthiness](https://docs.docker.com/engine/reference/builder/#healthcheck) of the corresponding containers. | false | No | +| `providers.docker.tls.ca` | Defines the path to the certificate authority used for the secure connection to Docker, it defaults to the system bundle. | "" | No | +| `providers.docker.tls.cert` | Defines the path to the public certificate used for the secure connection to Docker. When using this option, setting the `key` option is required. | "" | Yes | +| `providers.docker.tls.key` | Defines the path to the private key used for the secure connection to Docker. When using this option, setting the `cert` option is required. | "" | Yes | +| `providers.docker.tls.insecureSkipVerify` | Instructs the provider to accept any certificate presented by the Docker server when establishing a TLS connection, regardless of the hostnames the certificate covers. | false | No | ### `endpoint` @@ -67,8 +66,6 @@ See the [Docker API Access](#docker-api-access) section for more information. The docker-compose file shares the docker sock with the Traefik container ```yaml - version: '3' - services: traefik: image: traefik:v3.1 # The official v3 Traefik docker image @@ -195,13 +192,13 @@ but still uses the `traefik.http.services..loadbalancer.server.port` that | port label | Container's binding | Routes to | |--------------------|----------------------------------------------------|----------------| - | - | - | IntIP:IntPort | - | - | ExtPort:IntPort | IntIP:IntPort | - | - | ExtIp:ExtPort:IntPort | ExtIp:ExtPort | - | LblPort | - | IntIp:LblPort | - | LblPort | ExtIp:ExtPort:LblPort | ExtIp:ExtPort | - | LblPort | ExtIp:ExtPort:OtherPort | IntIp:LblPort | - | LblPort | ExtIp1:ExtPort1:IntPort1 & ExtIp2:LblPort:IntPort2 | ExtIp2:LblPort | + | - | - | IntIP:IntPort | + | - | ExtPort:IntPort | IntIP:IntPort | + | - | ExtIp:ExtPort:IntPort | ExtIp:ExtPort | + | LblPort | - | IntIp:LblPort | + | LblPort | ExtIp:ExtPort:LblPort | ExtIp:ExtPort | + | LblPort | ExtIp:ExtPort:OtherPort | IntIp:LblPort | + | LblPort | ExtIp1:ExtPort1:IntPort1 & ExtIp2:LblPort:IntPort2 | ExtIp2:LblPort | !!! info "" In the above table: @@ -273,6 +270,10 @@ created. If the expression is empty, all detected containers are included. The expression syntax is based on the `Label("key", "value")`, and `LabelRegex("key", "value")` functions, as well as the usual boolean logic, as shown in examples below. +!!! tip "Constraints key limitations" + + Note that `traefik.*` is a reserved label namespace for configuration and can not be used as a key for custom constraints. + ??? example "Constraints Expression Examples" ```toml @@ -305,7 +306,7 @@ as well as the usual boolean logic, as shown in examples below. constraints = "LabelRegex(`a.label.name`, `a.+`)" ``` -For additional information, refer to [Restrict the Scope of Service Discovery](./overview.md#restrict-the-scope-of-service-discovery). +For additional information, refer to [Restrict the Scope of Service Discovery](./overview.md#exposedbydefault-and-traefikenable). ```yaml tab="File (YAML)" providers: diff --git a/docs/content/reference/install-configuration/providers/hashicorp/consul-catalog.md b/docs/content/reference/install-configuration/providers/hashicorp/consul-catalog.md index 975c0685d..e69717389 100644 --- a/docs/content/reference/install-configuration/providers/hashicorp/consul-catalog.md +++ b/docs/content/reference/install-configuration/providers/hashicorp/consul-catalog.md @@ -32,34 +32,34 @@ Attaching tags to services: | Field | Description | Default | Required | |:------|:----------------------------------------------------------|:---------------------|:---------| -| `providers.providersThrottleDuration` | Minimum amount of time to wait for, after a configuration reload, before taking into account any new configuration refresh event.
If multiple events occur within this time, only the most recent one is taken into account, and all others are discarded.
**This option cannot be set per provider, but the throttling algorithm applies to each of them independently.** | 2s | No | -| `providers.consulCatalog.refreshInterval` | Defines the polling interval.| 15s | No | -| `providers.consulCatalog.prefix` | Defines the prefix for Consul Catalog tags defining Traefik labels.| traefik | yes | -| `providers.consulCatalog.requireConsistent` | Forces the read to be fully consistent. See [here](#requireconsistent) for more information.| false | yes | -| `providers.consulCatalog.exposedByDefault` | Expose Consul Catalog services by default in Traefik. If set to `false`, services that do not have a `traefik.enable=true` tag will be ignored from the resulting routing configuration. See [here](../overview.md#restrict-the-scope-of-service-discovery). | true | no | -| `providers.consulCatalog.defaultRule` | The Default Host rule for all services. See [here](#defaultrule) for more information. | ```"Host(`{{ normalize .Name }}`)"``` | No | -| `providers.consulCatalog.connectAware` | Enable Consul Connect support. If set to `true`, Traefik will be enabled to communicate with Connect services. | false | No | -| `providers.consulCatalog.connectByDefault` | Consider every service as Connect capable by default. If set to true, Traefik will consider every Consul Catalog service to be Connect capable by default. The option can be overridden on an instance basis with the traefik.consulcatalog.connect tag. | false | No | -| `providers.consulCatalog.serviceName` | Defines the name of the Traefik service in Consul Catalog. | "traefik" | No | -| `providers.consulCatalog.constraints` | Defines an expression that Traefik matches against the container labels to determine whether to create any route for that container. See [here](#constraints) for more information. | "" | No | -| `providers.consulCatalog.namespaces` | Defines the namespaces to query. See [here](#namespaces) for more information. | "" | no | -| `providers.consulCatalog.stale` | Instruct Traefik to use stale consistency for catalog reads. | false | no | -| `providers.consulCatalog.cache` | Instruct Traefik to use local agent caching for catalog reads. | false | no | -| `providers.consulCatalog.endpoint` | Defines the Consul server endpoint. | - | yes | -| `providers.consulCatalog.endpoint.address` | Defines the address of the Consul server. | 127.0.0.1:8500 | no | -| `providers.consulCatalog.endpoint.scheme` | Defines the URI scheme for the Consul server. | "" | no | -| `providers.consulCatalog.endpoint.datacenter` | Defines the datacenter to use. If not provided in Traefik, Consul uses the default agent datacenter. | "" | no | -| `providers.consulCatalog.endpoint.token` | Defines a per-request ACL token which overwrites the agent's default token. | "" | no | -| `providers.consulCatalog.endpoint.endpointWaitTime` | Defines a duration for which a `watch` can block. If not provided, the agent default values will be used. | "" | no | -| `providers.consulCatalog.endpoint.httpAuth` | Defines authentication settings for the HTTP client using HTTP Basic Authentication. | N/A | no | -| `providers.consulCatalog.endpoint.httpAuth.username` | Defines the username to use for HTTP Basic Authentication. | "" | no | -| `providers.consulCatalog.endpoint.httpAuth.password` | Defines the password to use for HTTP Basic Authentication. | "" | no | -| `providers.consulCatalog.strictChecks` | Define which [Consul Service health checks](https://developer.hashicorp.com/consul/docs/services/usage/checks#define-initial-health-check-status) are allowed to take on traffic. | "passing,warning" | no | -| `providers.consulCatalog.tls.ca` | Defines the path to the certificate authority used for the secure connection to Consul Calatog, it defaults to the system bundle. | "" | No | -| `providers.consulCatalog.tls.cert` | Defines the path to the public certificate used for the secure connection to Consul Calatog. When using this option, setting the `key` option is required. | "" | Yes | -| `providers.consulCatalog.tls.key` | Defines the path to the private key used for the secure connection to Consul Catalog. When using this option, setting the `cert` option is required. | "" | Yes | -| `providers.consulCatalog.tls.insecureSkipVerify` | Instructs the provider to accept any certificate presented by Consul Catalog when establishing a TLS connection, regardless of the hostnames the certificate covers. | false | No | -| `providers.consulCatalog.watch` | When set to `true`, watches for Consul changes ([Consul watches checks](https://www.consul.io/docs/dynamic-app-config/watches#checks)). | false | No | +| `providers.providersThrottleDuration` | Minimum amount of time to wait for, after a configuration reload, before taking into account any new configuration refresh event.
If multiple events occur within this time, only the most recent one is taken into account, and all others are discarded.
**This option cannot be set per provider, but the throttling algorithm applies to each of them independently.** | 2s | No | +| `providers.consulCatalog.refreshInterval` | Defines the polling interval.| 15s | No | +| `providers.consulCatalog.prefix` | Defines the prefix for Consul Catalog tags defining Traefik labels.| traefik | yes | +| `providers.consulCatalog.requireConsistent` | Forces the read to be fully consistent. See [here](#requireconsistent) for more information.| false | yes | +| `providers.consulCatalog.exposedByDefault` | Expose Consul Catalog services by default in Traefik. If set to `false`, services that do not have a `traefik.enable=true` tag will be ignored from the resulting routing configuration. See [here](../overview.md#exposedbydefault-and-traefikenable). | true | no | +| `providers.consulCatalog.defaultRule` | The Default Host rule for all services. See [here](#defaultrule) for more information. | ```"Host(`{{ normalize .Name }}`)"``` | No | +| `providers.consulCatalog.connectAware` | Enable Consul Connect support. If set to `true`, Traefik will be enabled to communicate with Connect services. | false | No | +| `providers.consulCatalog.connectByDefault` | Consider every service as Connect capable by default. If set to true, Traefik will consider every Consul Catalog service to be Connect capable by default. The option can be overridden on an instance basis with the traefik.consulcatalog.connect tag. | false | No | +| `providers.consulCatalog.serviceName` | Defines the name of the Traefik service in Consul Catalog. | "traefik" | No | +| `providers.consulCatalog.constraints` | Defines an expression that Traefik matches against the container labels to determine whether to create any route for that container. See [here](#constraints) for more information. | "" | No | +| `providers.consulCatalog.namespaces` | Defines the namespaces to query. See [here](#namespaces) for more information. | "" | no | +| `providers.consulCatalog.stale` | Instruct Traefik to use stale consistency for catalog reads. | false | no | +| `providers.consulCatalog.cache` | Instruct Traefik to use local agent caching for catalog reads. | false | no | +| `providers.consulCatalog.endpoint` | Defines the Consul server endpoint. | - | yes | +| `providers.consulCatalog.endpoint.address` | Defines the address of the Consul server. | 127.0.0.1:8500 | no | +| `providers.consulCatalog.endpoint.scheme` | Defines the URI scheme for the Consul server. | "" | no | +| `providers.consulCatalog.endpoint.datacenter` | Defines the datacenter to use. If not provided in Traefik, Consul uses the default agent datacenter. | "" | no | +| `providers.consulCatalog.endpoint.token` | Defines a per-request ACL token which overwrites the agent's default token. | "" | no | +| `providers.consulCatalog.endpoint.endpointWaitTime` | Defines a duration for which a `watch` can block. If not provided, the agent default values will be used. | "" | no | +| `providers.consulCatalog.endpoint.httpAuth` | Defines authentication settings for the HTTP client using HTTP Basic Authentication. | N/A | no | +| `providers.consulCatalog.endpoint.httpAuth.username` | Defines the username to use for HTTP Basic Authentication. | "" | no | +| `providers.consulCatalog.endpoint.httpAuth.password` | Defines the password to use for HTTP Basic Authentication. | "" | no | +| `providers.consulCatalog.strictChecks` | Define which [Consul Service health checks](https://developer.hashicorp.com/consul/docs/services/usage/checks#define-initial-health-check-status) are allowed to take on traffic. | "passing,warning" | no | +| `providers.consulCatalog.tls.ca` | Defines the path to the certificate authority used for the secure connection to Consul Calatog, it defaults to the system bundle. | "" | No | +| `providers.consulCatalog.tls.cert` | Defines the path to the public certificate used for the secure connection to Consul Calatog. When using this option, setting the `key` option is required. | "" | Yes | +| `providers.consulCatalog.tls.key` | Defines the path to the private key used for the secure connection to Consul Catalog. When using this option, setting the `cert` option is required. | "" | Yes | +| `providers.consulCatalog.tls.insecureSkipVerify` | Instructs the provider to accept any certificate presented by Consul Catalog when establishing a TLS connection, regardless of the hostnames the certificate covers. | false | No | +| `providers.consulCatalog.watch` | When set to `true`, watches for Consul changes ([Consul watches checks](https://www.consul.io/docs/dynamic-app-config/watches#checks)). | false | No | ### `requireConsistent` @@ -112,6 +112,10 @@ created. If the expression is empty, all detected services are included. The expression syntax is based on the ```Tag(`tag`)```, and ```TagRegex(`tag`)``` functions, as well as the usual boolean logic, as shown in examples below. +!!! tip "Constraints key limitations" + + Note that `traefik.*` is a reserved label namespace for configuration and can not be used as a key for custom constraints. + ??? example "Constraints Expression Examples" ```toml @@ -162,7 +166,7 @@ providers: # ... ``` -For additional information, refer to [Restrict the Scope of Service Discovery](../overview.md#restrict-the-scope-of-service-discovery). +For additional information, refer to [Restrict the Scope of Service Discovery](../overview.md#exposedbydefault-and-traefikenable). ### `namespaces` diff --git a/docs/content/reference/install-configuration/providers/hashicorp/consul.md b/docs/content/reference/install-configuration/providers/hashicorp/consul.md index 01c29796f..1f72646ce 100644 --- a/docs/content/reference/install-configuration/providers/hashicorp/consul.md +++ b/docs/content/reference/install-configuration/providers/hashicorp/consul.md @@ -26,18 +26,18 @@ providers: | Field | Description | Default | Required | |:------|:----------------------------------------------------------|:---------------------|:---------| -| `providers.providersThrottleDuration` | Minimum amount of time to wait for, after a configuration reload, before taking into account any new configuration refresh event.
If multiple events occur within this time, only the most recent one is taken into account, and all others are discarded.
**This option cannot be set per provider, but the throttling algorithm applies to each of them independently.** | 2s | No | -| `providers.consul.endpoints` | Defines the endpoint to access Consul. | "127.0.0.1:8500" | yes | -| `providers.consul.rootKey` | Defines the root key of the configuration. | "traefik" | yes | -| `providers.consul.namespaces` | Defines the namespaces to query. See [here](#namespaces) for more information | "" | no | -| `providers.consul.username` | Defines a username to connect to Consul with. | "" | no | -| `providers.consul.password` | Defines a password with which to connect to Consul. | "" | no | -| `providers.consul.token` | Defines a token with which to connect to Consul. | "" | no | -| `providers.consul.tls` | Defines the TLS configuration used for the secure connection to Consul | - | No | -| `providers.consul.tls.ca` | Defines the path to the certificate authority used for the secure connection to Consul, it defaults to the system bundle. | - | Yes | -| `providers.consul.tls.cert` | Defines the path to the public certificate used for the secure connection to Consul. When using this option, setting the `key` option is required. | - | Yes | -| `providers.consul.tls.key` | Defines the path to the private key used for the secure connection to Consul. When using this option, setting the `cert` option is required. | - | Yes | -| `providers.consul.tls.insecureSkipVerify` | Instructs the provider to accept any certificate presented by Consul when establishing a TLS connection, regardless of the hostnames the certificate covers. | false | No | +| `providers.providersThrottleDuration` | Minimum amount of time to wait for, after a configuration reload, before taking into account any new configuration refresh event.
If multiple events occur within this time, only the most recent one is taken into account, and all others are discarded.
**This option cannot be set per provider, but the throttling algorithm applies to each of them independently.** | 2s | No | +| `providers.consul.endpoints` | Defines the endpoint to access Consul. | "127.0.0.1:8500" | yes | +| `providers.consul.rootKey` | Defines the root key of the configuration. | "traefik" | yes | +| `providers.consul.namespaces` | Defines the namespaces to query. See [here](#namespaces) for more information | "" | no | +| `providers.consul.username` | Defines a username to connect to Consul with. | "" | no | +| `providers.consul.password` | Defines a password with which to connect to Consul. | "" | no | +| `providers.consul.token` | Defines a token with which to connect to Consul. | "" | no | +| `providers.consul.tls` | Defines the TLS configuration used for the secure connection to Consul | - | No | +| `providers.consul.tls.ca` | Defines the path to the certificate authority used for the secure connection to Consul, it defaults to the system bundle. | - | Yes | +| `providers.consul.tls.cert` | Defines the path to the public certificate used for the secure connection to Consul. When using this option, setting the `key` option is required. | - | Yes | +| `providers.consul.tls.key` | Defines the path to the private key used for the secure connection to Consul. When using this option, setting the `cert` option is required. | - | Yes | +| `providers.consul.tls.insecureSkipVerify` | Instructs the provider to accept any certificate presented by Consul when establishing a TLS connection, regardless of the hostnames the certificate covers. | false | No | ### `namespaces` diff --git a/docs/content/reference/install-configuration/providers/hashicorp/nomad.md b/docs/content/reference/install-configuration/providers/hashicorp/nomad.md index 19608cd94..29ddc764e 100644 --- a/docs/content/reference/install-configuration/providers/hashicorp/nomad.md +++ b/docs/content/reference/install-configuration/providers/hashicorp/nomad.md @@ -39,25 +39,25 @@ service { | Field | Description | Default | Required | |:------|:----------------------------------------------------------|:---------------------|:---------| -| `providers.providersThrottleDuration` | Minimum amount of time to wait for, after a configuration reload, before taking into account any new configuration refresh event.
If multiple events occur within this time, only the most recent one is taken into account, and all others are discarded.
**This option cannot be set per provider, but the throttling algorithm applies to each of them independently.** | 2s | No | -| `providers.nomad.namespaces` | Defines the namespaces in which the nomad services will be discovered.| "" | No | -| `providers.nomad.refreshInterval` | Defines the polling interval. This option is ignored when the `watch` option is enabled | 15s | No | -| `providers.nomad.watch` | Enables the watch mode to refresh the configuration on a per-event basis. | false | No | -| `providers.nomad.throttleDuration` | Defines how often the provider is allowed to handle service events from Nomad. This option is only compatible when the `watch` option is enabled | 0s | No | -| `providers.nomad.defaultRule` | The Default Host rule for all services. See [here](#defaultrule) for more information | ```"Host(`{{ normalize .Name }}`)"``` | No | -| `providers.nomad.constraints` | Defines an expression that Traefik matches against the container labels to determine whether to create any route for that container. See [here](#constraints) for more information. | "" | No | -| `providers.nomad.exposedByDefault` | Expose Nomad services by default in Traefik. If set to `false`, services that do not have a `traefik.enable=true` tag will be ignored from the resulting routing configuration. See [here](../overview.md#restrict-the-scope-of-service-discovery) for additional information | true | No | -| `providers.nomad.allowEmptyServices` | Instructs the provider to create any [servers load balancer](../../../../routing/services/index.md#servers-load-balancer) defined for Docker containers regardless of the [healthiness](https://docs.docker.com/engine/reference/builder/#healthcheck) of the corresponding containers. | false | No | -| `providers.nomad.prefix` | Defines the prefix for Nomad service tags defining Traefik labels. | `traefik` | yes | -| `providers.nomad.stale` | Instructs Traefik to use stale consistency for Nomad service API reads. See [here](#stale) for more information | false | No | -| `providers.nomad.endpoint.address` | Defines the Address of the Nomad server. | `http://127.0.0.1:4646` | No | -| `providers.nomad.endpoint.token` | Defines a per-request ACL token if Nomad ACLs are enabled. See [here](#token) for more information | "" | No | -| `providers.nomad.endpoint.endpointWaitTime` | Defines a duration for which a `watch` can block. If not provided, the agent default values will be used. | "" | No | -| `providers.nomad.endpoint.tls` | Defines the TLS configuration used for the secure connection to the Nomad APi. | - | No | -| `providers.nomad.endpoint.tls.ca` | Defines the path to the certificate authority used for the secure connection to the Nomad API, it defaults to the system bundle. | "" | No | -| `providers.nomad.endpoint.tls.cert` | Defines the path to the public certificate used for the secure connection to the Nomad API. When using this option, setting the `key` option is required. | '" | Yes | -| `providers.nomad.endpoint.tls.key` | Defines the path to the private key used for the secure connection to the Nomad API. When using this option, setting the `cert` option is required. | "" | Yes | -| `providers.nomad.endpoint.tls.insecureSkipVerify` | Instructs the provider to accept any certificate presented by Nomad when establishing a TLS connection, regardless of the hostnames the certificate covers. | false | No | +| `providers.providersThrottleDuration` | Minimum amount of time to wait for, after a configuration reload, before taking into account any new configuration refresh event.
If multiple events occur within this time, only the most recent one is taken into account, and all others are discarded.
**This option cannot be set per provider, but the throttling algorithm applies to each of them independently.** | 2s | No | +| `providers.nomad.namespaces` | Defines the namespaces in which the nomad services will be discovered.| "" | No | +| `providers.nomad.refreshInterval` | Defines the polling interval. This option is ignored when the `watch` option is enabled | 15s | No | +| `providers.nomad.watch` | Enables the watch mode to refresh the configuration on a per-event basis. | false | No | +| `providers.nomad.throttleDuration` | Defines how often the provider is allowed to handle service events from Nomad. This option is only compatible when the `watch` option is enabled | 0s | No | +| `providers.nomad.defaultRule` | The Default Host rule for all services. See [here](#defaultrule) for more information | ```"Host(`{{ normalize .Name }}`)"``` | No | +| `providers.nomad.constraints` | Defines an expression that Traefik matches against the container labels to determine whether to create any route for that container. See [here](#constraints) for more information. | "" | No | +| `providers.nomad.exposedByDefault` | Expose Nomad services by default in Traefik. If set to `false`, services that do not have a `traefik.enable=true` tag will be ignored from the resulting routing configuration. See [here](../overview.md#exposedbydefault-and-traefikenable) for additional information | true | No | +| `providers.nomad.allowEmptyServices` | Instructs the provider to create any [servers load balancer](../../../../routing/services/index.md#servers-load-balancer) defined for Docker containers regardless of the [healthiness](https://docs.docker.com/engine/reference/builder/#healthcheck) of the corresponding containers. | false | No | +| `providers.nomad.prefix` | Defines the prefix for Nomad service tags defining Traefik labels. | `traefik` | yes | +| `providers.nomad.stale` | Instructs Traefik to use stale consistency for Nomad service API reads. See [here](#stale) for more information | false | No | +| `providers.nomad.endpoint.address` | Defines the Address of the Nomad server. | `http://127.0.0.1:4646` | No | +| `providers.nomad.endpoint.token` | Defines a per-request ACL token if Nomad ACLs are enabled. See [here](#token) for more information | "" | No | +| `providers.nomad.endpoint.endpointWaitTime` | Defines a duration for which a `watch` can block. If not provided, the agent default values will be used. | "" | No | +| `providers.nomad.endpoint.tls` | Defines the TLS configuration used for the secure connection to the Nomad APi. | - | No | +| `providers.nomad.endpoint.tls.ca` | Defines the path to the certificate authority used for the secure connection to the Nomad API, it defaults to the system bundle. | "" | No | +| `providers.nomad.endpoint.tls.cert` | Defines the path to the public certificate used for the secure connection to the Nomad API. When using this option, setting the `key` option is required. | '" | Yes | +| `providers.nomad.endpoint.tls.key` | Defines the path to the private key used for the secure connection to the Nomad API. When using this option, setting the `cert` option is required. | "" | Yes | +| `providers.nomad.endpoint.tls.insecureSkipVerify` | Instructs the provider to accept any certificate presented by Nomad when establishing a TLS connection, regardless of the hostnames the certificate covers. | false | No | ### `namespaces` @@ -191,6 +191,10 @@ created. If the expression is empty, all detected services are included. The expression syntax is based on the ```Tag(`tag`)```, and ```TagRegex(`tag`)``` functions, as well as the usual boolean logic, as shown in examples below. +!!! tip "Constraints key limitations" + + Note that `traefik.*` is a reserved label namespace for configuration and can not be used as a key for custom constraints. + ??? example "Constraints Expression Examples" ```toml @@ -241,7 +245,7 @@ providers: # ... ``` -For additional information, refer to [Restrict the Scope of Service Discovery](../overview.md#restrict-the-scope-of-service-discovery). +For additional information, refer to [Restrict the Scope of Service Discovery](../overview.md#exposedbydefault-and-traefikenable). ## Routing Configuration diff --git a/docs/content/reference/install-configuration/providers/kubernetes/kubernetes-crd.md b/docs/content/reference/install-configuration/providers/kubernetes/kubernetes-crd.md index 6e787d206..50ea7ff2d 100644 --- a/docs/content/reference/install-configuration/providers/kubernetes/kubernetes-crd.md +++ b/docs/content/reference/install-configuration/providers/kubernetes/kubernetes-crd.md @@ -20,10 +20,10 @@ When you install Traefik without using the Helm Chart, or when you are upgrading ```bash # Install Traefik Resource Definitions: -kubectl apply -f https://raw.githubusercontent.com/traefik/traefik/v3.4/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml +kubectl apply -f https://raw.githubusercontent.com/traefik/traefik/v3.5/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml # Install RBAC for Traefik: -kubectl apply -f https://raw.githubusercontent.com/traefik/traefik/v3.4/docs/content/reference/dynamic-configuration/kubernetes-crd-rbac.yml +kubectl apply -f https://raw.githubusercontent.com/traefik/traefik/v3.5/docs/content/reference/dynamic-configuration/kubernetes-crd-rbac.yml ``` ## Configuration Example @@ -54,19 +54,19 @@ providers: | Field | Description | Default | Required | |:------|:----------------------------------------------------------|:--------|:---------| -| `providers.providersThrottleDuration` | Minimum amount of time to wait for, after a configuration reload, before taking into account any new configuration refresh event.
If multiple events occur within this time, only the most recent one is taken into account, and all others are discarded.
**This option cannot be set per provider, but the throttling algorithm applies to each of them independently.** | 2s | No | -| `providers.kubernetesCRD.endpoint` | Server endpoint URL.
More information [here](#endpoint). | "" | No | -| `providers.kubernetesCRD.token` | Bearer token used for the Kubernetes client configuration. | "" | No | -| `providers.kubernetesCRD.certAuthFilePath` | Path to the certificate authority file.
Used for the Kubernetes client configuration. | "" | No | -| `providers.kubernetesCRD.namespaces` | Array of namespaces to watch.
If left empty, watch all namespaces. | [] | No | -| `providers.kubernetesCRD.labelselector` | Allow filtering on specific resource objects only using label selectors.
Only to Traefik [Custom Resources](#list-of-resources) (they all must match the filter).
No effect on Kubernetes `Secrets`, `EndpointSlices` and `Services`.
See [label-selectors](https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors) for details. | "" | No | -| `providers.kubernetesCRD.ingressClass` | Value of `kubernetes.io/ingress.class` annotation that identifies resource objects to be processed.
If empty, resources missing the annotation, having an empty value, or the value `traefik` are processed. | "" | No | -| `providers.kubernetesCRD.throttleDuration` | Minimum amount of time to wait between two Kubernetes events before producing a new configuration.
This prevents a Kubernetes cluster that updates many times per second from continuously changing your Traefik configuration.
If empty, every event is caught. | 0s | No | -| `providers.kubernetesCRD.allowEmptyServices` | Allows creating a route to reach a service that has no endpoint available.
It allows Traefik to handle the requests and responses targeting this service (applying middleware or observability operations) before returning a `503` HTTP Status. | false | No | -| `providers.kubernetesCRD.allowCrossNamespace` | Allows the `IngressRoutes` to reference resources in namespaces other than theirs. | false | No | -| `providers.kubernetesCRD.allowExternalNameServices` | Allows the `IngressRoutes` to reference ExternalName services. | false | No | -| `providers.kubernetesCRD.nativeLBByDefault` | Allow using the Kubernetes Service load balancing between the pods instead of the one provided by Traefik for every `IngressRoute` by default.
It can br overridden in the [`ServerTransport`](../../../../routing/services/index.md#serverstransport). | false | No | -| `providers.kubernetesCRD.disableClusterScopeResources` | Prevent from discovering cluster scope resources (`IngressClass` and `Nodes`).
By doing so, it alleviates the requirement of giving Traefik the rights to look up for cluster resources.
Furthermore, Traefik will not handle IngressRoutes with IngressClass references, therefore such Ingresses will be ignored (please note that annotations are not affected by this option).
This will also prevent from using the `NodePortLB` options on services. | false | No | +| `providers.providersThrottleDuration` | Minimum amount of time to wait for, after a configuration reload, before taking into account any new configuration refresh event.
If multiple events occur within this time, only the most recent one is taken into account, and all others are discarded.
**This option cannot be set per provider, but the throttling algorithm applies to each of them independently.** | 2s | No | +| `providers.kubernetesCRD.endpoint` | Server endpoint URL.
More information [here](#endpoint). | "" | No | +| `providers.kubernetesCRD.token` | Bearer token used for the Kubernetes client configuration. | "" | No | +| `providers.kubernetesCRD.certAuthFilePath` | Path to the certificate authority file.
Used for the Kubernetes client configuration. | "" | No | +| `providers.kubernetesCRD.namespaces` | Array of namespaces to watch.
If left empty, watch all namespaces. | [] | No | +| `providers.kubernetesCRD.labelselector` | Allow filtering on specific resource objects only using label selectors.
Only to Traefik [Custom Resources](#list-of-resources) (they all must match the filter).
No effect on Kubernetes `Secrets`, `EndpointSlices` and `Services`.
See [label-selectors](https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors) for details. | "" | No | +| `providers.kubernetesCRD.ingressClass` | Value of `kubernetes.io/ingress.class` annotation that identifies resource objects to be processed.
If empty, resources missing the annotation, having an empty value, or the value `traefik` are processed. | "" | No | +| `providers.kubernetesCRD.throttleDuration` | Minimum amount of time to wait between two Kubernetes events before producing a new configuration.
This prevents a Kubernetes cluster that updates many times per second from continuously changing your Traefik configuration.
If empty, every event is caught. | 0s | No | +| `providers.kubernetesCRD.allowEmptyServices` | Allows creating a route to reach a service that has no endpoint available.
It allows Traefik to handle the requests and responses targeting this service (applying middleware or observability operations) before returning a `503` HTTP Status. | false | No | +| `providers.kubernetesCRD.allowCrossNamespace` | Allows the `IngressRoutes` to reference resources in namespaces other than theirs. | false | No | +| `providers.kubernetesCRD.allowExternalNameServices` | Allows the `IngressRoutes` to reference ExternalName services. | false | No | +| `providers.kubernetesCRD.nativeLBByDefault` | Allow using the Kubernetes Service load balancing between the pods instead of the one provided by Traefik for every `IngressRoute` by default.
It can br overridden in the [`ServerTransport`](../../../../routing/services/index.md#serverstransport). | false | No | +| `providers.kubernetesCRD.disableClusterScopeResources` | Prevent from discovering cluster scope resources (`IngressClass` and `Nodes`).
By doing so, it alleviates the requirement of giving Traefik the rights to look up for cluster resources.
Furthermore, Traefik will not handle IngressRoutes with IngressClass references, therefore such Ingresses will be ignored (please note that annotations are not affected by this option).
This will also prevent from using the `NodePortLB` options on services. | false | No | ### endpoint @@ -108,18 +108,18 @@ See the dedicated section in [routing](../../../../routing/providers/kubernetes- -| Resource | Purpose | -|--------------------------------------------------|--------------------------------------------------------------------| -| [IngressRoute](../../../../routing/providers/kubernetes-crd.md#kind-ingressroute) | HTTP Routing | -| [Middleware](../../../../middlewares/http/overview.md) | Tweaks the HTTP requests before they are sent to your service | -| [TraefikService](../../../../routing/providers/kubernetes-crd.md#kind-traefikservice) | Abstraction for HTTP loadbalancing/mirroring | -| [TLSOptions](../../../../routing/providers/kubernetes-crd.md#kind-tlsoption) | Allows configuring some parameters of the TLS connection | -| [TLSStores](../../../../routing/providers/kubernetes-crd.md#kind-tlsstore) | Allows configuring the default TLS store | -| [ServersTransport](../../../../routing/providers/kubernetes-crd.md#kind-serverstransport) | Allows configuring the transport between Traefik and the backends | -| [IngressRouteTCP](../../../../routing/providers/kubernetes-crd.md#kind-ingressroutetcp) | TCP Routing | -| [MiddlewareTCP](../../../../routing/providers/kubernetes-crd.md#kind-middlewaretcp) | Tweaks the TCP requests before they are sent to your service | -| [ServersTransportTCP](../../../../routing/providers/kubernetes-crd.md#kind-serverstransporttc) | Allows configuring the transport between Traefik and the backends | -| [IngressRouteUDP](../../../../routing/providers/kubernetes-crd.md#kind-ingressrouteudp) | UDP Routing | +| Resource | Purpose | +|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------| +| [IngressRoute](../../../routing-configuration/kubernetes/crd/http/ingressroute.md) | HTTP Routing | +| [Middleware](../../../routing-configuration/kubernetes/crd/http/middleware.md) | Tweaks the HTTP requests before they are sent to your service | +| [TraefikService](../../../routing-configuration/kubernetes/crd/http/traefikservice.md) | Abstraction for HTTP loadbalancing/mirroring | +| [TLSOptions](../../../routing-configuration/kubernetes/crd/tls/tlsoption.md) | Allows configuring some parameters of the TLS connection | +| [TLSStores](../../../routing-configuration/kubernetes/crd/tls/tlsstore.md) | Allows configuring the default TLS store | +| [ServersTransport](../../../routing-configuration/kubernetes/crd/http/serverstransport.md) | Allows configuring the transport between Traefik and the backends | +| [IngressRouteTCP](../../../routing-configuration/kubernetes/crd/tcp/ingressroutetcp.md) | TCP Routing | +| [MiddlewareTCP](../../../routing-configuration/kubernetes/crd/tcp/middlewaretcp.md) | Tweaks the TCP requests before they are sent to your service | +| [ServersTransportTCP](../../../routing-configuration/kubernetes/crd/tcp/serverstransporttcp.md) | Allows configuring the transport between Traefik and the backends | +| [IngressRouteUDP](../../../routing-configuration/kubernetes/crd/udp/ingressrouteudp.md) | UDP Routing | ## Particularities diff --git a/docs/content/reference/install-configuration/providers/kubernetes/kubernetes-gateway.md b/docs/content/reference/install-configuration/providers/kubernetes/kubernetes-gateway.md index 98b3f2add..bdbf04428 100644 --- a/docs/content/reference/install-configuration/providers/kubernetes/kubernetes-gateway.md +++ b/docs/content/reference/install-configuration/providers/kubernetes/kubernetes-gateway.md @@ -8,11 +8,11 @@ description: "Learn how to use the Kubernetes Gateway API as a provider for conf The Kubernetes Gateway provider is a Traefik implementation of the [Gateway API](https://gateway-api.sigs.k8s.io/) specification from the Kubernetes Special Interest Groups (SIGs). -This provider supports Standard version [v1.2.1](https://github.com/kubernetes-sigs/gateway-api/releases/tag/v1.2.1) of the Gateway API specification. +This provider supports Standard version [v1.3.0](https://github.com/kubernetes-sigs/gateway-api/releases/tag/v1.3.0) of the Gateway API specification. It fully supports all HTTP core and some extended features, as well as the `TCPRoute` and `TLSRoute` resources from the [Experimental channel](https://gateway-api.sigs.k8s.io/concepts/versioning/?h=#release-channels). -For more details, check out the conformance [report](https://github.com/kubernetes-sigs/gateway-api/tree/main/conformance/reports/v1.2.1/traefik-traefik). +For more details, check out the conformance [report](https://github.com/kubernetes-sigs/gateway-api/tree/main/conformance/reports/v1.3.0/traefik-traefik). !!! info "Using The Helm Chart" @@ -27,14 +27,14 @@ For more details, check out the conformance [report](https://github.com/kubernet ```bash # Install Gateway API CRDs from the Standard channel. - kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.2.1/standard-install.yaml + kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.3.0/standard-install.yaml ``` 2. Install/update the Traefik [RBAC](../../../dynamic-configuration/kubernetes-gateway-rbac.yml). ```bash # Install Traefik RBACs. - kubectl apply -f https://raw.githubusercontent.com/traefik/traefik/v3.4/docs/content/reference/dynamic-configuration/kubernetes-gateway-rbac.yml + kubectl apply -f https://raw.githubusercontent.com/traefik/traefik/v3.5/docs/content/reference/dynamic-configuration/kubernetes-gateway-rbac.yml ``` ## Configuration Example @@ -69,19 +69,19 @@ providers: | Field | Description | Default | Required | |:----------------------------------------------------------------------|:------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:--------|:---------| -| `providers.providersThrottleDuration` | Minimum amount of time to wait for, after a configuration reload, before taking into account any new configuration refresh event.
If multiple events occur within this time, only the most recent one is taken into account, and all others are discarded.
**This option cannot be set per provider, but the throttling algorithm applies to each of them independently.** | 2s | No | -| `providers.kubernetesGateway.endpoint` | Server endpoint URL.
More information [here](#endpoint). | "" | No | -| `providers.kubernetesGateway.experimentalChannel` | Toggles support for the Experimental Channel resources ([Gateway API release channels documentation](https://gateway-api.sigs.k8s.io/concepts/versioning/#release-channels)).
(ex: `TCPRoute` and `TLSRoute`) | false | No | -| `providers.kubernetesGateway.token` | Bearer token used for the Kubernetes client configuration. | "" | No | -| `providers.kubernetesGateway.certAuthFilePath` | Path to the certificate authority file.
Used for the Kubernetes client configuration. | "" | No | -| `providers.kubernetesGateway.namespaces` | Array of namespaces to watch.
If left empty, watch all namespaces. | [] | No | -| `providers.kubernetesGateway.labelselector` | Allow filtering on specific resource objects only using label selectors.
Only to Traefik [Custom Resources](./kubernetes-crd.md#list-of-resources) (they all must match the filter).
No effect on Kubernetes `Secrets`, `EndpointSlices` and `Services`.
See [label-selectors](https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors) for details. | "" | No | -| `providers.kubernetesGateway.throttleDuration` | Minimum amount of time to wait between two Kubernetes events before producing a new configuration.
This prevents a Kubernetes cluster that updates many times per second from continuously changing your Traefik configuration.
If empty, every event is caught. | 0s | No | -| `providers.kubernetesGateway.nativeLBByDefault` | Defines whether to use Native Kubernetes load-balancing mode by default. For more information, please check out the `traefik.io/service.nativelb` service annotation documentation. | false | No | -| `providers.kubernetesGateway.`
`statusAddress.hostname` | Hostname copied to the Gateway `status.addresses`. | "" | No | -| `providers.kubernetesGateway.`
`statusAddress.ip` | IP address copied to the Gateway `status.addresses`, and currently only supports one IP value (IPv4 or IPv6). | "" | No | -| `providers.kubernetesGateway.`
`statusAddress.service.namespace` | The namespace of the Kubernetes service to copy status addresses from.
When using third parties tools like External-DNS, this option can be used to copy the service `loadbalancer.status` (containing the service's endpoints IPs) to the Gateway `status.addresses`. | "" | No | -| `providers.kubernetesGateway.`
`statusAddress.service.name` | The name of the Kubernetes service to copy status addresses from.
When using third parties tools like External-DNS, this option can be used to copy the service `loadbalancer.status` (containing the service's endpoints IPs) to the Gateway `status.addresses`. | "" | No | +| `providers.providersThrottleDuration` | Minimum amount of time to wait for, after a configuration reload, before taking into account any new configuration refresh event.
If multiple events occur within this time, only the most recent one is taken into account, and all others are discarded.
**This option cannot be set per provider, but the throttling algorithm applies to each of them independently.** | 2s | No | +| `providers.kubernetesGateway.endpoint` | Server endpoint URL.
More information [here](#endpoint). | "" | No | +| `providers.kubernetesGateway.experimentalChannel` | Toggles support for the Experimental Channel resources ([Gateway API release channels documentation](https://gateway-api.sigs.k8s.io/concepts/versioning/#release-channels)).
(ex: `TCPRoute` and `TLSRoute`) | false | No | +| `providers.kubernetesGateway.token` | Bearer token used for the Kubernetes client configuration. | "" | No | +| `providers.kubernetesGateway.certAuthFilePath` | Path to the certificate authority file.
Used for the Kubernetes client configuration. | "" | No | +| `providers.kubernetesGateway.namespaces` | Array of namespaces to watch.
If left empty, watch all namespaces. | [] | No | +| `providers.kubernetesGateway.labelselector` | Allow filtering on specific resource objects only using label selectors.
Only to Traefik [Custom Resources](./kubernetes-crd.md#list-of-resources) (they all must match the filter).
No effect on Kubernetes `Secrets`, `EndpointSlices` and `Services`.
See [label-selectors](https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors) for details. | "" | No | +| `providers.kubernetesGateway.throttleDuration` | Minimum amount of time to wait between two Kubernetes events before producing a new configuration.
This prevents a Kubernetes cluster that updates many times per second from continuously changing your Traefik configuration.
If empty, every event is caught. | 0s | No | +| `providers.kubernetesGateway.nativeLBByDefault` | Defines whether to use Native Kubernetes load-balancing mode by default. For more information, please check out the `traefik.io/service.nativelb` service annotation documentation. | false | No | +| `providers.kubernetesGateway.`
`statusAddress.hostname`
| Hostname copied to the Gateway `status.addresses`. | "" | No | +| `providers.kubernetesGateway.`
`statusAddress.ip`
| IP address copied to the Gateway `status.addresses`, and currently only supports one IP value (IPv4 or IPv6). | "" | No | +| `providers.kubernetesGateway.`
`statusAddress.service.namespace`
| The namespace of the Kubernetes service to copy status addresses from.
When using third parties tools like External-DNS, this option can be used to copy the service `loadbalancer.status` (containing the service's endpoints IPs) to the Gateway `status.addresses`. | "" | No | +| `providers.kubernetesGateway.`
`statusAddress.service.name`
| The name of the Kubernetes service to copy status addresses from.
When using third parties tools like External-DNS, this option can be used to copy the service `loadbalancer.status` (containing the service's endpoints IPs) to the Gateway `status.addresses`. | "" | No | diff --git a/docs/content/reference/install-configuration/providers/kubernetes/kubernetes-ingress-nginx.md b/docs/content/reference/install-configuration/providers/kubernetes/kubernetes-ingress-nginx.md new file mode 100644 index 000000000..2d9e93c1e --- /dev/null +++ b/docs/content/reference/install-configuration/providers/kubernetes/kubernetes-ingress-nginx.md @@ -0,0 +1,112 @@ +--- +title: "Traefik Kubernetes Ingress NGINX Documentation" +description: "Understand the requirements, routing configuration, and how to set up the Kubernetes Ingress NGINX provider. Read the technical documentation." +--- + +# Traefik & Ingresses with NGINX Annotations + +The experimental Traefik Kubernetes Ingress NGINX provider is a Kubernetes Ingress controller; i.e, +it manages access to cluster services by supporting the [Ingress](https://kubernetes.io/docs/concepts/services-networking/ingress/) specification. +It also supports some of the [ingress-nginx](https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/annotations/) annotations on ingresses to customize their behavior. + +!!! warning "Ingress Discovery" + + The Kubernetes Ingress NGINX provider is discovering by default all Ingresses in the cluster, + which may lead to duplicated routers if you are also using the Kubernetes Ingress provider. + We recommend to use IngressClass for the Ingresses you want to be handled by this provider, + or to use the `watchNamespace` or `watchNamespaceSelector` options to limit the discovery of Ingresses to a specific namespace or set of namespaces. + +## Configuration Example + +As this provider is an experimental feature, it needs to be enabled in the experimental and in the provider sections of the configuration. +You can enable the Kubernetes Ingress NGINX provider as detailed below: + +```yaml tab="File (YAML)" +experimental: + kubernetesIngressNGINX: true + +providers: + kubernetesIngressNGINX: {} +``` + +```toml tab="File (TOML)" +[experimental.kubernetesIngressNGINX] + +[providers.kubernetesIngressNGINX] +``` + +```bash tab="CLI" +--experimental.kubernetesingressnginx=true +--providers.kubernetesingressnginx=true +``` + +The provider then watches for incoming ingresses events, such as the example below, +and derives the corresponding dynamic configuration from it, +which in turn creates the resulting routers, services, handlers, etc. + +## Configuration Options + + +| Field | Description | Default | Required | +|:------------------------------------------------------------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:--------|:---------| +| `providers.providersThrottleDuration` | Minimum amount of time to wait for, after a configuration reload, before taking into account any new configuration refresh event.
If multiple events occur within this time, only the most recent one is taken into account, and all others are discarded.
**This option cannot be set per provider, but the throttling algorithm applies to each of them independently.** | 2s | No | +| `providers.kubernetesIngressNGINX.endpoint` | Server endpoint URL.
More information [here](#endpoint). | "" | No | +| `providers.kubernetesIngressNGINX.token` | Bearer token used for the Kubernetes client configuration. | "" | No | +| `providers.kubernetesIngressNGINX.certAuthFilePath` | Path to the certificate authority file.
Used for the Kubernetes client configuration. | "" | No | +| `providers.kubernetesIngressNGINX.throttleDuration` | Minimum amount of time to wait between two Kubernetes events before producing a new configuration.
This prevents a Kubernetes cluster that updates many times per second from continuously changing your Traefik configuration.
If empty, every event is caught. | 0s | No | +| `providers.kubernetesIngressNGINX.watchNamespace` | Namespace the controller watches for updates to Kubernetes objects. All namespaces are watched if this parameter is left empty. | "" | No | +| `providers.kubernetesIngressNGINX.watchNamespaceSelector` | Selector selects namespaces the controller watches for updates to Kubernetes objects. | "" | No | +| `providers.kubernetesIngressNGINX.ingressClass` | Name of the ingress class this controller satisfies. | "" | No | +| `providers.kubernetesIngressNGINX.controllerClass` | Ingress Class Controller value this controller satisfies. | "" | No | +| `providers.kubernetesIngressNGINX.watchIngressWithoutClass` | Define if Ingress Controller should also watch for Ingresses without an IngressClass or the annotation specified. | false | No | +| `providers.kubernetesIngressNGINX.ingressClassByName` | Define if Ingress Controller should watch for Ingress Class by Name together with Controller Class. | false | No | +| `providers.kubernetesIngressNGINX.publishService` | Service fronting the Ingress controller. Takes the form namespace/name. | "" | No | +| `providers.kubernetesIngressNGINX.publishStatusAddress` | Customized address (or addresses, separated by comma) to set as the load-balancer status of Ingress objects this controller satisfies. | "" | No | +| `providers.kubernetesIngressNGINX.defaultBackendService` | Service used to serve HTTP requests not matching any known server name (catch-all). Takes the form 'namespace/name'. | "" | No | +| `providers.kubernetesIngressNGINX.disableSvcExternalName` | Disable support for Services of type ExternalName. | false | No | + + + +### `endpoint` + +The Kubernetes server endpoint URL. + +When deployed into Kubernetes, Traefik reads the environment variables `KUBERNETES_SERVICE_HOST` +and `KUBERNETES_SERVICE_PORT` or `KUBECONFIG` to construct the endpoint. + +The access token is looked up in `/var/run/secrets/kubernetes.io/serviceaccount/token` +and the SSL CA certificate in `/var/run/secrets/kubernetes.io/serviceaccount/ca.crt`. +Both are mounted automatically when deployed inside Kubernetes. + +The endpoint may be specified to override the environment variable values inside +a cluster. + +When the environment variables are not found, Traefik tries to connect to the +Kubernetes API server with an external-cluster client. + +In this case, the endpoint is required. +Specifically, it may be set to the URL used by `kubectl proxy` to connect to a Kubernetes +cluster using the granted authentication and authorization of the associated kubeconfig. + +```yaml tab="File (YAML)" +providers: + kubernetesIngressNGINX: + endpoint: "http://localhost:8080" + # ... +``` + +```toml tab="File (TOML)" +[providers.kubernetesIngressNGINX] + endpoint = "http://localhost:8080" + # ... +``` + +```bash tab="CLI" +--providers.kubernetesingressnginx.endpoint=http://localhost:8080 +``` + +## Routing Configuration + +See the dedicated section in [routing](../../../routing-configuration/kubernetes/ingress-nginx.md). + +{!traefik-for-business-applications.md!} diff --git a/docs/content/reference/install-configuration/providers/kubernetes/kubernetes-ingress.md b/docs/content/reference/install-configuration/providers/kubernetes/kubernetes-ingress.md index adf102e64..5ac53b269 100644 --- a/docs/content/reference/install-configuration/providers/kubernetes/kubernetes-ingress.md +++ b/docs/content/reference/install-configuration/providers/kubernetes/kubernetes-ingress.md @@ -3,11 +3,16 @@ title: "Traefik Kubernetes Ingress Documentation" description: "Understand the requirements, routing configuration, and how to set up Traefik Proxy as your Kubernetes Ingress Controller. Read the technical documentation." --- -# Traefik & Kubernetes +# Traefik & Kubernetes The Traefik Kubernetes Ingress provider is a Kubernetes Ingress controller; i.e, it manages access to cluster services by supporting the [Ingress](https://kubernetes.io/docs/concepts/services-networking/ingress/) specification. +??? warning "Ingress Backend Resource not supported" + + Referencing backend service endpoints using [`spec.rules.http.paths.backend.resource`](https://kubernetes.io/docs/reference/kubernetes-api/service-resources/ingress-v1/#IngressBackend) is not supported. + Use `spec.rules.http.paths.backend.service` instead. + ## Configuration Example You can enable the `kubernetesIngress` provider as detailed below: @@ -37,27 +42,29 @@ and derives the corresponding dynamic configuration from it, which in turn creates the resulting routers, services, handlers, etc. ## Configuration Options + -| Field | Description | Default | Required | -|:-----------------------------------------------------------------------|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:--------|:---------| -| `providers.providersThrottleDuration` | Minimum amount of time to wait for, after a configuration reload, before taking into account any new configuration refresh event.
If multiple events occur within this time, only the most recent one is taken into account, and all others are discarded.
**This option cannot be set per provider, but the throttling algorithm applies to each of them independently.** | 2s | No | -| `providers.kubernetesIngress.endpoint` | Server endpoint URL.
More information [here](#endpoint). | "" | No | -| `providers.kubernetesIngress.token` | Bearer token used for the Kubernetes client configuration. | "" | No | -| `providers.kubernetesIngress.certAuthFilePath` | Path to the certificate authority file.
Used for the Kubernetes client configuration. | "" | No | -| `providers.kubernetesCRD.namespaces` | Array of namespaces to watch.
If left empty, watch all namespaces. | | No | -| `providers.kubernetesIngress.labelselector` | Allow filtering on Ingress objects using label selectors.
No effect on Kubernetes `Secrets`, `EndpointSlices` and `Services`.
See [label-selectors](https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors) for details. | "" | No | -| `providers.kubernetesIngress.ingressClass` | The `IngressClass` resource name or the `kubernetes.io/ingress.class` annotation value that identifies resource objects to be processed.
If empty, resources missing the annotation, having an empty value, or the value `traefik` are processed. | "" | No | -| `providers.kubernetesIngress.disableIngressClassLookup` | Prevent to discover IngressClasses in the cluster.
It alleviates the requirement of giving Traefik the rights to look IngressClasses up.
Ignore Ingresses with IngressClass.
Annotations are not affected by this option. | false | No | -| `providers.kubernetesIngress.`
`ingressEndpoint.hostname` | Hostname used for Kubernetes Ingress endpoints. | "" | No | -| `providers.kubernetesIngress.`
`ingressEndpoint.ip` | This IP will get copied to the Ingress `status.loadbalancer.ip`, and currently only supports one IP value (IPv4 or IPv6). | "" | No | -| `providers.kubernetesIngress.`
`ingressEndpoint.publishedService` | The Kubernetes service to copy status from.
More information [here](#ingressendpointpublishedservice). | "" | No | -| `providers.kubernetesIngress.throttleDuration` | Minimum amount of time to wait between two Kubernetes events before producing a new configuration.
This prevents a Kubernetes cluster that updates many times per second from continuously changing your Traefik configuration.
If empty, every event is caught. | 0s | No | -| `providers.kubernetesIngress.allowEmptyServices` | Allows creating a route to reach a service that has no endpoint available.
It allows Traefik to handle the requests and responses targeting this service (applying middleware or observability operations) before returning a `503` HTTP Status. | false | No | -| `providers.kubernetesIngress.allowCrossNamespace` | Allows the `Ingress` to reference resources in namespaces other than theirs. | false | No | -| `providers.kubernetesIngress.allowExternalNameServices` | Allows the `Ingress` to reference ExternalName services. | false | No | -| `providers.kubernetesIngress.nativeLBByDefault` | Allow using the Kubernetes Service load balancing between the pods instead of the one provided by Traefik for every `Ingress` by default.
It can br overridden in the [`ServerTransport`](../../../../routing/services/index.md#serverstransport). | false | No | -| `providers.kubernetesIngress.disableClusterScopeResources` | Prevent from discovering cluster scope resources (`IngressClass` and `Nodes`).
By doing so, it alleviates the requirement of giving Traefik the rights to look up for cluster resources.
Furthermore, Traefik will not handle Ingresses with IngressClass references, therefore such Ingresses will be ignored (please note that annotations are not affected by this option).
This will also prevent from using the `NodePortLB` options on services. | false | No | +| Field | Description | Default | Required | +| :------------------------------------------------------------------ | :------------- | :------ | :------- | +| `providers.providersThrottleDuration` | Minimum amount of time to wait for, after a configuration reload, before taking into account any new configuration refresh event.
If multiple events occur within this time, only the most recent one is taken into account, and all others are discarded.
**This option cannot be set per provider, but the throttling algorithm applies to each of them independently.** | 2s | No | +| `providers.kubernetesIngress.endpoint` | Server endpoint URL.
More information [here](#endpoint). | "" | No | +| `providers.kubernetesIngress.token` | Bearer token used for the Kubernetes client configuration. | "" | No | +| `providers.kubernetesIngress.certAuthFilePath` | Path to the certificate authority file.
Used for the Kubernetes client configuration. | "" | No | +| `providers.kubernetesIngress.namespaces` | Array of namespaces to watch.
If left empty, watch all namespaces. | | No | +| `providers.kubernetesIngress.labelselector` | Allow filtering on Ingress objects using label selectors.
No effect on Kubernetes `Secrets`, `EndpointSlices` and `Services`.
See [label-selectors](https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors) for details. | "" | No | +| `providers.kubernetesIngress.ingressClass` | The `IngressClass` resource name or the `kubernetes.io/ingress.class` annotation value that identifies resource objects to be processed.
If empty, resources missing the annotation, having an empty value, or the value `traefik` are processed. | "" | No | +| `providers.kubernetesIngress.disableIngressClassLookup` | Prevent to discover IngressClasses in the cluster.
It alleviates the requirement of giving Traefik the rights to look IngressClasses up.
Ignore Ingresses with IngressClass.
Annotations are not affected by this option. | false | No | +| `providers.kubernetesIngress.`
`ingressEndpoint.hostname`
| Hostname used for Kubernetes Ingress endpoints. | "" | No | +| `providers.kubernetesIngress.`
`ingressEndpoint.ip`
| This IP will get copied to the Ingress `status.loadbalancer.ip`, and currently only supports one IP value (IPv4 or IPv6). | "" | No | +| `providers.kubernetesIngress.`
`ingressEndpoint.publishedService`
| The Kubernetes service to copy status from.
More information [here](#ingressendpointpublishedservice). | "" | No | +| `providers.kubernetesIngress.throttleDuration` | Minimum amount of time to wait between two Kubernetes events before producing a new configuration.
This prevents a Kubernetes cluster that updates many times per second from continuously changing your Traefik configuration.
If empty, every event is caught. | 0s | No | +| `providers.kubernetesIngress.allowEmptyServices` | Allows creating a route to reach a service that has no endpoint available.
It allows Traefik to handle the requests and responses targeting this service (applying middleware or observability operations) before returning a `503` HTTP Status. | false | No | +| `providers.kubernetesIngress.allowCrossNamespace` | Allows the `Ingress` to reference resources in namespaces other than theirs. | false | No | +| `providers.kubernetesIngress.allowExternalNameServices` | Allows the `Ingress` to reference ExternalName services. | false | No | +| `providers.kubernetesIngress.nativeLBByDefault` | Allow using the Kubernetes Service load balancing between the pods instead of the one provided by Traefik for every `Ingress` by default.
It can br overridden in the [`ServerTransport`](../../../../routing/services/index.md#serverstransport). | false | No | +| `providers.kubernetesIngress.disableClusterScopeResources` | Prevent from discovering cluster scope resources (`IngressClass` and `Nodes`).
By doing so, it alleviates the requirement of giving Traefik the rights to look up for cluster resources.
Furthermore, Traefik will not handle Ingresses with IngressClass references, therefore such Ingresses will be ignored (please note that annotations are not affected by this option).
This will also prevent from using the `NodePortLB` options on services. | false | No | +| `providers.kubernetesIngress.strictPrefixMatching` | Make prefix matching strictly comply with the Kubernetes Ingress specification (path-element-wise matching instead of character-by-character string matching). For example, a PathPrefix of `/foo` will match `/foo`, `/foo/`, and `/foo/bar` but not `/foobar`. | false | No | @@ -75,7 +82,7 @@ Both are mounted automatically when deployed inside Kubernetes. The endpoint may be specified to override the environment variable values inside a cluster. -When the environment variables are not found, Traefik tries to connect to the +When the environment variables are not found, Traefik tries to connect to the Kubernetes API server with an external-cluster client. In this case, the endpoint is required. @@ -99,7 +106,7 @@ providers: --providers.kubernetesingress.endpoint=http://localhost:8080 ``` -### `ingressEndpoint.publishedService` +### `ingressEndpoint.publishedService` Format: `namespace/servicename`. @@ -130,17 +137,16 @@ providers: --providers.kubernetesingress.ingressendpoint.publishedservice=namespace/foo-service ``` - ## Routing Configuration See the dedicated section in [routing](../../../../routing/providers/kubernetes-ingress.md). ## Further -To learn more about the various aspects of the Ingress specification that +To learn more about the various aspects of the Ingress specification that Traefik supports, -many examples of Ingresses definitions are located in the test -[examples](https://github.com/traefik/traefik/tree/v3.1/pkg/provider/kubernetes/ingress/fixtures) +many examples of Ingresses definitions are located in the test +[examples](https://github.com/traefik/traefik/tree/v3.1/pkg/provider/kubernetes/ingress/fixtures) of the Traefik repository. {!traefik-for-business-applications.md!} diff --git a/docs/content/reference/install-configuration/providers/kv/etcd.md b/docs/content/reference/install-configuration/providers/kv/etcd.md index b5e80e6b6..9cc848604 100644 --- a/docs/content/reference/install-configuration/providers/kv/etcd.md +++ b/docs/content/reference/install-configuration/providers/kv/etcd.md @@ -26,16 +26,16 @@ providers: | Field | Description | Default | Required | |:------|:----------------------------------------------------------|:---------------------|:---------| -| `providers.providersThrottleDuration` | Minimum amount of time to wait for, after a configuration reload, before taking into account any new configuration refresh event.
If multiple events occur within this time, only the most recent one is taken into account, and all others are discarded.
**This option cannot be set per provider, but the throttling algorithm applies to each of them independently.** | 2s | No | -| `providers.etcd.endpoints` | Defines the endpoint to access etcd. | "127.0.0.1:2379" | Yes | -| `providers.etcd.rootKey` | Defines the root key for the configuration. | "traefik" | Yes | -| `providers.etcd.username` | Defines a username with which to connect to etcd. | "" | No | -| `providers.etcd.password` | Defines a password for connecting to etcd. | "" | No | -| `providers.etcd.tls` | Defines the TLS configuration used for the secure connection to etcd. | - | No | -| `providers.etcd.tls.ca` | Defines the path to the certificate authority used for the secure connection to etcd, it defaults to the system bundle. | "" | No | -| `providers.etcd.tls.cert` | Defines the path to the public certificate used for the secure connection to etcd. When using this option, setting the `key` option is required. | "" | Yes | -| `providers.etcd.tls.key` | Defines the path to the private key used for the secure connection to etcd. When using this option, setting the `cert` option is required. | "" | Yes | -| `providers.etcd.tls.insecureSkipVerify` | Instructs the provider to accept any certificate presented by etcd when establishing a TLS connection, regardless of the hostnames the certificate covers. | false | No | +| `providers.providersThrottleDuration` | Minimum amount of time to wait for, after a configuration reload, before taking into account any new configuration refresh event.
If multiple events occur within this time, only the most recent one is taken into account, and all others are discarded.
**This option cannot be set per provider, but the throttling algorithm applies to each of them independently.** | 2s | No | +| `providers.etcd.endpoints` | Defines the endpoint to access etcd. | "127.0.0.1:2379" | Yes | +| `providers.etcd.rootKey` | Defines the root key for the configuration. | "traefik" | Yes | +| `providers.etcd.username` | Defines a username with which to connect to etcd. | "" | No | +| `providers.etcd.password` | Defines a password for connecting to etcd. | "" | No | +| `providers.etcd.tls` | Defines the TLS configuration used for the secure connection to etcd. | - | No | +| `providers.etcd.tls.ca` | Defines the path to the certificate authority used for the secure connection to etcd, it defaults to the system bundle. | "" | No | +| `providers.etcd.tls.cert` | Defines the path to the public certificate used for the secure connection to etcd. When using this option, setting the `key` option is required. | "" | Yes | +| `providers.etcd.tls.key` | Defines the path to the private key used for the secure connection to etcd. When using this option, setting the `cert` option is required. | "" | Yes | +| `providers.etcd.tls.insecureSkipVerify` | Instructs the provider to accept any certificate presented by etcd when establishing a TLS connection, regardless of the hostnames the certificate covers. | false | No | ## Routing Configuration diff --git a/docs/content/reference/install-configuration/providers/kv/redis.md b/docs/content/reference/install-configuration/providers/kv/redis.md index 40c91bfbc..ae5f25b18 100644 --- a/docs/content/reference/install-configuration/providers/kv/redis.md +++ b/docs/content/reference/install-configuration/providers/kv/redis.md @@ -26,25 +26,25 @@ providers: | Field | Description | Default | Required | |:------|:----------------------------------------------------------|:---------------------|:---------| -| `providers.providersThrottleDuration` | Minimum amount of time to wait for, after a configuration reload, before taking into account any new configuration refresh event.
If multiple events occur within this time, only the most recent one is taken into account, and all others are discarded.
**This option cannot be set per provider, but the throttling algorithm applies to each of them independently.** | 2s | No | -| `providers.redis.endpoints` | Defines the endpoint to access Redis. | "127.0.0.1:6379" | Yes | -| `providers.redis.rootKey` | Defines the root key for the configuration. | "traefik" | Yes | -| `providers.redis.username` | Defines a username for connecting to Redis. | "" | No | -| `providers.redis.password` | Defines a password for connecting to Redis. | "" | No | -| `providers.redis.db` | Defines the database to be selected after connecting to the Redis. | 0 | No | -| `providers.redis.tls` | Defines the TLS configuration used for the secure connection to Redis. | - | No | -| `providers.redis.tls.ca` | Defines the path to the certificate authority used for the secure connection to Redis, it defaults to the system bundle. | "" | No | -| `providers.redis.tls.cert` | Defines the path to the public certificate used for the secure connection to Redis. When using this option, setting the `key` option is required. | "" | Yes | -| `providers.redis.tls.key` | Defines the path to the private key used for the secure connection to Redis. When using this option, setting the `cert` option is required. | "" | Yes | -| `providers.redis.tls.insecureSkipVerify` | Instructs the provider to accept any certificate presented by Redis when establishing a TLS connection, regardless of the hostnames the certificate covers. | false | No | -| `providers.redis.sentinel` | Defines the Sentinel configuration used to interact with Redis Sentinel. | - | No | -| `providers.redis.sentinel.masterName` | Defines the name of the Sentinel master. | "" | Yes | -| `providers.redis.sentinel.username` | Defines the username for Sentinel authentication. | "" | No | -| `providers.redis.sentinel.password` | Defines the password for Sentinel authentication. | "" | No | -| `providers.redis.sentinel.latencyStrategy` | Defines whether to route commands to the closest master or replica nodes (mutually exclusive with RandomStrategy and ReplicaStrategy). | false | No | -| `providers.redis.sentinel.randomStrategy` | Defines whether to route commands randomly to master or replica nodes (mutually exclusive with LatencyStrategy and ReplicaStrategy). | false | No | -| `providers.redis.sentinel.replicaStrategy` | Defines whether to route commands randomly to master or replica nodes (mutually exclusive with LatencyStrategy and ReplicaStrategy). | false | No | -| `providers.redis.sentinel.useDisconnectedReplicas` | Defines whether to use replicas disconnected with master when cannot get connected replicas. | false | false | +| `providers.providersThrottleDuration` | Minimum amount of time to wait for, after a configuration reload, before taking into account any new configuration refresh event.
If multiple events occur within this time, only the most recent one is taken into account, and all others are discarded.
**This option cannot be set per provider, but the throttling algorithm applies to each of them independently.** | 2s | No | +| `providers.redis.endpoints` | Defines the endpoint to access Redis. | "127.0.0.1:6379" | Yes | +| `providers.redis.rootKey` | Defines the root key for the configuration. | "traefik" | Yes | +| `providers.redis.username` | Defines a username for connecting to Redis. | "" | No | +| `providers.redis.password` | Defines a password for connecting to Redis. | "" | No | +| `providers.redis.db` | Defines the database to be selected after connecting to the Redis. | 0 | No | +| `providers.redis.tls` | Defines the TLS configuration used for the secure connection to Redis. | - | No | +| `providers.redis.tls.ca` | Defines the path to the certificate authority used for the secure connection to Redis, it defaults to the system bundle. | "" | No | +| `providers.redis.tls.cert` | Defines the path to the public certificate used for the secure connection to Redis. When using this option, setting the `key` option is required. | "" | Yes | +| `providers.redis.tls.key` | Defines the path to the private key used for the secure connection to Redis. When using this option, setting the `cert` option is required. | "" | Yes | +| `providers.redis.tls.insecureSkipVerify` | Instructs the provider to accept any certificate presented by Redis when establishing a TLS connection, regardless of the hostnames the certificate covers. | false | No | +| `providers.redis.sentinel` | Defines the Sentinel configuration used to interact with Redis Sentinel. | - | No | +| `providers.redis.sentinel.masterName` | Defines the name of the Sentinel master. | "" | Yes | +| `providers.redis.sentinel.username` | Defines the username for Sentinel authentication. | "" | No | +| `providers.redis.sentinel.password` | Defines the password for Sentinel authentication. | "" | No | +| `providers.redis.sentinel.latencyStrategy` | Defines whether to route commands to the closest master or replica nodes (mutually exclusive with RandomStrategy and ReplicaStrategy). | false | No | +| `providers.redis.sentinel.randomStrategy` | Defines whether to route commands randomly to master or replica nodes (mutually exclusive with LatencyStrategy and ReplicaStrategy). | false | No | +| `providers.redis.sentinel.replicaStrategy` | Defines whether to route commands randomly to master or replica nodes (mutually exclusive with LatencyStrategy and ReplicaStrategy). | false | No | +| `providers.redis.sentinel.useDisconnectedReplicas` | Defines whether to use replicas disconnected with master when cannot get connected replicas. | false | false | ## Routing Configuration diff --git a/docs/content/reference/install-configuration/providers/kv/zk.md b/docs/content/reference/install-configuration/providers/kv/zk.md index 38042a1a9..b77e5a23d 100644 --- a/docs/content/reference/install-configuration/providers/kv/zk.md +++ b/docs/content/reference/install-configuration/providers/kv/zk.md @@ -26,16 +26,16 @@ providers: | Field | Description | Default | Required | |:------|:----------------------------------------------------------|:---------------------|:---------| -| `providers.providersThrottleDuration` | Minimum amount of time to wait for, after a configuration reload, before taking into account any new configuration refresh event.
If multiple events occur within this time, only the most recent one is taken into account, and all others are discarded.
**This option cannot be set per provider, but the throttling algorithm applies to each of them independently.** | 2s | No | -| `providers.zooKeeper.endpoints` | Defines the endpoint to access ZooKeeper. | "127.0.0.1:2181" | Yes | -| `providers.zooKeeper.rootKey` | Defines the root key for the configuration. | "traefik" | Yes | -| `providers.zooKeeper.username` | Defines a username with which to connect to zooKeeper. | "" | No | -| `providers.zooKeeper.password` | Defines a password for connecting to zooKeeper. | "" | No | -| `providers.zooKeeper.tls` | Defines the TLS configuration used for the secure connection to zooKeeper. | - | No | -| `providers.zooKeeper.tls.ca` | Defines the path to the certificate authority used for the secure connection to zooKeeper, it defaults to the system bundle. | "" | No | -| `providers.zooKeeper.tls.cert` | Defines the path to the public certificate used for the secure connection to zooKeeper. When using this option, setting the `key` option is required. | "" | Yes | -| `providers.zooKeeper.tls.key` | Defines the path to the private key used for the secure connection to zooKeeper. When using this option, setting the `cert` option is required. | "" | Yes | -| `providers.zooKeeper.tls.insecureSkipVerify` | Instructs the provider to accept any certificate presented by etcd when establishing a TLS connection, regardless of the hostnames the certificate covers. | false | No | +| `providers.providersThrottleDuration` | Minimum amount of time to wait for, after a configuration reload, before taking into account any new configuration refresh event.
If multiple events occur within this time, only the most recent one is taken into account, and all others are discarded.
**This option cannot be set per provider, but the throttling algorithm applies to each of them independently.** | 2s | No | +| `providers.zooKeeper.endpoints` | Defines the endpoint to access ZooKeeper. | "127.0.0.1:2181" | Yes | +| `providers.zooKeeper.rootKey` | Defines the root key for the configuration. | "traefik" | Yes | +| `providers.zooKeeper.username` | Defines a username with which to connect to zooKeeper. | "" | No | +| `providers.zooKeeper.password` | Defines a password for connecting to zooKeeper. | "" | No | +| `providers.zooKeeper.tls` | Defines the TLS configuration used for the secure connection to zooKeeper. | - | No | +| `providers.zooKeeper.tls.ca` | Defines the path to the certificate authority used for the secure connection to zooKeeper, it defaults to the system bundle. | "" | No | +| `providers.zooKeeper.tls.cert` | Defines the path to the public certificate used for the secure connection to zooKeeper. When using this option, setting the `key` option is required. | "" | Yes | +| `providers.zooKeeper.tls.key` | Defines the path to the private key used for the secure connection to zooKeeper. When using this option, setting the `cert` option is required. | "" | Yes | +| `providers.zooKeeper.tls.insecureSkipVerify` | Instructs the provider to accept any certificate presented by etcd when establishing a TLS connection, regardless of the hostnames the certificate covers. | false | No | ## Routing Configuration diff --git a/docs/content/reference/install-configuration/providers/others/ecs.md b/docs/content/reference/install-configuration/providers/others/ecs.md index f6aeb2731..2cca65977 100644 --- a/docs/content/reference/install-configuration/providers/others/ecs.md +++ b/docs/content/reference/install-configuration/providers/others/ecs.md @@ -26,18 +26,18 @@ providers: | Field | Description | Default | Required | |:------|:----------------------------------------------------------|:---------------------|:---------| -| `providers.providersThrottleDuration` | Minimum amount of time to wait for, after a configuration reload, before taking into account any new configuration refresh event.
If multiple events occur within this time, only the most recent one is taken into account, and all others are discarded.
**This option cannot be set per provider, but the throttling algorithm applies to each of them independently.** | 2s | No | -| `providers.ecs.autoDiscoverClusters` | Search for services in cluster list. If set to `true` service discovery is enabled for all clusters. | false | No | -| `providers.ecs.ecsAnywhere` | Enable ECS Anywhere support. | false | No | -| `providers.ecs.clusters` | Search for services in cluster list. This option is ignored if `autoDiscoverClusters` is set to `true`. | `["default"]` | No | -| `providers.ecs.exposedByDefault` | Expose ECS services by default in Traefik. | true | No | -| `providers.ecs.constraints` | Defines an expression that Traefik matches against the container labels to determine whether to create any route for that container. See [here](#constraints) for more information. | true | No | -| `providers.ecs.healthyTasksOnly` | Defines whether Traefik discovers only healthy tasks (`HEALTHY` healthStatus). | false | No | -| `providers.ecs.defaultRule` | The Default Host rule for all services. See [here](#defaultrule) for more information. | ```"Host(`{{ normalize .Name }}`)"``` | No | -| `providers.ecs.refreshSeconds` | Defines the polling interval (in seconds). | 15 | No | -| `providers.ecs.region` | Defines the region of the ECS instance. See [here](#credentials) for more information. | "" | No | -| `providers.ecs.accessKeyID` | Defines the Access Key ID for the ECS instance. See [here](#credentials) for more information. | "" | No | -| `providers.ecs.secretAccessKey` | Defines the Secret Access Key for the ECS instance. See [here](#credentials) for more information. | "" | No | +| `providers.providersThrottleDuration` | Minimum amount of time to wait for, after a configuration reload, before taking into account any new configuration refresh event.
If multiple events occur within this time, only the most recent one is taken into account, and all others are discarded.
**This option cannot be set per provider, but the throttling algorithm applies to each of them independently.** | 2s | No | +| `providers.ecs.autoDiscoverClusters` | Search for services in cluster list. If set to `true` service discovery is enabled for all clusters. | false | No | +| `providers.ecs.ecsAnywhere` | Enable ECS Anywhere support. | false | No | +| `providers.ecs.clusters` | Search for services in cluster list. This option is ignored if `autoDiscoverClusters` is set to `true`. | `["default"]` | No | +| `providers.ecs.exposedByDefault` | Expose ECS services by default in Traefik. | true | No | +| `providers.ecs.constraints` | Defines an expression that Traefik matches against the container labels to determine whether to create any route for that container. See [here](#constraints) for more information. | true | No | +| `providers.ecs.healthyTasksOnly` | Defines whether Traefik discovers only healthy tasks (`HEALTHY` healthStatus). | false | No | +| `providers.ecs.defaultRule` | The Default Host rule for all services. See [here](#defaultrule) for more information. | ```"Host(`{{ normalize .Name }}`)"``` | No | +| `providers.ecs.refreshSeconds` | Defines the polling interval (in seconds). | 15 | No | +| `providers.ecs.region` | Defines the region of the ECS instance. See [here](#credentials) for more information. | "" | No | +| `providers.ecs.accessKeyID` | Defines the Access Key ID for the ECS instance. See [here](#credentials) for more information. | "" | No | +| `providers.ecs.secretAccessKey` | Defines the Secret Access Key for the ECS instance. See [here](#credentials) for more information. | "" | No | ### `constraints` @@ -49,6 +49,10 @@ If the expression is empty, all detected containers are included. The expression syntax is based on the `Label("key", "value")`, and `LabelRegex("key", "value")` functions, as well as the usual boolean logic, as shown in examples below. +!!! tip "Constraints key limitations" + + Note that `traefik.*` is a reserved label namespace for configuration and can not be used as a key for custom constraints. + ??? example "Constraints Expression Examples" ```toml @@ -99,7 +103,7 @@ providers: # ... ``` -For additional information, refer to [Restrict the Scope of Service Discovery](../overview.md#restrict-the-scope-of-service-discovery). +For additional information, refer to [Restrict the Scope of Service Discovery](../overview.md#exposedbydefault-and-traefikenable). ### `defaultRule` diff --git a/docs/content/reference/install-configuration/providers/others/file.md b/docs/content/reference/install-configuration/providers/others/file.md index 6d4fad6d2..737c7a65c 100644 --- a/docs/content/reference/install-configuration/providers/others/file.md +++ b/docs/content/reference/install-configuration/providers/others/file.md @@ -100,10 +100,10 @@ http: | Field | Description | Default | Required | |:------|:----------------------------------------------------------|:---------------------|:---------| -| `providers.providersThrottleDuration` | Minimum amount of time to wait for, after a configuration reload, before taking into account any new configuration refresh event.
If multiple events occur within this time, only the most recent one is taken into account, and all others are discarded.
**This option cannot be set per provider, but the throttling algorithm applies to each of them independently.** | 2s | No | -| `providers.file.filename` | Defines the path to the configuration file. | "" | Yes | -| `providers.file.directory` | Defines the path to the directory that contains the configuration files. The `filename` and `directory` options are mutually exclusive. It is recommended to use `directory`. | "" | Yes | -| `providers.file.watch` | Set the `watch` option to `true` to allow Traefik to automatically watch for file changes. It works with both the `filename` and the `directory` options. | true | No | +| `providers.providersThrottleDuration` | Minimum amount of time to wait for, after a configuration reload, before taking into account any new configuration refresh event.
If multiple events occur within this time, only the most recent one is taken into account, and all others are discarded.
**This option cannot be set per provider, but the throttling algorithm applies to each of them independently.** | 2s | No | +| `providers.file.filename` | Defines the path to the configuration file. | "" | Yes | +| `providers.file.directory` | Defines the path to the directory that contains the configuration files. The `filename` and `directory` options are mutually exclusive. It is recommended to use `directory`. | "" | Yes | +| `providers.file.watch` | Set the `watch` option to `true` to allow Traefik to automatically watch for file changes. It works with both the `filename` and the `directory` options. | true | No | !!! warning "Limitations" diff --git a/docs/content/reference/install-configuration/providers/others/http.md b/docs/content/reference/install-configuration/providers/others/http.md index 4ac29b6fe..9e67df2e3 100644 --- a/docs/content/reference/install-configuration/providers/others/http.md +++ b/docs/content/reference/install-configuration/providers/others/http.md @@ -30,15 +30,15 @@ providers: | Field | Description | Default | Required | |:------|:----------------------------------------------------------|:---------------------|:---------| -| `providers.providersThrottleDuration` | Minimum amount of time to wait for, after a configuration reload, before taking into account any new configuration refresh event.
If multiple events occur within this time, only the most recent one is taken into account, and all others are discarded.
**This option cannot be set per provider, but the throttling algorithm applies to each of them independently.** | 2s | No | -| `providers.http.endpoint` | Defines the HTTP(S) endpoint to poll. | "" | Yes | -| `providers.http.pollInterval` | Defines the polling interval. | 5s | No | -| `providers.http.pollTimeout` | Defines the polling timeout when connecting to the endpoint. | 5s | No | -| `providers.http.headers` | Defines custom headers to be sent to the endpoint. | "" | No | -| `providers.http.tls.ca` | Defines the path to the certificate authority used for the secure connection to the endpoint, it defaults to the system bundle. | "" | No | -| `providers.http.tls.cert` | Defines the path to the public certificate used for the secure connection to the endpoint. When using this option, setting the `key` option is required. | "" | Yes | -| `providers.http.tls.key` | Defines the path to the private key used for the secure connection to the endpoint. When using this option, setting the `cert` option is required. | "" | Yes | -| `providers.http.tls.insecureSkipVerify` | Instructs the provider to accept any certificate presented by endpoint when establishing a TLS connection, regardless of the hostnames the certificate covers. | false | No | +| `providers.providersThrottleDuration` | Minimum amount of time to wait for, after a configuration reload, before taking into account any new configuration refresh event.
If multiple events occur within this time, only the most recent one is taken into account, and all others are discarded.
**This option cannot be set per provider, but the throttling algorithm applies to each of them independently.** | 2s | No | +| `providers.http.endpoint` | Defines the HTTP(S) endpoint to poll. | "" | Yes | +| `providers.http.pollInterval` | Defines the polling interval. | 5s | No | +| `providers.http.pollTimeout` | Defines the polling timeout when connecting to the endpoint. | 5s | No | +| `providers.http.headers` | Defines custom headers to be sent to the endpoint. | "" | No | +| `providers.http.tls.ca` | Defines the path to the certificate authority used for the secure connection to the endpoint, it defaults to the system bundle. | "" | No | +| `providers.http.tls.cert` | Defines the path to the public certificate used for the secure connection to the endpoint. When using this option, setting the `key` option is required. | "" | Yes | +| `providers.http.tls.key` | Defines the path to the private key used for the secure connection to the endpoint. When using this option, setting the `cert` option is required. | "" | Yes | +| `providers.http.tls.insecureSkipVerify` | Instructs the provider to accept any certificate presented by endpoint when establishing a TLS connection, regardless of the hostnames the certificate covers. | false | No | ### headers diff --git a/docs/content/reference/install-configuration/providers/overview.md b/docs/content/reference/install-configuration/providers/overview.md index 3edc93471..5253f57a0 100644 --- a/docs/content/reference/install-configuration/providers/overview.md +++ b/docs/content/reference/install-configuration/providers/overview.md @@ -51,20 +51,20 @@ Below is the list of the currently supported providers in Traefik. | Provider | Type | Configuration Type | Provider Name | |--------------------------------------------------------------|--------------|----------------------|---------------------| -| [Docker](./docker.md) | Orchestrator | Label | `docker` | -| [Docker Swarm](./swarm.md) | Orchestrator | Label | `swarm` | -| [Kubernetes IngressRoute](./kubernetes/kubernetes-crd.md) | Orchestrator | Custom Resource | `kubernetescrd` | -| [Kubernetes Ingress](./kubernetes/kubernetes-ingress.md) | Orchestrator | Ingress | `kubernetes` | -| [Kubernetes Gateway API](./kubernetes/kubernetes-gateway.md) | Orchestrator | Gateway API Resource | `kubernetesgateway` | -| [Consul Catalog](./hashicorp/consul-catalog.md) | Orchestrator | Label | `consulcatalog` | -| [Nomad](./hashicorp/nomad.md) | Orchestrator | Label | `nomad` | -| [ECS](./others/ecs.md) | Orchestrator | Label | `ecs` | -| [File](./others/file.md) | Manual | YAML/TOML format | `file` | -| [Consul](./hashicorp/consul.md) | KV | KV | `consul` | -| [Etcd](./kv/etcd.md) | KV | KV | `etcd` | -| [ZooKeeper](./kv/zk.md) | KV | KV | `zookeeper` | -| [Redis](./kv/redis.md) | KV | KV | `redis` | -| [HTTP](./others/http.md) | Manual | JSON/YAML format | `http` | +| [Docker](./docker.md) | Orchestrator | Label | `docker` | +| [Docker Swarm](./swarm.md) | Orchestrator | Label | `swarm` | +| [Kubernetes IngressRoute](./kubernetes/kubernetes-crd.md) | Orchestrator | Custom Resource | `kubernetescrd` | +| [Kubernetes Ingress](./kubernetes/kubernetes-ingress.md) | Orchestrator | Ingress | `kubernetes` | +| [Kubernetes Gateway API](./kubernetes/kubernetes-gateway.md) | Orchestrator | Gateway API Resource | `kubernetesgateway` | +| [Consul Catalog](./hashicorp/consul-catalog.md) | Orchestrator | Label | `consulcatalog` | +| [Nomad](./hashicorp/nomad.md) | Orchestrator | Label | `nomad` | +| [ECS](./others/ecs.md) | Orchestrator | Label | `ecs` | +| [File](./others/file.md) | Manual | YAML/TOML format | `file` | +| [Consul](./hashicorp/consul.md) | KV | KV | `consul` | +| [Etcd](./kv/etcd.md) | KV | KV | `etcd` | +| [ZooKeeper](./kv/zk.md) | KV | KV | `zookeeper` | +| [Redis](./kv/redis.md) | KV | KV | `redis` | +| [HTTP](./others/http.md) | Manual | JSON/YAML format | `http` | !!! info "More Providers" diff --git a/docs/content/reference/install-configuration/providers/swarm.md b/docs/content/reference/install-configuration/providers/swarm.md index becfdd577..8f876dfd2 100644 --- a/docs/content/reference/install-configuration/providers/swarm.md +++ b/docs/content/reference/install-configuration/providers/swarm.md @@ -33,7 +33,6 @@ When there is only one service, and the router does not specify a service, then that service is automatically assigned to the router. ```yaml tab="Labels" -version: "3" services: my-container: deploy: @@ -44,25 +43,25 @@ services: ## Configuration Options -| Field | Description | Default | Required | -|:------|:----------------------------------------------------------|:---------------------|:---------| -| `providers.providersThrottleDuration` | Minimum amount of time to wait for, after a configuration reload, before taking into account any new configuration refresh event.
If multiple events occur within this time, only the most recent one is taken into account, and all others are discarded.
**This option cannot be set per provider, but the throttling algorithm applies to each of them independently.** | 2s | No | -| `providers.swarm.endpoint` | Specifies the Docker API endpoint. See [here](#endpoint) for more information| `unix:///var/run/docker.sock` | Yes | -| `providers.swarm.username` | Defines the username for Basic HTTP authentication. This should be used when the Docker daemon socket is exposed through an HTTP proxy that requires Basic HTTP authentication.| "" | No | -| `providers.swarm.password` | Defines the password for Basic HTTP authentication. This should be used when the Docker daemon socket is exposed through an HTTP proxy that requires Basic HTTP authentication.| "" | No | -| `providers.swarm.useBindPortIP` | Instructs Traefik to use the IP/Port attached to the container's binding instead of its inner network IP/Port. See [here](#usebindportip) for more information | false | No | -| `providers.swarm.exposedByDefault` | Expose containers by default through Traefik. See [here](./overview.md#restrict-the-scope-of-service-discovery) for additional information | true | No | -| `providers.swarm.network` | Defines a default docker network to use for connections to all containers. This option can be overridden on a per-container basis with the `traefik.docker.network` label.| "" | No | -| `providers.swarm.defaultRule` | Defines what routing rule to apply to a container if no rule is defined by a label. See [here](#defaultrule) for more information | ```"Host(`{{ normalize .Name }}`)"``` | No | -| `providers.swarm.refreshSeconds` | Defines the polling interval for Swarm Mode. | "15s" | No | -| `providers.swarm.httpClientTimeout` | Defines the client timeout (in seconds) for HTTP connections. If its value is 0, no timeout is set. | 0 | No | -| `providers.swarm.watch` | Instructs Traefik to watch Docker events or not. | True | No | -| `providers.swarm.constraints` | Defines an expression that Traefik matches against the container labels to determine whether to create any route for that container. See [here](#constraints) for more information. | "" | No | -| `providers.swarm.allowEmptyServices` | Instructs the provider to create any [servers load balancer](../../../routing/services/index.md#servers-load-balancer) defined for Docker containers regardless of the [healthiness](https://docs.docker.com/engine/reference/builder/#healthcheck) of the corresponding containers. | false | No | -| `providers.swarm.tls.ca` | Defines the path to the certificate authority used for the secure connection to Docker, it defaults to the system bundle. | "" | No | -| `providers.swarm.tls.cert` | Defines the path to the public certificate used for the secure connection to Docker. When using this option, setting the `key` option is required. | "" | Yes | -| `providers.swarm.tls.key` | Defines the path to the private key used for the secure connection to Docker. When using this option, setting the `cert` option is required. | "" | Yes | -| `providers.swarm.tls.insecureSkipVerify` | Instructs the provider to accept any certificate presented by the Docker server when establishing a TLS connection, regardless of the hostnames the certificate covers. | false | No | +| Field | Description | Default | Required | +|:-----------------------------------------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:--------------------------------------|:---------| +| `providers.providersThrottleDuration` | Minimum amount of time to wait for, after a configuration reload, before taking into account any new configuration refresh event.
If multiple events occur within this time, only the most recent one is taken into account, and all others are discarded.
**This option cannot be set per provider, but the throttling algorithm applies to each of them independently.** | 2s | No | +| `providers.swarm.endpoint` | Specifies the Docker API endpoint. See [here](#endpoint) for more information | `unix:///var/run/docker.sock` | Yes | +| `providers.swarm.username` | Defines the username for Basic HTTP authentication. This should be used when the Docker daemon socket is exposed through an HTTP proxy that requires Basic HTTP authentication. | "" | No | +| `providers.swarm.password` | Defines the password for Basic HTTP authentication. This should be used when the Docker daemon socket is exposed through an HTTP proxy that requires Basic HTTP authentication. | "" | No | +| `providers.swarm.useBindPortIP` | Instructs Traefik to use the IP/Port attached to the container's binding instead of its inner network IP/Port. See [here](#usebindportip) for more information | false | No | +| `providers.swarm.exposedByDefault` | Expose containers by default through Traefik. See [here](./overview.md#exposedbydefault-and-traefikenable) for additional information | true | No | +| `providers.swarm.network` | Defines a default docker network to use for connections to all containers. This option can be overridden on a per-container basis with the `traefik.swarm.network` label. | "" | No | +| `providers.swarm.defaultRule` | Defines what routing rule to apply to a container if no rule is defined by a label. See [here](#defaultrule) for more information | ```"Host(`{{ normalize .Name }}`)"``` | No | +| `providers.swarm.refreshSeconds` | Defines the polling interval for Swarm Mode. | "15s" | No | +| `providers.swarm.httpClientTimeout` | Defines the client timeout (in seconds) for HTTP connections. If its value is 0, no timeout is set. | 0 | No | +| `providers.swarm.watch` | Instructs Traefik to watch Docker events or not. | True | No | +| `providers.swarm.constraints` | Defines an expression that Traefik matches against the container labels to determine whether to create any route for that container. See [here](#constraints) for more information. | "" | No | +| `providers.swarm.allowEmptyServices` | Instructs the provider to create any [servers load balancer](../../../routing/services/index.md#servers-load-balancer) defined for Docker containers regardless of the [healthiness](https://docs.docker.com/engine/reference/builder/#healthcheck) of the corresponding containers. | false | No | +| `providers.swarm.tls.ca` | Defines the path to the certificate authority used for the secure connection to Docker, it defaults to the system bundle. | "" | No | +| `providers.swarm.tls.cert` | Defines the path to the public certificate used for the secure connection to Docker. When using this option, setting the `key` option is required. | "" | Yes | +| `providers.swarm.tls.key` | Defines the path to the private key used for the secure connection to Docker. When using this option, setting the `cert` option is required. | "" | Yes | +| `providers.swarm.tls.insecureSkipVerify` | Instructs the provider to accept any certificate presented by the Docker server when establishing a TLS connection, regardless of the hostnames the certificate covers. | false | No | ### `endpoint` @@ -73,8 +72,6 @@ See the [Docker Swarm API Access](#docker-api-access) section for more informati The docker-compose file shares the docker sock with the Traefik container ```yaml - version: '3' - services: traefik: image: traefik:v3.1 # The official v3 Traefik docker image @@ -100,7 +97,7 @@ See the [Docker Swarm API Access](#docker-api-access) section for more informati ``` ```bash tab="CLI" - --providers.docker.endpoint=unix:///var/run/docker.sock + --providers.swarm.endpoint=unix:///var/run/docker.sock # ... ``` @@ -201,13 +198,13 @@ but still uses the `traefik.http.services..loadbalancer.server.port` that | port label | Container's binding | Routes to | |--------------------|----------------------------------------------------|----------------| - | - | - | IntIP:IntPort | - | - | ExtPort:IntPort | IntIP:IntPort | - | - | ExtIp:ExtPort:IntPort | ExtIp:ExtPort | - | LblPort | - | IntIp:LblPort | - | LblPort | ExtIp:ExtPort:LblPort | ExtIp:ExtPort | - | LblPort | ExtIp:ExtPort:OtherPort | IntIp:LblPort | - | LblPort | ExtIp1:ExtPort1:IntPort1 & ExtIp2:LblPort:IntPort2 | ExtIp2:LblPort | + | - | - | IntIP:IntPort | + | - | ExtPort:IntPort | IntIP:IntPort | + | - | ExtIp:ExtPort:IntPort | ExtIp:ExtPort | + | LblPort | - | IntIp:LblPort | + | LblPort | ExtIp:ExtPort:LblPort | ExtIp:ExtPort | + | LblPort | ExtIp:ExtPort:OtherPort | IntIp:LblPort | + | LblPort | ExtIp1:ExtPort1:IntPort1 & ExtIp2:LblPort:IntPort2 | ExtIp2:LblPort | !!! info "" In the above table: @@ -279,6 +276,10 @@ created. If the expression is empty, all detected containers are included. The expression syntax is based on the `Label("key", "value")`, and `LabelRegex("key", "value")` functions, as well as the usual boolean logic, as shown in examples below. +!!! tip "Constraints key limitations" + + Note that `traefik.*` is a reserved label namespace for configuration and can not be used as a key for custom constraints. + ??? example "Constraints Expression Examples" ```toml @@ -311,7 +312,7 @@ as well as the usual boolean logic, as shown in examples below. constraints = "LabelRegex(`a.label.name`, `a.+`)" ``` -For additional information, refer to [Restrict the Scope of Service Discovery](./overview.md#restrict-the-scope-of-service-discovery). +For additional information, refer to [Restrict the Scope of Service Discovery](./overview.md#exposedbydefault-and-traefikenable). ```yaml tab="File (YAML)" providers: @@ -405,8 +406,6 @@ docker service create \ ``` ```yml tab="With Docker Compose" -version: '3' - services: traefik: # ... diff --git a/docs/content/reference/install-configuration/tls/certificate-resolvers/acme.md b/docs/content/reference/install-configuration/tls/certificate-resolvers/acme.md index 1b05bde5a..f923e6c72 100644 --- a/docs/content/reference/install-configuration/tls/certificate-resolvers/acme.md +++ b/docs/content/reference/install-configuration/tls/certificate-resolvers/acme.md @@ -73,27 +73,30 @@ certificatesResolvers: ACME certificate resolvers have the following configuration options: -| Field | Description | Default | Required | -|:------------------|:--------------------|:-----------------------------------------------|:---------| -| `acme.email` | Email address used for registration. | "" | Yes | -| `acme.caServer` | CA server to use. | https://acme-v02.api.letsencrypt.org/directory | No | -| `acme.preferredChain` | Preferred chain to use. If the CA offers multiple certificate chains, prefer the chain with an issuer matching this Subject Common Name. If no match, the default offered chain will be used. | "" | No | -| `acme.keyType` | KeyType to use. | "RSA4096" | No | -| `acme.eab` | Enable external account binding.| | No | -| `acme.eab.kid` | Key identifier from External CA. | "" | No | -| `acme.eab.hmacEncoded` | HMAC key from External CA, should be in Base64 URL Encoding without padding format. | "" | No | -| `acme.certificatesDuration` | The certificates' duration in hours, exclusively used to determine renewal dates. | 2160 | No | -| `acme.dnsChallenge` | Enable DNS-01 challenge. More information [here](#dnschallenge). | - | No | -| `acme.dnsChallenge.provider` | DNS provider to use. | "" | No | -| `acme.dnsChallenge.resolvers` | DNS servers to resolve the FQDN authority. | [] | No | -| `acme.dnsChallenge.propagation.delayBeforeChecks` | By default, the provider will verify the TXT DNS challenge record before letting ACME verify. If `delayBeforeCheck` is greater than zero, this check is delayed for the configured duration in seconds. This is Useful if internal networks block external DNS queries. | 0s | No | -| `acme.dnsChallenge.propagation.disableChecks` | Disables the challenge TXT record propagation checks, before notifying ACME that the DNS challenge is ready. Please note that disabling checks can prevent the challenge from succeeding. | false | No | -| `acme.dnsChallenge.propagation.requireAllRNS` | Enables the challenge TXT record to be propagated to all recursive nameservers. If you have disabled authoritative nameservers checks (with `propagation.disableANSChecks`), it is recommended to check all recursive nameservers instead. | false | No | -| `acme.dnsChallenge.propagation.disableANSChecks` | Disables the challenge TXT record propagation checks against authoritative nameservers. This option will skip the propagation check against the nameservers of the authority (SOA). It should be used only if the nameservers of the authority are not reachable. | false | No | -| `acme.httpChallenge` | Enable HTTP-01 challenge. More information [here](#httpchallenge). | | No | -| `acme.httpChallenge.entryPoint` | EntryPoint to use for the HTTP-01 challenges. Must be reachable by Let's Encrypt through port 80 | "" | Yes | -| `acme.tlsChallenge` | Enable TLS-ALPN-01 challenge. Traefik must be reachable by Let's Encrypt through port 443. More information [here](#tlschallenge). | - | No | -| `acme.storage` | File path used for certificates storage. | "acme.json" | Yes | +| Field | Description | Default | Required | +|:--------------------------------------------------|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:-----------------------------------------------|:---------| +| `acme.email` | Email address used for registration. | "" | Yes | +| `acme.caServer` | CA server to use. | https://acme-v02.api.letsencrypt.org/directory | No | +| `acme.preferredChain` | Preferred chain to use. If the CA offers multiple certificate chains, prefer the chain with an issuer matching this Subject Common Name. If no match, the default offered chain will be used. | "" | No | +| `acme.keyType` | KeyType to use. | "RSA4096" | No | +| `acme.eab` | Enable external account binding. | | No | +| `acme.eab.kid` | Key identifier from External CA. | "" | No | +| `acme.eab.hmacEncoded` | HMAC key from External CA, should be in Base64 URL Encoding without padding format. | "" | No | +| `acme.certificatesDuration` | The certificates' duration in hours, exclusively used to determine renewal dates. | 2160 | No | +| `acme.clientTimeout` | Timeout for HTTP Client used to communicate with the ACME server. | 2m | No | +| `acme.clientResponseHeaderTimeout` | Timeout for response headers for HTTP Client used to communicate with the ACME server. | 30s | No | +| `acme.dnsChallenge` | Enable DNS-01 challenge. More information [here](#dnschallenge). | - | No | +| `acme.dnsChallenge.provider` | DNS provider to use. | "" | No | +| `acme.dnsChallenge.resolvers` | DNS servers to resolve the FQDN authority. | [] | No | +| `acme.dnsChallenge.propagation.delayBeforeChecks` | By default, the provider will verify the TXT DNS challenge record before letting ACME verify. If `delayBeforeCheck` is greater than zero, this check is delayed for the configured duration in seconds. This is Useful if internal networks block external DNS queries. | 0s | No | +| `acme.dnsChallenge.propagation.disableChecks` | Disables the challenge TXT record propagation checks, before notifying ACME that the DNS challenge is ready. Please note that disabling checks can prevent the challenge from succeeding. | false | No | +| `acme.dnsChallenge.propagation.requireAllRNS` | Enables the challenge TXT record to be propagated to all recursive nameservers. If you have disabled authoritative nameservers checks (with `propagation.disableANSChecks`), it is recommended to check all recursive nameservers instead. | false | No | +| `acme.dnsChallenge.propagation.disableANSChecks` | Disables the challenge TXT record propagation checks against authoritative nameservers. This option will skip the propagation check against the nameservers of the authority (SOA). It should be used only if the nameservers of the authority are not reachable. | false | No | +| `acme.httpChallenge` | Enable HTTP-01 challenge. More information [here](#httpchallenge). | | No | +| `acme.httpChallenge.entryPoint` | EntryPoint to use for the HTTP-01 challenges. Must be reachable by Let's Encrypt through port 80 | "" | Yes | +| `acme.httpChallenge.delay` | The delay between the creation of the challenge and the validation. A value lower than or equal to zero means no delay. | 0 | No | +| `acme.tlsChallenge` | Enable TLS-ALPN-01 challenge. Traefik must be reachable by Let's Encrypt through port 443. More information [here](#tlschallenge). | - | No | +| `acme.storage` | File path used for certificates storage. | "acme.json" | Yes | ## Automatic Certificate Renewal diff --git a/docs/content/reference/install-configuration/tls/ocsp.md b/docs/content/reference/install-configuration/tls/ocsp.md new file mode 100644 index 000000000..577f3749c --- /dev/null +++ b/docs/content/reference/install-configuration/tls/ocsp.md @@ -0,0 +1,71 @@ +--- +title: "Traefik OCSP Documentation" +description: "Learn how to configure Traefik to use OCSP. Read the technical documentation." +--- + +# OCSP + +Check certificate status and perform OCSP stapling. +{: .subtitle } + +## Overview + +### OCSP Stapling + +When OCSP is enabled, Traefik checks the status of every certificate in the store that provides an OCSP responder URL, +including the default certificate, and staples the OCSP response to the TLS handshake. +The OCSP check is performed when the certificate is loaded, +and once every hour until it is successful at the halfway point before the update date. + +### Caching + +Traefik caches the OCSP response as long as the associated certificate is provided by the configuration. +When a certificate is no longer provided, +the OCSP response has a 24 hour TTL waiting to be provided again or eventually removed. +The OCSP response is cached in memory and is not persisted between Traefik restarts. + +## Configuration + +### General + +Enabling OCSP is part of the [install configuration](../boot-environment.md). +It can be defined by using a file (YAML or TOML) or CLI arguments: + +```yaml tab="File (YAML)" +## Static configuration +ocsp: {} +``` + +```toml tab="File (TOML)" +## Static configuration +[ocsp] +``` + +```bash tab="CLI" +## Static configuration +--ocsp=true +``` + +### Responder Overrides + +The `responderOverrides` option defines the OCSP responder URLs to use instead of the one provided by the certificate. +This is useful when you want to use a different OCSP responder. + +```yaml tab="File (YAML)" +## Static configuration +ocsp: + responderOverrides: + foo: bar +``` + +```toml tab="File (TOML)" +## Static configuration +[ocsp] + [ocsp.responderOverrides] + foo = "bar" +``` + +```bash tab="CLI" +## Static configuration +--ocsp.responderoverrides.foo=bar +``` diff --git a/docs/content/reference/install-configuration/tls/spiffe.md b/docs/content/reference/install-configuration/tls/spiffe.md index d7067c8f0..ca4c10ddc 100644 --- a/docs/content/reference/install-configuration/tls/spiffe.md +++ b/docs/content/reference/install-configuration/tls/spiffe.md @@ -44,7 +44,7 @@ spiffe: ## ServersTransport Enabling SPIFFE does not imply that backend connections are going to use it automatically. -Each [ServersTransport](../../../routing/services/index.md#serverstransport_1) or [TCPServersTransport](../../../routing/services/index.md#serverstransport_2), that is meant to be secured with SPIFFE, must explicitly enable it (see [SPIFFE with ServersTransport](../../../routing/services/index.md#spiffe) or [SPIFFE with TCPServersTransport](../../../routing/services/index.md#spiffe_1)). +Each [ServersTransport](../../routing-configuration/http/load-balancing/serverstransport.md) or [TCPServersTransport](../../routing-configuration/tcp/serverstransport.md), that is meant to be secured with SPIFFE, must explicitly enable it (see [SPIFFE with ServersTransport](../../routing-configuration/http/load-balancing/serverstransport.md#opt-spiffe) or [SPIFFE with TCPServersTransport](../../routing-configuration/tcp/serverstransport.md#opt-serverstransport-spiffe)). ### Configuration Example diff --git a/docs/content/reference/routing-configuration/dynamic-configuration-methods.md b/docs/content/reference/routing-configuration/dynamic-configuration-methods.md index cb158d0b2..20b30222e 100644 --- a/docs/content/reference/routing-configuration/dynamic-configuration-methods.md +++ b/docs/content/reference/routing-configuration/dynamic-configuration-methods.md @@ -72,8 +72,6 @@ When using Docker or Amazon ECS, you can define routing configuration using cont When deploying a Docker container, you can specify labels to define routing rules and services: ```yaml - version: '3' - services: my-service: image: my-image diff --git a/docs/content/reference/routing-configuration/http/load-balancing/serverstransport.md b/docs/content/reference/routing-configuration/http/load-balancing/serverstransport.md index 89d7b31c4..5774e60df 100644 --- a/docs/content/reference/routing-configuration/http/load-balancing/serverstransport.md +++ b/docs/content/reference/routing-configuration/http/load-balancing/serverstransport.md @@ -94,19 +94,20 @@ labels: ## Configuration Options -| Field | Description | Default | Required | -|:------|:----------------------------------------------------------|:---------------------|:---------| -| `serverName` | Configures the server name that will be used as the SNI. | "" | No | -| `certificates` | Defines the list of certificates (as file paths, or data bytes) that will be set as client certificates for mTLS. | [] | No | -| `insecureSkipVerify` | Controls whether the server's certificate chain and host name is verified. | false | No | -| `rootcas` | Set of root certificate authorities to use when verifying server certificates. (for mTLS connections). | [] | No | -| `maxIdleConnsPerHost` | Maximum idle (keep-alive) connections to keep per-host. | 200 | No | -| `disableHTTP2` | Disables HTTP/2 for connections with servers. | false | No | -| `peerCertURI` | Defines the URI used to match against SAN URIs during the server's certificate verification. | "" | No | -| `forwardingTimeouts.dialTimeout` | Amount of time to wait until a connection to a server can be established.
0 = no timeout | 30s | No | -| `forwardingTimeouts.responseHeaderTimeout` | Amount of time to wait for a server's response headers after fully writing the request (including its body, if any).
0 = no timeout | 0s | No | -| `forwardingTimeouts.idleConnTimeout` | Maximum amount of time an idle (keep-alive) connection will remain idle before closing itself.
0 = no timeout | 90s | No | -| `forwardingTimeouts.readIdleTimeout` | Defines the timeout after which a health check using ping frame will be carried out if no frame is received on the HTTP/2 connection. | 0s | No | -| `forwardingTimeouts.pingTimeout` | Defines the timeout after which the HTTP/2 connection will be closed if a response to ping is not received. | 15s | No | -| `spiffe.ids` | Defines the allowed SPIFFE IDs.
This takes precedence over the SPIFFE TrustDomain. | [] | No | -| `spiffe.trustDomain` | Defines the SPIFFE trust domain. | "" | No | +| Field | Description | Default | Required | +|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:-----------------------------------------------------------------------------------------------------------------------------------------|:--------|:---------| +| `serverName` | Configures the server name that will be used as the SNI. | "" | No | +| `certificates` | Defines the list of certificates (as file paths, or data bytes) that will be set as client certificates for mTLS. | [] | No | +| `insecureSkipVerify` | Controls whether the server's certificate chain and host name is verified. | false | No | +| `rootcas` | Set of root certificate authorities to use when verifying server certificates. (for mTLS connections). | [] | No | +| `maxIdleConnsPerHost` | Maximum idle (keep-alive) connections to keep per-host. | 200 | No | +| `disableHTTP2` | Disables HTTP/2 for connections with servers. | false | No | +| `peerCertURI` | Defines the URI used to match against SAN URIs during the server's certificate verification. | "" | No | +| `forwardingTimeouts.dialTimeout` | Amount of time to wait until a connection to a server can be established.
0 = no timeout | 30s | No | +| `forwardingTimeouts.responseHeaderTimeout` | Amount of time to wait for a server's response headers after fully writing the request (including its body, if any).
0 = no timeout | 0s | No | +| `forwardingTimeouts.idleConnTimeout` | Maximum amount of time an idle (keep-alive) connection will remain idle before closing itself.
0 = no timeout | 90s | No | +| `forwardingTimeouts.readIdleTimeout` | Defines the timeout after which a health check using ping frame will be carried out if no frame is received on the HTTP/2 connection. | 0s | No | +| `forwardingTimeouts.pingTimeout` | Defines the timeout after which the HTTP/2 connection will be closed if a response to ping is not received. | 15s | No | +| `spiffe` | Defines the SPIFFE configuration. An empty `spiffe` section enables SPIFFE (that allows any SPIFFE ID). | | No | +| `spiffe.ids` | Defines the allowed SPIFFE IDs.
This takes precedence over the SPIFFE TrustDomain. | [] | No | +| `spiffe.trustDomain` | Defines the SPIFFE trust domain. | "" | No | diff --git a/docs/content/reference/routing-configuration/http/load-balancing/service.md b/docs/content/reference/routing-configuration/http/load-balancing/service.md index eaa665417..38f26d077 100644 --- a/docs/content/reference/routing-configuration/http/load-balancing/service.md +++ b/docs/content/reference/routing-configuration/http/load-balancing/service.md @@ -1,7 +1,10 @@ --- title: "Traefik HTTP Services Documentation" description: "A service is in charge of connecting incoming requests to the Servers that can handle them. Read the technical documentation." ---- +--- + +Traefik services define how to distribute incoming traffic across your backend servers. +Each service implements one of the load balancing strategies detailed on this page to ensure optimal traffic distribution and high availability. ## Service Load Balancer @@ -9,7 +12,7 @@ The load balancers are able to load balance the requests between multiple instan Each service has a load-balancer, even if there is only one server to forward traffic to. -## Configuration Example +### Configuration Example ```yaml tab="Structured (YAML)" http: @@ -70,7 +73,6 @@ labels: ```json tab="Tags" { - // ... "Tags": [ "traefik.http.services.my-service.loadBalancer.servers[0].url=http://private-ip-server-1/", "traefik.http.services.my-service.loadBalancer.servers[0].weight=2", @@ -88,15 +90,15 @@ labels: ### Configuration Options -| Field | Description | Required | -|----------|------------------------------------------|----------| -|`servers`| Represents individual backend instances for your service | Yes | -|`sticky`| Defines a `Set-Cookie` header is set on the initial response to let the client know which server handles the first response. | No | -|`healthcheck`| Configures health check to remove unhealthy servers from the load balancing rotation. | No | -|`passHostHeader`| Allows forwarding of the client Host header to server. By default, `passHostHeader` is true. | No | -|`serversTransport`| Allows to reference an [HTTP ServersTransport](./serverstransport.md) configuration for the communication between Traefik and your servers. If no `serversTransport` is specified, the `default@internal` will be used. | No | -| `responseForwarding` | Configures how Traefik forwards the response from the backend server to the client.| No | -| `responseForwarding.FlushInterval` | Specifies the interval in between flushes to the client while copying the response body. It is a duration in milliseconds, defaulting to 100ms. A negative value means to flush immediately after each write to the client. The `FlushInterval` is ignored when ReverseProxy recognizes a response as a streaming response; for such responses, writes are flushed to the client immediately. | No | +| Field | Description | Required | +|------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------| +| `servers` | Represents individual backend instances for your service | Yes | +| `sticky` | Defines a `Set-Cookie` header is set on the initial response to let the client know which server handles the first response. | No | +| `healthcheck` | Configures health check to remove unhealthy servers from the load balancing rotation. | No | +| `passHostHeader` | Allows forwarding of the client Host header to server. By default, `passHostHeader` is true. | No | +| `serversTransport` | Allows to reference an [HTTP ServersTransport](./serverstransport.md) configuration for the communication between Traefik and your servers. If no `serversTransport` is specified, the `default@internal` will be used. | No | +| `responseForwarding` | Configures how Traefik forwards the response from the backend server to the client. | No | +| `responseForwarding.FlushInterval` | Specifies the interval in between flushes to the client while copying the response body. It is a duration in milliseconds, defaulting to 100ms. A negative value means to flush immediately after each write to the client. The `FlushInterval` is ignored when ReverseProxy recognizes a response as a streaming response; for such responses, writes are flushed to the client immediately. | No | #### Servers @@ -104,11 +106,11 @@ Servers represent individual backend instances for your service. The [service lo ##### Configuration Options -| Field | Description | Required | -|----------|------------------------------------------|----------| -|`url`| Points to a specific instance. | Yes for File provider, No for [Docker provider](../../other-providers/docker.md) | -|`weight`| Allows for weighted load balancing on the servers. | No | -|`preservePath`| Allows to preserve the URL path. | No | +| Field | Description | Required | +|----------------|----------------------------------------------------|----------------------------------------------------------------------------------| +| `url` | Points to a specific instance. | Yes for File provider, No for [Docker provider](../../other-providers/docker.md) | +| `weight` | Allows for weighted load balancing on the servers. | No | +| `preservePath` | Allows to preserve the URL path. | No | #### Health Check @@ -118,20 +120,179 @@ To propagate status changes (e.g. all servers of this service are down) upwards, Below are the available options for the health check mechanism: -| Field | Description | Default | Required | -|----------|------------------------------------------|----------|--------| -|`path`| Defines the server URL path for the health check endpoint. | "" | Yes | -|`scheme`| Replaces the server URL scheme for the health check endpoint. | | No | -|`mode`| If defined to `grpc`, will use the gRPC health check protocol to probe the server. | http | No | -|`hostname`| Defines the value of hostname in the Host header of the health check request. | "" | No | -|`port`| Replaces the server URL port for the health check endpoint. | | No | -|`interval`| Defines the frequency of the health check calls. | 30s | No | -|`timeout`| Defines the maximum duration Traefik will wait for a health check request before considering the server unhealthy. | 5s | No | -|`headers`| Defines custom headers to be sent to the health check endpoint. | | No | -|`followRedirects`| Defines whether redirects should be followed during the health check calls. | true | No | -|`hostname`| Defines the value of hostname in the Host header of the health check request. | "" | No | -|`method`| Defines the HTTP method that will be used while connecting to the endpoint. | GET | No | -|`status`| Defines the expected HTTP status code of the response to the health check request. | | No | +| Field | Description | Default | Required | +|---------------------|-------------------------------------------------------------------------------------------------------------------------------|---------|----------| +| `path` | Defines the server URL path for the health check endpoint. | "" | Yes | +| `scheme` | Replaces the server URL scheme for the health check endpoint. | | No | +| `mode` | If defined to `grpc`, will use the gRPC health check protocol to probe the server. | http | No | +| `hostname` | Defines the value of hostname in the Host header of the health check request. | "" | No | +| `port` | Replaces the server URL port for the health check endpoint. | | No | +| `interval` | Defines the frequency of the health check calls for healthy targets. | 30s | No | +| `unhealthyInterval` | Defines the frequency of the health check calls for unhealthy targets. When not defined, it defaults to the `interval` value. | 30s | No | +| `timeout` | Defines the maximum duration Traefik will wait for a health check request before considering the server unhealthy. | 5s | No | +| `headers` | Defines custom headers to be sent to the health check endpoint. | | No | +| `followRedirects` | Defines whether redirects should be followed during the health check calls. | true | No | +| `hostname` | Defines the value of hostname in the Host header of the health check request. | "" | No | +| `method` | Defines the HTTP method that will be used while connecting to the endpoint. | GET | No | +| `status` | Defines the expected HTTP status code of the response to the health check request. | | No | + +#### Sticky sessions + +When sticky sessions are enabled, a `Set-Cookie` header is set on the initial response to let the client know which server handles the first response. +On subsequent requests, to keep the session alive with the same server, the client should send the cookie with the value set. + +##### Stickiness on multiple levels + + When chaining or mixing load-balancers (e.g. a load-balancer of servers is one of the "children" of a load-balancer of services), for stickiness to work all the way, the option needs to be specified at all required levels. Which means the client needs to send a cookie with as many key/value pairs as there are sticky levels. + +##### Stickiness & Unhealthy Servers + + If the server specified in the cookie becomes unhealthy, the request will be forwarded to a new server (and the cookie will keep track of the new server). + +##### Cookie Name + + The default cookie name is an abbreviation of a sha1 (ex: `_1d52e`). + +##### MaxAge + + By default, the affinity cookie will never expire as the `MaxAge` option is set to zero. + + This option indicates the number of seconds until the cookie expires. + When set to a negative number, the cookie expires immediately. + +##### Secure & HTTPOnly & SameSite flags + + By default, the affinity cookie is created without those flags. + One however can change that through configuration. + + `SameSite` can be `none`, `lax`, `strict` or empty. + +##### Domain + + The Domain attribute of a cookie specifies the domain for which the cookie is valid. + + By setting the Domain attribute, the cookie can be shared across subdomains (for example, a cookie set for example.com would be accessible to www.example.com, api.example.com, etc.). This is particularly useful in cases where sticky sessions span multiple subdomains, ensuring that the session is maintained even when the client interacts with different parts of the infrastructure. + +??? example "Adding Stickiness -- Using the [File Provider](../../../install-configuration/providers/others/file.md)" + + ```yaml tab="YAML" + ## Dynamic configuration + http: + services: + my-service: + loadBalancer: + sticky: + cookie: {} + ``` + + ```toml tab="TOML" + ## Dynamic configuration + [http.services] + [http.services.my-service] + [http.services.my-service.loadBalancer.sticky.cookie] + ``` + +??? example "Adding Stickiness with custom Options -- Using the [File Provider](../../../install-configuration/providers/others/file.md)" + + ```yaml tab="YAML" + ## Dynamic configuration + http: + services: + my-service: + loadBalancer: + sticky: + cookie: + name: my_sticky_cookie_name + secure: true + domain: mysite.site + httpOnly: true + ``` + + ```toml tab="TOML" + ## Dynamic configuration + [http.services] + [http.services.my-service] + [http.services.my-service.loadBalancer.sticky.cookie] + name = "my_sticky_cookie_name" + secure = true + httpOnly = true + domain = "mysite.site" + sameSite = "none" + ``` + +??? example "Setting Stickiness on all the required levels -- Using the [File Provider](../../../install-configuration/providers/others/file.md)" + + ```yaml tab="YAML" + ## Dynamic configuration + http: + services: + wrr1: + weighted: + sticky: + cookie: + name: lvl1 + services: + - name: whoami1 + weight: 1 + - name: whoami2 + weight: 1 + + whoami1: + loadBalancer: + sticky: + cookie: + name: lvl2 + servers: + - url: http://127.0.0.1:8081 + - url: http://127.0.0.1:8082 + + whoami2: + loadBalancer: + sticky: + cookie: + name: lvl2 + servers: + - url: http://127.0.0.1:8083 + - url: http://127.0.0.1:8084 + ``` + + ```toml tab="TOML" + ## Dynamic configuration + [http.services] + [http.services.wrr1] + [http.services.wrr1.weighted.sticky.cookie] + name = "lvl1" + [[http.services.wrr1.weighted.services]] + name = "whoami1" + weight = 1 + [[http.services.wrr1.weighted.services]] + name = "whoami2" + weight = 1 + + [http.services.whoami1] + [http.services.whoami1.loadBalancer] + [http.services.whoami1.loadBalancer.sticky.cookie] + name = "lvl2" + [[http.services.whoami1.loadBalancer.servers]] + url = "http://127.0.0.1:8081" + [[http.services.whoami1.loadBalancer.servers]] + url = "http://127.0.0.1:8082" + + [http.services.whoami2] + [http.services.whoami2.loadBalancer] + [http.services.whoami2.loadBalancer.sticky.cookie] + name = "lvl2" + [[http.services.whoami2.loadBalancer.servers]] + url = "http://127.0.0.1:8083" + [[http.services.whoami2.loadBalancer.servers]] + url = "http://127.0.0.1:8084" + ``` + + To keep a session open with the same server, the client would then need to specify the two levels within the cookie for each request, e.g. with curl: + + ``` + curl -b "lvl1=whoami1; lvl2=http://127.0.0.1:8081" http://localhost:8000 + ``` ## Weighted Round Robin (WRR) @@ -141,7 +302,7 @@ This strategy is only available to load balance between services and not between !!! info "Supported Providers" - This strategy can be defined currently with the [File](../../../install-configuration/providers/others/file.md) or [IngressRoute](../../../install-configuration/providers/kubernetes/kubernetes-ingress.md) providers. To load balance between servers based on weights, the Load Balancer service should be used instead. + This strategy can be defined currently with the [File](../../../install-configuration/providers/others/file.md) or [IngressRoute](../../../install-configuration/providers/kubernetes/kubernetes-crd.md) providers. To load balance between servers based on weights, the Load Balancer service should be used instead. ```yaml tab="Structured (YAML)" ## Dynamic configuration @@ -260,14 +421,49 @@ http: [[http.services.appv2.loadBalancer.servers]] url = "http://private-ip-server-2/" ``` +## P2C + +Power of two choices algorithm is a load balancing strategy that selects two servers at random and chooses the one with the least number of active requests. + +??? example "P2C Load Balancing -- Using the [File Provider](../../../install-configuration/providers/others/file.md)" + + ```yaml tab="YAML" + ## Dynamic configuration + http: + services: + my-service: + loadBalancer: + strategy: "p2c" + servers: + - url: "http://private-ip-server-1/" + - url: "http://private-ip-server-2/" + - url: "http://private-ip-server-3/" + ``` + + ```toml tab="TOML" + ## Dynamic configuration + [http.services] + [http.services.my-service.loadBalancer] + strategy = "p2c" + [[http.services.my-service.loadBalancer.servers]] + url = "http://private-ip-server-1/" + [[http.services.my-service.loadBalancer.servers]] + url = "http://private-ip-server-2/" + [[http.services.my-service.loadBalancer.servers]] + url = "http://private-ip-server-3/" + ``` ## Mirroring The mirroring is able to mirror requests sent to a service to other services. Please note that by default the whole request is buffered in memory while it is being mirrored. See the `maxBodySize` option in the example below for how to modify this behaviour. You can also omit the request body by setting the `mirrorBody` option to false. +!!! warning "Default behavior of `percent`" + + When configuring a `mirror` service, if the `percent` field is not set, it defaults to `0`, meaning **no traffic will be sent to the mirror**. + !!! info "Supported Providers" - This strategy can be defined currently with the [File](../../../install-configuration/providers/others/file.md) or [IngressRoute](../../../install-configuration/providers/kubernetes/kubernetes-ingress.md) providers. + This strategy can be defined currently with the [File](../../../install-configuration/providers/others/file.md) or [IngressRoute](../../../install-configuration/providers/kubernetes/kubernetes-crd.md) providers. ```yaml tab="Structured (YAML)" ## Dynamic configuration @@ -285,6 +481,8 @@ http: maxBodySize: 1024 mirrors: - name: appv2 + # Percent defines the percentage of requests that should be mirrored. + # Default value is 0, which means no traffic will be sent to the mirror. percent: 10 appv1: diff --git a/docs/content/reference/routing-configuration/http/middlewares/addprefix.md b/docs/content/reference/routing-configuration/http/middlewares/addprefix.md index 84c34e8ca..0bb182d8b 100644 --- a/docs/content/reference/routing-configuration/http/middlewares/addprefix.md +++ b/docs/content/reference/routing-configuration/http/middlewares/addprefix.md @@ -3,8 +3,6 @@ title: "Traefik AddPrefix Documentation" description: "Learn how to implement the HTTP AddPrefix middleware in Traefik Proxy to updates request paths before being forwarded. Read the technical documentation." --- -![AddPrefix](../../../../assets/img/middleware/addprefix.png) - The `addPrefix` middleware updates the path of a request before forwarding it. ## Configuration Examples @@ -56,4 +54,4 @@ spec: | Field | Description | Default | Required | |:-----------------------------|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:--------|:---------| -| `prefix` | String to add **before** the current path in the requested URL. It should include a leading slash (`/`). | "" | Yes | +| `prefix` | String to add **before** the current path in the requested URL. It should include a leading slash (`/`). | "" | Yes | diff --git a/docs/content/reference/routing-configuration/http/middlewares/apikey.md b/docs/content/reference/routing-configuration/http/middlewares/apikey.md new file mode 100644 index 000000000..454c141a1 --- /dev/null +++ b/docs/content/reference/routing-configuration/http/middlewares/apikey.md @@ -0,0 +1,56 @@ +--- +title: 'API Key Authentication' +description: 'Traefik Hub API Gateway - The API Key authentication middleware allows you to secure an API by requiring a secret key, base64 encoded or not, to be given, via an HTTP header, a cookie or a query parameter.' +--- + +!!! info "Traefik Hub Feature" + This middleware is available exclusively in [Traefik Hub](https://traefik.io/traefik-hub/). Learn more about [Traefik Hub's advanced features](https://doc.traefik.io/traefik-hub/api-gateway/intro). + +The API Key authentication middleware allows you to secure an API by requiring a secret key, base64 encoded or not, to be given, via an HTTP header, a cookie or a query parameter. + +--- + +## Configuration Example + +```yaml tab="Middleware API Key" +apiVersion: traefik.io/v1alpha1 +kind: Middleware +metadata: + name: test-apikey + namespace: apps +spec: + plugin: + apiKey: + keySource: + headerAuthScheme: Bearer + header: Authorization + secretNonBase64Encoded: true + secretValues: + - "urn:k8s:secret:apikey:secret" + - "urn:k8s:secret:apikey:othersecret" +``` + +```yaml tab="Values Secret" +apiVersion: v1 +kind: Secret +type: Opaque +metadata: + name: apikey + namespace: whoami +stringData: + secret: $2y$05$D4SPFxzfWKcx1OXfVhRbvOTH/QB0Lm6AXTk8.NOmU4rPLX2t6UUuW # htpasswd -nbB "" foo | cut -c 2- + othersecret: $2y$05$HbLL.g5dUqJippH0RuAGL.RaM9wNS2cT7hp6.vbv5okdCmVBSDzzK # htpasswd -nbB "" bar | cut -c 2- +``` + +## Configuration Options + +| Field | Description | Default | Required | +|:-----------------------------|:------------------------------------------------|:--------|:---------| +| `keySource.header` | Defines the header name containing the secret sent by the client.
Either `keySource.header` or `keySource.query` or `keySource.cookie` must be set. | "" | No | +| `keySource.headerAuthScheme` | Defines the scheme when using `Authorization` as header name.
Check out the `Authorization` header [documentation](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Authorization#syntax). | "" | No | +| `keySource.query` | Defines the query parameter name containing the secret sent by the client.
Either `keySource.header` or `keySource.query` or `keySource.cookie` must be set. | "" | No | +| `keySource.cookie` | Defines the cookie name containing the secret sent by the client.
Either `keySource.header` or `keySource.query` or `keySource.cookie` must be set. | "" | No | +| `secretNonBase64Encoded` | Defines whether the secret sent by the client is base64 encoded. | false | No | +| `secretValues` | Contain the hash of the API keys.
Supported hashing algorithms are Bcrypt, SHA1 and MD5.
The hash should be generated using `htpasswd`.
Can reference a Kubernetes Secret using the URN format: `urn:k8s:secret:[name]:[valueKey]` | [] | Yes | + +{!traefik-for-business-applications.md!} diff --git a/docs/content/reference/routing-configuration/http/middlewares/basicauth.md b/docs/content/reference/routing-configuration/http/middlewares/basicauth.md index 29587f6a3..be372250b 100644 --- a/docs/content/reference/routing-configuration/http/middlewares/basicauth.md +++ b/docs/content/reference/routing-configuration/http/middlewares/basicauth.md @@ -3,8 +3,6 @@ title: "Traefik BasicAuth Documentation" description: "The HTTP basic authentication (BasicAuth) middleware in Traefik Proxy restricts access to your Services to known users. Read the technical documentation." --- -![BasicAuth](../../../../assets/img/middleware/basicauth.png) - The `basicAuth` middleware grants access to services to authorized users only. ## Configuration Examples @@ -66,11 +64,11 @@ spec: | Field | Description | Default | Required | |:-----------|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:--------|:---------| -| `users` | Array of authorized users. Each user must be declared using the `name:hashed-password` format. (More information [here](#users))| "" | No | -| `usersFile` | Path to an external file that contains the authorized users for the middleware.
The file content is a list of `name:hashed-password`. (More information [here](#usersfile)) | "" | No | -| `realm` | Allow customizing the realm for the authentication.| "traefik" | No | -| `headerField` | Allow defining a header field to store the authenticated user.| "" | No | -| `removeHeader` | Allow removing the authorization header before forwarding the request to your service. | false | No | +| `users` | Array of authorized users. Each user must be declared using the `name:hashed-password` format. (More information [here](#users-usersfile))| "" | No | +| `usersFile` | Path to an external file that contains the authorized users for the middleware.
The file content is a list of `name:hashed-password`. (More information [here](#users-usersfile)) | "" | No | +| `realm` | Allow customizing the realm for the authentication.| "traefik" | No | +| `headerField` | Allow defining a header field to store the authenticated user.| "" | No | +| `removeHeader` | Allow removing the authorization header before forwarding the request to your service. | false | No | ### Passwords format diff --git a/docs/content/reference/routing-configuration/http/middlewares/buffering.md b/docs/content/reference/routing-configuration/http/middlewares/buffering.md index 4fe818ded..af334a221 100644 --- a/docs/content/reference/routing-configuration/http/middlewares/buffering.md +++ b/docs/content/reference/routing-configuration/http/middlewares/buffering.md @@ -3,8 +3,6 @@ title: "Traefik Buffering Documentation" description: "The HTTP buffering middleware in Traefik Proxy limits the size of requests that can be forwarded to Services. Read the technical documentation." --- -![Buffering](../../../../assets/img/middleware/buffering.png) - The `buffering` middleware limits the size of requests that can be forwarded to services. With buffering, Traefik reads the entire request into memory (possibly buffering large requests into disk), and rejects requests that are over a specified size limit. @@ -60,11 +58,11 @@ spec: | Field | Description | Default | Required | |:------|:------------|:--------|:---------| -| `maxRequestBodyBytes` | 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. | 0 | No | -| `memRequestBodyBytes` | Threshold (in bytes) from which the request will be buffered on disk instead of in memory with the `memRequestBodyBytes` option.| 1048576 | No | -| `maxResponseBodyBytes` | Maximum allowed response size from the Service (in bytes).
If the response exceeds the allowed size, it is not forwarded to the client. The client gets a `500` (Internal Server Error) response instead. | 0 | No | -| `memResponseBodyBytes` | Threshold (in bytes) from which the response will be buffered on disk instead of in memory with the `memResponseBodyBytes` option.| 1048576 | No | -| `retryExpression` | Replay the request using `retryExpression`.
More information [here](#retryexpression). | "" | No | +| `maxRequestBodyBytes` | 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. | 0 | No | +| `memRequestBodyBytes` | Threshold (in bytes) from which the request will be buffered on disk instead of in memory with the `memRequestBodyBytes` option.| 1048576 | No | +| `maxResponseBodyBytes` | Maximum allowed response size from the Service (in bytes).
If the response exceeds the allowed size, it is not forwarded to the client. The client gets a `500` (Internal Server Error) response instead. | 0 | No | +| `memResponseBodyBytes` | Threshold (in bytes) from which the response will be buffered on disk instead of in memory with the `memResponseBodyBytes` option.| 1048576 | No | +| `retryExpression` | Replay the request using `retryExpression`.
More information [here](#retryexpression). | "" | No | ### retryExpression diff --git a/docs/content/reference/routing-configuration/http/middlewares/chain.md b/docs/content/reference/routing-configuration/http/middlewares/chain.md index 3eb1e6ad4..2cb81665d 100644 --- a/docs/content/reference/routing-configuration/http/middlewares/chain.md +++ b/docs/content/reference/routing-configuration/http/middlewares/chain.md @@ -168,4 +168,4 @@ spec: | Field | Description | Default | Required | |:------|:------------|:--------|:---------| -| `middlewares` | List of middlewares to chain.
The middlewares have to be in the same namespace as the `chain` middleware. | [] | Yes | +| `middlewares` | List of middlewares to chain.
The middlewares have to be in the same namespace as the `chain` middleware. | [] | Yes | diff --git a/docs/content/reference/routing-configuration/http/middlewares/circuitbreaker.md b/docs/content/reference/routing-configuration/http/middlewares/circuitbreaker.md index dfd2be843..999041adc 100644 --- a/docs/content/reference/routing-configuration/http/middlewares/circuitbreaker.md +++ b/docs/content/reference/routing-configuration/http/middlewares/circuitbreaker.md @@ -3,8 +3,6 @@ title: "Traefik CircuitBreaker Documentation" description: "The HTTP circuit breaker in Traefik Proxy prevents stacking requests to unhealthy Services, resulting in cascading failures. Read the technical documentation." --- -![Circuit Breaker](../../../../assets/img/middleware/circuitbreaker.png) - The HTTP circuit breaker prevents stacking requests to unhealthy Services, resulting in cascading failures. When your system is healthy, the circuit is closed (normal operations). @@ -67,11 +65,11 @@ spec: | Field | Description | Default | Required | |:------|:------------|:--------|:---------| -| `expression` | Condition to open the circuit breaker and applies the fallback mechanism instead of calling your services.
More information [here](#expression) | 100ms | No | -| `checkPeriod` | The interval between successive checks of the circuit breaker condition (when in standby state). | 100ms | No | -| `fallbackDuration` | The duration for which the circuit breaker will wait before trying to recover (from a tripped state). | 10s | No | -| `recoveryDuration` | The duration for which the circuit breaker will try to recover (as soon as it is in recovering state). | 10s | No | -| `responseCode` | The status code that the circuit breaker will return while it is in the open state. | 503 | No | +| `expression` | Condition to open the circuit breaker and applies the fallback mechanism instead of calling your services.
More information [here](#expression) | 100ms | No | +| `checkPeriod` | The interval between successive checks of the circuit breaker condition (when in standby state). | 100ms | No | +| `fallbackDuration` | The duration for which the circuit breaker will wait before trying to recover (from a tripped state). | 10s | No | +| `recoveryDuration` | The duration for which the circuit breaker will try to recover (as soon as it is in recovering state). | 10s | No | +| `responseCode` | The status code that the circuit breaker will return while it is in the open state. | 503 | No | ### expression @@ -79,9 +77,9 @@ The `expression` option can check three different metrics: | Metrics | Description | Example | |:------|:------------|:--------| -| `NetworkErrorRatio` | The network error ratio to open the circuit breaker. | `NetworkErrorRatio() > 0.30` opens the circuit breaker at a 30% ratio of network errors | -| `ResponseCodeRatio` | The status code ratio to open the circuit breaker.
More information [below](#responsecoderatio) | `ResponseCodeRatio(500, 600, 0, 600) > 0.25` opens the circuit breaker if 25% of the requests returned a 5XX status (amongst the request that returned a status code from 0 to 5XX) | -| `LatencyAtQuantileMS` | The latency at a quantile in milliseconds to open the circuit breaker when a given proportion of your requests become too slow.
Only floating point number (with the trailing .0) for the quantile value. | `LatencyAtQuantileMS(50.0) > 100` opens the circuit breaker when the median latency (quantile 50) reaches 100ms. | +| `NetworkErrorRatio` | The network error ratio to open the circuit breaker. | `NetworkErrorRatio() > 0.30` opens the circuit breaker at a 30% ratio of network errors | +| `ResponseCodeRatio` | The status code ratio to open the circuit breaker.
More information [below](#responsecoderatio) | `ResponseCodeRatio(500, 600, 0, 600) > 0.25` opens the circuit breaker if 25% of the requests returned a 5XX status (amongst the request that returned a status code from 0 to 5XX) | +| `LatencyAtQuantileMS` | The latency at a quantile in milliseconds to open the circuit breaker when a given proportion of your requests become too slow.
Only floating point number (with the trailing .0) for the quantile value. | `LatencyAtQuantileMS(50.0) > 100` opens the circuit breaker when the median latency (quantile 50) reaches 100ms. | #### ResponseCodeRatio @@ -113,8 +111,8 @@ Here is the list of supported operators: ### Fallback mechanism -The fallback mechanism returns a `HTTP 503 Service Unavailable` to the client instead of calling the target service. -This behavior cannot be configured. +By default the fallback mechanism returns a `HTTP 503 Service Unavailable` to the client instead of calling the target service. +The response code can be configured. ## State diff --git a/docs/content/reference/routing-configuration/http/middlewares/compress.md b/docs/content/reference/routing-configuration/http/middlewares/compress.md index e1b8ad8d4..f67c62c9a 100644 --- a/docs/content/reference/routing-configuration/http/middlewares/compress.md +++ b/docs/content/reference/routing-configuration/http/middlewares/compress.md @@ -51,11 +51,11 @@ spec: | Field | Description | Default | Required | |:-----------------------------|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:--------|:---------| -|`excludedContentTypes` | List of content types to compare the `Content-Type` header of the incoming requests and responses before compressing.
The responses with content types defined in `excludedContentTypes` are not compressed.
Content types are compared in a case-insensitive, whitespace-ignored manner.
**The `excludedContentTypes` and `includedContentTypes` options are mutually exclusive.** | "" | No | -|`defaultEncoding` | specifies the default encoding if the `Accept-Encoding` header is not in the request or contains a wildcard (`*`). | "" | No | -|`encodings` | Specifies the list of supported compression encodings. At least one encoding value must be specified, and valid entries are `zstd` (Zstandard), `br` (Brotli), and `gzip` (Gzip). The order of the list also sets the priority, the top entry has the highest priority. | zstd, br, gzip | No | -| `includedContentTypes` | List of content types to compare the `Content-Type` header of the responses before compressing.
The responses with content types defined in `includedContentTypes` are compressed.
Content types are compared in a case-insensitive, whitespace-ignored manner.
**The `excludedContentTypes` and `includedContentTypes` options are mutually exclusive.** | "" | No | -| `minResponseBodyBytes` | `Minimum amount of bytes a response body must have to be compressed.
Responses smaller than the specified values will **not** be compressed. | 1024 | No | +| `excludedContentTypes` | List of content types to compare the `Content-Type` header of the incoming requests and responses before compressing.
The responses with content types defined in `excludedContentTypes` are not compressed.
Content types are compared in a case-insensitive, whitespace-ignored manner.
**The `excludedContentTypes` and `includedContentTypes` options are mutually exclusive.** | "" | No | +| `defaultEncoding` | specifies the default encoding if the `Accept-Encoding` header is not in the request or contains a wildcard (`*`). | "" | No | +| `encodings` | Specifies the list of supported compression encodings. At least one encoding value must be specified, and valid entries are `zstd` (Zstandard), `br` (Brotli), and `gzip` (Gzip). The order of the list also sets the priority, the top entry has the highest priority. | zstd, br, gzip | No | +| `includedContentTypes` | List of content types to compare the `Content-Type` header of the responses before compressing.
The responses with content types defined in `includedContentTypes` are compressed.
Content types are compared in a case-insensitive, whitespace-ignored manner.
**The `excludedContentTypes` and `includedContentTypes` options are mutually exclusive.** | "" | No | +| `minResponseBodyBytes` | `Minimum amount of bytes a response body must have to be compressed.
Responses smaller than the specified values will **not** be compressed. | 1024 | No | ## Compression activation diff --git a/docs/content/reference/routing-configuration/http/middlewares/digestauth.md b/docs/content/reference/routing-configuration/http/middlewares/digestauth.md index 22dd5b4bf..974ef48ce 100644 --- a/docs/content/reference/routing-configuration/http/middlewares/digestauth.md +++ b/docs/content/reference/routing-configuration/http/middlewares/digestauth.md @@ -3,8 +3,6 @@ title: "Traefik DigestAuth Documentation" description: "Traefik Proxy's HTTP DigestAuth middleware restricts access to your services to known users. Read the technical documentation." --- -![DigestAuth](../../../../assets/img/middleware/digestauth.png) - The `DigestAuth` middleware grants access to services to authorized users only. ## Configuration Examples @@ -61,11 +59,11 @@ spec: | Field | Description | Default | Required | |:-----------|:---------------------------------------------------------------------------------|:--------|:---------| -| `users` | Array of authorized users. Each user must be declared using the `name:realm:encoded-password` format.
The option `users` supports Kubernetes secrets.
(More information [here](#users--usersfile))| [] | No | -| `usersFile` | Path to an external file that contains the authorized users for the middleware.
The file content is a list of `name:realm:encoded-password`. (More information [here](#users--usersfile)) | "" | No | -| `realm` | Allow customizing the realm for the authentication.| "traefik" | No | -| `headerField` | Allow defining a header field to store the authenticated user.| "" | No | -| `removeHeader` | Allow removing the authorization header before forwarding the request to your service. | false | No | +| `users` | Array of authorized users. Each user must be declared using the `name:realm:encoded-password` format.
The option `users` supports Kubernetes secrets.
(More information [here](#users--usersfile))| [] | No | +| `usersFile` | Path to an external file that contains the authorized users for the middleware.
The file content is a list of `name:realm:encoded-password`. (More information [here](#users--usersfile)) | "" | No | +| `realm` | Allow customizing the realm for the authentication.| "traefik" | No | +| `headerField` | Allow defining a header field to store the authenticated user.| "" | No | +| `removeHeader` | Allow removing the authorization header before forwarding the request to your service. | false | No | ### Passwords format diff --git a/docs/content/reference/routing-configuration/http/middlewares/distributed-ratelimit.md b/docs/content/reference/routing-configuration/http/middlewares/distributed-ratelimit.md new file mode 100644 index 000000000..a5f7eb94e --- /dev/null +++ b/docs/content/reference/routing-configuration/http/middlewares/distributed-ratelimit.md @@ -0,0 +1,181 @@ +--- +title: "Distributed RateLimit" +description: "Traefik Hub API Gateway - The Distributed RateLimit middleware ensures Services receive fair amounts of requests throughout your cluster and not only on an individual proxy." +--- + +!!! info "Traefik Hub Feature" + This middleware is available exclusively in [Traefik Hub](https://traefik.io/traefik-hub/). Learn more about [Traefik Hub's advanced features](https://doc.traefik.io/traefik-hub/api-gateway/intro). + +The Distributed RateLimit middleware ensures that requests are limited over time throughout your cluster and not only on an individual proxy. + +It is based on a [token bucket](https://en.wikipedia.org/wiki/Token_bucket) implementation. + +--- + +## Configuration Example + +Below is an advanced configuration that enables the Distributed RateLimit middleware with Redis backend for cluster-wide rate limiting. + +```yaml tab="Middleware Distributed Rate Limit" +# Here, a limit of 100 requests per second is allowed. +# In addition, a burst of 200 requests is allowed. +apiVersion: traefik.io/v1alpha1 +kind: Middleware +metadata: + name: test-distributedratelimit + namespace: traefik +spec: + plugin: + distributedRateLimit: + burst: 200 + denyOnError: false + limit: 100 + period: 1s + responseHeaders: true + sourceCriterion: + ipStrategy: + excludedIPs: + - 172.20.176.201 + store: + redis: + endpoints: + - my-release-redis-master.default.svc.cluster.local:6379 + # Use the field password of the Secret redis in the same namespace + password: urn:k8s:secret:redis:password + timeout: 500ms +``` + +```yaml tab="Kubernetes Secret" +apiVersion: v1 +kind: Secret +metadata: + name: redis + namespace: traefik +stringData: + password: mysecret12345678 +``` + +## Rate and Burst + +The rate is defined by dividing `limit` by `period`. +For a rate below 1 req/s, define a `period` larger than a second + +The middleware is based on a [token bucket](https://en.wikipedia.org/wiki/Token_bucket) implementation. +In this analogy, the `limit` and `period` parameters define the **rate** at which the bucket refills, and the `burst` is the size (volume) of the bucket. + +```yaml +apiVersion: traefik.io/v1alpha1 +kind: Middleware +metadata: + name: test-ratelimit +spec: + plugin: + distributedRateLimit: + burst: 100 + period: 1m + limit: 6 +``` + +In the example above, the middleware allows up to 100 connections in parallel (`burst`). +Each connection consume a token, once the 100 tokens are consumed, the other ones are blocked until at least one token is available in the bucket. + +When the bucket is not full, on token is generated every 10 seconds (6 every 1 minutes (`period` / `limit`)). + +## Configuration Options + +| Field | Description | Default | Required | +|:-----------|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:--------|:---------| +| `limit` | Number of requests used to define the rate using the `period`.
0 means **no rate limiting**.
More information [here](#rate-and-burst).| 0 | No | +| `period` | Period of time used to define the rate.
More information [here](#rate-and-burst).| 1s | No | +| `burst` | Maximum number of requests allowed to go through at the very same moment.
More information [here](#rate-and-burst). | 1 | No | +| `denyOnError` | Forces to return a 429 error if the number of remaining requests accepted cannot be get.
Set to `false`, this option allows the request to reach the backend. | true | No | +| `responseHeaders` | Injects the following rate limiting headers in the response:
- `X-Rate-Limit-Remaining`
- `X-Rate-Limit-Limit`
- `X-Rate-Limit-Period`
- `X-Rate-Limit-Reset`
The added headers indicate how many tokens are left in the bucket (in the token bucket analogy) after the reservation for the request was made. | false | No | +| `store.redis.endpoints` | Endpoints of the Redis instances to connect to (example: `redis.traefik-hub.svc.cluster.local:6379`) | "" | Yes | +| `store.redis.username` | The username Traefik Hub will use to connect to Redis | "" | No | +| `store.redis.password` | The password Traefik Hub will use to connect to Redis | "" | No | +| `store.redis.database` | The database Traefik Hub will use to sore information (default: `0`) | "" | No | +| `store.redis.cluster` | Enable Redis Cluster | "" | No | +| `store.redis.tls.caBundle` | Custom CA bundle | "" | No | +| `store.redis.tls.cert` | TLS certificate | "" | No | +| `store.redis.tls.key` | TLS key | "" | No | +| `store.redis.tls.insecureSkipVerify` | Allow skipping the TLS verification | "" | No | +| `store.redis.sentinel.masterSet` | Name of the set of main nodes to use for main selection. Required when using Sentinel. | "" | No | +| `store.redis.sentinel.username` | Username to use for sentinel authentication (can be different from `username`) | "" | No | +| `store.redis.sentinel.password` | Password to use for sentinel authentication (can be different from `password`) | "" | No | +| `sourceCriterion.requestHost` | Whether to consider the request host as the source.
More information about `sourceCriterion`[here](#sourcecriterion). | false | No | +| `sourceCriterion.requestHeaderName` | Name of the header used to group incoming requests.
More information about `sourceCriterion`[here](#sourcecriterion). | "" | No | +| `sourceCriterion.ipStrategy.depth` | Depth position of the IP to select in the `X-Forwarded-For` header (starting from the right).
0 means no depth.
If greater than the total number of IPs in `X-Forwarded-For`, then the client IP is empty
If higher than 0, the `excludedIPs` options is not evaluated.
More information about [`sourceCriterion`](#sourcecriterion), [`ipStrategy`](#ipstrategy), and [`depth`](#sourcecriterionipstrategydepth) below. | 0 | No | +| `sourceCriterion.ipStrategy.excludedIPs` | Allows Traefik to scan the `X-Forwarded-For` header and select the first IP not in the list.
If `depth` is specified, `excludedIPs` is ignored.
More information about [`sourceCriterion`](#sourcecriterion), [`ipStrategy`](#ipstrategy), and [`excludedIPs`](#sourcecriterionipstrategyexcludedips) below. | | No | + +### sourceCriterion + +The `sourceCriterion` option 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 request's remote address field (as an `ipStrategy`). + +### ipStrategy + +The `ipStrategy` option defines two parameters that configures how Traefik determines the client IP: `depth`, and `excludedIPs`. + +As a middleware, rate-limiting happens before the actual proxying to the backend takes place. +In addition, the previous network hop only gets appended to `X-Forwarded-For` during the last stages of proxying, that is after it has already passed through rate-limiting. +Therefore, during rate-limiting, as the previous network hop is not yet present in `X-Forwarded-For`, it cannot be found and/or relied upon. + +### sourceCriterion.ipStrategy.depth + +If `depth` is set to 2, and the request `X-Forwarded-For` header is `"10.0.0.1,11.0.0.1,12.0.0.1,13.0.0.1"` then the "real" client IP is `"10.0.0.1"` (at depth 4) but the IP used as the criterion is `"12.0.0.1"` (`depth=2`). + +| `X-Forwarded-For` | `depth` | clientIP | +|-----------------------------------------|---------|--------------| +| `"10.0.0.1,11.0.0.1,12.0.0.1,13.0.0.1"` | `1` | `"13.0.0.1"` | +| `"10.0.0.1,11.0.0.1,12.0.0.1,13.0.0.1"` | `3` | `"11.0.0.1"` | +| `"10.0.0.1,11.0.0.1,12.0.0.1,13.0.0.1"` | `5` | `""` | + +### sourceCriterion.ipStrategy.excludedIPs + +Contrary to what the name might suggest, this option is *not* about excluding an IP from the rate limiter, and therefore cannot be used to deactivate rate limiting for some IPs. + +`excludedIPs` is meant to address two classes of somewhat distinct use-cases: + +1. Distinguish IPs which are behind the same (set of) reverse-proxies so that each of them contributes, independently to the others, to its own rate-limit "bucket" (cf the [token bucket](https://en.wikipedia.org/wiki/Token_bucket)). +In this case, `excludedIPs` should be set to match the list of `X-Forwarded-For IPs` that are to be excluded, in order to find the actual clientIP. + +Example to use each IP as a distinct source: + +| `X-Forwarded-For` | excludedIPs | clientIP | +|--------------------------------|-----------------------|--------------| +| `"10.0.0.1,11.0.0.1,12.0.0.1"` | `"11.0.0.1,12.0.0.1"` | `"10.0.0.1"` | +| `"10.0.0.2,11.0.0.1,12.0.0.1"` | `"11.0.0.1,12.0.0.1"` | `"10.0.0.2"` | + +2. Group together a set of IPs (also behind a common set of reverse-proxies) so that they are considered the same source, and all contribute to the same rate-limit bucket. + +Example to group IPs together as same source: + +| `X-Forwarded-For` | excludedIPs | clientIP | +|--------------------------------|--------------|--------------| +| `"10.0.0.1,11.0.0.1,12.0.0.1"` | `"12.0.0.1"` | `"11.0.0.1"` | +| `"10.0.0.2,11.0.0.1,12.0.0.1"` | `"12.0.0.1"` | `"11.0.0.1"` | +| `"10.0.0.3,11.0.0.1,12.0.0.1"` | `"12.0.0.1"` | `"11.0.0.1"` | + +### store + +A Distributed Rate Limit middleware uses a persistent KV storage to store data. + +Refer to the [redis options](#configuration-options) to configure the Redis connection. + +Connection parameters to your [Redis](https://redis.io/ "Link to website of Redis") server are attached to your Middleware deployment. + +The following Redis modes are supported: + +- Single instance mode +- [Redis Cluster](https://redis.io/docs/management/scaling "Link to official Redis documentation about Redis Cluster mode") +- [Redis Sentinel](https://redis.io/docs/management/sentinel "Link to official Redis documentation about Redis Sentinel mode") + +For more information about Redis, we recommend the [official Redis documentation](https://redis.io/docs/ "Link to official Redis documentation"). + +!!! info + + If you use Redis in single instance mode or Redis Sentinel, you can configure the `database` field. + This value won't be taken into account if you use Redis Cluster (only database `0` is available). + + In this case, a warning is displayed, and the value is ignored. diff --git a/docs/content/reference/routing-configuration/http/middlewares/errorpages.md b/docs/content/reference/routing-configuration/http/middlewares/errorpages.md index 456badfbd..2855a5cbb 100644 --- a/docs/content/reference/routing-configuration/http/middlewares/errorpages.md +++ b/docs/content/reference/routing-configuration/http/middlewares/errorpages.md @@ -3,8 +3,6 @@ title: "Traefik Errors Documentation" description: "In Traefik Proxy, the Errors middleware returns custom pages according to configured ranges of HTTP Status codes. Read the technical documentation." --- -![Errors](../../../../assets/img/middleware/errorpages.png) - The `errors` middleware returns a custom page in lieu of the default, according to configured ranges of HTTP Status codes. ## Configuration Examples @@ -20,6 +18,9 @@ http: - "501" - "503" - "505-599" + statusRewrites: + "418": "404" + "502-504": "500" service: error-handler-service query: "/{status}.html" @@ -35,6 +36,10 @@ http: service = "error-handler-service" query = "/{status}.html" + [http.middlewares.test-errors.errors.statusRewrites] + "418" = "404" + "502-504" = "500" + [http.services] # ... definition of the error-handler-service ``` @@ -43,6 +48,8 @@ http: # Dynamic Custom Error Page for 5XX Status Code labels: - "traefik.http.middlewares.test-errors.errors.status=500,501,503,505-599" + - "traefik.http.middlewares.test-errors.errors.statusRewrites.418=404" + - "traefik.http.middlewares.test-errors.errors.statusRewrites.502-504=500" - "traefik.http.middlewares.test-errors.errors.service=error-handler-service" - "traefik.http.middlewares.test-errors.errors.query=/{status}.html" ``` @@ -53,6 +60,8 @@ labels: // ... "Tags": [ "traefik.http.middlewares.test-errors.errors.status=500,501,503,505-599", + "traefik.http.middlewares.test-errors.errors.statusRewrites.418=404", + "traefik.http.middlewares.test-errors.errors.statusRewrites.502-504=500", "traefik.http.middlewares.test-errors.errors.service=error-handler-service", "traefik.http.middlewares.test-errors.errors.query=/{status}.html" ] @@ -73,6 +82,9 @@ spec: - "501" - "503" - "505-599" + statusRewrites: + "418": "404" + "502-504": "500" query: /{status}.html service: name: error-handler-service @@ -83,9 +95,10 @@ spec: | Field | Description | Default | Required | |:-----------|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:--------|:---------| -| `status` | Defines which status or range of statuses should result in an error page.
The status code ranges are inclusive (`505-599` will trigger with every code between `505` and `599`, `505` and `599` included).
You can define either a status code as a number (`500`), as multiple comma-separated numbers (`500,502`), as ranges by separating two codes with a dash (`505-599`), or a combination of the two (`404,418,505-599`). | [] | No | -| `service` | The service that will serve the new requested error page.
More information [here](#service-and-hostheader). | "" | No | -| `query` | The URL for the error page (hosted by `service`).
More information [here](#query) | "" | No | +| `status` | Defines which status or range of statuses should result in an error page.
The status code ranges are inclusive (`505-599` will trigger with every code between `505` and `599`, `505` and `599` included).
You can define either a status code as a number (`500`), as multiple comma-separated numbers (`500,502`), as ranges by separating two codes with a dash (`505-599`), or a combination of the two (`404,418,505-599`). | [] | No | +| `statusRewrites` | An optional mapping of status codes to be rewritten. More information [here](#statusrewrites). | [] | No | +| `service` | The service that will serve the new requested error page.
More information [here](#service-and-hostheader). | "" | No | +| `query` | The URL for the error page (hosted by `service`).
More information [here](#query) | "" | No | ### service and HostHeader @@ -96,6 +109,15 @@ the [`passHostHeader`](../../../../routing/services/index.md#pass-host-header) o !!!info "Kubernetes" When specifying a service in Kubernetes (e.g., in an IngressRoute), you need to reference the `name`, `namespace`, and `port` of your Kubernetes Service resource. For example, `my-service.my-namespace@kubernetescrd` (or `my-service.my-namespace@kubernetescrd:80`) ensures that requests go to the correct service and port. +### statusRewrites + +`statusRewrites` is an optional mapping of status codes to be rewritten. + +For example, if a service returns a 418, you might want to rewrite it to a 404. +You can map individual status codes or even ranges to a different status code. + +The syntax for ranges follows the same rules as the `status` option. + ### query There are multiple variables that can be placed in the `query` option to insert values in the URL. @@ -104,5 +126,6 @@ The table below lists all the available variables and their associated values. | Variable | Value | |------------|------------------------------------------------------------------| -| `{status}` | The response status code. | -| `{url}` | The [escaped](https://pkg.go.dev/net/url#QueryEscape) request URL.| +| `{status}` | The response status code. | +| `{originalStatus}` | The original response status code, if it has been modified by the `statusRewrites` option. | +| `{url}` | The [escaped](https://pkg.go.dev/net/url#QueryEscape) request URL.| diff --git a/docs/content/reference/routing-configuration/http/middlewares/forwardauth.md b/docs/content/reference/routing-configuration/http/middlewares/forwardauth.md index 62a61bc7e..5df864c26 100644 --- a/docs/content/reference/routing-configuration/http/middlewares/forwardauth.md +++ b/docs/content/reference/routing-configuration/http/middlewares/forwardauth.md @@ -3,8 +3,6 @@ title: "Traefik ForwardAuth Documentation" description: "In Traefik Proxy, the HTTP ForwardAuth middleware delegates authentication to an external Service. Read the technical documentation." --- -![AuthForward](../../../../assets/img/middleware/authforward.png) - The `forwardAuth` middleware delegates authentication to an external service. If the service answers with a 2XX code, access is granted, and the original request is performed. Otherwise, the response from the authentication server is returned. @@ -57,23 +55,23 @@ spec: | Field | Description | Default | Required | |:-----------|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:--------|:---------| -| `address` | Authentication server address. | "" | Yes | -| `trustForwardHeader` | Trust all `X-Forwarded-*` headers. | false | No | -| `authResponseHeaders` | List of headers to copy from the authentication server response and set on forwarded request, replacing any existing conflicting headers. | [] | No | -| `authResponseHeadersRegex` | Regex to match by the headers to copy from the authentication server response and set on forwarded request, after stripping all headers that match the regex.
More information [here](#authresponseheadersregex). | "" | No | -| `authRequestHeaders` | List of the headers to copy from the request to the authentication server.
It allows filtering headers that should not be passed to the authentication server.
If not set or empty, then all request headers are passed. | [] | No | -| `addAuthCookiesToResponse` | List of cookies to copy from the authentication server to the response, replacing any existing conflicting cookie from the forwarded response.
Please note that all backend cookies matching the configured list will not be added to the response. | [] | No | -| `forwardBody` | Sets the `forwardBody` option to `true` to send the Body. As body is read inside Traefik before forwarding, this breaks streaming. | false | No | -| `maxBodySize` | Set the `maxBodySize` to limit the body size in bytes. If body is bigger than this, it returns a 401 (unauthorized). | -1 | No | -| `headerField` | Defines a header field to store the authenticated user. | "" | No | -| `preserveLocationHeader` | Defines whether to forward the Location header to the client as is or prefix it with the domain name of the authentication server. | false | No | -| `PreserveRequestMethod` | Defines whether to preserve the original request method while forwarding the request to the authentication server. | false | No | -| `tls.ca` | Sets the path to the certificate authority used for the secured connection to the authentication server, it defaults to the system bundle. | "" | No | -| `tls.cert` | Sets the path to the public certificate used for the secure connection to the authentication server. When using this option, setting the key option is required. | "" | No | -| `tls.key` | Sets the path to the private key used for the secure connection to the authentication server. When using this option, setting the `cert` option is required. | "" | No | -| `tls.caSecret` | Defines the secret that contains the certificate authority used for the secured connection to the authentication server, it defaults to the system bundle. **This option is only available for the Kubernetes CRD**. | | No | -| `tls.certSecret` | Defines the secret that contains both the private and public certificates used for the secure connection to the authentication server. **This option is only available for the Kubernetes CRD**. | | No | -| `tls.insecureSkipVerify` | During TLS connections, if this option is set to `true`, the authentication server will accept any certificate presented by the server regardless of the host names it covers. | false | No | +| `address` | Authentication server address. | "" | Yes | +| `trustForwardHeader` | Trust all `X-Forwarded-*` headers. | false | No | +| `authResponseHeaders` | List of headers to copy from the authentication server response and set on forwarded request, replacing any existing conflicting headers. | [] | No | +| `authResponseHeadersRegex` | Regex to match by the headers to copy from the authentication server response and set on forwarded request, after stripping all headers that match the regex.
More information [here](#authresponseheadersregex). | "" | No | +| `authRequestHeaders` | List of the headers to copy from the request to the authentication server.
It allows filtering headers that should not be passed to the authentication server.
If not set or empty, then all request headers are passed. | [] | No | +| `addAuthCookiesToResponse` | List of cookies to copy from the authentication server to the response, replacing any existing conflicting cookie from the forwarded response.
Please note that all backend cookies matching the configured list will not be added to the response. | [] | No | +| `forwardBody` | Sets the `forwardBody` option to `true` to send the Body. As body is read inside Traefik before forwarding, this breaks streaming. | false | No | +| `maxBodySize` | Set the `maxBodySize` to limit the body size in bytes. If body is bigger than this, it returns a 401 (unauthorized). | -1 | No | +| `headerField` | Defines a header field to store the authenticated user. | "" | No | +| `preserveLocationHeader` | Defines whether to forward the Location header to the client as is or prefix it with the domain name of the authentication server. | false | No | +| `preserveRequestMethod` | Defines whether to preserve the original request method while forwarding the request to the authentication server. | false | No | +| `tls.ca` | Sets the path to the certificate authority used for the secured connection to the authentication server, it defaults to the system bundle. | "" | No | +| `tls.cert` | Sets the path to the public certificate used for the secure connection to the authentication server. When using this option, setting the key option is required. | "" | No | +| `tls.key` | Sets the path to the private key used for the secure connection to the authentication server. When using this option, setting the `cert` option is required. | "" | No | +| `tls.caSecret` | Defines the secret that contains the certificate authority used for the secured connection to the authentication server, it defaults to the system bundle. **This option is only available for the Kubernetes CRD**. | | No | +| `tls.certSecret` | Defines the secret that contains both the private and public certificates used for the secure connection to the authentication server. **This option is only available for the Kubernetes CRD**. | | No | +| `tls.insecureSkipVerify` | During TLS connections, if this option is set to `true`, the authentication server will accept any certificate presented by the server regardless of the host names it covers. | false | No | ### authResponseHeadersRegex @@ -89,10 +87,10 @@ The following request properties are provided to the forward-auth target endpoin | Property | Forward-Request Header | |-------------------|------------------------| -| HTTP Method | X-Forwarded-Method | -| Protocol | X-Forwarded-Proto | -| Host | X-Forwarded-Host | -| Request URI | X-Forwarded-Uri | -| Source IP-Address | X-Forwarded-For | +| HTTP Method | `X-Forwarded-Method` | +| Protocol | `X-Forwarded-Proto` | +| Host | `X-Forwarded-Host` | +| Request URI | `X-Forwarded-Uri` | +| Source IP-Address | `X-Forwarded-For` | {!traefik-for-business-applications.md!} diff --git a/docs/content/reference/routing-configuration/http/middlewares/grpcweb.md b/docs/content/reference/routing-configuration/http/middlewares/grpcweb.md index 782266fdd..d478c0b60 100644 --- a/docs/content/reference/routing-configuration/http/middlewares/grpcweb.md +++ b/docs/content/reference/routing-configuration/http/middlewares/grpcweb.md @@ -56,7 +56,7 @@ spec: | Field | Description | Default | Required | |:-----------------------------|:------------------------------------------|:--------|:---------| -| `allowOrigins` | List of allowed origins.
A wildcard origin `*` can also be configured to match all requests.
More information [here](#alloworigins). | [] | No | +| `allowOrigins` | List of allowed origins.
A wildcard origin `*` can also be configured to match all requests.
More information [here](#alloworigins). | [] | No | ### allowOrigins diff --git a/docs/content/reference/routing-configuration/http/middlewares/headers.md b/docs/content/reference/routing-configuration/http/middlewares/headers.md index 2d419fc9f..5d468363e 100644 --- a/docs/content/reference/routing-configuration/http/middlewares/headers.md +++ b/docs/content/reference/routing-configuration/http/middlewares/headers.md @@ -3,19 +3,17 @@ title: "Traefik Headers Documentation" description: "In Traefik Proxy, the HTTP headers middleware manages the headers of requests and responses. Read the technical documentation." --- -![Headers](../../../../assets/img/middleware/headers.png) - The Headers middleware manages the headers of requests and responses. By default, the following headers are automatically added when proxying requests: | Property | HTTP Header | |---------------------------|----------------------------| -| Client's IP | X-Forwarded-For, X-Real-Ip | -| Host | X-Forwarded-Host | -| Port | X-Forwarded-Port | -| Protocol | X-Forwarded-Proto | -| Proxy Server's Hostname | X-Forwarded-Server | +| Client's IP | `X-Forwarded-For`, `X-Real-Ip` | +| Host | `X-Forwarded-Host` | +| Port | `X-Forwarded-Port` | +| Protocol | `X-Forwarded-Proto` | +| Proxy Server's Hostname | `X-Forwarded-Server` | ## Configuration Examples @@ -268,34 +266,34 @@ spec: | Field | Description | Default | Required | | ----------------------------- | ------------------------------------------------- | --------- | -------- | -| `customRequestHeaders` | Lists the header names and values for requests. | [] | No | -| `customResponseHeaders` | Lists the header names and values for responses. | [] | No | -| `accessControlAllowCredentials` | Indicates if the request can include user credentials.| false | No | -| `accessControlAllowHeaders` | Specifies allowed request header names. | [] | No | -| `accessControlAllowMethods` | Specifies allowed request methods. | [] | No | -| `accessControlAllowOriginList` | Specifies allowed origins. More information [here](#accesscontrolalloworiginlist) | [] | No | -| `accessControlAllowOriginListRegex` | Allows origins matching regex. More information [here](#accesscontrolalloworiginlistregex) | [] | No | -| `accessControlExposeHeaders` | Specifies which headers are safe to expose to the API of a CORS API specification. | [] | No | -| `accessControlMaxAge` | Time (in seconds) to cache preflight requests. | 0 | No | -| `addVaryHeader` | Used in conjunction with `accessControlAllowOriginList` to determine whether the `Vary` header should be added or modified to demonstrate that server responses can differ based on the value of the origin header. | false | No | -| `allowedHosts` | Lists allowed domain names. | [] | No | -| `hostsProxyHeaders` | Specifies header keys for proxied hostname. | [] | No | -| `sslProxyHeaders` | Defines a set of header keys with associated values that would indicate a valid HTTPS request. It can be useful when using other proxies (example: `"X-Forwarded-Proto": "https"`). | {} | No | -| `stsSeconds` | Max age for `Strict-Transport-Security` header. | 0 | No | -| `stsIncludeSubdomains` | If set to `true`, the `includeSubDomains` directive is appended to the `Strict-Transport-Security` header. | false | No | -| `stsPreload` | Adds preload flag to STS header. | false | No | -| `forceSTSHeader` | Adds STS header for HTTP connections. | false | No | -| `frameDeny` | Set `frameDeny` to `true` to add the `X-Frame-Options` header with the value of `DENY`. | false | No | -| `customFrameOptionsValue` | allows the `X-Frame-Options` header value to be set with a custom value. This overrides the `FrameDeny` option. | "" | No | -| `contentTypeNosniff` | Set `contentTypeNosniff` to true to add the `X-Content-Type-Options` header with the value `nosniff`. | false | No | -| `browserXssFilter` | Set `browserXssFilter` to true to add the `X-XSS-Protection` header with the value `1; mode=block`. | false | No | -| `customBrowserXSSValue` | allows the `X-XSS-Protection` header value to be set with a custom value. This overrides the `BrowserXssFilter` option. | false | No | -| `contentSecurityPolicy` | allows the `Content-Security-Policy` header value to be set with a custom value. | false | No | -| `contentSecurityPolicyReportOnly` | allows the `Content-Security-Policy-Report-Only` header value to be set with a custom value. | "" | No | -| `publicKey` | Implements HPKP for certificate pinning. | "" | No | -| `referrerPolicy` | Controls forwarding of `Referer` header. | "" | No | -| `permissionsPolicy` | allows sites to control browser features. | "" | No | -| `isDevelopment` | Set `true` when developing to mitigate the unwanted effects of the `AllowedHosts`, SSL, and STS options. Usually testing takes place using HTTP, not HTTPS, and on `localhost`, not your production domain. | false | No | +| `customRequestHeaders` | Lists the header names and values for requests. | [] | No | +| `customResponseHeaders` | Lists the header names and values for responses. | [] | No | +| `accessControlAllowCredentials` | Indicates if the request can include user credentials.| false | No | +| `accessControlAllowHeaders` | Specifies allowed request header names. | [] | No | +| `accessControlAllowMethods` | Specifies allowed request methods. | [] | No | +| `accessControlAllowOriginList` | Specifies allowed origins. More information [here](#accesscontrolalloworiginlist) | [] | No | +| `accessControlAllowOriginListRegex` | Allows origins matching regex. More information [here](#accesscontrolalloworiginlistregex) | [] | No | +| `accessControlExposeHeaders` | Specifies which headers are safe to expose to the API of a CORS API specification. | [] | No | +| `accessControlMaxAge` | Time (in seconds) to cache preflight requests. | 0 | No | +| `addVaryHeader` | Used in conjunction with `accessControlAllowOriginList` to determine whether the `Vary` header should be added or modified to demonstrate that server responses can differ based on the value of the origin header. | false | No | +| `allowedHosts` | Lists allowed domain names. | [] | No | +| `hostsProxyHeaders` | Specifies header keys for proxied hostname. | [] | No | +| `sslProxyHeaders` | Defines a set of header keys with associated values that would indicate a valid HTTPS request. It can be useful when using other proxies (example: `"X-Forwarded-Proto": "https"`). | {} | No | +| `stsSeconds` | Max age for `Strict-Transport-Security` header. | 0 | No | +| `stsIncludeSubdomains` | If set to `true`, the `includeSubDomains` directive is appended to the `Strict-Transport-Security` header. | false | No | +| `stsPreload` | Adds preload flag to STS header. | false | No | +| `forceSTSHeader` | Adds STS header for HTTP connections. | false | No | +| `frameDeny` | Set `frameDeny` to `true` to add the `X-Frame-Options` header with the value of `DENY`. | false | No | +| `customFrameOptionsValue` | allows the `X-Frame-Options` header value to be set with a custom value. This overrides the `FrameDeny` option. | "" | No | +| `contentTypeNosniff` | Set `contentTypeNosniff` to true to add the `X-Content-Type-Options` header with the value `nosniff`. | false | No | +| `browserXssFilter` | Set `browserXssFilter` to true to add the `X-XSS-Protection` header with the value `1; mode=block`. | false | No | +| `customBrowserXSSValue` | allows the `X-XSS-Protection` header value to be set with a custom value. This overrides the `BrowserXssFilter` option. | false | No | +| `contentSecurityPolicy` | allows the `Content-Security-Policy` header value to be set with a custom value. | false | No | +| `contentSecurityPolicyReportOnly` | allows the `Content-Security-Policy-Report-Only` header value to be set with a custom value. | "" | No | +| `publicKey` | Implements HPKP for certificate pinning. | "" | No | +| `referrerPolicy` | Controls forwarding of `Referer` header. | "" | No | +| `permissionsPolicy` | allows sites to control browser features. | "" | No | +| `isDevelopment` | Set `true` when developing to mitigate the unwanted effects of the `AllowedHosts`, SSL, and STS options. Usually testing takes place using HTTP, not HTTPS, and on `localhost`, not your production domain. | false | No | ### `accessControlAllowOriginList` diff --git a/docs/content/reference/routing-configuration/http/middlewares/hmac.md b/docs/content/reference/routing-configuration/http/middlewares/hmac.md new file mode 100644 index 000000000..2eabab306 --- /dev/null +++ b/docs/content/reference/routing-configuration/http/middlewares/hmac.md @@ -0,0 +1,207 @@ +--- +title: "HMAC" +description: "Traefik Hub API Gateway - The HMAC Middleware allows you secure your APIs using the HMAC mechanism." +--- + +!!! info "Traefik Hub Feature" + This middleware is available exclusively in [Traefik Hub](https://traefik.io/traefik-hub/). Learn more about [Traefik Hub's advanced features](https://doc.traefik.io/traefik-hub/api-gateway/intro). + +This middleware validates a digital signature computed using the content of an HTTP request and a shared secret that is +sent to the proxy using the `Authorization` or `Proxy-Authorization` header. + +It ensures: + +- **The identity of the sender (Authentication)**: If the signature is validated by the proxy, it means that the sender +actually owns the shared secret. As a consequence, the sender's identity is considered to be proven. +- **The integrity of the request**: As the signature is based on a subset of the HTTP request, it means that if the +signature is validated by the proxy, the request used to generate the signature has not been modified between the sender +and the proxy. This middleware also allows validating the content integrity using the +[Digest header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Digest). + +This middleware is based on the [HTTP Signature Draft](https://datatracker.ietf.org/doc/html/draft-cavage-http-signatures-12). + +--- + +## Configuration Example + +Below is an advanced configuration that enables the HMAC middleware, sets one secret, ensures that the digest sum of the +request body is validated and ensures that the given headers must be included in the computation of the signature of the +request. + +```yaml tab="Middleware HMAC" +apiVersion: traefik.io/v1alpha1 +kind: Middleware +metadata: + name: hmac-auth +spec: + plugin: + hmac: + keys: + - id: secret-key + key: secret + validateDigest: true + enforcedHeaders: + - (request-target) + - (created) + - (expires) + - host + +``` + +## Configuration Options + +| Field | Description | Default | Required | +|:------------------|:---------------------------------------------|:--------|:---------| +| `keys` | A static set of secret keys to be used by HMAC middleware. | | Yes | +| `validateDigest` | Determines whether the middleware should validate the digest sum of the request body. | true | No | +| `enforcedHeaders` | A set of headers that must be included in the computation of the signature of the request. | | No | + +## Authentication Mechanism + +The sender and proxy share a common secret identified by a `keyId`. How the sender gets the secret and keeps it safe is out of scope of this documentation. + +### Crafting the Authorization Header + +To authenticate a request, the sender must provide an `Authorization` or `ProxyAuthorization` header fulfilling the HMAC authentication scheme. + +This header carries a set of parameters: + +```bash +Authorization: Hmac keyId="secret-id-1",algorithm="hmac-sha256",headers="(request-target) (created) (expires) host x-example",signature="c29tZXNpZ25hdHVyZQ==",created="1584453022",expires="1584453032" +``` + +| Parameter | Description | Example | +|-------------|--------------------------------|------------------------------------| +| `keyId` | Identifier of the key being used by the sender to build the signature | `keyId="secret-key-1"` | +| `algorithm` | Algorithm used to generate the signature.
Supported values are `hmac-sha1`, `hmac-sha256`, `hmac-sha384` and `hmac-sha512`. | `algorithm="hmac-sha512"` | +| `headers` | List of headers to use in order to build the signature string.
Each item **must** be lowercase. | `headers="host content-type"` | +| `signature` | Digital Signature of the request. See [computing the signature](#computing-the-signature). | `signature="c29tZXNpZ25hdHVyZQ=="` | +| `created` | Unix timestamp of the signature creation. | `created="1574453022"` | +| `expires` | Unix timestamp of the signature expiration. | `expires="1574453022"` | + +!!! danger "Time sensitivity" + If the `created` timestamp is in the future or the `expires` timestamp is in the past, the middleware will refuse the request. + This behaviour makes using this middleware sensitive to clock skew between the client and the server. + + Make sure that your client and your server have their clocks synchronized. + +### Computing the Signature + +The signature is the base64-encoded value of the result of an HMAC signature algorithm computed with a `signature string` and the sender's `secret key`. + +For example: + +```bash +signature=base64(HMAC(signatureString, secret)) +``` + +### Crafting the Signature String + +A signed HTTP request needs to be tolerant of some trivial alterations during transmission as it goes through gateways, proxies and other entities. +As a consequence, signing the whole request is not an option as a single header modification could result in a not valid signature. + +To avoid this problem, this middleware builds the `signature string` from a subset of header values defined by the sender with the `headers` parameter of the authorization header. + +To build the signature string, the client **must** take the values of each header specified by the `headers` parameter **in the order they appear**, then apply the following logic to each of them: + +1. If the header is a special header, then evaluate its value according to [the special headers values section](#special-header-values) +2. If the header is not a special header, then append the lowercase header name followed with an ASCII colon `:`, an ASCII space \` \` and the header value. +If the header has multiple values then append those values separated by an ASCII comma `,` and an ASCII space \` \` +3. If value is not the last value then append an ASCII newline `\n`. The signature string MUST NOT include a trailing ASCII newline + +!!! warning + All headers values are trimmed from their spaces." + +#### Special Header Values + +By design, all the information of an HTTP request is not available through headers. However, it makes sense to secure the request using them. + +To allow this, the `headers` parameter accepts special header names that can be used. + +| Value | Description | Signature String Example | +| --------------------- | ------------------------------------------------------------- |------------------------- | +| `(request-target)` | Obtained by concatenating the lowercase `:method`, an ASCII space, and the `:path` pseudo-headers ([as specified in HTTP/2](https://tools.ietf.org/html/rfc7540#section-8.1.2.3)). | `(request-target): get /api/V1/resource?query=foo` | +| `(created)` | Value of the authorization header `created` parameter. | `(created): 1584453022` | +| `(expires)` | Value of the authorization header `expires` parameter. | `(expires): 1584453082` | + +Their evaluated value is obtained by appending the special header name with an ASCII colon `:` an ASCII space \` \` then the designated value. + +```bash tab="Example" +(created): 1929494939 +(request-target): get /foo/bar +``` + +#### Signature String Example + +Here is an example with the authorization header parameters set: + +- `headers="(request-target) (created) (expires) host x-example x-emptyheader cache-control"` +- `created="1584466921"` +- `expires="1584466931"` + +```bash tab="Request" +GET /foo HTTP/1.1 +Host: example.org +X-Example: Example header + with some whitespace. +X-EmptyHeader: +X-NotIncluded: always +Cache-Control: max-age=60 +Cache-Control: must-revalidate +``` + +```bash tab="Expected Signature String" +(request-target): get /foo +(created): 1584466921 +(expires): 1584466931 +host: example.org +x-example: Example header with some whitespace. +x-emptyheader: +cache-control: max-age=60, must-revalidate +``` + +#### Enforced Headers + +It is possible to configure the middleware to enforce a minimum set of headers to create the signature string. +This means that any request that does not have the enforced headers in its signature is systematically refused. + +This option also configures the headers list returned when [initiating the authentication](#initiating-the-authentication). + +It defaults to `(request-target) (created) (expires)`. + +!!! danger "Always enforce (created) and (expires)" + The `created` and `expires` header parameters protect against replay attacks. + To make sure that their value is not modified during transport, it is **highly recommended** to always include those parameters values in the signature using the `(created)` and `(expired)` special headers value. + + To do so, it is recommended to **always** configure the middleware to enforce `(created)` and `(expires)`. + +### Initiating the Authentication + +The authentication can be initiated by the proxy. A `401 Unauthorized` response is returned with a `WWW-Authenticate` header indicating to use the `Hmac` authentication scheme. + +```bash +WWW-Authenticate: Hmac headers="(request-target) (created) (expires) host x-example" +``` + +This header indicates that the sender needs to provide an Authorization header that fulfills the `Hmac` authentication schemes. +It also indicates a list of headers that have to be included in the signature using the `headers` parameter. + +!!! note "Enforced headers" + The list of headers carried in the `WWW-Authenticate` header is the list of [enforced headers](#enforced-headers) indicated in the middleware configuration. + +## Validating Request Body Integrity + +It is possible to make sure that the body of the incoming request has not been altered during transmission by including the [digest header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Digest) in the signature string. + +This middleware, by default, validates the digest sum of the body, if there is one. + +Only SHA-256 and SHA-512 checksums are supported for checksum computation. + +!!! warning "Potential CPU and memory usage" + + Validating the digest makes the middleware read the request body and computes a checksum for it. + As a consequence it can cause high memory and CPU usage on proxies. + + To disable this feature and only perform authentication, set the `validateDigest` option to `false` in the middleware configuration. + +{!traefik-for-business-applications.md!} diff --git a/docs/content/reference/routing-configuration/http/middlewares/inflightreq.md b/docs/content/reference/routing-configuration/http/middlewares/inflightreq.md index e8fc6a001..32d157162 100644 --- a/docs/content/reference/routing-configuration/http/middlewares/inflightreq.md +++ b/docs/content/reference/routing-configuration/http/middlewares/inflightreq.md @@ -54,12 +54,12 @@ spec: | Field | Description | Default | Required | |:-----------|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:--------|:---------| -| `amount` | The `amount` option 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). | 0 | No | -| `sourceCriterion.requestHost` | Whether to consider the request host as the source.
More information about `sourceCriterion`[here](#sourcecriterion). | false | No | -| `sourceCriterion.requestHeaderName` | Name of the header used to group incoming requests.
More information about `sourceCriterion`[here](#sourcecriterion). | "" | No | -| `sourceCriterion.ipStrategy.depth` | Depth position of the IP to select in the `X-Forwarded-For` header (starting from the right).
0 means no depth.
If greater than the total number of IPs in `X-Forwarded-For`, then the client IP is empty
If higher than 0, the `excludedIPs` options is not evaluated.
More information about [`sourceCriterion`](#sourcecriterion), [`ipStrategy](#ipstrategy), and [`depth`](#example-of-depth--x-forwarded-for) below. | 0 | No | -| `sourceCriterion.ipStrategy.excludedIPs` | Allows Traefik to scan the `X-Forwarded-For` header and select the first IP not in the list.
If `depth` is specified, `excludedIPs` is ignored.
More information about [`sourceCriterion`](#sourcecriterion), [`ipStrategy](#ipstrategy), and [`excludedIPs`](#example-of-excludedips--x-forwarded-for) below. | | No | -| `sourceCriterion.ipStrategy.ipv6Subnet` | If `ipv6Subnet` is provided and the selected IP is IPv6, the IP is transformed into the first IP of the subnet it belongs to.
More information about [`sourceCriterion`](#sourcecriterion), [`ipStrategy.ipv6Subnet`](#ipstrategyipv6subnet), and [`excludedIPs`](#example-of-excludedips--x-forwarded-for) below. | | No | +| `amount` | The `amount` option 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). | 0 | No | +| `sourceCriterion.requestHost` | Whether to consider the request host as the source.
More information about `sourceCriterion`[here](#sourcecriterion). | false | No | +| `sourceCriterion.requestHeaderName` | Name of the header used to group incoming requests.
More information about `sourceCriterion`[here](#sourcecriterion). | "" | No | +| `sourceCriterion.ipStrategy.depth` | Depth position of the IP to select in the `X-Forwarded-For` header (starting from the right).
0 means no depth.
If greater than the total number of IPs in `X-Forwarded-For`, then the client IP is empty
If higher than 0, the `excludedIPs` options is not evaluated.
More information about [`sourceCriterion`](#sourcecriterion), [`ipStrategy](#ipstrategy), and [`depth`](#example-of-depth--x-forwarded-for) below. | 0 | No | +| `sourceCriterion.ipStrategy.excludedIPs` | Allows Traefik to scan the `X-Forwarded-For` header and select the first IP not in the list.
If `depth` is specified, `excludedIPs` is ignored.
More information about [`sourceCriterion`](#sourcecriterion), [`ipStrategy](#ipstrategy), and [`excludedIPs`](#example-of-excludedips--x-forwarded-for) below. | | No | +| `sourceCriterion.ipStrategy.ipv6Subnet` | If `ipv6Subnet` is provided and the selected IP is IPv6, the IP is transformed into the first IP of the subnet it belongs to.
More information about [`sourceCriterion`](#sourcecriterion), [`ipStrategy.ipv6Subnet`](#ipstrategyipv6subnet), and [`excludedIPs`](#example-of-excludedips--x-forwarded-for) below. | | No | ### sourceCriterion @@ -90,26 +90,26 @@ If `ipv6Subnet` is provided, the IP is transformed in the following way. | IP | ipv6Subnet | clientIP | |---------------------------|--------------|-----------------------| -| `"::abcd:1111:2222:3333"` | `64` | `"::0:0:0:0"` | -| `"::abcd:1111:2222:3333"` | `80` | `"::abcd:0:0:0:0"` | -| `"::abcd:1111:2222:3333"` | `96` | `"::abcd:1111:0:0:0"` | +| `"::abcd:1111:2222:3333"` | `64` | `"::0:0:0:0"` | +| `"::abcd:1111:2222:3333"` | `80` | `"::abcd:0:0:0:0"` | +| `"::abcd:1111:2222:3333"` | `96` | `"::abcd:1111:0:0:0"` | -### Example of Depth & X-Forwarded-For +### Example of Depth & `X-Forwarded-For` If `depth` is set to 2, and the request `X-Forwarded-For` header is `"10.0.0.1,11.0.0.1,12.0.0.1,13.0.0.1"` then the "real" client IP is `"10.0.0.1"` (at depth 4) but the IP used as the criterion is `"12.0.0.1"` (`depth=2`). -| X-Forwarded-For | depth | clientIP | -|-----------------------------------------|---------|--------------| -| `"10.0.0.1,11.0.0.1,12.0.0.1,13.0.0.1"` | `1` | `"13.0.0.1"` | -| `"10.0.0.1,11.0.0.1,12.0.0.1,13.0.0.1"` | `3` | `"11.0.0.1"` | -| `"10.0.0.1,11.0.0.1,12.0.0.1,13.0.0.1"` | `5` | `""` | +| `X-Forwarded-For` | depth | clientIP | +|-----------------------------------------|-------|--------------| +| `"10.0.0.1,11.0.0.1,12.0.0.1,13.0.0.1"` | `1` | `"13.0.0.1"` | +| `"10.0.0.1,11.0.0.1,12.0.0.1,13.0.0.1"` | `3` | `"11.0.0.1"` | +| `"10.0.0.1,11.0.0.1,12.0.0.1,13.0.0.1"` | `5` | `""` | ### Example of ExcludedIPs & X-Forwarded-For -| X-Forwarded-For | excludedIPs | clientIP | +| `X-Forwarded-For` | excludedIPs | clientIP | |-----------------------------------------|-----------------------|--------------| -| `"10.0.0.1,11.0.0.1,12.0.0.1,13.0.0.1"` | `"12.0.0.1,13.0.0.1"` | `"11.0.0.1"` | -| `"10.0.0.1,11.0.0.1,12.0.0.1,13.0.0.1"` | `"15.0.0.1,13.0.0.1"` | `"12.0.0.1"` | -| `"10.0.0.1,11.0.0.1,12.0.0.1,13.0.0.1"` | `"10.0.0.1,13.0.0.1"` | `"12.0.0.1"` | -| `"10.0.0.1,11.0.0.1,12.0.0.1,13.0.0.1"` | `"15.0.0.1,16.0.0.1"` | `"13.0.0.1"` | -| `"10.0.0.1,11.0.0.1"` | `"10.0.0.1,11.0.0.1"` | `""` | +| `"10.0.0.1,11.0.0.1,12.0.0.1,13.0.0.1"` | `"12.0.0.1,13.0.0.1"` | `"11.0.0.1"` | +| `"10.0.0.1,11.0.0.1,12.0.0.1,13.0.0.1"` | `"15.0.0.1,13.0.0.1"` | `"12.0.0.1"` | +| `"10.0.0.1,11.0.0.1,12.0.0.1,13.0.0.1"` | `"10.0.0.1,13.0.0.1"` | `"12.0.0.1"` | +| `"10.0.0.1,11.0.0.1,12.0.0.1,13.0.0.1"` | `"15.0.0.1,16.0.0.1"` | `"13.0.0.1"` | +| `"10.0.0.1,11.0.0.1"` | `"10.0.0.1,11.0.0.1"` | `""` | diff --git a/docs/content/reference/routing-configuration/http/middlewares/ipallowlist.md b/docs/content/reference/routing-configuration/http/middlewares/ipallowlist.md index 8703b2a3a..a370f85c8 100644 --- a/docs/content/reference/routing-configuration/http/middlewares/ipallowlist.md +++ b/docs/content/reference/routing-configuration/http/middlewares/ipallowlist.md @@ -56,10 +56,10 @@ spec: | Field | Description | Default | Required | |:-----------|:------------------------------|:--------|:---------| -| `sourceRange` | List of allowed IPs (or ranges of allowed IPs by using CIDR notation). | | Yes | -| `ipStrategy.depth` | Depth position of the IP to select in the `X-Forwarded-For` header (starting from the right).
0 means no depth.
If greater than the total number of IPs in `X-Forwarded-For`, then the client IP is empty
If higher than 0, the `excludedIPs` options is not evaluated.
More information about [`ipStrategy](#ipstrategy), and [`depth`](#example-of-depth--x-forwarded-for) below. | 0 | No | -| `ipStrategy.excludedIPs` | Allows Traefik to scan the `X-Forwarded-For` header and select the first IP not in the list.
If `depth` is specified, `excludedIPs` is ignored.
More information about [`ipStrategy](#ipstrategy), and [`excludedIPs`](#example-of-excludedips--x-forwarded-for) below. | | No | -| `ipStrategy.ipv6Subnet` | If `ipv6Subnet` is provided and the selected IP is IPv6, the IP is transformed into the first IP of the subnet it belongs to.
More information about [`ipStrategy.ipv6Subnet`](#ipstrategyipv6subnet), and [`excludedIPs`](#example-of-excludedips--x-forwarded-for) below. | | No | +| `sourceRange` | List of allowed IPs (or ranges of allowed IPs by using CIDR notation). | | Yes | +| `ipStrategy.depth` | Depth position of the IP to select in the `X-Forwarded-For` header (starting from the right).
0 means no depth.
If greater than the total number of IPs in `X-Forwarded-For`, then the client IP is empty
If higher than 0, the `excludedIPs` options is not evaluated.
More information about [`ipStrategy](#ipstrategy), and [`depth`](#example-of-depth--x-forwarded-for) below. | 0 | No | +| `ipStrategy.excludedIPs` | Allows Traefik to scan the `X-Forwarded-For` header and select the first IP not in the list.
If `depth` is specified, `excludedIPs` is ignored.
More information about [`ipStrategy](#ipstrategy), and [`excludedIPs`](#example-of-excludedips--x-forwarded-for) below. | | No | +| `ipStrategy.ipv6Subnet` | If `ipv6Subnet` is provided and the selected IP is IPv6, the IP is transformed into the first IP of the subnet it belongs to.
More information about [`ipStrategy.ipv6Subnet`](#ipstrategyipv6subnet), and [`excludedIPs`](#example-of-excludedips--x-forwarded-for) below. | | No | ### ipStrategy @@ -95,26 +95,26 @@ If `ipv6Subnet` is provided, the IP is transformed in the following way. | IP | ipv6Subnet | clientIP | |---------------------------|--------------|-----------------------| -| `"::abcd:1111:2222:3333"` | `64` | `"::0:0:0:0"` | -| `"::abcd:1111:2222:3333"` | `80` | `"::abcd:0:0:0:0"` | -| `"::abcd:1111:2222:3333"` | `96` | `"::abcd:1111:0:0:0"` | +| `"::abcd:1111:2222:3333"` | `64` | `"::0:0:0:0"` | +| `"::abcd:1111:2222:3333"` | `80` | `"::abcd:0:0:0:0"` | +| `"::abcd:1111:2222:3333"` | `96` | `"::abcd:1111:0:0:0"` | -### Example of Depth & X-Forwarded-For +### Example of Depth & `X-Forwarded-For` If `depth` is set to 2, and the request `X-Forwarded-For` header is `"10.0.0.1,11.0.0.1,12.0.0.1,13.0.0.1"` then the "real" client IP is `"10.0.0.1"` (at depth 4) but the IP used as the criterion is `"12.0.0.1"` (`depth=2`). -| X-Forwarded-For | depth | clientIP | +| `X-Forwarded-For` | depth | clientIP | |-----------------------------------------|---------|--------------| -| `"10.0.0.1,11.0.0.1,12.0.0.1,13.0.0.1"` | `1` | `"13.0.0.1"` | -| `"10.0.0.1,11.0.0.1,12.0.0.1,13.0.0.1"` | `3` | `"11.0.0.1"` | -| `"10.0.0.1,11.0.0.1,12.0.0.1,13.0.0.1"` | `5` | `""` | +| `"10.0.0.1,11.0.0.1,12.0.0.1,13.0.0.1"` | `1` | `"13.0.0.1"` | +| `"10.0.0.1,11.0.0.1,12.0.0.1,13.0.0.1"` | `3` | `"11.0.0.1"` | +| `"10.0.0.1,11.0.0.1,12.0.0.1,13.0.0.1"` | `5` | `""` | -### Example of ExcludedIPs & X-Forwarded-For +### Example of ExcludedIPs & `X-Forwarded-For` -| X-Forwarded-For | excludedIPs | clientIP | +| `X-Forwarded-For` | excludedIPs | clientIP | |-----------------------------------------|-----------------------|--------------| -| `"10.0.0.1,11.0.0.1,12.0.0.1,13.0.0.1"` | `"12.0.0.1,13.0.0.1"` | `"11.0.0.1"` | -| `"10.0.0.1,11.0.0.1,12.0.0.1,13.0.0.1"` | `"15.0.0.1,13.0.0.1"` | `"12.0.0.1"` | -| `"10.0.0.1,11.0.0.1,12.0.0.1,13.0.0.1"` | `"10.0.0.1,13.0.0.1"` | `"12.0.0.1"` | -| `"10.0.0.1,11.0.0.1,12.0.0.1,13.0.0.1"` | `"15.0.0.1,16.0.0.1"` | `"13.0.0.1"` | -| `"10.0.0.1,11.0.0.1"` | `"10.0.0.1,11.0.0.1"` | `""` | +| `"10.0.0.1,11.0.0.1,12.0.0.1,13.0.0.1"` | `"12.0.0.1,13.0.0.1"` | `"11.0.0.1"` | +| `"10.0.0.1,11.0.0.1,12.0.0.1,13.0.0.1"` | `"15.0.0.1,13.0.0.1"` | `"12.0.0.1"` | +| `"10.0.0.1,11.0.0.1,12.0.0.1,13.0.0.1"` | `"10.0.0.1,13.0.0.1"` | `"12.0.0.1"` | +| `"10.0.0.1,11.0.0.1,12.0.0.1,13.0.0.1"` | `"15.0.0.1,16.0.0.1"` | `"13.0.0.1"` | +| `"10.0.0.1,11.0.0.1"` | `"10.0.0.1,11.0.0.1"` | `""` | diff --git a/docs/content/reference/routing-configuration/http/middlewares/jwt.md b/docs/content/reference/routing-configuration/http/middlewares/jwt.md new file mode 100644 index 000000000..2aebcdf86 --- /dev/null +++ b/docs/content/reference/routing-configuration/http/middlewares/jwt.md @@ -0,0 +1,233 @@ +--- +title: 'JWT Authentication' +description: 'Traefik Hub API Gateway - The JWT Authentication middleware verifies that a valid JWT token is provided in the Authorization header.' +--- + +!!! info "Traefik Hub Feature" + This middleware is available exclusively in [Traefik Hub](https://traefik.io/traefik-hub/). Learn more about [Traefik Hub's advanced features](https://doc.traefik.io/traefik-hub/api-gateway/intro). + +The JWT middleware verifies that a valid JWT token is provided in the `Authorization` header (`Authorization: Bearer `). +If the token can't be passed as an `Authorization` header, it can be given as form data or as a query parameter. +See the `tokenKey` option for more information. + +With no specific configuration, a JWT middleware only validates the signature of a JWT and checks the `nbf`, `exp` and `iat` standard claims (if they are present). +Custom claim validation can be configured with [Custom Claims Validation](#claims). + +--- + +## Configuration Example + +```yaml tab="Middleware JWT" +apiVersion: traefik.io/v1alpha1 +kind: Middleware +metadata: + name: test-jwt +spec: + plugin: + jwt: + signingSecret: my-secret + forwardHeaders: + Group: grp + Expires-At: exp + claims: Equals(`grp`, `admin`) +``` + +## Configuration Options + +| Field | Description | Default | Required | +|:----------------|:------------------------------------------------|:--------|:---------| +| `signingSecret` | Defines the secret used for signing the JWT certificates.
It is then used by the middleware to verify incoming requests.
At least one of `signingSecret`, `publicKey`, `jwksFile` or `jwksUrl` options must be set. (More information [here](#signingsecret)) | "" | No | +| `signingSecretBase64Encoded` | Defines whether the `signingSecret` is base64-encoded.
If set to `true`, the `signingSecret` is base64-decoded before being used. | false | No | +| `publicKey` | Defines the public key used to verify secret signature in incoming requests.
In that case, users should sign their token using a private key corresponding to the configured public key.
At least one of `signingSecret`, `publicKey`, `jwksFile` or `jwksUrl` options must be set. | "" | No | +| `jwksFile` | Defines a set of [JWK](https://tools.ietf.org/html/rfc7517) to be used to verify the signature of JWTs.
The option can either be a path to a file mounted on the API Gateway or directly the content of a JWK set file.
At least one of `signingSecret`, `publicKey`, `jwksFile` or `jwksUrl` options must be set. (More information [here](#jwksfile)) | "" | No | +| `jwksUrl` | Defines the URL of the host serving a [JWK](https://tools.ietf.org/html/rfc7517) set.
The keys are cached if the HTTP Cache Control allows for caching.
At least one of `signingSecret`, `publicKey`, `jwksFile` or `jwksUrl` options must be set.
(More information [here](#jwksurl)) | "" | No | +| `forwardAuthorization` | Defines whether the authorization header will be forwarded or stripped from a request after it has been approved by the middleware. | false | No | +| `tokenKey` | Defines the name of the query and form data parameter used for passing the JWT, for applications that can't pass it in the `Authorization` header.
The middleware always looks in the `Authorization` header first, even with this option enabled.
This option should only be enabled if the JWT cannot be passed as an Authorization header, as it is not recommended by the [RFC](https://www.rfc-editor.org/rfc/rfc6750#section-2). | "" | No | +| `claims` | Defines the claims to validate in order to authorize the request.
The `claims` option can only be used with JWT-formatted token. (More information [here](#claims)) | "" | No | +| `usernameClaim` | Defines the claim that will be evaluated to populate the `clientusername` in the access logs.
The `usernameClaim` option can only be used with JWT-formatted token.| "" | No | +| `forwardHeaders` | Defines the HTTP headers to add to requests and populates them with values extracted from the access token claims returned by the authorization server.
Claims to be forwarded that are not found in the JWT result in empty headers.
The `forwardHeaders` option can only be used with JWT-formatted token. | [] | No | +| `clientConfig.tls.ca` | PEM-encoded certificate bundle or a URN referencing a secret containing the certificate bundle used to establish a TLS connection with the authorization server (More information [here](#clientconfig)) | "" | No | +| `clientConfig.tls.cert` | PEM-encoded certificate or a URN referencing a secret containing the certificate used to establish a TLS connection with the Vault server (More information [here](#clientconfig)) | "" | No | +| `clientConfig.tls.key` | PEM-encoded key or a URN referencing a secret containing the key used to establish a TLS connection with the Vault server. (More information [here](#clientconfig)) | "" | No | +| `clientConfig.tls.insecureSkipVerify` | Disables TLS certificate verification when communicating with the authorization server.
Useful for testing purposes but strongly discouraged for production. (More information [here](#clientconfig)) | "" | No | +| `clientConfig.timeoutSeconds` | Defines the time before giving up requests to the authorization server. | 5 | No | +| `clientConfig.maxRetries` | Defines the number of retries for requests to authorization server that fail. | 3 | No | + +### claims + +#### Syntax + +The following functions are supported in `claims`: + +| Function | Description | Example | +|-------------------|--------------------|-----------------| +| Equals | Validates the equality of the value in `key` with `value`. | Equals(\`grp\`, \`admin\`) | +| Prefix | Validates the value in `key` has the prefix of `value`. | Prefix(\`referrer\`, \`http://example.com\`) | +| Contains (string) | Validates the value in `key` contains `value`. | Contains(\`referrer\`, \`/foo/\`) | +| Contains (array) | Validates the `key` array contains the `value`. | Contains(\`areas\`, \`home\`) | +| SplitContains | Validates the value in `key` contains the `value` once split by the separator. | SplitContains(\`scope\`, \` \`, \`writer\`) | +| OneOf | Validates the `key` array contains one of the `values`. | OneOf(\`areas\`, \`office\`, \`lab\`) | + +All functions can be joined by boolean operands. The supported operands are: + +| Operand | Description | Example | +|---------|--------------------|-----------------| +| && | Compares two functions and returns true only if both evaluate to true. | Equals(\`grp\`, \`admin\`) && Equals(\`active\`, \`true\`) | +| \|\| | Compares two functions and returns true if either evaluate to true. | Equals(\`grp\`, \`admin\`) \|\| Equals(\`active\`, \`true\`) | +| ! | Returns false if the function is true, otherwise returns true. | !Equals(\`grp\`, \`testers\`) | + +All examples will return true for the following data structure: + +```json tab="JSON" +{ + "active": true, + "grp": "admin", + "scope": "reader writer deploy", + "referrer": "http://example.com/foo/bar", + "areas": [ + "office", + "home" + ] +} +``` + +#### Nested Claims + +Nested claims are supported by using a `.` between keys. For example: + +```bash tab="Key" +user.name +``` + +```json tab="Claims" +{ + "active": true, + "grp": "admin", + "scope": "reader writer deploy", + "referrer": "http://example.com/foo/bar", + "areas": [ + "office", + "home" + ], + "user" { + "name": "John Snow", + "status": "undead" + } +} +``` + +```bash tab="Result" +John Snow +``` + +!!! note "Handling keys that contain a '.'" + +If the `key` contains a dot, the dot can be escaped using `\.` + +!!! note "Handling a key that contains a '\'" + +If the `key` contains a `\`, it needs to be doubled `\\`. + +### clientConfig + +Defines the configuration used to connect the API Gateway to a Third Party Software such as an Identity Provider. + +#### `clientConfig.tls` + +##### Storing secret values in Kubernetes secrets + +When configuring the `tls.ca`, `tls.cert`, `tls.key`, it is possible to reference Kubernetes secrets defined in the same namespace as the Middleware. +The reference to a Kubernetes secret takes the form of a URN: + +```text +urn:k8s:secret:[name]:[valueKey] +``` + +```yaml tab="Middleware JWT" +apiVersion: traefik.io/v1alpha1 +kind: Middleware +metadata: + name: test-jwt +spec: + plugin: + jwt: + clientConfig: + tls: + ca: "urn:k8s:secret:tls:ca" + cert: "urn:k8s:secret:tls:cert" + key: "urn:k8s:secret:tls:key" + insecureSkipVerify: true +``` + +```yaml tab="Kubernetes TLS Secret" +apiVersion: v1 +kind: Secret +metadata: + name: tls +stringData: + ca: |- + -----BEGIN CERTIFICATE----- + MIIB9TCCAWACAQAwgbgxGTAXBgNVBAoMEFF1b1ZhZGlzIExpbWl0ZWQxHDAaBgNV + BAsME0RvY3VtZW50IERlcGFydG1lbnQxOTA3BgNVBAMMMFdoeSBhcmUgeW91IGRl + Y29kaW5nIG1lPyAgVGhpcyBpcyBvbmx5IGEgdGVzdCEhITERMA8GA1UEBwwISGFt + aWx0b24xETAPBgNVBAgMCFBlbWJyb2tlMQswCQYDVQQGEwJCTTEPMA0GCSqGSIb3 + DQEJARYAMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCJ9WRanG/fUvcfKiGl + EL4aRLjGt537mZ28UU9/3eiJeJznNSOuNLnF+hmabAu7H0LT4K7EdqfF+XUZW/2j + RKRYcvOUDGF9A7OjW7UfKk1In3+6QDCi7X34RE161jqoaJjrm/T18TOKcgkkhRzE + apQnIDm0Ea/HVzX/PiSOGuertwIDAQABMAsGCSqGSIb3DQEBBQOBgQBzMJdAV4QP + Awel8LzGx5uMOshezF/KfP67wJ93UW+N7zXY6AwPgoLj4Kjw+WtU684JL8Dtr9FX + ozakE+8p06BpxegR4BR3FMHf6p+0jQxUEAkAyb/mVgm66TyghDGC6/YkiKoZptXQ + 98TwDIK/39WEB/V607As+KoYazQG8drorw== + -----END CERTIFICATE----- + cert: |- + -----BEGIN CERTIFICATE----- + MIIB9TCCAWACAQAwgbgxGTAXBgNVBAoMEFF1b1ZhZGlzIExpbWl0ZWQxHDAaBgNV + BAsME0RvY3VtZW50IERlcGFydG1lbnQxOTA3BgNVBAMMMFdoeSBhcmUgeW91IGRl + Y29kaW5nIG1lPyAgVGhpcyBpcyBvbmx5IGEgdGVzdCEhITERMA8GA1UEBwwISGFt + aWx0b24xETAPBgNVBAgMCFBlbWJyb2tlMQswCQYDVQQGEwJCTTEPMA0GCSqGSIb3 + DQEJARYAMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCJ9WRanG/fUvcfKiGl + EL4aRLjGt537mZ28UU9/3eiJeJznNSOuNLnF+hmabAu7H0LT4K7EdqfF+XUZW/2j + RKRYcvOUDGF9A7OjW7UfKk1In3+6QDCi7X34RE161jqoaJjrm/T18TOKcgkkhRzE + apQnIDm0Ea/HVzX/PiSOGuertwIDAQABMAsGCSqGSIb3DQEBBQOBgQBzMJdAV4QP + Awel8LzGx5uMOshezF/KfP67wJ93UW+N7zXY6AwPgoLj4Kjw+WtU684JL8Dtr9FX + ozakE+8p06BpxegR4BR3FMHf6p+0jQxUEAkAyb/mVgm66TyghDGC6/YkiKoZptXQ + 98TwDIK/39WEB/V607As+KoYazQG8drorw== + -----END CERTIFICATE----- + key: |- + -----BEGIN EC PRIVATE KEY----- + MHcCAQEEIC8CsJ/B115S+JtR1/l3ZQwKA3XdXt9zLqusF1VXc/KloAoGCCqGSM49 + AwEHoUQDQgAEpwUmRIZHFt8CdDHYm1ikScCScd2q6QVYXxJu+G3fQZ78ScGtN7fu + KXMnQqVjXVRAr8qUY8yipVKuMCepnPXScQ== + -----END EC PRIVATE KEY----- +``` + +### jwksFile + +#### JWT Header Key ID + +If the JWT header contains a `kid` header, the middleware expects to find a JWK. +If a JWK cannot be found, it returns a `401 Unauthorized` error. + +### jwksUrl + +#### JWT Header Key ID + +If the JWT header contains a `kid` header, the middleware expects to find a JWK. +If a JWK cannot be found, it returns a `401 Unauthorized` error. + +#### JWT Issuer Claim + +If `jwksUrl` is set to a path and the `iss` property is missing in the JWT it's trying to verify, the middleware returns a `401 Unauthorized` error. + +### signingSecret + +#### Storing secret values in Kubernetes secrets + +When configuring the `signingSecret`, it is possible to reference a Kubernetes secret defined in the same namespace as the Middleware. +The reference to a Kubernetes secret takes the form of a URN: + +```text +urn:k8s:secret:[name]:[valueKey] +``` + +{!traefik-for-business-applications.md!} diff --git a/docs/content/reference/routing-configuration/http/middlewares/ldap.md b/docs/content/reference/routing-configuration/http/middlewares/ldap.md new file mode 100644 index 000000000..9a0efc6f2 --- /dev/null +++ b/docs/content/reference/routing-configuration/http/middlewares/ldap.md @@ -0,0 +1,105 @@ +--- +title: 'LDAP Authentication' +description: 'Traefik Hub API Gateway - The LDAP Authentication middleware secures your applications by delegating the authentication to an external LDAP server.' +--- + +!!! info "Traefik Hub Feature" + This middleware is available exclusively in [Traefik Hub](https://traefik.io/traefik-hub/). Learn more about [Traefik Hub's advanced features](https://doc.traefik.io/traefik-hub/api-gateway/intro). + +The LDAP Authentication middleware secures your applications by delegating the authentication to an external LDAP server. + +The LDAP middleware will look for user credentials in the `Authorization` header of each request. Credentials must be encoded with the following format: `base64(username:password)`. + +--- + +## Configuration Examples + +```yaml tab="Basic usage" +apiVersion: traefik.io/v1alpha1 +kind: Middleware +metadata: + name: test-ldap-auth + namespace: apps +spec: + plugin: + ldap: + url: ldap://ldap.example.org:636 + baseDN: dc=example,dc=org +``` + +```yaml tab="Basic usage with bind need" +apiVersion: traefik.io/v1alpha1 +kind: Middleware +metadata: + name: test-ldap-auth + namespace: apps +spec: + plugin: + ldap: + url: ldap://ldap.example.org:636 + baseDN: dc=example,dc=org + bindDN: cn=binding_user,dc=example,dc=org + bindPassword: "urn:k8s:secret:my-secret:bindpassword" +``` + +```yaml tab="Enabling search, bind & WWW-Authenticate header" +apiVersion: traefik.io/v1alpha1 +kind: Middleware +metadata: + name: test-ldap-auth + namespace: apps +spec: + plugin: + ldap: + url: ldap://ldap.example.org:636 + baseDN: dc=example,dc=org + searchFilter: (&(objectClass=inetOrgPerson)(gidNumber=500)(uid=%s)) + forwardUsername: true + forwardUsernameHeader: Custom-Username-Header-Name + wwwAuthenticateHeader: true + wwwAuthenticateHeaderRealm: traefikee +``` + +## Configuration Options + +| Field | Description | Default | Required | +|:------|:------------|:--------|:---------| +| `url` | LDAP server URL. Either the `ldaps` or `ldap` protocol and end with a port (ex: `ldaps://ldap.example.org:636`). | "" | Yes | +| `startTLS` | Enable [`StartTLS`](https://tools.ietf.org/html/rfc4511#section-4.14) request when initializing the connection with the LDAP server. | false | No | +| `certificateAuthority` | PEM-encoded certificate to use to establish a connection with the LDAP server if the connection uses TLS but that the certificate was signed by a custom Certificate Authority. | "" | No | +| `insecureSkipVerify` | Allow proceeding and operating even for server TLS connections otherwise considered insecure. | false | No | +| `bindDN` | Domain name to bind to in order to authenticate to the LDAP server when running on search mode.
Leaving this empty with search mode means binds are anonymous, which is rarely expected behavior.
Not used when running in [bind mode](#bind-mode-vs-search-mode). | "" | No | +| `bindPassword` | Password for the `bindDN` used in search mode to authenticate with the LDAP server. More information [here](#bindpassword) | "" | No | +| `connPool` | Pool of connections to the LDAP server (to minimize the impact on the performance). | None | No | +| `connPool.size` | Number of connections managed by the pool can be customized with the `size` property. | 10 | No | +| `connPool.burst` | Ephemeral connections that are opened when the pool is already full. Once the number of connection exceeds `size` + `burst`, a `Too Many Connections` error is returned. | 5 | No | +| `connPool.ttl` | Pooled connections are still meant to be short-lived, so they are closed after roughly one minute by default. This behavior can be modified with the `ttl` property. | 60s | No | +| `baseDN` | Base domain name that should be used for bind and search queries. | "" | Yes | +| `attribute` | The attribute used to bind a user. Bind queries use this pattern: `=,`, where the username is extracted from the request header. | cn | Yes | +| `forwardUsername` | Forward the username in a specific header, defined using the `forwardUsernameHeader` option. | "" | No | +| `forwardUsernameHeader` | Name of the header to put the username in when forwarding it. This is not used if the `forwardUsername` option is set to `false`. | Username | Yes | +| `forwardAuthorization` | Enable to forward the authorization header from the request after it has been approved by the middleware. | false | Yes | +| `searchFilter` | If not empty, the middleware will run in [search mode](#bind-mode-vs-search-mode), filtering search results with the given query.
Filter queries can use the `%s` placeholder that is replaced by the username provided in the `Authorization` header of the request (for example: `(&(objectClass=inetOrgPerson)(gidNumber=500)(uid=%s))`). | "" | No | +| `wwwAuthenticateHeader` | Allow setting a `WWW-Authenticate` header in the `401 Unauthorized` response. See [the WWW-Authenticate header documentation](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/WWW-Authenticate) for more information.
The `realm` directive of the `WWW-Authenticate` header can be customized with the `wwwAuthenticateHeaderRealm` option. | false | No | +| `wwwAuthenticateHeaderRealm` | Realm name to set in the `WWW-Authenticate` header. This option is ineffective unless the `wwwAuthenticateHeader` option is set to `true`. | "" | No | + +### bindPassword + +When setting the `bindPassword`, you can reference a Kubernetes secret from the same namespace as the Middleware using the following URN format: + +```text +urn:k8s:secret:[secretName]:[key] +``` + +## Bind Mode vs Search Mode + +If no filter is specified in its configuration, the middleware runs in the default bind mode, +meaning it tries to make a bind request to the LDAP server with the credentials provided in the request headers. +If the bind succeeds, the middleware forwards the request, otherwise it returns a `401 Unauthorized` status code. + +If a filter query is specified in the middleware configuration, and the Authentication Source referenced has a `bindDN` +and a `bindPassword`, then the middleware runs in search mode. In this mode, a search query with the given filter is +issued to the LDAP server before trying to bind. If result of this search returns only 1 record, +it tries to issue a bind request with this record, otherwise it aborts a `401 Unauthorized` status code. + +{!traefik-for-business-applications.md!} diff --git a/docs/content/reference/routing-configuration/http/middlewares/oauth2-client-credentials.md b/docs/content/reference/routing-configuration/http/middlewares/oauth2-client-credentials.md new file mode 100644 index 000000000..80eecea88 --- /dev/null +++ b/docs/content/reference/routing-configuration/http/middlewares/oauth2-client-credentials.md @@ -0,0 +1,255 @@ +--- +title: 'OAuth 2.0 Client Credentials Authentication' +description: 'Traefik Hub API Gateway - The OAuth 2.0 Client Credentials Authentication middleware secures your applications using the client credentials flow' +--- + +!!! info "Traefik Hub Feature" + This middleware is available exclusively in [Traefik Hub](https://traefik.io/traefik-hub/). Learn more about [Traefik Hub's advanced features](https://doc.traefik.io/traefik-hub/api-gateway/intro). + +The OAuth 2.0 Client Credentials Authentication middleware allows Traefik Hub to secure routes using the OAuth 2.0 Client Credentials flow as described in the [RFC 6749](https://www.rfc-editor.org/rfc/rfc6749.html#section-4.4). +Access tokens can be cached using an external KV store. + +The OAuth Client Credentials Authentication middleware allows using Redis (or Sentinel) as persistent KV store to authorization access tokens +while they are valid. This reduces latency and the number of calls made to the authorization server. + +--- + +## Configuration Example + +```yaml tab="Middleware OAuth Client Credentials" +apiVersion: traefik.io/v1alpha1 +kind: Middleware +metadata: + name: test-client-creds +spec: + plugin: + oAuthClientCredentials: + url: https://tenant.auth0.com/oauth/token + clientID: urn:k8s:my-secret:my-secret:clientID + clientSecret: urn:k8s:my-secret:my-secret:clientSecret + audience: https://api.example.com + forwardHeaders: + Group: grp + Expires-At: exp + claims: Equals(`grp`, `admin`) +``` + +```yaml tab="Kubernetes Secret" +apiVersion: v1 +kind: Secret +type: Opaque +metadata: + name: my-secret +stringData: + clientID: my-oauth-client-name + clientSecret: mypasswd +``` + +## Configuration Options + +| Field | Description | Default | Required | +|:------|:--------------------------------------------------------------------------------------------|:--------|:---------| +| `audience` | Defines the audience configured in your authorization server.
The audience value is the base address of the resource being accessed, for example: https://api.example.com. | "" | Yes | +| `claims` | Defines the claims to validate in order to authorize the request.
The `claims` option can only be used with JWT-formatted token. (More information [here](#claims)) | "" | No | +| `clientConfig.tls.ca` | PEM-encoded certificate bundle or a URN referencing a secret containing the certificate bundle used to establish a TLS connection with the authorization server (More information [here](#clientconfig)) | "" | No | +| `clientConfig.tls.cert` | PEM-encoded certificate or a URN referencing a secret containing the certificate used to establish a TLS connection with the Vault server (More information [here](#clientconfig)) | "" | No | +| `clientConfig.tls.key` | PEM-encoded key or a URN referencing a secret containing the key used to establish a TLS connection with the Vault server. (More information [here](#clientconfig)) | "" | No | +| `clientConfig.tls.insecureSkipVerify` | Disables TLS certificate verification when communicating with the authorization server.
Useful for testing purposes but strongly discouraged for production. (More information [here](#clientconfig)) | "" | No | +| `clientConfig.timeoutSeconds` | Defines the time before giving up requests to the authorization server. | 5 | No | +| `clientConfig.maxRetries` | Defines the number of retries for requests to authorization server that fail. | 3 | No | +| `clientID` | Defines the unique client identifier for an account on the OpenID Connect provider, must be set when the `clientSecret` option is set.
More information [here](#storing-secret-values-in-kubernetes-secrets). | "" | Yes | +| `clientSecret` | Defines the unique client secret for an account on the OpenID Connect provider, must be set when the `clientID` option is set.
More information [here](#storing-secret-values-in-kubernetes-secrets). | "" | Yes | +| `forwardHeaders` | Defines the HTTP headers to add to requests and populates them with values extracted from the access token claims returned by the authorization server.
Claims to be forwarded that are not found in the JWT result in empty headers.
The `forwardHeaders` option can only be used with JWT-formatted token. | [] | No | +| `store.keyPrefix` | Defines the prefix of the key for the entries that store the sessions. | "" | No | +| `store.redis.endpoints` | Endpoints of the Redis instances to connect to (example: `redis.traefik-hub.svc.cluster.local:6379`) | "" | Yes | +| `store.redis.username` | The username Traefik Hub will use to connect to Redis | "" | No | +| `store.redis.password` | The password Traefik Hub will use to connect to Redis | "" | No | +| `store.redis.database` | The database Traefik Hub will use to sore information (default: `0`) | "" | No | +| `store.redis.cluster` | Enable Redis Cluster | "" | No | +| `store.redis.tls.caBundle` | Custom CA bundle | "" | No | +| `store.redis.tls.cert` | TLS certificate | "" | No | +| `store.redis.tls.key` | TLS | "" | No | +| `store.redis.tls.insecureSkipVerify` | Allow skipping the TLS verification | "" | No | +| `store.redis.sentinel.masterSet` | Name of the set of main nodes to use for main selection. Required when using Sentinel. | "" | No | +| `store.redis.sentinel.username` | Username to use for sentinel authentication (can be different from `username`) | "" | No | +| `store.redis.sentinel.password` | Password to use for sentinel authentication (can be different from `password`) | "" | No | +| `url` | Defines the authorization server URL (for example: `https://tenant.auth0.com/oauth/token`). | "" | Yes | +| `usernameClaim` | Defines the claim that will be evaluated to populate the `clientusername` in the access logs.
The `usernameClaim` option can only be used with JWT-formatted token.| "" | No | + +### Storing secret values in Kubernetes secrets + +It is possible to reference Kubernetes secrets defined in the same namespace as the Middleware. +The reference to a Kubernetes secret takes the form of a URN: + +```text +urn:k8s:secret:[name]:[valueKey] +``` + +### claims + +#### Syntax + +The following functions are supported in `claims`: + +| Function | Description | Example | +|-------------------|--------------------|-----------------| +| Equals | Validates the equality of the value in `key` with `value`. | Equals(\`grp\`, \`admin\`) | +| Prefix | Validates the value in `key` has the prefix of `value`. | Prefix(\`referrer\`, \`http://example.com\`) | +| Contains (string) | Validates the value in `key` contains `value`. | Contains(\`referrer\`, \`/foo/\`) | +| Contains (array) | Validates the `key` array contains the `value`. | Contains(\`areas\`, \`home\`) | +| SplitContains | Validates the value in `key` contains the `value` once split by the separator. | SplitContains(\`scope\`, \` \`, \`writer\`) | +| OneOf | Validates the `key` array contains one of the `values`. | OneOf(\`areas\`, \`office\`, \`lab\`) | + +All functions can be joined by boolean operands. The supported operands are: + +| Operand | Description | Example | +|---------|--------------------|-----------------| +| && | Compares two functions and returns true only if both evaluate to true. | Equals(\`grp\`, \`admin\`) && Equals(\`active\`, \`true\`) | +| \|\| | Compares two functions and returns true if either evaluate to true. | Equals(\`grp\`, \`admin\`) \|\| Equals(\`active\`, \`true\`) | +| ! | Returns false if the function is true, otherwise returns true. | !Equals(\`grp\`, \`testers\`) | + +All examples will return true for the following data structure: + +```json tab="JSON" +{ + "active": true, + "grp": "admin", + "scope": "reader writer deploy", + "referrer": "http://example.com/foo/bar", + "areas": [ + "office", + "home" + ] +} +``` + +#### Nested Claims + +Nested claims are supported by using a `.` between keys. For example: + +```bash tab="Key" +user.name +``` + +```json tab="Claims" +{ + "active": true, + "grp": "admin", + "scope": "reader writer deploy", + "referrer": "http://example.com/foo/bar", + "areas": [ + "office", + "home" + ], + "user" { + "name": "John Snow", + "status": "undead" + } +} +``` + +```bash tab="Result" +John Snow +``` + +!!! note "Handling keys that contain a '.'" + +If the `key` contains a dot, the dot can be escaped using `\.` + +!!! note "Handling a key that contains a '\'" + +If the `key` contains a `\`, it needs to be doubled `\\`. + +### clientConfig + +Defines the configuration used to connect the API Gateway to a Third Party Software such as an Identity Provider. + +#### `clientConfig.tls` + +##### Storing secret values in Kubernetes secrets + +When configuring the `tls.ca`, `tls.cert`, `tls.key`, it is possible to reference Kubernetes secrets defined in the same namespace as the Middleware. +The reference to a Kubernetes secret takes the form of a URN: + +```text +urn:k8s:secret:[name]:[valueKey] +``` + +```yaml tab="Middleware JWT" +apiVersion: traefik.io/v1alpha1 +kind: Middleware +metadata: + name: test-client-creds +spec: + plugin: + oAuthClientCredentials: + clientConfig: + tls: + ca: "urn:k8s:secret:tls:ca" + cert: "urn:k8s:secret:tls:cert" + key: "urn:k8s:secret:tls:key" + insecureSkipVerify: true +``` + +```yaml tab="Kubernetes TLS Secret" +apiVersion: v1 +kind: Secret +metadata: + name: tls +stringData: + ca: |- + -----BEGIN CERTIFICATE----- + MIIB9TCCAWACAQAwgbgxGTAXBgNVBAoMEFF1b1ZhZGlzIExpbWl0ZWQxHDAaBgNV + BAsME0RvY3VtZW50IERlcGFydG1lbnQxOTA3BgNVBAMMMFdoeSBhcmUgeW91IGRl + Y29kaW5nIG1lPyAgVGhpcyBpcyBvbmx5IGEgdGVzdCEhITERMA8GA1UEBwwISGFt + aWx0b24xETAPBgNVBAgMCFBlbWJyb2tlMQswCQYDVQQGEwJCTTEPMA0GCSqGSIb3 + DQEJARYAMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCJ9WRanG/fUvcfKiGl + EL4aRLjGt537mZ28UU9/3eiJeJznNSOuNLnF+hmabAu7H0LT4K7EdqfF+XUZW/2j + RKRYcvOUDGF9A7OjW7UfKk1In3+6QDCi7X34RE161jqoaJjrm/T18TOKcgkkhRzE + apQnIDm0Ea/HVzX/PiSOGuertwIDAQABMAsGCSqGSIb3DQEBBQOBgQBzMJdAV4QP + Awel8LzGx5uMOshezF/KfP67wJ93UW+N7zXY6AwPgoLj4Kjw+WtU684JL8Dtr9FX + ozakE+8p06BpxegR4BR3FMHf6p+0jQxUEAkAyb/mVgm66TyghDGC6/YkiKoZptXQ + 98TwDIK/39WEB/V607As+KoYazQG8drorw== + -----END CERTIFICATE----- + cert: |- + -----BEGIN CERTIFICATE----- + MIIB9TCCAWACAQAwgbgxGTAXBgNVBAoMEFF1b1ZhZGlzIExpbWl0ZWQxHDAaBgNV + BAsME0RvY3VtZW50IERlcGFydG1lbnQxOTA3BgNVBAMMMFdoeSBhcmUgeW91IGRl + Y29kaW5nIG1lPyAgVGhpcyBpcyBvbmx5IGEgdGVzdCEhITERMA8GA1UEBwwISGFt + aWx0b24xETAPBgNVBAgMCFBlbWJyb2tlMQswCQYDVQQGEwJCTTEPMA0GCSqGSIb3 + DQEJARYAMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCJ9WRanG/fUvcfKiGl + EL4aRLjGt537mZ28UU9/3eiJeJznNSOuNLnF+hmabAu7H0LT4K7EdqfF+XUZW/2j + RKRYcvOUDGF9A7OjW7UfKk1In3+6QDCi7X34RE161jqoaJjrm/T18TOKcgkkhRzE + apQnIDm0Ea/HVzX/PiSOGuertwIDAQABMAsGCSqGSIb3DQEBBQOBgQBzMJdAV4QP + Awel8LzGx5uMOshezF/KfP67wJ93UW+N7zXY6AwPgoLj4Kjw+WtU684JL8Dtr9FX + ozakE+8p06BpxegR4BR3FMHf6p+0jQxUEAkAyb/mVgm66TyghDGC6/YkiKoZptXQ + 98TwDIK/39WEB/V607As+KoYazQG8drorw== + -----END CERTIFICATE----- + key: |- + -----BEGIN EC PRIVATE KEY----- + MHcCAQEEIC8CsJ/B115S+JtR1/l3ZQwKA3XdXt9zLqusF1VXc/KloAoGCCqGSM49 + AwEHoUQDQgAEpwUmRIZHFt8CdDHYm1ikScCScd2q6QVYXxJu+G3fQZ78ScGtN7fu + KXMnQqVjXVRAr8qUY8yipVKuMCepnPXScQ== + -----END EC PRIVATE KEY----- +``` + +### store.redis + +Connection parameters to your [Redis](https://redis.io/ "Link to website of Redis") server are attached to your Middleware deployment. + +The following Redis modes are supported: + +- Single instance mode +- [Redis Cluster](https://redis.io/docs/management/scaling "Link to official Redis documentation about Redis Cluster mode") +- [Redis Sentinel](https://redis.io/docs/management/sentinel "Link to official Redis documentation about Redis Sentinel mode") + +!!! info + + If you use Redis in single instance mode or Redis Sentinel, you can configure the `database` field. + This value won't be taken into account if you use Redis Cluster (only database `0` is available). + + In this case, a warning is displayed, and the value is ignored. + +For more information about Redis, we recommend the [official Redis documentation](https://redis.io/docs/ "Link to official Redis documentation"). + +{!traefik-for-business-applications.md!} diff --git a/docs/content/reference/routing-configuration/http/middlewares/oauth2-token-introspection.md b/docs/content/reference/routing-configuration/http/middlewares/oauth2-token-introspection.md new file mode 100644 index 000000000..8633abcbb --- /dev/null +++ b/docs/content/reference/routing-configuration/http/middlewares/oauth2-token-introspection.md @@ -0,0 +1,209 @@ +--- +title: 'OAuth 2.0 Token Introspection Authentication' +description: 'Traefik Hub API Gateway - OAuth 2.0 Token Introspection allows to retrieve metadata about an access token from an OAuth 2.0 server.' +--- + +!!! info "Traefik Hub Feature" + This middleware is available exclusively in [Traefik Hub](https://traefik.io/traefik-hub/). Learn more about [Traefik Hub's advanced features](https://doc.traefik.io/traefik-hub/api-gateway/intro). + +OAuth 2.0 Token Introspection allows Traefik Hub to retrieve metadata about an access token from an OAuth 2.0 server with the Token Introspection extension. + +The metadata can be used to restrict the access to applications. For more information please refer to the [RFC](https://tools.ietf.org/html/rfc7662). + +--- + +## Configuration Example + +```yaml tab="Middleware OAuth Token Introspection" +apiVersion: traefik.io/v1alpha1 +kind: Middleware +metadata: + name: test-oauth-intro +spec: + plugin: + oAuthIntrospection: + tokenSource: + header: Authorization + headerAuthScheme: Bearer + clientConfig: + url: "https://YOUR-KEYCLOAK-ADDRESS/realms/YOUR-REALM/protocol/openid-connect/token/introspect" + headers: + Authorization: Basic ZXhhbXBsZTpleGFtcGxl # echo -n "$CLIENT_ID:$CLIENT_SECRET" | base64 + tokenTypeHint: access_token + forwardHeaders: + Group: grp + Expires-At: exp + claims: Equals(`grp`, `admin`) +``` + +## Configuration Options + +| Field | Description | Default | Required | +|:------|:------------|:--------|:---------| +| `claims` | Defines the claims to validate in order to authorize the request.
The `claims` option can only be used with JWT-formatted token. (More information [here](#claims)) | "" | No | +| `clientConfig.url` | Defines the introspection endpoint URL. It must include the scheme and path. | "" | Yes | +| `clientConfig.headers` | Defines the headers to send in every introspection request. Values can be plain strings or a valid [Go template](https://pkg.go.dev/text/template).
Currently, a variable of type [`Request`](https://pkg.go.dev/net/http#Request) corresponding to the request being introspected is accessible in templates. | "" | No | +| `clientConfig.tokenTypeHint` | Defines the type of token being introspected, sent as a hint to the introspection server.
Please refer to the [official documentation](https://tools.ietf.org/html/rfc7662) for more details. | "" | No | +| `clientConfig.tls.ca` | PEM-encoded certificate bundle or a URN referencing a secret containing the certificate bundle used to establish a TLS connection with the authorization server (More information [here](#clientconfig)) | "" | No | +| `clientConfig.tls.cert` | PEM-encoded certificate or a URN referencing a secret containing the certificate used to establish a TLS connection with the Vault server (More information [here](#clientconfig)) | "" | No | +| `clientConfig.tls.key` | PEM-encoded key or a URN referencing a secret containing the key used to establish a TLS connection with the Vault server. (More information [here](#clientconfig)) | "" | No | +| `clientConfig.tls.insecureSkipVerify` | Disables TLS certificate verification when communicating with the authorization server.
Useful for testing purposes but strongly discouraged for production. (More information [here](#clientconfig)) | "" | No | +| `clientConfig.timeoutSeconds` | Defines the time before giving up requests to the authorization server. | 5 | No | +| `clientConfig.maxRetries` | Defines the number of retries for requests to authorization server that fail. | 3 | No | +| `forwardAuthorization` | Defines whether the authorization header will be forwarded or stripped from a request after it has been approved by the middleware. | false | No | +| `forwardHeaders` | Defines the HTTP headers to add to requests and populates them with values extracted from the access token claims returned by the authorization server.
Claims to be forwarded that are not found in the JWT result in empty headers.
The `forwardHeaders` option can only be used with JWT-formatted token. | [] | No | +| `tokenSource.header` | Defines the header name containing the secret sent by the client.
At least one `tokenSource`option must be set.| "" | No | +| `tokenSource.headerAuthScheme` | Defines the scheme when using `Authorization` as header name.
Check out the `Authorization` header [documentation](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Authorization#syntax).
At least one `tokenSource`option must be set. | "" | No | +| `tokenSource.query` | Defines the query parameter name containing the secret sent by the client.
At least one `tokenSource`option must be set.| "" | No | +| `tokenSource.cookie` | Defines the cookie name containing the secret sent by the client.
At least one `tokenSource`option must be set.| "" | No | +| `usernameClaim` | Defines the claim that will be evaluated to populate the `clientusername` in the access logs.
The `usernameClaim` option can only be used with JWT-formatted token.| "" | No | + +### claims + +#### Syntax + +The following functions are supported in `claims`: + +| Function | Description | Example | +|-------------------|--------------------|-----------------| +| Equals | Validates the equality of the value in `key` with `value`. | Equals(\`grp\`, \`admin\`) | +| Prefix | Validates the value in `key` has the prefix of `value`. | Prefix(\`referrer\`, \`http://example.com\`) | +| Contains (string) | Validates the value in `key` contains `value`. | Contains(\`referrer\`, \`/foo/\`) | +| Contains (array) | Validates the `key` array contains the `value`. | Contains(\`areas\`, \`home\`) | +| SplitContains | Validates the value in `key` contains the `value` once split by the separator. | SplitContains(\`scope\`, \` \`, \`writer\`) | +| OneOf | Validates the `key` array contains one of the `values`. | OneOf(\`areas\`, \`office\`, \`lab\`) | + +All functions can be joined by boolean operands. The supported operands are: + +| Operand | Description | Example | +|---------|--------------------|-----------------| +| && | Compares two functions and returns true only if both evaluate to true. | Equals(\`grp\`, \`admin\`) && Equals(\`active\`, \`true\`) | +| \|\| | Compares two functions and returns true if either evaluate to true. | Equals(\`grp\`, \`admin\`) \|\| Equals(\`active\`, \`true\`) | +| ! | Returns false if the function is true, otherwise returns true. | !Equals(\`grp\`, \`testers\`) | + +All examples will return true for the following data structure: + +```json tab="JSON" +{ + "active": true, + "grp": "admin", + "scope": "reader writer deploy", + "referrer": "http://example.com/foo/bar", + "areas": [ + "office", + "home" + ] +} +``` + +#### Nested Claims + +Nested claims are supported by using a `.` between keys. For example: + +```bash tab="Key" +user.name +``` + +```json tab="Claims" +{ + "active": true, + "grp": "admin", + "scope": "reader writer deploy", + "referrer": "http://example.com/foo/bar", + "areas": [ + "office", + "home" + ], + "user" { + "name": "John Snow", + "status": "undead" + } +} +``` + +```bash tab="Result" +John Snow +``` + +!!! note "Handling keys that contain a '.'" + +If the `key` contains a dot, the dot can be escaped using `\.` + +!!! note "Handling a key that contains a '\'" + +If the `key` contains a `\`, it needs to be doubled `\\`. + +### clientConfig + +Defines the configuration used to connect the API Gateway to a Third Party Software such as an Identity Provider. + +#### `clientConfig.tls` + +##### Storing secret values in Kubernetes secrets + +When configuring the `tls.ca`, `tls.cert`, `tls.key`, it is possible to reference Kubernetes secrets defined in the same namespace as the Middleware. +The reference to a Kubernetes secret takes the form of a URN: + +```text +urn:k8s:secret:[name]:[valueKey] +``` + +```yaml tab="Middleware JWT" +apiVersion: traefik.io/v1alpha1 +kind: Middleware +metadata: + name: test-oauth-intro +spec: + plugin: + oAuthIntrospection: + clientConfig: + tls: + ca: "urn:k8s:secret:tls:ca" + cert: "urn:k8s:secret:tls:cert" + key: "urn:k8s:secret:tls:key" + insecureSkipVerify: true +``` + +```yaml tab="Kubernetes TLS Secret" +apiVersion: v1 +kind: Secret +metadata: + name: tls +stringData: + ca: |- + -----BEGIN CERTIFICATE----- + MIIB9TCCAWACAQAwgbgxGTAXBgNVBAoMEFF1b1ZhZGlzIExpbWl0ZWQxHDAaBgNV + BAsME0RvY3VtZW50IERlcGFydG1lbnQxOTA3BgNVBAMMMFdoeSBhcmUgeW91IGRl + Y29kaW5nIG1lPyAgVGhpcyBpcyBvbmx5IGEgdGVzdCEhITERMA8GA1UEBwwISGFt + aWx0b24xETAPBgNVBAgMCFBlbWJyb2tlMQswCQYDVQQGEwJCTTEPMA0GCSqGSIb3 + DQEJARYAMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCJ9WRanG/fUvcfKiGl + EL4aRLjGt537mZ28UU9/3eiJeJznNSOuNLnF+hmabAu7H0LT4K7EdqfF+XUZW/2j + RKRYcvOUDGF9A7OjW7UfKk1In3+6QDCi7X34RE161jqoaJjrm/T18TOKcgkkhRzE + apQnIDm0Ea/HVzX/PiSOGuertwIDAQABMAsGCSqGSIb3DQEBBQOBgQBzMJdAV4QP + Awel8LzGx5uMOshezF/KfP67wJ93UW+N7zXY6AwPgoLj4Kjw+WtU684JL8Dtr9FX + ozakE+8p06BpxegR4BR3FMHf6p+0jQxUEAkAyb/mVgm66TyghDGC6/YkiKoZptXQ + 98TwDIK/39WEB/V607As+KoYazQG8drorw== + -----END CERTIFICATE----- + cert: |- + -----BEGIN CERTIFICATE----- + MIIB9TCCAWACAQAwgbgxGTAXBgNVBAoMEFF1b1ZhZGlzIExpbWl0ZWQxHDAaBgNV + BAsME0RvY3VtZW50IERlcGFydG1lbnQxOTA3BgNVBAMMMFdoeSBhcmUgeW91IGRl + Y29kaW5nIG1lPyAgVGhpcyBpcyBvbmx5IGEgdGVzdCEhITERMA8GA1UEBwwISGFt + aWx0b24xETAPBgNVBAgMCFBlbWJyb2tlMQswCQYDVQQGEwJCTTEPMA0GCSqGSIb3 + DQEJARYAMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCJ9WRanG/fUvcfKiGl + EL4aRLjGt537mZ28UU9/3eiJeJznNSOuNLnF+hmabAu7H0LT4K7EdqfF+XUZW/2j + RKRYcvOUDGF9A7OjW7UfKk1In3+6QDCi7X34RE161jqoaJjrm/T18TOKcgkkhRzE + apQnIDm0Ea/HVzX/PiSOGuertwIDAQABMAsGCSqGSIb3DQEBBQOBgQBzMJdAV4QP + Awel8LzGx5uMOshezF/KfP67wJ93UW+N7zXY6AwPgoLj4Kjw+WtU684JL8Dtr9FX + ozakE+8p06BpxegR4BR3FMHf6p+0jQxUEAkAyb/mVgm66TyghDGC6/YkiKoZptXQ + 98TwDIK/39WEB/V607As+KoYazQG8drorw== + -----END CERTIFICATE----- + key: |- + -----BEGIN EC PRIVATE KEY----- + MHcCAQEEIC8CsJ/B115S+JtR1/l3ZQwKA3XdXt9zLqusF1VXc/KloAoGCCqGSM49 + AwEHoUQDQgAEpwUmRIZHFt8CdDHYm1ikScCScd2q6QVYXxJu+G3fQZ78ScGtN7fu + KXMnQqVjXVRAr8qUY8yipVKuMCepnPXScQ== + -----END EC PRIVATE KEY----- +``` + +{!traefik-for-business-applications.md!} diff --git a/docs/content/reference/routing-configuration/http/middlewares/oidc.md b/docs/content/reference/routing-configuration/http/middlewares/oidc.md new file mode 100644 index 000000000..120e79bf9 --- /dev/null +++ b/docs/content/reference/routing-configuration/http/middlewares/oidc.md @@ -0,0 +1,430 @@ +--- +title: 'OpenID Connect Authentication' +description: 'Traefik Hub API Gateway - The OIDC Authentication middleware secures your applications by delegating the authentication to an external provider.' +--- + +!!! info "Traefik Hub Feature" + This middleware is available exclusively in [Traefik Hub](https://traefik.io/traefik-hub/). Learn more about [Traefik Hub's advanced features](https://doc.traefik.io/traefik-hub/api-gateway/intro). + +The OIDC Authentication middleware secures your applications by delegating the authentication to an external provider + +--- + +## Configuration Example + +```yaml tab="Middleware OIDC" +apiVersion: traefik.io/v1alpha1 +kind: Middleware +metadata: + name: test-oidc + namespace: whoami +spec: + plugin: + oidc: + issuer: "https://tenant.auth0.com/realms/myrealm" + redirectUrl: "/callback" + clientID: "urn:k8s:secret:my-secret:clientId" + clientSecret: "urn:k8s:secret:my-secret:clientSecret" + session: + name: customsessioncookiename + sliding: false + refresh: false + expiry: 10 + sameSite: none + httpOnly: false + secure: true + stateCookie: + name: customstatecookiename + maxAge: 10 + sameSite: none + httpOnly: true + secure: true + forwardHeaders: + Group: grp + Expires-At: exp + claims: Equals(`grp`, `admin`) + csrf: {} +``` + +```yaml tab="Kubernetes Secret" +apiVersion: v1 +kind: Secret +metadata: + name: my-secret +stringData: + clientID: my-oidc-client-name + clientSecret: mysecret +``` + +## Configuration Options + +| Field | Description | Default | Required | +|:------|:------------|:--------|:---------| +| `issuer` | Defines the URL to the OpenID Connect provider (for example, `https://accounts.google.com`).
It should point to the server which provides the OpenID Connect configuration. | "" | Yes | +| `redirectUrl` | Defines the URL used by the OpenID Connect provider to redirect back to the middleware once the authorization is complete. (More information [here](#redirecturl)) | "" | Yes | +| `clientID` | Defines the unique client identifier for an account on the OpenID Connect provider, must be set when the `clientSecret` option is set. (More information [here](#clientid-clientsecret)) | "" | Yes | +| `clientSecret` | Defines the unique client secret for an account on the OpenID Connect provider, must be set when the `clientID` option is set. (More information [here](#clientid-clientsecret)) | "" | Yes | +| `claims` | Defines the claims to validate in order to authorize the request.
The `claims` option can only be used with JWT-formatted token. (More information [here](#claims)) | "" | No | +| `usernameClaim` | Defines the claim that will be evaluated to populate the `clientusername` in the access logs.
The `usernameClaim` option can only be used with JWT-formatted token.| "" | No | +| `forwardHeaders` | Defines the HTTP headers to add to requests and populates them with values extracted from the access token claims returned by the authorization server.
Claims to be forwarded that are not found in the JWT result in empty headers.
The `forwardHeaders` option can only be used with JWT-formatted token. | [] | No | +| `clientConfig.tls.ca` | PEM-encoded certificate bundle or a URN referencing a secret containing the certificate bundle used to establish a TLS connection with the authorization server (More information [here](#clientconfig)) | "" | No | +| `clientConfig.tls.cert` | PEM-encoded certificate or a URN referencing a secret containing the certificate used to establish a TLS connection with the Vault server (More information [here](#clientconfig)) | "" | No | +| `clientConfig.tls.key` | PEM-encoded key or a URN referencing a secret containing the key used to establish a TLS connection with the Vault server. (More information [here](#clientconfig)) | "" | No | +| `clientConfig.tls.insecureSkipVerify` | Disables TLS certificate verification when communicating with the authorization server.
Useful for testing purposes but strongly discouraged for production. (More information [here](#clientconfig)) | "" | No | +| `clientConfig.timeoutSeconds` | Defines the time before giving up requests to the authorization server. | 5 | No | +| `clientConfig.maxRetries` | Defines the number of retries for requests to authorization server that fail. | 3 | No | +| `pkce` | Defines the Proof Key for Code Exchange as described in [RFC 7636](https://datatracker.ietf.org/doc/html/rfc7636). | false | No | +| `discoveryParams` | A map of arbitrary query parameters to be added to the openid-configuration well-known URI during the discovery mechanism. | "" | No | +| `scopes` | The scopes to request. Must include `openid`. | openid | No | +| `authParams` | A map of the arbitrary query parameters to be passed to the Authentication Provider.
When a `prompt` key is set to an empty string in the AuthParams,the prompt parameter is not added to the OAuth2 authorization URL Which means the user won't be prompted for consent.| "" | No | +| `disableLogin` | Disables redirections to the authentication provider
This can be useful for protecting APIs where redirecting to a login page is undesirable. | false | No | +| `loginUrl` | Defines the URL used to start authorization when needed.
All other requests that are not already authorized will return a 401 Unauthorized. When left empty, all requests can start authorization.
It can be a path (`/login` for example), a host and a path (`example.com/login`) or a complete URL (`https://example.com/login`).
Only `http` and `https` schemes are supported.| "" | No | +| `logoutUrl` |Defines the URL on which the session should be deleted in order to log users out.
It can be a path (`/logout` for example), a host and a path (`example.com/logout`) or a complete URL (`https://example.com/logout`).
Only `http` and `https` schemes are supported.| "" | No | +| `postLoginRedirectUrl` |If set and used in conjunction with `loginUrl`, the middleware will redirect to this URL after successful login.
It can be a path (`/after/login` for example), a host and a path (`example.com/after/login`) or a complete URL (`https://example.com/after/login`).
Only `http` and `https` schemes are supported. | "" | No | +| `postLogoutRedirectUrl` | If set and used in conjunction with `logoutUrl`, the middleware will redirect to this URL after logout.
It can be a path (`/after/logout` for example), a host and a path (`example.com/after/logout`) or a complete URL (`https://example.com/after/logout`).
Only `http` and `https` schemes are supported. | "" | No | +| `backchannelLogoutUrl` | Defines the URL called by the OIDC provider when a user logs out (see https://openid.net/specs/openid-connect-rpinitiated-1_0.html#OpenID.BackChannel).
It can be a path (`/backchannel-logout` for example), a host and a path (`example.com/backchannel-logout`) or a complete URL (`https://example.com/backchannel-logout`).
Only `http` and `https` schemes are supported.
This feature is currently in an experimental state and has been tested exclusively with the Keycloak OIDC provider. | "" | No | +| `backchannelLogoutSessionsRequired` | This specifies whether the OIDC provider includes the sid (session ID) Claim in the Logout Token to identify the user session (see https://openid.net/specs/openid-connect-backchannel-1_0.html#BCRegistration).
If omitted, the default value is false.
This feature is currently in an experimental state and has been tested exclusively with the Keycloak OIDC provider. | false | No | +| `stateCookie.name` | Defines the name of the state cookie. |"`MIDDLEWARE_NAME`-state" | No | +| `stateCookie.path` | Defines the URL path that must exist in the requested URL in order to send the Cookie header.
The `%x2F` ('/') character is considered a directory separator, and subdirectories will match as well.
For example, if `stateCookie.path` is set to `/docs`, these paths will match: `/docs`,`/docs/web/`,`/docs/web/http`.| "/" | No | +| `stateCookie.domain` | Defines the hosts that are allowed to receive the cookie.
If specified, then subdomains are always included.
For example, if it is set to `example.com`, then cookies are included on subdomains like `api.example.com`. | "" | No | +| `stateCookie.maxAge` |Defines the number of seconds after which the state cookie should expire.
A zero or negative number will expire the cookie immediately. | 600 | No | +| `stateCookie.sameSite` | Informsbrowsers how they should handle the state cookie on cross-site requests.
Setting it to `lax` or `strict` can provide some protection against cross-site request forgery attacks ([CSRF](https://developer.mozilla.org/en-US/docs/Glossary/CSRF)).
More information [here](#samesite---accepted-values). | lax | No | +| `stateCookie.httpOnly` | Forbids JavaScript from accessing the cookie.
For example, through the `Document.cookie` property, the `XMLHttpRequest` API, or the `Request` API.
This mitigates attacks against cross-site scripting ([XSS](https://developer.mozilla.org/en-US/docs/Glossary/XSS)). | true | No | +| `stateCookie.secure` | Defines whether the state cookie is only sent to the server when a request is made with the `https` scheme. | false | No | +| `session.name` | The name of the session cookie. |"`MIDDLEWARE_NAME`-session"| No | +| `session.path` | Defines the URL path that must exist in the requested URL in order to send the Cookie header.
The `%x2F` ('/'') character is considered a directory separator, and subdirectories will match as well.
For example, if `stateCookie.path` is set to `/docs`, these paths will match: `/docs`,`/docs/web/`,`/docs/web/http`.| "/" | No | +| `session.domain` | Specifies the hosts that are allowed to receive the cookie. If specified, then subdomains are always included. If specified, then subdomains are always included.
For example, if it is set to `example.com`, then cookies are included on subdomains like `api.example.com`.| "" | No | +| `session.expiry` | Number of seconds after which the session should expire. A zero or negative number is **prohibited**. | 86400 (24h) | No | +| `session.sliding` | Forces the middleware to renew the session cookie each time an authenticated request is received. | true | No | +| `session.refresh` | Enables the access token refresh when it expires. | true | No | +| `session.sameSite` | Inform browsers how they should handle the session cookie on cross-site requests.
Setting it to `lax` or `strict` can provide some protection against cross-site request forgery attacks ([CSRF](https://developer.mozilla.org/en-US/docs/Glossary/CSRF)).
More information [here](#samesite---accepted-values). | lax | No | +| `session.httpOnly` | Forbids JavaScript from accessing the cookie.
For example, through the `Document.cookie` property, the `XMLHttpRequest` API, or the `Request` API.
This mitigates attacks against cross-site scripting ([XSS](https://developer.mozilla.org/en-US/docs/Glossary/XSS)). | true | No | +| `session.secure` | Defines whether the session cookie is only sent to the server when a request is made with the `https` scheme. | false | No | +| `session.store.redis.endpoints` | Endpoints of the Redis instances to connect to (example: `redis.traefik-hub.svc.cluster.local:6379`) | "" | Yes | +| `session.store.redis.username` | The username Traefik Hub will use to connect to Redis | "" | No | +| `session.store.redis.password` | The password Traefik Hub will use to connect to Redis | "" | No | +| `session.store.redis.database` | The database Traefik Hub will use to sore information (default: `0`) | "" | No | +| `session.store.redis.cluster` | Enable Redis Cluster | "" | No | +| `session.store.redis.tls.caBundle` | Custom CA bundle | "" | No | +| `session.store.redis.tls.cert` | TLS certificate | "" | No | +| `session.store.redis.tls.key` | TLS key | "" | No | +| `session.store.redis.tls.insecureSkipVerify` | Allow skipping the TLS verification | "" | No | +| `session.store.redis.sentinel.masterSet` | Name of the set of main nodes to use for main selection. Required when using Sentinel. | "" | No | +| `session.store.redis.sentinel.username` | Username to use for sentinel authentication (can be different from `username`) | "" | No | +| `session.store.redis.sentinel.password` | Password to use for sentinel authentication (can be different from `password`) | "" | No | +| `csrf` | When enabled, a CSRF cookie, named `traefikee-csrf-token`, is bound to the OIDC session to protect service from CSRF attacks.
It is based on the [Signed Double Submit Cookie](https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html#signed-double-submit-cookie) implementation as defined by the OWASP Foundation.
Moreinformation [here](#csrf). | "" | No | +| `csrf.secure` | Defines whether the CSRF cookie is only sent to the server when a request is made with the `https` scheme. | false | No | +| `csrf.headerName` | Defines the name of the header used to send the CSRF token value received previously in the CSRF cookie. | TraefikHub-Csrf-Token | No | + +### redirectUrl + +#### Add specific rule on the IngressRoute + +The URL informs the OpenID Connect provider how to return to the middleware. +If the router rule is accepting all paths on a domain, no extra work is needed. +If the router rule is specific about the paths allowed, the path set in this option should be included. + +```yaml tab="Defining specific rule for redirectUrl" +apiVersion: traefik.io/v1alpha1 +kind: IngressRoute +metadata: + name: whoami +spec: + entryPoints: + - web + - websecure + routes: + # Rules to match the loginUrl and redirectUrl can be added into + # your current router. + - match: Path(`/myapi`) || Path(`/login`) || Path(`/callback`) + kind: Rule + middlewares: + - name: test-oidc +``` + +This URL will not be passed to the upstream application, but rather handled by the middleware itself. +The chosen URL should therefore not conflict with any URLs needed by the upstream application. + +This URL sometimes needs to be set in the OpenID Connect Provider's configuration as well (like for Google Accounts for example). + +It can be the absolute URL, relative to the protocol (inherits the request protocol), or relative to the domain (inherits the request domain and protocol). +See the following examples. + +#### Inherit the Protocol and Domain from the Request and Uses the Redirecturl’s Path + +| Request URL | RedirectURL| Result | +|:------------|:-----------|:-------| +| `http://expl.co` | `/cback` | `http://expl.co/cback` | + +#### Inherit the Protocol from the Request and Uses the Redirecturl’s Domain and Path + +| Request URL | RedirectURL| Result | +|:------------|:-----------|:-------| +| `https://scur.co` | `expl.co/cback`| `https://expl.co/cback` | + +#### Replace the Request URL with the Redirect URL since It Is an Absolute URL + +| Request URL | RedirectURL| Result | +|:------------|:-----------|:-------| +| `https://scur.co` | `http://expl.co/cback` | `http://expl.co/cback` | + +!!! note "Supported Schemes" + + Only `http` and `https` schemes are supported. + +```yaml tab="Defining the redirectUrl" +apiVersion: traefik.io/v1alpha1 +kind: Middleware +metadata: + name: test-oidc +spec: + plugin: + oidc: + issuer: "https://tenant.auth0.com/realms/myrealm" + redirectUrl: "/callback" + clientID: my-oidc-client-name + clientSecret: mysecret +``` + +### clientID, clientSecret + +#### Storing secret values in Kubernetes secrets + +When configuring the `clientID` and the `clientSecret`, it is possible to reference Kubernetes secrets defined in the same namespace as the Middleware. +The reference to a Kubernetes secret takes the form of a URN: + +```text +urn:k8s:secret:[name]:[valueKey] +``` + +### claims + +#### Syntax + +The following functions are supported in `claims`: + +| Function | Description | Example | +|-------------------|--------------------|-----------------| +| Equals | Validates the equality of the value in `key` with `value`. | Equals(\`grp\`, \`admin\`) | +| Prefix | Validates the value in `key` has the prefix of `value`. | Prefix(\`referrer\`, \`http://example.com\`) | +| Contains (string) | Validates the value in `key` contains `value`. | Contains(\`referrer\`, \`/foo/\`) | +| Contains (array) | Validates the `key` array contains the `value`. | Contains(\`areas\`, \`home\`) | +| SplitContains | Validates the value in `key` contains the `value` once split by the separator. | SplitContains(\`scope\`, \` \`, \`writer\`) | +| OneOf | Validates the `key` array contains one of the `values`. | OneOf(\`areas\`, \`office\`, \`lab\`) | + +All functions can be joined by boolean operands. The supported operands are: + +| Operand | Description | Example | +|---------|--------------------|-----------------| +| && | Compares two functions and returns true only if both evaluate to true. | Equals(\`grp\`, \`admin\`) && Equals(\`active\`, \`true\`) | +| \|\| | Compares two functions and returns true if either evaluate to true. | Equals(\`grp\`, \`admin\`) \|\| Equals(\`active\`, \`true\`) | +| ! | Returns false if the function is true, otherwise returns true. | !Equals(\`grp\`, \`testers\`) | + +All examples will return true for the following data structure: + +```json tab="JSON" +{ + "active": true, + "grp": "admin", + "scope": "reader writer deploy", + "referrer": "http://example.com/foo/bar", + "areas": [ + "office", + "home" + ] +} +``` + +#### Nested Claims + +Nested claims are supported by using a `.` between keys. For example: + +```bash tab="Key" +user.name +``` + +```json tab="Claims" +{ + "active": true, + "grp": "admin", + "scope": "reader writer deploy", + "referrer": "http://example.com/foo/bar", + "areas": [ + "office", + "home" + ], + "user" { + "name": "John Snow", + "status": "undead" + } +} +``` + +```bash tab="Result" +John Snow +``` + +!!! note "Handling keys that contain a '.'" + +If the `key` contains a dot, the dot can be escaped using `\.` + +!!! note "Handling a key that contains a '\'" + +If the `key` contains a `\`, it needs to be doubled `\\`. + +!!! note "Access Token and ID Token claims" + + The first argument of the function, which represents the key to look for in the token claims, can be prefixed to specify which of the two kinds of token is inspected. + Possible prefix values are `id_token.` and `access_token.`. If no prefix is specified, it defaults to the ID token. + + | Example | Description | + | ----------------------------------------- | ------------------------------------------------------------------------------ | + | Equals(\`id_token.grp\`, \`admin\`) | Checks if the value of claim `grp` in the ID token is `admin`. | + | Prefix(\`access_token.referrer\`, \`http://example.com\`) | Checks if the value of claim `referrer` in the access token is prefixed by `http://example.com\`.| + | OneOf(\`areas\`, \`office\`, \`lab\`) | Checks if the value of claim `areas` in the ID token is `office` or `labs`. | + +### clientConfig + +Defines the configuration used to connect the API Gateway to a Third Party Software such as an Identity Provider. + +#### `clientConfig.tls` + +##### Storing secret values in Kubernetes secrets + +When configuring the `tls.ca`, `tls.cert`, `tls.key`, it is possible to reference Kubernetes secrets defined in the same namespace as the Middleware. +The reference to a Kubernetes secret takes the form of a URN: + +```text +urn:k8s:secret:[name]:[valueKey] +``` + +```yaml tab="Middleware JWT" +apiVersion: traefik.io/v1alpha1 +kind: Middleware +metadata: + name: test-oidc +spec: + plugin: + oidc: + clientConfig: + tls: + ca: "urn:k8s:secret:tls:ca" + cert: "urn:k8s:secret:tls:cert" + key: "urn:k8s:secret:tls:key" + insecureSkipVerify: true +``` + +```yaml tab="Kubernetes TLS Secret" +apiVersion: v1 +kind: Secret +metadata: + name: tls +stringData: + ca: |- + -----BEGIN CERTIFICATE----- + MIIB9TCCAWACAQAwgbgxGTAXBgNVBAoMEFF1b1ZhZGlzIExpbWl0ZWQxHDAaBgNV + BAsME0RvY3VtZW50IERlcGFydG1lbnQxOTA3BgNVBAMMMFdoeSBhcmUgeW91IGRl + Y29kaW5nIG1lPyAgVGhpcyBpcyBvbmx5IGEgdGVzdCEhITERMA8GA1UEBwwISGFt + aWx0b24xETAPBgNVBAgMCFBlbWJyb2tlMQswCQYDVQQGEwJCTTEPMA0GCSqGSIb3 + DQEJARYAMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCJ9WRanG/fUvcfKiGl + EL4aRLjGt537mZ28UU9/3eiJeJznNSOuNLnF+hmabAu7H0LT4K7EdqfF+XUZW/2j + RKRYcvOUDGF9A7OjW7UfKk1In3+6QDCi7X34RE161jqoaJjrm/T18TOKcgkkhRzE + apQnIDm0Ea/HVzX/PiSOGuertwIDAQABMAsGCSqGSIb3DQEBBQOBgQBzMJdAV4QP + Awel8LzGx5uMOshezF/KfP67wJ93UW+N7zXY6AwPgoLj4Kjw+WtU684JL8Dtr9FX + ozakE+8p06BpxegR4BR3FMHf6p+0jQxUEAkAyb/mVgm66TyghDGC6/YkiKoZptXQ + 98TwDIK/39WEB/V607As+KoYazQG8drorw== + -----END CERTIFICATE----- + cert: |- + -----BEGIN CERTIFICATE----- + MIIB9TCCAWACAQAwgbgxGTAXBgNVBAoMEFF1b1ZhZGlzIExpbWl0ZWQxHDAaBgNV + BAsME0RvY3VtZW50IERlcGFydG1lbnQxOTA3BgNVBAMMMFdoeSBhcmUgeW91IGRl + Y29kaW5nIG1lPyAgVGhpcyBpcyBvbmx5IGEgdGVzdCEhITERMA8GA1UEBwwISGFt + aWx0b24xETAPBgNVBAgMCFBlbWJyb2tlMQswCQYDVQQGEwJCTTEPMA0GCSqGSIb3 + DQEJARYAMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCJ9WRanG/fUvcfKiGl + EL4aRLjGt537mZ28UU9/3eiJeJznNSOuNLnF+hmabAu7H0LT4K7EdqfF+XUZW/2j + RKRYcvOUDGF9A7OjW7UfKk1In3+6QDCi7X34RE161jqoaJjrm/T18TOKcgkkhRzE + apQnIDm0Ea/HVzX/PiSOGuertwIDAQABMAsGCSqGSIb3DQEBBQOBgQBzMJdAV4QP + Awel8LzGx5uMOshezF/KfP67wJ93UW+N7zXY6AwPgoLj4Kjw+WtU684JL8Dtr9FX + ozakE+8p06BpxegR4BR3FMHf6p+0jQxUEAkAyb/mVgm66TyghDGC6/YkiKoZptXQ + 98TwDIK/39WEB/V607As+KoYazQG8drorw== + -----END CERTIFICATE----- + key: |- + -----BEGIN EC PRIVATE KEY----- + MHcCAQEEIC8CsJ/B115S+JtR1/l3ZQwKA3XdXt9zLqusF1VXc/KloAoGCCqGSM49 + AwEHoUQDQgAEpwUmRIZHFt8CdDHYm1ikScCScd2q6QVYXxJu+G3fQZ78ScGtN7fu + KXMnQqVjXVRAr8qUY8yipVKuMCepnPXScQ== + -----END EC PRIVATE KEY----- +``` + +### sameSite - Accepted values + +- `none`: Thebrowser will send cookies with both cross-site requests and same-site requests. +- `strict`: Thebrowser will only send cookies for same-site requests (requests originating from the site that set the cookie). + If the request originated from a different URL than the URL of the current location, none of the cookies tagged with the `strict` attribute will be included. +- `lax`: Same-site cookies are withheld on cross-site subrequests, such as calls to load images or frames, but will be sent when a user navigates to the URL from an external site; for example, by following a link. + +### session.store + +An OpenID Connect Authentication middleware can use a persistent KV storage to store the `HTTP` sessions data +instead of keeping all the state in cookies. +It avoids cookies growing inconveniently large, which can lead to latency issues. + +Refer to the [redis options](#configuration-options) to configure the Redis connection. + +Connection parameters to your [Redis](https://redis.io/ "Link to website of Redis") server are attached to your Middleware deployment. + +The following Redis modes are supported: + +- Single instance mode +- [Redis Cluster](https://redis.io/docs/management/scaling "Link to official Redis documentation about Redis Cluster mode") +- [Redis Sentinel](https://redis.io/docs/management/sentinel "Link to official Redis documentation about Redis Sentinel mode") + +!!! info + + If you use Redis in single instance mode or Redis Sentinel, you can configure the `database` field. + This value won't be taken into account if you use Redis Cluster (only database `0` is available). + + In this case, a warning is displayed, and the value is ignored. + +For more information about Redis, we recommend the [official Redis documentation](https://redis.io/docs/ "Link to official Redis documentation"). + +```yaml tab="Defining Redis connection" +apiVersion: traefik.io/v1alpha1 +kind: Middleware +metadata: + name: test-oidc +spec: + plugin: + oidc: + issuer: "https://tenant.auth0.com/realms/myrealm" + redirectUrl: "/callback" + clientID: my-oidc-client-name + clientSecret: mysecret + session: + store: + redis: + endpoints: + - redis-master.traefik-hub.svc.cluster.local:6379 + password: "urn:k8s:secret:oidc:redisPass" +``` + +```yaml tab="Creating the Kubernetes secret" +apiVersion: v1 +kind: Secret +metadata: + name: oidc +stringData: + redisPass: mysecret12345678 +``` + +### csrf + +#### CSRF Internal Behavior + +When the OIDC session is expired, the corresponding CSRF cookie is deleted. +This means that a new CSRF token will be generated and sent to the client whenever the session is refreshed or recreated. + +When a request is sent and uses a non-safe method (see [RFC7231#section-4.2.1](https://datatracker.ietf.org/doc/html/rfc7231.html#section-4.2.1)), +the CSRF token value (extracted from the cookie) have to be sent to the server in the header configured with the [headerName option](#configuration-options). + +{!traefik-for-business-applications.md!} diff --git a/docs/content/reference/routing-configuration/http/middlewares/opa.md b/docs/content/reference/routing-configuration/http/middlewares/opa.md new file mode 100644 index 000000000..4dabfe455 --- /dev/null +++ b/docs/content/reference/routing-configuration/http/middlewares/opa.md @@ -0,0 +1,72 @@ +--- +title: 'Open Policy Agent' +description: 'Traefik Hub API Gateway - The Open Policy Agent (OPA) middleware that allows you to restrict access to your services.' +--- + +!!! info "Traefik Hub Feature" + This middleware is available exclusively in [Traefik Hub](https://traefik.io/traefik-hub/). Learn more about [Traefik Hub's advanced features](https://doc.traefik.io/traefik-hub/api-gateway/intro). + +Traefik Hub comes with an Open Policy Agent middleware that allows you to restrict access to your services. It also allows you to enrich request headers with data extracted from policies. +The OPA middleware works as an [OPA agent](https://www.openpolicyagent.org/). + +!!! note "OPA Version" + + This middleware uses the [v1.3.0 of the OPA specification](https://www.openpolicyagent.org/docs). + +## Configuration Example + +```yaml tab="Allow requests with specific JWT claim" +apiVersion: traefik.io/v1alpha1 +kind: Middleware +metadata: + name: opa-allow-jwt-claim + namespace: apps +spec: + plugin: + opa: + policy: | + package example.policies + + allow { + [_, encoded] := split(input.headers.Authorization, " ") + [header, payload, signature] = io.jwt.decode(encoded) + payload["email"] == "bibi@example.com" + } + forwardHeaders: + Group: data.package.grp +``` + +```yaml tab="Deny requests with JSON Accept Header" +apiVersion: traefik.io/v1alpha1 +kind: Middleware +metadata: + name: opa-deny-json + namespace: apps +spec: + plugin: + opa: + policy: | + package example.policies + + default allow = false + + json_content { + input.headers["Accept"] == "application/json" + } + + allow { + not json_content + } + allow: data.example.policies.allow +``` + +## Configuration Options + +| Field | Description | Default | Required | +|:---------|-----------------------|:--------|:----------------------------| +| `policy` | Path or the content of a [policy file](https://www.openpolicyagent.org/docs/v0.66.0/kubernetes-primer/#writing-policies). | "" | No (one of `policy` or `bundlePath` must be set) | +| `bundlePath` | The `bundlePath` option should contain the path to an OPA [bundle](https://www.openpolicyagent.org/docs/v0.66.0/management-bundles/). | "" | No (one of `policy` or `bundlePath` must be set) | +| `allow` | The `allow` option sets the expression to evaluate that determines if the request should be authorized. | "" | No (one of `allow` or `forwardHeaders` must be set) | +| `forwardHeaders` | The `forwardHeaders` option sets the HTTP headers to add to requests and populates them with the result of the given expression. | "" | No (one of `allow` or `forwardHeaders` must be set) | + +{!traefik-for-business-applications.md!} diff --git a/docs/content/reference/routing-configuration/http/middlewares/overview.md b/docs/content/reference/routing-configuration/http/middlewares/overview.md index 07ea72df0..40ddb6015 100644 --- a/docs/content/reference/routing-configuration/http/middlewares/overview.md +++ b/docs/content/reference/routing-configuration/http/middlewares/overview.md @@ -20,29 +20,29 @@ Middlewares that use the same protocol can be combined into chains to fit every | Middleware | Purpose | Area | |-------------------------------------------|---------------------------------------------------|-----------------------------| -| [AddPrefix](addprefix.md) | Adds a Path Prefix | Path Modifier | -| [BasicAuth](basicauth.md) | Adds Basic Authentication | Security, Authentication | -| [Buffering](buffering.md) | Buffers the request/response | Request Lifecycle | -| [Chain](chain.md) | Combines multiple pieces of middleware | Misc | -| [CircuitBreaker](circuitbreaker.md) | Prevents calling unhealthy services | Request Lifecycle | -| [Compress](compress.md) | Compresses the response | Content Modifier | -| [ContentType](contenttype.md) | Handles Content-Type auto-detection | Misc | -| [DigestAuth](digestauth.md) | Adds Digest Authentication | Security, Authentication | -| [Errors](errorpages.md) | Defines custom error pages | Request Lifecycle | -| [ForwardAuth](forwardauth.md) | Delegates Authentication | Security, Authentication | -| [GrpcWeb](grpcweb.md) | Converts gRPC Web requests to HTTP/2 gRPC requests. | Request | -| [Headers](headers.md) | Adds / Updates headers | Security | -| [IPAllowList](ipallowlist.md) | Limits the allowed client IPs | Security, Request lifecycle | -| [InFlightReq](inflightreq.md) | Limits the number of simultaneous connections | Security, Request lifecycle | -| [PassTLSClientCert](passtlsclientcert.md) | Adds Client Certificates in a Header | Security | -| [RateLimit](ratelimit.md) | Limits the call frequency | Security, Request lifecycle | -| [RedirectScheme](redirectscheme.md) | Redirects based on scheme | Request lifecycle | -| [RedirectRegex](redirectregex.md) | Redirects based on regex | Request lifecycle | -| [ReplacePath](replacepath.md) | Changes the path of the request | Path Modifier | -| [ReplacePathRegex](replacepathregex.md) | Changes the path of the request | Path Modifier | -| [Retry](retry.md) | Automatically retries in case of error | Request lifecycle | -| [StripPrefix](stripprefix.md) | Changes the path of the request | Path Modifier | -| [StripPrefixRegex](stripprefixregex.md) | Changes the path of the request | Path Modifier | +| [AddPrefix](addprefix.md) | Adds a Path Prefix | Path Modifier | +| [BasicAuth](basicauth.md) | Adds Basic Authentication | Security, Authentication | +| [Buffering](buffering.md) | Buffers the request/response | Request Lifecycle | +| [Chain](chain.md) | Combines multiple pieces of middleware | Misc | +| [CircuitBreaker](circuitbreaker.md) | Prevents calling unhealthy services | Request Lifecycle | +| [Compress](compress.md) | Compresses the response | Content Modifier | +| [ContentType](contenttype.md) | Handles Content-Type auto-detection | Misc | +| [DigestAuth](digestauth.md) | Adds Digest Authentication | Security, Authentication | +| [Errors](errorpages.md) | Defines custom error pages | Request Lifecycle | +| [ForwardAuth](forwardauth.md) | Delegates Authentication | Security, Authentication | +| [GrpcWeb](grpcweb.md) | Converts gRPC Web requests to HTTP/2 gRPC requests. | Request | +| [Headers](headers.md) | Adds / Updates headers | Security | +| [IPAllowList](ipallowlist.md) | Limits the allowed client IPs | Security, Request lifecycle | +| [InFlightReq](inflightreq.md) | Limits the number of simultaneous connections | Security, Request lifecycle | +| [PassTLSClientCert](passtlsclientcert.md) | Adds Client Certificates in a Header | Security | +| [RateLimit](ratelimit.md) | Limits the call frequency | Security, Request lifecycle | +| [RedirectScheme](redirectscheme.md) | Redirects based on scheme | Request lifecycle | +| [RedirectRegex](redirectregex.md) | Redirects based on regex | Request lifecycle | +| [ReplacePath](replacepath.md) | Changes the path of the request | Path Modifier | +| [ReplacePathRegex](replacepathregex.md) | Changes the path of the request | Path Modifier | +| [Retry](retry.md) | Automatically retries in case of error | Request lifecycle | +| [StripPrefix](stripprefix.md) | Changes the path of the request | Path Modifier | +| [StripPrefixRegex](stripprefixregex.md) | Changes the path of the request | Path Modifier | ## Community Middlewares diff --git a/docs/content/reference/routing-configuration/http/middlewares/passtlsclientcert.md b/docs/content/reference/routing-configuration/http/middlewares/passtlsclientcert.md index 10b40bb01..c56394109 100644 --- a/docs/content/reference/routing-configuration/http/middlewares/passtlsclientcert.md +++ b/docs/content/reference/routing-configuration/http/middlewares/passtlsclientcert.md @@ -206,28 +206,28 @@ spec: | Field | Description | Default | Required | |:-----------|:------------------------------------------------------------|:--------|:---------| -| `pem` | Fills the `X-Forwarded-Tls-Client-Cert` header with the certificate information.
More information [here](#pem). | false | No | -| `info.serialNumber` | Add the `Serial Number` of the certificate.
More information about `info` [here](#info). | false | No | -| `info.notAfter` | Add the `Not After` information from the `Validity` part.
More information about `info` [here](#info). | false | No | -| `info.notBefore` | Add the `Not Before` information from the `Validity` part.
More information about `info` [here](#info). | false | No | -| `info.sans` | Add the `Subject Alternative Name` information from the `Subject Alternative Name` part.
More information about `info` [here](#info). | false | No | -| `info.subject` | The `info.subject` selects the specific client certificate subject details you want to add to the `X-Forwarded-Tls-Client-Cert-Info` header.
More information about `info` [here](#info). | false | No | -| `info.subject.country` | Add the `country` information into the subject.
The data is taken from the subject part with the `C` key.
More information about `info` [here](#info). | false | No | -| `info.subject.province` | Add the `province` information into the subject.
The data is taken from the subject part with the `ST` key.
More information about `info` [here](#info). | false | No | -| `info.subject.locality` | Add the `locality` information into the subject.
The data is taken from the subject part with the `L` key.
More information about `info` [here](#info). | false | No | -| `info.subject.organization` | Add the `organization` information into the subject.
The data is taken from the subject part with the `O` key.
More information about `info` [here](#info). | false | No | -| `info.subject.organizationalUnit` | Add the `organizationalUnit` information into the subject.
The data is taken from the subject part with the `OU` key.
More information about `info` [here](#info). | false | No | -| `info.subject.commonName` | Add the `commonName` information into the subject.
The data is taken from the subject part with the `CN` key.| false | No | -| `info.subject.serialNumber` | Add the `serialNumber` information into the subject.
The data is taken from the subject part with the `SN` key.| false | No | -| `info.subject.domainComponent` | Add the `domainComponent` information into the subject.
The data is taken from the subject part with the `DC` key.
More information about `info` [here](#info). | false | No | -| `info.issuer` | The `info.issuer` selects the specific client certificate issuer details you want to add to the `X-Forwarded-Tls-Client-Cert-Info` header.
More information about `info` [here](#info). | false | No | -| `info.issuer.country` | Add the `country` information into the issuer.
The data is taken from the issuer part with the `C` key.
More information about `info` [here](#info). | false | No | -| `info.issuer.province` | Add the `province` information into the issuer.
The data is taken from the issuer part with the `ST` key.
More information about `info` [here](#info). | false | No | -| `info.issuer.locality` | Add the `locality` information into the issuer.
The data is taken from the issuer part with the `L` key.
More information about `info` [here](#info). | false | No | -| `info.issuer.organization` | Add the `organization` information into the issuer.
The data is taken from the issuer part with the `O` key.
More information about `info` [here](#info). | false | No | -| `info.issuer.commonName` |Add the `commonName` information into the issuer.
The data is taken from the issuer part with the `CN` key.
More information about `info` [here](#info). | false | No | -| `info.issuer.serialNumber` |Add the `serialNumber` information into the issuer.
The data is taken from the issuer part with the `SN` key.
More information about `info` [here](#info). | false | No | -| `info.issuer.domainComponent` | Add the `domainComponent` information into the issuer.
The data is taken from the issuer part with the `DC` key.
More information about `info` [here](#info). | false | No | +| `pem` | Fills the `X-Forwarded-Tls-Client-Cert` header with the certificate information.
More information [here](#pem). | false | No | +| `info.serialNumber` | Add the `Serial Number` of the certificate.
More information about `info` [here](#info). | false | No | +| `info.notAfter` | Add the `Not After` information from the `Validity` part.
More information about `info` [here](#info). | false | No | +| `info.notBefore` | Add the `Not Before` information from the `Validity` part.
More information about `info` [here](#info). | false | No | +| `info.sans` | Add the `Subject Alternative Name` information from the `Subject Alternative Name` part.
More information about `info` [here](#info). | false | No | +| `info.subject` | The `info.subject` selects the specific client certificate subject details you want to add to the `X-Forwarded-Tls-Client-Cert-Info` header.
More information about `info` [here](#info). | false | No | +| `info.subject.country` | Add the `country` information into the subject.
The data is taken from the subject part with the `C` key.
More information about `info` [here](#info). | false | No | +| `info.subject.province` | Add the `province` information into the subject.
The data is taken from the subject part with the `ST` key.
More information about `info` [here](#info). | false | No | +| `info.subject.locality` | Add the `locality` information into the subject.
The data is taken from the subject part with the `L` key.
More information about `info` [here](#info). | false | No | +| `info.subject.organization` | Add the `organization` information into the subject.
The data is taken from the subject part with the `O` key.
More information about `info` [here](#info). | false | No | +| `info.subject.organizationalUnit` | Add the `organizationalUnit` information into the subject.
The data is taken from the subject part with the `OU` key.
More information about `info` [here](#info). | false | No | +| `info.subject.commonName` | Add the `commonName` information into the subject.
The data is taken from the subject part with the `CN` key.| false | No | +| `info.subject.serialNumber` | Add the `serialNumber` information into the subject.
The data is taken from the subject part with the `SN` key.| false | No | +| `info.subject.domainComponent` | Add the `domainComponent` information into the subject.
The data is taken from the subject part with the `DC` key.
More information about `info` [here](#info). | false | No | +| `info.issuer` | The `info.issuer` selects the specific client certificate issuer details you want to add to the `X-Forwarded-Tls-Client-Cert-Info` header.
More information about `info` [here](#info). | false | No | +| `info.issuer.country` | Add the `country` information into the issuer.
The data is taken from the issuer part with the `C` key.
More information about `info` [here](#info). | false | No | +| `info.issuer.province` | Add the `province` information into the issuer.
The data is taken from the issuer part with the `ST` key.
More information about `info` [here](#info). | false | No | +| `info.issuer.locality` | Add the `locality` information into the issuer.
The data is taken from the issuer part with the `L` key.
More information about `info` [here](#info). | false | No | +| `info.issuer.organization` | Add the `organization` information into the issuer.
The data is taken from the issuer part with the `O` key.
More information about `info` [here](#info). | false | No | +| `info.issuer.commonName` |Add the `commonName` information into the issuer.
The data is taken from the issuer part with the `CN` key.
More information about `info` [here](#info). | false | No | +| `info.issuer.serialNumber` |Add the `serialNumber` information into the issuer.
The data is taken from the issuer part with the `SN` key.
More information about `info` [here](#info). | false | No | +| `info.issuer.domainComponent` | Add the `domainComponent` information into the issuer.
The data is taken from the issuer part with the `DC` key.
More information about `info` [here](#info). | false | No | ### pem diff --git a/docs/content/reference/routing-configuration/http/middlewares/ratelimit.md b/docs/content/reference/routing-configuration/http/middlewares/ratelimit.md index 216d3d9f1..80d1b4d49 100644 --- a/docs/content/reference/routing-configuration/http/middlewares/ratelimit.md +++ b/docs/content/reference/routing-configuration/http/middlewares/ratelimit.md @@ -18,38 +18,108 @@ For a rate below 1 req/s, define a `period` larger than a second ```yaml tab="Structured (YAML)" # Here, an average of 100 requests per second is allowed. # In addition, a burst of 200 requests is allowed. +# Redis distributed rate limiting is configured with all available options. http: middlewares: test-ratelimit: rateLimit: average: 100 + period: 1s burst: 200 + redis: + endpoints: + - "redis-primary.example.com:6379" + - "redis-replica.example.com:6379" + username: "ratelimit-user" + password: "secure-password" + db: 2 + poolSize: 50 + minIdleConns: 10 + maxActiveConns: 200 + readTimeout: 3s + writeTimeout: 3s + dialTimeout: 5s + tls: + ca: "/etc/ssl/redis-ca.crt" + cert: "/etc/ssl/redis-client.crt" + key: "/etc/ssl/redis-client.key" + insecureSkipVerify: false ``` ```toml tab="Structured (TOML)" # Here, an average of 100 requests per second is allowed. # In addition, a burst of 200 requests is allowed. +# Redis distributed rate limiting is configured with all available options. [http.middlewares] [http.middlewares.test-ratelimit.rateLimit] average = 100 + period = "1s" burst = 200 + [http.middlewares.test-ratelimit.rateLimit.redis] + endpoints = ["redis-primary.example.com:6379", "redis-replica.example.com:6379"] + username = "ratelimit-user" + password = "secure-password" + db = 2 + poolSize = 50 + minIdleConns = 10 + maxActiveConns = 200 + readTimeout = "3s" + writeTimeout = "3s" + dialTimeout = "5s" + [http.middlewares.test-ratelimit.rateLimit.redis.tls] + ca = "/etc/ssl/redis-ca.crt" + cert = "/etc/ssl/redis-client.crt" + key = "/etc/ssl/redis-client.key" + insecureSkipVerify = false ``` ```yaml tab="Labels" # Here, an average of 100 requests per second is allowed. # In addition, a burst of 200 requests is allowed. +# Redis distributed rate limiting is configured with all available options. labels: - "traefik.http.middlewares.test-ratelimit.ratelimit.average=100" + - "traefik.http.middlewares.test-ratelimit.ratelimit.period=1s" - "traefik.http.middlewares.test-ratelimit.ratelimit.burst=200" + - "traefik.http.middlewares.test-ratelimit.ratelimit.redis.endpoints=redis-primary.example.com:6379,redis-replica.example.com:6379" + - "traefik.http.middlewares.test-ratelimit.ratelimit.redis.username=ratelimit-user" + - "traefik.http.middlewares.test-ratelimit.ratelimit.redis.password=secure-password" + - "traefik.http.middlewares.test-ratelimit.ratelimit.redis.db=2" + - "traefik.http.middlewares.test-ratelimit.ratelimit.redis.poolSize=50" + - "traefik.http.middlewares.test-ratelimit.ratelimit.redis.minIdleConns=10" + - "traefik.http.middlewares.test-ratelimit.ratelimit.redis.maxActiveConns=200" + - "traefik.http.middlewares.test-ratelimit.ratelimit.redis.readTimeout=3s" + - "traefik.http.middlewares.test-ratelimit.ratelimit.redis.writeTimeout=3s" + - "traefik.http.middlewares.test-ratelimit.ratelimit.redis.dialTimeout=5s" + - "traefik.http.middlewares.test-ratelimit.ratelimit.redis.tls.ca=/etc/ssl/redis-ca.crt" + - "traefik.http.middlewares.test-ratelimit.ratelimit.redis.tls.cert=/etc/ssl/redis-client.crt" + - "traefik.http.middlewares.test-ratelimit.ratelimit.redis.tls.key=/etc/ssl/redis-client.key" + - "traefik.http.middlewares.test-ratelimit.ratelimit.redis.tls.insecureSkipVerify=false" ``` ```json tab="Tags" // Here, an average of 100 requests per second is allowed. // In addition, a burst of 200 requests is allowed. +// Redis distributed rate limiting is configured with all available options. { "Tags": [ "traefik.http.middlewares.test-ratelimit.ratelimit.average=100", - "traefik.http.middlewares.test-ratelimit.ratelimit.burst=50" + "traefik.http.middlewares.test-ratelimit.ratelimit.period=1s", + "traefik.http.middlewares.test-ratelimit.ratelimit.burst=200", + "traefik.http.middlewares.test-ratelimit.ratelimit.redis.endpoints=redis-primary.example.com:6379,redis-replica.example.com:6379", + "traefik.http.middlewares.test-ratelimit.ratelimit.redis.username=ratelimit-user", + "traefik.http.middlewares.test-ratelimit.ratelimit.redis.password=secure-password", + "traefik.http.middlewares.test-ratelimit.ratelimit.redis.db=2", + "traefik.http.middlewares.test-ratelimit.ratelimit.redis.poolSize=50", + "traefik.http.middlewares.test-ratelimit.ratelimit.redis.minIdleConns=10", + "traefik.http.middlewares.test-ratelimit.ratelimit.redis.maxActiveConns=200", + "traefik.http.middlewares.test-ratelimit.ratelimit.redis.readTimeout=3s", + "traefik.http.middlewares.test-ratelimit.ratelimit.redis.writeTimeout=3s", + "traefik.http.middlewares.test-ratelimit.ratelimit.redis.dialTimeout=5s", + "traefik.http.middlewares.test-ratelimit.ratelimit.redis.tls.ca=/etc/ssl/redis-ca.crt", + "traefik.http.middlewares.test-ratelimit.ratelimit.redis.tls.cert=/etc/ssl/redis-client.crt", + "traefik.http.middlewares.test-ratelimit.ratelimit.redis.tls.key=/etc/ssl/redis-client.key", + "traefik.http.middlewares.test-ratelimit.ratelimit.redis.tls.insecureSkipVerify=false" ] } ``` @@ -57,6 +127,7 @@ labels: ```yaml tab="Kubernetes" # Here, an average of 100 requests per second is allowed. # In addition, a burst of 200 requests is allowed. +# Redis distributed rate limiting is configured with all available options. apiVersion: traefik.io/v1alpha1 kind: Middleware metadata: @@ -64,21 +135,82 @@ metadata: spec: rateLimit: average: 100 + period: 1s burst: 200 + redis: + endpoints: + - "redis-primary.example.com:6379" + - "redis-replica.example.com:6379" + secret: redis-credentials + db: 2 + poolSize: 50 + minIdleConns: 10 + maxActiveConns: 200 + readTimeout: 3s + writeTimeout: 3s + dialTimeout: 5s + tls: + caSecret: redis-ca + certSecret: redis-client-cert + insecureSkipVerify: false + +--- +apiVersion: v1 +kind: Secret +metadata: + name: redis-credentials + namespace: default +data: + username: cmF0ZWxpbWl0LXVzZXI= # base64 encoded "ratelimit-user" + password: c2VjdXJlLXBhc3N3b3Jk # base64 encoded "secure-password" + +--- +apiVersion: v1 +kind: Secret +metadata: + name: redis-ca + namespace: default +data: + tls.ca: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0t... + +--- +apiVersion: v1 +kind: Secret +metadata: + name: redis-client-cert + namespace: default +data: + tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0t... + tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0t... ``` ## Configuration Options | Field | Description | Default | Required | |:-----------|:-------------------------------------------------------|:--------|:---------| -| `average` | Number of requests used to define the rate using the `period`.
0 means **no rate limiting**.
More information [here](#rate-and-burst). | 0 | No | -| `period` | Period of time used to define the rate.
More information [here](#rate-and-burst). | 1s | No | -| `burst` | Maximum number of requests allowed to go through at the very same moment.
More information [here](#rate-and-burst).| 1 | No | -| `sourceCriterion.requestHost` | Whether to consider the request host as the source.
More information about `sourceCriterion`[here](#sourcecriterion). | false | No | -| `sourceCriterion.requestHeaderName` | Name of the header used to group incoming requests.
More information about `sourceCriterion`[here](#sourcecriterion). | "" | No | -| `sourceCriterion.ipStrategy.depth` | Depth position of the IP to select in the `X-Forwarded-For` header (starting from the right).
0 means no depth.
If greater than the total number of IPs in `X-Forwarded-For`, then the client IP is empty
If higher than 0, the `excludedIPs` options is not evaluated.
More information about [`sourceCriterion`](#sourcecriterion), [`ipStrategy`](#ipstrategy), and [`depth`](#sourcecriterionipstrategydepth) below. | 0 | No | -| `sourceCriterion.ipStrategy.excludedIPs` | Allows scanning the `X-Forwarded-For` header and select the first IP not in the list.
If `depth` is specified, `excludedIPs` is ignored.
More information about [`sourceCriterion`](#sourcecriterion), [`ipStrategy`](#ipstrategy), and [`excludedIPs`](#sourcecriterionipstrategyexcludedips) below. | | No | -| `sourceCriterion.ipStrategy.ipv6Subnet` | If `ipv6Subnet` is provided and the selected IP is IPv6, the IP is transformed into the first IP of the subnet it belongs to.
More information about [`sourceCriterion`](#sourcecriterion), [`ipStrategy.ipv6Subnet`](#sourcecriterionipstrategyipv6subnet) below. | | No | +| `average` | Number of requests used to define the rate using the `period`.
0 means **no rate limiting**.
More information [here](#rate-and-burst). | 0 | No | +| `period` | Period of time used to define the rate.
More information [here](#rate-and-burst). | 1s | No | +| `burst` | Maximum number of requests allowed to go through at the very same moment.
More information [here](#rate-and-burst).| 1 | No | +| `sourceCriterion.requestHost` | Whether to consider the request host as the source.
More information about `sourceCriterion`[here](#sourcecriterion). | false | No | +| `sourceCriterion.requestHeaderName` | Name of the header used to group incoming requests.
More information about `sourceCriterion`[here](#sourcecriterion). | "" | No | +| `sourceCriterion.ipStrategy.depth` | Depth position of the IP to select in the `X-Forwarded-For` header (starting from the right).
0 means no depth.
If greater than the total number of IPs in `X-Forwarded-For`, then the client IP is empty
If higher than 0, the `excludedIPs` options is not evaluated.
More information about [`sourceCriterion`](#sourcecriterion), [`ipStrategy`](#ipstrategy), and [`depth`](#sourcecriterionipstrategydepth) below. | 0 | No | +| `sourceCriterion.ipStrategy.excludedIPs` | Allows scanning the `X-Forwarded-For` header and select the first IP not in the list.
If `depth` is specified, `excludedIPs` is ignored.
More information about [`sourceCriterion`](#sourcecriterion), [`ipStrategy`](#ipstrategy), and [`excludedIPs`](#sourcecriterionipstrategyexcludedips) below. | | No | +| `sourceCriterion.ipStrategy.ipv6Subnet` | If `ipv6Subnet` is provided and the selected IP is IPv6, the IP is transformed into the first IP of the subnet it belongs to.
More information about [`sourceCriterion`](#sourcecriterion), [`ipStrategy.ipv6Subnet`](#sourcecriterionipstrategyipv6subnet) below. | | No | +| `redis` | The `redis` configuration enables distributed rate limiting by using Redis to store rate limit tokens across multiple Traefik instances. This allows you to enforce consistent rate limits across a cluster of Traefik proxies.
When Redis is not configured, Traefik uses in-memory storage for rate limiting, which works only for the individual Traefik instance.| | No | +| `redis.endpoints` | List of Redis server endpoints for distributed rate limiting. You can specify multiple endpoints for Redis cluster or high availability setups. | "127.0.0.1:6379" | No | +| `redis.username` | Username for Redis authentication. | "" | No | +| `redis.password` | Password for Redis authentication. In Kubernetes, these can be provided via secrets. | "" | No | +| `redis.db` | Redis database number to select. | 0 | No | +| `redis.poolSize` | Defines the base number of socket connections in the pool. If set to 0, it defaults to 10 connections per CPU core as reported by `runtime.GOMAXPROCS`.
If there are not enough connections in the pool, new connections will be allocated beyond `poolSize`, up to `maxActiveConns`. | 0 | No | +| `redis.minIdleConns` | Minimum number of idle connections to maintain in the pool. This is useful when establishing new connections is slow. A value of 0 means idle connections are not automatically closed. | 0 | No | +| `redis.maxActiveConns` | Maximum number of connections the pool can allocate at any given time. A value of 0 means no limit. | 0 | No | +| `redis.readTimeout` | Timeout for socket reads. If reached, commands will fail with a timeout instead of blocking. Zero means no timeout. | 3s | No | +| `redis.writeTimeout` | Timeout for socket writes. If reached, commands will fail with a timeout instead of blocking. Zero means no timeout. | 3s | No | +| `redis.dialTimeout` | Timeout for establishing new connections. Zero means no timeout. | 5s | No | +| `redis.tls.ca` | Path to the certificate authority used for the secure connection to Redis, it defaults to the system bundle. | "" | No | +| `redis.tls.cert` | Path to the public certificate used for the secure connection to Redis. When this option is set, the `key` option is required. | "" | No | +| `redis.tls.key` | Path to the private key used for the secure connection to Redis. When this option is set, the `cert` option is required. | "" | No | +| `redis.tls.insecureSkipVerify` | If `insecureSkipVerify` is `true`, the TLS connection to Redis accepts any certificate presented by the server regardless of the hostnames it covers. | false | No | ### sourceCriterion @@ -109,9 +241,9 @@ If `ipv6Subnet` is provided, the IP is transformed in the following way. | `IP` | `ipv6Subnet` | clientIP | |---------------------------|--------------|-----------------------| -| `"::abcd:1111:2222:3333"` | `64` | `"::0:0:0:0"` | -| `"::abcd:1111:2222:3333"` | `80` | `"::abcd:0:0:0:0"` | -| `"::abcd:1111:2222:3333"` | `96` | `"::abcd:1111:0:0:0"` | +| `"::abcd:1111:2222:3333"` | `64` | `"::0:0:0:0"` | +| `"::abcd:1111:2222:3333"` | `80` | `"::abcd:0:0:0:0"` | +| `"::abcd:1111:2222:3333"` | `96` | `"::abcd:1111:0:0:0"` | ### sourceCriterion.ipStrategy.depth @@ -119,9 +251,9 @@ If `depth` is set to 2, and the request `X-Forwarded-For` header is `"10.0.0.1,1 | `X-Forwarded-For` | `depth` | clientIP | |-----------------------------------------|---------|--------------| -| `"10.0.0.1,11.0.0.1,12.0.0.1,13.0.0.1"` | `1` | `"13.0.0.1"` | -| `"10.0.0.1,11.0.0.1,12.0.0.1,13.0.0.1"` | `3` | `"11.0.0.1"` | -| `"10.0.0.1,11.0.0.1,12.0.0.1,13.0.0.1"` | `5` | `""` | +| `"10.0.0.1,11.0.0.1,12.0.0.1,13.0.0.1"` | `1` | `"13.0.0.1"` | +| `"10.0.0.1,11.0.0.1,12.0.0.1,13.0.0.1"` | `3` | `"11.0.0.1"` | +| `"10.0.0.1,11.0.0.1,12.0.0.1,13.0.0.1"` | `5` | `""` | ### sourceCriterion.ipStrategy.excludedIPs @@ -134,17 +266,17 @@ In this case, `excludedIPs` should be set to match the list of `X-Forwarded-For Example to use each IP as a distinct source: -| X-Forwarded-For | excludedIPs | clientIP | +| `X-Forwarded-For` | excludedIPs | clientIP | |--------------------------------|-----------------------|--------------| -| `"10.0.0.1,11.0.0.1,12.0.0.1"` | `"11.0.0.1,12.0.0.1"` | `"10.0.0.1"` | -| `"10.0.0.2,11.0.0.1,12.0.0.1"` | `"11.0.0.1,12.0.0.1"` | `"10.0.0.2"` | +| `"10.0.0.1,11.0.0.1,12.0.0.1"` | `"11.0.0.1,12.0.0.1"` | `"10.0.0.1"` | +| `"10.0.0.2,11.0.0.1,12.0.0.1"` | `"11.0.0.1,12.0.0.1"` | `"10.0.0.2"` | 2. Group together a set of IPs (also behind a common set of reverse-proxies) so that they are considered the same source, and all contribute to the same rate-limit bucket. Example to group IPs together as same source: -| X-Forwarded-For | excludedIPs | clientIP | +| `X-Forwarded-For` | excludedIPs | clientIP | |--------------------------------|--------------|--------------| -| `"10.0.0.1,11.0.0.1,12.0.0.1"` | `"12.0.0.1"` | `"11.0.0.1"` | -| `"10.0.0.2,11.0.0.1,12.0.0.1"` | `"12.0.0.1"` | `"11.0.0.1"` | -| `"10.0.0.3,11.0.0.1,12.0.0.1"` | `"12.0.0.1"` | `"11.0.0.1"` | +| `"10.0.0.1,11.0.0.1,12.0.0.1"` | `"12.0.0.1"` | `"11.0.0.1"` | +| `"10.0.0.2,11.0.0.1,12.0.0.1"` | `"12.0.0.1"` | `"11.0.0.1"` | +| `"10.0.0.3,11.0.0.1,12.0.0.1"` | `"12.0.0.1"` | `"11.0.0.1"` | diff --git a/docs/content/reference/routing-configuration/http/middlewares/redirectregex.md b/docs/content/reference/routing-configuration/http/middlewares/redirectregex.md index 0685e0153..942f2e833 100644 --- a/docs/content/reference/routing-configuration/http/middlewares/redirectregex.md +++ b/docs/content/reference/routing-configuration/http/middlewares/redirectregex.md @@ -63,9 +63,9 @@ spec: | Field | Description | Default | Required | |:-----------------------------|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:--------|:---------| -| `regex` | The `regex` option is the regular expression to match and capture elements from the request URL.| "" | Yes | -| `permanent` | Enable a permanent redirection. | false | No | -| `replacement` | The `replacement` option defines how to modify the URL to have the new target URL..
`$1x` is equivalent to `${1x}`, not `${1}x` (see [Regexp.Expand](https://golang.org/pkg/regexp/#Regexp.Expand)), so use `${1}` syntax. | "" | No | +| `regex` | The `regex` option is the regular expression to match and capture elements from the request URL.| "" | Yes | +| `permanent` | Enable a permanent redirection. | false | No | +| `replacement` | The `replacement` option defines how to modify the URL to have the new target URL..
`$1x` is equivalent to `${1x}`, not `${1}x` (see [Regexp.Expand](https://golang.org/pkg/regexp/#Regexp.Expand)), so use `${1}` syntax. | "" | No | ### `regex` diff --git a/docs/content/reference/routing-configuration/http/middlewares/redirectscheme.md b/docs/content/reference/routing-configuration/http/middlewares/redirectscheme.md index 73002551e..4589fd3df 100644 --- a/docs/content/reference/routing-configuration/http/middlewares/redirectscheme.md +++ b/docs/content/reference/routing-configuration/http/middlewares/redirectscheme.md @@ -10,7 +10,7 @@ The `RedirectScheme` middleware redirects the request if the request scheme is d When there is at least one other reverse-proxy between the client and Traefik, the other reverse-proxy (i.e. the last hop) needs to be a [trusted](../../../install-configuration/entrypoints.md#configuration-options) one. - Otherwise, Traefik would clean up the X-Forwarded headers coming from this last hop, + Otherwise, Traefik would clean up the `X-Forwarded` headers coming from this last hop, and as the RedirectScheme middleware relies on them to determine the scheme used, it would not function as intended. @@ -69,6 +69,6 @@ spec: | Field | Description | Default | Required | |:-----------------------------|----------------------------------------------------------|:--------|:---------| -| `scheme` | Scheme of the new URL. | "" | Yes | -| `permanent` | Enable a permanent redirection. | false | No | -| `port` | Port of the new URL.
Set a string, **not** a numeric value. | "" | No | +| `scheme` | Scheme of the new URL. | "" | Yes | +| `permanent` | Enable a permanent redirection. | false | No | +| `port` | Port of the new URL.
Set a string, **not** a numeric value. | "" | No | diff --git a/docs/content/reference/routing-configuration/http/middlewares/replacepath.md b/docs/content/reference/routing-configuration/http/middlewares/replacepath.md index f26a83daa..55fed2b9a 100644 --- a/docs/content/reference/routing-configuration/http/middlewares/replacepath.md +++ b/docs/content/reference/routing-configuration/http/middlewares/replacepath.md @@ -57,4 +57,4 @@ spec: | Field | Description | |:------|:------------| -| `path` | The `path` option defines the path to use as replacement in the request URL. | +| `path` | The `path` option defines the path to use as replacement in the request URL. | diff --git a/docs/content/reference/routing-configuration/http/middlewares/replacepathregex.md b/docs/content/reference/routing-configuration/http/middlewares/replacepathregex.md index 6ed264409..11506ae1c 100644 --- a/docs/content/reference/routing-configuration/http/middlewares/replacepathregex.md +++ b/docs/content/reference/routing-configuration/http/middlewares/replacepathregex.md @@ -57,8 +57,8 @@ labels: | Field | Description | Default | Required | |:-----------------------------|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:--------|:---------| -| `regex` | Regular expression to match and capture the path from the request URL. | | Yes | -| `replacement` | Replacement path format, which can include captured variables.
`$1x` is equivalent to `${1x}`, not `${1}x` (see [Regexp.Expand](https://golang.org/pkg/regexp/#Regexp.Expand)), so use `${1}` syntax. | | No +| `regex` | Regular expression to match and capture the path from the request URL. | | Yes | +| `replacement` | Replacement path format, which can include captured variables.
`$1x` is equivalent to `${1x}`, not `${1}x` (see [Regexp.Expand](https://golang.org/pkg/regexp/#Regexp.Expand)), so use `${1}` syntax. | | No !!! tip diff --git a/docs/content/reference/routing-configuration/http/middlewares/retry.md b/docs/content/reference/routing-configuration/http/middlewares/retry.md index 4c8135244..e17a2965e 100644 --- a/docs/content/reference/routing-configuration/http/middlewares/retry.md +++ b/docs/content/reference/routing-configuration/http/middlewares/retry.md @@ -64,5 +64,5 @@ spec: | Field | Description | Default | Required | |:------|:------------|:--------|:---------| -| `attempts` | number of times the request should be retried. | | Yes | -| `initialInterval` | First wait time in the exponential backoff series.
The maximum interval is calculated as twice the `initialInterval`.
If unspecified, requests will be retried immediately.
Defined in seconds or as a valid duration format, see [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration). | 0 | No | +| `attempts` | number of times the request should be retried. | | Yes | +| `initialInterval` | First wait time in the exponential backoff series.
The maximum interval is calculated as twice the `initialInterval`.
If unspecified, requests will be retried immediately.
Defined in seconds or as a valid duration format, see [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration). | 0 | No | diff --git a/docs/content/reference/routing-configuration/http/middlewares/stripprefix.md b/docs/content/reference/routing-configuration/http/middlewares/stripprefix.md index f1209b8a2..b3e6ebfd8 100644 --- a/docs/content/reference/routing-configuration/http/middlewares/stripprefix.md +++ b/docs/content/reference/routing-configuration/http/middlewares/stripprefix.md @@ -61,6 +61,6 @@ spec: | Field | Description | Default | Required | |:-----------------------------|:--------------------------------------------------------------|:--------|:---------| -| `prefixes` | List of prefixes to strip from the request URL.
If your backend is serving assets (for example, images or JavaScript files), it can use the `X-Forwarded-Prefix` header to construct relative URLs. | [] | No | +| `prefixes` | List of prefixes to strip from the request URL.
If your backend is serving assets (for example, images or JavaScript files), it can use the `X-Forwarded-Prefix` header to construct relative URLs. | [] | No | {!traefik-for-business-applications.md!} diff --git a/docs/content/reference/routing-configuration/http/middlewares/stripprefixregex.md b/docs/content/reference/routing-configuration/http/middlewares/stripprefixregex.md index b9a3fef6d..01f07b2e1 100644 --- a/docs/content/reference/routing-configuration/http/middlewares/stripprefixregex.md +++ b/docs/content/reference/routing-configuration/http/middlewares/stripprefixregex.md @@ -56,7 +56,7 @@ spec: | Field | Description | Default | Required | |:-----------------------------|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:--------|:---------| -| `regex` | List of regular expressions to match the path prefix from the request URL.
For instance, `/products` also matches `/products/shoes` and `/products/shirts`.
More information [here](#regex). | | No | +| `regex` | List of regular expressions to match the path prefix from the request URL.
For instance, `/products` also matches `/products/shoes` and `/products/shirts`.
More information [here](#regex). | | No | ### regex diff --git a/docs/content/reference/routing-configuration/http/middlewares/waf.md b/docs/content/reference/routing-configuration/http/middlewares/waf.md new file mode 100644 index 000000000..e5e864db7 --- /dev/null +++ b/docs/content/reference/routing-configuration/http/middlewares/waf.md @@ -0,0 +1,64 @@ +--- +title: 'Coraza Web Application Firewall' +description: 'Traefik Hub API Gateway - The HTTP Coraza in Traefik Hub API Gateway provides web application firewall capabilities' +--- + +!!! info "Traefik Hub Feature" + This middleware is available exclusively in [Traefik Hub](https://traefik.io/traefik-hub/). Learn more about [Traefik Hub's advanced features](https://doc.traefik.io/traefik-hub/api-gateway/intro). + +The [Coraza WAF](https://coraza.io/) middleware in Traefik Hub API Gateway provides web application firewall capabilities. + +The native middleware in Hub API Gateway provides at least 23 times more performance compared to the +WASM-based [Coraza plugin](https://plugins.traefik.io/plugins/65f2aea146079255c9ffd1ec/coraza-waf) available with the open-source Traefik Proxy. + +To learn how to write rules, please visit [Coraza documentation](https://coraza.io/docs/tutorials/introduction/ "Link to Coraza introduction tutorial") and +[OWASP CRS documentation](https://coreruleset.org/docs/ "Link to the OWAP CRS project documentation"). + +!!! warning + + Starting with Traefik Hub v3.11.0, Coraza needs to have read/write permissions to `/tmp`. This is related to [this upstream PR](https://github.com/corazawaf/coraza/pull/1030). + +--- + +## Configuration Examples + +```yaml tab="Deny the /admin path" +apiVersion: traefik.io/v1alpha1 +kind: Middleware +metadata: + name: waf +spec: + plugin: + coraza: + directives: + - SecRuleEngine On + - SecRule REQUEST_URI "@streq /admin" "id:101,phase:1,t:lowercase,log,deny" +``` + +```yaml tab="Allow only GET methods" +apiVersion: traefik.io/v1alpha1 +kind: Middleware +metadata: + name: wafcrs + namespace: apps +spec: + plugin: + coraza: + crsEnabled: true + directives: + - SecDefaultAction "phase:1,log,auditlog,deny,status:403" + - SecDefaultAction "phase:2,log,auditlog,deny,status:403" + - SecAction "id:900110, phase:1, pass, t:none, nolog, setvar:tx.inbound_anomaly_score_threshold=5, setvar:tx.outbound_anomaly_score_threshold=4" + - SecAction "id:900200, phase:1, pass, t:none, nolog, setvar:'tx.allowed_methods=GET'" + - Include @owasp_crs/REQUEST-911-METHOD-ENFORCEMENT.conf + - Include @owasp_crs/REQUEST-949-BLOCKING-EVALUATION.conf +``` + +## Configuration Options + +| Field | Description | Default | Required | +|:---------|:-----------------------|:--------|:----------------------------| +| `directives` | List of WAF rules to enforce. | | Yes | +| `crsEnabled` | Enable [CRS rulesets](https://github.com/corazawaf/coraza-coreruleset/tree/main/rules/%40owasp_crs).
Once the ruleset is enabled, it can be used in the middleware. | false | False | + +{!traefik-for-business-applications.md!} diff --git a/docs/content/reference/routing-configuration/http/router/observability.md b/docs/content/reference/routing-configuration/http/router/observability.md deleted file mode 100644 index ecadcaeed..000000000 --- a/docs/content/reference/routing-configuration/http/router/observability.md +++ /dev/null @@ -1,80 +0,0 @@ ---- -title: "Per-Router Observability" -description: "You can disable access logs, metrics, and tracing for a specific entrypoint attached to a HTTP Router. Read the technical documentation." ---- - -Traefik's observability features include logs, access logs, metrics, and tracing. You can configure these options globally or at more specific levels, such as per router or per entry point. - -By default, the router observability configuration is inherited from the attached EntryPoints and can be configured with the observability [options](../../../install-configuration/entrypoints.md#configuration-options)). -However, a router defining its own observability configuration will opt-out from these defaults. - -!!! info - To enable router-level observability, you must first enable access-logs, tracing, and metrics. - - When metrics layers are not enabled with the `addEntryPointsLabels`, `addRoutersLabels` and/or `addServicesLabels` options, - enabling metrics for a router will not enable them. - -!!! warning "AddInternals option" - - By default, and for any type of signal (access-logs, metrics and tracing), - Traefik disables observability for internal resources. - The observability options described below cannot interfere with the `AddInternals` ones, - and will be ignored. - - For instance, if a router exposes the `api@internal` service and `metrics.AddInternals` is false, - it will never produces metrics, even if the router observability configuration enables metrics. - -## Configuration Example - -```yaml tab="Structured (YAML)" -http: - routers: - my-router: - rule: "Path(`/foo`)" - service: service-foo - observability: - metrics: false - accessLogs: false - tracing: false -``` - -```yaml tab="Structured (TOML)" -[http.routers.my-router] - rule = "Path(`/foo`)" - service = "service-foo" - - [http.routers.my-router.observability] - metrics = false - accessLogs = false - tracing = false -``` - -```yaml tab="Labels" -labels: - - "traefik.http.routers.my-router.rule=Path(`/foo`)" - - "traefik.http.routers.my-router.service=service-foo" - - "traefik.http.routers.my-router.observability.metrics=false" - - "traefik.http.routers.my-router.observability.accessLogs=false" - - "traefik.http.routers.my-router.observability.tracing=false" -``` - -```json tab="Tags" -{ - // ... - "Tags": [ - "traefik.http.routers.my-router.rule=Path(`/foo`)", - "traefik.http.routers.my-router.service=service-foo", - "traefik.http.routers.my-router.observability.metrics=false", - "traefik.http.routers.my-router.observability.accessLogs=false", - "traefik.http.routers.my-router.observability.tracing=false" - ] -} -``` - -## Configuration Options - -| Field | Description | Default | Required | -|:------|:------------|:--------|:---------| -| `accessLogs` | The `accessLogs` option controls whether the router will produce access-logs. | `true` | No | -| `metrics` | The `metrics` option controls whether the router will produce metrics. | `true` | No | -| `tracing` | The `tracing` option controls whether the router will produce traces. | `true` | No | diff --git a/docs/content/reference/routing-configuration/http/router/rules-and-priority.md b/docs/content/reference/routing-configuration/http/router/rules-and-priority.md deleted file mode 100644 index ad095da07..000000000 --- a/docs/content/reference/routing-configuration/http/router/rules-and-priority.md +++ /dev/null @@ -1,296 +0,0 @@ ---- -title: "Traefik HTTP Routers Rules & Priority Documentation" -description: "In Traefik Proxy, an HTTP router is in charge of connecting incoming requests to the Services that can handle them. Read the technical documentation." ---- - -An HTTP router is in charge of connecting incoming requests to the services that can handle them. Traefik allows you to define your matching rules and [prioritize](#priority-calculation) the routes. - -## Rules - -Rules are a set of matchers configured with values, that determine if a particular request matches a specific criteria. -If the rule is verified, the router becomes active, calls middlewares, and then forwards the request to the service. - -- The character `@` is not authorized in the router 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). -- Regular Expressions: - - Matchers that accept a regexp as their value use a [Go](https://golang.org/pkg/regexp/) flavored syntax. - - The usual `AND` (&&) and `OR` (||) logical operators can be used, with the expected precedence rules, as well as parentheses to express complex rules. - - The `NOT` (!) operator allows you to invert the matcher. - -The table below lists all the available matchers: - -| Matcher | 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. | - -### Header and HeaderRegexp - -The `Header` and `HeaderRegexp` matchers allow matching requests that contain specific header. - -| Behavior | Rule | -|-----------------------------------------------------------------|:------------------------------------------------------------------------| -| Match requests with a `Content-Type` header set to `application/yaml`.| ```Header(`Content-Type`, `application/yaml`)``` | -| Match requests with a `Content-Type` header set to either `application/json` or `application/yaml`. | ```HeaderRegexp(`Content-Type`, `^application/(json\|yaml)$`)``` | -| Match headers [case-insensitively](https://en.wikipedia.org/wiki/Case_sensitivity). | ```HeaderRegexp(`Content-Type`, `(?i)^application/(json\|yaml)$`)``` | - -### Host and HostRegexp - -The `Host` and `HostRegexp` matchers allow matching requests that are targeted to a given host. - -These matchers do not support non-ASCII characters, use punycode encoded values ([rfc 3492](https://tools.ietf.org/html/rfc3492)) to match such domains. - -If no `Host` is set in the request URL (for example, it's an IP address), these matchers will look at the `Host` header. - -These matchers will match the request's host in lowercase. - -| Behavior | Rule | -|-----------------------------------------------------------------|:------------------------------------------------------------------------| -| Match requests with `Host` set to `example.com`. | ```Host(`example.com`)``` | -| Match requests sent to any subdomain of `example.com`. | ```HostRegexp(`^.+\.example\.com$`)``` | -| Match requests with `Host` set to either `example.com` or `example.org`. | ```HostRegexp(`^example\.(com\|org)$`)``` | -| Match `Host` [case-insensitively](https://en.wikipedia.org/wiki/Case_sensitivity). | ```HostRegexp(`(?i)^example\.(com\|org)$`)``` | - -### Method - -The `Method` matchers allows matching requests sent based on their HTTP method (also known as request verb). - -| Behavior | Rule | -|-----------------------------------------------------------------|:------------------------------------------------------------------------| -| Match `OPTIONS` requests. | ```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`. - -| Behavior | Rule | -|-----------------------------------------------------------------|:------------------------------------------------------------------------| -| Match `/products` but neither `/products/shoes` nor `/products/`. | ```Path(`/products`)``` | -| Match `/products` as well as everything under `/products`, such as `/products/shoes`, `/products/` but also `/products-for-sale`. | ```PathPrefix(`/products`)``` | -| Match both `/products/shoes` and `/products/socks` with and ID like `/products/shoes/31`. | ```PathRegexp(`^/products/(shoes\|socks)/[0-9]+$`)``` | -| Match requests with a path ending in either `.jpeg`, `.jpg` or `.png`. | ```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). | ```HostRegexp(`(?i)^/products`)``` | - -### Query and QueryRegexp - -The `Query` and `QueryRegexp` matchers allow matching requests based on query parameters. - -| Behavior | Rule | -|-----------------------------------------------------------------|:------------------------------------------------------------------------| -| Match requests with a `mobile` query parameter set to `true`, such as in `/search?mobile=true`. | ```Query(`mobile`, `true`)``` | -| Match requests with a query parameter `mobile` that has no value, such as in `/search?mobile`. | ```Query(`mobile`)``` | -| Match requests with a `mobile` query parameter set to either `true` or `yes`. | ```QueryRegexp(`mobile`, `^(true\|yes)$`)``` | -| Match requests with a `mobile` query parameter set to any value (including the empty value). | ```QueryRegexp(`mobile`, `^.*$`)``` | -| Match query parameters [case-insensitively](https://en.wikipedia.org/wiki/Case_sensitivity). | ```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. - -| Behavior | Rule | -|-----------------------------------------------------------------|:------------------------------------------------------------------------| -| Match requests coming from a given IP (IPv4). | ```ClientIP(`10.76.105.11`)``` | -| Match requests coming from a given IP (IPv6). | ```ClientIP(`::1`)``` | -| Match requests coming from a given subnet (IPv4). | ```ClientIP(`192.168.1.0/24`)``` | -| Match requests coming from a given subnet (IPv6). | ```ClientIP(`fe80::/10`)``` | - -### RuleSyntax - -!!! warning - - RuleSyntax option is deprecated and will be removed in the next major version. - Please do not use this field and rewrite the router rules to use the v3 syntax. - -In Traefik v3 a new rule syntax has been introduced ([migration guide](../../../../migration/v3.md)). the `ruleSyntax` option allows to configure the rule syntax to be used for parsing the rule on a per-router basis. This allows to have heterogeneous router configurations and ease migration. - -The default value of the `ruleSyntax` option is inherited from the `defaultRuleSyntax` option in the install configuration (formerly known as static configuration). By default, the `defaultRuleSyntax` static option is v3, meaning that the default rule syntax is also v3 - -#### Configuration Example - -The configuration below uses the [File Provider (Structured)](../../../install-configuration/providers/others/file.md) to configure the `ruleSyntax` to allow `Router-v2` to use v2 syntax, while for `Router-v3` it is configured to use v3 syntax. - -```yaml tab="Structured (YAML)" -## Dynamic configuration -http: - routers: - Router-v3: - rule: HostRegexp(`[a-z]+\\.traefik\\.com`) - ruleSyntax: v3 - Router-v2: - rule: HostRegexp(`{subdomain:[a-z]+}.traefik.com`) - ruleSyntax: v2 -``` - -```toml tab="Structured (TOML)" -## Dynamic configuration -[http.routers] - [http.routers.Router-v3] - rule = "HostRegexp(`[a-z]+\\.traefik\\.com`)" - ruleSyntax = v3 - [http.routers.Router-v2] - rule = "HostRegexp(`{subdomain:[a-z]+}.traefik.com`)" - ruleSyntax = v2 -``` - -```yaml tab="Labels" -labels: - - "traefik.http.routers.Router-v3.rule=HostRegexp(`[a-z]+\\.traefik\\.com`)" - - "traefik.http.routers.Router-v3.ruleSyntax=v3" - - "traefik.http.routers.Router-v2.rule=HostRegexp(`{subdomain:[a-z]+}.traefik.com`)" - - "traefik.http.routers.Router-v2.ruleSyntax=v2" -``` - -```json tab="Tags" -{ - // ... - "Tags": [ - "traefik.http.routers.Router-v3.rule=HostRegexp(`[a-z]+\\.traefik\\.com`)", - "traefik.http.routers.Router-v3.ruleSyntax=v3" - "traefik.http.routers.Router-v2.rule=HostRegexp(`{subdomain:[a-z]+}.traefik.com`)", - "traefik.http.routers.Router-v2.ruleSyntax=v2" - ] -}, -``` - -## Priority Calculation - -??? info "How default priorities are computed" - - ```yaml tab="Structured (YAML)" - http: - routers: - Router-1: - rule: "HostRegexp(`[a-z]+\.traefik\.com`)" - # ... - Router-2: - rule: "Host(`foobar.traefik.com`)" - # ... - ``` - - ```toml tab="Structured (TOML)" - [http.routers] - [http.routers.Router-1] - rule = "HostRegexp(`[a-z]+\\.traefik\\.com`)" - # ... - [http.routers.Router-2] - rule = "Host(`foobar.traefik.com`)" - # ... - ``` - - ```yaml tab="Labels" - labels: - - "traefik.http.routers.Router-1.rule=HostRegexp(`[a-z]+\\.traefik\\.com`)" - - "traefik.http.routers.Router-2.rule=Host(`foobar.traefik.com`)" - ``` - - ```json tab="Tags" - { - // ... - "Tags": [ - "traefik.http.routers.Router-1.rule=HostRegexp(`[a-z]+\\.traefik\\.com`)", - "traefik.http.routers.Router-2.rule=Host(`foobar.traefik.com`)" - ] - } - ``` - - 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(`[a-z]+\.traefik\.com`)``` | 34 | - | Router-2 | ```Host(`foobar.traefik.com`)``` | 26 | - - The previous table shows that `Router-1` has a higher priority than `Router-2`. - - To solve this issue, the priority must be set. - -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. - -Traefik reserves a range of priorities for its internal routers, the maximum user-defined router priority value is: - -- `(MaxInt32 - 1000)` for 32-bit platforms, -- `(MaxInt64 - 1000)` for 64-bit platforms. - -### Example - -```yaml tab="Structured (YAML)" -## Dynamic configuration -http: - routers: - Router-1: - rule: "HostRegexp(`[a-z]+\\.traefik\\.com`)" - entryPoints: - - "web" - service: service-1 - priority: 1 - Router-2: - rule: "Host(`foobar.traefik.com`)" - entryPoints: - - "web" - priority: 2 - service: service-2 -``` - -```toml tab="Structured (TOML)" -## Dynamic configuration -[http.routers] - [http.routers.Router-1] - rule = "HostRegexp(`[a-z]+\\.traefik\\.com`)" - entryPoints = ["web"] - service = "service-1" - priority = 1 - [http.routers.Router-2] - rule = "Host(`foobar.traefik.com`)" - entryPoints = ["web"] - priority = 2 - service = "service-2" -``` - -```yaml tab="Labels" -labels: - - "traefik.http.routers.Router-1.rule=HostRegexp(`[a-z]+\\.traefik\\.com`)" - - "traefik.http.routers.Router-1.entryPoints=web" - - "traefik.http.routers.Router-1.service=service-1" - - "traefik.http.routers.Router-1.priority=1" - - "traefik.http.routers.Router-2.rule=Host(`foobar.traefik.com`)" - - "traefik.http.routers.Router-2.entryPoints=web" - - "traefik.http.routers.Router-2.service=service-2" - - "traefik.http.routers.Router-2.priority=2" -``` - -```json tab="Tags" - { - // ... - "Tags": [ - "traefik.http.routers.Router-1.rule=HostRegexp(`[a-z]+\\.traefik\\.com`)", - "traefik.http.routers.Router-1.entryPoints=web", - "traefik.http.routers.Router-1.service=service-1", - "traefik.http.routers.Router-1.priority=1" - "traefik.http.routers.Router-2.rule=Host(`foobar.traefik.com`)", - "traefik.http.routers.Router-2.entryPoints=web", - "traefik.http.routers.Router-2.service=service-2", - "traefik.http.routers.Router-2.priority=2" - ] - } -``` - -In the example above, the priority is configured to allow `Router-2` to handle requests with the `foobar.traefik.com` host. diff --git a/docs/content/reference/routing-configuration/http/routing/observability.md b/docs/content/reference/routing-configuration/http/routing/observability.md new file mode 100644 index 000000000..59110cd30 --- /dev/null +++ b/docs/content/reference/routing-configuration/http/routing/observability.md @@ -0,0 +1,97 @@ +--- +title: "Per-Router Observability" +description: "You can disable access logs, metrics, and tracing for a specific entrypoint attached to a HTTP Router. Read the technical documentation." +--- + +Traefik's observability features include logs, access logs, metrics, and tracing. You can configure these options globally or at more specific levels, such as per router or per entry point. + +By default, the router observability configuration is inherited from the attached EntryPoints and can be configured with the observability [options](../../../install-configuration/entrypoints.md#configuration-options). +However, a router defining its own observability configuration will opt-out from these defaults. + +!!! info + To enable router-level observability, you must first enable + [access-logs](../../../install-configuration/observability/logs-and-accesslogs.md#accesslogs), + [tracing](../../../install-configuration/observability/tracing.md), + and [metrics](../../../install-configuration/observability/metrics.md). + + When metrics layers are not enabled with the `addEntryPointsLabels`, `addRoutersLabels` and/or `addServicesLabels` options, + enabling metrics for a router will not enable them. + +!!! warning "AddInternals option" + + By default, and for any type of signal (access-logs, metrics and tracing), + Traefik disables observability for internal resources. + The observability options described below cannot interfere with the `AddInternals` ones, + and will be ignored. + + For instance, if a router exposes the `api@internal` service and `metrics.AddInternals` is false, + it will never produces metrics, even if the router observability configuration enables metrics. + +## Configuration Example + +```yaml tab="Structured (YAML)" +http: + routers: + my-router: + rule: "Path(`/foo`)" + service: service-foo + observability: + metrics: false + accessLogs: false + tracing: false + traceVerbosity: detailed +``` + +```yaml tab="Structured (TOML)" +[http.routers.my-router] + rule = "Path(`/foo`)" + service = "service-foo" + + [http.routers.my-router.observability] + metrics = false + accessLogs = false + tracing = false + traceVerbosity = "detailed" +``` + +```yaml tab="Labels" +labels: + - "traefik.http.routers.my-router.rule=Path(`/foo`)" + - "traefik.http.routers.my-router.service=service-foo" + - "traefik.http.routers.my-router.observability.metrics=false" + - "traefik.http.routers.my-router.observability.accessLogs=false" + - "traefik.http.routers.my-router.observability.tracing=false" + - "traefik.http.routers.my-router.observability.traceVerbosity=detailed" +``` + +```json tab="Tags" +{ + // ... + "Tags": [ + "traefik.http.routers.my-router.rule=Path(`/foo`)", + "traefik.http.routers.my-router.service=service-foo", + "traefik.http.routers.my-router.observability.metrics=false", + "traefik.http.routers.my-router.observability.accessLogs=false", + "traefik.http.routers.my-router.observability.tracing=false", + "traefik.http.routers.my-router.observability.traceVerbosity=detailed" + ] +} +``` + +## Configuration Options + +| Field | Description | Default | Required | +|:-----------------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:----------|:---------| +| `accessLogs` | The `accessLogs` option controls whether the router will produce access-logs. | `true` | No | +| `metrics` | The `metrics` option controls whether the router will produce metrics. | `true` | No | +| `tracing` | The `tracing` option controls whether the router will produce traces. | `true` | No | +| `traceVerbosity` | The `traceVerbosity` option controls the tracing verbosity level for the router. Possible values: `minimal` (default), `detailed`. If not set, the value is inherited from the entryPoint. | `minimal` | No | + +#### traceVerbosity + +`observability.traceVerbosity` defines the tracing verbosity level for the router. + +Possible values are: + +- `minimal`: produces a single server span and one client span for each request processed by a router. +- `detailed`: enables the creation of additional spans for each middleware executed for each request processed by a router. diff --git a/docs/content/reference/routing-configuration/http/routing/router.md b/docs/content/reference/routing-configuration/http/routing/router.md new file mode 100644 index 000000000..2e9a7eb56 --- /dev/null +++ b/docs/content/reference/routing-configuration/http/routing/router.md @@ -0,0 +1,116 @@ +--- +title: "Traefik HTTP Routers Documentation" +description: "HTTP routers are responsible for connecting incoming requests to the services that can handle them. Read the technical documentation." +--- + +## HTTP Router + +An HTTP router is in charge of connecting incoming requests to the services that can handle them. Routers analyze incoming requests based on rules, and when a match is found, forward the request through any configured middlewares to the appropriate service. + +## Configuration Example + +```yaml tab="Structured (YAML)" +http: + routers: + my-router: + entryPoints: + - "web" + - "websecure" + rule: "Host(`example.com`) && Path(`/api`)" + priority: 10 + middlewares: + - "auth" + - "ratelimit" + tls: + certResolver: "letsencrypt" + options: "modern" + domains: + - main: "example.com" + sans: + - "www.example.com" + observability: + metrics: true + accessLogs: true + tracing: true + service: my-service +``` + +```toml tab="Structured (TOML)" +[http.routers] + [http.routers.my-router] + entryPoints = ["web", "websecure"] + rule = "Host(`example.com`) && Path(`/api`)" + priority = 10 + middlewares = ["auth", "ratelimit"] + service = "my-service" + + [http.routers.my-router.tls] + certResolver = "letsencrypt" + options = "modern" + + [[http.routers.my-router.tls.domains]] + main = "example.com" + sans = ["www.example.com"] + + [http.routers.my-router.observability] + metrics = true + accessLogs = true + tracing = true +``` + +```yaml tab="Labels" +labels: + - "traefik.http.routers.my-router.entrypoints=web,websecure" + - "traefik.http.routers.my-router.rule=Host(`example.com`) && Path(`/api`)" + - "traefik.http.routers.my-router.priority=10" + - "traefik.http.routers.my-router.middlewares=auth,ratelimit" + - "traefik.http.routers.my-router.service=my-service" + - "traefik.http.routers.my-router.tls.certresolver=letsencrypt" + - "traefik.http.routers.my-router.tls.options=modern" + - "traefik.http.routers.my-router.tls.domains[0].main=example.com" + - "traefik.http.routers.my-router.tls.domains[0].sans=www.example.com" + - "traefik.http.routers.my-router.observability.metrics=true" + - "traefik.http.routers.my-router.observability.accessLogs=true" + - "traefik.http.routers.my-router.observability.tracing=true" +``` + +```json tab="Tags" +{ + "Tags": [ + "traefik.http.routers.my-router.entrypoints=web,websecure", + "traefik.http.routers.my-router.rule=Host(`example.com`) && Path(`/api`)", + "traefik.http.routers.my-router.priority=10", + "traefik.http.routers.my-router.middlewares=auth,ratelimit", + "traefik.http.routers.my-router.service=my-service", + "traefik.http.routers.my-router.tls.certresolver=letsencrypt", + "traefik.http.routers.my-router.tls.options=modern", + "traefik.http.routers.my-router.tls.domains[0].main=example.com", + "traefik.http.routers.my-router.tls.domains[0].sans=www.example.com", + "traefik.http.routers.my-router.observability.metrics=true", + "traefik.http.routers.my-router.observability.accessLogs=true", + "traefik.http.routers.my-router.observability.tracing=true" + ] +} +``` + +## Configuration Options + +| Field | Description | Default | Required | +|----------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------|----------| +| `entryPoints` | The list of entry points to which the router is attached. If not specified, HTTP routers are attached to all entry points. | All entry points | No | +| `rule` | 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. See [Rules & Priority](./rules-and-priority.md) for details. | | Yes | +| `priority` | 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. See [Rules & Priority](./rules-and-priority.md) for details. | Rule length | No | +| `middlewares` | The list of middlewares that are applied to the router. Middlewares are applied in the order they are declared. See [Middlewares overview](../middlewares/overview.md) for available middlewares. | | No | +| `tls` | TLS configuration for the router. When specified, the router will only handle HTTPS requests. | | No | +| `tls.certResolver` | The name of the certificate resolver to use for automatic certificate generation. See [Certificate Resolver](../tls/overview.md#certificate-resolver) for details. | | No | +| `tls.options` | The name of the TLS options to use for configuring TLS parameters (cipher suites, min/max TLS version, client authentication, etc.). See [TLS Options](../tls/tls-options.md) for detailed configuration. | `default` | No | +| `tls.domains` | List of domains and Subject Alternative Names (SANs) for explicit certificate domain specification. When using ACME certificate resolvers, domains are automatically extracted from router rules, making this option optional. | | No | +| `observability` | Observability configuration for the router. Allows fine-grained control over access logs, metrics, and tracing per router. See [Observability](./observability.md) for details. | Inherited from entry points | No | +| `service` | The name of the service that will handle the matched requests. Services can be load balancer services, weighted round robin, mirroring, or failover services. See [Service](../load-balancing/service.md) for details. | | Yes | + +## Router Naming + +- The character `@` is not authorized in the router name +- In provider-specific configurations (Docker, Kubernetes), router names are often auto-generated based on service names and rules + +{!traefik-for-business-applications.md!} diff --git a/docs/content/reference/routing-configuration/http/routing/rules-and-priority.md b/docs/content/reference/routing-configuration/http/routing/rules-and-priority.md new file mode 100644 index 000000000..7e15cb954 --- /dev/null +++ b/docs/content/reference/routing-configuration/http/routing/rules-and-priority.md @@ -0,0 +1,296 @@ +--- +title: "Traefik HTTP Routers Rules & Priority Documentation" +description: "In Traefik Proxy, an HTTP router is in charge of connecting incoming requests to the Services that can handle them. Read the technical documentation." +--- + +An HTTP router is in charge of connecting incoming requests to the services that can handle them. Traefik allows you to define your matching rules and [prioritize](#priority-calculation) the routes. + +## Rules + +Rules are a set of matchers configured with values, that determine if a particular request matches a specific criteria. +If the rule is verified, the router becomes active, calls middlewares, and then forwards the request to the service. + +- The character `@` is not authorized in the router 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). +- Regular Expressions: + - Matchers that accept a regexp as their value use a [Go](https://golang.org/pkg/regexp/) flavored syntax. + - The usual `AND` (&&) and `OR` (||) logical operators can be used, with the expected precedence rules, as well as parentheses to express complex rules. + - The `NOT` (!) operator allows you to invert the matcher. + +The table below lists all the available matchers: + +| Matcher | 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. | + +### Header and HeaderRegexp + +The `Header` and `HeaderRegexp` matchers allow matching requests that contain specific header. + +| Behavior | Rule | +|-----------------------------------------------------------------|:------------------------------------------------------------------------| +| Match requests with a `Content-Type` header set to `application/yaml`. | ```Header(`Content-Type`, `application/yaml`)``` | +| Match requests with a `Content-Type` header set to either `application/json` or `application/yaml`. | ```HeaderRegexp(`Content-Type`, `^application/(json\|yaml)$`)``` | +| Match headers [case-insensitively](https://en.wikipedia.org/wiki/Case_sensitivity). | ```HeaderRegexp(`Content-Type`, `(?i)^application/(json\|yaml)$`)``` | + +### Host and HostRegexp + +The `Host` and `HostRegexp` matchers allow matching requests that are targeted to a given host. + +These matchers do not support non-ASCII characters, use punycode encoded values ([rfc 3492](https://tools.ietf.org/html/rfc3492)) to match such domains. + +If no `Host` is set in the request URL (for example, it's an IP address), these matchers will look at the `Host` header. + +These matchers will match the request's host in lowercase. + +| Behavior | Rule | +|-----------------------------------------------------------------|:------------------------------------------------------------------------| +| Match requests with `Host` set to `example.com`. | ```Host(`example.com`)``` | +| Match requests sent to any subdomain of `example.com`. | ```HostRegexp(`^.+\.example\.com$`)``` | +| Match requests with `Host` set to either `example.com` or `example.org`. | ```HostRegexp(`^example\.(com\|org)$`)``` | +| Match `Host` [case-insensitively](https://en.wikipedia.org/wiki/Case_sensitivity). | ```HostRegexp(`(?i)^example\.(com\|org)$`)``` | + +### Method + +The `Method` matchers allows matching requests sent based on their HTTP method (also known as request verb). + +| Behavior | Rule | +|-----------------------------------------------------------------|:------------------------------------------------------------------------| +| Match `OPTIONS` requests. | ```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`. + +| Behavior | Rule | +|-----------------------------------------------------------------|:------------------------------------------------------------------------| +| Match `/products` but neither `/products/shoes` nor `/products/`. | ```Path(`/products`)``` | +| Match `/products` as well as everything under `/products`, such as `/products/shoes`, `/products/` but also `/products-for-sale`. | ```PathPrefix(`/products`)``` | +| Match both `/products/shoes` and `/products/socks` with and ID like `/products/shoes/31`. | ```PathRegexp(`^/products/(shoes\|socks)/[0-9]+$`)``` | +| Match requests with a path ending in either `.jpeg`, `.jpg` or `.png`. | ```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). | ```PathRegexp(`(?i)^/products`)``` | + +### Query and QueryRegexp + +The `Query` and `QueryRegexp` matchers allow matching requests based on query parameters. + +| Behavior | Rule | +|-----------------------------------------------------------------|:------------------------------------------------------------------------| +| Match requests with a `mobile` query parameter set to `true`, such as in `/search?mobile=true`. | ```Query(`mobile`, `true`)``` | +| Match requests with a query parameter `mobile` that has no value, such as in `/search?mobile`. | ```Query(`mobile`)``` | +| Match requests with a `mobile` query parameter set to either `true` or `yes`. | ```QueryRegexp(`mobile`, `^(true\|yes)$`)``` | +| Match requests with a `mobile` query parameter set to any value (including the empty value). | ```QueryRegexp(`mobile`, `^.*$`)``` | +| Match query parameters [case-insensitively](https://en.wikipedia.org/wiki/Case_sensitivity). | ```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. + +| Behavior | Rule | +|-----------------------------------------------------------------|:------------------------------------------------------------------------| +| Match requests coming from a given IP (IPv4). | ```ClientIP(`10.76.105.11`)``` | +| Match requests coming from a given IP (IPv6). | ```ClientIP(`::1`)``` | +| Match requests coming from a given subnet (IPv4). | ```ClientIP(`192.168.1.0/24`)``` | +| Match requests coming from a given subnet (IPv6). | ```ClientIP(`fe80::/10`)``` | + +### RuleSyntax + +!!! warning + + RuleSyntax option is deprecated and will be removed in the next major version. + Please do not use this field and rewrite the router rules to use the v3 syntax. + +In Traefik v3 a new rule syntax has been introduced ([migration guide](../../../../migrate/v3.md)). the `ruleSyntax` option allows to configure the rule syntax to be used for parsing the rule on a per-router basis. This allows to have heterogeneous router configurations and ease migration. + +The default value of the `ruleSyntax` option is inherited from the `defaultRuleSyntax` option in the install configuration (formerly known as static configuration). By default, the `defaultRuleSyntax` static option is v3, meaning that the default rule syntax is also v3 + +#### Configuration Example + +The configuration below uses the [File Provider (Structured)](../../../install-configuration/providers/others/file.md) to configure the `ruleSyntax` to allow `Router-v2` to use v2 syntax, while for `Router-v3` it is configured to use v3 syntax. + +```yaml tab="Structured (YAML)" +## Dynamic configuration +http: + routers: + Router-v3: + rule: HostRegexp(`[a-z]+\\.traefik\\.com`) + ruleSyntax: v3 + Router-v2: + rule: HostRegexp(`{subdomain:[a-z]+}.traefik.com`) + ruleSyntax: v2 +``` + +```toml tab="Structured (TOML)" +## Dynamic configuration +[http.routers] + [http.routers.Router-v3] + rule = "HostRegexp(`[a-z]+\\.traefik\\.com`)" + ruleSyntax = v3 + [http.routers.Router-v2] + rule = "HostRegexp(`{subdomain:[a-z]+}.traefik.com`)" + ruleSyntax = v2 +``` + +```yaml tab="Labels" +labels: + - "traefik.http.routers.Router-v3.rule=HostRegexp(`[a-z]+\\.traefik\\.com`)" + - "traefik.http.routers.Router-v3.ruleSyntax=v3" + - "traefik.http.routers.Router-v2.rule=HostRegexp(`{subdomain:[a-z]+}.traefik.com`)" + - "traefik.http.routers.Router-v2.ruleSyntax=v2" +``` + +```json tab="Tags" +{ + // ... + "Tags": [ + "traefik.http.routers.Router-v3.rule=HostRegexp(`[a-z]+\\.traefik\\.com`)", + "traefik.http.routers.Router-v3.ruleSyntax=v3" + "traefik.http.routers.Router-v2.rule=HostRegexp(`{subdomain:[a-z]+}.traefik.com`)", + "traefik.http.routers.Router-v2.ruleSyntax=v2" + ] +}, +``` + +## Priority Calculation + +??? info "How default priorities are computed" + + ```yaml tab="Structured (YAML)" + http: + routers: + Router-1: + rule: "HostRegexp(`[a-z]+\.traefik\.com`)" + # ... + Router-2: + rule: "Host(`foobar.traefik.com`)" + # ... + ``` + + ```toml tab="Structured (TOML)" + [http.routers] + [http.routers.Router-1] + rule = "HostRegexp(`[a-z]+\\.traefik\\.com`)" + # ... + [http.routers.Router-2] + rule = "Host(`foobar.traefik.com`)" + # ... + ``` + + ```yaml tab="Labels" + labels: + - "traefik.http.routers.Router-1.rule=HostRegexp(`[a-z]+\\.traefik\\.com`)" + - "traefik.http.routers.Router-2.rule=Host(`foobar.traefik.com`)" + ``` + + ```json tab="Tags" + { + // ... + "Tags": [ + "traefik.http.routers.Router-1.rule=HostRegexp(`[a-z]+\\.traefik\\.com`)", + "traefik.http.routers.Router-2.rule=Host(`foobar.traefik.com`)" + ] + } + ``` + + 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(`[a-z]+\.traefik\.com`)``` | 34 | + | Router-2 | ```Host(`foobar.traefik.com`)``` | 26 | + + The previous table shows that `Router-1` has a higher priority than `Router-2`. + + To solve this issue, the priority must be set. + +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. + +Traefik reserves a range of priorities for its internal routers, the maximum user-defined router priority value is: + +- `(MaxInt32 - 1000)` for 32-bit platforms, +- `(MaxInt64 - 1000)` for 64-bit platforms. + +### Example + +```yaml tab="Structured (YAML)" +## Dynamic configuration +http: + routers: + Router-1: + rule: "HostRegexp(`[a-z]+\\.traefik\\.com`)" + entryPoints: + - "web" + service: service-1 + priority: 1 + Router-2: + rule: "Host(`foobar.traefik.com`)" + entryPoints: + - "web" + priority: 2 + service: service-2 +``` + +```toml tab="Structured (TOML)" +## Dynamic configuration +[http.routers] + [http.routers.Router-1] + rule = "HostRegexp(`[a-z]+\\.traefik\\.com`)" + entryPoints = ["web"] + service = "service-1" + priority = 1 + [http.routers.Router-2] + rule = "Host(`foobar.traefik.com`)" + entryPoints = ["web"] + priority = 2 + service = "service-2" +``` + +```yaml tab="Labels" +labels: + - "traefik.http.routers.Router-1.rule=HostRegexp(`[a-z]+\\.traefik\\.com`)" + - "traefik.http.routers.Router-1.entryPoints=web" + - "traefik.http.routers.Router-1.service=service-1" + - "traefik.http.routers.Router-1.priority=1" + - "traefik.http.routers.Router-2.rule=Host(`foobar.traefik.com`)" + - "traefik.http.routers.Router-2.entryPoints=web" + - "traefik.http.routers.Router-2.service=service-2" + - "traefik.http.routers.Router-2.priority=2" +``` + +```json tab="Tags" + { + // ... + "Tags": [ + "traefik.http.routers.Router-1.rule=HostRegexp(`[a-z]+\\.traefik\\.com`)", + "traefik.http.routers.Router-1.entryPoints=web", + "traefik.http.routers.Router-1.service=service-1", + "traefik.http.routers.Router-1.priority=1" + "traefik.http.routers.Router-2.rule=Host(`foobar.traefik.com`)", + "traefik.http.routers.Router-2.entryPoints=web", + "traefik.http.routers.Router-2.service=service-2", + "traefik.http.routers.Router-2.priority=2" + ] + } +``` + +In the example above, the priority is configured to allow `Router-2` to handle requests with the `foobar.traefik.com` host. diff --git a/docs/content/reference/routing-configuration/http/tls/overview.md b/docs/content/reference/routing-configuration/http/tls/overview.md index 3c9a3b712..c1e1a6892 100644 --- a/docs/content/reference/routing-configuration/http/tls/overview.md +++ b/docs/content/reference/routing-configuration/http/tls/overview.md @@ -1,10 +1,103 @@ --- -title: "Traefik TLS Documentation" -description: "Learn how to configure the transport layer security (TLS) connection in Traefik Proxy. Read the technical documentation." +title: "Traefik HTTP TLS Documentation" +description: "Learn how to configure the transport layer security (TLS) connection for HTTP services in Traefik Proxy. Read the technical documentation." --- -Traefik's TLS configuration defines how TLS negotiation is handled for incoming connections. +## General -The next section of this documentation explains how to configure TLS connections through a definition in the dynamic configuration and how to configure TLS options, and certificates stores. +When an HTTP router is configured to handle HTTPS traffic, include a `tls` field in its definition. +This field tells Traefik that the router should process only TLS requests and ignore non-TLS traffic. + +By default, an HTTP router with a TLS field will terminate the TLS connections, +meaning that it will send decrypted data to the services. +The TLS configuration provides several options for fine-tuning the TLS behavior, +including automatic certificate generation, custom TLS options, and explicit domain specification. + +## Configuration Example + +```yaml tab="Structured (YAML)" +http: + routers: + my-https-router: + rule: "Host(`example.com`) && Path(`/api`)" + service: "my-http-service" + tls: + certResolver: "letsencrypt" + options: "modern-tls" + domains: + - main: "example.com" + sans: + - "www.example.com" + - "api.example.com" +``` + +```toml tab="Structured (TOML)" +[http.routers.my-https-router] + rule = "Host(`example.com`) && Path(`/api`)" + service = "my-http-service" + + [http.routers.my-https-router.tls] + certResolver = "letsencrypt" + options = "modern-tls" + + [[http.routers.my-https-router.tls.domains]] + main = "example.com" + sans = ["www.example.com", "api.example.com"] +``` + +```yaml tab="Labels" +labels: + - "traefik.http.routers.my-https-router.rule=Host(`example.com`) && Path(`/api`)" + - "traefik.http.routers.my-https-router.service=my-http-service" + - "traefik.http.routers.my-https-router.tls=true" + - "traefik.http.routers.my-https-router.tls.certresolver=letsencrypt" + - "traefik.http.routers.my-https-router.tls.options=modern-tls" + - "traefik.http.routers.my-https-router.tls.domains[0].main=example.com" + - "traefik.http.routers.my-https-router.tls.domains[0].sans=www.example.com,api.example.com" +``` + +```json tab="Tags" +{ + "Tags": [ + "traefik.http.routers.my-https-router.rule=Host(`example.com`) && Path(`/api`)", + "traefik.http.routers.my-https-router.service=my-http-service", + "traefik.http.routers.my-https-router.tls=true", + "traefik.http.routers.my-https-router.tls.certresolver=letsencrypt", + "traefik.http.routers.my-https-router.tls.options=modern-tls", + "traefik.http.routers.my-https-router.tls.domains[0].main=example.com", + "traefik.http.routers.my-https-router.tls.domains[0].sans=www.example.com,api.example.com" + ] +} +``` + +## Configuration Options + +| Field | Description | Default | Required | +|:-----------------------------------------------------------------------------------|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:----------|:---------| +| `options` | The name of the TLS options to use for configuring TLS parameters (cipher suites, min/max TLS version, client authentication, etc.). See [TLS Options](./tls-options.md) for detailed configuration. | `default` | No | +| `certResolver` | The name of the certificate resolver to use for automatic certificate generation via ACME providers (such as Let's Encrypt). See the [Certificate Resolver](./#certificate-resolver) section for more details. | "" | No | +| `domains` | List of domains and Subject Alternative Names (SANs) for explicit certificate domain specification. See the [Custom Domains](./#custom-domains) section for more details. | [] | No | + +## Certificate Resolver + +The `tls.certResolver` option allows you to specify a certificate resolver for automatic certificate generation via ACME providers (such as Let's Encrypt). + +When a certificate resolver is configured for a router, +Traefik will automatically obtain and manage TLS certificates for the domains specified in the router's rule (in the `Host` matcher) or in the `tls.domains` configuration (with `tls.domains` taking precedence). + +!!! important "Prerequisites" + + - Certificate resolvers must be defined in the [static configuration](../../../install-configuration/tls/certificate-resolvers/acme.md) + - The router must have `tls` enabled + - An ACME challenge type must be configured for the certificate resolver + +## Custom Domains + +When using ACME certificate resolvers, domains are automatically extracted from router rules, +but the `tls.domains` option allows you to explicitly specify the domains and Subject Alternative Names (SANs) for which certificates should be generated. + +This provides fine-grained control over certificate generation and takes precedence over domains automatically extracted from router rules. + +Every domain must have A/AAAA records pointing to Traefik. {!traefik-for-business-applications.md!} diff --git a/docs/content/reference/routing-configuration/http/tls/tls-options.md b/docs/content/reference/routing-configuration/http/tls/tls-options.md index 992b3497c..939edf91e 100644 --- a/docs/content/reference/routing-configuration/http/tls/tls-options.md +++ b/docs/content/reference/routing-configuration/http/tls/tls-options.md @@ -106,7 +106,7 @@ tls: ### Curve Preferences -This option allows to set the preferred elliptic curves in a specific order. +This option allows to set the preferred elliptic curves. The names of the curves defined by [`crypto`](https://godoc.org/crypto/tls#CurveID) (e.g. `CurveP521`) and the [RFC defined names](https://tools.ietf.org/html/rfc8446#section-4.2.7) (e. g. `secp521r1`) can be used. @@ -188,17 +188,17 @@ Traefik supports mutual authentication, through the `clientAuth` section. For authentication policies that require verification of the client certificate, the certificate authority for the certificates should be set in `clientAuth.caFiles`. -In Kubernetes environment, CA certificate can be set in `clientAuth.secretNames`. See [TLSOption resource](../../kubernetes/crd/http/tlsoption.md) for more details. +In Kubernetes environment, CA certificate can be set in `clientAuth.secretNames`. See [TLSOption resource](../../kubernetes/crd/tls/tlsoption.md) for more details. The `clientAuth.clientAuthType` option governs the behaviour as follows: | Option | Operation | | --------- | ----------- | -| `NoClientCert` | Disregards any client certificate.| -| `RequestClientCert` | Asks for a certificate but proceeds anyway if none is provided. | -| `RequireAnyClientCert` | Requires a certificate but does not verify if it is signed by a CA listed in `clientAuth.caFiles` or in `clientAuth.secretNames`. | -| `VerifyClientCertIfGiven` | If a certificate is provided, verifies if it is signed by a CA listed in `clientAuth.caFiles` or in `clientAuth.secretNames`. Otherwise proceeds without any certificate. | -| `RequireAndVerifyClientCert` | requires a certificate, which must be signed by a CA listed in `clientAuth.caFiles` or in `clientAuth.secretNames`. | +| `NoClientCert` | Disregards any client certificate.| +| `RequestClientCert` | Asks for a certificate but proceeds anyway if none is provided. | +| `RequireAnyClientCert` | Requires a certificate but does not verify if it is signed by a CA listed in `clientAuth.caFiles` or in `clientAuth.secretNames`. | +| `VerifyClientCertIfGiven` | If a certificate is provided, verifies if it is signed by a CA listed in `clientAuth.caFiles` or in `clientAuth.secretNames`. Otherwise proceeds without any certificate. | +| `RequireAndVerifyClientCert` | requires a certificate, which must be signed by a CA listed in `clientAuth.caFiles` or in `clientAuth.secretNames`. | ```yaml tab="Structured (YAML)" # Dynamic configuration diff --git a/docs/content/reference/routing-configuration/kubernetes/crd/http/ingressroute.md b/docs/content/reference/routing-configuration/kubernetes/crd/http/ingressroute.md index 28651bb89..4328ebfd1 100644 --- a/docs/content/reference/routing-configuration/kubernetes/crd/http/ingressroute.md +++ b/docs/content/reference/routing-configuration/kubernetes/crd/http/ingressroute.md @@ -3,7 +3,7 @@ title: "Kubernetes IngressRoute" description: "An IngressRoute is a Traefik CRD is in charge of connecting incoming requests to the Services that can handle them in HTTP." --- -`IngressRoute` is the CRD implementation of a [Traefik HTTP router](../../../http/router/rules-and-priority.md). +`IngressRoute` is the CRD implementation of a [Traefik HTTP router](../../../http/routing/rules-and-priority.md). Before creating `IngressRoute` objects, you need to apply the [Traefik Kubernetes CRDs](https://doc.traefik.io/traefik/reference/dynamic-configuration/kubernetes-crd/#definitions) to your Kubernetes cluster. @@ -36,7 +36,7 @@ spec: accessLogs: true metrics: true tracing: true - # Set a pirority + # Set a priority priority: 10 services: # Target a Kubernetes Support @@ -74,164 +74,29 @@ spec: ## Configuration Options -| Field | Description | Default | Required | -|:------|:----------------------------------------------------------|:---------------------|:---------| -| `entryPoints` | List of [entry points](../../../../install-configuration/entrypoints.md) names.
If not specified, HTTP routers will accept requests from all EntryPoints in the list of default EntryPoints. | | No | -| `routes` | List of routes. | | Yes | -| `routes[n].kind` | Kind of router matching, only `Rule` is allowed yet. | "Rule" | No | -| `routes[n].match` | Defines the [rule](../../../http/router/rules-and-priority.md#rules) corresponding to an underlying router. | | Yes | -| `routes[n].priority` | Defines the [priority](../../../http/router/rules-and-priority.md#priority-calculation) to disambiguate rules of the same length, for route matching.
If not set, 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, the default rules length sorting is used. | 0 | No | -| `routes[n].middlewares` | List of middlewares to attach to the IngressRoute.
More information [here](#middleware). | "" | No | -| `routes[n].`
`middlewares[m].`
`name` | Middleware name.
The character `@` is not authorized.
More information [here](#middleware). | | Yes | -| `routes[n].`
`middlewares[m].`
`namespace` | Middleware namespace.
Can be empty if the middleware belongs to the same namespace as the IngressRoute.
More information [here](#middleware). | | No | -| `routes[n].`
`observability.`
`accesslogs` | Defines whether the route will produce [access-logs](../../../../install-configuration/observability/logs-and-accesslogs.md). See [here](../../../http/router/observability.md) for more information. | false | No | -| `routes[n].`
`observability.`
`metrics` | Defines whether the route will produce [metrics](../../../../install-configuration/observability/metrics.md). See [here](../../../http/router/observability.md) for more information. | false | No | -| `routes[n].`
`observability.`
`tracing` | Defines whether the route will produce [traces](../../../../install-configuration/observability/tracing.md). See [here](../../../http/router/observability.md) for more information. | false | No | -| `routes[n].`
`services` | List of any combination of TraefikService and [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/).
More information [here](#externalname-service). | | No | -| `routes[n].`
`services[m].`
`kind` | Kind of the service targeted.
Two values allowed:
- **Service**: Kubernetes Service
**TraefikService**: Traefik Service.
More information [here](#externalname-service). | "Service" | No | -| `routes[n].`
`services[m].`
`name` | Service name.
The character `@` is not authorized.
More information [here](#middleware). | | Yes | -| `routes[n].`
`services[m].`
`namespace` | Service namespace.
Can be empty if the service belongs to the same namespace as the IngressRoute.
More information [here](#externalname-service). | | No | -| `routes[n].`
`services[m].`
`port` | Service port (number or port name).
Evaluated only if the kind is **Service**. | | No | -| `routes[n].`
`services[m].`
`responseForwarding.`
`flushInterval` | Interval, in milliseconds, in between flushes to the client while copying the response body.
A negative value means to flush immediately after each write to the client.
This configuration is ignored when a response is a streaming response; for such responses, writes are flushed to the client immediately.
Evaluated only if the kind is **Service**. | 100ms | No | -| `routes[n].`
`services[m].`
`scheme` | Scheme to use for the request to the upstream Kubernetes Service.
Evaluated only if the kind is **Service**. | "http"
"https" if `port` is 443 or contains the string *https*. | No | -| `routes[n].`
`services[m].`
`serversTransport` | Name of ServersTransport resource to use to configure the transport between Traefik and your servers.
Evaluated only if the kind is **Service**. | "" | No | -| `routes[n].`
`services[m].`
`passHostHeader` | Forward client Host header to server.
Evaluated only if the kind is **Service**. | true | No | -| `routes[n].`
`services[m].`
`healthCheck.scheme` | Server URL scheme for the health check endpoint.
Evaluated only if the kind is **Service**.
Only for [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) of type [ExternalName](#externalname-service). | "" | No | -| `routes[n].`
`services[m].`
`healthCheck.mode` | Health check mode.
If defined to grpc, will use the gRPC health check protocol to probe the server.
Evaluated only if the kind is **Service**.
Only for [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) of type [ExternalName](#externalname-service). | "http" | No | -| `routes[n].`
`services[m].`
`healthCheck.path` | Server URL path for the health check endpoint.
Evaluated only if the kind is **Service**.
Only for [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) of type [ExternalName](#externalname-service). | "" | No | -| `routes[n].`
`services[m].`
`healthCheck.interval` | Frequency of the health check calls.
Evaluated only if the kind is **Service**.
Only for [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) of type [ExternalName](#externalname-service). | "100ms" | No | -| `routes[n].`
`services[m].`
`healthCheck.method` | HTTP method for the health check endpoint.
Evaluated only if the kind is **Service**.
Only for [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) of type [ExternalName](#externalname-service). | "GET" | No | -| `routes[n].`
`services[m].`
`healthCheck.status` | Expected HTTP status code of the response to the health check request.
Only for [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) of type ExternalName.
If not set, expect a status between 200 and 399.
Evaluated only if the kind is **Service**. | | No | -| `routes[n].`
`services[m].`
`healthCheck.port` | URL port for the health check endpoint.
Evaluated only if the kind is **Service**.
Only for [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) of type [ExternalName](#externalname-service). | | No | -| `routes[n].`
`services[m].`
`healthCheck.timeout` | Maximum duration to wait before considering the server unhealthy.
Evaluated only if the kind is **Service**.
Only for [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) of type [ExternalName](#externalname-service). | "5s" | No | -| `routes[n].`
`services[m].`
`healthCheck.hostname` | Value in the Host header of the health check request.
Evaluated only if the kind is **Service**.
Only for [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) of type [ExternalName](#externalname-service). | "" | No | -| `routes[n].`
`services[m].`
`healthCheck.`
`followRedirect` | Follow the redirections during the healtchcheck.
Evaluated only if the kind is **Service**.
Only for [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) of type [ExternalName](#externalname-service). | true | No | -| `routes[n].`
`services[m].`
`healthCheck.headers` | Map of header to send to the health check endpoint
Evaluated only if the kind is **Service**.
Only for [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) of type [ExternalName](#externalname-service)). | | No | -| `routes[n].`
`services[m].`
`sticky.`
`cookie.name` | Name of the cookie used for the stickiness.
When sticky sessions are enabled, a `Set-Cookie` header is set on the initial response to let the client know which server handles the first response.
On subsequent requests, to keep the session alive with the same server, the client should send the cookie with the value set.
If the server pecified in the cookie becomes unhealthy, the request will be forwarded to a new server (and the cookie will keep track of the new server).
Evaluated only if the kind is **Service**. | "" | No | -| `routes[n].`
`services[m].`
`sticky.`
`cookie.httpOnly` | Allow the cookie can be accessed by client-side APIs, such as JavaScript.
Evaluated only if the kind is **Service**. | false | No | -| `routes[n].`
`services[m].`
`sticky.`
`cookie.secure` | Allow the cookie can only be transmitted over an encrypted connection (i.e. HTTPS).
Evaluated only if the kind is **Service**. | false | No | -| `routes[n].`
`services[m].`
`sticky.`
`cookie.sameSite` | [SameSite](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite) policy
Allowed values:
-`none`
-`lax`
`strict`
Evaluated only if the kind is **Service**. | "" | No | -| `routes[n].`
`services[m].`
`sticky.`
`cookie.maxAge` | Number of seconds until the cookie expires.
Negative number, the cookie expires immediately.
0, the cookie never expires.
Evaluated only if the kind is **Service**. | 0 | No | -| `routes[n].`
`services[m].`
`strategy` | Load balancing strategy between the servers.
RoundRobin is the only supported value yet.
Evaluated only if the kind is **Service**. | "RoundRobin" | No | -| `routes[n].`
`services[m].`
`weight` | Service weight.
To use only to refer to WRR TraefikService | "" | No | -| `routes[n].`
`services[m].`
`nativeLB` | Allow using the Kubernetes Service load balancing between the pods instead of the one provided by Traefik.
Evaluated only if the kind is **Service**. | false | No | -| `routes[n].`
`services[m].`
`nodePortLB` | Use the nodePort IP address when the service type is NodePort.
It allows services to be reachable when Traefik runs externally from the Kubernetes cluster but within the same network of the nodes.
Evaluated only if the kind is **Service**. | false | No | -| `tls` | TLS configuration.
Can be an empty value(`{}`):
A self signed is generated in such a case
(or the [default certificate](tlsstore.md) is used if it is defined.) | | No | -| `tls.secretName` | [Secret](https://kubernetes.io/docs/concepts/configuration/secret/) name used to store the certificate (in the same namesapce as the `IngressRoute`) | "" | No | -| `tls.`
`options.name` | Name of the [`TLSOption`](tlsoption.md) to use.
More information [here](#tls-options). | "" | No | -| `tls.`
`options.namespace` | Namespace of the [`TLSOption`](tlsoption.md) to use. | "" | No | -| `tls.certResolver` | Name of the [Certificate Resolver](../../../../install-configuration/tls/certificate-resolvers/overview.md) to use to generate automatic TLS certificates. | "" | No | -| `tls.domains` | List of domains to serve using the certificates generates (one `tls.domain`= one certificate).
More information in the [dedicated section](../../../../install-configuration/tls/certificate-resolvers/acme.md#domain-definition). | | No | -| `tls.`
`domains[n].main` | Main domain name | "" | Yes | -| `tls.`
`domains[n].sans` | List of alternative domains (SANs) | | No | +| Field | Description | Default | Required | +|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:--------|:---------| +| `entryPoints` | List of [entry points](../../../../install-configuration/entrypoints.md) names.
If not specified, HTTP routers will accept requests from all EntryPoints in the list of default EntryPoints. | | No | +| `routes` | List of routes. | | Yes | +| `routes[n].kind` | Kind of router matching, only `Rule` is allowed yet. | "Rule" | No | +| `routes[n].match` | Defines the [rule](../../../http/routing/rules-and-priority.md#rules) corresponding to an underlying router. | | Yes | +| `routes[n].priority` | Defines the [priority](../../../http/routing/rules-and-priority.md#priority-calculation) to disambiguate rules of the same length, for route matching.
If not set, 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, the default rules length sorting is used. | 0 | No | +| `routes[n].middlewares` | List of middlewares to attach to the IngressRoute.
More information [here](#middleware). | "" | No | +| `routes[n].`
`middlewares[m].`
`name`
| Middleware name.
The character `@` is not authorized.
More information [here](#middleware). | | Yes | +| `routes[n].`
`middlewares[m].`
`namespace`
| Middleware namespace.
Can be empty if the middleware belongs to the same namespace as the IngressRoute.
More information [here](#middleware). | | No | +| `routes[n].`
`observability.`
`accesslogs`
| Defines whether the route will produce [access-logs](../../../../install-configuration/observability/logs-and-accesslogs.md). See [here](../../../http/routing/observability.md) for more information. | false | No | +| `routes[n].`
`observability.`
`metrics`
| Defines whether the route will produce [metrics](../../../../install-configuration/observability/metrics.md). See [here](../../../http/routing/observability.md) for more information. | false | No | +| `routes[n].`
`observability.`
`tracing`
| Defines whether the route will produce [traces](../../../../install-configuration/observability/tracing.md). See [here](../../../http/routing/observability.md) for more information. | false | No | +| `tls` | TLS configuration.
Can be an empty value(`{}`):
A self signed is generated in such a case
(or the [default certificate](../tls/tlsstore.md) is used if it is defined.) | | No | +| `routes[n].`
`services`
| List of any combination of [TraefikService](./traefikservice.md) and [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/).
Exhaustive list of option in the [`Service`](./service.md#configuration-options) documentation. | | No | +| `tls.secretName` | [Secret](https://kubernetes.io/docs/concepts/configuration/secret/) name used to store the certificate (in the same namesapce as the `IngressRoute`) | "" | No | +| `tls.`
`options.name`
| Name of the [`TLSOption`](../tls/tlsoption.md) to use.
More information [here](#tls-options). | "" | No | +| `tls.`
`options.namespace`
| Namespace of the [`TLSOption`](../tls/tlsoption.md) to use. | "" | No | +| `tls.certResolver` | Name of the [Certificate Resolver](../../../../install-configuration/tls/certificate-resolvers/overview.md) to use to generate automatic TLS certificates. | "" | No | +| `tls.domains` | List of domains to serve using the certificates generates (one `tls.domain`= one certificate).
More information in the [dedicated section](../../../../install-configuration/tls/certificate-resolvers/acme.md#domain-definition). | | No | +| `tls.`
`domains[n].main`
| Main domain name | "" | Yes | +| `tls.`
`domains[n].sans`
| List of alternative domains (SANs) | | No | -### ExternalName Service - -Traefik backends creation needs a port to be set, however Kubernetes [ExternalName Service](https://kubernetes.io/docs/concepts/services-networking/service/#externalname) could be defined without any port. Accordingly, Traefik supports defining a port in two ways: - -- only on `IngressRoute` service -- on both sides, you'll be warned if the ports don't match, and the `IngressRoute` service port is used - -Thus, in case of two sides port definition, Traefik expects a match between ports. - -=== "Ports defined on Resource" - - ```yaml tab="IngressRoute" - apiVersion: traefik.io/v1alpha1 - kind: IngressRoute - metadata: - name: test.route - namespace: apps - - spec: - entryPoints: - - foo - routes: - - match: Host(`example.net`) - kind: Rule - services: - - name: external-svc - port: 80 - ``` - - ```yaml tab="Service ExternalName" - apiVersion: v1 - kind: Service - metadata: - name: external-svc - namespace: apps - - spec: - externalName: external.domain - type: ExternalName - ``` - -=== "Port defined on the Service" - - ```yaml tab="IngressRoute" - apiVersion: traefik.io/v1alpha1 - kind: IngressRoute - metadata: - name: test.route - namespace: apps - - spec: - entryPoints: - - foo - routes: - - match: Host(`example.net`) - kind: Rule - services: - - name: external-svc - ``` - - ```yaml tab="Service ExternalName" - apiVersion: v1 - kind: Service - metadata: - name: external-svc - namespace: apps - - spec: - externalName: external.domain - type: ExternalName - ports: - - port: 80 - ``` - -=== "Port defined on both sides" - - ```yaml tab="IngressRoute" - apiVersion: traefik.io/v1alpha1 - kind: IngressRoute - metadata: - name: test.route - namespace: apps - - spec: - entryPoints: - - foo - routes: - - match: Host(`example.net`) - kind: Rule - services: - - name: external-svc - port: 80 - ``` - - ```yaml tab="Service ExternalName" - apiVersion: v1 - kind: Service - metadata: - name: external-svc - namespace: apps - - spec: - externalName: external.domain - type: ExternalName - ports: - - port: 80 - ``` ### Middleware @@ -281,114 +146,11 @@ same namespace as the IngressRoute) - `Service` (default value): to reference a [Kubernetes Service](https://kubernetes.io/docs/concepts/services-networking/service/) - `TraefikService`: to reference an object [`TraefikService`](../http/traefikservice.md) -### Port Definition - -Traefik backends creation needs a port to be set, however Kubernetes [ExternalName Service](https://kubernetes.io/docs/concepts/services-networking/service/#externalname) could be defined without any port. Accordingly, Traefik supports defining a port in two ways: - -- only on `IngressRoute` service -- on both sides, you'll be warned if the ports don't match, and the `IngressRoute` service port is used - -Thus, in case of two sides port definition, Traefik expects a match between ports. - -??? example - - ```yaml tab="IngressRoute" - --- - apiVersion: traefik.io/v1alpha1 - kind: IngressRoute - metadata: - name: test.route - namespace: default - - spec: - entryPoints: - - foo - - routes: - - match: Host(`example.net`) - kind: Rule - services: - - name: external-svc - port: 80 - - --- - apiVersion: v1 - kind: Service - metadata: - name: external-svc - namespace: default - spec: - externalName: external.domain - type: ExternalName - ``` - - ```yaml tab="ExternalName Service" - --- - apiVersion: traefik.io/v1alpha1 - kind: IngressRoute - metadata: - name: test.route - namespace: default - - spec: - entryPoints: - - foo - - routes: - - match: Host(`example.net`) - kind: Rule - services: - - name: external-svc - - --- - apiVersion: v1 - kind: Service - metadata: - name: external-svc - namespace: default - spec: - externalName: external.domain - type: ExternalName - ports: - - port: 80 - ``` - - ```yaml tab="Both sides" - --- - apiVersion: traefik.io/v1alpha1 - kind: IngressRoute - metadata: - name: test.route - namespace: default - - spec: - entryPoints: - - foo - - routes: - - match: Host(`example.net`) - kind: Rule - services: - - name: external-svc - port: 80 - - --- - apiVersion: v1 - kind: Service - metadata: - name: external-svc - namespace: default - spec: - externalName: external.domain - type: ExternalName - ports: - - port: 80 - ``` ### TLS Options The `options` field enables fine-grained control of the TLS parameters. -It refers to a [TLSOption](./tlsoption.md) and will be applied only if a `Host` +It refers to a [TLSOption](../tls/tlsoption.md) and will be applied only if a `Host` rule is defined. #### Server Name Association @@ -454,108 +216,3 @@ TLS options references, a conflict occurs, such as in the example below. If that happens, both mappings are discarded, and the host name (`example.net` in the example) for these routers gets associated with the default TLS options instead. - -### Load Balancing - -You can declare and use Kubernetes Service load balancing as detailed below: - -```yaml tab="IngressRoute" -apiVersion: traefik.io/v1alpha1 -kind: IngressRoute -metadata: - name: ingressroutebar - namespace: default - -spec: - entryPoints: - - web - routes: - - match: Host(`example.com`) && PathPrefix(`/foo`) - kind: Rule - services: - - name: svc1 - namespace: default - - name: svc2 - namespace: default -``` - -```yaml tab="K8s Service" -apiVersion: v1 -kind: Service -metadata: - name: svc1 - namespace: default - -spec: - ports: - - name: http - port: 80 - selector: - app: traefiklabs - task: app1 ---- -apiVersion: v1 -kind: Service -metadata: - name: svc2 - namespace: default - -spec: - ports: - - name: http - port: 80 - selector: - app: traefiklabs - task: app2 -``` - -!!! important "Kubernetes Service Native Load-Balancing" - - To avoid creating the server load-balancer with the pod IPs and use Kubernetes Service clusterIP directly, - one should set the service `NativeLB` option to true. - Please note that, by default, Traefik reuses the established connections to the backends for performance purposes. This can prevent the requests load balancing between the replicas from behaving as one would expect when the option is set. - By default, `NativeLB` is false. - - ??? example "Example" - - ```yaml - --- - apiVersion: traefik.io/v1alpha1 - kind: IngressRoute - metadata: - name: test.route - namespace: default - - spec: - entryPoints: - - foo - - routes: - - match: Host(`example.net`) - kind: Rule - services: - - name: svc - port: 80 - # Here, nativeLB instructs to build the server load-balancer with the Kubernetes Service clusterIP only. - nativeLB: true - - --- - apiVersion: v1 - kind: Service - metadata: - name: svc - namespace: default - spec: - type: ClusterIP - ... - ``` - -### Configuring Backend Protocol - -There are 3 ways to configure the backend protocol for communication between Traefik and your pods: - -- Setting the scheme explicitly (http/https/h2c) -- Configuring the name of the kubernetes service port to start with https (https) -- Setting the kubernetes service port to use port 443 (https) - -If you do not configure the above, Traefik will assume an http connection. diff --git a/docs/content/reference/routing-configuration/kubernetes/crd/http/serverstransport.md b/docs/content/reference/routing-configuration/kubernetes/crd/http/serverstransport.md index 7c364f685..63f5c04a2 100644 --- a/docs/content/reference/routing-configuration/kubernetes/crd/http/serverstransport.md +++ b/docs/content/reference/routing-configuration/kubernetes/crd/http/serverstransport.md @@ -55,18 +55,18 @@ spec: | Field | Description | Default | Required | |:------|:----------------------------------------------------------|:---------------------|:---------| -| `serverstransport.`
`serverName` | Defines the server name that will be used for SNI. | | No | -| `serverstransport.`
`insecureSkipVerify` | Controls whether the server's certificate chain and host name is verified. | false | No | -| `serverstransport.`
`rootcas` | Set of root certificate authorities to use when verifying server certificates. (for mTLS connections). | | No | -| `serverstransport.`
`certificatesSecrets` | Certificates to present to the server for mTLS. | | No | -| `serverstransport.`
`maxIdleConnsPerHost` | Maximum idle (keep-alive) connections to keep per-host. | 200 | No | -| `serverstransport.`
`disableHTTP2` | Disables HTTP/2 for connections with servers. | false | No | -| `serverstransport.`
`peerCertURI` | Defines the URI used to match against SAN URIs during the server's certificate verification. | "" | No | -| `serverstransport.`
`forwardingTimeouts.dialTimeout` | Amount of time to wait until a connection to a server can be established.
Zero means no timeout. | 30s | No | -| `serverstransport.`
`forwardingTimeouts.responseHeaderTimeout` | Amount of time to wait for a server's response headers after fully writing the request (including its body, if any).
Zero means no timeout | 0s | No | -| `serverstransport.`
`forwardingTimeouts.idleConnTimeout` | Maximum amount of time an idle (keep-alive) connection will remain idle before closing itself.
Zero means no timeout. | 90s | No | -| `serverstransport.`
`spiffe.ids` | Allow SPIFFE IDs.
This takes precedence over the SPIFFE TrustDomain. | | No | -| `serverstransport.`
`spiffe.trustDomain` | Allow SPIFFE trust domain. | "" | No | +| `serverstransport.`
`serverName`
| Defines the server name that will be used for SNI. | | No | +| `serverstransport.`
`insecureSkipVerify`
| Controls whether the server's certificate chain and host name is verified. | false | No | +| `serverstransport.`
`rootcas`
| Set of root certificate authorities to use when verifying server certificates. (for mTLS connections). | | No | +| `serverstransport.`
`certificatesSecrets`
| Certificates to present to the server for mTLS. | | No | +| `serverstransport.`
`maxIdleConnsPerHost`
| Maximum idle (keep-alive) connections to keep per-host. | 200 | No | +| `serverstransport.`
`disableHTTP2`
| Disables HTTP/2 for connections with servers. | false | No | +| `serverstransport.`
`peerCertURI`
| Defines the URI used to match against SAN URIs during the server's certificate verification. | "" | No | +| `serverstransport.`
`forwardingTimeouts.dialTimeout`
| Amount of time to wait until a connection to a server can be established.
Zero means no timeout. | 30s | No | +| `serverstransport.`
`forwardingTimeouts.responseHeaderTimeout`
| Amount of time to wait for a server's response headers after fully writing the request (including its body, if any).
Zero means no timeout | 0s | No | +| `serverstransport.`
`forwardingTimeouts.idleConnTimeout`
| Maximum amount of time an idle (keep-alive) connection will remain idle before closing itself.
Zero means no timeout. | 90s | No | +| `serverstransport.`
`spiffe.ids`
| Allow SPIFFE IDs.
This takes precedence over the SPIFFE TrustDomain. | | No | +| `serverstransport.`
`spiffe.trustDomain`
| Allow SPIFFE trust domain. | "" | No | !!! note "CA Secret" The CA secret must contain a base64 encoded certificate under either a tls.ca or a ca.crt key. diff --git a/docs/content/reference/routing-configuration/kubernetes/crd/http/service.md b/docs/content/reference/routing-configuration/kubernetes/crd/http/service.md new file mode 100644 index 000000000..b69515b25 --- /dev/null +++ b/docs/content/reference/routing-configuration/kubernetes/crd/http/service.md @@ -0,0 +1,430 @@ +--- +title: "Kubernetes Service" +description: "A Service is a not Traefik CRD, it allows you to describe the Service option in an IngressRoute or a Traefik Service." +--- + +`Service` is the implementation of a [Traefik HTTP service](../../../http/load-balancing/service.md). + +There is no dedicated CRD, a `Service` is part of: + +- [`IngressRoute`](./ingressroute.md) +- [`TraefikService`](./traefikservice.md) + +Note that, before creating `IngressRoute` or `TraefikService` objects, you need to apply the [Traefik Kubernetes CRDs](https://doc.traefik.io/traefik/reference/dynamic-configuration/kubernetes-crd/#definitions) to your Kubernetes cluster. + +This registers the Traefik-specific resources. + +## Configuration Example + +You can declare a `Service` either as part of an `IngressRoute` or a `TraefikService` as detailed below: + +```yaml tab="IngressRoute" +apiVersion: traefik.io/v1alpha1 +kind: IngressRoute +metadata: + name: test-name + namespace: apps +spec: + entryPoints: + - web + routes: + - kind: Rule + # Rule on the Host + match: Host(`test.example.com`) + services: + # Target a Kubernetes Service + - kind: Service + name: foo + namespace: apps + # Customize the connection between Traefik and the backend + passHostHeader: true + port: 80 + responseForwarding: + flushInterval: 1ms + scheme: https + sticky: + cookie: + httpOnly: true + name: cookie + secure: true + strategy: RoundRobin +``` + +```yaml tab="TraefikService" +apiVersion: traefik.io/v1alpha1 +kind: TraefikService +metadata: + name: wrr1 + namespace: apps + +spec: + weighted: + services: + # Target a Kubernetes Service + - kind: Service + name: foo + namespace: apps + # Customize the connection between Traefik and the backend + passHostHeader: true + port: 80 + responseForwarding: + flushInterval: 1ms + scheme: https + sticky: + cookie: + httpOnly: true + name: cookie + secure: true + strategy: RoundRobin +``` + +## Configuration Options + +| Field | Description | Default | Required | +|:---------------------------------------------------------------------------------|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:---------------------------------------------------------------------|:---------| +| `kind` | Kind of the service targeted.
Two values allowed:
- **Service**: Kubernetes Service
**TraefikService**: Traefik Service.
More information [here](#externalname-service). | "Service" | No | +| `name` | Service name.
The character `@` is not authorized.
More information [here](#middleware). | | Yes | +| `namespace` | Service namespace.
Can be empty if the service belongs to the same namespace as the IngressRoute.
More information [here](#externalname-service). | | No | +| `port` | Service port (number or port name).
Evaluated only if the kind is **Service**. | | No | +| `responseForwarding.`
`flushInterval`
| Interval, in milliseconds, in between flushes to the client while copying the response body.
A negative value means to flush immediately after each write to the client.
This configuration is ignored when a response is a streaming response; for such responses, writes are flushed to the client immediately.
Evaluated only if the kind is **Service**. | 100ms | No | +| `scheme` | Scheme to use for the request to the upstream Kubernetes Service.
Evaluated only if the kind is **Service**. | "http"
"https" if `port` is 443 or contains the string *https*. | No | +| `serversTransport` | Name of ServersTransport resource to use to configure the transport between Traefik and your servers.
Evaluated only if the kind is **Service**. | "" | No | +| `passHostHeader` | Forward client Host header to server.
Evaluated only if the kind is **Service**. | true | No | +| `healthCheck.scheme` | Server URL scheme for the health check endpoint.
Evaluated only if the kind is **Service**.
Only for [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) of type [ExternalName](#externalname-service). | "" | No | +| `healthCheck.mode` | Health check mode.
If defined to grpc, will use the gRPC health check protocol to probe the server.
Evaluated only if the kind is **Service**.
Only for [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) of type [ExternalName](#externalname-service). | "http" | No | +| `healthCheck.path` | Server URL path for the health check endpoint.
Evaluated only if the kind is **Service**.
Only for [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) of type [ExternalName](#externalname-service). | "" | No | +| `healthCheck.interval` | Frequency of the health check calls for healthy targets.
Evaluated only if the kind is **Service**.
Only for [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) of type [ExternalName](#externalname-service). | "100ms" | No | +| `healthCheck.unhealthyInterval` | Frequency of the health check calls for unhealthy targets.
When not defined, it defaults to the `interval` value.
Evaluated only if the kind is **Service**.
Only for [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) of type [ExternalName](#externalname-service). | "100ms" | No | +| `healthCheck.method` | HTTP method for the health check endpoint.
Evaluated only if the kind is **Service**.
Only for [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) of type [ExternalName](#externalname-service). | "GET" | No | +| `healthCheck.status` | Expected HTTP status code of the response to the health check request.
Only for [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) of type ExternalName.
If not set, expect a status between 200 and 399.
Evaluated only if the kind is **Service**. | | No | +| `healthCheck.port` | URL port for the health check endpoint.
Evaluated only if the kind is **Service**.
Only for [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) of type [ExternalName](#externalname-service). | | No | +| `healthCheck.timeout` | Maximum duration to wait before considering the server unhealthy.
Evaluated only if the kind is **Service**.
Only for [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) of type [ExternalName](#externalname-service). | "5s" | No | +| `healthCheck.hostname` | Value in the Host header of the health check request.
Evaluated only if the kind is **Service**.
Only for [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) of type [ExternalName](#externalname-service). | "" | No | +| `healthCheck.`
`followRedirect`
| Follow the redirections during the healtchcheck.
Evaluated only if the kind is **Service**.
Only for [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) of type [ExternalName](#externalname-service). | true | No | +| `healthCheck.headers` | Map of header to send to the health check endpoint
Evaluated only if the kind is **Service**.
Only for [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) of type [ExternalName](#externalname-service)). | | No | +| `sticky.`
`cookie.name`
| Name of the cookie used for the stickiness.
When sticky sessions are enabled, a `Set-Cookie` header is set on the initial response to let the client know which server handles the first response.
On subsequent requests, to keep the session alive with the same server, the client should send the cookie with the value set.
If the server pecified in the cookie becomes unhealthy, the request will be forwarded to a new server (and the cookie will keep track of the new server).
Evaluated only if the kind is **Service**. | "" | No | +| `sticky.`
`cookie.httpOnly`
| Allow the cookie can be accessed by client-side APIs, such as JavaScript.
Evaluated only if the kind is **Service**. | false | No | +| `sticky.`
`cookie.secure`
| Allow the cookie can only be transmitted over an encrypted connection (i.e. HTTPS).
Evaluated only if the kind is **Service**. | false | No | +| `sticky.`
`cookie.sameSite`
| [SameSite](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite) policy
Allowed values:
-`none`
-`lax`
`strict`
Evaluated only if the kind is **Service**. | "" | No | +| `sticky.`
`cookie.maxAge`
| Number of seconds until the cookie expires.
Negative number, the cookie expires immediately.
0, the cookie never expires.
Evaluated only if the kind is **Service**. | 0 | No | +| `strategy` | Load balancing strategy between the servers.
RoundRobin is the only supported value yet.
Evaluated only if the kind is **Service**. | "RoundRobin" | No | +| `nativeLB` | Allow using the Kubernetes Service load balancing between the pods instead of the one provided by Traefik.
Evaluated only if the kind is **Service**. | false | No | +| `nodePortLB` | Use the nodePort IP address when the service type is NodePort.
It allows services to be reachable when Traefik runs externally from the Kubernetes cluster but within the same network of the nodes.
Evaluated only if the kind is **Service**. | false | No | + + +### ExternalName Service + +Traefik backends creation needs a port to be set, however Kubernetes [ExternalName Service](https://kubernetes.io/docs/concepts/services-networking/service/#externalname) could be defined without any port. Accordingly, Traefik supports defining a port in two ways: + +- only on `IngressRoute` service +- on both sides, you'll be warned if the ports don't match, and the `IngressRoute` service port is used + +Thus, in case of two sides port definition, Traefik expects a match between ports. + +=== "Ports defined on Resource" + + ```yaml tab="IngressRoute" + apiVersion: traefik.io/v1alpha1 + kind: IngressRoute + metadata: + name: test.route + namespace: apps + + spec: + entryPoints: + - foo + routes: + - match: Host(`example.net`) + kind: Rule + services: + - name: external-svc + port: 80 + ``` + + ```yaml tab="Service ExternalName" + apiVersion: v1 + kind: Service + metadata: + name: external-svc + namespace: apps + + spec: + externalName: external.domain + type: ExternalName + ``` + +=== "Port defined on the Service" + + ```yaml tab="IngressRoute" + apiVersion: traefik.io/v1alpha1 + kind: IngressRoute + metadata: + name: test.route + namespace: apps + + spec: + entryPoints: + - foo + routes: + - match: Host(`example.net`) + kind: Rule + services: + - name: external-svc + ``` + + ```yaml tab="Service ExternalName" + apiVersion: v1 + kind: Service + metadata: + name: external-svc + namespace: apps + + spec: + externalName: external.domain + type: ExternalName + ports: + - port: 80 + ``` + +=== "Port defined on both sides" + + ```yaml tab="IngressRoute" + apiVersion: traefik.io/v1alpha1 + kind: IngressRoute + metadata: + name: test.route + namespace: apps + + spec: + entryPoints: + - foo + routes: + - match: Host(`example.net`) + kind: Rule + services: + - name: external-svc + port: 80 + ``` + + ```yaml tab="Service ExternalName" + apiVersion: v1 + kind: Service + metadata: + name: external-svc + namespace: apps + + spec: + externalName: external.domain + type: ExternalName + ports: + - port: 80 + ``` + +### Port Definition + +Traefik backends creation needs a port to be set, however Kubernetes [ExternalName Service](https://kubernetes.io/docs/concepts/services-networking/service/#externalname) could be defined without any port. Accordingly, Traefik supports defining a port in two ways: + +- only on `IngressRoute` service +- on both sides, you'll be warned if the ports don't match, and the `IngressRoute` service port is used + +Thus, in case of two sides port definition, Traefik expects a match between ports. + +??? example + + ```yaml tab="IngressRoute" + --- + apiVersion: traefik.io/v1alpha1 + kind: IngressRoute + metadata: + name: test.route + namespace: default + + spec: + entryPoints: + - foo + + routes: + - match: Host(`example.net`) + kind: Rule + services: + - name: external-svc + port: 80 + + --- + apiVersion: v1 + kind: Service + metadata: + name: external-svc + namespace: default + spec: + externalName: external.domain + type: ExternalName + ``` + + ```yaml tab="ExternalName Service" + --- + apiVersion: traefik.io/v1alpha1 + kind: IngressRoute + metadata: + name: test.route + namespace: default + + spec: + entryPoints: + - foo + + routes: + - match: Host(`example.net`) + kind: Rule + services: + - name: external-svc + + --- + apiVersion: v1 + kind: Service + metadata: + name: external-svc + namespace: default + spec: + externalName: external.domain + type: ExternalName + ports: + - port: 80 + ``` + + ```yaml tab="Both sides" + --- + apiVersion: traefik.io/v1alpha1 + kind: IngressRoute + metadata: + name: test.route + namespace: default + + spec: + entryPoints: + - foo + + routes: + - match: Host(`example.net`) + kind: Rule + services: + - name: external-svc + port: 80 + + --- + apiVersion: v1 + kind: Service + metadata: + name: external-svc + namespace: default + spec: + externalName: external.domain + type: ExternalName + ports: + - port: 80 + ``` + +### Load Balancing + +You can declare and use Kubernetes Service load balancing as detailed below: + +```yaml tab="IngressRoute" +apiVersion: traefik.io/v1alpha1 +kind: IngressRoute +metadata: + name: ingressroutebar + namespace: default + +spec: + entryPoints: + - web + routes: + - match: Host(`example.com`) && PathPrefix(`/foo`) + kind: Rule + services: + - name: svc1 + namespace: default + - name: svc2 + namespace: default +``` + +```yaml tab="K8s Service" +apiVersion: v1 +kind: Service +metadata: + name: svc1 + namespace: default + +spec: + ports: + - name: http + port: 80 + selector: + app: traefiklabs + task: app1 +--- +apiVersion: v1 +kind: Service +metadata: + name: svc2 + namespace: default + +spec: + ports: + - name: http + port: 80 + selector: + app: traefiklabs + task: app2 +``` + +!!! important "Kubernetes Service Native Load-Balancing" + + To avoid creating the server load-balancer with the pod IPs and use Kubernetes Service clusterIP directly, + one should set the service `NativeLB` option to true. + Please note that, by default, Traefik reuses the established connections to the backends for performance purposes. This can prevent the requests load balancing between the replicas from behaving as one would expect when the option is set. + By default, `NativeLB` is false. + + ??? example "Example" + + ```yaml + --- + apiVersion: traefik.io/v1alpha1 + kind: IngressRoute + metadata: + name: test.route + namespace: default + + spec: + entryPoints: + - foo + + routes: + - match: Host(`example.net`) + kind: Rule + services: + - name: svc + port: 80 + # Here, nativeLB instructs to build the server load-balancer with the Kubernetes Service clusterIP only. + nativeLB: true + + --- + apiVersion: v1 + kind: Service + metadata: + name: svc + namespace: default + spec: + type: ClusterIP + ... + ``` + +### Configuring Backend Protocol + +There are 3 ways to configure the backend protocol for communication between Traefik and your pods: + +- Setting the scheme explicitly (http/https/h2c) +- Configuring the name of the kubernetes service port to start with https (https) +- Setting the kubernetes service port to use port 443 (https) + +If you do not configure the above, Traefik will assume an http connection. diff --git a/docs/content/reference/routing-configuration/kubernetes/crd/http/tlsstore.md b/docs/content/reference/routing-configuration/kubernetes/crd/http/tlsstore.md deleted file mode 100644 index cab2b1cea..000000000 --- a/docs/content/reference/routing-configuration/kubernetes/crd/http/tlsstore.md +++ /dev/null @@ -1,39 +0,0 @@ ---- -title: "TLSStore" -description: "TLS Store in Traefik Proxy" ---- - -In Traefik, certificates are grouped together in certificates stores. - -`TLSStore` is the CRD implementation of a [Traefik TLS Store](../../../http/tls/tls-certificates.md#certificates-stores). - -Before creating `TLSStore` objects, you need to apply the [Traefik Kubernetes CRDs](https://doc.traefik.io/traefik/reference/dynamic-configuration/kubernetes-crd/#definitions) to your Kubernetes cluster. - -!!! Tip "Default TLS Store" - Traefik currently only uses the TLS Store named "default". This default `TLSStore` should be in a namespace discoverable by Traefik. Since it is used by default on `IngressRoute` and `IngressRouteTCP` objects, there never is a need to actually reference it. This means that you cannot have two stores that are named default in different Kubernetes namespaces. As a consequence, with respect to TLS stores, the only change that makes sense (and only if needed) is to configure the default `TLSStore`. - -## Configuration Example - -```yaml tab="TLSStore" -apiVersion: traefik.io/v1alpha1 -kind: TLSStore -metadata: - name: default - -spec: - defaultCertificate: - secretName: supersecret -``` - -## Configuration Options - -| Field | Description | Required | -|:---------------------------------------|:-------------------------|:---------| -| `certificates[n].secretName` | List of Kubernetes [Secrets](https://kubernetes.io/docs/concepts/configuration/secret/), each of them holding a key/certificate pair to add to the store. | No | -| `defaultCertificate.secretName` | Name of the Kubernetes [Secret](https://kubernetes.io/docs/concepts/configuration/secret/) served for connections without a SNI, or without a matching domain. If no default certificate is provided, Traefik will use the generated one. Do not use if the option `defaultGeneratedCert` is set. | No | -| `defaultGeneratedCert.resolver` | Name of the ACME resolver to use to generate the default certificate.
Do not use if the option `defaultCertificate` is set. | No | -| `defaultGeneratedCert.domain.main` | Main domain used to generate the default certificate.
Do not use if the option `defaultCertificate` is set. | No | -| `defaultGeneratedCert.domain.sans` | List of [Subject Alternative Name](https://en.wikipedia.org/wiki/Subject_Alternative_Name) used to generate the default certificate.
Do not use if the option `defaultCertificate` is set. | No | - -!!! note "DefaultCertificate vs DefaultGeneratedCert" - If both `defaultCertificate` and `defaultGeneratedCert` are set, the TLS certificate contained in `defaultCertificate.secretName` is served. The ACME default certificate is not generated. diff --git a/docs/content/reference/routing-configuration/kubernetes/crd/http/traefikservice.md b/docs/content/reference/routing-configuration/kubernetes/crd/http/traefikservice.md index acda27c48..55bae513e 100644 --- a/docs/content/reference/routing-configuration/kubernetes/crd/http/traefikservice.md +++ b/docs/content/reference/routing-configuration/kubernetes/crd/http/traefikservice.md @@ -148,42 +148,15 @@ data: ### Configuration Options -| Field | Description | Default | Required | -|:------|:----------------------------------------------------------|:---------------------|:---------| -| `services` | List of any combination of TraefikService and [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/).
. | | No | -| `services[m].`
`kind` | Kind of the service targeted.
Two values allowed:
- **Service**: Kubernetes Service
- **TraefikService**: Traefik Service. | "" | No | -| `services[m].`
`name` | Service name.
The character `@` is not authorized. | "" | Yes | -| `services[m].`
`namespace` | Service namespace. | "" | No | -| `services[m].`
`port` | Service port (number or port name).
Evaluated only if the kind is **Service**. | "" | No | -| `services[m].`
`responseForwarding.`
`flushInterval` | Interval, in milliseconds, in between flushes to the client while copying the response body.
A negative value means to flush immediately after each write to the client.
This configuration is ignored when a response is a streaming response; for such responses, writes are flushed to the client immediately.
Evaluated only if the kind is **Service**. | 100ms | No | -| `services[m].`
`scheme` | Scheme to use for the request to the upstream Kubernetes Service.
Evaluated only if the kind is **Service**. | "http"
"https" if `port` is 443 or contains the string *https*. | No | -| `services[m].`
`serversTransport` | Name of ServersTransport resource to use to configure the transport between Traefik and your servers.
Evaluated only if the kind is **Service**. | "" | No | -| `services[m].`
`passHostHeader` | Forward client Host header to server.
Evaluated only if the kind is **Service**. | true | No | -| `services[m].`
`healthCheck.scheme` | Server URL scheme for the health check endpoint.
Evaluated only if the kind is **Service**.
Only for [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) of type `ExternalName`. | "" | No | -| `services[m].`
`healthCheck.mode` | Health check mode.
If defined to grpc, will use the gRPC health check protocol to probe the server.
Evaluated only if the kind is **Service**.
Only for [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) of type `ExternalName`. | "http" | No | -| `services[m].`
`healthCheck.path` | Server URL path for the health check endpoint.
Evaluated only if the kind is **Service**.
Only for [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) of type `ExternalName`. | "" | No | -| `services[m].`
`healthCheck.interval` | Frequency of the health check calls.
Evaluated only if the kind is **Service**.
Only for [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) of type [ExternalName]`ExternalName`. | "100ms" | No | -| `services[m].`
`healthCheck.method` | HTTP method for the health check endpoint.
Evaluated only if the kind is **Service**.
Only for [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) of type `ExternalName`. | "GET" | No | -| `services[m].`
`healthCheck.status` | Expected HTTP status code of the response to the health check request.
Only for [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) of type ExternalName.
If not set, expect a status between 200 and 399.
Evaluated only if the kind is **Service**. | | No | -| `services[m].`
`healthCheck.port` | URL port for the health check endpoint.
Evaluated only if the kind is **Service**.
Only for [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) of type `ExternalName`. | | No | -| `services[m].`
`healthCheck.timeout` | Maximum duration to wait before considering the server unhealthy.
Evaluated only if the kind is **Service**.
Only for [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) of type `ExternalName`. | "5s" | No | -| `services[m].`
`healthCheck.hostname` | Value in the Host header of the health check request.
Evaluated only if the kind is **Service**.
Only for [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) of type `ExternalName`. | "" | No | -| `services[m].`
`healthCheck.`
`followRedirect` | Follow the redirections during the healtchcheck.
Evaluated only if the kind is **Service**.
Only for [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) of type `ExternalName`. | true | No | -| `services[m].`
`healthCheck.headers` | Map of header to send to the health check endpoint
Evaluated only if the kind is **Service**.
Only for [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) of type `ExternalName`. | | No | -| `services[m].`
`sticky.`
`cookie.name` | Name of the cookie used for the stickiness.
Evaluated only if the kind is **Service**. | Abbreviation of a sha1
(ex: `_1d52e`). | No | -| `services[m].`
`sticky.`
`cookie.httpOnly` | Allow the cookie can be accessed by client-side APIs, such as JavaScript.
Evaluated only if the kind is **Service**. | false | No | -| `services[m].`
`sticky.`
`cookie.secure` | Allow the cookie can only be transmitted over an encrypted connection (i.e. HTTPS).
Evaluated only if the kind is **Service**. | false | No | -| `services[m].`
`sticky.`
`cookie.sameSite` | [SameSite](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite) policy.
Allowed values:
-`none`
-`lax`
`strict`
Evaluated only if the kind is **Service**. | "" | No | -| `services[m].`
`sticky.`
`cookie.maxAge` | Number of seconds until the cookie expires.
Negative number, the cookie expires immediately.
0, the cookie never expires.
Evaluated only if the kind is **Service**. | 0 | No | -| `services[m].`
`strategy` | Load balancing strategy between the servers.
RoundRobin is the only supported value yet.
Evaluated only if the kind is **Service**. | "RoundRobin" | No | -| `services[m].`
`weight` | Service weight.
To use only to refer to WRR TraefikService | "" | No | -| `services[m].`
`nativeLB` | Allow using the Kubernetes Service load balancing between the pods instead of the one provided by Traefik.
Evaluated only if the kind is **Service**. | false | No | -| `services[m].`
`nodePortLB` | Use the nodePort IP address when the service type is NodePort.
It allows services to be reachable when Traefik runs externally from the Kubernetes cluster but within the same network of the nodes.
Evaluated only if the kind is **Service**. | false | No | -| `sticky.`
`cookie.name` | Name of the cookie used for the stickiness at the WRR service level.
When sticky sessions are enabled, a `Set-Cookie` header is set on the initial response to let the client know which server handles the first response.
On subsequent requests, to keep the session alive with the same server, the client should send the cookie with the value set.
If the server pecified in the cookie becomes unhealthy, the request will be forwarded to a new server (and the cookie will keep track of the new server).
More information about WRR stickiness [here](#stickiness-on-multiple-levels) | Abbreviation of a sha1
(ex: `_1d52e`). | No | -| `sticky.`
`cookie.httpOnly` | Allow the cookie used for the stickiness at the WRR service level to be accessed by client-side APIs, such as JavaScript.
More information about WRR stickiness [here](#stickiness-on-multiple-levels) | false | No | -| `sticky.`
`cookie.secure` | Allow the cookie used for the stickiness at the WRR service level to be only transmitted over an encrypted connection (i.e. HTTPS).
More information about WRR stickiness [here](#stickiness-on-multiple-levels) | false | No | -| `sticky.`
`cookie.sameSite` | [SameSite](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite) policy for the cookie used for the stickiness at the WRR service level.
Allowed values:
-`none`
-`lax`
`strict`
More information about WRR stickiness [here](#stickiness-on-multiple-levels) | "" | No | -| `sticky.`
`cookie.maxAge` | Number of seconds until the cookie used for the stickiness at the WRR service level expires.
Negative number, the cookie expires immediately.
0, the cookie never expires. | 0 | No | +| Field | Description | Default | Required | +|:---------------------------------------------------------------|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:---------------------------------------------------------------------|:---------| +| `services` | List of any combination of TraefikService and [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/).
. Exhaustive list of option in the [`Service`](./service.md#configuration-options) documentation. | | No | +| `services[m].weight` | Service weight. | "" | No | +| `sticky.`
`cookie.name`
| Name of the cookie used for the stickiness at the WRR service level.
When sticky sessions are enabled, a `Set-Cookie` header is set on the initial response to let the client know which server handles the first response.
On subsequent requests, to keep the session alive with the same server, the client should send the cookie with the value set.
If the server pecified in the cookie becomes unhealthy, the request will be forwarded to a new server (and the cookie will keep track of the new server).
More information about WRR stickiness [here](#stickiness-on-multiple-levels) | Abbreviation of a sha1
(ex: `_1d52e`). | No | +| `sticky.`
`cookie.httpOnly`
| Allow the cookie used for the stickiness at the WRR service level to be accessed by client-side APIs, such as JavaScript.
More information about WRR stickiness [here](#stickiness-on-multiple-levels) | false | No | +| `sticky.`
`cookie.secure`
| Allow the cookie used for the stickiness at the WRR service level to be only transmitted over an encrypted connection (i.e. HTTPS).
More information about WRR stickiness [here](#stickiness-on-multiple-levels) | false | No | +| `sticky.`
`cookie.sameSite`
| [SameSite](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite) policy for the cookie used for the stickiness at the WRR service level.
Allowed values:
-`none`
-`lax`
`strict`
More information about WRR stickiness [here](#stickiness-on-multiple-levels) | "" | No | +| `sticky.`
`cookie.maxAge`
| Number of seconds until the cookie used for the stickiness at the WRR service level expires.
Negative number, the cookie expires immediately.
0, the cookie never expires. | 0 | No | #### Stickiness on multiple levels @@ -304,6 +277,8 @@ spec: mirroring: name: svc1 # svc1 receives 100% of the traffic port: 80 + mirrorBody: true # Set to false by default + maxBodySize: 1M mirrors: - name: svc2 # svc2 receives a copy of 20% of this traffic port: 80 @@ -366,71 +341,31 @@ spec: ### Configuration Options -!!!note "Main and mirrored services" +#### Main Service Options - The main service properties are set as the option root level. +The main service properties are set as the option root level. - The mirrored services properties are set in the `mirrors` list. +The main service provides the same options as a [`Service`](./service.md). -| Field | Description | Default | Required | -|:------|:----------------------------------------------------------|:---------------------|:---------| -| `kind` | Kind of the main service.
Two values allowed:
- **Service**: Kubernetes Service
- **TraefikService**: Traefik Service.
More information [here](#services) | "" | No | -| `name` | Main service name.
The character `@` is not authorized. | "" | Yes | -| `namespace` | Main service namespace.
More information [here](#services). | "" | No | -| `port` | Main service port (number or port name).
Evaluated only if the kind of the main service is **Service**. | "" | No | -| `responseForwarding.`
`flushInterval` | Interval, in milliseconds, in between flushes to the client while copying the response body.
A negative value means to flush immediately after each write to the client.
This configuration is ignored when a response is a streaming response; for such responses, writes are flushed to the client immediately.
Evaluated only if the kind of the main service is **Service**. | 100ms | No | -| `scheme` | Scheme to use for the request to the upstream Kubernetes Service.
Evaluated only if the kind of the main service is **Service**. | "http"
"https" if `port` is 443 or contains the string *https*. | No | -| `serversTransport` | Name of ServersTransport resource to use to configure the transport between Traefik and the main service's servers.
Evaluated only if the kind of the main service is **Service**. | "" | No | -| `passHostHeader` | Forward client Host header to main service's server.
Evaluated only if the kind of the main service is **Service**. | true | No | -| `healthCheck.scheme` | Server URL scheme for the health check endpoint.
Evaluated only if the kind of the main service is **Service**.
Only for [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) of type [ExternalName](#services). | "" | No | -| `healthCheck.mode` | Health check mode.
If defined to grpc, will use the gRPC health check protocol to probe the server.
Evaluated only if the kind of the main service is **Service**.
Only for [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) of type [ExternalName](#services). | "http" | No | -| `healthCheck.path` | Server URL path for the health check endpoint.
Evaluated only if the kind of the main service is **Service**.
Only for [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) of type [ExternalName](#services). | "" | No | -| `healthCheck.interval` | Frequency of the health check calls.
Evaluated only if the kind of the main service is **Service**.
Only for [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) of type [ExternalName](#services). | "100ms" | No | -| `healthCheck.method` | HTTP method for the health check endpoint.
Evaluated only if the kind of the main service is **Service**.
Only for [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) of type [ExternalName](#services). | "GET" | No | -| `healthCheck.status` | Expected HTTP status code of the response to the health check request.
Only for [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) of type ExternalName.
If not set, expect a status between 200 and 399.
Evaluated only if the kind of the main service is **Service**. | | No | -| `healthCheck.port` | URL port for the health check endpoint.
Evaluated only if the kind of the main service is **Service**.
Only for [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) of type [ExternalName](#services). | | No | -| `healthCheck.timeout` | Maximum duration to wait before considering the server unhealthy.
Evaluated only if the kind of the main service is **Service**.
Only for [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) of type [ExternalName](#services). | "5s" | No | -| `healthCheck.hostname` | Value in the Host header of the health check request.
Evaluated only if the kind of the main service is **Service**.
Only for [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) of type [ExternalName](#services). | "" | No | -| `healthCheck.`
`followRedirect` | Follow the redirections during the healtchcheck.
Evaluated only if the kind of the main service is **Service**.
Only for [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) of type [ExternalName](#services). | true | No | -| `healthCheck.headers` | Map of header to send to the health check endpoint
Evaluated only if the kind of the main service is **Service**.
Only for [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) of type [ExternalName](#services). | | No | -| `sticky.`
`cookie.name` | Name of the cookie used for the stickiness on the main service.
Evaluated only if the kind of the main service is **Service**. | Abbreviation of a sha1
(ex: `_1d52e`). | No | -| `sticky.`
`cookie.httpOnly` | Allow the cookie can be accessed by client-side APIs, such as JavaScript.
Evaluated only if the kind of the main service is **Service**. | false | No | -| `sticky.`
`cookie.secure` | Allow the cookie can only be transmitted over an encrypted connection (i.e. HTTPS).
Evaluated only if the kind of the main service is **Service**. | false | No | -| `sticky.`
`cookie.sameSite` | [SameSite](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite) policy.
Allowed values:
-`none`
-`lax`
`strict`
Evaluated only if the kind of the main service is **Service**. | "" | No | -| `sticky.`
`cookie.maxAge` | Number of seconds until the cookie expires.
Negative number, the cookie expires immediately.
0, the cookie never expires.
Evaluated only if the kind of the main service is **Service**. | 0 | No | -| `strategy` | Load balancing strategy between the main service's servers.
RoundRobin is the only supported value yet.
Evaluated only if the kind of the main service is **Service**. | "RoundRobin" | No | -| `weight` | Service weight.
To use only to refer to WRR TraefikService | "" | No | -| `nativeLB` | Allow using the Kubernetes Service load balancing between the pods instead of the one provided by Traefik.
Evaluated only if the kind of the main service is **Service**. | false | No | -| `nodePortLB` | Use the nodePort IP address when the service type is NodePort.
It allows services to be reachable when Traefik runs externally from the Kubernetes cluster but within the same network of the nodes.
Evaluated only if the kind of the main service is **Service**. | false | No | -| `maxBodySize` | Maximum size allowed for the body of the request.
If the body is larger, the request is not mirrored.
-1 means unlimited size. | -1 | No | -| `mirrors` | List of mirrored services to target.
It can be any combination of TraefikService and [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/).
More information [here](#services). | | No | -| `mirrors[m].`
`kind` | Kind of the mirrored service targeted.
Two values allowed:
- **Service**: Kubernetes Service
- **TraefikService**: Traefik Service.
More information [here](#services) | "" | No | -| `mirrors[m].`
`name` | Mirrored service name.
The character `@` is not authorized. | "" | Yes | -| `mirrors[m].`
`namespace` | Mirrored service namespace.
More information [here](#services). | "" | No | -| `mirrors[m].`
`port` | Mirrored service port (number or port name).
Evaluated only if the kind of the mirrored service is **Service**. | "" | No | -| `mirrors[m].`
`percent` | Part of the traffic to mirror in percent (from 0 to 100) | 0 | No | -| `mirrors[m].`
`responseForwarding.`
`flushInterval` | Interval, in milliseconds, in between flushes to the client while copying the response body.
A negative value means to flush immediately after each write to the client.
This configuration is ignored when a response is a streaming response; for such responses, writes are flushed to the client immediately.
Evaluated only if the kind of the mirrored service is **Service**. | 100ms | No | -| `mirrors[m].`
`scheme` | Scheme to use for the request to the mirrored service.
Evaluated only if the kind of the mirrored service is **Service**. | "http"
"https" if `port` is 443 or contains the string *https*. | No | -| `mirrors[m].`
`serversTransport` | Name of ServersTransport resource to use to configure the transport between Traefik and the mirrored service servers.
Evaluated only if the kind of the mirrored service is **Service**. | "" | No | -| `mirrors[m].`
`passHostHeader` | Forward client Host header to the mirrored service servers.
Evaluated only if the kind of the mirrored service is **Service**. | true | No | -| `mirrors[m].`
`healthCheck.scheme` | Server URL scheme for the health check endpoint.
Evaluated only if the kind of the mirrored service is **Service**.
Only for [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) of type [ExternalName](#services). | "" | No | -| `mirrors[m].`
`healthCheck.mode` | Health check mode.
If defined to grpc, will use the gRPC health check protocol to probe the server.
Evaluated only if the kind of the mirrored service is **Service**.
Only for [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) of type [ExternalName](#services). | "http" | No | -| `mirrors[m].`
`healthCheck.path` | Server URL path for the health check endpoint.
Evaluated only if the kind of the mirrored service is **Service**.
Only for [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) of type [ExternalName](#services). | "" | No | -| `mirrors[m].`
`healthCheck.interval` | Frequency of the health check calls.
Evaluated only if the kind of the mirrored service is **Service**.
Only for [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) of type [ExternalName](#services). | "100ms" | No | -| `mirrors[m].`
`healthCheck.method` | HTTP method for the health check endpoint.
Evaluated only if the kind of the mirrored service is **Service**.
Only for [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) of type [ExternalName](#services). | "GET" | No | -| `mirrors[m].`
`healthCheck.status` | Expected HTTP status code of the response to the health check request.
Only for [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) of type ExternalName.
If not set, expect a status between 200 and 399.
Evaluated only if the kind of the mirrored service is **Service**. | | No | -| `mirrors[m].`
`healthCheck.port` | URL port for the health check endpoint.
Evaluated only if the kind of the mirrored service is **Service**.
Only for [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) of type [ExternalName](#services). | | No | -| `mirrors[m].`
`healthCheck.timeout` | Maximum duration to wait before considering the server unhealthy.
Evaluated only if the kind of the mirrored service is **Service**.
Only for [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) of type [ExternalName](#services). | "5s" | No | -| `mirrors[m].`
`healthCheck.hostname` | Value in the Host header of the health check request.
Evaluated only if the kind of the mirrored service is **Service**.
Only for [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) of type [ExternalName](#services). | "" | No | -| `mirrors[m].`
`healthCheck.`
`followRedirect` | Follow the redirections during the healtchcheck.
Evaluated only if the kind of the mirrored service is **Service**.
Only for [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) of type [ExternalName](#services). | true | No | -| `mirrors[m].`
`healthCheck.headers` | Map of header to send to the health check endpoint
Evaluated only if the kind of the mirrored service is **Service**.
Only for [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) of type [ExternalName](#services). | | No | -| `mirrors[m].`
`sticky.`
`cookie.name` | Name of the cookie used for the stickiness.
When sticky sessions are enabled, a `Set-Cookie` header is set on the initial response to let the client know which server handles the first response.
On subsequent requests, to keep the session alive with the same server, the client should send the cookie with the value set.
If the server pecified in the cookie becomes unhealthy, the request will be forwarded to a new server (and the cookie will keep track of the new server).
Evaluated only if the kind of the mirrored service is **Service**. | "" | No | -| `mirrors[m].`
`sticky.`
`cookie.httpOnly` | Allow the cookie can be accessed by client-side APIs, such as JavaScript.
Evaluated only if the kind of the mirrored service is **Service**. | false | No | -| `mirrors[m].`
`sticky.`
`cookie.secure` | Allow the cookie can only be transmitted over an encrypted connection (i.e. HTTPS).
Evaluated only if the kind of the mirrored service is **Service**. | false | No | -| `mirrors[m].`
`sticky.`
`cookie.sameSite` | [SameSite](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite) policy.
Allowed values:
-`none`
-`lax`
`strict`
Evaluated only if the kind of the mirrored service is **Service**. | "" | No | -| `mirrors[m].`
`sticky.`
`cookie.maxAge` | Number of seconds until the cookie expires.
Negative number, the cookie expires immediately.
0, the cookie never expires.
Evaluated only if the kind of the mirrored service is **Service**. | 0 | No | -| `mirrors[m].`
`strategy` | Load balancing strategy between the servers.
RoundRobin is the only supported value yet.
Evaluated only if the kind of the mirrored service is **Service**. | "RoundRobin" | No | -| `mirrors[m].`
`weight` | Service weight.
To use only to refer to WRR TraefikService | "" | No | -| `mirrors[m].`
`nativeLB` | Allow using the Kubernetes Service load balancing between the pods instead of the one provided by Traefik.
Evaluated only if the kind of the mirrored service is **Service**. | false | No | -| `mirrors[m].`
`nodePortLB` | Use the nodePort IP address when the service type is NodePort.
It allows services to be reachable when Traefik runs externally from the Kubernetes cluster but within the same network of the nodes.
Evaluated only if the kind of the mirrored service is **Service**. | false | No | -| `mirrorBody` | Defines whether the request body should be mirrored. | true | No | +The exhaustive list of the service options is described in the [`Service`](./service.md#configuration-options) documentation. +The mirror main service dedicated option are described below. + +| Field | Description | Default | Required | +|:--------------------------------------------------------------|:----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:---------------------------------------------------------------------|:---------| +| `mirrorBody` | Defines whether the request body should be mirrored. | true | No | +| `maxBodySize` | Maximum size allowed for the body of the request.
If the body is larger, the request is not mirrored.
-1 means unlimited size. | -1 | No | +| `mirrors` | List of mirrored services to target.
It can be any combination of TraefikService and [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/).
Exhaustive list of option in the [`Service`](./service.md#configuration-options) documentation. | | Yes | + +#### Mirrored Services Options + +The mirrored services properties are set in the `mirrors` list. + +A mirrored service provides the same options as a [`Service`](./service.md). + +The exhaustive list of the service options is described in the [`Service`](./service.md#configuration-options) documentation. +The mirrorerd service dedicated option are described below. + + +| Field | Description | Default | Required | +|:--------------------------------------------------------------|:----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:---------------------------------------------------------------------|:---------| +| `mirrors[m].percent` | Traffic percentage to route to the service. | 0 | No | diff --git a/docs/content/reference/routing-configuration/kubernetes/crd/tcp/ingressroutetcp.md b/docs/content/reference/routing-configuration/kubernetes/crd/tcp/ingressroutetcp.md index 44598e18a..4ae214dae 100644 --- a/docs/content/reference/routing-configuration/kubernetes/crd/tcp/ingressroutetcp.md +++ b/docs/content/reference/routing-configuration/kubernetes/crd/tcp/ingressroutetcp.md @@ -3,7 +3,7 @@ title: "Kubernetes IngressRouteTCP" description: "An IngressRouteTCP is a Traefik CRD is in charge of connecting incoming TCP connections to the Services that can handle them." --- -`IngressRouteTCP` is the CRD implementation of a [Traefik TCP router](../../../tcp/router/rules-and-priority.md). +`IngressRouteTCP` is the CRD implementation of a [Traefik TCP router](../../../tcp/routing/rules-and-priority.md). Before creating `IngressRouteTCP` objects, you need to apply the [Traefik Kubernetes CRDs](https://doc.traefik.io/traefik/reference/dynamic-configuration/kubernetes-crd/#definitions) to your Kubernetes cluster. @@ -16,7 +16,7 @@ This registers the `IngressRouteTCP` kind and other Traefik-specific resources. You can declare an `IngressRouteTCP` as detailed below: -```yaml tab="IngressRoute" +```yaml tab="IngressRouteTCP" apiVersion: traefik.io/v1alpha1 kind: IngressRouteTCP metadata: @@ -36,12 +36,9 @@ spec: - name: foo port: 8080 weight: 10 - proxyProtocol: - version: 1 serversTransport: transport nativeLB: true nodePortLB: true - tls: false tls: secretName: supersecret @@ -59,33 +56,33 @@ spec: ## Configuration Options -| Field | Description | Default | Required | -|-------------------------------------|-----------------------------|-------------------------------------------|-----------------------| -| `entryPoints` | List of entrypoints names. | | No | -| `routes` | List of routes. | | Yes | -| `routes[n].match` | Defines the [rule](../../../tcp/router/rules-and-priority.md#rules) of the underlying router. | | Yes | -| `routes[n].priority` | Defines the [priority](../../../tcp/router/rules-and-priority.md#priority) to disambiguate rules of the same length, for route matching. | | No | -| `routes[n].middlewares[n].name` | Defines the [MiddlewareTCP](./middlewaretcp.md) name. | | Yes | -| `routes[n].middlewares[n].namespace` | Defines the [MiddlewareTCP](./middlewaretcp.md) namespace. | ""| No| -| `routes[n].services` | List of [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) definitions. | | No | -| `routes[n].services[n].name` | Defines the name of a [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/). | | Yes | -| `routes[n].services[n].port` | Defines the port of a [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/). This can be a reference to a named port.| | Yes | -| `routes[n].services[n].weight` | Defines the weight to apply to the server load balancing. | 1 | No | -| `routes[n].services[n].proxyProtocol` | Defines the [PROXY protocol](../../../../install-configuration/entrypoints.md#proxyprotocol-and-load-balancers) configuration. | | No | -| `routes[n].services[n].proxyProtocol.version` | Defines the [PROXY protocol](../../../../install-configuration/entrypoints.md#proxyprotocol-and-load-balancers) version. | | No | -| `routes[n].services[n].serversTransport` | Defines the [ServersTransportTCP](./serverstransporttcp.md).
The `ServersTransport` namespace is assumed to be the [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) namespace. | | No | -| `routes[n].services[n].nativeLB` | Controls, when creating the load-balancer, whether the LB's children are directly the pods IPs or if the only child is the Kubernetes Service clusterIP. See [here](#nativelb) for more information. | false | No | -| `routes[n].services[n].nodePortLB` | Controls, when creating the load-balancer, whether the LB's children are directly the nodes internal IPs using the nodePort when the service type is `NodePort`. It allows services to be reachable when Traefik runs externally from the Kubernetes cluster but within the same network of the nodes. | false | No | -| `tls` | Defines [TLS](../../../../install-configuration/tls/certificate-resolvers/overview.md) certificate configuration. | | No | -| `tls.secretName` | Defines the [secret](https://kubernetes.io/docs/concepts/configuration/secret/) name used to store the certificate (in the `IngressRoute` namespace). | "" | No | -| `tls.options` | Defines the reference to a [TLSOption](../http/tlsoption.md). | "" | No | -| `tls.options.name` | Defines the [TLSOption](../http/tlsoption.md) name. | "" | No | -| `tls.options.namespace` | Defines the [TLSOption](../http/tlsoption.md) namespace. | "" | No | -| `tls.certResolver` | Defines the reference to a [CertResolver](../../../../install-configuration/tls/certificate-resolvers/overview.md). | "" | No | -| `tls.domains` | List of domains. | "" | No | -| `tls.domains[n].main` | Defines the main domain name. | "" | No | -| `tls.domains[n].sans` | List of SANs (alternative domains). | "" | No | -| `tls.passthrough` | If `true`, delegates the TLS termination to the backend. | false | No | +| Field | Description | Default | Required | +|-------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------|-----------------------| +| `entryPoints` | List of entrypoints names. | | No | +| `routes` | List of routes. | | Yes | +| `routes[n].match` | Defines the [rule](../../../tcp/routing/rules-and-priority.md#rules) of the underlying router. | | Yes | +| `routes[n].priority` | Defines the [priority](../../../tcp/routing/rules-and-priority.md#priority-calculation) to disambiguate rules of the same length, for route matching. | | No | +| `routes[n].middlewares[n].name` | Defines the [MiddlewareTCP](./middlewaretcp.md) name. | | Yes | +| `routes[n].middlewares[n].namespace` | Defines the [MiddlewareTCP](./middlewaretcp.md) namespace. | ""| No| +| `routes[n].services` | List of [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) definitions. | | No | +| `routes[n].services[n].name` | Defines the name of a [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/). | | Yes | +| `routes[n].services[n].port` | Defines the port of a [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/). This can be a reference to a named port. | | Yes | +| `routes[n].services[n].weight` | Defines the weight to apply to the server load balancing. | 1 | No | +| `routes[n].services[n].proxyProtocol` | Defines the [PROXY protocol](../../../../install-configuration/entrypoints.md#proxyprotocol-and-load-balancers) configuration. | | No | +| `routes[n].services[n].proxyProtocol.version` | Defines the [PROXY protocol](../../../../install-configuration/entrypoints.md#proxyprotocol-and-load-balancers) version. | | No | +| `routes[n].services[n].serversTransport` | Defines the [ServersTransportTCP](./serverstransporttcp.md).
The `ServersTransport` namespace is assumed to be the [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) namespace. | | No | +| `routes[n].services[n].nativeLB` | Controls, when creating the load-balancer, whether the LB's children are directly the pods IPs or if the only child is the Kubernetes Service clusterIP. See [here](#nativelb) for more information. | false | No | +| `routes[n].services[n].nodePortLB` | Controls, when creating the load-balancer, whether the LB's children are directly the nodes internal IPs using the nodePort when the service type is `NodePort`. It allows services to be reachable when Traefik runs externally from the Kubernetes cluster but within the same network of the nodes. | false | No | +| `tls` | Defines [TLS](../../../../install-configuration/tls/certificate-resolvers/overview.md) certificate configuration. | | No | +| `tls.secretName` | Defines the [secret](https://kubernetes.io/docs/concepts/configuration/secret/) name used to store the certificate (in the `IngressRoute` namespace). | "" | No | +| `tls.options` | Defines the reference to a [TLSOption](../tls/tlsoption.md). | "" | No | +| `tls.options.name` | Defines the [TLSOption](../tls/tlsoption.md) name. | "" | No | +| `tls.options.namespace` | Defines the [TLSOption](../tls/tlsoption.md) namespace. | "" | No | +| `tls.certResolver` | Defines the reference to a [CertResolver](../../../../install-configuration/tls/certificate-resolvers/overview.md). | "" | No | +| `tls.domains` | List of domains. | "" | No | +| `tls.domains[n].main` | Defines the main domain name. | "" | No | +| `tls.domains[n].sans` | List of SANs (alternative domains). | "" | No | +| `tls.passthrough` | If `true`, delegates the TLS termination to the backend. | false | No | ### ExternalName Service diff --git a/docs/content/reference/routing-configuration/kubernetes/crd/tcp/serverstransporttcp.md b/docs/content/reference/routing-configuration/kubernetes/crd/tcp/serverstransporttcp.md index 7e275df61..456ed9d2b 100644 --- a/docs/content/reference/routing-configuration/kubernetes/crd/tcp/serverstransporttcp.md +++ b/docs/content/reference/routing-configuration/kubernetes/crd/tcp/serverstransporttcp.md @@ -29,6 +29,9 @@ metadata: namespace: default spec: + proxyProtocol: + version: 2 + terminationDelay: 100ms tls: serverName: example.org insecureSkipVerify: true @@ -36,16 +39,18 @@ spec: ## Configuration Options -| Field | Description | Default | Required | -|-------------------------------------|-----------------------------|-------------------------------------------|-----------------------| -| `dialTimeout` | The amount of time to wait until a connection to a server can be established. If zero, no timeout exists. | 30s | No | -| `dialKeepAlive` | The interval between keep-alive probes for an active network connection.
If this option is set to zero, keep-alive probes are sent with a default value (currently 15 seconds),
if supported by the protocol and operating system. Network protocols or operating systems that do not support keep-alives ignore this field.
If negative, keep-alive probes are turned off.| 15s | No | -| `terminationDelay` | Defines the delay to wait before fully terminating the connection, after one connected peer has closed its writing capability.| 100ms | No | -| `tls.serverName` | ServerName used to contact the server. | "" | No | -| `tls.insecureSkipVerify` | Controls whether the server's certificate chain and host name is verified. | false | No | -| `tls.peerCertURI` | Defines the URI used to match against SAN URIs during the server's certificate verification. | "" | No | -| `tls.rootCAsSecrets` | Defines the set of root certificate authorities to use when verifying server certificates.
The CA secret must contain a base64 encoded certificate under either a `tls.ca` or a `ca.crt` key.| "" | No | -| `tls.certificatesSecrets` | Certificates to present to the server for mTLS.| "" | No | -| `spiffe` | Configures [SPIFFE](../../../../install-configuration/tls/spiffe.md) options. | "" | No | -| `spiffe.ids` | Defines the allowed SPIFFE IDs. This takes precedence over the SPIFFE `trustDomain`. |""| No | -| `spiffe.trustDomain` | Defines the allowed SPIFFE trust domain. | "" | No | +| Field | Description | Default | Required | +|---------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------|----------| +| `dialTimeout` | The amount of time to wait until a connection to a server can be established. If zero, no timeout exists. | 30s | No | +| `dialKeepAlive` | The interval between keep-alive probes for an active network connection.
If this option is set to zero, keep-alive probes are sent with a default value (currently 15 seconds),
if supported by the protocol and operating system. Network protocols or operating systems that do not support keep-alives ignore this field.
If negative, keep-alive probes are turned off. | 15s | No | +| `proxyProtocol` | Defines the Proxy Protocol configuration. An empty `proxyProtocol` section enables Proxy Protocol version 2. | | No | +| `proxyProtocol.version` | Traefik supports PROXY Protocol version 1 and 2 on TCP Services. | | No | +| `terminationDelay` | Defines the delay to wait before fully terminating the connection, after one connected peer has closed its writing capability. | 100ms | No | +| `tls.serverName` | ServerName used to contact the server. | "" | No | +| `tls.insecureSkipVerify` | Controls whether the server's certificate chain and host name is verified. | false | No | +| `tls.peerCertURI` | Defines the URI used to match against SAN URIs during the server's certificate verification. | "" | No | +| `tls.rootCAsSecrets` | Defines the set of root certificate authorities to use when verifying server certificates.
The CA secret must contain a base64 encoded certificate under either a `tls.ca` or a `ca.crt` key. | "" | No | +| `tls.certificatesSecrets` | Certificates to present to the server for mTLS. | "" | No | +| `spiffe` | Configures [SPIFFE](../../../../install-configuration/tls/spiffe.md) options. | "" | No | +| `spiffe.ids` | Defines the allowed SPIFFE IDs. This takes precedence over the SPIFFE `trustDomain`. | "" | No | +| `spiffe.trustDomain` | Defines the allowed SPIFFE trust domain. | "" | No | diff --git a/docs/content/reference/routing-configuration/kubernetes/crd/tcp/tlsoption.md b/docs/content/reference/routing-configuration/kubernetes/crd/tcp/tlsoption.md deleted file mode 100644 index 4d4db1b01..000000000 --- a/docs/content/reference/routing-configuration/kubernetes/crd/tcp/tlsoption.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -title: "TLSOption" -description: "TLS Options in Traefik Proxy" ---- - ---8<-- "content/reference/routing-configuration/kubernetes/crd/http/tlsoption.md" diff --git a/docs/content/reference/routing-configuration/kubernetes/crd/tcp/tlsstore.md b/docs/content/reference/routing-configuration/kubernetes/crd/tcp/tlsstore.md deleted file mode 100644 index 27c98164a..000000000 --- a/docs/content/reference/routing-configuration/kubernetes/crd/tcp/tlsstore.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -title: "TLSStore" -description: "TLS Store in Traefik Proxy" ---- - ---8<-- "content/reference/routing-configuration/kubernetes/crd/http/tlsstore.md" diff --git a/docs/content/reference/routing-configuration/kubernetes/crd/http/tlsoption.md b/docs/content/reference/routing-configuration/kubernetes/crd/tls/tlsoption.md similarity index 53% rename from docs/content/reference/routing-configuration/kubernetes/crd/http/tlsoption.md rename to docs/content/reference/routing-configuration/kubernetes/crd/tls/tlsoption.md index 6887e8516..8ff6515dc 100644 --- a/docs/content/reference/routing-configuration/kubernetes/crd/http/tlsoption.md +++ b/docs/content/reference/routing-configuration/kubernetes/crd/tls/tlsoption.md @@ -48,19 +48,19 @@ spec: | Field | Description | Default | Required | |:----------------------------|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:---------------------------|:---------| -| `minVersion` | Minimum TLS version that is acceptable. | "VersionTLS12" | No | -| `maxVersion` | Maximum TLS version that is acceptable.
We do not recommend setting this option to disable TLS 1.3. | | No | -| `cipherSuites` | List of supported [cipher suites](https://godoc.org/crypto/tls#pkg-constants) for TLS versions up to TLS 1.2.
[Cipher suites defined for TLS 1.2 and below cannot be used in TLS 1.3, and vice versa.](https://tools.ietf.org/html/rfc8446)
With TLS 1.3, [the cipher suites are not configurable](https://golang.org/doc/go1.12#tls_1_3) (all supported cipher suites are safe in this case). | | No | -| `curvePreferences` | List of the elliptic curves references that will be used in an ECDHE handshake, in preference order.
Use curves names from [`crypto`](https://godoc.org/crypto/tls#CurveID) or the [RFC](https://tools.ietf.org/html/rfc8446#section-4.2.7).
See [CurveID](https://godoc.org/crypto/tls#CurveID) for more information. | | No | -| `clientAuth.secretNames` | Client Authentication (mTLS) option.
List of names of the referenced Kubernetes [Secrets](https://kubernetes.io/docs/concepts/configuration/secret/) (in TLSOption namespace).
The secret must contain a certificate under either a `tls.ca` or a `ca.crt` key. | | No | -| `clientAuth.clientAuthType` | Client Authentication (mTLS) option.
Client authentication type to apply. Available values [here](#client-authentication-mtls). | | No | -| `sniStrict` | Allow rejecting connections from clients connections that do not specify a server_name extension.
The [default certificate](../../../http/tls/tls-certificates.md#default-certificate) is never served is the option is enabled. | false | No | -| `alpnProtocols` | List of supported application level protocols for the TLS handshake, in order of preference.
If the client supports ALPN, the selected protocol will be one from this list, and the connection will fail if there is no mutually supported protocol. | "h2, http/1.1, acme-tls/1" | No | -| `disableSessiontTickets` | Allow disabling the use of session tickets, forcing every client to perform a full TLS handshake instead of resuming sessions. | false | No | +| `minVersion` | Minimum TLS version that is acceptable. | "VersionTLS12" | No | +| `maxVersion` | Maximum TLS version that is acceptable.
We do not recommend setting this option to disable TLS 1.3. | | No | +| `cipherSuites` | List of supported [cipher suites](https://godoc.org/crypto/tls#pkg-constants) for TLS versions up to TLS 1.2.
[Cipher suites defined for TLS 1.2 and below cannot be used in TLS 1.3, and vice versa.](https://tools.ietf.org/html/rfc8446)
With TLS 1.3, [the cipher suites are not configurable](https://golang.org/doc/go1.12#tls_1_3) (all supported cipher suites are safe in this case). | | No | +| `curvePreferences` | List of the elliptic curves references that will be used in an ECDHE handshake.
Use curves names from [`crypto`](https://godoc.org/crypto/tls#CurveID) or the [RFC](https://tools.ietf.org/html/rfc8446#section-4.2.7).
See [CurveID](https://godoc.org/crypto/tls#CurveID) for more information. | | No | +| `clientAuth.secretNames` | Client Authentication (mTLS) option.
List of names of the referenced Kubernetes [Secrets](https://kubernetes.io/docs/concepts/configuration/secret/) (in TLSOption namespace).
The secret must contain a certificate under either a `tls.ca` or a `ca.crt` key. | | No | +| `clientAuth.clientAuthType` | Client Authentication (mTLS) option.
Client authentication type to apply. Available values [here](#client-authentication-mtls). | | No | +| `sniStrict` | Allow rejecting connections from clients connections that do not specify a server_name extension.
The [default certificate](../../../http/tls/tls-certificates.md#default-certificate) is never served is the option is enabled. | false | No | +| `alpnProtocols` | List of supported application level protocols for the TLS handshake, in order of preference.
If the client supports ALPN, the selected protocol will be one from this list, and the connection will fail if there is no mutually supported protocol. | "h2, http/1.1, acme-tls/1" | No | +| `disableSessiontTickets` | Allow disabling the use of session tickets, forcing every client to perform a full TLS handshake instead of resuming sessions. | false | No | ### Client Authentication (mTLS) -The `clientAuth.clientAuthType` option governs the behaviour as follows: +The `clientAuth.clientAuthType` option governs the behavior as follows: - `NoClientCert`: disregards any client certificate. - `RequestClientCert`: asks for a certificate but proceeds anyway if none is provided. @@ -78,6 +78,6 @@ The default behavior is summed up in the table below: | Configuration | Behavior | |:--------------------------|:------------------------------------------------------------| -| No `default` TLS Option | Default internal set of TLS Options by default. | -| One `default` TLS Option | Custom TLS Options applied by default. | -| Many `default` TLS Option | Error log + Default internal set of TLS Options by default. | +| No `default` TLS Option | Default internal set of TLS Options by default. | +| One `default` TLS Option | Custom TLS Options applied by default. | +| Many `default` TLS Option | Error log + Default internal set of TLS Options by default. | diff --git a/docs/content/reference/routing-configuration/kubernetes/crd/tls/tlsstore.md b/docs/content/reference/routing-configuration/kubernetes/crd/tls/tlsstore.md new file mode 100644 index 000000000..f4c99c805 --- /dev/null +++ b/docs/content/reference/routing-configuration/kubernetes/crd/tls/tlsstore.md @@ -0,0 +1,39 @@ +--- +title: "TLSStore" +description: "TLS Store in Traefik Proxy" +--- + +In Traefik, certificates are grouped together in certificates stores. + +`TLSStore` is the CRD implementation of a [Traefik TLS Store](../../../http/tls/tls-certificates.md#certificates-stores). + +Before creating `TLSStore` objects, you need to apply the [Traefik Kubernetes CRDs](https://doc.traefik.io/traefik/reference/dynamic-configuration/kubernetes-crd/#definitions) to your Kubernetes cluster. + +!!! Tip "Default TLS Store" + Traefik currently only uses the TLS Store named "default". This default `TLSStore` should be in a namespace discoverable by Traefik. Since it is used by default on `IngressRoute` and `IngressRouteTCP` objects, there never is a need to actually reference it. This means that you cannot have two stores that are named default in different Kubernetes namespaces. As a consequence, with respect to TLS stores, the only change that makes sense (and only if needed) is to configure the default `TLSStore`. + +## Configuration Example + +```yaml tab="TLSStore" +apiVersion: traefik.io/v1alpha1 +kind: TLSStore +metadata: + name: default + +spec: + defaultCertificate: + secretName: supersecret +``` + +## Configuration Options + +| Field | Description | Required | +|:---------------------------------------|:-------------------------|:---------| +| `certificates[n].secretName` | List of Kubernetes [Secrets](https://kubernetes.io/docs/concepts/configuration/secret/), each of them holding a key/certificate pair to add to the store. | No | +| `defaultCertificate.secretName` | Name of the Kubernetes [Secret](https://kubernetes.io/docs/concepts/configuration/secret/) served for connections without a SNI, or without a matching domain. If no default certificate is provided, Traefik will use the generated one. Do not use if the option `defaultGeneratedCert` is set. | No | +| `defaultGeneratedCert.resolver` | Name of the ACME resolver to use to generate the default certificate.
Do not use if the option `defaultCertificate` is set. | No | +| `defaultGeneratedCert.domain.main` | Main domain used to generate the default certificate.
Do not use if the option `defaultCertificate` is set. | No | +| `defaultGeneratedCert.domain.sans` | List of [Subject Alternative Name](https://en.wikipedia.org/wiki/Subject_Alternative_Name) used to generate the default certificate.
Do not use if the option `defaultCertificate` is set. | No | + +!!! note "DefaultCertificate vs DefaultGeneratedCert" + If both `defaultCertificate` and `defaultGeneratedCert` are set, the TLS certificate contained in `defaultCertificate.secretName` is served. The ACME default certificate is not generated. diff --git a/docs/content/reference/routing-configuration/kubernetes/crd/udp/ingressrouteudp.md b/docs/content/reference/routing-configuration/kubernetes/crd/udp/ingressrouteudp.md index 3dfdc1367..eadfd1773 100644 --- a/docs/content/reference/routing-configuration/kubernetes/crd/udp/ingressrouteudp.md +++ b/docs/content/reference/routing-configuration/kubernetes/crd/udp/ingressrouteudp.md @@ -3,7 +3,7 @@ title: "IngressRouteUDP" description: "Understand the routing configuration for the Kubernetes IngressRouteUDP & Traefik CRD" --- -`IngressRouteUDP` is the CRD implementation of a [Traefik UDP router](../../../udp/router/rules-priority.md). +`IngressRouteUDP` is the CRD implementation of a [Traefik UDP router](../../../udp/routing/rules-priority.md). Before creating `IngressRouteUDP` objects, you need to apply the [Traefik Kubernetes CRDs](https://doc.traefik.io/traefik/reference/dynamic-configuration/kubernetes-crd/#definitions) to your Kubernetes cluster. @@ -32,14 +32,14 @@ spec: | Field | Description | Default | Required | |------------------------------------|-----------------------------|-------------------------------------------|-----------------------| -| `entryPoints` | List of entrypoints names. | | No | -| ` routes ` | List of routes. | | Yes | -| `routes[n].services` | List of [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) definitions. See [here](#externalname-service) for `ExternalName Service` setup. | | No | -| `services[n].name` | Defines the name of a [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/). | | Yes | -| `routes[n].services[n].port` | Defines the port of a [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/). This can be a reference to a named port.| | Yes | -| `routes[n].services[n].weight` | Defines the weight to apply to the server load balancing. | 1 | No | -| `routes[n].services[n].nativeLB` | Controls, when creating the load-balancer, whether the LB's children are directly the pods IPs or if the only child is the Kubernetes Service clusterIP. | false | No | -| `routes[n].services[n].nodePortLB` | Controls, when creating the load-balancer, whether the LB's children are directly the nodes internal IPs using the nodePort when the service type is NodePort. It allows services to be reachable when Traefik runs externally from the Kubernetes cluster but within the same network of the nodes. See [here](#nativelb) for more information. | false | No | +| `entryPoints` | List of entrypoints names. | | No | +| ` routes ` | List of routes. | | Yes | +| `routes[n].services` | List of [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) definitions. See [here](#externalname-service) for `ExternalName Service` setup. | | No | +| `services[n].name` | Defines the name of a [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/). | | Yes | +| `routes[n].services[n].port` | Defines the port of a [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/). This can be a reference to a named port.| | Yes | +| `routes[n].services[n].weight` | Defines the weight to apply to the server load balancing. | 1 | No | +| `routes[n].services[n].nativeLB` | Controls, when creating the load-balancer, whether the LB's children are directly the pods IPs or if the only child is the Kubernetes Service clusterIP. | false | No | +| `routes[n].services[n].nodePortLB` | Controls, when creating the load-balancer, whether the LB's children are directly the nodes internal IPs using the nodePort when the service type is NodePort. It allows services to be reachable when Traefik runs externally from the Kubernetes cluster but within the same network of the nodes. See [here](#nativelb) for more information. | false | No | ### ExternalName Service diff --git a/docs/content/reference/routing-configuration/kubernetes/gateway-api.md b/docs/content/reference/routing-configuration/kubernetes/gateway-api.md index 19128303c..a25e147c4 100644 --- a/docs/content/reference/routing-configuration/kubernetes/gateway-api.md +++ b/docs/content/reference/routing-configuration/kubernetes/gateway-api.md @@ -748,7 +748,6 @@ By default, NativeLB is `false`. Note that it is possible to override the default value by using the option [`nativeLBByDefault`](../../install-configuration/providers/kubernetes/kubernetes-gateway.md) at the provider level. ```yaml ---- apiVersion: v1 kind: Service metadata: @@ -757,7 +756,10 @@ metadata: annotations: traefik.io/service.nativelb: "true" spec: -[...] + ports: + - name: web + port: 80 + ``` {!traefik-for-business-applications.md!} diff --git a/docs/content/reference/routing-configuration/kubernetes/ingress-nginx.md b/docs/content/reference/routing-configuration/kubernetes/ingress-nginx.md new file mode 100644 index 000000000..af98d1b2b --- /dev/null +++ b/docs/content/reference/routing-configuration/kubernetes/ingress-nginx.md @@ -0,0 +1,402 @@ +--- +title: "Traefik Kubernetes Ingress NGINX Routing Configuration" +description: "Understand the routing configuration for the Kubernetes Ingress NGINX Controller and Traefik Proxy. Read the technical documentation." +--- + +# Traefik & Ingresses with NGINX Annotations + +The experimental Kubernetes Controller for Ingresses with NGINX annotations. +{: .subtitle } + +!!! warning "Ingress Discovery" + + The Kubernetes Ingress NGINX provider is discovering by default all Ingresses in the cluster, + which may lead to duplicated routers if you are also using the Kubernetes Ingress provider. + We recommend to use IngressClass for the Ingresses you want to be handled by this provider, + or to use the `watchNamespace` or `watchNamespaceSelector` options to limit the discovery of Ingresses to a specific namespace or set of namespaces. + +## Routing Configuration + +The Kubernetes Ingress NGINX provider watches for incoming ingresses events, such as the example below, +and derives the corresponding dynamic configuration from it, +which in turn will create the resulting routers, services, handlers, etc. + +## Configuration Example + +??? example "Configuring Kubernetes Ingress NGINX Controller" + + ```yaml tab="RBAC" + --- + apiVersion: rbac.authorization.k8s.io/v1 + kind: ClusterRole + metadata: + name: traefik-ingress-controller + rules: + - apiGroups: + - "" + resources: + - namespaces + verbs: + - get + - apiGroups: + - "" + resources: + - configmaps + - pods + - secrets + - endpoints + verbs: + - get + - list + - watch + - apiGroups: + - "" + resources: + - services + verbs: + - get + - list + - watch + - apiGroups: + - networking.k8s.io + resources: + - ingresses + verbs: + - get + - list + - watch + - apiGroups: + - networking.k8s.io + resources: + - ingresses/status + verbs: + - update + - apiGroups: + - networking.k8s.io + resources: + - ingressclasses + verbs: + - get + - list + - watch + - apiGroups: + - "" + resources: + - events + verbs: + - create + - patch + - apiGroups: + - discovery.k8s.io + resources: + - endpointslices + verbs: + - list + - watch + - get + + --- + apiVersion: rbac.authorization.k8s.io/v1 + kind: ClusterRoleBinding + metadata: + name: traefik-ingress-controller + roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: traefik-ingress-controller + subjects: + - kind: ServiceAccount + name: traefik-ingress-controller + namespace: default + ``` + + ```yaml tab="Traefik" + --- + apiVersion: v1 + kind: ServiceAccount + metadata: + name: traefik-ingress-controller + + --- + apiVersion: apps/v1 + kind: Deployment + metadata: + name: traefik + labels: + app: traefik + + spec: + replicas: 1 + selector: + matchLabels: + app: traefik + template: + metadata: + labels: + app: traefik + spec: + serviceAccountName: traefik-ingress-controller + containers: + - name: traefik + image: traefik:v3.5 + args: + - --entryPoints.web.address=:80 + - --providers.kubernetesingressnginx + ports: + - name: web + containerPort: 80 + + --- + apiVersion: v1 + kind: Service + metadata: + name: traefik + spec: + type: LoadBalancer + selector: + app: traefik + ports: + - name: web + port: 80 + targetPort: 80 + ``` + + ```yaml tab="Whoami" + --- + apiVersion: apps/v1 + kind: Deployment + metadata: + name: whoami + labels: + app: whoami + + spec: + replicas: 2 + selector: + matchLabels: + app: whoami + template: + metadata: + labels: + app: whoami + spec: + containers: + - name: whoami + image: traefik/whoami + ports: + - containerPort: 80 + + --- + apiVersion: v1 + kind: Service + metadata: + name: whoami + + spec: + selector: + app: whoami + ports: + - name: http + port: 80 + ``` + + ```yaml tab="Ingress" + --- + apiVersion: networking.k8s.io/v1 + kind: IngressClass + metadata: + name: nginx + spec: + controller: k8s.io/ingress-nginx + + --- + apiVersion: networking.k8s.io/v1 + kind: Ingress + metadata: + name: myingress + + spec: + ingressClassName: nginx + rules: + - host: whoami.localhost + http: + paths: + - path: /bar + pathType: Exact + backend: + service: + name: whoami + port: + number: 80 + - path: /foo + pathType: Exact + backend: + service: + name: whoami + port: + number: 80 + ``` + +## Annotations Support + +This section lists all known NGINX Ingress annotations, split between those currently implemented (with limitations if any) and those not implemented. +Limitations or behavioral differences are indicated where relevant. + +!!! warning "Global configuration" + + Traefik does not expose all global configuration options to control default behaviors for ingresses. + + Some behaviors that are globally configurable in NGINX (such as default SSL redirect, rate limiting, or affinity) are currently not supported and cannot be overridden per-ingress as in NGINX. + +### Caveats and Key Behavioral Differences + +- **Authentication**: Forward auth behaves differently and session caching is not supported. NGINX supports sub-request based auth, while Traefik forwards the original request. +- **Session Affinity**: Only persistent mode is supported. +- **Leader Election**: Not supported; no cluster mode with leader election. +- **Default Backend**: Only `defaultBackend` in Ingress spec is supported; the annotation is ignored. +- **Load Balancing**: Only round_robin is supported; EWMA and IP hash are not supported. +- **CORS**: NGINX responds with all configured headers unconditionally; Traefik handles headers differently between pre-flight and regular requests. +- **TLS/Backend Protocols**: AUTO_HTTP, FCGI and some TLS options are not supported in Traefik. +- **Path Handling**: Traefik preserves trailing slashes by default; NGINX removes them unless configured otherwise. + +### Supported NGINX Annotations + +| Annotation | Limitations / Notes | +|-------------------------------------------------------|--------------------------------------------------------------------------------------------| +| `nginx.ingress.kubernetes.io/affinity` | | +| `nginx.ingress.kubernetes.io/affinity-mode` | Only persistent mode supported; balanced/canary not supported. | +| `nginx.ingress.kubernetes.io/auth-type` | | +| `nginx.ingress.kubernetes.io/auth-secret` | | +| `nginx.ingress.kubernetes.io/auth-secret-type` | | +| `nginx.ingress.kubernetes.io/auth-realm` | | +| `nginx.ingress.kubernetes.io/auth-url` | Only URL and response headers copy supported. Forward auth behaves differently than NGINX. | +| `nginx.ingress.kubernetes.io/auth-method` | | +| `nginx.ingress.kubernetes.io/auth-response-headers` | | +| `nginx.ingress.kubernetes.io/ssl-redirect` | Cannot opt-out per route if enabled globally. | +| `nginx.ingress.kubernetes.io/force-ssl-redirect` | Cannot opt-out per route if enabled globally. | +| `nginx.ingress.kubernetes.io/ssl-passthrough` | Some differences in SNI/default backend handling. | +| `nginx.ingress.kubernetes.io/use-regex` | | +| `nginx.ingress.kubernetes.io/session-cookie-name` | | +| `nginx.ingress.kubernetes.io/session-cookie-path` | | +| `nginx.ingress.kubernetes.io/session-cookie-domain` | | +| `nginx.ingress.kubernetes.io/session-cookie-samesite` | | +| `nginx.ingress.kubernetes.io/load-balance` | Only round_robin supported; ewma and IP hash not supported. | +| `nginx.ingress.kubernetes.io/backend-protocol` | FCGI and AUTO_HTTP not supported. | +| `nginx.ingress.kubernetes.io/enable-cors` | Partial support. | +| `nginx.ingress.kubernetes.io/cors-allow-credentials` | | +| `nginx.ingress.kubernetes.io/cors-allow-headers` | | +| `nginx.ingress.kubernetes.io/cors-allow-methods` | | +| `nginx.ingress.kubernetes.io/cors-allow-origin` | | +| `nginx.ingress.kubernetes.io/cors-max-age` | | +| `nginx.ingress.kubernetes.io/proxy-ssl-server-name` | | +| `nginx.ingress.kubernetes.io/proxy-ssl-name` | | +| `nginx.ingress.kubernetes.io/proxy-ssl-verify` | | +| `nginx.ingress.kubernetes.io/proxy-ssl-secret` | | +| `nginx.ingress.kubernetes.io/service-upstream` | | + +### Unsupported NGINX Annotations + +!!! question "Want to Add Support for More Annotations?" + + You can help extend support in two ways: + + - [**Open a PR**](../../../contributing/submitting-pull-requests.md) with the new annotation support. + - **Reach out** to the [Traefik Labs support team](https://info.traefik.io/request-commercial-support?cta=doc). + + All contributions and suggestions are welcome — let's build this together! + + +| Annotation | Notes | +|-----------------------------------------------------------------------------|------------------------------------------------------| +| `nginx.ingress.kubernetes.io/app-root` | Not supported yet. | +| `nginx.ingress.kubernetes.io/affinity-canary-behavior` | Not supported yet. | +| `nginx.ingress.kubernetes.io/auth-tls-secret` | Not supported yet. | +| `nginx.ingress.kubernetes.io/auth-tls-verify-depth` | Not supported yet. | +| `nginx.ingress.kubernetes.io/auth-tls-verify-client` | Not supported yet. | +| `nginx.ingress.kubernetes.io/auth-tls-error-page` | Not supported yet. | +| `nginx.ingress.kubernetes.io/auth-tls-pass-certificate-to-upstream` | Not supported yet. | +| `nginx.ingress.kubernetes.io/auth-tls-match-cn` | Not supported yet. | +| `nginx.ingress.kubernetes.io/auth-cache-key` | Not supported yet. | +| `nginx.ingress.kubernetes.io/auth-cache-duration` | Not supported yet. | +| `nginx.ingress.kubernetes.io/auth-keepalive` | Not supported yet. | +| `nginx.ingress.kubernetes.io/auth-keepalive-share-vars` | Not supported yet. | +| `nginx.ingress.kubernetes.io/auth-keepalive-requests` | Not supported yet. | +| `nginx.ingress.kubernetes.io/auth-keepalive-timeout` | Not supported yet. | +| `nginx.ingress.kubernetes.io/auth-proxy-set-headers` | Not supported yet. | +| `nginx.ingress.kubernetes.io/auth-snippet` | Not supported yet. | +| `nginx.ingress.kubernetes.io/enable-global-auth` | Not supported yet. | +| `nginx.ingress.kubernetes.io/canary` | Not supported yet. | +| `nginx.ingress.kubernetes.io/canary-by-header` | Not supported yet. | +| `nginx.ingress.kubernetes.io/canary-by-header-value` | Not supported yet. | +| `nginx.ingress.kubernetes.io/canary-by-header-pattern` | Not supported yet. | +| `nginx.ingress.kubernetes.io/canary-by-cookie` | Not supported yet. | +| `nginx.ingress.kubernetes.io/canary-weight` | Not supported yet. | +| `nginx.ingress.kubernetes.io/canary-weight-total` | Not supported yet. | +| `nginx.ingress.kubernetes.io/client-body-buffer-size` | Not supported yet. | +| `nginx.ingress.kubernetes.io/configuration-snippet` | Not supported yet. | +| `nginx.ingress.kubernetes.io/custom-http-errors` | Not supported yet. | +| `nginx.ingress.kubernetes.io/disable-proxy-intercept-errors` | Not supported yet. | +| `nginx.ingress.kubernetes.io/default-backend` | Not supported yet; use `defaultBackend` in Ingress spec. | +| `nginx.ingress.kubernetes.io/limit-rate-after` | Not supported yet. | +| `nginx.ingress.kubernetes.io/limit-rate` | Not supported yet. | +| `nginx.ingress.kubernetes.io/limit-whitelist` | Not supported yet. | +| `nginx.ingress.kubernetes.io/limit-rps` | Not supported yet. | +| `nginx.ingress.kubernetes.io/limit-rpm` | Not supported yet. | +| `nginx.ingress.kubernetes.io/limit-burst-multiplier` | Not supported yet. | +| `nginx.ingress.kubernetes.io/limit-connections` | Not supported yet. | +| `nginx.ingress.kubernetes.io/global-rate-limit` | Not supported yet. | +| `nginx.ingress.kubernetes.io/global-rate-limit-window` | Not supported yet. | +| `nginx.ingress.kubernetes.io/global-rate-limit-key` | Not supported yet. | +| `nginx.ingress.kubernetes.io/global-rate-limit-ignored-cidrs` | Not supported yet. | +| `nginx.ingress.kubernetes.io/permanent-redirect` | Not supported yet. | +| `nginx.ingress.kubernetes.io/permanent-redirect-code` | Not supported yet. | +| `nginx.ingress.kubernetes.io/temporal-redirect` | Not supported yet. | +| `nginx.ingress.kubernetes.io/preserve-trailing-slash` | Not supported yet; Traefik preserves by default. | +| `nginx.ingress.kubernetes.io/proxy-cookie-domain` | Not supported yet. | +| `nginx.ingress.kubernetes.io/proxy-cookie-path` | Not supported yet. | +| `nginx.ingress.kubernetes.io/proxy-connect-timeout` | Not supported yet. | +| `nginx.ingress.kubernetes.io/proxy-send-timeout` | Not supported yet. | +| `nginx.ingress.kubernetes.io/proxy-read-timeout` | Not supported yet. | +| `nginx.ingress.kubernetes.io/proxy-next-upstream` | Not supported yet. | +| `nginx.ingress.kubernetes.io/proxy-next-upstream-timeout` | Not supported yet. | +| `nginx.ingress.kubernetes.io/proxy-next-upstream-tries` | Not supported yet. | +| `nginx.ingress.kubernetes.io/proxy-request-buffering` | Not supported yet. | +| `nginx.ingress.kubernetes.io/proxy-redirect-from` | Not supported yet. | +| `nginx.ingress.kubernetes.io/proxy-redirect-to` | Not supported yet. | +| `nginx.ingress.kubernetes.io/proxy-http-version` | Not supported yet. | +| `nginx.ingress.kubernetes.io/proxy-ssl-ciphers` | Not supported yet. | +| `nginx.ingress.kubernetes.io/proxy-ssl-verify-depth` | Not supported yet. | +| `nginx.ingress.kubernetes.io/proxy-ssl-protocols` | Not supported yet. | +| `nginx.ingress.kubernetes.io/enable-rewrite-log` | Not supported yet. | +| `nginx.ingress.kubernetes.io/rewrite-target` | Not supported yet. | +| `nginx.ingress.kubernetes.io/satisfy` | Not supported yet. | +| `nginx.ingress.kubernetes.io/server-alias` | Not supported yet. | +| `nginx.ingress.kubernetes.io/server-snippet` | Not supported yet. | +| `nginx.ingress.kubernetes.io/session-cookie-conditional-samesite-none` | Not supported yet. | +| `nginx.ingress.kubernetes.io/session-cookie-expires` | Not supported yet. | +| `nginx.ingress.kubernetes.io/session-cookie-change-on-failure` | Not supported yet. | +| `nginx.ingress.kubernetes.io/ssl-ciphers` | Not supported yet. | +| `nginx.ingress.kubernetes.io/ssl-prefer-server-ciphers` | Not supported yet. | +| `nginx.ingress.kubernetes.io/connection-proxy-header` | Not supported yet. | +| `nginx.ingress.kubernetes.io/enable-access-log` | Not supported yet. | +| `nginx.ingress.kubernetes.io/enable-opentracing` | Not supported yet. | +| `nginx.ingress.kubernetes.io/opentracing-trust-incoming-span` | Not supported yet. | +| `nginx.ingress.kubernetes.io/enable-opentelemetry` | Not supported yet. | +| `nginx.ingress.kubernetes.io/opentelemetry-trust-incoming-span` | Not supported yet. | +| `nginx.ingress.kubernetes.io/enable-modsecurity` | Not supported yet. | +| `nginx.ingress.kubernetes.io/enable-owasp-core-rules` | Not supported yet. | +| `nginx.ingress.kubernetes.io/modsecurity-transaction-id` | Not supported yet. | +| `nginx.ingress.kubernetes.io/modsecurity-snippet` | Not supported yet. | +| `nginx.ingress.kubernetes.io/mirror-request-body` | Not supported yet. | +| `nginx.ingress.kubernetes.io/mirror-target` | Not supported yet. | +| `nginx.ingress.kubernetes.io/mirror-host` | Not supported yet. | +| `nginx.ingress.kubernetes.io/x-forwarded-prefix` | Not supported yet. | +| `nginx.ingress.kubernetes.io/upstream-hash-by` | Not supported yet. | +| `nginx.ingress.kubernetes.io/upstream-vhost` | Not supported yet. | +| `nginx.ingress.kubernetes.io/denylist-source-range` | Not supported yet. | +| `nginx.ingress.kubernetes.io/whitelist-source-range` | Not supported yet. | +| `nginx.ingress.kubernetes.io/proxy-buffering` | Not supported yet. | +| `nginx.ingress.kubernetes.io/proxy-buffers-number` | Not supported yet. | +| `nginx.ingress.kubernetes.io/proxy-buffer-size` | Not supported yet. | +| `nginx.ingress.kubernetes.io/proxy-max-temp-file-size` | Not supported yet. | +| `nginx.ingress.kubernetes.io/stream-snippet` | Not supported yet. | diff --git a/docs/content/reference/routing-configuration/kubernetes/ingress.md b/docs/content/reference/routing-configuration/kubernetes/ingress.md index f0dba8651..a9b0aab71 100644 --- a/docs/content/reference/routing-configuration/kubernetes/ingress.md +++ b/docs/content/reference/routing-configuration/kubernetes/ingress.md @@ -69,7 +69,7 @@ spec: ??? info "`traefik.ingress.kubernetes.io/router.priority`" - See [priority](../http/router/rules-and-priority.md#priority-calculation) for more information. + See [priority](../http/routing/rules-and-priority.md#priority-calculation) for more information. ```yaml traefik.ingress.kubernetes.io/router.priority: "42" @@ -82,7 +82,7 @@ spec: RuleSyntax option is deprecated and will be removed in the next major version. Please do not use this field and rewrite the router rules to use the v3 syntax. - See [rule syntax](../http/router/rules-and-priority.md#rulesyntax) for more information. + See [rule syntax](../http/routing/rules-and-priority.md#rulesyntax) for more information. ```yaml traefik.ingress.kubernetes.io/router.rulesyntax: "v2" @@ -133,7 +133,7 @@ spec: ??? info "`traefik.ingress.kubernetes.io/router.tls.options`" - See [options](../kubernetes/crd/http/tlsoption.md) for more information. + See [options](../kubernetes/crd/tls/tlsoption.md) for more information. ```yaml traefik.ingress.kubernetes.io/router.tls.options: foobar@file @@ -141,7 +141,7 @@ spec: ??? info "`traefik.ingress.kubernetes.io/router.observability.accesslogs`" - See [here](../http/router/observability.md) for more information. + See [here](../http/routing/observability.md) for more information. ```yaml traefik.ingress.kubernetes.io/router.observability.accesslogs: true @@ -149,7 +149,7 @@ spec: ??? info "`traefik.ingress.kubernetes.io/router.observability.metrics`" - See [here](../http/router/observability.md) for more information. + See [here](../http/routing/observability.md) for more information. ```yaml traefik.ingress.kubernetes.io/router.observability.metrics: true @@ -157,7 +157,7 @@ spec: ??? info "`traefik.ingress.kubernetes.io/router.observability.tracing`" - See [here](../http/router/observability.md) for more information. + See [here](../http/routing/observability.md) for more information. ```yaml traefik.ingress.kubernetes.io/router.observability.tracing: true @@ -402,7 +402,7 @@ This way, any Ingress attached to this Entrypoint will have TLS termination by d serviceAccountName: traefik-ingress-controller containers: - name: traefik - image: traefik:v3.4 + image: traefik:v3.5 args: - --entryPoints.websecure.address=:443 - --entryPoints.websecure.http.tls @@ -589,7 +589,7 @@ and will connect via TLS automatically. Please note that by enabling TLS communication between traefik and your pods, you will have to have trusted certificates that have the proper trust chain and IP subject name. If this is not an option, you may need to skip TLS certificate verification. - See the [`insecureSkipVerify` TLSOption](../kubernetes/crd/http/tlsoption.md) setting for more details. + See the [`insecureSkipVerify` TLSOption](../kubernetes/crd/tls/tlsoption.md) setting for more details. ## Global Default Backend Ingresses diff --git a/docs/content/reference/routing-configuration/other-providers/consul-catalog.md b/docs/content/reference/routing-configuration/other-providers/consul-catalog.md index 534891cf3..7958a4c01 100644 --- a/docs/content/reference/routing-configuration/other-providers/consul-catalog.md +++ b/docs/content/reference/routing-configuration/other-providers/consul-catalog.md @@ -25,7 +25,7 @@ With Consul Catalog, Traefik can leverage tags attached to a service to generate ### General -Traefik creates, for each consul Catalog service, a corresponding [service](../http/load-balancing/service.md) and [router](../http/router/rules-and-priority.md). +Traefik creates, for each consul Catalog service, a corresponding [service](../http/load-balancing/service.md) and [router](../http/routing/rules-and-priority.md). The Service automatically gets a server per instance in this consul Catalog service, and the router gets a default rule attached to it, based on the service name. @@ -37,7 +37,7 @@ For example, to change the rule, you could add the tag ```traefik.http.routers.m ??? info "`traefik.http.routers..rule`" - See [rule](../http/router/rules-and-priority.md) for more information. + See [rule](../http/routing/rules-and-priority.md) for more information. ```yaml traefik.http.routers.myrouter.rule=Host(`example.com`) @@ -50,7 +50,7 @@ For example, to change the rule, you could add the tag ```traefik.http.routers.m RuleSyntax option is deprecated and will be removed in the next major version. Please do not use this field and rewrite the router rules to use the v3 syntax. - See [ruleSyntax](../http/router/rules-and-priority.md#rulesyntax) for more information. + See [ruleSyntax](../http/routing/rules-and-priority.md#rulesyntax) for more information. ```yaml traefik.http.routers.myrouter.ruleSyntax=v3 @@ -58,7 +58,7 @@ For example, to change the rule, you could add the tag ```traefik.http.routers.m ??? info "`traefik.http.routers..priority`" - See [priority](../http/router/rules-and-priority.md#priority-calculation) for more information. + See [priority](../http/routing/rules-and-priority.md#priority-calculation) for more information. ```yaml - "traefik.tcp.routers.mytcprouter.priority=42" @@ -222,6 +222,14 @@ you'd add the tag `traefik.http.services.{name-of-your-choice}.loadbalancer.pass traefik.http.services.myservice.loadbalancer.healthcheck.interval=10 ``` +??? info "`traefik.http.services..loadbalancer.healthcheck.unhealthyinterval`" + + See [health check](../http/load-balancing/service.md#health-check) for more information. + + ```yaml + traefik.http.services.myservice.loadbalancer.healthcheck.unhealthyinterval=10 + ``` + ??? info "`traefik.http.services..loadbalancer.healthcheck.path`" See [health check](../http/load-balancing/service.md#health-check) for more information. @@ -377,7 +385,7 @@ You can declare TCP Routers, Middlewares and/or Services using tags. ??? info "`traefik.tcp.routers..rule`" - See [rule](../tcp/router/rules-and-priority.md#rules) for more information. + See [rule](../tcp/routing/rules-and-priority.md#rules) for more information. ```yaml traefik.tcp.routers.mytcprouter.rule=HostSNI(`example.com`) @@ -397,7 +405,7 @@ You can declare TCP Routers, Middlewares and/or Services using tags. ``` ??? info "`traefik.tcp.routers..priority`" - See [priority](../tcp/router/rules-and-priority.md#priority) for more information. + See [priority](../tcp/routing/rules-and-priority.md#priority-calculation) for more information. ```yaml - "traefik.tcp.routers.mytcprouter.priority=42" ``` @@ -452,7 +460,7 @@ You can declare TCP Routers, Middlewares and/or Services using tags. ??? info "`traefik.tcp.routers..tls.passthrough`" - See [Passthrough](../tcp/tls.md#passthrough) for more information. + See [Passthrough](../tcp/tls.md#opt-passthrough) for more information. ```yaml traefik.tcp.routers.mytcprouter.tls.passthrough=true @@ -477,14 +485,6 @@ You can declare TCP Routers, Middlewares and/or Services using tags. traefik.tcp.services.mytcpservice.loadbalancer.server.tls=true ``` -??? info "`traefik.tcp.services..loadbalancer.proxyprotocol.version`" - - See [PROXY protocol](../tcp/service.md#proxy-protocol) for more information. - - ```yaml - traefik.tcp.services.mytcpservice.loadbalancer.proxyprotocol.version=1 - ``` - ??? info "`traefik.tcp.services..loadbalancer.serverstransport`" Allows to reference a ServersTransport resource that is defined either with the File provider or the Kubernetes CRD one. diff --git a/docs/content/reference/routing-configuration/other-providers/docker.md b/docs/content/reference/routing-configuration/other-providers/docker.md index e17c9fc91..a6773b190 100644 --- a/docs/content/reference/routing-configuration/other-providers/docker.md +++ b/docs/content/reference/routing-configuration/other-providers/docker.md @@ -35,7 +35,6 @@ With Docker, Traefik can leverage labels attached to a container to generate rou Attaching labels to containers (in your docker compose file) ```yaml - version: "3" services: my-container: # ... @@ -48,7 +47,6 @@ With Docker, Traefik can leverage labels attached to a container to generate rou Forward requests for `http://example.com` to `http://:12345`: ```yaml - version: "3" services: my-container: # ... @@ -71,7 +69,6 @@ With Docker, Traefik can leverage labels attached to a container to generate rou In this example, requests are forwarded for `http://example-a.com` to `http://:8000` in addition to `http://example-b.com` forwarding to `http://:9000`: ```yaml - version: "3" services: my-container: # ... @@ -96,7 +93,7 @@ With Docker, Traefik can leverage labels attached to a container to generate rou ### General -Traefik creates, for each container, a corresponding [service](../http/load-balancing/service.md) and [router](../http/router/rules-and-priority.md). +Traefik creates, for each container, a corresponding [service](../http/load-balancing/service.md) and [router](../http/routing/rules-and-priority.md). The Service automatically gets a server per instance of the container, and the router automatically gets a rule defined by `defaultRule` (if no rule for it was defined in labels). @@ -150,7 +147,7 @@ For example, to change the rule, you could add the label ```traefik.http.routers ??? info "`traefik.http.routers..rule`" - See [rule](../http/router/rules-and-priority.md) for more information. + See [rule](../http/routing/rules-and-priority.md) for more information. ```yaml "traefik.http.routers.myrouter.rule=Host(`example.com`)" @@ -163,7 +160,7 @@ For example, to change the rule, you could add the label ```traefik.http.routers RuleSyntax option is deprecated and will be removed in the next major version. Please do not use this field and rewrite the router rules to use the v3 syntax. - See [ruleSyntax](../http/router/rules-and-priority.md#rulesyntax) for more information. + See [ruleSyntax](../http/routing/rules-and-priority.md#rulesyntax) for more information. ```yaml traefik.http.routers.myrouter.ruleSyntax=v3 @@ -255,7 +252,7 @@ For example, to change the rule, you could add the label ```traefik.http.routers ??? info "`traefik.http.routers..priority`" - See [priority](../http/router/rules-and-priority.md#priority-calculation) for more information. + See [priority](../http/routing/rules-and-priority.md#priority-calculation) for more information. ```yaml "traefik.http.routers.myrouter.priority=42" @@ -327,6 +324,14 @@ you'd add the label `traefik.http.services..loadbalancer.pa "traefik.http.services.myservice.loadbalancer.healthcheck.interval=10s" ``` +??? info "`traefik.http.services..loadbalancer.healthcheck.unhealthyinterval`" + + See [health check](../http/load-balancing/service.md#health-check) for more information. + + ```yaml + "traefik.http.services.myservice.loadbalancer.healthcheck.unhealthyinterval=10s" + ``` + ??? info "`traefik.http.services..loadbalancer.healthcheck.path`" See [health check](../http/load-balancing/service.md#health-check) for more information. @@ -493,7 +498,7 @@ You can declare TCP Routers and/or Services using labels. ??? info "`traefik.tcp.routers..rule`" - See [rule](../tcp/router/rules-and-priority.md#rules) for more information. + See [rule](../tcp/routing/rules-and-priority.md#rules) for more information. ```yaml "traefik.tcp.routers.mytcprouter.rule=HostSNI(`example.com`)" @@ -560,7 +565,7 @@ You can declare TCP Routers and/or Services using labels. ??? info "`traefik.tcp.routers..tls.passthrough`" - See [TLS](../tcp/tls.md#passthrough) for more information. + See [TLS](../tcp/tls.md#opt-passthrough) for more information. ```yaml "traefik.tcp.routers.mytcprouter.tls.passthrough=true" @@ -568,7 +573,7 @@ You can declare TCP Routers and/or Services using labels. ??? info "`traefik.tcp.routers..priority`" - See [priority](../tcp/router/rules-and-priority.md) for more information. + See [priority](../tcp/routing/rules-and-priority.md) for more information. ```yaml "traefik.tcp.routers.mytcprouter.priority=42" @@ -592,14 +597,6 @@ You can declare TCP Routers and/or Services using labels. "traefik.tcp.services.mytcpservice.loadbalancer.server.tls=true" ``` -??? info "`traefik.tcp.services..loadbalancer.proxyprotocol.version`" - - See [PROXY protocol](../tcp/service.md#proxy-protocol) for more information. - - ```yaml - "traefik.tcp.services.mytcpservice.loadbalancer.proxyprotocol.version=1" - ``` - ??? info "`traefik.tcp.services..loadbalancer.serverstransport`" Allows to reference a ServersTransport resource that is defined either with the File provider or the Kubernetes CRD one. diff --git a/docs/content/reference/routing-configuration/other-providers/ecs.md b/docs/content/reference/routing-configuration/other-providers/ecs.md index b17d9330a..03b4f4d45 100644 --- a/docs/content/reference/routing-configuration/other-providers/ecs.md +++ b/docs/content/reference/routing-configuration/other-providers/ecs.md @@ -25,7 +25,7 @@ With ECS, Traefik can leverage labels attached to a container to generate routin ### General -Traefik creates, for each elastic service, a corresponding [service](../http/load-balancing/service.md) and [router](../http/router/rules-and-priority.md). +Traefik creates, for each elastic service, a corresponding [service](../http/load-balancing/service.md) and [router](../http/routing/rules-and-priority.md). The Service automatically gets a server per elastic container, and the router gets a default rule attached to it, based on the service name. @@ -39,7 +39,7 @@ For example, to change the rule, you could add the label ```traefik.http.routers ??? info "`traefik.http.routers..rule`" - See [rule](../http/router/rules-and-priority.md#rules) for more information. + See [rule](../http/routing/rules-and-priority.md#rules) for more information. ```yaml traefik.http.routers.myrouter.rule=Host(`example.com`) @@ -52,7 +52,7 @@ For example, to change the rule, you could add the label ```traefik.http.routers RuleSyntax option is deprecated and will be removed in the next major version. Please do not use this field and rewrite the router rules to use the v3 syntax. - See [ruleSyntax](../http/router/rules-and-priority.md#rulesyntax) for more information. + See [ruleSyntax](../http/routing/rules-and-priority.md#rulesyntax) for more information. ```yaml traefik.http.routers.myrouter.ruleSyntax=v3 @@ -146,7 +146,7 @@ For example, to change the rule, you could add the label ```traefik.http.routers ??? info "`traefik.http.routers..priority`" - See [priority](../http/router/rules-and-priority.md#priority-calculation) for more information. + See [priority](../http/routing/rules-and-priority.md#priority-calculation) for more information. ```yaml traefik.http.routers.myrouter.priority=42 @@ -218,6 +218,14 @@ you'd add the label `traefik.http.services.{name-of-your-choice}.loadbalancer.pa traefik.http.services.myservice.loadbalancer.healthcheck.interval=10 ``` +??? info "`traefik.http.services..loadbalancer.healthcheck.unhealthyinterval`" + + See [health check](../http/load-balancing/service.md#health-check) for more information. + + ```yaml + traefik.http.services.myservice.loadbalancer.healthcheck.unhealthyinterval=10 + ``` + ??? info "`traefik.http.services..loadbalancer.healthcheck.path`" See [health check](../http/load-balancing/service.md#health-check) for more information. @@ -446,7 +454,7 @@ You can declare TCP Routers and/or Services using labels. ??? info "`traefik.tcp.routers..tls.passthrough`" - See [Passthrough](../tcp/tls.md#passthrough) for more information. + See [Passthrough](../tcp/tls.md#opt-passthrough) for more information. ```yaml traefik.tcp.routers.mytcprouter.tls.passthrough=true @@ -454,7 +462,7 @@ You can declare TCP Routers and/or Services using labels. ??? info "`traefik.tcp.routers..priority`" - See [priority](../tcp/router/rules-and-priority.md#priority) for more information. + See [priority](../tcp/routing/rules-and-priority.md#priority-calculation) for more information. ```yaml traefik.tcp.routers.mytcprouter.priority=42 @@ -486,14 +494,6 @@ You can declare TCP Routers and/or Services using labels. traefik.http.services.myservice.loadbalancer.server.weight=42 ``` -??? info "`traefik.tcp.services..loadbalancer.proxyprotocol.version`" - - See [PROXY protocol](../tcp/service.md#proxy-protocol) for more information. - - ```yaml - traefik.tcp.services.mytcpservice.loadbalancer.proxyprotocol.version=1 - ``` - ??? info "`traefik.tcp.services..loadbalancer.serverstransport`" Allows to reference a ServersTransport resource that is defined either with the File provider or the Kubernetes CRD one. diff --git a/docs/content/reference/routing-configuration/other-providers/file.md b/docs/content/reference/routing-configuration/other-providers/file.md new file mode 100644 index 000000000..58dd5b2fc --- /dev/null +++ b/docs/content/reference/routing-configuration/other-providers/file.md @@ -0,0 +1,120 @@ +--- +title: "Traefik File Dynamic Configuration" +description: "This guide will provide you with the YAML and TOML files for dynamic configuration in Traefik Proxy. Read the technical documentation." +--- + + +# Traefik and Configuration Files + +!!! warning "Work In Progress" + + This page is still work in progress to provide a better documention of the routing options. + + It has been created to provide a centralized page with all the option in YAML and TOML format. + +## Configuration Options + +```yml tab="YAML" +--8<-- "content/reference/routing-configuration/other-providers/file.yaml" +``` + +```toml tab="TOML" +--8<-- "content/reference/routing-configuration/other-providers/file.toml" +``` + +## Go Templating + +!!! warning + + Go Templating only works with dedicated dynamic configuration files. + Templating does not work in the Traefik main static configuration file. + +Traefik supports using Go templating to automatically generate repetitive sections of configuration files. +These sections must be a valid [Go template](https://pkg.go.dev/text/template/), and can use +[sprig template functions](https://masterminds.github.io/sprig/). + +To illustrate, it is possible to easily define multiple routers, services, and TLS certificates as described in the following examples: + +??? example "Configuring Using Templating" + + ```yaml tab="YAML" + http: + routers: + {{range $i, $e := until 100 }} + router{{ $e }}-{{ env "MY_ENV_VAR" }}: + # ... + {{end}} + + services: + {{range $i, $e := until 100 }} + application{{ $e }}: + # ... + {{end}} + + tcp: + routers: + {{range $i, $e := until 100 }} + router{{ $e }}: + # ... + {{end}} + + services: + {{range $i, $e := until 100 }} + service{{ $e }}: + # ... + {{end}} + + tls: + certificates: + {{ range $i, $e := until 10 }} + - certFile: "/etc/traefik/cert-{{ $e }}.pem" + keyFile: "/etc/traefik/cert-{{ $e }}.key" + store: + - "my-store-foo-{{ $e }}" + - "my-store-bar-{{ $e }}" + {{end}} + ``` + + ```toml tab="TOML" + # template-rules.toml + [http] + + [http.routers] + {{ range $i, $e := until 100 }} + [http.routers.router{{ $e }}-{{ env "MY_ENV_VAR" }}] + # ... + {{ end }} + + [http.services] + {{ range $i, $e := until 100 }} + [http.services.service{{ $e }}] + # ... + {{ end }} + + [tcp] + + [tcp.routers] + {{ range $i, $e := until 100 }} + [tcp.routers.router{{ $e }}] + # ... + {{ end }} + + [tcp.services] + {{ range $i, $e := until 100 }} + [http.services.service{{ $e }}] + # ... + {{ end }} + + {{ range $i, $e := until 10 }} + [[tls.certificates]] + certFile = "/etc/traefik/cert-{{ $e }}.pem" + keyFile = "/etc/traefik/cert-{{ $e }}.key" + stores = ["my-store-foo-{{ $e }}", "my-store-bar-{{ $e }}"] + {{ end }} + + [tls.config] + {{ range $i, $e := until 10 }} + [tls.config.TLS{{ $e }}] + # ... + {{ end }} + ``` diff --git a/docs/content/reference/routing-configuration/other-providers/file.toml b/docs/content/reference/routing-configuration/other-providers/file.toml new file mode 100644 index 000000000..985f96e21 --- /dev/null +++ b/docs/content/reference/routing-configuration/other-providers/file.toml @@ -0,0 +1,615 @@ +## CODE GENERATED AUTOMATICALLY +## THIS FILE MUST NOT BE EDITED BY HAND +[http] + [http.routers] + [http.routers.Router0] + entryPoints = ["foobar", "foobar"] + middlewares = ["foobar", "foobar"] + service = "foobar" + rule = "foobar" + ruleSyntax = "foobar" + priority = 42 + [http.routers.Router0.tls] + options = "foobar" + certResolver = "foobar" + + [[http.routers.Router0.tls.domains]] + main = "foobar" + sans = ["foobar", "foobar"] + + [[http.routers.Router0.tls.domains]] + main = "foobar" + sans = ["foobar", "foobar"] + [http.routers.Router0.observability] + accessLogs = true + metrics = true + tracing = true + traceVerbosity = "foobar" + [http.routers.Router1] + entryPoints = ["foobar", "foobar"] + middlewares = ["foobar", "foobar"] + service = "foobar" + rule = "foobar" + ruleSyntax = "foobar" + priority = 42 + [http.routers.Router1.tls] + options = "foobar" + certResolver = "foobar" + + [[http.routers.Router1.tls.domains]] + main = "foobar" + sans = ["foobar", "foobar"] + + [[http.routers.Router1.tls.domains]] + main = "foobar" + sans = ["foobar", "foobar"] + [http.routers.Router1.observability] + accessLogs = true + metrics = true + tracing = true + traceVerbosity = "foobar" + [http.services] + [http.services.Service01] + [http.services.Service01.failover] + service = "foobar" + fallback = "foobar" + [http.services.Service01.failover.healthCheck] + [http.services.Service02] + [http.services.Service02.loadBalancer] + strategy = "foobar" + passHostHeader = true + serversTransport = "foobar" + [http.services.Service02.loadBalancer.sticky] + [http.services.Service02.loadBalancer.sticky.cookie] + name = "foobar" + secure = true + httpOnly = true + sameSite = "foobar" + maxAge = 42 + path = "foobar" + domain = "foobar" + + [[http.services.Service02.loadBalancer.servers]] + url = "foobar" + weight = 42 + preservePath = true + + [[http.services.Service02.loadBalancer.servers]] + url = "foobar" + weight = 42 + preservePath = true + [http.services.Service02.loadBalancer.healthCheck] + scheme = "foobar" + mode = "foobar" + path = "foobar" + method = "foobar" + status = 42 + port = 42 + interval = "42s" + unhealthyInterval = "42s" + timeout = "42s" + hostname = "foobar" + followRedirects = true + [http.services.Service02.loadBalancer.healthCheck.headers] + name0 = "foobar" + name1 = "foobar" + [http.services.Service02.loadBalancer.responseForwarding] + flushInterval = "42s" + [http.services.Service03] + [http.services.Service03.mirroring] + service = "foobar" + mirrorBody = true + maxBodySize = 42 + + [[http.services.Service03.mirroring.mirrors]] + name = "foobar" + percent = 42 + + [[http.services.Service03.mirroring.mirrors]] + name = "foobar" + percent = 42 + [http.services.Service03.mirroring.healthCheck] + [http.services.Service04] + [http.services.Service04.weighted] + + [[http.services.Service04.weighted.services]] + name = "foobar" + weight = 42 + + [[http.services.Service04.weighted.services]] + name = "foobar" + weight = 42 + [http.services.Service04.weighted.sticky] + [http.services.Service04.weighted.sticky.cookie] + name = "foobar" + secure = true + httpOnly = true + sameSite = "foobar" + maxAge = 42 + path = "foobar" + domain = "foobar" + [http.services.Service04.weighted.healthCheck] + [http.middlewares] + [http.middlewares.Middleware01] + [http.middlewares.Middleware01.addPrefix] + prefix = "foobar" + [http.middlewares.Middleware02] + [http.middlewares.Middleware02.basicAuth] + users = ["foobar", "foobar"] + usersFile = "foobar" + realm = "foobar" + removeHeader = true + headerField = "foobar" + [http.middlewares.Middleware03] + [http.middlewares.Middleware03.buffering] + maxRequestBodyBytes = 42 + memRequestBodyBytes = 42 + maxResponseBodyBytes = 42 + memResponseBodyBytes = 42 + retryExpression = "foobar" + [http.middlewares.Middleware04] + [http.middlewares.Middleware04.chain] + middlewares = ["foobar", "foobar"] + [http.middlewares.Middleware05] + [http.middlewares.Middleware05.circuitBreaker] + expression = "foobar" + checkPeriod = "42s" + fallbackDuration = "42s" + recoveryDuration = "42s" + responseCode = 42 + [http.middlewares.Middleware06] + [http.middlewares.Middleware06.compress] + excludedContentTypes = ["foobar", "foobar"] + includedContentTypes = ["foobar", "foobar"] + minResponseBodyBytes = 42 + encodings = ["foobar", "foobar"] + defaultEncoding = "foobar" + [http.middlewares.Middleware07] + [http.middlewares.Middleware07.contentType] + autoDetect = true + [http.middlewares.Middleware08] + [http.middlewares.Middleware08.digestAuth] + users = ["foobar", "foobar"] + usersFile = "foobar" + removeHeader = true + realm = "foobar" + headerField = "foobar" + [http.middlewares.Middleware09] + [http.middlewares.Middleware09.errors] + status = ["foobar", "foobar"] + service = "foobar" + query = "foobar" + [http.middlewares.Middleware09.errors.statusRewrites] + name0 = 42 + name1 = 42 + [http.middlewares.Middleware10] + [http.middlewares.Middleware10.forwardAuth] + address = "foobar" + trustForwardHeader = true + authResponseHeaders = ["foobar", "foobar"] + authResponseHeadersRegex = "foobar" + authRequestHeaders = ["foobar", "foobar"] + addAuthCookiesToResponse = ["foobar", "foobar"] + headerField = "foobar" + forwardBody = true + maxBodySize = 42 + preserveLocationHeader = true + preserveRequestMethod = true + [http.middlewares.Middleware10.forwardAuth.tls] + ca = "foobar" + cert = "foobar" + key = "foobar" + insecureSkipVerify = true + caOptional = true + [http.middlewares.Middleware11] + [http.middlewares.Middleware11.grpcWeb] + allowOrigins = ["foobar", "foobar"] + [http.middlewares.Middleware12] + [http.middlewares.Middleware12.headers] + accessControlAllowCredentials = true + accessControlAllowHeaders = ["foobar", "foobar"] + accessControlAllowMethods = ["foobar", "foobar"] + accessControlAllowOriginList = ["foobar", "foobar"] + accessControlAllowOriginListRegex = ["foobar", "foobar"] + accessControlExposeHeaders = ["foobar", "foobar"] + accessControlMaxAge = 42 + addVaryHeader = true + allowedHosts = ["foobar", "foobar"] + hostsProxyHeaders = ["foobar", "foobar"] + stsSeconds = 42 + stsIncludeSubdomains = true + stsPreload = true + forceSTSHeader = true + frameDeny = true + customFrameOptionsValue = "foobar" + contentTypeNosniff = true + browserXssFilter = true + customBrowserXSSValue = "foobar" + contentSecurityPolicy = "foobar" + contentSecurityPolicyReportOnly = "foobar" + publicKey = "foobar" + referrerPolicy = "foobar" + permissionsPolicy = "foobar" + isDevelopment = true + featurePolicy = "foobar" + sslRedirect = true + sslTemporaryRedirect = true + sslHost = "foobar" + sslForceHost = true + [http.middlewares.Middleware12.headers.customRequestHeaders] + name0 = "foobar" + name1 = "foobar" + [http.middlewares.Middleware12.headers.customResponseHeaders] + name0 = "foobar" + name1 = "foobar" + [http.middlewares.Middleware12.headers.sslProxyHeaders] + name0 = "foobar" + name1 = "foobar" + [http.middlewares.Middleware13] + [http.middlewares.Middleware13.ipAllowList] + sourceRange = ["foobar", "foobar"] + rejectStatusCode = 42 + [http.middlewares.Middleware13.ipAllowList.ipStrategy] + depth = 42 + excludedIPs = ["foobar", "foobar"] + ipv6Subnet = 42 + [http.middlewares.Middleware14] + [http.middlewares.Middleware14.ipWhiteList] + sourceRange = ["foobar", "foobar"] + [http.middlewares.Middleware14.ipWhiteList.ipStrategy] + depth = 42 + excludedIPs = ["foobar", "foobar"] + ipv6Subnet = 42 + [http.middlewares.Middleware15] + [http.middlewares.Middleware15.inFlightReq] + amount = 42 + [http.middlewares.Middleware15.inFlightReq.sourceCriterion] + requestHeaderName = "foobar" + requestHost = true + [http.middlewares.Middleware15.inFlightReq.sourceCriterion.ipStrategy] + depth = 42 + excludedIPs = ["foobar", "foobar"] + ipv6Subnet = 42 + [http.middlewares.Middleware16] + [http.middlewares.Middleware16.passTLSClientCert] + pem = true + [http.middlewares.Middleware16.passTLSClientCert.info] + notAfter = true + notBefore = true + sans = true + serialNumber = true + [http.middlewares.Middleware16.passTLSClientCert.info.subject] + country = true + province = true + locality = true + organization = true + organizationalUnit = true + commonName = true + serialNumber = true + domainComponent = true + [http.middlewares.Middleware16.passTLSClientCert.info.issuer] + country = true + province = true + locality = true + organization = true + commonName = true + serialNumber = true + domainComponent = true + [http.middlewares.Middleware17] + [http.middlewares.Middleware17.plugin] + [http.middlewares.Middleware17.plugin.PluginConf0] + name0 = "foobar" + name1 = "foobar" + [http.middlewares.Middleware17.plugin.PluginConf1] + name0 = "foobar" + name1 = "foobar" + [http.middlewares.Middleware18] + [http.middlewares.Middleware18.rateLimit] + average = 42 + period = "42s" + burst = 42 + [http.middlewares.Middleware18.rateLimit.sourceCriterion] + requestHeaderName = "foobar" + requestHost = true + [http.middlewares.Middleware18.rateLimit.sourceCriterion.ipStrategy] + depth = 42 + excludedIPs = ["foobar", "foobar"] + ipv6Subnet = 42 + [http.middlewares.Middleware18.rateLimit.redis] + endpoints = ["foobar", "foobar"] + username = "foobar" + password = "foobar" + db = 42 + poolSize = 42 + minIdleConns = 42 + maxActiveConns = 42 + readTimeout = "42s" + writeTimeout = "42s" + dialTimeout = "42s" + [http.middlewares.Middleware18.rateLimit.redis.tls] + ca = "foobar" + cert = "foobar" + key = "foobar" + insecureSkipVerify = true + [http.middlewares.Middleware19] + [http.middlewares.Middleware19.redirectRegex] + regex = "foobar" + replacement = "foobar" + permanent = true + [http.middlewares.Middleware20] + [http.middlewares.Middleware20.redirectScheme] + scheme = "foobar" + port = "foobar" + permanent = true + [http.middlewares.Middleware21] + [http.middlewares.Middleware21.replacePath] + path = "foobar" + [http.middlewares.Middleware22] + [http.middlewares.Middleware22.replacePathRegex] + regex = "foobar" + replacement = "foobar" + [http.middlewares.Middleware23] + [http.middlewares.Middleware23.retry] + attempts = 42 + initialInterval = "42s" + [http.middlewares.Middleware24] + [http.middlewares.Middleware24.stripPrefix] + prefixes = ["foobar", "foobar"] + forceSlash = true + [http.middlewares.Middleware25] + [http.middlewares.Middleware25.stripPrefixRegex] + regex = ["foobar", "foobar"] + [http.serversTransports] + [http.serversTransports.ServersTransport0] + serverName = "foobar" + insecureSkipVerify = true + rootCAs = ["foobar", "foobar"] + maxIdleConnsPerHost = 42 + disableHTTP2 = true + peerCertURI = "foobar" + + [[http.serversTransports.ServersTransport0.certificates]] + certFile = "foobar" + keyFile = "foobar" + + [[http.serversTransports.ServersTransport0.certificates]] + certFile = "foobar" + keyFile = "foobar" + [http.serversTransports.ServersTransport0.forwardingTimeouts] + dialTimeout = "42s" + responseHeaderTimeout = "42s" + idleConnTimeout = "42s" + readIdleTimeout = "42s" + pingTimeout = "42s" + [http.serversTransports.ServersTransport0.spiffe] + ids = ["foobar", "foobar"] + trustDomain = "foobar" + [http.serversTransports.ServersTransport1] + serverName = "foobar" + insecureSkipVerify = true + rootCAs = ["foobar", "foobar"] + maxIdleConnsPerHost = 42 + disableHTTP2 = true + peerCertURI = "foobar" + + [[http.serversTransports.ServersTransport1.certificates]] + certFile = "foobar" + keyFile = "foobar" + + [[http.serversTransports.ServersTransport1.certificates]] + certFile = "foobar" + keyFile = "foobar" + [http.serversTransports.ServersTransport1.forwardingTimeouts] + dialTimeout = "42s" + responseHeaderTimeout = "42s" + idleConnTimeout = "42s" + readIdleTimeout = "42s" + pingTimeout = "42s" + [http.serversTransports.ServersTransport1.spiffe] + ids = ["foobar", "foobar"] + trustDomain = "foobar" + +[tcp] + [tcp.routers] + [tcp.routers.TCPRouter0] + entryPoints = ["foobar", "foobar"] + middlewares = ["foobar", "foobar"] + service = "foobar" + rule = "foobar" + ruleSyntax = "foobar" + priority = 42 + [tcp.routers.TCPRouter0.tls] + passthrough = true + options = "foobar" + certResolver = "foobar" + + [[tcp.routers.TCPRouter0.tls.domains]] + main = "foobar" + sans = ["foobar", "foobar"] + + [[tcp.routers.TCPRouter0.tls.domains]] + main = "foobar" + sans = ["foobar", "foobar"] + [tcp.routers.TCPRouter1] + entryPoints = ["foobar", "foobar"] + middlewares = ["foobar", "foobar"] + service = "foobar" + rule = "foobar" + ruleSyntax = "foobar" + priority = 42 + [tcp.routers.TCPRouter1.tls] + passthrough = true + options = "foobar" + certResolver = "foobar" + + [[tcp.routers.TCPRouter1.tls.domains]] + main = "foobar" + sans = ["foobar", "foobar"] + + [[tcp.routers.TCPRouter1.tls.domains]] + main = "foobar" + sans = ["foobar", "foobar"] + [tcp.services] + [tcp.services.TCPService01] + [tcp.services.TCPService01.loadBalancer] + serversTransport = "foobar" + terminationDelay = 42 + + [[tcp.services.TCPService01.loadBalancer.servers]] + address = "foobar" + tls = true + + [[tcp.services.TCPService01.loadBalancer.servers]] + address = "foobar" + tls = true + [tcp.services.TCPService01.loadBalancer.proxyProtocol] + version = 42 + [tcp.services.TCPService02] + [tcp.services.TCPService02.weighted] + + [[tcp.services.TCPService02.weighted.services]] + name = "foobar" + weight = 42 + + [[tcp.services.TCPService02.weighted.services]] + name = "foobar" + weight = 42 + [tcp.middlewares] + [tcp.middlewares.TCPMiddleware01] + [tcp.middlewares.TCPMiddleware01.ipAllowList] + sourceRange = ["foobar", "foobar"] + [tcp.middlewares.TCPMiddleware02] + [tcp.middlewares.TCPMiddleware02.ipWhiteList] + sourceRange = ["foobar", "foobar"] + [tcp.middlewares.TCPMiddleware03] + [tcp.middlewares.TCPMiddleware03.inFlightConn] + amount = 42 + [tcp.serversTransports] + [tcp.serversTransports.TCPServersTransport0] + dialKeepAlive = "42s" + dialTimeout = "42s" + terminationDelay = "42s" + [tcp.serversTransports.TCPServersTransport0.proxyProtocol] + version = 42 + [tcp.serversTransports.TCPServersTransport0.tls] + serverName = "foobar" + insecureSkipVerify = true + rootCAs = ["foobar", "foobar"] + peerCertURI = "foobar" + + [[tcp.serversTransports.TCPServersTransport0.tls.certificates]] + certFile = "foobar" + keyFile = "foobar" + + [[tcp.serversTransports.TCPServersTransport0.tls.certificates]] + certFile = "foobar" + keyFile = "foobar" + [tcp.serversTransports.TCPServersTransport0.tls.spiffe] + ids = ["foobar", "foobar"] + trustDomain = "foobar" + [tcp.serversTransports.TCPServersTransport1] + dialKeepAlive = "42s" + dialTimeout = "42s" + terminationDelay = "42s" + [tcp.serversTransports.TCPServersTransport1.proxyProtocol] + version = 42 + [tcp.serversTransports.TCPServersTransport1.tls] + serverName = "foobar" + insecureSkipVerify = true + rootCAs = ["foobar", "foobar"] + peerCertURI = "foobar" + + [[tcp.serversTransports.TCPServersTransport1.tls.certificates]] + certFile = "foobar" + keyFile = "foobar" + + [[tcp.serversTransports.TCPServersTransport1.tls.certificates]] + certFile = "foobar" + keyFile = "foobar" + [tcp.serversTransports.TCPServersTransport1.tls.spiffe] + ids = ["foobar", "foobar"] + trustDomain = "foobar" + +[udp] + [udp.routers] + [udp.routers.UDPRouter0] + entryPoints = ["foobar", "foobar"] + service = "foobar" + [udp.routers.UDPRouter1] + entryPoints = ["foobar", "foobar"] + service = "foobar" + [udp.services] + [udp.services.UDPService01] + [udp.services.UDPService01.loadBalancer] + + [[udp.services.UDPService01.loadBalancer.servers]] + address = "foobar" + + [[udp.services.UDPService01.loadBalancer.servers]] + address = "foobar" + [udp.services.UDPService02] + [udp.services.UDPService02.weighted] + + [[udp.services.UDPService02.weighted.services]] + name = "foobar" + weight = 42 + + [[udp.services.UDPService02.weighted.services]] + name = "foobar" + weight = 42 + +[tls] + + [[tls.certificates]] + certFile = "foobar" + keyFile = "foobar" + stores = ["foobar", "foobar"] + + [[tls.certificates]] + certFile = "foobar" + keyFile = "foobar" + stores = ["foobar", "foobar"] + [tls.options] + [tls.options.Options0] + minVersion = "foobar" + maxVersion = "foobar" + cipherSuites = ["foobar", "foobar"] + curvePreferences = ["foobar", "foobar"] + sniStrict = true + alpnProtocols = ["foobar", "foobar"] + disableSessionTickets = true + preferServerCipherSuites = true + [tls.options.Options0.clientAuth] + caFiles = ["foobar", "foobar"] + clientAuthType = "foobar" + [tls.options.Options1] + minVersion = "foobar" + maxVersion = "foobar" + cipherSuites = ["foobar", "foobar"] + curvePreferences = ["foobar", "foobar"] + sniStrict = true + alpnProtocols = ["foobar", "foobar"] + disableSessionTickets = true + preferServerCipherSuites = true + [tls.options.Options1.clientAuth] + caFiles = ["foobar", "foobar"] + clientAuthType = "foobar" + [tls.stores] + [tls.stores.Store0] + [tls.stores.Store0.defaultCertificate] + certFile = "foobar" + keyFile = "foobar" + [tls.stores.Store0.defaultGeneratedCert] + resolver = "foobar" + [tls.stores.Store0.defaultGeneratedCert.domain] + main = "foobar" + sans = ["foobar", "foobar"] + [tls.stores.Store1] + [tls.stores.Store1.defaultCertificate] + certFile = "foobar" + keyFile = "foobar" + [tls.stores.Store1.defaultGeneratedCert] + resolver = "foobar" + [tls.stores.Store1.defaultGeneratedCert.domain] + main = "foobar" + sans = ["foobar", "foobar"] diff --git a/docs/content/reference/routing-configuration/other-providers/file.yaml b/docs/content/reference/routing-configuration/other-providers/file.yaml new file mode 100644 index 000000000..29822fddb --- /dev/null +++ b/docs/content/reference/routing-configuration/other-providers/file.yaml @@ -0,0 +1,698 @@ +## CODE GENERATED AUTOMATICALLY +## THIS FILE MUST NOT BE EDITED BY HAND +http: + routers: + Router0: + entryPoints: + - foobar + - foobar + middlewares: + - foobar + - foobar + service: foobar + rule: foobar + ruleSyntax: foobar + priority: 42 + tls: + options: foobar + certResolver: foobar + domains: + - main: foobar + sans: + - foobar + - foobar + - main: foobar + sans: + - foobar + - foobar + observability: + accessLogs: true + metrics: true + tracing: true + traceVerbosity: foobar + Router1: + entryPoints: + - foobar + - foobar + middlewares: + - foobar + - foobar + service: foobar + rule: foobar + ruleSyntax: foobar + priority: 42 + tls: + options: foobar + certResolver: foobar + domains: + - main: foobar + sans: + - foobar + - foobar + - main: foobar + sans: + - foobar + - foobar + observability: + accessLogs: true + metrics: true + tracing: true + traceVerbosity: foobar + services: + Service01: + failover: + service: foobar + fallback: foobar + healthCheck: {} + Service02: + loadBalancer: + sticky: + cookie: + name: foobar + secure: true + httpOnly: true + sameSite: foobar + maxAge: 42 + path: foobar + domain: foobar + servers: + - url: foobar + weight: 42 + preservePath: true + - url: foobar + weight: 42 + preservePath: true + strategy: foobar + healthCheck: + scheme: foobar + mode: foobar + path: foobar + method: foobar + status: 42 + port: 42 + interval: 42s + unhealthyInterval: 42s + timeout: 42s + hostname: foobar + followRedirects: true + headers: + name0: foobar + name1: foobar + passHostHeader: true + responseForwarding: + flushInterval: 42s + serversTransport: foobar + Service03: + mirroring: + service: foobar + mirrorBody: true + maxBodySize: 42 + mirrors: + - name: foobar + percent: 42 + - name: foobar + percent: 42 + healthCheck: {} + Service04: + weighted: + services: + - name: foobar + weight: 42 + - name: foobar + weight: 42 + sticky: + cookie: + name: foobar + secure: true + httpOnly: true + sameSite: foobar + maxAge: 42 + path: foobar + domain: foobar + healthCheck: {} + middlewares: + Middleware01: + addPrefix: + prefix: foobar + Middleware02: + basicAuth: + users: + - foobar + - foobar + usersFile: foobar + realm: foobar + removeHeader: true + headerField: foobar + Middleware03: + buffering: + maxRequestBodyBytes: 42 + memRequestBodyBytes: 42 + maxResponseBodyBytes: 42 + memResponseBodyBytes: 42 + retryExpression: foobar + Middleware04: + chain: + middlewares: + - foobar + - foobar + Middleware05: + circuitBreaker: + expression: foobar + checkPeriod: 42s + fallbackDuration: 42s + recoveryDuration: 42s + responseCode: 42 + Middleware06: + compress: + excludedContentTypes: + - foobar + - foobar + includedContentTypes: + - foobar + - foobar + minResponseBodyBytes: 42 + encodings: + - foobar + - foobar + defaultEncoding: foobar + Middleware07: + contentType: + autoDetect: true + Middleware08: + digestAuth: + users: + - foobar + - foobar + usersFile: foobar + removeHeader: true + realm: foobar + headerField: foobar + Middleware09: + errors: + status: + - foobar + - foobar + statusRewrites: + name0: 42 + name1: 42 + service: foobar + query: foobar + Middleware10: + forwardAuth: + address: foobar + tls: + ca: foobar + cert: foobar + key: foobar + insecureSkipVerify: true + caOptional: true + trustForwardHeader: true + authResponseHeaders: + - foobar + - foobar + authResponseHeadersRegex: foobar + authRequestHeaders: + - foobar + - foobar + addAuthCookiesToResponse: + - foobar + - foobar + headerField: foobar + forwardBody: true + maxBodySize: 42 + preserveLocationHeader: true + preserveRequestMethod: true + Middleware11: + grpcWeb: + allowOrigins: + - foobar + - foobar + Middleware12: + headers: + customRequestHeaders: + name0: foobar + name1: foobar + customResponseHeaders: + name0: foobar + name1: foobar + accessControlAllowCredentials: true + accessControlAllowHeaders: + - foobar + - foobar + accessControlAllowMethods: + - foobar + - foobar + accessControlAllowOriginList: + - foobar + - foobar + accessControlAllowOriginListRegex: + - foobar + - foobar + accessControlExposeHeaders: + - foobar + - foobar + accessControlMaxAge: 42 + addVaryHeader: true + allowedHosts: + - foobar + - foobar + hostsProxyHeaders: + - foobar + - foobar + sslProxyHeaders: + name0: foobar + name1: foobar + stsSeconds: 42 + stsIncludeSubdomains: true + stsPreload: true + forceSTSHeader: true + frameDeny: true + customFrameOptionsValue: foobar + contentTypeNosniff: true + browserXssFilter: true + customBrowserXSSValue: foobar + contentSecurityPolicy: foobar + contentSecurityPolicyReportOnly: foobar + publicKey: foobar + referrerPolicy: foobar + permissionsPolicy: foobar + isDevelopment: true + featurePolicy: foobar + sslRedirect: true + sslTemporaryRedirect: true + sslHost: foobar + sslForceHost: true + Middleware13: + ipAllowList: + sourceRange: + - foobar + - foobar + ipStrategy: + depth: 42 + excludedIPs: + - foobar + - foobar + ipv6Subnet: 42 + rejectStatusCode: 42 + Middleware14: + ipWhiteList: + sourceRange: + - foobar + - foobar + ipStrategy: + depth: 42 + excludedIPs: + - foobar + - foobar + ipv6Subnet: 42 + Middleware15: + inFlightReq: + amount: 42 + sourceCriterion: + ipStrategy: + depth: 42 + excludedIPs: + - foobar + - foobar + ipv6Subnet: 42 + requestHeaderName: foobar + requestHost: true + Middleware16: + passTLSClientCert: + pem: true + info: + notAfter: true + notBefore: true + sans: true + serialNumber: true + subject: + country: true + province: true + locality: true + organization: true + organizationalUnit: true + commonName: true + serialNumber: true + domainComponent: true + issuer: + country: true + province: true + locality: true + organization: true + commonName: true + serialNumber: true + domainComponent: true + Middleware17: + plugin: + PluginConf0: + name0: foobar + name1: foobar + PluginConf1: + name0: foobar + name1: foobar + Middleware18: + rateLimit: + average: 42 + period: 42s + burst: 42 + sourceCriterion: + ipStrategy: + depth: 42 + excludedIPs: + - foobar + - foobar + ipv6Subnet: 42 + requestHeaderName: foobar + requestHost: true + redis: + endpoints: + - foobar + - foobar + tls: + ca: foobar + cert: foobar + key: foobar + insecureSkipVerify: true + username: foobar + password: foobar + db: 42 + poolSize: 42 + minIdleConns: 42 + maxActiveConns: 42 + readTimeout: 42s + writeTimeout: 42s + dialTimeout: 42s + Middleware19: + redirectRegex: + regex: foobar + replacement: foobar + permanent: true + Middleware20: + redirectScheme: + scheme: foobar + port: foobar + permanent: true + Middleware21: + replacePath: + path: foobar + Middleware22: + replacePathRegex: + regex: foobar + replacement: foobar + Middleware23: + retry: + attempts: 42 + initialInterval: 42s + Middleware24: + stripPrefix: + prefixes: + - foobar + - foobar + forceSlash: true + Middleware25: + stripPrefixRegex: + regex: + - foobar + - foobar + serversTransports: + ServersTransport0: + serverName: foobar + insecureSkipVerify: true + rootCAs: + - foobar + - foobar + certificates: + - certFile: foobar + keyFile: foobar + - certFile: foobar + keyFile: foobar + maxIdleConnsPerHost: 42 + forwardingTimeouts: + dialTimeout: 42s + responseHeaderTimeout: 42s + idleConnTimeout: 42s + readIdleTimeout: 42s + pingTimeout: 42s + disableHTTP2: true + peerCertURI: foobar + spiffe: + ids: + - foobar + - foobar + trustDomain: foobar + ServersTransport1: + serverName: foobar + insecureSkipVerify: true + rootCAs: + - foobar + - foobar + certificates: + - certFile: foobar + keyFile: foobar + - certFile: foobar + keyFile: foobar + maxIdleConnsPerHost: 42 + forwardingTimeouts: + dialTimeout: 42s + responseHeaderTimeout: 42s + idleConnTimeout: 42s + readIdleTimeout: 42s + pingTimeout: 42s + disableHTTP2: true + peerCertURI: foobar + spiffe: + ids: + - foobar + - foobar + trustDomain: foobar +tcp: + routers: + TCPRouter0: + entryPoints: + - foobar + - foobar + middlewares: + - foobar + - foobar + service: foobar + rule: foobar + ruleSyntax: foobar + priority: 42 + tls: + passthrough: true + options: foobar + certResolver: foobar + domains: + - main: foobar + sans: + - foobar + - foobar + - main: foobar + sans: + - foobar + - foobar + TCPRouter1: + entryPoints: + - foobar + - foobar + middlewares: + - foobar + - foobar + service: foobar + rule: foobar + ruleSyntax: foobar + priority: 42 + tls: + passthrough: true + options: foobar + certResolver: foobar + domains: + - main: foobar + sans: + - foobar + - foobar + - main: foobar + sans: + - foobar + - foobar + services: + TCPService01: + loadBalancer: + servers: + - address: foobar + tls: true + - address: foobar + tls: true + serversTransport: foobar + proxyProtocol: + version: 42 + terminationDelay: 42 + TCPService02: + weighted: + services: + - name: foobar + weight: 42 + - name: foobar + weight: 42 + middlewares: + TCPMiddleware01: + ipAllowList: + sourceRange: + - foobar + - foobar + TCPMiddleware02: + ipWhiteList: + sourceRange: + - foobar + - foobar + TCPMiddleware03: + inFlightConn: + amount: 42 + serversTransports: + TCPServersTransport0: + dialKeepAlive: 42s + dialTimeout: 42s + proxyProtocol: + version: 42 + terminationDelay: 42s + tls: + serverName: foobar + insecureSkipVerify: true + rootCAs: + - foobar + - foobar + certificates: + - certFile: foobar + keyFile: foobar + - certFile: foobar + keyFile: foobar + peerCertURI: foobar + spiffe: + ids: + - foobar + - foobar + trustDomain: foobar + TCPServersTransport1: + dialKeepAlive: 42s + dialTimeout: 42s + proxyProtocol: + version: 42 + terminationDelay: 42s + tls: + serverName: foobar + insecureSkipVerify: true + rootCAs: + - foobar + - foobar + certificates: + - certFile: foobar + keyFile: foobar + - certFile: foobar + keyFile: foobar + peerCertURI: foobar + spiffe: + ids: + - foobar + - foobar + trustDomain: foobar +udp: + routers: + UDPRouter0: + entryPoints: + - foobar + - foobar + service: foobar + UDPRouter1: + entryPoints: + - foobar + - foobar + service: foobar + services: + UDPService01: + loadBalancer: + servers: + - address: foobar + - address: foobar + UDPService02: + weighted: + services: + - name: foobar + weight: 42 + - name: foobar + weight: 42 +tls: + certificates: + - certFile: foobar + keyFile: foobar + stores: + - foobar + - foobar + - certFile: foobar + keyFile: foobar + stores: + - foobar + - foobar + options: + Options0: + minVersion: foobar + maxVersion: foobar + cipherSuites: + - foobar + - foobar + curvePreferences: + - foobar + - foobar + clientAuth: + caFiles: + - foobar + - foobar + clientAuthType: foobar + sniStrict: true + alpnProtocols: + - foobar + - foobar + disableSessionTickets: true + preferServerCipherSuites: true + Options1: + minVersion: foobar + maxVersion: foobar + cipherSuites: + - foobar + - foobar + curvePreferences: + - foobar + - foobar + clientAuth: + caFiles: + - foobar + - foobar + clientAuthType: foobar + sniStrict: true + alpnProtocols: + - foobar + - foobar + disableSessionTickets: true + preferServerCipherSuites: true + stores: + Store0: + defaultCertificate: + certFile: foobar + keyFile: foobar + defaultGeneratedCert: + resolver: foobar + domain: + main: foobar + sans: + - foobar + - foobar + Store1: + defaultCertificate: + certFile: foobar + keyFile: foobar + defaultGeneratedCert: + resolver: foobar + domain: + main: foobar + sans: + - foobar + - foobar diff --git a/docs/content/reference/routing-configuration/other-providers/kv.md b/docs/content/reference/routing-configuration/other-providers/kv.md index 963d83cb0..8f11d50ba 100644 --- a/docs/content/reference/routing-configuration/other-providers/kv.md +++ b/docs/content/reference/routing-configuration/other-providers/kv.md @@ -5,387 +5,87 @@ description: "Read the technical documentation to learn the Traefik Routing Conf # Traefik & KV Stores -## Routing Configuration +## Configuration Options !!! info "Keys" Keys are case-insensitive. -### Routers +### HTTP + +#### Routers !!! warning "The character `@` is not authorized in the router name ``." -??? info "`traefik/http/routers//rule`" +| Key (Path) | Description | Value | +|--------------------------------------|--------------------------------------|----------------------------| +| `traefik/http/routers//rule` | See [rule](../http/routing/rules-and-priority.md#rules) for more information. | ```Host(`example.com`)``` | +| `traefik/http/routers//ruleSyntax` | See [rule](../http/routing/rules-and-priority.md#rulesyntax) for more information.
RuleSyntax option is deprecated and will be removed in the next major version.
Please do not use this field and rewrite the router rules to use the v3 syntax. | `v3` | +| `traefik/http/routers//entrypoints/0` | See [entry points](../../install-configuration/entrypoints.md) for more information. | `web` | +| `traefik/http/routers//entrypoints/1` | See [entry points](../../install-configuration/entrypoints.md) for more information. | `websecure` | +| `traefik/http/routers//middlewares/0` | See [middlewares overview](../http/middlewares/overview.md) for more information. | `auth` | +| `traefik/http/routers//middlewares/1` | | `prefix` | +| `traefik/http/routers//service` | See [service](../http/load-balancing/service.md) for more information. | `myservice` | +| `traefik/http/routers//tls` | See [tls](../http/tls/overview.md) for more information. | `true` | +| `traefik/http/routers//tls/certresolver` | See [certResolver](../../install-configuration/tls/certificate-resolvers/overview.md) for more information. | `myresolver` | +| `traefik/http/routers//tls/domains/0/main` | See [domains](../../install-configuration/tls/certificate-resolvers/acme.md#domain-definition) for more information. | `example.org` | +| `traefik/http/routers//tls/domains/0/sans/0` | See [domains](../../install-configuration/tls/certificate-resolvers/acme.md#domain-definition) for more information. | `test.example.org` | +| `traefik/http/routers//tls/domains/0/sans/1` | See [domains](../../install-configuration/tls/certificate-resolvers/acme.md#domain-definition) for more information. | `dev.example.org` | +| `traefik/http/routers//tls/options` | See [TLS Options](../http/tls/tls-options.md) for more information. | `foobar` | +| `traefik/http/routers//observability/accesslogs` | The accessLogs option controls whether the router will produce access-logs. | `true` | +| `traefik/http/routers//observability/metrics` | The metrics option controls whether the router will produce metrics. | `true` | +| `traefik/http/routers//observability/tracing` | The tracing option controls whether the router will produce traces. | `true` | +| `traefik/http/routers//priority` | See [priority](../http/routing/rules-and-priority.md#priority-calculation) for more information. | `42` | - See [rule](../http/router/rules-and-priority.md#rules) for more information. - - | Key (Path) | Value | - |--------------------------------------|----------------------------| - | `traefik/http/routers/myrouter/rule` | ```Host(`example.com`)``` | - -??? info "`traefik/http/routers//ruleSyntax`" - - !!! warning - - RuleSyntax option is deprecated and will be removed in the next major version. - Please do not use this field and rewrite the router rules to use the v3 syntax. - - See [rule](../http/router/rules-and-priority.md#rulesyntax) for more information. - - | Key (Path) | Value | - |--------------------------------------|----------------------------| - | `traefik/http/routers/myrouter/ruleSyntax` | `v3` | - -??? info "`traefik/http/routers//entrypoints`" - - See [entry points](../../install-configuration/entrypoints.md) for more information. - - | Key (Path) | Value | - |-----------------------------------------------|-------------| - | `traefik/http/routers/myrouter/entrypoints/0` | `web` | - | `traefik/http/routers/myrouter/entrypoints/1` | `websecure` | - -??? info "`traefik/http/routers//middlewares`" - - See [middlewares overview](../http/middlewares/overview.md) for more information. - - | Key (Path) | Value | - |-----------------------------------------------|-------------| - | `traefik/http/routers/myrouter/middlewares/0` | `auth` | - | `traefik/http/routers/myrouter/middlewares/1` | `prefix` | - | `traefik/http/routers/myrouter/middlewares/2` | `cb` | - -??? info "`traefik/http/routers//service`" - - See [service](../http/load-balancing/service.md) for more information. - - | Key (Path) | Value | - |-----------------------------------------|-------------| - | `traefik/http/routers/myrouter/service` | `myservice` | - -??? info "`traefik/http/routers//tls`" - - See [tls](../http/tls/overview.md) for more information. - - | Key (Path) | Value | - |-------------------------------------|--------| - | `traefik/http/routers/myrouter/tls` | `true` | - -??? info "`traefik/http/routers//tls/certresolver`" - - See [certResolver](../../install-configuration/tls/certificate-resolvers/overview.md) for more information. - - | Key (Path) | Value | - |--------------------------------------------------|--------------| - | `traefik/http/routers/myrouter/tls/certresolver` | `myresolver` | - -??? info "`traefik/http/routers//tls/domains//main`" - - See [domains](../../install-configuration/tls/certificate-resolvers/acme.md#domain-definition) for more information. - - | Key (Path) | Value | - |----------------------------------------------------|---------------| - | `traefik/http/routers/myrouter/tls/domains/0/main` | `example.org` | - -??? info "`traefik/http/routers//tls/domains//sans/`" - - See [domains](../../install-configuration/tls/certificate-resolvers/acme.md#domain-definition) for more information. - - | Key (Path) | Value | - |------------------------------------------------------|--------------------| - | `traefik/http/routers/myrouter/tls/domains/0/sans/0` | `test.example.org` | - | `traefik/http/routers/myrouter/tls/domains/0/sans/1` | `dev.example.org` | - -??? info "`traefik/http/routers//tls/options`" - - See [TLS](../http/tls/overview.md) for more information. - - | Key (Path) | Value | - |---------------------------------------------|----------| - | `traefik/http/routers/myrouter/tls/options` | `foobar` | - -??? info "`traefik/http/routers//observability/accesslogs`" - - The accessLogs option controls whether the router will produce access-logs. - - | Key (Path) | Value | - |----------------------------------------------------------|--------| - | `traefik/http/routers/myrouter/observability/accesslogs` | `true` | - -??? info "`traefik/http/routers//observability/metrics`" - - The metrics option controls whether the router will produce metrics. - - | Key (Path) | Value | - |-------------------------------------------------------|--------| - | `traefik/http/routers/myrouter/observability/metrics` | `true` | - -??? info "`traefik/http/routers//observability/tracing`" - - The tracing option controls whether the router will produce traces. - - | Key (Path) | Value | - |-------------------------------------------------------|--------| - | `traefik/http/routers/myrouter/observability/tracing` | `true` | - -??? info "`traefik/http/routers//priority`" - - See [domains](../../install-configuration/tls/certificate-resolvers/acme.md#domain-definition) for more information. - - | Key (Path) | Value | - |------------------------------------------|-------| - | `traefik/http/routers/myrouter/priority` | `42` | - -### Services +#### Services !!! warning "The character `@` is not authorized in the service name ``." -??? info "`traefik/http/services//loadbalancer/servers//url`" - - See [servers](../http/load-balancing/service.md#servers) for more information. - - | Key (Path) | Value | - |-----------------------------------------------------------------|-----------------------------------------| - | `traefik/http/services/myservice/loadbalancer/servers/0/url` | `http://:/` | - -??? info "`traefik/http/services//loadbalancer/servers//preservePath`" - - See [servers](../http/load-balancing/service.md#servers) for more information. - - | Key (Path) | Value | - |-----------------------------------------------------------------|-----------------------------------------| - | `traefik/http/services/myservice/loadbalancer/servers/0/preservePath` | `true` | - -??? info "`traefik/http/services//loadbalancer/servers//weight`" - - See [servers](../http/load-balancing/service.md#servers) for more information. - - | Key (Path) | Value | - |-----------------------------------------------------------------|-----------------------------------------| - | `traefik/http/services/myservice/loadbalancer/servers/0/weight` | `1` | - -??? info "`traefik/http/services//loadbalancer/serverstransport`" - - Allows to reference a ServersTransport resource that is defined either with the File provider or the Kubernetes CRD one. - See [serverstransport](../http/load-balancing/serverstransport.md) for more information. - - | Key (Path) | Value | - |-----------------------------------------------------------------|---------------| - | `traefik/http/services/myservice/loadbalancer/serverstransport` | `foobar@file` | - -??? info "`traefik/http/services//loadbalancer/passhostheader`" - - | Key (Path) | Value | - |-----------------------------------------------------------------|--------| - | `traefik/http/services/myservice/loadbalancer/passhostheader` | `true` | - -??? info "`traefik/http/services//loadbalancer/healthcheck/headers/`" - - See [health check](../http/load-balancing/service.md#health-check) for more information. - - | Key (Path) | Value | - |--------------------------------------------------------------------------|----------| - | `traefik/http/services/myservice/loadbalancer/healthcheck/headers/X-Foo` | `foobar` | - -??? info "`traefik/http/services//loadbalancer/healthcheck/hostname`" - - See [health check](../http/load-balancing/service.md#health-check) for more information. - - | Key (Path) | Value | - |---------------------------------------------------------------------|---------------| - | `traefik/http/services/myservice/loadbalancer/healthcheck/hostname` | `example.org` | - -??? info "`traefik/http/services//loadbalancer/healthcheck/interval`" - - See [health check](../http/load-balancing/service.md#health-check) for more information. - - | Key (Path) | Value | - |---------------------------------------------------------------------|-------| - | `traefik/http/services/myservice/loadbalancer/healthcheck/interval` | `10` | - -??? info "`traefik/http/services//loadbalancer/healthcheck/path`" - - See [health check](../http/load-balancing/service.md#health-check) for more information. - - | Key (Path) | Value | - |-----------------------------------------------------------------|--------| - | `traefik/http/services/myservice/loadbalancer/healthcheck/path` | `/foo` | - -??? info "`traefik/http/services//loadbalancer/healthcheck/method`" - - See [health check](../http/load-balancing/service.md#health-check) for more information. - - | Key (Path) | Value | - |-------------------------------------------------------------------|----------| - | `traefik/http/services/myservice/loadbalancer/healthcheck/method` | `foobar` | - -??? info "`traefik/http/services//loadbalancer/healthcheck/status`" - - See [health check](../http/load-balancing/service.md#health-check) for more information. - - | Key (Path) | Value | - |-------------------------------------------------------------------|-------| - | `traefik/http/services/myservice/loadbalancer/healthcheck/status` | `42` | - -??? info "`traefik/http/services//loadbalancer/healthcheck/port`" - - See [health check](../http/load-balancing/service.md#health-check) for more information. - - | Key (Path) | Value | - |-----------------------------------------------------------------|-------| - | `traefik/http/services/myservice/loadbalancer/healthcheck/port` | `42` | - -??? info "`traefik/http/services//loadbalancer/healthcheck/scheme`" - - See [health check](../http/load-balancing/service.md#health-check) for more information. - - | Key (Path) | Value | - |-------------------------------------------------------------------|--------| - | `traefik/http/services/myservice/loadbalancer/healthcheck/scheme` | `http` | - -??? info "`traefik/http/services//loadbalancer/healthcheck/timeout`" - - See [health check](../http/load-balancing/service.md#health-check) for more information. - - | Key (Path) | Value | - |--------------------------------------------------------------------|-------| - | `traefik/http/services/myservice/loadbalancer/healthcheck/timeout` | `10` | - -??? info "`traefik/http/services//loadbalancer/sticky`" - - | Key (Path) | Value | - |-------------------------------------------------------|--------| - | `traefik/http/services/myservice/loadbalancer/sticky` | `true` | - -??? info "`traefik/http/services//loadbalancer/sticky/cookie/httponly`" - - | Key (Path) | Value | - |-----------------------------------------------------------------------|--------| - | `traefik/http/services/myservice/loadbalancer/sticky/cookie/httponly` | `true` | - -??? info "`traefik/http/services//loadbalancer/sticky/cookie/name`" - - | Key (Path) | Value | - |-------------------------------------------------------------------|----------| - | `traefik/http/services/myservice/loadbalancer/sticky/cookie/name` | `foobar` | - -??? info "`traefik/http/services//loadbalancer/sticky/cookie/path`" - - | Key (Path) | Value | - |-------------------------------------------------------------------|-----------| - | `traefik/http/services/myservice/loadbalancer/sticky/cookie/path` | `/foobar` | - -??? info "`traefik/http/services//loadbalancer/sticky/cookie/secure`" - - | Key (Path) | Value | - |---------------------------------------------------------------------|--------| - | `traefik/http/services/myservice/loadbalancer/sticky/cookie/secure` | `true` | - -??? info "`traefik/http/services//loadbalancer/sticky/cookie/samesite`" - - | Key (Path) | Value | - |-----------------------------------------------------------------------|--------| - | `traefik/http/services/myservice/loadbalancer/sticky/cookie/samesite` | `none` | - -??? info "`traefik/http/services//loadbalancer/sticky/cookie/maxage`" - - | Key (Path) | Value | - |---------------------------------------------------------------------|-------| - | `traefik/http/services/myservice/loadbalancer/sticky/cookie/maxage` | `42` | - -??? info "`traefik/http/services//loadbalancer/responseforwarding/flushinterval`" - - | Key (Path) | Value | - |---------------------------------------------------------------------------------|-------| - | `traefik/http/services/myservice/loadbalancer/responseforwarding/flushinterval` | `10` | - -??? info "`traefik/http/services//mirroring/service`" - - | Key (Path) | Value | - |----------------------------------------------------------|----------| - | `traefik/http/services//mirroring/service` | `foobar` | - -??? info "`traefik/http/services//mirroring/mirrors//name`" - - | Key (Path) | Value | - |-------------------------------------------------------------------|----------| - | `traefik/http/services//mirroring/mirrors//name` | `foobar` | - -??? info "`traefik/http/services//mirroring/mirrors//percent`" - - | Key (Path) | Value | - |----------------------------------------------------------------------|-------| - | `traefik/http/services//mirroring/mirrors//percent` | `42` | - -??? info "`traefik/http/services//weighted/services//name`" - - | Key (Path) | Value | - |-------------------------------------------------------------------|----------| - | `traefik/http/services//weighted/services//name` | `foobar` | - -??? info "`traefik/http/services//weighted/services//weight`" - - | Key (Path) | Value | - |---------------------------------------------------------------------|-------| - | `traefik/http/services//weighted/services//weight` | `42` | - -??? info "`traefik/http/services//weighted/sticky/cookie/name`" - - | Key (Path) | Value | - |--------------------------------------------------------------------|----------| - | `traefik/http/services//weighted/sticky/cookie/name` | `foobar` | - -??? info "`traefik/http/services//weighted/sticky/cookie/secure`" - - | Key (Path) | Value | - |----------------------------------------------------------------------|--------| - | `traefik/http/services//weighted/sticky/cookie/secure` | `true` | - -??? info "`traefik/http/services//weighted/sticky/cookie/samesite`" - - | Key (Path) | Value | - |------------------------------------------------------------------------|--------| - | `traefik/http/services//weighted/sticky/cookie/samesite` | `none` | - -??? info "`traefik/http/services//weighted/sticky/cookie/httpOnly`" - - | Key (Path) | Value | - |------------------------------------------------------------------------|--------| - | `traefik/http/services//weighted/sticky/cookie/httpOnly` | `true` | - -??? info "`traefik/http/services//weighted/sticky/cookie/maxage`" - - | Key (Path) | Value | - |----------------------------------------------------------------------|-------| - | `traefik/http/services//weighted/sticky/cookie/maxage` | `42` | - -??? info "`traefik/http/services//failover/fallback`" - - See [Failover](../http/load-balancing/service.md#failover) for more information - - | Key (Path) | Value | - |----------------------------------------------------------------------|-------| - | `traefik/http/services//failover/fallback` | `backup` | - -??? info "`traefik/http/services//failover/healthcheck`" - - See [Failover](../http/load-balancing/service.md#failover) for more information - - | Key (Path) | Value | - |----------------------------------------------------------------------|-------| - | `traefik/http/services//failover/healthcheck` | `{}` | - -??? info "`traefik/http/services//failover/service`" - - See [Failover](../http/load-balancing/service.md#failover) for more information - - | Key (Path) | Value | - |----------------------------------------------------------------------|-------| - | `traefik/http/services//failover/service` | `main` | - -### Middleware - -More information about available middlewares in the dedicated [middlewares section](../http/middlewares/overview.md). +| Key (Path) | Description | Value | +|-----------------------------------------------------------------|-----------------------------------------------------------------|-----------------------------------------| +| `traefik/http/services/myservice/loadbalancer/servers/0/url` | See [servers](../http/load-balancing/service.md#servers) for more information. | `http://:/` | +| `traefik/http/services/myservice/loadbalancer/servers/0/preservePath` | See [servers](../http/load-balancing/service.md#servers) for more information. | `true` | +| `traefik/http/services/myservice/loadbalancer/servers/0/weight` | See [servers](../http/load-balancing/service.md#servers) for more information. | `1` | +| `traefik/http/services/myservice/loadbalancer/serverstransport` | Allows to reference a ServersTransport resource that is defined either with the File provider or the Kubernetes CRD one.
See [serverstransport](../http/load-balancing/serverstransport.md) for more information. | `foobar@file` | +| `traefik/http/services/myservice/loadbalancer/passhostheader` | See [Service](../http/load-balancing/service.md) for more information. | `true` | +| `traefik/http/services/myservice/loadbalancer/healthcheck/headers/X-Foo` | See [health check](../http/load-balancing/service.md#health-check) for more information. | `foobar` | +| `traefik/http/services/myservice/loadbalancer/healthcheck/hostname` | See [health check](../http/load-balancing/service.md#health-check) for more information. | `example.org` | +| `traefik/http/services/myservice/loadbalancer/healthcheck/interval` | See [health check](../http/load-balancing/service.md#health-check) for more information. | `10` | +| `traefik/http/services/myservice/loadbalancer/healthcheck/path` | See [health check](../http/load-balancing/service.md#health-check) for more information. | `/foo` | +| `traefik/http/services/myservice/loadbalancer/healthcheck/method` | See [health check](../http/load-balancing/service.md#health-check) for more information. | `foobar` | +| `traefik/http/services/myservice/loadbalancer/healthcheck/status` | See [health check](../http/load-balancing/service.md#health-check) for more information. | `42` | +| `traefik/http/services/myservice/loadbalancer/healthcheck/port` | See [health check](../http/load-balancing/service.md#health-check) for more information. | `42` | +| `traefik/http/services/myservice/loadbalancer/healthcheck/scheme` | See [health check](../http/load-balancing/service.md#health-check) for more information. | `http` | +| `traefik/http/services/myservice/loadbalancer/healthcheck/timeout` | See [health check](../http/load-balancing/service.md#health-check) for more information. | `10` | +| `traefik/http/services/myservice/loadbalancer/sticky` | See [Service](../http/load-balancing/service.md#sticky-sessions) for more information. | `true` | +| `traefik/http/services/myservice/loadbalancer/sticky/cookie/httponly` | See [Service](../http/load-balancing/service.md#sticky-sessions) for more information. | `true` | +| `traefik/http/services/myservice/loadbalancer/sticky/cookie/name` | See [Service](../http/load-balancing/service.md#sticky-sessions) for more information. | `foobar` | +| `traefik/http/services/myservice/loadbalancer/sticky/cookie/path` | See [Service](../http/load-balancing/service.md#sticky-sessions) for more information. | `/foobar` | +| `traefik/http/services/myservice/loadbalancer/sticky/cookie/secure` | See [Service](../http/load-balancing/service.md#sticky-sessions) for more information. | `true` | +| `traefik/http/services/myservice/loadbalancer/sticky/cookie/samesite` | See [Service](../http/load-balancing/service.md#sticky-sessions) for more information. | `none` | +| `traefik/http/services/myservice/loadbalancer/sticky/cookie/maxage` | See [Service](../http/load-balancing/service.md#sticky-sessions) for more information. | `42` | +| `traefik/http/services/myservice/loadbalancer/responseforwarding/flushinterval` | See [Service](../http/load-balancing/service.md) for more information. | `10` | +| `traefik/http/services//mirroring/service` | See [Service](../http/load-balancing/service.md#mirroring) for more information. | `foobar` | +| `traefik/http/services//mirroring/mirrors//name` | See [Service](../http/load-balancing/service.md#mirroring) for more information. | `foobar` | +| `traefik/http/services//mirroring/mirrors//percent` | See [Service](../http/load-balancing/service.md#mirroring)for more information. | `42` | +| `traefik/http/services//weighted/services//name` | See [Service](../http/load-balancing/service.md#weighted-round-robin-wrr) for more information. | `foobar` | +| `traefik/http/services//weighted/services//weight` | See [Service](../http/load-balancing/service.md#weighted-round-robin-wrr) for more information. | `42` | +| `traefik/http/services//weighted/sticky/cookie/name` | See [Service](../http/load-balancing/service.md#weighted-round-robin-wrr) for more information. | `foobar` | +| `traefik/http/services//weighted/sticky/cookie/secure` | See [Service](../http/load-balancing/service.md#weighted-round-robin-wrr) for more information. | `true` | +| `traefik/http/services//weighted/sticky/cookie/samesite` | See [Service](../http/load-balancing/service.md#weighted-round-robin-wrr) for more information. | `none` | +| `traefik/http/services//weighted/sticky/cookie/httpOnly` | See [Service](../http/load-balancing/service.md#weighted-round-robin-wrr) for more information. | `true` | +| `traefik/http/services//weighted/sticky/cookie/maxage` | See [Service](../http/load-balancing/service.md#weighted-round-robin-wrr) for more information. | `42` | +| `traefik/http/services//failover/fallback` | See [Failover](../http/load-balancing/service.md#failover) for more information. | `backup` | +| `traefik/http/services//failover/healthcheck` | See [Failover](../http/load-balancing/service.md#failover) for more information. | `{}` | +| `traefik/http/services//failover/service` | See [Failover](../http/load-balancing/service.md#failover) for more information. | `main` | + +#### Middleware + +##### Configuration Options + +| Key (Path) | Description | Value | +|-----------------------------------------------------------------|-----------------------------------------------------------------|-----------------------------------------| +| `traefik/http/middlewares/mymiddleware/middleware_type/middleware_option` | With `middleware_type` the type of middleware (ex: `forwardAuth`, `headers`, etc)
and `middleware_option` the middleware option to set (ex for the middleware `addPrefix`: `prefix`).
More information about available middlewares in the dedicated [middlewares section](../http/middlewares/overview.md). | `foobar` | !!! warning "The character `@` is not authorized in the middleware name." @@ -393,142 +93,68 @@ More information about available middlewares in the dedicated [middlewares secti If you declare multiple middleware with the same name but with different parameters, the middleware fails to be declared. +##### Configuration Example + +```bash +# Declaring a middleware +traefik/http/middlewares/myAddPrefix/addPrefix/prefix=/foobar +# Referencing a middleware +traefik/http/routers//middlewares/0=myAddPrefix +``` + +#### ServerTransport + +##### Configuration Options + +| Key (Path) | Description | Value | +|-----------------------------------------------------------------|-----------------------------------------------------------------|-----------------------------------------| +| `traefik/http/serversTransports//st_option` | With `st_option` the ServerTransport option to set (ex `maxIdleConnsPerHost`).
More information about available options in the dedicated [ServerTransport section](../http/load-balancing/serverstransport.md). | ServerTransport Options | + +##### Configuration Example + +```bash +# Declaring a ServerTransport +traefik/http/serversTransports/myServerTransport/maxIdleConnsPerHost=-1 +traefik/http/serversTransports/myServerTransport/certificates/0/certFile=mypath/cert.pem +traefik/http/serversTransports/myServerTransport/certificates/0/keyFile=mypath/key.pem +# Referencing a middleware +traefik/http/services/myService/serversTransports/0=myServerTransport +``` + ### TCP You can declare TCP Routers and/or Services using KV. -#### TCP Routers +#### Routers -??? info "`traefik/tcp/routers//entrypoints`" +| Key (Path) | Description | Value | +|-------------------------------------------------|-------------------------------------------------|-------| +| `traefik/tcp/routers/mytcprouter/entrypoints/0` | See [entry points](../../install-configuration/entrypoints.md) for more information. | `ep1` | +| `traefik/tcp/routers/mytcprouter/entrypoints/1` | See [entry points](../../install-configuration/entrypoints.md) for more information. | `ep2` | +| `traefik/tcp/routers/my-router/rule` | See [entry points](../../install-configuration/entrypoints.md) for more information. | ```HostSNI(`example.com`)``` | +| `traefik/tcp/routers/mytcprouter/service` | See [service](../tcp/service.md) for more information. | `myservice` | +| `traefik/tcp/routers/mytcprouter/tls` | See [TLS](../tcp/tls.md) for more information. | `true` | +| `traefik/tcp/routers/mytcprouter/tls/certresolver` | See [certResolver](../tcp/tls.md#configuration-options) for more information. | `myresolver` | +| `traefik/tcp/routers/mytcprouter/tls/domains/0/main` | See [TLS](../tcp/tls.md) for more information. | `example.org` | +| `traefik/tcp/routers/mytcprouter/tls/domains/0/sans/0` | See [TLS](../tcp/tls.md) for more information. | `test.example.org` | +| `traefik/tcp/routers/mytcprouter/tls/domains/0/sans/1` | See [TLS](../tcp/tls.md) for more information. | `dev.example.org` | +| `traefik/tcp/routers/mytcprouter/tls/options` | See [TLS](../tcp/tls.md) for more information. | `foobar` | +| `traefik/tcp/routers/mytcprouter/tls/passthrough` | See [TLS](../tcp/tls.md) for more information. | `true` | +| `traefik/tcp/routers/mytcprouter/priority` | See [priority](../tcp/routing/rules-and-priority.md#priority-calculation) for more information. | `42` | - See [entry points](../../install-configuration/entrypoints.md) for more information. +#### Services - | Key (Path) | Value | - |-------------------------------------------------|-------| - | `traefik/tcp/routers/mytcprouter/entrypoints/0` | `ep1` | - | `traefik/tcp/routers/mytcprouter/entrypoints/1` | `ep2` | - -??? info "`traefik/tcp/routers//rule`" +| Key (Path) | Description | Value | +|--------------------------------------------------------------------|--------------------------------------------------------------------|------------------| +| `traefik/tcp/services/mytcpservice/loadbalancer/servers/0/address` | See [servers](../tcp/service.md#servers-load-balancer) for more information. | `xx.xx.xx.xx:xx` | +| `traefik/tcp/services/mytcpservice/loadbalancer/servers/0/tls` | See [servers](../tcp/service.md#servers-load-balancer) for more information. | `true` | +| `traefik/tcp/services/myservice/loadbalancer/serverstransport` | Allows to reference a ServersTransport resource that is defined either with the File provider or the Kubernetes CRD one.
See [serverstransport](../tcp/serverstransport.md) for more information. | `foobar@file` | +| `traefik/tcp/services//weighted/services/0/name` | See [Service](../tcp/service.md#weighted-round-robin) for more information. | `foobar` | +| `traefik/tcp/services//weighted/services/0/weight` | See [Service](../tcp/service.md#weighted-round-robin) for more information. | `42` | - See [entry points](../../install-configuration/entrypoints.md) for more information. +#### Middleware - | Key (Path) | Value | - |--------------------------------------|------------------------------| - | `traefik/tcp/routers/my-router/rule` | ```HostSNI(`example.com`)``` | - -??? info "`traefik/tcp/routers//service`" - - See [service](../tcp/service.md) for more information. - - | Key (Path) | Value | - |-------------------------------------------|-------------| - | `traefik/tcp/routers/mytcprouter/service` | `myservice` | - -??? info "`traefik/tcp/routers//tls`" - - See [TLS](../tcp/tls.md) for more information. - - | Key (Path) | Value | - |---------------------------------------|--------| - | `traefik/tcp/routers/mytcprouter/tls` | `true` | - -??? info "`traefik/tcp/routers//tls/certresolver`" - - See [certResolver](../tcp/tls.md#configuration-options) for more information. - - | Key (Path) | Value | - |----------------------------------------------------|--------------| - | `traefik/tcp/routers/mytcprouter/tls/certresolver` | `myresolver` | - -??? info "`traefik/tcp/routers//tls/domains//main`" - - See [TLS](../tcp/tls.md) for more information. - - | Key (Path) | Value | - |------------------------------------------------------|---------------| - | `traefik/tcp/routers/mytcprouter/tls/domains/0/main` | `example.org` | - -??? info "`traefik/tcp/routers//tls/domains//sans`" - - See [TLS](../tcp/tls.md) for more information. - - | Key (Path) | Value | - |--------------------------------------------------------|--------------------| - | `traefik/tcp/routers/mytcprouter/tls/domains/0/sans/0` | `test.example.org` | - | `traefik/tcp/routers/mytcprouter/tls/domains/0/sans/1` | `dev.example.org` | - -??? info "`traefik/tcp/routers//tls/options`" - - See [TLS](../tcp/tls.md) for more information. - - | Key (Path) | Value | - |-----------------------------------------------|----------| - | `traefik/tcp/routers/mytcprouter/tls/options` | `foobar` | - -??? info "`traefik/tcp/routers//tls/passthrough`" - - See [TLS](../tcp/tls.md) for more information. - - | Key (Path) | Value | - |---------------------------------------------------|--------| - | `traefik/tcp/routers/mytcprouter/tls/passthrough` | `true` | - -??? info "`traefik/tcp/routers//priority`" - - See [priority](../tcp/router/rules-and-priority.md#priority) for more information. - - | Key (Path) | Value | - |------------------------------------------|-------| - | `traefik/tcp/routers/mytcprouter/priority` | `42` | - -#### TCP Services - -??? info "`traefik/tcp/services//loadbalancer/servers//address`" - - See [servers](../tcp/service.md#servers-load-balancer) for more information. - - | Key (Path) | Value | - |--------------------------------------------------------------------|------------------| - | `traefik/tcp/services/mytcpservice/loadbalancer/servers/0/address` | `xx.xx.xx.xx:xx` | - -??? info "`traefik/tcp/services//loadbalancer/servers//tls`" - - See [servers](../tcp/service.md#servers-load-balancer) for more information. - - | Key (Path) | Value | - |--------------------------------------------------------------------|------------------| - | `traefik/tcp/services/mytcpservice/loadbalancer/servers/0/tls` | `true` | - -??? info "`traefik/tcp/services//loadbalancer/proxyprotocol/version`" - - See [PROXY protocol](../tcp/service.md#proxy-protocol) for more information. - - | Key (Path) | Value | - |------------------------------------------------------------------------|-------| - | `traefik/tcp/services/mytcpservice/loadbalancer/proxyprotocol/version` | `1` | - -??? info "`traefik/tcp/services//loadbalancer/serverstransport`" - - Allows to reference a ServersTransport resource that is defined either with the File provider or the Kubernetes CRD one. - See [serverstransport](../tcp/serverstransport.md) for more information. - - | Key (Path) | Value | - |-----------------------------------------------------------------|---------------| - | `traefik/tcp/services/myservice/loadbalancer/serverstransport` | `foobar@file` | - -??? info "`traefik/tcp/services//weighted/services//name`" - - | Key (Path) | Value | - |---------------------------------------------------------------------|----------| - | `traefik/tcp/services//weighted/services/0/name` | `foobar` | - -??? info "`traefik/tcp/services//weighted/services//weight`" - - | Key (Path) | Value | - |------------------------------------------------------------------|-------| - | `traefik/tcp/services//weighted/services/0/weight` | `42` | - -#### TCP Middleware +##### Configuration Options You can declare pieces of middleware using tags starting with `traefik/tcp/middlewares/{name-of-your-choice}.`, followed by the middleware type/options. @@ -536,80 +162,83 @@ For example, to declare a middleware [`InFlightConn`](../tcp/middlewares/infligh More information about available middlewares in the dedicated [middlewares section](../tcp/middlewares/overview.md). -??? example "Declaring and Referencing a Middleware" - - ```bash - # ... - # Declaring a middleware - traefik/tcp/middlewares/test-inflightconn/amount=10 - # Referencing a middleware - traefik/tcp/routers.my-service/middlewares=test-inflightconn - ``` +| Key (Path) | Description | Value | +|-----------------------------------------------------------------|-----------------------------------------------------------------|-----------------------------------------| +| `traefik/tcp/middlewares/mymiddleware/middleware_type/middleware_option` | With `middleware_type` the type of middleware (ex: `inflightconn`)
and `middleware_option` the middleware option to set (ex for the middleware `inflightconn`: `amount`).
More information about available middlewares in the dedicated [middlewares section](../tcp/middlewares/overview.md). | `foobar` | !!! warning "Conflicts in Declaration" If you declare multiple middleware with the same name but with different parameters, the middleware fails to be declared. +##### Configuration Example + +```bash +# Declaring a middleware +traefik/tcp/middlewares/test-inflightconn/amount=10 +# Referencing a middleware +traefik/tcp/routers//middlewares/0=test-inflightconn +``` + +#### ServerTransport + +##### Configuration Options + +| Key (Path) | Description | Value | +|-----------------------------------------------------------------|-----------------------------------------------------------------|-----------------------------------------| +| `traefik/tcp/serversTransports//st_option` | With `st_option` the ServerTransport option to set (ex `maxIdleConnsPerHost`).
More information about available options in the dedicated [ServerTransport section](../tcp/serverstransport.md). | ServerTransport Options | + +##### Configuration Example + +```bash +# Declaring a ServerTransport +traefik/tcp/serversTransports/myServerTransport/maxIdleConnsPerHost=-1 +# Referencing a middleware +traefik/tcp/services/myService/serversTransports/0=myServerTransport +``` + ### UDP You can declare UDP Routers and/or Services using KV. -#### UDP Routers +#### Routers -??? info "`traefik/udp/routers//entrypoints/`" +| Key (Path) | Description | Value | +|------------------------------------------------------------------|------------------------------------------------------------------|-------| +| `traefik/udp/routers/myudprouter/entrypoints/0` | See [UDP Router](../udp/routing/rules-priority.md#entrypoints) for more information. | `foobar` | +| `traefik/udp/routers/myudprouter/service` | See [UDP Router](../udp/routing/rules-priority.md#configuration-example) for more information. | `foobar` | - | Key (Path) | Value | - |------------------------------------------------------------------|-------| - | `traefik/udp/routers/myudprouter/entrypoints/0` | `foobar` | +#### Services -??? info "`traefik/udp/routers//service`" - - | Key (Path) | Value | - |------------------------------------------------------------------|-------| - | `traefik/udp/routers/myudprouter/service` | `foobar` | - -#### UDP Services - -??? info "`traefik/udp/services/loadBalancer/servers//address`" - - | Key (Path) | Value | - |------------------------------------------------------------------|-------| - | `traefik/udp/services/loadBalancer/servers//address` | `foobar` | - -??? info "`traefik/udp/services/weighted/services//name`" - - | Key (Path) | Value | - |------------------------------------------------------------------|-------| - | `traefik/udp/services/weighted/services/0/name` | `foobar` | - -??? info "`traefik/udp/services/weighted/services//name`" - - | Key (Path) | Value | - |------------------------------------------------------------------|-------| - | `traefik/udp/services/weighted/servers/0/weight` | `42` | +| Key (Path) | Description | Value | +|------------------------------------------------------------------|------------------------------------------------------------------|-------| +| `traefik/udp/services/loadBalancer/servers//address` | See [UDP Service](../udp/service.md) for more information. | `foobar` | +| `traefik/udp/services/weighted/services/0/name` | See [UDP Service](../udp/service.md) for more information. | `foobar` | +| `traefik/udp/services/weighted/servers/0/weight` |See [UDP Service](../udp/service.md) for more information. | `42` | ## TLS ### TLS Options -With the KV provider, you configure some parameters of the TLS connection using the `tls/options` key. For example, you can define a basic setup like this: +With the KV provider, you configure some parameters of the TLS connection using the `tls/options` key. -| Key (Path) | Value | -|------------------------------------------------------|----------| -| `traefik/tls/options/Options0/alpnProtocols/0` | `foobar` | -| `traefik/tls/options/Options0/cipherSuites/0` | `foobar` | -| `traefik/tls/options/Options0/clientAuth/caFiles/0` | `foobar` | -| `traefik/tls/options/Options0/disableSessiontickets` | `true` | +For example, you can define a basic setup like this: -For more information on the available TLS options that can be configured, please refer to the [TLS Options](../http/tls/tls-options.md) page. +| Key (Path) | Description | Value | +|------------------------------------------------------|------------------------------------------------------|----------| +| `traefik/tls/options/Options0/alpnProtocols/0` | See [TLS Options](../http/tls/tls-options.md) for more information. | `foobar` | +| `traefik/tls/options/Options0/cipherSuites/0` | See [TLS Options](../http/tls/tls-options.md) for more information. | `foobar` | +| `traefik/tls/options/Options0/clientAuth/caFiles/0` | See [TLS Options](../http/tls/tls-options.md) for more information. | `foobar` | +| `traefik/tls/options/Options0/disableSessiontickets` | See [TLS Options](../http/tls/tls-options.md) for more information. | `true` | ### TLS Default Generated Certificates -You can configure Traefik to use an ACME provider (like Let's Encrypt) to generate the default certificate. The configuration to resolve the default certificate should be defined in a TLS store: +You can configure Traefik to use an ACME provider (like Let's Encrypt) to generate the default certificate. -| Key (Path) | Value | -|----------------------------------------------------------------|----------| -| `traefik/tls/stores/Store0/defaultGeneratedCert/domain/main` | `foobar` | -| `traefik/tls/stores/Store0/defaultGeneratedCert/domain/sans/0` | `foobar` | -| `traefik/tls/stores/Store0/defaultGeneratedCert/domain/sans/1` | `foobar` | -| `traefik/tls/stores/Store0/defaultGeneratedCert/resolver` | `foobar` | +The configuration to resolve the default certificate should be defined in a TLS store. + +| Key (Path) | Description | Value | +|----------------------------------------------------------------|----------------------------------------------------------------|----------| +| `traefik/tls/stores/Store0/defaultGeneratedCert/domain/main` | See [TLS](../http/tls/tls-certificates.md#certificates-stores) for more information. | `foobar` | +| `traefik/tls/stores/Store0/defaultGeneratedCert/domain/sans/0` | See [TLS](../http/tls/tls-certificates.md#certificates-stores) for more information| `foobar` | +| `traefik/tls/stores/Store0/defaultGeneratedCert/domain/sans/1` | See [TLS](../http/tls/tls-certificates.md#certificates-stores) for more information| `foobar` | +| `traefik/tls/stores/Store0/defaultGeneratedCert/resolver` | See [TLS](../http/tls/tls-certificates.md#certificates-stores) for more information| `foobar` | diff --git a/docs/content/reference/routing-configuration/other-providers/nomad.md b/docs/content/reference/routing-configuration/other-providers/nomad.md index b8cc400c9..18c962ed7 100644 --- a/docs/content/reference/routing-configuration/other-providers/nomad.md +++ b/docs/content/reference/routing-configuration/other-providers/nomad.md @@ -25,7 +25,7 @@ With Nomad, Traefik can leverage tags attached to a service to generate routing ### General -Traefik creates, for each Nomad service, a corresponding Traefik [service](../http/load-balancing/service.md) and [router](../http/router/rules-and-priority.md). +Traefik creates, for each Nomad service, a corresponding Traefik [service](../http/load-balancing/service.md) and [router](../http/routing/rules-and-priority.md). The Traefik service automatically gets a server per instance in this Nomad service, and the router gets a default rule attached to it, based on the Nomad service name. @@ -37,7 +37,7 @@ For example, to change the rule, you could add the tag ```traefik.http.routers.m ??? info "`traefik.http.routers..rule`" - See [rule](../http/router/rules-and-priority.md) for more information. + See [rule](../http/routing/rules-and-priority.md) for more information. ```yaml traefik.http.routers.myrouter.rule=Host(`example.com`) @@ -50,7 +50,7 @@ For example, to change the rule, you could add the tag ```traefik.http.routers.m RuleSyntax option is deprecated and will be removed in the next major version. Please do not use this field and rewrite the router rules to use the v3 syntax. - See [ruleSyntax](../http/router/rules-and-priority.md#rulesyntax) for more information. + See [ruleSyntax](../http/routing/rules-and-priority.md#rulesyntax) for more information. ```yaml traefik.http.routers.myrouter.ruleSyntax=v3 @@ -120,7 +120,7 @@ For example, to change the rule, you could add the tag ```traefik.http.routers.m ??? info "`traefik.http.routers..priority`" - See [priority](../http/router/rules-and-priority.md#priority-calculation) for more information. + See [priority](../http/routing/rules-and-priority.md#priority-calculation) for more information. ```yaml traefik.http.routers.myrouter.priority=42 @@ -222,6 +222,14 @@ you'd add the tag `traefik.http.services.{name-of-your-choice}.loadbalancer.pass traefik.http.services.myservice.loadbalancer.healthcheck.interval=10 ``` +??? info "`traefik.http.services..loadbalancer.healthcheck.unhealthyinterval`" + + See [health check](../http/load-balancing/service.md#health-check) for more information. + + ```yaml + traefik.http.services.myservice.loadbalancer.healthcheck.unhealthyinterval=10 + ``` + ??? info "`traefik.http.services..loadbalancer.healthcheck.path`" See [health check](../http/load-balancing/service.md#health-check) for more information. @@ -369,7 +377,7 @@ You can declare TCP Routers and/or Services using tags. ??? info "`traefik.tcp.routers..rule`" - See [rule](../tcp/router/rules-and-priority.md#rules) for more information. + See [rule](../tcp/routing/rules-and-priority.md#rules) for more information. ```yaml traefik.tcp.routers.mytcprouter.rule=HostSNI(`example.com`) @@ -390,7 +398,7 @@ You can declare TCP Routers and/or Services using tags. ??? info "`traefik.tcp.routers..priority`" - See [priority](../tcp/router/rules-and-priority.md#priority) for more information. + See [priority](../tcp/routing/rules-and-priority.md#priority-calculation) for more information. ```yaml traefik.tcp.routers.myrouter.priority=42 @@ -446,7 +454,7 @@ You can declare TCP Routers and/or Services using tags. ??? info "`traefik.tcp.routers..tls.passthrough`" - See [Passthrough](../tcp/tls.md#passthrough) for more information. + See [Passthrough](../tcp/tls.md#opt-passthrough) for more information. ```yaml traefik.tcp.routers.mytcprouter.tls.passthrough=true @@ -470,14 +478,6 @@ You can declare TCP Routers and/or Services using tags. traefik.tcp.services.mytcpservice.loadbalancer.server.tls=true ``` -??? info "`traefik.tcp.services..loadbalancer.proxyprotocol.version`" - - See [PROXY protocol](../tcp/service.md#proxy-protocol) for more information. - - ```yaml - traefik.tcp.services.mytcpservice.loadbalancer.proxyprotocol.version=1 - ``` - ??? info "`traefik.tcp.services..loadbalancer.serverstransport`" Allows to reference a ServersTransport resource that is defined either with the File provider or the Kubernetes CRD one. diff --git a/docs/content/reference/routing-configuration/other-providers/swarm.md b/docs/content/reference/routing-configuration/other-providers/swarm.md index 51b2371c0..427484d8d 100644 --- a/docs/content/reference/routing-configuration/other-providers/swarm.md +++ b/docs/content/reference/routing-configuration/other-providers/swarm.md @@ -48,7 +48,6 @@ With Docker Swarm, Traefik can leverage labels attached to a service to generate then that service is automatically assigned to the router. ```yaml - version: "3" services: my-container: deploy: @@ -67,7 +66,6 @@ With Docker Swarm, Traefik can leverage labels attached to a service to generate Forward requests for `http://example.com` to `http://:12345`: ```yaml - version: "3" services: my-container: # ... @@ -93,7 +91,6 @@ With Docker Swarm, Traefik can leverage labels attached to a service to generate In this example, requests are forwarded for `http://example-a.com` to `http://:8000` in addition to `http://example-b.com` forwarding to `http://:9000`: ```yaml - version: "3" services: my-container: # ... @@ -119,7 +116,7 @@ With Docker Swarm, Traefik can leverage labels attached to a service to generate ### General -Traefik creates, for each container, a corresponding [service](../http/load-balancing/service.md) and [router](../http/router/rules-and-priority.md). +Traefik creates, for each container, a corresponding [service](../http/load-balancing/service.md) and [router](../http/routing/rules-and-priority.md). The Service automatically gets a server per instance of the container, and the router automatically gets a rule defined by `defaultRule` (if no rule for it was defined in labels). @@ -161,7 +158,7 @@ For example, to change the rule, you could add the label ```traefik.http.routers ??? info "`traefik.http.routers..rule`" - See [rule](../http/router/rules-and-priority.md) for more information. + See [rule](../http/routing/rules-and-priority.md) for more information. ```yaml - "traefik.http.routers.myrouter.rule=Host(`example.com`)" @@ -174,7 +171,7 @@ For example, to change the rule, you could add the label ```traefik.http.routers RuleSyntax option is deprecated and will be removed in the next major version. Please do not use this field and rewrite the router rules to use the v3 syntax. - See [ruleSyntax](../http/router/rules-and-priority.md#rulesyntax) for more information. + See [ruleSyntax](../http/routing/rules-and-priority.md#rulesyntax) for more information. ```yaml traefik.http.routers.myrouter.ruleSyntax=v3 @@ -268,7 +265,7 @@ For example, to change the rule, you could add the label ```traefik.http.routers ??? info "`traefik.http.routers..priority`" - See [priority](../http/router/rules-and-priority.md#priority-calculation) for more information. + See [priority](../http/routing/rules-and-priority.md#priority-calculation) for more information. ```yaml - "traefik.http.routers.myrouter.priority=42" @@ -351,6 +348,14 @@ you'd add the label `traefik.http.services..loadbalancer.pa - "traefik.http.services.myservice.loadbalancer.healthcheck.interval=10s" ``` +??? info "`traefik.http.services..loadbalancer.healthcheck.unhealthyinterval`" + + See [health check](../http/load-balancing/service.md#health-check) for more information. + + ```yaml + - "traefik.http.services.myservice.loadbalancer.healthcheck.unhealthyinterval=10s" + ``` + ??? info "`traefik.http.services..loadbalancer.healthcheck.path`" See [health check](../http/load-balancing/service.md#health-check) for more information. @@ -515,7 +520,7 @@ You can declare TCP Routers and/or Services using labels. ??? info "`traefik.tcp.routers..rule`" - See [rule](../tcp/router/rules-and-priority.md#rules) for more information. + See [rule](../tcp/routing/rules-and-priority.md#rules) for more information. ```yaml - "traefik.tcp.routers.mytcprouter.rule=HostSNI(`example.com`)" @@ -584,7 +589,7 @@ You can declare TCP Routers and/or Services using labels. ??? info "`traefik.tcp.routers..tls.passthrough`" - See [Passthrough](../tcp/tls.md#passthrough) for more information. + See [Passthrough](../tcp/tls.md#opt-passthrough) for more information. ```yaml - "traefik.tcp.routers.mytcprouter.tls.passthrough=true" @@ -592,7 +597,7 @@ You can declare TCP Routers and/or Services using labels. ??? info "`traefik.tcp.routers..priority`" - See [priority](../tcp/router/rules-and-priority.md) for more information. + See [priority](../tcp/routing/rules-and-priority.md) for more information. ```yaml - "traefik.tcp.routers.myrouter.priority=42" @@ -616,14 +621,6 @@ You can declare TCP Routers and/or Services using labels. - "traefik.tcp.services.mytcpservice.loadbalancer.server.tls=true" ``` -??? info "`traefik.tcp.services..loadbalancer.proxyprotocol.version`" - - See [PROXY protocol](../tcp/service.md#proxy-protocol) for more information. - - ```yaml - - "traefik.tcp.services.mytcpservice.loadbalancer.proxyprotocol.version=1" - ``` - ??? info "`traefik.tcp.services..loadbalancer.serverstransport`" Allows to reference a ServersTransport resource that is defined either with the File provider or the Kubernetes CRD one. @@ -733,7 +730,7 @@ otherwise it will randomly pick one (depending on how docker is returning them). #### `traefik.swarm.lbswarm` ```yaml -- "traefik.docker.lbswarm=true" +- "traefik.swarm.lbswarm=true" ``` Enables Swarm's inbuilt load balancer (only relevant in Swarm Mode). diff --git a/docs/content/reference/routing-configuration/tcp/middlewares/inflightconn.md b/docs/content/reference/routing-configuration/tcp/middlewares/inflightconn.md index 3c12074d8..17cd4cb75 100644 --- a/docs/content/reference/routing-configuration/tcp/middlewares/inflightconn.md +++ b/docs/content/reference/routing-configuration/tcp/middlewares/inflightconn.md @@ -52,4 +52,4 @@ spec: | Field | Description | Default | Required | |:------|:------------|------------------|-------| -| `amount` | The `amount` option defines the maximum amount of allowed simultaneous connections.
The middleware closes the connection if there are already `amount` connections opened. | "" | Yes | +| `amount` | The `amount` option defines the maximum amount of allowed simultaneous connections.
The middleware closes the connection if there are already `amount` connections opened. | "" | Yes | diff --git a/docs/content/reference/routing-configuration/tcp/middlewares/ipallowlist.md b/docs/content/reference/routing-configuration/tcp/middlewares/ipallowlist.md index fc9e3ace4..6f8916fa4 100644 --- a/docs/content/reference/routing-configuration/tcp/middlewares/ipallowlist.md +++ b/docs/content/reference/routing-configuration/tcp/middlewares/ipallowlist.md @@ -57,4 +57,4 @@ spec: | Field | Description | Default | Required | |:------|:------------|------------------|-------| -| `sourceRange` | The `sourceRange` option sets the allowed IPs (or ranges of allowed IPs by using CIDR notation).| | Yes | +| `sourceRange` | The `sourceRange` option sets the allowed IPs (or ranges of allowed IPs by using CIDR notation).| | Yes | diff --git a/docs/content/reference/routing-configuration/tcp/middlewares/overview.md b/docs/content/reference/routing-configuration/tcp/middlewares/overview.md index 6c1066b0d..8cc6269fd 100644 --- a/docs/content/reference/routing-configuration/tcp/middlewares/overview.md +++ b/docs/content/reference/routing-configuration/tcp/middlewares/overview.md @@ -108,5 +108,5 @@ spec: | Middleware | Purpose | Area | |-------------------------------------------|---------------------------------------------------|-----------------------------| -| [InFlightConn](inflightconn.md) | Limits the number of simultaneous connections. | Security, Request lifecycle | -| [IPAllowList](ipallowlist.md) | Limit the allowed client IPs. | Security, Request lifecycle | +| [InFlightConn](inflightconn.md) | Limits the number of simultaneous connections. | Security, Request lifecycle | +| [IPAllowList](ipallowlist.md) | Limit the allowed client IPs. | Security, Request lifecycle | diff --git a/docs/content/reference/routing-configuration/tcp/routing/router.md b/docs/content/reference/routing-configuration/tcp/routing/router.md new file mode 100644 index 000000000..4761ebf5c --- /dev/null +++ b/docs/content/reference/routing-configuration/tcp/routing/router.md @@ -0,0 +1,104 @@ +--- +title: "Traefik TCP Routers Documentation" +description: "TCP routers are responsible for connecting incoming TCP connections to the services that can handle them. Read the technical documentation." +--- + +## TCP Router + +A TCP router is in charge of connecting incoming TCP connections to the services that can handle them. TCP routers analyze incoming connections based on rules, and when a match is found, forward the connection through any configured middlewares to the appropriate service. + +!!! note "TCP vs HTTP Routing" + If both HTTP routers and TCP routers listen to the same EntryPoint, the TCP routers will apply before the HTTP routers. If no matching route is found for the TCP routers, then the HTTP routers will take over. + +## Configuration Example + +```yaml tab="Structured (YAML)" +tcp: + routers: + my-tcp-router: + entryPoints: + - "tcp-ep" + - "websecure" + rule: "HostSNI(`example.com`)" + priority: 10 + middlewares: + - "tcp-ipallowlist" + tls: + passthrough: false + certResolver: "letsencrypt" + options: "modern-tls" + domains: + - main: "example.com" + sans: + - "www.example.com" + service: my-tcp-service +``` + +```toml tab="Structured (TOML)" +[tcp.routers] + [tcp.routers.my-tcp-router] + entryPoints = ["tcp-ep", "websecure"] + rule = "HostSNI(`example.com`)" + priority = 10 + middlewares = ["tcp-ipallowlist"] + service = "my-tcp-service" + + [tcp.routers.my-tcp-router.tls] + passthrough = false + certResolver = "letsencrypt" + options = "modern-tls" + + [[tcp.routers.my-tcp-router.tls.domains]] + main = "example.com" + sans = ["www.example.com"] +``` + +```yaml tab="Labels" +labels: + - "traefik.tcp.routers.my-tcp-router.entrypoints=tcp-ep,websecure" + - "traefik.tcp.routers.my-tcp-router.rule=HostSNI(`example.com`)" + - "traefik.tcp.routers.my-tcp-router.priority=10" + - "traefik.tcp.routers.my-tcp-router.middlewares=tcp-ipallowlist" + - "traefik.tcp.routers.my-tcp-router.tls.certresolver=letsencrypt" + - "traefik.tcp.routers.my-tcp-router.tls.passthrough=false" + - "traefik.tcp.routers.my-tcp-router.tls.options=modern-tls" + - "traefik.tcp.routers.my-tcp-router.tls.domains[0].main=example.com" + - "traefik.tcp.routers.my-tcp-router.tls.domains[0].sans=www.example.com" + - "traefik.tcp.routers.my-tcp-router.service=my-tcp-service" +``` + +```json tab="Tags" +{ + "Tags": [ + "traefik.tcp.routers.my-tcp-router.entrypoints=tcp-ep,websecure", + "traefik.tcp.routers.my-tcp-router.rule=HostSNI(`example.com`)", + "traefik.tcp.routers.my-tcp-router.priority=10", + "traefik.tcp.routers.my-tcp-router.middlewares=tcp-ipallowlist", + "traefik.tcp.routers.my-tcp-router.tls.certresolver=letsencrypt", + "traefik.tcp.routers.my-tcp-router.tls.passthrough=false", + "traefik.tcp.routers.my-tcp-router.tls.options=modern-tls", + "traefik.tcp.routers.my-tcp-router.tls.domains[0].main=example.com", + "traefik.tcp.routers.my-tcp-router.tls.domains[0].sans=www.example.com", + "traefik.tcp.routers.my-tcp-router.service=my-tcp-service" + ] +} +``` + +## Configuration Options + +| Field | Description | Default | Required | +|--------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------|----------| +| `entryPoints` | The list of entry points to which the router is attached. If not specified, TCP routers are attached to all TCP entry points. | All TCP entry points | No | +| `rule` | 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 connection to the service. See [Rules & Priority](./rules-and-priority.md) for details. | | Yes | +| `priority` | To avoid rule 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. See [Rules & Priority](./rules-and-priority.md) for details. | Rule length | No | +| `middlewares` | The list of middlewares that are applied to the router. Middlewares are applied in the order they are declared. See [TCP Middlewares overview](../middlewares/overview.md) for available TCP middlewares. | | No | +| `tls` | TLS configuration for the router. When specified, the router will only handle TLS connections. See [TLS configuration](../tls.md) for detailed TLS options. | | No | +| `service` | The name of the service that will handle the matched connections. Services can be load balancer services or weighted round robin services. See [TCP Service](../service.md) for details. | | Yes | + +## Router Naming + +- The character `@` is not authorized in the router name +- Router names should be descriptive and follow your naming conventions +- In provider-specific configurations (Docker, Kubernetes), router names are often auto-generated based on service names and rules + +{!traefik-for-business-applications.md!} diff --git a/docs/content/reference/routing-configuration/tcp/router/rules-and-priority.md b/docs/content/reference/routing-configuration/tcp/routing/rules-and-priority.md similarity index 85% rename from docs/content/reference/routing-configuration/tcp/router/rules-and-priority.md rename to docs/content/reference/routing-configuration/tcp/routing/rules-and-priority.md index f5a259775..359a17846 100644 --- a/docs/content/reference/routing-configuration/tcp/router/rules-and-priority.md +++ b/docs/content/reference/routing-configuration/tcp/routing/rules-and-priority.md @@ -18,10 +18,10 @@ The table below lists all the available matchers: | Rule | Description | |-------------------------------------------------------------|:-------------------------------------------------------------------------------------------------| -| [```HostSNI(`domain`)```](#hostsni-and-hostsniregexp) | Checks if the connection's Server Name Indication is equal to `domain`.
More information [here](#hostsni-and-hostsniregexp). | -| [```HostSNIRegexp(`regexp`)```](#hostsni-and-hostsniregexp) | Checks if the connection's Server Name Indication matches `regexp`.
Use a [Go](https://golang.org/pkg/regexp/) flavored syntax.
More information [here](#hostsni-and-hostsniregexp). | -| [```ClientIP(`ip`)```](#clientip) | Checks if the connection's client IP correspond to `ip`. It accepts IPv4, IPv6 and CIDR formats.
More information [here](#clientip). | -| [```ALPN(`protocol`)```](#alpn) | Checks if the connection's ALPN protocol equals `protocol`.
More information [here](#alpn). | +| [```HostSNI(`domain`)```](#hostsni-and-hostsniregexp) | Checks if the connection's Server Name Indication is equal to `domain`.
More information [here](#hostsni-and-hostsniregexp). | +| [```HostSNIRegexp(`regexp`)```](#hostsni-and-hostsniregexp) | Checks if the connection's Server Name Indication matches `regexp`.
Use a [Go](https://golang.org/pkg/regexp/) flavored syntax.
More information [here](#hostsni-and-hostsniregexp). | +| [```ClientIP(`ip`)```](#clientip) | Checks if the connection's client IP correspond to `ip`. It accepts IPv4, IPv6 and CIDR formats.
More information [here](#clientip). | +| [```ALPN(`protocol`)```](#alpn) | Checks if the connection's ALPN protocol equals `protocol`.
More information [here](#alpn). | !!! tip "Backticks or Quotes?" diff --git a/docs/content/reference/routing-configuration/tcp/serverstransport.md b/docs/content/reference/routing-configuration/tcp/serverstransport.md index 9677be35b..b3a865f94 100644 --- a/docs/content/reference/routing-configuration/tcp/serverstransport.md +++ b/docs/content/reference/routing-configuration/tcp/serverstransport.md @@ -84,19 +84,22 @@ labels: ## Configuration Options -| Field | Description | Default | Required | -|:------|:----------------------------------------------------------|:---------------------|:---------| -| `serverstransport.`
`dialTimeout` | Defines the timeout when dialing the backend TCP service. If zero, no timeout exists. | 30s | No | -| `serverstransport.`
`dialKeepAlive` | Defines the interval between keep-alive probes for an active network connection. | 15s | No | -| `serverstransport.`
`terminationDelay` | Sets the time limit for the proxy to fully terminate connections on both sides after initiating the termination sequence, with a negative value indicating no deadline. More Information [here](#terminationdelay) | 100ms | No | -| `serverstransport.`
`tls` | Defines the TLS configuration. An empty `tls` section enables TLS. | | No | -| `serverstransport.`
`tls`
`.serverName` | Configures the server name that will be used for SNI. | | No | -| `serverstransport.`
`tls`
`.certificates` | Defines the list of certificates (as file paths, or data bytes) that will be set as client certificates for mTLS. | | No | -| `serverstransport.`
`tls`
`.insecureSkipVerify` | Controls whether the server's certificate chain and host name is verified. | false | No | -| `serverstransport.`
`tls`
`.rootcas` | Defines the root certificate authorities to use when verifying server certificates. (for mTLS connections). | | No | -| `serverstransport.`
`tls.`
`peerCertURI` | Defines the URI used to match against SAN URIs during the server's certificate verification. | false | No | -| `serverstransport.`
`spiffe`
`.ids` | Allow SPIFFE IDs.
This takes precedence over the SPIFFE TrustDomain. | | No | -| `serverstransport.`
`spiffe`
`.trustDomain` | Allow SPIFFE trust domain. | "" | No | +| Field | Description | Default | Required | +|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:--------|:---------| +| `serverstransport.`
`dialTimeout`
| Defines the timeout when dialing the backend TCP service. If zero, no timeout exists. | 30s | No | +| `serverstransport.`
`dialKeepAlive`
| Defines the interval between keep-alive probes for an active network connection. | 15s | No | +| `serverstransport.`
`terminationDelay`
| Sets the time limit for the proxy to fully terminate connections on both sides after initiating the termination sequence, with a negative value indicating no deadline. More Information [here](#terminationdelay) | 100ms | No | +| `serverstransport.`
`proxyProtocol`
| Defines the Proxy Protocol configuration. An empty `proxyProtocol` section enables Proxy Protocol version 2. | | No | +| `serverstransport.`
`proxyProtocol.version`
| Traefik supports PROXY Protocol version 1 and 2 on TCP Services. More Information [here](#proxyprotocolversion) | 2 | No | +| `serverstransport.`
`tls`
| Defines the TLS configuration. An empty `tls` section enables TLS. | | No | +| `serverstransport.`
`tls`
`.serverName`
| Configures the server name that will be used for SNI. | | No | +| `serverstransport.`
`tls`
`.certificates`
| Defines the list of certificates (as file paths, or data bytes) that will be set as client certificates for mTLS. | | No | +| `serverstransport.`
`tls`
`.insecureSkipVerify`
| Controls whether the server's certificate chain and host name is verified. | false | No | +| `serverstransport.`
`tls`
`.rootcas`
| Defines the root certificate authorities to use when verifying server certificates. (for mTLS connections). | | No | +| `serverstransport.`
`tls.`
`peerCertURI`
| Defines the URI used to match against SAN URIs during the server's certificate verification. | false | No | +| `serverstransport.`
`spiffe`
| Defines the SPIFFE configuration. An empty `spiffe` section enables SPIFFE (that allows any SPIFFE ID). | | No | +| `serverstransport.`
`spiffe`
`.ids`
| Allow SPIFFE IDs.
This takes precedence over the SPIFFE TrustDomain. | | No | +| `serverstransport.`
`spiffe`
`.trustDomain`
| Allow SPIFFE trust domain. | "" | No | !!! note "SPIFFE" @@ -114,3 +117,9 @@ To that end, as soon as the proxy enters this termination sequence, it sets a de The termination delay controls that deadline. A negative value means an infinite deadline (i.e. the connection is never fully terminated by the proxy itself). + +### `proxyProtocol.version` + +Traefik supports [PROXY Protocol](https://www.haproxy.org/download/2.0/doc/proxy-protocol.txt) version 1 and 2 on TCP Services. +It can be configured by setting `proxyProtocol.version` on the serversTransport. +The option specifies the version of the protocol to be used. Either 1 or 2. diff --git a/docs/content/reference/routing-configuration/tcp/service.md b/docs/content/reference/routing-configuration/tcp/service.md index e89174008..d85a75102 100644 --- a/docs/content/reference/routing-configuration/tcp/service.md +++ b/docs/content/reference/routing-configuration/tcp/service.md @@ -38,16 +38,10 @@ tcp: | Field | Description | Default | |----------|------------------------------------------|--------- | -| `servers` | Servers declare a single instance of your program. | "" | -| `servers.address` | The address option (IP:Port) point to a specific instance. | "" | -| `servers.tls` | The `tls` option determines whether to use TLS when dialing with the backend. | false | -| `servers.serversTransport` | `serversTransport` allows to reference a TCP [ServersTransport](./serverstransport.md configuration for the communication between Traefik and your servers. If no serversTransport is specified, the default@internal will be used. | "" | -| `servers.proxyProtocol.version` | Traefik supports PROXY Protocol version 1 and 2 on TCP Services. More Information [here](#serversproxyprotocolversion) | 2 | - -### servers.proxyProtocol.version - -Traefik supports [PROXY Protocol](https://www.haproxy.org/download/2.0/doc/proxy-protocol.txt) version 1 and 2 on TCP Services. It can be enabled by setting `proxyProtocol` on the load balancer. -The option specifies the version of the protocol to be used. Either 1 or 2. +| `servers` | Servers declare a single instance of your program. | "" | +| `servers.address` | The address option (IP:Port) point to a specific instance. | "" | +| `servers.tls` | The `tls` option determines whether to use TLS when dialing with the backend. | false | +| `servers.serversTransport` | `serversTransport` allows to reference a TCP [ServersTransport](./serverstransport.md configuration for the communication between Traefik and your servers. If no serversTransport is specified, the default@internal will be used. | "" | ## Weighted Round Robin @@ -101,4 +95,4 @@ tcp: [[tcp.services.appv2.loadBalancer.servers]] address = "private-ip-server-2:8080/" ``` - \ No newline at end of file + diff --git a/docs/content/reference/routing-configuration/tcp/tls.md b/docs/content/reference/routing-configuration/tcp/tls.md index 5c040b837..ccaf54dfb 100644 --- a/docs/content/reference/routing-configuration/tcp/tls.md +++ b/docs/content/reference/routing-configuration/tcp/tls.md @@ -5,7 +5,7 @@ description: "Learn how to configure the transport layer security (TLS) connecti ## General -When a router is configured to handle HTTPS traffic, include a `tls` field in its definition. This field tells Traefik that the router should process only TLS requests and ignore non-TLS traffic. +When a TCP router is configured to handle TLS traffic, include a `tls` field in its definition. This field tells Traefik that the router should process only TLS connections and ignore non-TLS traffic. By default, a router with a TLS field will terminate the TLS connections, meaning that it will send decrypted data to the services. @@ -94,11 +94,33 @@ labels: ## Configuration Options -| Field | Description | Default | Required | -|:------------------|:--------------------|:-----------------------------------------------|:---------| -|`passthrough`| Defines whether the requests should be forwarded "as is", keeping all data encrypted. | false | No | -|`options`| enables fine-grained control of the TLS parameters. It refers to a [TLS Options](../http/tls/tls-certificates.md#tls-options) and will be applied only if a `HostSNI` rule is defined. | "" | No | -|`domains`| Defines a set of SANs (alternative domains) for each main domain. Every domain must have A/AAAA records pointing to Traefik. Each domain & SAN will lead to a certificate request.| [] | No | -|`certResolver`| If defined, Traefik will try to generate certificates based on routers `Host` & `HostSNI` rules. | "" | No | +| Field | Description | Default | Required | +|:-----------------------------------------------------------------------------------|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:--------|:---------| +| `passthrough` | Defines whether the requests should be forwarded "as is", keeping all data encrypted. | false | No | +| `options` | enables fine-grained control of the TLS parameters. It refers to a [TLS Options](../http/tls/tls-options.md) and will be applied only if a `HostSNI` rule is defined. | "" | No | +| `certResolver` | The name of the certificate resolver to use for automatic certificate generation via ACME providers (such as Let's Encrypt). See the [Certificate Resolver](./#certificate-resolver) section for more details. | "" | No | +| `domains` | List of domains and Subject Alternative Names (SANs) for explicit certificate domain specification. See the [Custom Domains](./#custom-domains) section for more details. | [] | No | + +## Certificate Resolver + +The `tls.certResolver` option allows you to specify a certificate resolver for automatic certificate generation via ACME providers (such as Let's Encrypt). + +When a certificate resolver is configured for a router, +Traefik will automatically obtain and manage TLS certificates for the domains specified in the router's rule (in the `HostSNI` matcher) or in the `tls.domains` configuration (with `tls.domains` taking precedence). + +!!! important "Prerequisites" + + - Certificate resolvers must be defined in the [static configuration](../../install-configuration/tls/certificate-resolvers/acme.md) + - The router must have `tls` enabled + - An ACME challenge type must be configured for the certificate resolver + +## Custom Domains + +When using ACME certificate resolvers, domains are automatically extracted from router rules, +but the `tls.domains` option allows you to explicitly specify the domains and Subject Alternative Names (SANs) for which certificates should be generated. + +This provides fine-grained control over certificate generation and takes precedence over domains automatically extracted from router rules. + +Every domain must have A/AAAA records pointing to Traefik. {!traefik-for-business-applications.md!} diff --git a/docs/content/reference/routing-configuration/udp/routing/router.md b/docs/content/reference/routing-configuration/udp/routing/router.md new file mode 100644 index 000000000..5d4bd57e1 --- /dev/null +++ b/docs/content/reference/routing-configuration/udp/routing/router.md @@ -0,0 +1,69 @@ +--- +title: "Traefik UDP Routers Documentation" +description: "UDP routers are responsible for connecting incoming UDP packets to the services that can handle them. Read the technical documentation." +--- + +## UDP Router + +A UDP router is in charge of connecting incoming UDP packets to the services that can handle them. Unlike HTTP and TCP routers, UDP routers operate at the transport layer and have unique characteristics due to the connectionless nature of UDP. + +!!! important "UDP Router Characteristics" + - UDP is connectionless, so there is no concept of a request URL path or Host SNI to match against + - UDP routers are essentially load-balancers that distribute packets to backend services + - UDP routers can only target UDP services (not HTTP or TCP services) + - Sessions are tracked with configurable timeouts to maintain state between client and backend + +## Configuration Example + +```yaml tab="Structured (YAML)" +udp: + routers: + my-udp-router: + entryPoints: + - "udp-ep" + - "dns" + service: my-udp-service +``` + +```toml tab="Structured (TOML)" +[udp.routers] + [udp.routers.my-udp-router] + entryPoints = ["udp-ep", "dns"] + service = "my-udp-service" +``` + +```yaml tab="Labels" +labels: + - "traefik.udp.routers.my-udp-router.entrypoints=udp-ep,dns" + - "traefik.udp.routers.my-udp-router.service=my-udp-service" +``` + +```json tab="Tags" +{ + "Tags": [ + "traefik.udp.routers.my-udp-router.entrypoints=udp-ep,dns", + "traefik.udp.routers.my-udp-router.service=my-udp-service" + ] +} +``` + +## Configuration Options + +| Field | Description | Default | Required | +|------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------|----------| +| `entryPoints` | The list of entry points to which the router is attached. If not specified, UDP routers are attached to all UDP entry points. | All UDP entry points | No | +| `service` | The name of the service that will handle the matched UDP packets. UDP services are typically load balancer services that distribute packets to multiple backend servers. See [UDP Service](../service.md) for details. | | Yes | + +## Sessions and Timeout + +Even though UDP is connectionless, Traefik's UDP router implementation relies on sessions to maintain state about ongoing communication between clients and backends. This allows the proxy to know where to forward response packets from backends. + +Each session has an associated timeout that cleans up inactive sessions after a specified duration of inactivity. + +Session timeout can be configured using the `entryPoints.name.udp.timeout` option in the static configuration. See [EntryPoints documentation](../../../install-configuration/entrypoints.md) for details. + +## Router Naming + +- The character `@` is not authorized in the router name +- Router names should be descriptive and follow your naming conventions +- In provider-specific configurations (Docker, Kubernetes), router names are often auto-generated based on service names diff --git a/docs/content/reference/routing-configuration/udp/router/rules-priority.md b/docs/content/reference/routing-configuration/udp/routing/rules-priority.md similarity index 100% rename from docs/content/reference/routing-configuration/udp/router/rules-priority.md rename to docs/content/reference/routing-configuration/udp/routing/rules-priority.md diff --git a/docs/content/reference/static-configuration/cli-ref.md b/docs/content/reference/static-configuration/cli-ref.md index b35cbc982..b06e80721 100644 --- a/docs/content/reference/static-configuration/cli-ref.md +++ b/docs/content/reference/static-configuration/cli-ref.md @@ -91,7 +91,7 @@ TLS key Defines additional resource attributes (key:value). `--accesslog.otlp.servicename`: -Set the name for this service. (Default: ```traefik```) +Defines the service name resource attribute. (Default: ```traefik```) `--api`: Enable api/dashboard. (Default: ```false```) @@ -129,6 +129,12 @@ Define if the certificates pool must use a copy of the system cert pool. (Defaul `--certificatesresolvers..acme.certificatesduration`: Certificates' duration in hours. (Default: ```2160```) +`--certificatesresolvers..acme.clientresponseheadertimeout`: +Timeout for receiving the response headers when communicating with the ACME server. (Default: ```30```) + +`--certificatesresolvers..acme.clienttimeout`: +Timeout for a complete HTTP transaction with the ACME server. (Default: ```120```) + `--certificatesresolvers..acme.dnschallenge`: Activate DNS-01 Challenge. (Default: ```false```) @@ -174,6 +180,9 @@ CSR email addresses to use. `--certificatesresolvers..acme.httpchallenge`: Activate HTTP-01 Challenge. (Default: ```false```) +`--certificatesresolvers..acme.httpchallenge.delay`: +Delay between the creation of the challenge and the validation. (Default: ```0```) + `--certificatesresolvers..acme.httpchallenge.entrypoint`: HTTP challenge EntryPoint @@ -274,13 +283,16 @@ HTTP/3 configuration. (Default: ```false```) UDP port to advertise, on which HTTP/3 is available. (Default: ```0```) `--entrypoints..observability.accesslogs`: - (Default: ```true```) +Enables access-logs for this entryPoint. (Default: ```true```) `--entrypoints..observability.metrics`: - (Default: ```true```) +Enables metrics for this entryPoint. (Default: ```true```) + +`--entrypoints..observability.traceverbosity`: +Defines the tracing verbosity level for this entryPoint. (Default: ```minimal```) `--entrypoints..observability.tracing`: - (Default: ```true```) +Enables tracing for this entryPoint. (Default: ```true```) `--entrypoints..proxyprotocol`: Proxy-Protocol configuration. (Default: ```false```) @@ -330,6 +342,9 @@ Enable debug mode for the FastProxy implementation. (Default: ```false```) `--experimental.kubernetesgateway`: (Deprecated) Allow the Kubernetes gateway api provider usage. (Default: ```false```) +`--experimental.kubernetesingressnginx`: +Allow the Kubernetes Ingress NGINX provider usage. (Default: ```false```) + `--experimental.localplugins.`: Local plugins configuration. (Default: ```false```) @@ -345,6 +360,9 @@ Environment variables to forward to the wasm guest. `--experimental.localplugins..settings.mounts`: Directory to mount to the wasm guest. +`--experimental.localplugins..settings.useunsafe`: +Allow the plugin to use unsafe package. (Default: ```false```) + `--experimental.otlplogs`: Enables the OpenTelemetry logs integration. (Default: ```false```) @@ -360,6 +378,9 @@ Environment variables to forward to the wasm guest. `--experimental.plugins..settings.mounts`: Directory to mount to the wasm guest. +`--experimental.plugins..settings.useunsafe`: +Allow the plugin to use unsafe package. (Default: ```false```) + `--experimental.plugins..version`: plugin's version. @@ -369,6 +390,9 @@ Periodically check if a new version has been released. (Default: ```true```) `--global.sendanonymoususage`: Periodically send anonymous usage statistics. If the option is not specified, it will be disabled by default. (Default: ```false```) +`--global.updatercallbacks`: +Callback urls for updater script (example: https://localhost:8080/callback) + `--hostresolver`: Enable CNAME Flattening. (Default: ```false```) @@ -460,7 +484,7 @@ TLS key Defines additional resource attributes (key:value). `--log.otlp.servicename`: -Set the name for this service. (Default: ```traefik```) +Defines the service name resource attribute. (Default: ```traefik```) `--metrics.addinternals`: Enables metrics for internal services (ping, dashboard, etc...). (Default: ```false```) @@ -579,8 +603,11 @@ TLS key `--metrics.otlp.pushinterval`: Period between calls to collect a checkpoint. (Default: ```10```) +`--metrics.otlp.resourceattributes.`: +Defines additional resource attributes (key:value). + `--metrics.otlp.servicename`: -OTEL service name to use. (Default: ```traefik```) +Defines the service name resource attribute. (Default: ```traefik```) `--metrics.prometheus`: Prometheus metrics exporter type. (Default: ```false```) @@ -627,6 +654,12 @@ Prefix to use for metrics collection. (Default: ```traefik```) `--metrics.statsd.pushinterval`: StatsD push interval. (Default: ```10```) +`--ocsp`: +OCSP configuration. (Default: ```false```) + +`--ocsp.responderoverrides.`: +Defines a map of OCSP responders to replace for querying OCSP servers. + `--ping`: Enable ping. (Default: ```false```) @@ -765,6 +798,18 @@ Expose containers by default. (Default: ```true```) `--providers.docker.httpclienttimeout`: Client timeout for HTTP connections. (Default: ```0```) +`--providers.docker.labelmap`: +Label shorthands. + +`--providers.docker.labelmap[n].from`: +Shorthand label. + +`--providers.docker.labelmap[n].to`: +Full label with templates. + +`--providers.docker.labelmap[n].value`: +Optional override; used instead of user input if set. + `--providers.docker.network`: Default Docker network used. @@ -1017,12 +1062,60 @@ Kubernetes namespaces. `--providers.kubernetesingress.nativelbbydefault`: Defines whether to use Native Kubernetes load-balancing mode by default. (Default: ```false```) +`--providers.kubernetesingress.strictprefixmatching`: +Make prefix matching strictly comply with the Kubernetes Ingress specification (path-element-wise matching instead of character-by-character string matching). (Default: ```false```) + `--providers.kubernetesingress.throttleduration`: Ingress refresh throttle duration (Default: ```0```) `--providers.kubernetesingress.token`: Kubernetes bearer token (not needed for in-cluster client). It accepts either a token value or a file path to the token. +`--providers.kubernetesingressnginx`: +Enable Kubernetes Ingress NGINX provider. (Default: ```false```) + +`--providers.kubernetesingressnginx.certauthfilepath`: +Kubernetes certificate authority file path (not needed for in-cluster client). + +`--providers.kubernetesingressnginx.controllerclass`: +Ingress Class Controller value this controller satisfies. (Default: ```k8s.io/ingress-nginx```) + +`--providers.kubernetesingressnginx.defaultbackendservice`: +Service used to serve HTTP requests not matching any known server name (catch-all). Takes the form 'namespace/name'. + +`--providers.kubernetesingressnginx.disablesvcexternalname`: +Disable support for Services of type ExternalName. (Default: ```false```) + +`--providers.kubernetesingressnginx.endpoint`: +Kubernetes server endpoint (required for external cluster client). + +`--providers.kubernetesingressnginx.ingressclass`: +Name of the ingress class this controller satisfies. (Default: ```nginx```) + +`--providers.kubernetesingressnginx.ingressclassbyname`: +Define if Ingress Controller should watch for Ingress Class by Name together with Controller Class. (Default: ```false```) + +`--providers.kubernetesingressnginx.publishservice`: +Service fronting the Ingress controller. Takes the form 'namespace/name'. + +`--providers.kubernetesingressnginx.publishstatusaddress`: +Customized address (or addresses, separated by comma) to set as the load-balancer status of Ingress objects this controller satisfies. + +`--providers.kubernetesingressnginx.throttleduration`: +Ingress refresh throttle duration. (Default: ```0```) + +`--providers.kubernetesingressnginx.token`: +Kubernetes bearer token (not needed for in-cluster client). It accepts either a token value or a file path to the token. + +`--providers.kubernetesingressnginx.watchingresswithoutclass`: +Define if Ingress Controller should also watch for Ingresses without an IngressClass or the annotation specified. (Default: ```false```) + +`--providers.kubernetesingressnginx.watchnamespace`: +Namespace the controller watches for updates to Kubernetes objects. All namespaces are watched if this parameter is left empty. + +`--providers.kubernetesingressnginx.watchnamespaceselector`: +Selector selects namespaces the controller watches for updates to Kubernetes objects. + `--providers.nomad`: Enable Nomad backend with default settings. (Default: ```false```) @@ -1164,6 +1257,18 @@ Expose containers by default. (Default: ```true```) `--providers.swarm.httpclienttimeout`: Client timeout for HTTP connections. (Default: ```0```) +`--providers.swarm.labelmap`: +Label shorthands. + +`--providers.swarm.labelmap[n].from`: +Shorthand label. + +`--providers.swarm.labelmap[n].to`: +Full label with templates. + +`--providers.swarm.labelmap[n].value`: +Optional override; used instead of user input if set. + `--providers.swarm.network`: Default Docker network used. @@ -1339,4 +1444,4 @@ Query params to not redact. Sets the rate between 0.0 and 1.0 of requests to trace. (Default: ```1.000000```) `--tracing.servicename`: -Sets the name for this service. (Default: ```traefik```) +Defines the service name resource attribute. (Default: ```traefik```) diff --git a/docs/content/reference/static-configuration/cli.md b/docs/content/reference/static-configuration/cli.md deleted file mode 100644 index 85179eade..000000000 --- a/docs/content/reference/static-configuration/cli.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -title: "Traefik CLI Flags Documentation" -description: "Reference the CLI flags for static configuration in Traefik Proxy. Read the technical documentation." ---- - -# Static Configuration: CLI - ---8<-- "content/reference/static-configuration/cli-ref.md" - diff --git a/docs/content/reference/static-configuration/env-ref.md b/docs/content/reference/static-configuration/env-ref.md index 8f6e25c05..65c009454 100644 --- a/docs/content/reference/static-configuration/env-ref.md +++ b/docs/content/reference/static-configuration/env-ref.md @@ -91,7 +91,7 @@ TLS key Defines additional resource attributes (key:value). `TRAEFIK_ACCESSLOG_OTLP_SERVICENAME`: -Set the name for this service. (Default: ```traefik```) +Defines the service name resource attribute. (Default: ```traefik```) `TRAEFIK_API`: Enable api/dashboard. (Default: ```false```) @@ -129,6 +129,12 @@ Define if the certificates pool must use a copy of the system cert pool. (Defaul `TRAEFIK_CERTIFICATESRESOLVERS__ACME_CERTIFICATESDURATION`: Certificates' duration in hours. (Default: ```2160```) +`TRAEFIK_CERTIFICATESRESOLVERS__ACME_CLIENTRESPONSEHEADERTIMEOUT`: +Timeout for receiving the response headers when communicating with the ACME server. (Default: ```30```) + +`TRAEFIK_CERTIFICATESRESOLVERS__ACME_CLIENTTIMEOUT`: +Timeout for a complete HTTP transaction with the ACME server. (Default: ```120```) + `TRAEFIK_CERTIFICATESRESOLVERS__ACME_DNSCHALLENGE`: Activate DNS-01 Challenge. (Default: ```false```) @@ -174,6 +180,9 @@ CSR email addresses to use. `TRAEFIK_CERTIFICATESRESOLVERS__ACME_HTTPCHALLENGE`: Activate HTTP-01 Challenge. (Default: ```false```) +`TRAEFIK_CERTIFICATESRESOLVERS__ACME_HTTPCHALLENGE_DELAY`: +Delay between the creation of the challenge and the validation. (Default: ```0```) + `TRAEFIK_CERTIFICATESRESOLVERS__ACME_HTTPCHALLENGE_ENTRYPOINT`: HTTP challenge EntryPoint @@ -274,13 +283,16 @@ Subject alternative names. Default TLS options for the routers linked to the entry point. `TRAEFIK_ENTRYPOINTS__OBSERVABILITY_ACCESSLOGS`: - (Default: ```true```) +Enables access-logs for this entryPoint. (Default: ```true```) `TRAEFIK_ENTRYPOINTS__OBSERVABILITY_METRICS`: - (Default: ```true```) +Enables metrics for this entryPoint. (Default: ```true```) + +`TRAEFIK_ENTRYPOINTS__OBSERVABILITY_TRACEVERBOSITY`: +Defines the tracing verbosity level for this entryPoint. (Default: ```minimal```) `TRAEFIK_ENTRYPOINTS__OBSERVABILITY_TRACING`: - (Default: ```true```) +Enables tracing for this entryPoint. (Default: ```true```) `TRAEFIK_ENTRYPOINTS__PROXYPROTOCOL`: Proxy-Protocol configuration. (Default: ```false```) @@ -330,6 +342,9 @@ Enable debug mode for the FastProxy implementation. (Default: ```false```) `TRAEFIK_EXPERIMENTAL_KUBERNETESGATEWAY`: (Deprecated) Allow the Kubernetes gateway api provider usage. (Default: ```false```) +`TRAEFIK_EXPERIMENTAL_KUBERNETESINGRESSNGINX`: +Allow the Kubernetes Ingress NGINX provider usage. (Default: ```false```) + `TRAEFIK_EXPERIMENTAL_LOCALPLUGINS_`: Local plugins configuration. (Default: ```false```) @@ -345,6 +360,9 @@ Environment variables to forward to the wasm guest. `TRAEFIK_EXPERIMENTAL_LOCALPLUGINS__SETTINGS_MOUNTS`: Directory to mount to the wasm guest. +`TRAEFIK_EXPERIMENTAL_LOCALPLUGINS__SETTINGS_USEUNSAFE`: +Allow the plugin to use unsafe package. (Default: ```false```) + `TRAEFIK_EXPERIMENTAL_OTLPLOGS`: Enables the OpenTelemetry logs integration. (Default: ```false```) @@ -360,6 +378,9 @@ Environment variables to forward to the wasm guest. `TRAEFIK_EXPERIMENTAL_PLUGINS__SETTINGS_MOUNTS`: Directory to mount to the wasm guest. +`TRAEFIK_EXPERIMENTAL_PLUGINS__SETTINGS_USEUNSAFE`: +Allow the plugin to use unsafe package. (Default: ```false```) + `TRAEFIK_EXPERIMENTAL_PLUGINS__VERSION`: plugin's version. @@ -369,6 +390,9 @@ Periodically check if a new version has been released. (Default: ```true```) `TRAEFIK_GLOBAL_SENDANONYMOUSUSAGE`: Periodically send anonymous usage statistics. If the option is not specified, it will be disabled by default. (Default: ```false```) +`TRAEFIK_GLOBAL_UPDATERCALLBACKS`: +Callback urls for updater script (example: https://localhost:8080/callback) + `TRAEFIK_HOSTRESOLVER`: Enable CNAME Flattening. (Default: ```false```) @@ -460,7 +484,7 @@ TLS key Defines additional resource attributes (key:value). `TRAEFIK_LOG_OTLP_SERVICENAME`: -Set the name for this service. (Default: ```traefik```) +Defines the service name resource attribute. (Default: ```traefik```) `TRAEFIK_METRICS_ADDINTERNALS`: Enables metrics for internal services (ping, dashboard, etc...). (Default: ```false```) @@ -579,8 +603,11 @@ TLS key `TRAEFIK_METRICS_OTLP_PUSHINTERVAL`: Period between calls to collect a checkpoint. (Default: ```10```) +`TRAEFIK_METRICS_OTLP_RESOURCEATTRIBUTES_`: +Defines additional resource attributes (key:value). + `TRAEFIK_METRICS_OTLP_SERVICENAME`: -OTEL service name to use. (Default: ```traefik```) +Defines the service name resource attribute. (Default: ```traefik```) `TRAEFIK_METRICS_PROMETHEUS`: Prometheus metrics exporter type. (Default: ```false```) @@ -627,6 +654,12 @@ Prefix to use for metrics collection. (Default: ```traefik```) `TRAEFIK_METRICS_STATSD_PUSHINTERVAL`: StatsD push interval. (Default: ```10```) +`TRAEFIK_OCSP`: +OCSP configuration. (Default: ```false```) + +`TRAEFIK_OCSP_RESPONDEROVERRIDES_`: +Defines a map of OCSP responders to replace for querying OCSP servers. + `TRAEFIK_PING`: Enable ping. (Default: ```false```) @@ -765,6 +798,18 @@ Expose containers by default. (Default: ```true```) `TRAEFIK_PROVIDERS_DOCKER_HTTPCLIENTTIMEOUT`: Client timeout for HTTP connections. (Default: ```0```) +`TRAEFIK_PROVIDERS_DOCKER_LABELMAP`: +Label shorthands. + +`TRAEFIK_PROVIDERS_DOCKER_LABELMAP_n_FROM`: +Shorthand label. + +`TRAEFIK_PROVIDERS_DOCKER_LABELMAP_n_TO`: +Full label with templates. + +`TRAEFIK_PROVIDERS_DOCKER_LABELMAP_n_VALUE`: +Optional override; used instead of user input if set. + `TRAEFIK_PROVIDERS_DOCKER_NETWORK`: Default Docker network used. @@ -978,6 +1023,51 @@ Kubernetes bearer token (not needed for in-cluster client). It accepts either a `TRAEFIK_PROVIDERS_KUBERNETESINGRESS`: Enable Kubernetes backend with default settings. (Default: ```false```) +`TRAEFIK_PROVIDERS_KUBERNETESINGRESSNGINX`: +Enable Kubernetes Ingress NGINX provider. (Default: ```false```) + +`TRAEFIK_PROVIDERS_KUBERNETESINGRESSNGINX_CERTAUTHFILEPATH`: +Kubernetes certificate authority file path (not needed for in-cluster client). + +`TRAEFIK_PROVIDERS_KUBERNETESINGRESSNGINX_CONTROLLERCLASS`: +Ingress Class Controller value this controller satisfies. (Default: ```k8s.io/ingress-nginx```) + +`TRAEFIK_PROVIDERS_KUBERNETESINGRESSNGINX_DEFAULTBACKENDSERVICE`: +Service used to serve HTTP requests not matching any known server name (catch-all). Takes the form 'namespace/name'. + +`TRAEFIK_PROVIDERS_KUBERNETESINGRESSNGINX_DISABLESVCEXTERNALNAME`: +Disable support for Services of type ExternalName. (Default: ```false```) + +`TRAEFIK_PROVIDERS_KUBERNETESINGRESSNGINX_ENDPOINT`: +Kubernetes server endpoint (required for external cluster client). + +`TRAEFIK_PROVIDERS_KUBERNETESINGRESSNGINX_INGRESSCLASS`: +Name of the ingress class this controller satisfies. (Default: ```nginx```) + +`TRAEFIK_PROVIDERS_KUBERNETESINGRESSNGINX_INGRESSCLASSBYNAME`: +Define if Ingress Controller should watch for Ingress Class by Name together with Controller Class. (Default: ```false```) + +`TRAEFIK_PROVIDERS_KUBERNETESINGRESSNGINX_PUBLISHSERVICE`: +Service fronting the Ingress controller. Takes the form 'namespace/name'. + +`TRAEFIK_PROVIDERS_KUBERNETESINGRESSNGINX_PUBLISHSTATUSADDRESS`: +Customized address (or addresses, separated by comma) to set as the load-balancer status of Ingress objects this controller satisfies. + +`TRAEFIK_PROVIDERS_KUBERNETESINGRESSNGINX_THROTTLEDURATION`: +Ingress refresh throttle duration. (Default: ```0```) + +`TRAEFIK_PROVIDERS_KUBERNETESINGRESSNGINX_TOKEN`: +Kubernetes bearer token (not needed for in-cluster client). It accepts either a token value or a file path to the token. + +`TRAEFIK_PROVIDERS_KUBERNETESINGRESSNGINX_WATCHINGRESSWITHOUTCLASS`: +Define if Ingress Controller should also watch for Ingresses without an IngressClass or the annotation specified. (Default: ```false```) + +`TRAEFIK_PROVIDERS_KUBERNETESINGRESSNGINX_WATCHNAMESPACE`: +Namespace the controller watches for updates to Kubernetes objects. All namespaces are watched if this parameter is left empty. + +`TRAEFIK_PROVIDERS_KUBERNETESINGRESSNGINX_WATCHNAMESPACESELECTOR`: +Selector selects namespaces the controller watches for updates to Kubernetes objects. + `TRAEFIK_PROVIDERS_KUBERNETESINGRESS_ALLOWEMPTYSERVICES`: Allow creation of services without endpoints. (Default: ```false```) @@ -1017,6 +1107,9 @@ Kubernetes namespaces. `TRAEFIK_PROVIDERS_KUBERNETESINGRESS_NATIVELBBYDEFAULT`: Defines whether to use Native Kubernetes load-balancing mode by default. (Default: ```false```) +`TRAEFIK_PROVIDERS_KUBERNETESINGRESS_STRICTPREFIXMATCHING`: +Make prefix matching strictly comply with the Kubernetes Ingress specification (path-element-wise matching instead of character-by-character string matching). (Default: ```false```) + `TRAEFIK_PROVIDERS_KUBERNETESINGRESS_THROTTLEDURATION`: Ingress refresh throttle duration (Default: ```0```) @@ -1164,6 +1257,18 @@ Expose containers by default. (Default: ```true```) `TRAEFIK_PROVIDERS_SWARM_HTTPCLIENTTIMEOUT`: Client timeout for HTTP connections. (Default: ```0```) +`TRAEFIK_PROVIDERS_SWARM_LABELMAP`: +Label shorthands. + +`TRAEFIK_PROVIDERS_SWARM_LABELMAP_n_FROM`: +Shorthand label. + +`TRAEFIK_PROVIDERS_SWARM_LABELMAP_n_TO`: +Full label with templates. + +`TRAEFIK_PROVIDERS_SWARM_LABELMAP_n_VALUE`: +Optional override; used instead of user input if set. + `TRAEFIK_PROVIDERS_SWARM_NETWORK`: Default Docker network used. @@ -1339,4 +1444,4 @@ Query params to not redact. Sets the rate between 0.0 and 1.0 of requests to trace. (Default: ```1.000000```) `TRAEFIK_TRACING_SERVICENAME`: -Sets the name for this service. (Default: ```traefik```) +Defines the service name resource attribute. (Default: ```traefik```) diff --git a/docs/content/reference/static-configuration/env.md b/docs/content/reference/static-configuration/env.md deleted file mode 100644 index d1758fa18..000000000 --- a/docs/content/reference/static-configuration/env.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -title: "Traefik Environment Variables Documentation" -description: "Reference the environment variables for static configuration in Traefik Proxy. Read the technical documentation." ---- - -# Static Configuration: Environment variables - -!!! warning "Environment Variable Casing" - - Traefik normalizes the environment variable key-value pairs by lowercasing them. - This means that when you interpolate a string in an environment variable's name, - that string will be treated as lowercase, regardless of its original casing. - - For example, assuming you have set environment variables as follows: - - ```bash - export TRAEFIK_ENTRYPOINTS_WEB=true - export TRAEFIK_ENTRYPOINTS_WEB_ADDRESS=:80 - - export TRAEFIK_CERTIFICATESRESOLVERS_myResolver=true - export TRAEFIK_CERTIFICATESRESOLVERS_myResolver_ACME_CASERVER=.... - ``` - - Although the Entrypoint is named `WEB` and the Certificate Resolver is named `myResolver`, - they have to be referenced respectively as `web`, and `myresolver` in the configuration. - ---8<-- "content/reference/static-configuration/env-ref.md" diff --git a/docs/content/reference/static-configuration/file.md b/docs/content/reference/static-configuration/file.md deleted file mode 100644 index c2f3174db..000000000 --- a/docs/content/reference/static-configuration/file.md +++ /dev/null @@ -1,14 +0,0 @@ ---- -title: "Traefik File Static Configuration" -description: "Reference the YAML and TOML files for static configuration in Traefik Proxy. Read the technical documentation." ---- - -# Static Configuration: File - -```yml tab="YAML" ---8<-- "content/reference/static-configuration/file.yaml" -``` - -```toml tab="TOML" ---8<-- "content/reference/static-configuration/file.toml" -``` diff --git a/docs/content/reference/static-configuration/file.toml b/docs/content/reference/static-configuration/file.toml index e548ee76d..7d22522e1 100644 --- a/docs/content/reference/static-configuration/file.toml +++ b/docs/content/reference/static-configuration/file.toml @@ -3,6 +3,7 @@ [global] checkNewVersion = true sendAnonymousUsage = true + updaterCallbacks = ["foobar", "foobar"] [serversTransport] insecureSkipVerify = true @@ -80,8 +81,9 @@ timeout = "42s" [entryPoints.EntryPoint0.observability] accessLogs = true - tracing = true metrics = true + tracing = true + traceVerbosity = "foobar" [providers] providersThrottleDuration = "42s" @@ -97,6 +99,16 @@ password = "foobar" endpoint = "foobar" httpClientTimeout = "42s" + + [[providers.docker.labelMap]] + from = "foobar" + to = "foobar" + value = "foobar" + + [[providers.docker.labelMap]] + from = "foobar" + to = "foobar" + value = "foobar" [providers.docker.tls] ca = "foobar" cert = "foobar" @@ -115,6 +127,16 @@ endpoint = "foobar" httpClientTimeout = "42s" refreshSeconds = "42s" + + [[providers.swarm.labelMap]] + from = "foobar" + to = "foobar" + value = "foobar" + + [[providers.swarm.labelMap]] + from = "foobar" + to = "foobar" + value = "foobar" [providers.swarm.tls] ca = "foobar" cert = "foobar" @@ -138,10 +160,26 @@ disableIngressClassLookup = true disableClusterScopeResources = true nativeLBByDefault = true + strictPrefixMatching = true [providers.kubernetesIngress.ingressEndpoint] ip = "foobar" hostname = "foobar" publishedService = "foobar" + [providers.kubernetesIngressNGINX] + endpoint = "foobar" + token = "foobar" + certAuthFilePath = "foobar" + throttleDuration = "42s" + watchNamespace = "foobar" + watchNamespaceSelector = "foobar" + ingressClass = "foobar" + controllerClass = "foobar" + watchIngressWithoutClass = true + ingressClassByName = true + publishService = "foobar" + publishStatusAddress = ["foobar", "foobar"] + defaultBackendService = "foobar" + disableSvcExternalName = true [providers.kubernetesCRD] endpoint = "foobar" token = "foobar" @@ -371,6 +409,9 @@ [metrics.otlp.http.headers] name0 = "foobar" name1 = "foobar" + [metrics.otlp.resourceAttributes] + name0 = "foobar" + name1 = "foobar" [ping] entryPoint = "foobar" @@ -511,6 +552,8 @@ storage = "foobar" keyType = "foobar" certificatesDuration = 42 + clientTimeout = "42s" + clientResponseHeaderTimeout = "42s" caCertificates = ["foobar", "foobar"] caSystemCertPool = true caServerName = "foobar" @@ -529,6 +572,7 @@ delayBeforeChecks = "42s" [certificatesResolvers.CertificateResolver0.acme.httpChallenge] entryPoint = "foobar" + delay = "42s" [certificatesResolvers.CertificateResolver0.acme.tlsChallenge] [certificatesResolvers.CertificateResolver0.tailscale] [certificatesResolvers.CertificateResolver1] @@ -541,6 +585,8 @@ storage = "foobar" keyType = "foobar" certificatesDuration = 42 + clientTimeout = "42s" + clientResponseHeaderTimeout = "42s" caCertificates = ["foobar", "foobar"] caSystemCertPool = true caServerName = "foobar" @@ -559,12 +605,14 @@ delayBeforeChecks = "42s" [certificatesResolvers.CertificateResolver1.acme.httpChallenge] entryPoint = "foobar" + delay = "42s" [certificatesResolvers.CertificateResolver1.acme.tlsChallenge] [certificatesResolvers.CertificateResolver1.tailscale] [experimental] abortOnPluginFailure = true otlplogs = true + kubernetesIngressNGINX = true kubernetesGateway = true [experimental.plugins] [experimental.plugins.Descriptor0] @@ -573,23 +621,27 @@ [experimental.plugins.Descriptor0.settings] envs = ["foobar", "foobar"] mounts = ["foobar", "foobar"] + useUnsafe = true [experimental.plugins.Descriptor1] moduleName = "foobar" version = "foobar" [experimental.plugins.Descriptor1.settings] envs = ["foobar", "foobar"] mounts = ["foobar", "foobar"] + useUnsafe = true [experimental.localPlugins] [experimental.localPlugins.LocalDescriptor0] moduleName = "foobar" [experimental.localPlugins.LocalDescriptor0.settings] envs = ["foobar", "foobar"] mounts = ["foobar", "foobar"] + useUnsafe = true [experimental.localPlugins.LocalDescriptor1] moduleName = "foobar" [experimental.localPlugins.LocalDescriptor1.settings] envs = ["foobar", "foobar"] mounts = ["foobar", "foobar"] + useUnsafe = true [experimental.fastProxy] debug = true @@ -598,3 +650,8 @@ [spiffe] workloadAPIAddr = "foobar" + +[ocsp] + [ocsp.responderOverrides] + name0 = "foobar" + name1 = "foobar" diff --git a/docs/content/reference/static-configuration/file.yaml b/docs/content/reference/static-configuration/file.yaml index 760802591..64aae401b 100644 --- a/docs/content/reference/static-configuration/file.yaml +++ b/docs/content/reference/static-configuration/file.yaml @@ -3,6 +3,9 @@ global: checkNewVersion: true sendAnonymousUsage: true + updaterCallbacks: + - foobar + - foobar serversTransport: insecureSkipVerify: true rootCAs: @@ -94,8 +97,9 @@ entryPoints: timeout: 42s observability: accessLogs: true - tracing: true metrics: true + tracing: true + traceVerbosity: foobar providers: providersThrottleDuration: 42s docker: @@ -106,6 +110,13 @@ providers: useBindPortIP: true watch: true defaultRule: foobar + labelMap: + - from: foobar + to: foobar + value: foobar + - from: foobar + to: foobar + value: foobar username: foobar password: foobar endpoint: foobar @@ -123,6 +134,13 @@ providers: useBindPortIP: true watch: true defaultRule: foobar + labelMap: + - from: foobar + to: foobar + value: foobar + - from: foobar + to: foobar + value: foobar username: foobar password: foobar endpoint: foobar @@ -157,6 +175,24 @@ providers: disableIngressClassLookup: true disableClusterScopeResources: true nativeLBByDefault: true + strictPrefixMatching: true + kubernetesIngressNGINX: + endpoint: foobar + token: foobar + certAuthFilePath: foobar + throttleDuration: 42s + watchNamespace: foobar + watchNamespaceSelector: foobar + ingressClass: foobar + controllerClass: foobar + watchIngressWithoutClass: true + ingressClassByName: true + publishService: foobar + publishStatusAddress: + - foobar + - foobar + defaultBackendService: foobar + disableSvcExternalName: true kubernetesCRD: endpoint: foobar token: foobar @@ -410,6 +446,9 @@ metrics: - 42 pushInterval: 42s serviceName: foobar + resourceAttributes: + name0: foobar + name1: foobar ping: entryPoint: foobar manualRouting: true @@ -557,6 +596,8 @@ certificatesResolvers: kid: foobar hmacEncoded: foobar certificatesDuration: 42 + clientTimeout: 42s + clientResponseHeaderTimeout: 42s caCertificates: - foobar - foobar @@ -576,6 +617,7 @@ certificatesResolvers: disablePropagationCheck: true httpChallenge: entryPoint: foobar + delay: 42s tlsChallenge: {} tailscale: {} CertificateResolver1: @@ -593,6 +635,8 @@ certificatesResolvers: kid: foobar hmacEncoded: foobar certificatesDuration: 42 + clientTimeout: 42s + clientResponseHeaderTimeout: 42s caCertificates: - foobar - foobar @@ -612,6 +656,7 @@ certificatesResolvers: disablePropagationCheck: true httpChallenge: entryPoint: foobar + delay: 42s tlsChallenge: {} tailscale: {} experimental: @@ -626,6 +671,7 @@ experimental: mounts: - foobar - foobar + useUnsafe: true Descriptor1: moduleName: foobar version: foobar @@ -636,6 +682,7 @@ experimental: mounts: - foobar - foobar + useUnsafe: true localPlugins: LocalDescriptor0: moduleName: foobar @@ -646,6 +693,7 @@ experimental: mounts: - foobar - foobar + useUnsafe: true LocalDescriptor1: moduleName: foobar settings: @@ -655,12 +703,18 @@ experimental: mounts: - foobar - foobar + useUnsafe: true abortOnPluginFailure: true fastProxy: debug: true otlplogs: true + kubernetesIngressNGINX: true kubernetesGateway: true core: defaultRuleSyntax: foobar spiffe: workloadAPIAddr: foobar +ocsp: + responderOverrides: + name0: foobar + name1: foobar diff --git a/docs/content/reference/static-configuration/overview.md b/docs/content/reference/static-configuration/overview.md deleted file mode 100644 index 853ab0a97..000000000 --- a/docs/content/reference/static-configuration/overview.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -title: "Traefik Static Configuration Overview" -description: "Read the official Traefik documentation to get started with static configuration in Traefik Proxy." ---- - -# Static Configuration - -- [File](./file.md) -- [CLI](./cli.md) -- [Environment Variables](./env.md) diff --git a/docs/content/routing/overview.md b/docs/content/routing/overview.md index e65bea351..dd1d0342f 100644 --- a/docs/content/routing/overview.md +++ b/docs/content/routing/overview.md @@ -10,8 +10,8 @@ What's Happening to the Requests? Let's zoom in on Traefik's architecture and talk about the components that enable the routes to be created. -First, when you start Traefik, you define [entrypoints](../entrypoints) (in their most basic forms, they are port numbers). -Then, connected to these entrypoints, [routers](../routers) analyze the incoming requests to see if they match a set of [rules](../routers#rule). +First, when you start Traefik, you define [entrypoints](../entrypoints/) (in their most basic forms, they are port numbers). +Then, connected to these entrypoints, [routers](../routers/) analyze the incoming requests to see if they match a set of [rules](../routers/#rule). If they do, the router might transform the request using pieces of [middleware](../middlewares/overview.md) before forwarding them to your [services](./services/index.md). ![Architecture](../assets/img/architecture-overview.png) @@ -246,7 +246,7 @@ http: Most of what happens to the connection between the clients and Traefik, and then between Traefik and the backend servers, is configured through the -[entrypoints](../entrypoints) and the [routers](../routers). +[entrypoints](../entrypoints/) and the [routers](../routers/). In addition, a few parameters are dedicated to configuring globally what happens with the connections between Traefik and the backends. diff --git a/docs/content/routing/providers/consul-catalog.md b/docs/content/routing/providers/consul-catalog.md index 76b326b55..26c222875 100644 --- a/docs/content/routing/providers/consul-catalog.md +++ b/docs/content/routing/providers/consul-catalog.md @@ -25,7 +25,7 @@ With Consul Catalog, Traefik can leverage tags attached to a service to generate !!! info "tags" - tags are case-insensitive. - - The complete list of tags can be found [the reference page](../../reference/dynamic-configuration/consul-catalog.md) + - The complete list of tags can be found [the reference page](../../reference/routing-configuration/other-providers/consul-catalog.md) ### General @@ -218,6 +218,14 @@ you'd add the tag `traefik.http.services.{name-of-your-choice}.loadbalancer.pass traefik.http.services.myservice.loadbalancer.healthcheck.interval=10 ``` +??? info "`traefik.http.services..loadbalancer.healthcheck.unhealthyinterval`" + + See [health check](../services/index.md#health-check) for more information. + + ```yaml + traefik.http.services.myservice.loadbalancer.healthcheck.unhealthyinterval=10 + ``` + ??? info "`traefik.http.services..loadbalancer.healthcheck.path`" See [health check](../services/index.md#health-check) for more information. diff --git a/docs/content/routing/providers/docker.md b/docs/content/routing/providers/docker.md index aedbfe09f..c47bc6c60 100644 --- a/docs/content/routing/providers/docker.md +++ b/docs/content/routing/providers/docker.md @@ -42,7 +42,6 @@ With Docker, Traefik can leverage labels attached to a container to generate rou Attaching labels to containers (in your docker compose file) ```yaml - version: "3" services: my-container: # ... @@ -55,7 +54,6 @@ With Docker, Traefik can leverage labels attached to a container to generate rou Forward requests for `http://example.com` to `http://:12345`: ```yaml - version: "3" services: my-container: # ... @@ -78,7 +76,6 @@ With Docker, Traefik can leverage labels attached to a container to generate rou In this example, requests are forwarded for `http://example-a.com` to `http://:8000` in addition to `http://example-b.com` forwarding to `http://:9000`: ```yaml - version: "3" services: my-container: # ... @@ -96,7 +93,7 @@ With Docker, Traefik can leverage labels attached to a container to generate rou !!! info "Labels" - Labels are case-insensitive. - - The complete list of labels can be found in [the reference page](../../reference/dynamic-configuration/docker.md). + - The complete list of labels can be found in [the reference page](../../reference/routing-configuration/other-providers/docker.md). ### General @@ -333,6 +330,14 @@ you'd add the label `traefik.http.services..loadbalancer.pa - "traefik.http.services.myservice.loadbalancer.healthcheck.interval=10s" ``` +??? info "`traefik.http.services..loadbalancer.healthcheck.unhealthyinterval`" + + See [health check](../services/index.md#health-check) for more information. + + ```yaml + - "traefik.http.services.myservice.loadbalancer.healthcheck.unhealthyinterval=10s" + ``` + ??? info "`traefik.http.services..loadbalancer.healthcheck.path`" See [health check](../services/index.md#health-check) for more information. diff --git a/docs/content/routing/providers/ecs.md b/docs/content/routing/providers/ecs.md index e85975409..4068fdb78 100644 --- a/docs/content/routing/providers/ecs.md +++ b/docs/content/routing/providers/ecs.md @@ -23,7 +23,7 @@ With ECS, Traefik can leverage labels attached to a container to generate routin !!! info "labels" - labels are case-insensitive. - - The complete list of labels can be found in [the reference page](../../reference/dynamic-configuration/ecs.md). + - The complete list of labels can be found in [the reference page](../../reference/routing-configuration/other-providers/ecs.md). ### General @@ -220,6 +220,14 @@ you'd add the label `traefik.http.services.{name-of-your-choice}.loadbalancer.pa traefik.http.services.myservice.loadbalancer.healthcheck.interval=10 ``` +??? info "`traefik.http.services..loadbalancer.healthcheck.unhealthyinterval`" + + See [health check](../services/index.md#health-check) for more information. + + ```yaml + traefik.http.services.myservice.loadbalancer.healthcheck.unhealthyinterval=10 + ``` + ??? info "`traefik.http.services..loadbalancer.healthcheck.path`" See [health check](../services/index.md#health-check) for more information. diff --git a/docs/content/routing/providers/kubernetes-crd.md b/docs/content/routing/providers/kubernetes-crd.md index d19c4c66c..095691e14 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:v3.4 + image: traefik:v3.5 args: - --log.level=DEBUG - --api @@ -392,7 +392,7 @@ Register the `IngressRoute` [kind](../../reference/dynamic-configuration/kuberne | [13] | `services[n].port` | Defines the port of a [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/). This can be a reference to a named port. | | [14] | `services[n].serversTransport` | Defines the reference to a [ServersTransport](#kind-serverstransport). The ServersTransport namespace is assumed to be the [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) namespace (see [ServersTransport reference](#serverstransport-reference)). | | [15] | `services[n].healthCheck` | Defines the HealthCheck when service references a [Kubernetes service](https://kubernetes.io/docs/concepts/services-networking/service/) of type ExternalName. | -| [16] | `services[n].strategy` | Defines the load-balancing strategy for the load-balancer. Supported values are `wrr` and `p2c`, please refer to the [Load Balancing documentation](../routing/services/#load-balancing-strategy) for more information. | +| [16] | `services[n].strategy` | Defines the load-balancing strategy for the load-balancer. Supported values are `wrr` and `p2c`, please refer to the [Load Balancing documentation](../../services/#load-balancing-strategy) for more information. | | [17] | `services[n].nativeLB` | Controls, when creating the load-balancer, whether the LB's children are directly the pods IPs or if the only child is the Kubernetes Service clusterIP. | | [18] | `services[n].nodePortLB` | Controls, when creating the load-balancer, whether the LB's children are directly the nodes internal IPs using the nodePort when the service type is NodePort. | | [19] | `tls` | Defines [TLS](../routers/index.md#tls) certificate configuration | @@ -1671,7 +1671,7 @@ or referencing TLS options in the [`IngressRoute`](#kind-ingressroute) / [`Ingre | [2] | `minVersion` | Defines the [minimum TLS version](../../https/tls.md#minimum-tls-version) that is acceptable. | | [3] | `maxVersion` | Defines the [maximum TLS version](../../https/tls.md#maximum-tls-version) that is acceptable. | | [4] | `cipherSuites` | list of supported [cipher suites](../../https/tls.md#cipher-suites) for TLS versions up to TLS 1.2. | -| [5] | `curvePreferences` | List of the [elliptic curves references](../../https/tls.md#curve-preferences) that will be used in an ECDHE handshake, in preference order. | +| [5] | `curvePreferences` | List of the [elliptic curves references](../../https/tls.md#curve-preferences) that will be used in an ECDHE handshake. | | [6] | `clientAuth` | determines the server's policy for TLS [Client Authentication](../../https/tls.md#client-authentication-mtls). | | [7] | `clientAuth.secretNames` | list of names of the referenced Kubernetes [Secrets](https://kubernetes.io/docs/concepts/configuration/secret/) (in TLSOption namespace). The secret must contain a certificate under either a `tls.ca` or a `ca.crt` key. | | [8] | `clientAuth.clientAuthType` | defines the client authentication type to apply. The available values are: `NoClientCert`, `RequestClientCert`, `VerifyClientCertIfGiven` and `RequireAndVerifyClientCert`. | diff --git a/docs/content/routing/providers/kubernetes-gateway.md b/docs/content/routing/providers/kubernetes-gateway.md index 3137db830..f658ca4ba 100644 --- a/docs/content/routing/providers/kubernetes-gateway.md +++ b/docs/content/routing/providers/kubernetes-gateway.md @@ -8,11 +8,11 @@ description: "The Kubernetes Gateway API can be used as a provider for routing a When using the Kubernetes Gateway API provider, Traefik leverages the Gateway API Custom Resource Definitions (CRDs) to obtain its routing configuration. For detailed information on the Gateway API concepts and resources, refer to the official [documentation](https://gateway-api.sigs.k8s.io/). -The Kubernetes Gateway API provider supports version [v1.2.1](https://github.com/kubernetes-sigs/gateway-api/releases/tag/v1.2.1) of the specification. +The Kubernetes Gateway API provider supports version [v1.3.0](https://github.com/kubernetes-sigs/gateway-api/releases/tag/v1.3.0) of the specification. It fully supports all `HTTPRoute` core and some extended features, like `GRPCRoute`, as well as the `TCPRoute` and `TLSRoute` resources from the [Experimental channel](https://gateway-api.sigs.k8s.io/concepts/versioning/?h=#release-channels). -For more details, check out the conformance [report](https://github.com/kubernetes-sigs/gateway-api/tree/main/conformance/reports/v1.2.1/traefik-traefik). +For more details, check out the conformance [report](https://github.com/kubernetes-sigs/gateway-api/tree/main/conformance/reports/v1.3.0/traefik-traefik). ## Deploying a Gateway diff --git a/docs/content/routing/providers/kubernetes-ingress.md b/docs/content/routing/providers/kubernetes-ingress.md index 5a82d8433..ff02ae929 100644 --- a/docs/content/routing/providers/kubernetes-ingress.md +++ b/docs/content/routing/providers/kubernetes-ingress.md @@ -130,7 +130,7 @@ which in turn will create the resulting routers, services, handlers, etc. serviceAccountName: traefik-ingress-controller containers: - name: traefik - image: traefik:v3.4 + image: traefik:v3.5 args: - --entryPoints.web.address=:80 - --providers.kubernetesingress @@ -439,7 +439,7 @@ If the Kubernetes cluster version is 1.18+, the new `pathType` property can be leveraged to define the rules matchers: - `Exact`: This path type forces the rule matcher to `Path` -- `Prefix`: This path type forces the rule matcher to `PathPrefix` +- `Prefix`: This path type forces the rule matcher to `PathPrefix`. Note that if you want the matching behavior to strictly comply with Kubernetes Ingress specification (request path is matched on an element-by-element basis), consider enabling [`strictPrefixMatching`](../../providers/kubernetes-ingress.md#strictprefixmatching) in the Ingress Provider configuration. Please see [this documentation](https://kubernetes.io/docs/concepts/services-networking/ingress/#path-types) for more information. @@ -593,7 +593,7 @@ This way, any Ingress attached to this Entrypoint will have TLS termination by d serviceAccountName: traefik-ingress-controller containers: - name: traefik - image: traefik:v3.4 + image: traefik:v3.5 args: - --entryPoints.websecure.address=:443 - --entryPoints.websecure.http.tls @@ -786,7 +786,7 @@ For more options, please refer to the available [annotations](#on-ingress). serviceAccountName: traefik-ingress-controller containers: - name: traefik - image: traefik:v3.4 + image: traefik:v3.5 args: - --entryPoints.websecure.address=:443 - --providers.kubernetesingress @@ -910,7 +910,7 @@ TLS certificates can be managed in Secrets objects. whether the LB's children are directly the pods IPs or if the only child is the Kubernetes Service clusterIP. One alternative is to use an `ExternalName` service to forward requests to the Kubernetes service through DNS. - To do so, one must [allow external name services](../providers/kubernetes-ingress/#allowexternalnameservices "Link to docs about allowing external name services"). + To do so, one must [allow external name services](../../../providers/kubernetes-ingress/#allowexternalnameservices "Link to docs about allowing external name services"). Traefik automatically requests endpoint information based on the service provided in the ingress spec. Although Traefik will connect directly to the endpoints (pods), diff --git a/docs/content/routing/providers/kv.md b/docs/content/routing/providers/kv.md index 37e6148f5..a5a5b807b 100644 --- a/docs/content/routing/providers/kv.md +++ b/docs/content/routing/providers/kv.md @@ -13,7 +13,7 @@ A Story of key & values !!! info "Keys" - Keys are case-insensitive. - - The complete list of keys can be found in [the reference page](../../reference/dynamic-configuration/kv.md). + - The complete list of keys can be found in [the reference page](../../reference/routing-configuration/other-providers/kv.md). ### Routers @@ -180,6 +180,14 @@ A Story of key & values |---------------------------------------------------------------------|-------| | `traefik/http/services/myservice/loadbalancer/healthcheck/interval` | `10` | +??? info "`traefik/http/services//loadbalancer/healthcheck/unhealthyinterval`" + + See [health check](../services/index.md#health-check) for more information. + + | Key (Path) | Value | + |------------------------------------------------------------------------------|-------| + | `traefik/http/services/myservice/loadbalancer/healthcheck/unhealthyinterval` | `10` | + ??? info "`traefik/http/services//loadbalancer/healthcheck/path`" See [health check](../services/index.md#health-check) for more information. diff --git a/docs/content/routing/providers/nomad.md b/docs/content/routing/providers/nomad.md index 613f35a7b..4ed3e8c2f 100644 --- a/docs/content/routing/providers/nomad.md +++ b/docs/content/routing/providers/nomad.md @@ -25,7 +25,7 @@ With Nomad, Traefik can leverage tags attached to a service to generate routing !!! info "tags" - tags are case-insensitive. - - The complete list of tags can be found [the reference page](../../reference/dynamic-configuration/nomad.md) + - The complete list of tags can be found [the reference page](../../reference/routing-configuration/other-providers/nomad.md) ### General @@ -218,6 +218,14 @@ you'd add the tag `traefik.http.services.{name-of-your-choice}.loadbalancer.pass traefik.http.services.myservice.loadbalancer.healthcheck.interval=10 ``` +??? info "`traefik.http.services..loadbalancer.healthcheck.unhealthyinterval`" + + See [health check](../services/index.md#health-check) for more information. + + ```yaml + traefik.http.services.myservice.loadbalancer.healthcheck.unhealthyinterval=10 + ``` + ??? info "`traefik.http.services..loadbalancer.healthcheck.path`" See [health check](../services/index.md#health-check) for more information. diff --git a/docs/content/routing/providers/swarm.md b/docs/content/routing/providers/swarm.md index bd9685ef6..c628e1b4e 100644 --- a/docs/content/routing/providers/swarm.md +++ b/docs/content/routing/providers/swarm.md @@ -55,7 +55,6 @@ With Docker Swarm, Traefik can leverage labels attached to a service to generate then that service is automatically assigned to the router. ```yaml - version: "3" services: my-container: deploy: @@ -74,7 +73,6 @@ With Docker Swarm, Traefik can leverage labels attached to a service to generate Forward requests for `http://example.com` to `http://:12345`: ```yaml - version: "3" services: my-container: # ... @@ -100,7 +98,6 @@ With Docker Swarm, Traefik can leverage labels attached to a service to generate In this example, requests are forwarded for `http://example-a.com` to `http://:8000` in addition to `http://example-b.com` forwarding to `http://:9000`: ```yaml - version: "3" services: my-container: # ... @@ -119,7 +116,7 @@ With Docker Swarm, Traefik can leverage labels attached to a service to generate !!! info "Labels" - Labels are case-insensitive. - - The complete list of labels can be found in [the reference page](../../reference/dynamic-configuration/docker.md). + - The complete list of labels can be found in [the reference page](../../reference/routing-configuration/other-providers/swarm.md). ### General @@ -347,6 +344,14 @@ you'd add the label `traefik.http.services..loadbalancer.pa - "traefik.http.services.myservice.loadbalancer.healthcheck.interval=10s" ``` +??? info "`traefik.http.services..loadbalancer.healthcheck.unhealthyinterval`" + + See [health check](../services/index.md#health-check) for more information. + + ```yaml + - "traefik.http.services.myservice.loadbalancer.healthcheck.unhealthyinterval=10s" + ``` + ??? info "`traefik.http.services..loadbalancer.healthcheck.path`" See [health check](../services/index.md#health-check) for more information. diff --git a/docs/content/routing/routers/index.md b/docs/content/routing/routers/index.md index ef61d56b1..06cd3f05b 100644 --- a/docs/content/routing/routers/index.md +++ b/docs/content/routing/routers/index.md @@ -527,7 +527,7 @@ A value of `0` for the priority is ignored: `priority = 0` means that the defaul _Optional, Default=""_ -In Traefik v3 a new rule syntax has been introduced ([migration guide](../../migration/v2-to-v3.md#router-rule-matchers)). +In Traefik v3 a new rule syntax has been introduced ([migration guide](../../migrate/v2-to-v3.md#router-rule-matchers)). `ruleSyntax` option allows to configure the rule syntax to be used for parsing the rule on a per-router basis. This allows to have heterogeneous router configurations and ease migration. @@ -1351,7 +1351,7 @@ A value of `0` for the priority is ignored: `priority = 0` means that the defaul _Optional, Default=""_ -In Traefik v3 a new rule syntax has been introduced ([migration guide](../../migration/v2-to-v3.md#router-rule-matchers)). +In Traefik v3 a new rule syntax has been introduced ([migration guide](../../migrate/v2-to-v3.md#router-rule-matchers)). `ruleSyntax` option allows to configure the rule syntax to be used for parsing the rule on a per-router basis. This allows to have heterogeneous router configurations and ease migration. diff --git a/docs/content/routing/services/index.md b/docs/content/routing/services/index.md index 199fb81a3..3a4cc5c0a 100644 --- a/docs/content/routing/services/index.md +++ b/docs/content/routing/services/index.md @@ -415,7 +415,8 @@ Below are the available options for the health check mechanism: - `mode` (default: http), if defined to `grpc`, will use the gRPC health check protocol to probe the server. - `hostname` (optional), sets the value of `hostname` in the `Host` header of the health check request. - `port` (optional), replaces the server URL `port` for the health check endpoint. -- `interval` (default: 30s), defines the frequency of the health check calls. +- `interval` (default: 30s), defines the frequency of the health check calls for healthy targets. +- `unhealthyInterval` (default: 30s), defines the frequency of the health check calls for unhealthy targets. When not defined, it defaults to the `interval` value. - `timeout` (default: 5s), defines the maximum duration Traefik will wait for a health check request before considering the server unhealthy. - `headers` (optional), defines custom headers to be sent to the health check endpoint. - `followRedirects` (default: true), defines whether redirects should be followed during the health check calls. @@ -424,7 +425,7 @@ Below are the available options for the health check mechanism: !!! info "Interval & Timeout Format" - Interval and timeout are to be given in a format understood by [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration). + Interval, UnhealthyInterval and Timeout are to be given in a format understood by [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration). !!! info "Recovering Servers" @@ -1259,6 +1260,10 @@ Please note that by default the whole request is buffered in memory while it is See the maxBodySize option in the example below for how to modify this behaviour. You can also omit the request body by setting the mirrorBody option to `false`. +!!! warning "Default behavior of `percent`" + + When configuring a `mirror` service, if the `percent` field is not set, it defaults to `0`, meaning **no traffic will be sent to the mirror**. + !!! info "Supported Providers" This strategy can be defined currently with the [File](../../providers/file.md) or [IngressRoute](../../providers/kubernetes-crd.md) providers. @@ -1279,6 +1284,8 @@ http: maxBodySize: 1024 mirrors: - name: appv2 + # Percent defines the percentage of requests that should be mirrored. + # Default value is 0, which means no traffic will be sent to the mirror. percent: 10 appv1: @@ -1641,79 +1648,6 @@ The `tls` determines whether to use TLS when dialing with the backend. If no serversTransport is specified, the `default@internal` will be used. The `default@internal` serversTransport is created from the [static configuration](../overview.md#tcp-servers-transports). -#### PROXY Protocol - -Traefik supports [PROXY Protocol](https://www.haproxy.org/download/2.0/doc/proxy-protocol.txt) version 1 and 2 on TCP Services. -It can be enabled by setting `proxyProtocol` on the load balancer. - -Below are the available options for the PROXY protocol: - -- `version` specifies the version of the protocol to be used. Either `1` or `2`. - -!!! info "Version" - - Specifying a version is optional. By default the version 2 will be used. - -??? example "A Service with Proxy Protocol v1 -- Using the [File Provider](../../providers/file.md)" - - ```yaml tab="YAML" - ## Dynamic configuration - tcp: - services: - my-service: - loadBalancer: - proxyProtocol: - version: 1 - ``` - - ```toml tab="TOML" - ## Dynamic configuration - [tcp.services] - [tcp.services.my-service.loadBalancer] - [tcp.services.my-service.loadBalancer.proxyProtocol] - version = 1 - ``` - -#### Termination Delay - -!!! warning - - Deprecated in favor of [`serversTransport.terminationDelay`](#terminationdelay). - Please note that if any `serversTransport` configuration on the servers load balancer is found, - it will take precedence over the servers load balancer `terminationDelay` value, - even if the `serversTransport.terminationDelay` is undefined. - -As a proxy between a client and a server, it can happen that either side (e.g. client side) decides to terminate its writing capability on the connection (i.e. issuance of a FIN packet). -The proxy needs to propagate that intent to the other side, and so when that happens, it also does the same on its connection with the other side (e.g. backend side). - -However, if for some reason (bad implementation, or malicious intent) the other side does not eventually do the same as well, -the connection would stay half-open, which would lock resources for however long. - -To that end, as soon as the proxy enters this termination sequence, it sets a deadline on fully terminating the connections on both sides. - -The termination delay controls that deadline. -It is a duration in milliseconds, defaulting to 100. -A negative value means an infinite deadline (i.e. the connection is never fully terminated by the proxy itself). - -??? example "A Service with a termination delay -- Using the [File Provider](../../providers/file.md)" - - ```yaml tab="YAML" - ## Dynamic configuration - tcp: - services: - my-service: - loadBalancer: - terminationDelay: 200 - ``` - - ```toml tab="TOML" - ## Dynamic configuration - [tcp.services] - [tcp.services.my-service.loadBalancer] - [[tcp.services.my-service.loadBalancer]] - terminationDelay = 200 - ``` - ### Weighted Round Robin The Weighted Round Robin (alias `WRR`) load-balancer of services is in charge of balancing the requests between multiple services based on provided weights. diff --git a/docs/content/secure/secure-api-access-with-jwt.md b/docs/content/secure/secure-api-access-with-jwt.md new file mode 100644 index 000000000..5e853a637 --- /dev/null +++ b/docs/content/secure/secure-api-access-with-jwt.md @@ -0,0 +1,204 @@ +--- +title: 'Secure API Access with JWT' +description: 'Traefik Hub API Gateway - Learn how to configure the JWT Authentication middleware for Ingress management.' +--- + +# Secure API Access with JWT + +!!! info "Traefik Hub Feature" + This middleware is available exclusively in [Traefik Hub](https://traefik.io/traefik-hub/). Learn more about [Traefik Hub's advanced features](https://doc.traefik.io/traefik-hub/api-gateway/intro). + +JSON Web Token (JWT) (defined in the [RFC 7519](https://tools.ietf.org/html/rfc7519)) allows +Traefik Hub API Gateway to secure the API access using a token signed using either a private signing secret or a plublic/private key. + +Traefik Hub API Gateway provides many kinds of sources to perform the token validation: + +- Setting a secret value in the middleware configuration (option `signingSecret`). +- Setting a public key: In that case, users should sign their token using a private key, and the public key can be used to verify the signature (option `publicKey`). +- Setting a [JSON Web Key (JWK)](https://datatracker.ietf.org/doc/html/rfc7517) file to define a set of JWK to be used to verify the signature of the incoming JWT (option `jwksFile`). +- Setting a [JSON Web Key (JWK)](https://datatracker.ietf.org/doc/html/rfc7517) URL to define the URL of the host serving a JWK set (option `jwksUrl`). + +!!! note "One single source" + The JWT middleware does not allow you to set more than one way to validate the incoming tokens. + When a Hub API Gateway receives a request that must be validated using the JWT middleware, it verifies the token using the source configured as described above. + If the token is successfully checked, the request is accepted. + +!!! note "Claim Usage" + A JWT can contain metadata in the form of claims (key-value pairs). + The claims contained in the JWT can be used for advanced use-cases such as adding an Authorization layer using the `claims`. + + More information in the [dedicated section](../reference/routing-configuration/http/middlewares/jwt.md#claims). + +## Verify a JWT with a secret + +To allow the Traefik Hub API Gateway to validate a JWT with a secret value stored in a Kubernetes Secret, apply the following configuration: + +```yaml tab="Middleware JWT" +apiVersion: traefik.io/v1alpha1 +kind: Middleware +metadata: + name: test-jwt + namespace: apps +spec: + plugin: + jwt: + signingSecret: "urn:k8s:secret:jwt:signingSecret" +``` + +```yaml tab="Kubernetes Secret" +apiVersion: v1 +kind: Secret +metadata: + name: jwt + namespace: apps +stringData: + signingSecret: mysuperlongsecret +``` + +```yaml tab="IngressRoute" +apiVersion: traefik.io/v1alpha1 +kind: IngressRoute +metadata: + name: my-app + namespace: apps +spec: + entryPoints: + - websecure + routes: + - match: Path(`/my-app`) + kind: Rule + services: + - name: whoami + port: 80 + middlewares: + - name: test-jwt +``` + +```yaml tab="Service & Deployment" +kind: Deployment +apiVersion: apps/v1 +metadata: + name: whoami + namespace: apps +spec: + replicas: 3 + selector: + matchLabels: + app: whoami + template: + metadata: + labels: + app: whoami + spec: + containers: + - name: whoami + image: traefik/whoami + +--- +apiVersion: v1 +kind: Service +metadata: + name: whoami + namespace: apps +spec: + ports: + - port: 80 + name: whoami + selector: + app: whoami +``` + +## Verify a JWT using an Identity Provider + +To allow the Traefik Hub API Gateway to validate a JWT using an Identity Provider, such as Keycloak and Azure AD in the examples below, apply the following configuration: + +```yaml tab="JWKS with Keycloak URL" +apiVersion: traefik.io/v1alpha1 +kind: Middleware +metadata: + name: test-jwt + namespace: apps +spec: + plugin: + jwt: + # Replace KEYCLOAK_URL and REALM_NAME with your values + jwksUrl: https://KEYCLOAK_URL/realms/REALM_NAME/protocol/openid-connect/certs + # Forward the content of the claim grp in the header Group + forwardHeaders: + Group: grp + # Check the value of the claim grp before sending the request to the backend + claims: Equals(`grp`, `admin`) +``` + +```yaml tab="JWKS with Azure AD URL" +apiVersion: traefik.io/v1alpha1 +kind: Middleware +metadata: + name: test-jwt + namespace: apps +spec: + plugin: + jwt: + jwksUrl: https://login.microsoftonline.com/common/discovery/v2.0/keys +``` + +```yaml tab="IngressRoute" +apiVersion: traefik.io/v1alpha1 +kind: IngressRoute +metadata: + name: my-app + namespace: apps +spec: + entryPoints: + - websecure + routes: + - match: Path(`/my-app`) + kind: Rule + services: + - name: whoami + port: 80 + middlewares: + - name: test-jwt +``` + +```yaml tab="Service & Deployment" +kind: Deployment +apiVersion: apps/v1 +metadata: + name: whoami + namespace: apps +spec: + replicas: 3 + selector: + matchLabels: + app: whoami + template: + metadata: + labels: + app: whoami + spec: + containers: + - name: whoami + image: traefik/whoami + +--- +apiVersion: v1 +kind: Service +metadata: + name: whoami + namespace: apps +spec: + ports: + - port: 80 + name: whoami + selector: + app: whoami +``` + +!!! note "Advanced Configuration" + Advanced options are described in the [reference page](../reference/routing-configuration/http/middlewares/jwt.md). + + For example, the metadata recovered from the Identity Provider can be used to restrict the access to the applications. + To do so, you can use the `claims` option, more information in the [dedicated section](../reference/routing-configuration/http/middlewares/jwt.md#claims). + +{!traefik-for-business-applications.md!} diff --git a/docs/content/secure/secure-api-access-with-oidc.md b/docs/content/secure/secure-api-access-with-oidc.md new file mode 100644 index 000000000..f7b18def9 --- /dev/null +++ b/docs/content/secure/secure-api-access-with-oidc.md @@ -0,0 +1,110 @@ +--- +title: 'Secure API Access with OIDC' +description: 'Traefik Hub API Gateway - The OIDC Authentication middleware secures your applications by delegating the authentication to an external provider.' +--- + +# Secure API Access with OIDC + +!!! info "Traefik Hub Feature" + This middleware is available exclusively in [Traefik Hub](https://traefik.io/traefik-hub/). Learn more about [Traefik Hub's advanced features](https://doc.traefik.io/traefik-hub/api-gateway/intro). + +OpenID Connect Authentication is built on top of the OAuth2 Authorization Code Flow (defined in [OAuth 2.0 RFC 6749, section 4.1](https://tools.ietf.org/html/rfc6749#section-4.1)). +It allows an application to be secured by delegating authentication to an external provider (Keycloak, Okta etc.) +and obtaining the end user's session claims and scopes for authorization purposes. + +To authenticate the user, the middleware redirects through the authentication provider. +Once the authentication is complete, users are redirected back to the middleware before being authorized to access the upstream application, as described in the diagram below: + +![OpenID Connect authentication flow](../assets/img/secure/oidc-auth-flow.png) + +
+ +To allow the OIDC Middleware to use the credentials provided by the requests, apply the following configuration: + +```yaml tab="Middleware OIDC" +apiVersion: traefik.io/v1alpha1 +kind: Middleware +metadata: + name: oidc-login + namespace: apps +spec: + plugin: + oidc: + issuer: MY_ISSUER_URL + clientId: "urn:k8s:secret:oidc-client:client_id" + clientSecret: "urn:k8s:secret:oidc-client:client_secret" + redirectUrl: /oidc/callback +``` + +```yaml tab="Kubernetes Secrets" +apiVersion: v1 +kind: Secret +metadata: + name: oidc-client +stringData: + client_id: my-oauth-client-ID # Set your ClientID here + client_secret: my-oauth-client-secret # Set your client secret here +``` + +```yaml tab="IngressRoute" +apiVersion: traefik.io/v1alpha1 +kind: IngressRoute +metadata: + name: secure-applications-apigateway-oauth2-client-credentials + namespace: apps +spec: + entryPoints: + - websecure + routes: + - match: Path(`/my-app`) + kind: Rule + services: + - name: whoami + port: 80 + middlewares: + - name: oidc-login +``` + +```yaml tab="Service & Deployment" +kind: Deployment +apiVersion: apps/v1 +metadata: + name: whoami + namespace: apps +spec: + replicas: 3 + selector: + matchLabels: + app: whoami + template: + metadata: + labels: + app: whoami + spec: + containers: + - name: whoami + image: traefik/whoami + +--- +apiVersion: v1 +kind: Service +metadata: + name: whoami + namespace: apps +spec: + ports: + - port: 80 + name: whoami + selector: + app: whoami +``` + +!!! note "Advanced Configuration" + + Advanced options are described in the [reference page](../reference/routing-configuration/http/middlewares/oidc.md). + + For example, you can find how to customize the session storage: + - Using a cookie ([Options `session`](../reference/routing-configuration/http/middlewares/oidc.md#configuration-options) (default behavior)) + - Using a [Redis store](../reference/routing-configuration/http/middlewares/oidc.md#sessionstore). + +{!traefik-for-business-applications.md!} diff --git a/docs/content/secure/secure-api-access-with-waf.md b/docs/content/secure/secure-api-access-with-waf.md new file mode 100644 index 000000000..455839b99 --- /dev/null +++ b/docs/content/secure/secure-api-access-with-waf.md @@ -0,0 +1,190 @@ +--- +title: 'Secure API Access with WAF' +description: 'Traefik Hub API Gateway - Learn how to configure the Coraza Web Application Firewall middleware to protect your applications from common web attacks.' +--- + +# Secure API Access with WAF + +!!! info "Traefik Hub Feature" + This middleware is available exclusively in [Traefik Hub](https://traefik.io/traefik-hub/). Learn more about [Traefik Hub's advanced features](https://doc.traefik.io/traefik-hub/api-gateway/intro). + +The [Coraza Web Application Firewall](https://coraza.io/) middleware in Traefik Hub API Gateway provides comprehensive protection against common web application attacks. The middleware supports the Coraza rule syntax and is compatible with [OWASP Core Rule Set (CRS)](https://coreruleset.org/docs/), allowing you to leverage proven security rules maintained by the security community. + +## Basic WAF Protection + +To protect your applications with custom security rules, apply the following configuration: + +```yaml tab="Middleware WAF" +apiVersion: traefik.io/v1alpha1 +kind: Middleware +metadata: + name: waf-protection + namespace: apps +spec: + plugin: + coraza: + directives: + - SecRuleEngine On + - SecRule REQUEST_URI "@streq /admin" "id:101,phase:1,t:lowercase,log,deny" + - SecRule ARGS "@detectSQLi" "id:102,phase:2,block,msg:'SQL Injection Attack Detected',logdata:'Matched Data: %{MATCHED_VAR} found within %{MATCHED_VAR_NAME}'" +``` + +This configuration implements three security directives that work together to protect an application: + +- **SecRuleEngine On**: Activates the WAF engine to begin processing incoming requests. Without this directive, all other rules remain inactive regardless of their configuration. + +- **Admin Path Protection**: The second rule blocks all access to `/admin` paths by examining the request URI. This completely prevents access to administrative interfaces that often contain sensitive functionality like user management, system configuration, or database administration tools. The rule triggers during phase 1 (request headers processing) and applies lowercase transformation to catch variations like `/Admin` or `/ADMIN`. + +- **SQL Injection Detection**: The third rule scans request parameters (query strings and form data) for SQL injection patterns using Coraza's built-in detection engine. The `ARGS` variable covers query string parameters like `?id=1` and form data from POST requests like `username=admin&password=123`, but does not include cookies. SQL injection attacks attempt to manipulate database queries by injecting malicious SQL code through user inputs. When detected, the rule blocks the request and logs detailed information about the attempted attack, including which parameter contained the malicious payload. + +```yaml tab="IngressRoute" +apiVersion: traefik.io/v1alpha1 +kind: IngressRoute +metadata: + name: protected-app + namespace: apps +spec: + entryPoints: + - websecure + routes: + - match: Path(`/my-app`) + kind: Rule + services: + - name: whoami + port: 80 + middlewares: + - name: waf-protection +``` + +```yaml tab="Service & Deployment" +kind: Deployment +apiVersion: apps/v1 +metadata: + name: whoami + namespace: apps +spec: + replicas: 3 + selector: + matchLabels: + app: whoami + template: + metadata: + labels: + app: whoami + spec: + containers: + - name: whoami + image: traefik/whoami + +--- +apiVersion: v1 +kind: Service +metadata: + name: whoami + namespace: apps +spec: + ports: + - port: 80 + name: whoami + selector: + app: whoami +``` + +## Advanced Protection with OWASP Core Rule Set + +To implement comprehensive protection using the OWASP Core Rule Set, which provides battle-tested rules against common attack patterns, apply the following configuration: + +```yaml tab="Middleware WAF with CRS" +apiVersion: traefik.io/v1alpha1 +kind: Middleware +metadata: + name: waf-crs-protection + namespace: apps +spec: + plugin: + coraza: + crsEnabled: true + directives: + - SecRuleEngine On + - SecDefaultAction "phase:1,log,auditlog,deny,status:403" + - SecDefaultAction "phase:2,log,auditlog,deny,status:403" + - SecAction "id:900110, phase:1, pass, t:none, nolog, setvar:tx.inbound_anomaly_score_threshold=5, setvar:tx.outbound_anomaly_score_threshold=4" + - SecAction "id:900200, phase:1, pass, t:none, nolog, setvar:'tx.allowed_methods=GET POST'" + - Include @owasp_crs/REQUEST-911-METHOD-ENFORCEMENT.conf + - Include @owasp_crs/REQUEST-949-BLOCKING-EVALUATION.conf +``` + +This advanced configuration implements [OWASP Core Rule Set (CRS)](https://coreruleset.org/docs/) protection with anomaly scoring: + +- **SecDefaultAction for Phase 1 & 2**: Sets default behavior for request processing phases. Phase 1 processes request headers while Phase 2 processes request body. When rules match, they log the event to both standard and audit logs, then deny the request with a 403 status code. + +- **Anomaly Score Configuration**: The first `SecAction` sets anomaly score thresholds where `inbound_anomaly_score_threshold=5` means requests scoring 5 or higher are blocked, and `outbound_anomaly_score_threshold=4` applies the same logic to responses. This scoring system allows multiple suspicious patterns to accumulate points rather than blocking on first detection, reducing false positives while maintaining security. + +- **Allowed Methods Configuration**: The second `SecAction` restricts HTTP methods to only `GET` and `POST` requests. This prevents potentially dangerous methods like `PUT`, `DELETE`, `PATCH`, or `OPTIONS` that could modify server resources or reveal system information. + +- **METHOD-ENFORCEMENT Rule Set**: The `REQUEST-911-METHOD-ENFORCEMENT.conf` file enforces the allowed HTTP methods policy defined above. It checks incoming requests against the permitted methods and contributes to the anomaly score for disallowed methods. + +- **BLOCKING-EVALUATION Rule Set**: The `REQUEST-949-BLOCKING-EVALUATION.conf` file evaluates the accumulated anomaly score against the configured thresholds. If the total score exceeds the threshold, it triggers the blocking action, preventing the request from reaching your application. + +```yaml tab="IngressRoute" +apiVersion: traefik.io/v1alpha1 +kind: IngressRoute +metadata: + name: crs-protected-app + namespace: apps +spec: + entryPoints: + - websecure + routes: + - match: Path(`/my-app`) + kind: Rule + services: + - name: whoami + port: 80 + middlewares: + - name: waf-crs-protection +``` + +```yaml tab="Service & Deployment" +kind: Deployment +apiVersion: apps/v1 +metadata: + name: whoami + namespace: apps +spec: + replicas: 3 + selector: + matchLabels: + app: whoami + template: + metadata: + labels: + app: whoami + spec: + containers: + - name: whoami + image: traefik/whoami + +--- +apiVersion: v1 +kind: Service +metadata: + name: whoami + namespace: apps +spec: + ports: + - port: 80 + name: whoami + selector: + app: whoami +``` + +!!! warning + Starting with Traefik Hub v3.11.0, Coraza requires read/write permissions to `/tmp`. This requirement stems from upstream changes in the Coraza engine. + +!!! note "Advanced Configuration" + Advanced options and detailed rule configuration are described in the [reference page](../reference/routing-configuration/http/middlewares/waf.md). + + The WAF middleware supports extensive customization through Coraza directives. You can create custom rules, tune detection thresholds, configure logging levels, and integrate with external threat intelligence feeds. For comprehensive rule writing guidance, consult the [Coraza documentation](https://coraza.io/docs/tutorials/introduction/) and [OWASP CRS documentation](https://coreruleset.org/docs/). + +{!traefik-for-business-applications.md!} diff --git a/docs/content/setup/docker.md b/docs/content/setup/docker.md new file mode 100644 index 000000000..beaf29f9d --- /dev/null +++ b/docs/content/setup/docker.md @@ -0,0 +1,299 @@ +--- +title: Setup Traefik Proxy in Docker Standalone +description: "Learn how to Setup Traefik on Docker with HTTP/HTTPS entrypoints, redirects, secure dashboard, basic TLS, metrics, tracing, access‑logs." +--- + +This guide provides an in-depth walkthrough for installing and configuring Traefik Proxy within a Docker container using the official Traefik Docker image & Docker Compose. In this guide, we'll cover the following: + +- Enable the [Docker provider](../reference/install-configuration/providers/docker.md) +- Expose **web** (HTTP :80) and **websecure** (HTTPS :443) entrypoints +- Redirect all HTTP traffic to HTTPS +- Secure the Traefik dashboard with **basic‑auth** +- Terminate TLS with a self‑signed certificate for `*.docker.localhost` +- Deploy the **whoami** demo service +- Enable access‑logs and Prometheus metrics + +## Prerequisites + +- Docker Desktop / Engine +- Docker Compose +- `openssl` +- `htpasswd` from `apache2-utils` + +## Create a self‑signed certificate + +Before Traefik can serve HTTPS locally it needs a certificate. In production you’d use one from a trusted CA, but for a single‑machine stack a quick self‑signed cert is enough. We can create one with openssl by running the following commands: + +```bash +mkdir -p certs +openssl req -x509 -nodes -days 365 -newkey rsa:2048 \ + -keyout certs/local.key -out certs/local.crt \ + -subj "/CN=*.docker.localhost" +``` + +The `certs` folder now holds `local.crt` and `local.key`, which will be mounted read‑only into Traefik. + +## Create the Traefik Dashboard Credentials + +In production, it is advisable to have some form of authentication/security for the Traefik dashboard. Traefik can be secured with the [basic‑auth middleware](../reference/routing-configuration/http/middlewares/basicauth.md). To do this, generate a hashed username / password pair that Traefik’s middleware will validate: + +```bash +htpasswd -nb admin "P@ssw0rd" | sed -e 's/\$/\$\$/g' +``` + +Copy the full output (e.g., admin:$$apr1$$…) — we'll need this for the middleware configuration. + +## Create a docker-compose.yaml + +Now define the whole stack in a Compose file. This file declares Traefik, mounts the certificate, sets up a dedicated network, and later hosts the whoami demo service. + +!!! note + You can also choose to use the Docker CLI and a configuration file to run Traefik, but for this tutorial, we'll be using Docker Compose. + +First, create a folder named `dynamic` and create a file named `tls.yaml` for dynamic configuration. Paste the TLS certificate configuration into the file: + +```yaml +tls: + certificates: + - certFile: /certs/local.crt + keyFile: /certs/local.key +``` + +In the same folder as the `dynamic/tls.yaml` file, create a `docker-compose.yaml` file and include the following: + +```yaml +services: + traefik: + image: traefik:v3.4 + container_name: traefik + restart: unless-stopped + security_opt: + - no-new-privileges:true + + networks: + # Connect to the 'traefik_proxy' overlay network for inter-container communication across nodes + - proxy + + ports: + - "80:80" + - "443:443" + - "8080:8080" + + volumes: + - /var/run/docker.sock:/var/run/docker.sock:ro + - ./certs:/certs:ro + - ./dynamic:/dynamic:ro + + command: + # EntryPoints + - "--entrypoints.web.address=:80" + - "--entrypoints.web.http.redirections.entrypoint.to=websecure" + - "--entrypoints.web.http.redirections.entrypoint.scheme=https" + - "--entrypoints.web.http.redirections.entrypoint.permanent=true" + - "--entrypoints.websecure.address=:443" + - "--entrypoints.websecure.http.tls=true" + + # Attach the static configuration tls.yaml file that contains the tls configuration settings + - "--providers.file.filename=/dynamic/tls.yaml" + + # Providers + - "--providers.docker=true" + - "--providers.docker.exposedbydefault=false" + - "--providers.docker.network=proxy" + + # API & Dashboard + - "--api.dashboard=true" + - "--api.insecure=false" + + # Observability + - "--log.level=INFO" + - "--accesslog=true" + - "--metrics.prometheus=true" + + # Traefik Dynamic configuration via Docker labels + labels: + # Enable self‑routing + - "traefik.enable=true" + + # Dashboard router + - "traefik.http.routers.dashboard.rule=Host(`dashboard.docker.localhost`)" + - "traefik.http.routers.dashboard.entrypoints=websecure" + - "traefik.http.routers.dashboard.service=api@internal" + - "traefik.http.routers.dashboard.tls=true" + + # Basic‑auth middleware + - "traefik.http.middlewares.dashboard-auth.basicauth.users=" + - "traefik.http.routers.dashboard.middlewares=dashboard-auth@docker" + +# Whoami application + whoami: + image: traefik/whoami + container_name: whoami + restart: unless-stopped + networks: + - proxy + labels: + - "traefik.enable=true" + - "traefik.http.routers.whoami.rule=Host(`whoami.docker.localhost`)" + - "traefik.http.routers.whoami.entrypoints=websecure" + - "traefik.http.routers.whoami.tls=true" + +networks: + proxy: + name: proxy +``` + +!!! info + - Remember to replace `` with the hash generated earlier. + - The `--api.insecure=false` flag is used to secure the API and prevent the dashboard from being exposed on port 8080. This is done because we are exposing the dashboard with a HTTPS router. + +## Launch the stack + +With the Compose file and supporting assets in place, start the containers and let Docker wire up networking behind the scenes: + +```bash +docker compose up -d +``` + +Traefik will start, read its static configuration from the `command` arguments, connect to the Docker socket, detect its own labels for dynamic configuration (dashboard routing and auth), and begin listening on ports 80 and 443. HTTP requests will be redirected to HTTPS. + +## Access the Dashboard + +Now that Traefik is deployed, you can access the dashboard at [https://dashboard.docker.localhost](https://dashboard.docker.localhost) and it should prompt for the Basic Authentication credentials you configured: + +![Traefik Dashboard](../assets/img/setup/traefik-dashboard-docker.png) + +## Test the whoami Application + +You can test the application using curl: + +```bash +curl -k https://whoami.docker.localhost/ +``` + +```bash +Hostname: whoami-76c9859cfc-k7jzs +IP: 127.0.0.1 +IP: ::1 +IP: 10.42.0.59 +IP: fe80::50d7:a2ff:fed5:2530 +RemoteAddr: 10.42.0.60:54148 +GET / HTTP/1.1 +Host: whoami.docker.localhost +User-Agent: curl/8.7.1 +Accept: */* +Accept-Encoding: gzip +X-Forwarded-For: 10.42.0.1 +X-Forwarded-Host: whoami.docker.localhost +X-Forwarded-Port: 443 +X-Forwarded-Proto: https +X-Forwarded-Server: traefik-644b7c67d9-f2tn9 +X-Real-Ip: 10.42.0.1 +``` + +Making the same request to the HTTP entrypoint will return the following: + +```bash +curl -k http://whoami.docker.localhost + +Moved Permanently +``` + +The above confirms that a redirection has taken place which means our setup works correctly. + +You can also open a browser and navigate to [https://whoami.docker.localhost](https://whoami.docker.localhost) to see a JSON dump from the service: + +![Whoami](../assets/img/setup/whoami-json-dump.png) + +!!! info + You can also navigate to the Traefik Dashboard at [https://dashboard.docker.localhost](https://dashboard.docker.localhost) to see that the route has been created. + +### Other Key Configuration Areas + +Beyond this initial setup, Traefik offers extensive configuration possibilities. Here are brief introductions and minimal examples using Docker Compose `command` arguments or `labels`. Consult the main documentation linked for comprehensive details. + +### TLS Certificate Management (Let's Encrypt) + +To make the `websecure` entry point serve valid HTTPS certificates automatically, enable Let's Encrypt (ACME). + +*Example `command` additions:* + +```yaml +command: + # ... other command arguments ... + - "--certificatesresolvers.le.acme.email=your-email@example.com" + - "--certificatesresolvers.le.acme.storage=/letsencrypt/acme.json" # Path inside container volume + - "--certificatesresolvers.le.acme.httpchallenge.entrypoint=web" + + # - "--certificatesresolvers.le.acme.dnschallenge.provider=your-dns-provider" # Needs provider setup + + # Optionally make 'le' the default resolver for TLS-enabled routers + - "--entrypoints.websecure.http.tls.certresolver=le" +``` + +This defines a resolver named `le`, sets the required email and storage path (within the mounted `/letsencrypt` volume), and enables the HTTP challenge. Refer to the [HTTPS/TLS Documentation](../reference/install-configuration/tls/certificate-resolvers/overview.md) and [Let's Encrypt Documentation](../reference/install-configuration/tls/certificate-resolvers/acme.md) for details on challenges and DNS provider configuration. + +### Metrics (Prometheus) + +You can expose Traefik's internal metrics for monitoring with Prometheus. We already enabled prometheus in our setup but we can further configure it. +*Example `command` additions:* + +```yaml +command: + # If using a dedicated metrics entry point, define it: + - "--entrypoints.metrics.address=:8082" + + # ... other command arguments ... + - "--metrics.prometheus=true" + + # Optionally change the entry point metrics are exposed on (defaults to 'traefik') + - "--metrics.prometheus.entrypoint=metrics" + + # Add labels to metrics for routers/services (can increase cardinality) + - "--metrics.prometheus.addrouterslabels=true" + - "--metrics.prometheus.addserviceslabels=true" +``` + +This enables the `/metrics` endpoint (typically accessed via the internal API port, often 8080 by default if not secured, or via a dedicated entry point). See the [Metrics Documentation](../reference/install-configuration/observability/metrics.md) for options. + +### Tracing (OTel): + +You can enable distributed tracing to follow requests through Traefik. +*Example `command` additions:* + +```yaml +command: + # ... other command arguments ... + - "--tracing.otel=true" + - "--tracing.otel.grpcendpoint=otel-collector:4317" # Adjust endpoint as needed + - "--tracing.otel.httpendpoint=otel-collector.observability:4318" # Adjust endpoint as needed +``` + +!!! note + This option requires a running OTEL collector accessible by Traefik. Consult the [Tracing Documentation](../reference/install-configuration/observability/tracing.md). + +### Access Logs + +You can configure Traefik to log incoming requests for debugging and analysis. +*Example `command` additions:* + +```yaml +command: + # ... other command arguments ... + - "--accesslog=true" # Enable access logs to stdout + + # Optionally change format or output file (requires volume) + - "--accesslog.format=json" + - "--accesslog.filepath=/path/to/access.log" + + # Optionally filter logs + - "--accesslog.filters.statuscodes=400-599" +``` + +This enables access logs to the container's standard output (viewable via `docker compose logs `). See the [Access Logs Documentation](../reference/install-configuration/observability/logs-and-accesslogs.md). + +### Conclusion + +You now have a basic Traefik setup in Docker with secure dashboard access and HTTP-to-HTTPS redirection. + +{!traefik-for-business-applications.md!} diff --git a/docs/content/setup/kubernetes.md b/docs/content/setup/kubernetes.md new file mode 100644 index 000000000..c54853e8c --- /dev/null +++ b/docs/content/setup/kubernetes.md @@ -0,0 +1,400 @@ +--- +title: "Setup Traefik on Kubernetes" +description: "Learn how to Setup Traefik on Kubernetes with HTTP/HTTPS entrypoints, redirects, secure dashboard, basic TLS, metrics, tracing, access‑logs." +--- + +This guide provides an in-depth walkthrough for installing and configuring Traefik Proxy within a Kubernetes cluster using the official Helm chart. In this guide, we'll cover the following: + +- Configure standard HTTP (`web`) and HTTPS (`websecure`) entry points, +- Implement automatic redirection from HTTP to HTTPS +- Secure the Traefik Dashboard using Basic Authentication. +- Deploy a demo application to test the setup +- Explore some other key configuration options + +## Prerequisites + +- A Kubernetes cluster +- Helm v3, +- Kubectl + +## Create the Cluster + +If you do not have a Kubernetes cluster already, you can spin up one with K3d: + +```bash +k3d cluster create traefik \ + --port 80:80@loadbalancer \ + --port 443:443@loadbalancer \ + --port 8000:8000@loadbalancer \ + --k3s-arg "--disable=traefik@server:0" +``` + +Ports `80` and `443` reach Traefik from the host, while port `8000` remains free for later demos. The built-in Traefik shipped with k3s is disabled to avoid conflicts. + +Check the context: + +```bash +kubectl cluster-info --context k3d-traefik +``` + +You should see something like this: + +```bash +Kubernetes control plane is running at https://0.0.0.0:56049 +CoreDNS is running at https://0.0.0.0:56049/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy +Metrics-server is running at https://0.0.0.0:56049/api/v1/namespaces/kube-system/services/https:metrics-server:https/proxy + +To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'. +``` + +## Add the chart repo and namespace + +Using Helm streamlines Kubernetes application deployment. Helm packages applications into "charts," which are collections of template files describing Kubernetes resources. We use the official Traefik Helm chart for a managed and customizable installation. + +```bash +helm repo add traefik https://traefik.github.io/charts +helm repo update +kubectl create namespace traefik +``` + +The first command registers the `traefik` repository alias pointing to the official chart location. The second command refreshes your local cache to ensure you have the latest list of charts and versions available from all configured repositories. + +## Create a Local Self‑Signed TLS Secret + +Traefik's Gateway listeners require a certificate whenever a listener uses `protocol: HTTPS`. + +For local development create a throw‑away self‑signed certificate and +store it in a Kubernetes Secret named **local‑selfsigned‑tls**. +The Gateway references this secret to terminate TLS on the `websecure` listener. + +```bash +# 1) Generate a self‑signed certificate valid for *.docker.localhost +openssl req -x509 -nodes -days 365 -newkey rsa:2048 \ + -keyout tls.key -out tls.crt \ + -subj "/CN=*.docker.localhost" + +# 2) Create the TLS secret in the traefik namespace +kubectl create secret tls local-selfsigned-tls \ + --cert=tls.crt --key=tls.key \ + --namespace traefik +``` + +### Why Do We Need To Do This + +The Gateway's HTTPS listener references this secret via `certificateRefs`. +Without it, the helm chart validation fails and the HTTP→HTTPS redirect chain breaks. + +!!! info "Production tip" + The self-signed certificate above is **only for local development**. For production, either store a certificate issued by your organization's CA in a Secret or let an automated issuer such as cert-manager or Traefik's ACME (Let's Encrypt) generate certificates on demand. Update the `certificateRefs` in the `websecure` listener—or use `traefik.io/tls.certresolver`—so clients receive a trusted certificate and no longer see browser warnings. + +## Prepare Helm Chart Configuration Values + +Create a `values.yaml` file with the following content: + +```yaml + +# Configure Network Ports and EntryPoints +# EntryPoints are the network listeners for incoming traffic. +ports: + # Defines the HTTP entry point named 'web' + web: + port: 80 + nodePort: 30000 + # Instructs this entry point to redirect all traffic to the 'websecure' entry point + redirections: + entryPoint: + to: websecure + scheme: https + permanent: true + + # Defines the HTTPS entry point named 'websecure' + websecure: + port: 443 + nodePort: 30001 + +# Enables the dashboard in Secure Mode +api: + dashboard: true + insecure: false + +ingressRoute: + dashboard: + enabled: true + matchRule: Host(`dashboard.docker.localhost`) + entryPoints: + - websecure + middlewares: + - name: dashboard-auth + +# Creates a BasiAuth Middleware and Secret for the Dashboard Security +extraObjects: + - apiVersion: v1 + kind: Secret + metadata: + name: dashboard-auth-secret + type: kubernetes.io/basic-auth + stringData: + username: admin + password: "P@ssw0rd" # Replace with an Actual Password + - apiVersion: traefik.io/v1alpha1 + kind: Middleware + metadata: + name: dashboard-auth + spec: + basicAuth: + secret: dashboard-auth-secret + +# We will route with Gateway API instead. +ingressClass: + enabled: false + +# Enable Gateway API Provider & Disables the KubernetesIngress provider +# Providers tell Traefik where to find routing configuration. +providers: + kubernetesIngress: + enabled: false + kubernetesGateway: + enabled: true + +## Gateway Listeners +gateway: + listeners: + web: # HTTP listener that matches entryPoint `web` + port: 80 + protocol: HTTP + namespacePolicy: + from: All + + websecure: # HTTPS listener that matches entryPoint `websecure` + port: 443 + protocol: HTTPS # TLS terminates inside Traefik + namespacePolicy: + from: All + mode: Terminate + certificateRefs: + - kind: Secret + name: local-selfsigned-tls # the Secret we created before the installation + group: "" + +# Enable Observability +logs: + general: + level: INFO + # This enables access logs, outputting them to Traefik's standard output by default. The [Access Logs Documentation](https://doc.traefik.io/traefik/observability/access-logs/) covers formatting, filtering, and output options. + access: + enabled: true + +# Enables Prometheus for Metrics +metrics: + prometheus: + enabled: true +``` + +## Install the Traefik Using the Helm Values + +Now, apply the configuration using the Helm client. + +```bash +# Install the chart into the 'traefik' namespace +helm install traefik traefik/traefik \ + --namespace traefik \ + --values values.yaml +``` + +**Command Breakdown:** + +- `helm install traefik`: Instructs Helm to install a new release named `traefik`. +- `traefik/traefik`: Specifies the chart to use (`traefik` chart from the `traefik` repository added earlier). +- `--namespace traefik`: Specifies the Kubernetes namespace to install into. Using a dedicated namespace is recommended practice. +- `--values values.yaml`: Applies the custom configuration from your `values.yaml` file. + +## Accessing the Dashboard + +Now that Traefik is deployed, you can access its dashboard at [https://dashboard.docker.localhost/](https://dashboard.docker.localhost/). When you access this link, your browser will prompt for the username and password. Ensure you use the credentials set in the `values.yaml` file to log in. Upon successful login, the dashboard will be displayed as shown below: + +![Traefik Dashboard](../assets/img/setup/traefik-dashboard.png) + +## Deploy a Demo Application + +To test the setup, deploy the [Traefik whoami](https://github.com/traefik/whoami) application in the Kubernetes cluster. Create a file named `whoami.yaml` and paste the following: + +```yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: whoami + namespace: traefik +spec: + replicas: 2 + selector: + matchLabels: + app: whoami + template: + metadata: + labels: + app: whoami + spec: + containers: + - name: whoami + image: traefik/whoami + ports: + - containerPort: 80 +--- +apiVersion: v1 +kind: Service +metadata: + name: whoami + namespace: traefik +spec: + selector: + app: whoami + ports: + - port: 80 +``` + +Apply the manifest: + +```bash +kubectl apply -f whoami.yaml +``` + +After deploying the application, you can expose the application by creating a [Gateway API HTTPRoute](https://gateway-api.sigs.k8s.io/api-types/httproute/). To do this, create a file named `whoami-route.yaml` and paste the following: + +```yaml +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: whoami + namespace: traefik +spec: + parentRefs: + - name: traefik-gateway # Name of the Gateway that Traefik creates when you enable the Gateway API provider + hostnames: + - "whoami.docker.localhost" + rules: + - matches: + - path: + type: PathPrefix + value: / + backendRefs: + - name: whoami + port: 80 +``` + +Apply the manifest: + +```bash +kubectl apply -f whoami-route.yaml +``` + +After you apply the manifest, navigate to the Routes in the Traefik Dashboard; you’ll see that the [https://whoami.docker.localhost](https://whoami.docker.localhost) route has been created. + +![Route](../assets/img/setup/route-in-dashboard.png) + +You can test the application using curl: + +```bash +curl -k https://whoami.docker.localhost/ +``` + +```bash +Hostname: whoami-76c9859cfc-k7jzs +IP: 127.0.0.1 +IP: ::1 +IP: 10.42.0.59 +IP: fe80::50d7:a2ff:fed5:2530 +RemoteAddr: 10.42.0.60:54148 +GET / HTTP/1.1 +Host: whoami.docker.localhost +User-Agent: curl/8.7.1 +Accept: */* +Accept-Encoding: gzip +X-Forwarded-For: 10.42.0.1 +X-Forwarded-Host: whoami.docker.localhost +X-Forwarded-Port: 443 +X-Forwarded-Proto: https +X-Forwarded-Server: traefik-644b7c67d9-f2tn9 +X-Real-Ip: 10.42.0.1 +``` + +You can also open a browser and navigate to [https://whoami.docker.localhost](https://whoami.docker.localhost) to see a JSON dump from the service. + +![Whoami](../assets/img/setup/whoami-json-dump.png) + +## Other Key Configuration Areas + +The above setup provides a secure base, but Traefik offers much more. Here's a brief overview of other essential configurations, with minimal examples using Helm `values.yaml` overrides. + +These examples illustrate how to enable features; consult the main documentation for detailed options. + +### TLS Certificate Management (Let's Encrypt) + +On the `websecure` entry point TLS is enabled by default. However, it currently lacks a valid certificate. Traefik can automatically obtain and renew TLS certificates from Let's Encrypt using the ACME protocol. + +*Example `values.yaml` addition:* + +```yaml +additionalArguments: + - "--certificatesresolvers.le.acme.email=your-email@example.com" + - "--certificatesresolvers.le.acme.storage=/data/acme.json" + - "--certificatesresolvers.le.acme.httpchallenge.entrypoint=web" + # - "--certificatesresolvers.le.acme.dnschallenge.provider=your-dns-provider" # Requires provider-specific config, adjust if you control your DNS provider + +# Enable persistence for ACME data (certificates, account) to ensure it survives pod restarts: +persistence: + enabled: true + name: data + size: 1Gi + storageClass: "" +``` + +This enables a certificate resolver named `le`, configures the mandatory email and storage file, and sets up the HTTP challenge (requires port 80 access). Refer to the [HTTPS/TLS Documentation](../reference/install-configuration/tls/certificate-resolvers/overview.md) and [Let's Encrypt Documentation](../reference/install-configuration/tls/certificate-resolvers/acme.md) for full details, including DNS challenge configuration. + +!!!info "Let's Encrypt in Production" + Let's Encrypt can only issue certificates for hostnames that point to a public IP address reachable on ports 80 (HTTP‑01) or via your DNS provider's API (DNS‑01). Replace the `*.docker.localhost` examples with a real domain you control, create the DNS records, and keep ports 80/443 open to your cluster so the validation can succeed. + +### Gateway API & ACME + +Traefik’s built‑in ACME/Let’s Encrypt integration works for IngressRoute and Ingress resources, but it does not issue certificates for Gateway API listeners. +If you’re using the Gateway API, install [cert‑manager](https://cert-manager.io/docs/) (or another certificate controller) and reference the secret it creates in `gateway.listeners.websecure.certificateRefs`. + +### Metrics (Prometheus) + +Traefik can expose detailed metrics in Prometheus format, essential for monitoring its performance and the traffic it handles. + +*Example `values.yaml` addition:* + +```yaml +# Enable metrics endpoint +metrics: + prometheus: + # The entry point metrics will be available on (usually internal/admin) + entryPoint: metrics + # Add standard Prometheus metrics + addRoutersLabels: true + addServicesLabels: true + # ... other options available +``` + +This enables the Prometheus endpoint on a dedicated `metrics` entry point (port 9100). See the [Metrics Documentation](../reference/install-configuration/observability/metrics.md) for configuration details and available metrics. + +### Tracing (OTel) + +Distributed tracing helps understand request latency and flow through your system, including Traefik itself. + +*Example `values.yaml` addition:* + +```yaml +additionalArguments: + - "--tracing.otel=true" + - "--tracing.otel.grpcendpoint=otel-collector.observability:4317" # Adjust endpoint as needed + - "--tracing.otel.httpendpoint=otel-collector.observability:4318" # Adjust endpoint as needed +``` + +This enables OTel tracing and specifies the collector endpoint. Consult the [Tracing Documentation](../reference/install-configuration/observability/tracing.md) for details on OTel tracing. + +## Conclusion + +This setup establishes Traefik with secure dashboard access and HTTPS redirection, along with pointers to enable observability & TLS. + +{!traefik-for-business-applications.md!} diff --git a/docs/content/setup/swarm.md b/docs/content/setup/swarm.md new file mode 100644 index 000000000..1b097831d --- /dev/null +++ b/docs/content/setup/swarm.md @@ -0,0 +1,330 @@ +--- +title: Setup Traefik Proxy in Docker Swarm +description: "Learn how to run Traefik v3 in Docker Swarm with HTTP/HTTPS entrypoints, redirects, a secured dashboard, self‑signed TLS, metrics, tracing, and access‑logs." +--- + +This guide provides an in‑depth walkthrough for installing and configuring Traefik Proxy as a **Swarm service** using `docker stack deploy`. It follows the same structure as the standalone‑Docker tutorial and covers: + +- Enable the [Swarm provider](../reference/install-configuration/providers/swarm.md) +- Expose **web** (HTTP :80) and **websecure** (HTTPS :443) entrypoints +- Redirect all HTTP traffic to HTTPS +- Secure the Traefik dashboard with **basic‑auth** +- Terminate TLS with a self‑signed certificate for `*.swarm.localhost` +- Deploy the **whoami** demo service +- Enable access‑logs and Prometheus metrics + +## Prerequisites + +- Docker Engine with **Swarm mode** initialised (`docker swarm init`) +- Docker Compose +- `openssl` +- `htpasswd` + +## Create a self‑signed certificate + +Before Traefik can serve HTTPS locally it needs a certificate. In production you’d use one from a trusted CA, but for a multi‑node dev swarm a quick self‑signed cert is enough: + +```bash +mkdir -p certs +openssl req -x509 -nodes -days 365 -newkey rsa:2048 \ + -keyout certs/local.key -out certs/local.crt \ + -subj "/CN=*.swarm.localhost" +``` + +## Create the Traefik Dashboard Credentials + +Generate a hashed username / password pair that Traefik’s middleware will validate: + +```bash +htpasswd -nb admin "P@ssw0rd" | sed -e 's/\$/\$\$/g' +``` + +Copy the full output (e.g., `admin:$$apr1$$…`) — we’ll paste it into the middleware label. + +## Create a docker‑compose‑swarm.yaml + +!!! note + Swarm uses `docker stack deploy`. The compose file can be named anything; we’ll use `docker‑compose‑swarm.yaml`. + +First, create a folder named **dynamic** and add **tls.yaml** for dynamic TLS configuration: + +```yaml +# dynamic/tls.yaml +tls: + certificates: + - certFile: /certs/local.crt + keyFile: /certs/local.key +``` + +In the same directory, create `docker‑compose‑swarm.yaml`: + +```yaml +services: + traefik: + image: traefik:v3.4 + + networks: + # Connect to the 'traefik_proxy' overlay network for inter-container communication across nodes + - traefik_proxy + + ports: + # Expose Traefik's entry points to the Swarm + # Swarm requires the long syntax for ports. + - target: 80 # Container port (Traefik web entry point) + published: 80 # Host port exposed on the nodes + protocol: tcp + # 'host' mode binds directly to the node's IP where the task runs. + # 'ingress' mode uses Swarm's Routing Mesh (load balances across nodes). + # Choose based on your load balancing strategy. 'host' is often simpler if using an external LB. + mode: host + - target: 443 # Container port ( Traefik websecure entry point) + published: 443 # Host port + protocol: tcp + mode: host + + volumes: + # Mount the Docker socket for the Swarm provider + # This MUST be run from a manager node to access the Swarm API via the socket. + - /var/run/docker.sock:/var/run/docker.sock:ro # Swarm API socket + - ./certs:/certs:ro + - ./dynamic:/dynamic:ro + + # Traefik Static configuration via command-line arguments + command: + # HTTP EntryPoint + - "--entrypoints.web.address=:80" + + # Configure HTTP to HTTPS Redirection + - "--entrypoints.web.http.redirections.entrypoint.to=websecure" + - "--entrypoints.web.http.redirections.entrypoint.scheme=https" + - "--entrypoints.web.http.redirections.entrypoint.permanent=true" + + # HTTPS EntryPoint + - "--entrypoints.websecure.address=:443" + - "--entrypoints.websecure.http.tls=true" + + # Attach dynamic TLS file + - "--providers.file.filename=/dynamic/tls.yaml" + + # Providers + + # Enable the Docker Swarm provider (instead of Docker provider) + - "--providers.swarm.endpoint=unix:///var/run/docker.sock" + + # Watch for Swarm service changes (requires socket access) + - "--providers.swarm.watch=true" + + # Recommended: Don't expose services by default; require explicit labels + - "--providers.swarm.exposedbydefault=false" + + # Specify the default network for Traefik to connect to services + - "--providers.swarm.network=traefik_traefik_proxy" + + # API & Dashboard + - "--api.dashboard=true" # Enable the dashboard + - "--api.insecure=false" # Explicitly disable insecure API mod + + # Observability + - "--log.level=INFO" # Set the Log Level e.g INFO, DEBUG + - "--accesslog=true" # Enable Access Logs + - "--metrics.prometheus=true" # Enable Prometheus + + deploy: + mode: replicated + replicas: 1 + placement: + + # Placement constraints restrict where Traefik tasks can run. + # Running on manager nodes is common for accessing the Swarm API via the socket. + constraints: + - node.role == manager + + # Traefik Dynamic configuration via labels + # In Swarm, labels on the service definition configure Traefik routing for that service. + labels: + - "traefik.enable=true" + + # Dashboard router + - "traefik.http.routers.dashboard.rule=Host(`dashboard.swarm.localhost`)" + - "traefik.http.routers.dashboard.entrypoints=websecure" + - "traefik.http.routers.dashboard.service=api@internal" + - "traefik.http.routers.dashboard.tls=true" + + # Basic‑auth middleware + - "traefik.http.middlewares.dashboard-auth.basicauth.users=" + - "traefik.http.routers.dashboard.middlewares=dashboard-auth@swarm" + + # Service hint + - "traefik.http.services.traefik.loadbalancer.server.port=8080" + + # Deploy the Whoami application + whoami: + image: traefik/whoami + networks: + - traefik_proxy + deploy: + labels: + # Enable Service discovery for Traefik + - "traefik.enable=true" + # Define the WHoami router rule + - "traefik.http.routers.whoami.rule=Host(`whoami.swarm.localhost`)" + # Expose Whoami on the HTTPS entrypoint + - "traefik.http.routers.whoami.entrypoints=websecure" + # Enable TLS + - "traefik.http.routers.whoami.tls=true" + # Expose the whoami port number to Traefik + - traefik.http.services.whoami.loadbalancer.server.port=80 + +# Define the overlay network for Swarm +networks: + traefik_proxy: + driver: overlay + attachable: true +``` + +!!! info + - Replace `` with the escaped hash from the previous step. + - The password hash is stored directly in a service label. This is fine for local development, but anyone with access to the Docker API can view it using `docker service inspect`. For production, use a more secure method to store secrets. + +## Launch the stack + +Create the overlay network once (if it doesn’t exist) and deploy: + +```bash +docker network create --driver overlay --attachable traefik_proxy || true +docker stack deploy -c docker-compose-swarm.yaml traefik +``` + +Swarm schedules the services on a manager node and binds ports 80/443. + +## Access the Dashboard + +Open **https://dashboard.swarm.localhost/** in your browser — the dashboard should prompt for the basic‑auth credentials you configured. + +![Traefik Dashboard](../assets/img/setup/traefik-dashboard-swarm.png) + +## Test the whoami Application + +You can test the application using curl: + +```bash +curl -k https://whoami.swarm.localhost/ +``` + +```bash +Hostname: whoami-76c9859cfc-k7jzs +IP: 127.0.0.1 +IP: ::1 +IP: 10.42.0.59 +IP: fe80::50d7:a2ff:fed5:2530 +RemoteAddr: 10.42.0.60:54148 +GET / HTTP/1.1 +Host: whoami.swarm.localhost +User-Agent: curl/8.7.1 +Accept: */* +Accept-Encoding: gzip +X-Forwarded-For: 10.42.0.1 +X-Forwarded-Host: whoami.swarm.localhost +X-Forwarded-Port: 443 +X-Forwarded-Proto: https +X-Forwarded-Server: traefik-644b7c67d9-f2tn9 +X-Real-Ip: 10.42.0.1 +``` + +Making the same request to the HTTP entrypoint will return the following: + +```bash +curl -k http://whoami.swarm.localhost + +Moved Permanently +``` + +Requesting the HTTP endpoint redirects to HTTPS, confirming the setup works. + +You can also open a browser and navigate to [https://whoami.swarm.localhost](https://whoami.swarm.localhost) to see a JSON dump from the service: + +![Whoami](../assets/img/setup/whoami-json-dump.png) + +### Other Key Configuration Areas + +Beyond this initial setup, Traefik offers extensive configuration possibilities. Here are brief introductions and minimal examples using Docker Compose `command` arguments or `labels`. Consult the main documentation linked for comprehensive details. + +#### TLS Certificate Management (Let’s Encrypt) + +To make the `websecure` entry point serve valid HTTPS certificates automatically, enable Let's Encrypt (ACME). + +```yaml +command: + # ... + - "--certificatesresolvers.le.acme.email=you@example.com" + - "--certificatesresolvers.le.acme.storage=/letsencrypt/acme.json" + - "--certificatesresolvers.le.acme.httpchallenge.entrypoint=web" + - "--entrypoints.websecure.http.tls.certresolver=le" +``` + +This defines a resolver named `le`, sets the required email and storage path (within the mounted `/letsencrypt` volume), and enables the HTTP challenge. Refer to the [HTTPS/TLS Documentation](../reference/install-configuration/tls/certificate-resolvers/overview.md) and [Let's Encrypt Documentation](../reference/install-configuration/tls/certificate-resolvers/acme.md) for details on challenges and DNS provider configuration. + +!!! note + + - Ensure the `/letsencrypt` path is on a **shared volume** or NFS so all nodes can read certificates. + - Ensure to mount the `/letsencrypt` volume in the `traefik` service in the `docker-compose-swarm.yaml` file. + +#### Metrics (Prometheus) + +You can expose Traefik's internal metrics for monitoring with Prometheus. We already enabled prometheus in our setup but we can further configure it. +*Example `command` additions:* + +```yaml +command: + # If using a dedicated metrics entry point, define it: + - "--entrypoints.metrics.address=:8082" + + - "--metrics.prometheus=true" + + # Optionally change the entry point metrics are exposed on (defaults to 'traefik') + - "--metrics.prometheus.entrypoint=metrics" + + # Add labels to metrics for routers/services (can increase cardinality) + - "--metrics.prometheus.addrouterslabels=true" +``` + +This enables the `/metrics` endpoint (typically accessed via the internal API port, often 8080 by default if not secured, or via a dedicated entry point). See the [Metrics Documentation](../reference/install-configuration/observability/metrics.md) for options. + +#### Tracing (OTel) + +You can enable distributed tracing to follow requests through Traefik. +*Example `command` additions:* + +```yaml +command: + # ... + - "--tracing.otel=true" + - "--tracing.otel.grpcendpoint=otel-collector:4317" +``` + +!!! note + This option requires a running OTEL collector accessible by Traefik. Consult the [Tracing Documentation](../reference/install-configuration/observability/tracing.md). + +#### Access Logs + +You can configure Traefik to log incoming requests for debugging and analysis. +*Example `command` additions:* + +```yaml +command: + # ... other command arguments ... + - "--accesslog=true" # Enable access logs to stdout + + # Optionally change format or output file (requires volume) + - "--accesslog.format=json" + - "--accesslog.filepath=/path/to/access.log" + + # Optionally filter logs + - "--accesslog.filters.statuscodes=400-599" +``` + +### Conclusion + +You now have Traefik running on Docker Swarm with HTTPS, a secured dashboard, automatic HTTP → HTTPS redirects, and foundational observability. Expand this stack with Let’s Encrypt, additional middlewares, or multiple Traefik replicas as your Swarm grows. + +{!traefik-for-business-applications.md!} diff --git a/docs/content/user-guides/cert-manager.md b/docs/content/user-guides/cert-manager.md index f776ea0fa..fbb90d918 100644 --- a/docs/content/user-guides/cert-manager.md +++ b/docs/content/user-guides/cert-manager.md @@ -56,11 +56,11 @@ The certificates can then be used in an Ingress / IngressRoute / HTTPRoute. ``` Let's see now how to use it with the various Kubernetes providers of Traefik Proxy. -The enabled providers can be seen on the [dashboard](../../operations/dashboard/) of Traefik Proxy and also in the INFO logs when Traefik Proxy starts. +The enabled providers can be seen on the [dashboard](../reference/install-configuration/api-dashboard.md) of Traefik Proxy and also in the INFO logs when Traefik Proxy starts. ### With an Ingress -To use this certificate with an Ingress, the [Kubernetes Ingress](../../providers/kubernetes-ingress/) provider has to be enabled. +To use this certificate with an Ingress, the [Kubernetes Ingress](../providers/kubernetes-ingress.md) provider has to be enabled. !!! info Traefik Helm Chart @@ -94,7 +94,7 @@ To use this certificate with an Ingress, the [Kubernetes Ingress](../../provider ### With an IngressRoute -To use this certificate with an IngressRoute, the [Kubernetes CRD](../../providers/kubernetes-crd) provider has to be enabled. +To use this certificate with an IngressRoute, the [Kubernetes CRD](../providers/kubernetes-crd.md) provider has to be enabled. !!! info Traefik Helm Chart @@ -124,7 +124,7 @@ To use this certificate with an IngressRoute, the [Kubernetes CRD](../../provide ### With an HTTPRoute -To use this certificate with an HTTPRoute, the [Kubernetes Gateway](../../routing/providers/kubernetes-gateway) provider has to be enabled. +To use this certificate with an HTTPRoute, the [Kubernetes Gateway](../routing/providers/kubernetes-gateway.md) provider has to be enabled. !!! info Traefik Helm Chart diff --git a/docs/content/user-guides/crd-acme/03-deployments.yml b/docs/content/user-guides/crd-acme/03-deployments.yml index 079d0ab41..b2d15b3b7 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:v3.4 + image: traefik:v3.5 args: - --api.insecure - --accesslog diff --git a/docs/content/user-guides/crd-acme/index.md b/docs/content/user-guides/crd-acme/index.md index fc2492533..49c9db013 100644 --- a/docs/content/user-guides/crd-acme/index.md +++ b/docs/content/user-guides/crd-acme/index.md @@ -49,10 +49,10 @@ and the RBAC authorization resources which will be referenced through the `servi ```bash # Install Traefik Resource Definitions: -kubectl apply -f https://raw.githubusercontent.com/traefik/traefik/v3.4/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml +kubectl apply -f https://raw.githubusercontent.com/traefik/traefik/v3.5/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml # Install RBAC for Traefik: -kubectl apply -f https://raw.githubusercontent.com/traefik/traefik/v3.4/docs/content/reference/dynamic-configuration/kubernetes-crd-rbac.yml +kubectl apply -f https://raw.githubusercontent.com/traefik/traefik/v3.5/docs/content/reference/dynamic-configuration/kubernetes-crd-rbac.yml ``` ### Services @@ -60,7 +60,7 @@ kubectl apply -f https://raw.githubusercontent.com/traefik/traefik/v3.4/docs/con Then, the services. One for Traefik itself, and one for the app it routes for, i.e. in this case our demo HTTP server: [whoami](https://github.com/traefik/whoami). ```bash -kubectl apply -f https://raw.githubusercontent.com/traefik/traefik/v3.4/docs/content/user-guides/crd-acme/02-services.yml +kubectl apply -f https://raw.githubusercontent.com/traefik/traefik/v3.5/docs/content/user-guides/crd-acme/02-services.yml ``` ```yaml @@ -73,7 +73,7 @@ Next, the deployments, i.e. the actual pods behind the services. Again, one pod for Traefik, and one for the whoami app. ```bash -kubectl apply -f https://raw.githubusercontent.com/traefik/traefik/v3.4/docs/content/user-guides/crd-acme/03-deployments.yml +kubectl apply -f https://raw.githubusercontent.com/traefik/traefik/v3.5/docs/content/user-guides/crd-acme/03-deployments.yml ``` ```yaml @@ -100,7 +100,7 @@ Look it up. We can now finally apply the actual ingressRoutes, with: ```bash -kubectl apply -f https://raw.githubusercontent.com/traefik/traefik/v3.4/docs/content/user-guides/crd-acme/04-ingressroutes.yml +kubectl apply -f https://raw.githubusercontent.com/traefik/traefik/v3.5/docs/content/user-guides/crd-acme/04-ingressroutes.yml ``` ```yaml @@ -126,7 +126,7 @@ Nowadays, TLS v1.0 and v1.1 are deprecated. In order to force TLS v1.2 or later on all your IngressRoute, you can define the `default` TLSOption: ```bash -kubectl apply -f https://raw.githubusercontent.com/traefik/traefik/v3.4/docs/content/user-guides/crd-acme/05-tlsoption.yml +kubectl apply -f https://raw.githubusercontent.com/traefik/traefik/v3.5/docs/content/user-guides/crd-acme/05-tlsoption.yml ``` ```yaml diff --git a/docs/content/user-guides/crd-acme/k3s.yml b/docs/content/user-guides/crd-acme/k3s.yml index 10171f561..23190c461 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:v3.4 image. + # 'docker save'), if you want to use it, instead of the traefik:v3.5 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 d276dc41e..2bcb322e6 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 @@ -1,9 +1,7 @@ -version: "3.3" - services: traefik: - image: "traefik:v3.4" + image: "traefik:v3.5" 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 ce629d8f2..63487e067 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 @@ -1,5 +1,3 @@ -version: "3.3" - secrets: ovh_endpoint: file: "./secrets/ovh_endpoint.secret" @@ -13,7 +11,7 @@ secrets: services: traefik: - image: "traefik:v3.4" + image: "traefik:v3.5" container_name: "traefik" command: #- "--log.level=DEBUG" diff --git a/docs/content/user-guides/docker-compose/acme-dns/index.md b/docs/content/user-guides/docker-compose/acme-dns/index.md index e626c633a..78f25f8d4 100644 --- a/docs/content/user-guides/docker-compose/acme-dns/index.md +++ b/docs/content/user-guides/docker-compose/acme-dns/index.md @@ -6,7 +6,7 @@ description: "Learn how to create a certificate with the Let's Encrypt DNS chall # Docker-compose with Let's Encrypt: DNS Challenge This guide aims to demonstrate how to create a certificate with the Let's Encrypt DNS challenge to use https on a simple service exposed with Traefik. -Please also read the [basic example](../basic-example) for details on how to expose such a service. +Please also read the [basic example](../basic-example/) for details on how to expose such a service. ## Prerequisite 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 82c248a68..3deb01ca2 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 @@ -1,9 +1,7 @@ -version: "3.3" - services: traefik: - image: "traefik:v3.4" + image: "traefik:v3.5" container_name: "traefik" command: #- "--log.level=DEBUG" diff --git a/docs/content/user-guides/docker-compose/acme-http/index.md b/docs/content/user-guides/docker-compose/acme-http/index.md index 5f9b90565..5f60c077f 100644 --- a/docs/content/user-guides/docker-compose/acme-http/index.md +++ b/docs/content/user-guides/docker-compose/acme-http/index.md @@ -6,7 +6,7 @@ description: "Learn how to create a certificate with the Let's Encrypt HTTP chal # Docker-compose with Let's Encrypt : HTTP Challenge This guide aims to demonstrate how to create a certificate with the Let's Encrypt HTTP challenge to use https on a simple service exposed with Traefik. -Please also read the [basic example](../basic-example) for details on how to expose such a service. +Please also read the [basic example](../basic-example/) for details on how to expose such a service. ## Prerequisite 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 e3faaa3ce..c95bb5eb9 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 @@ -1,9 +1,7 @@ -version: "3.3" - services: traefik: - image: "traefik:v3.4" + image: "traefik:v3.5" container_name: "traefik" command: #- "--log.level=DEBUG" diff --git a/docs/content/user-guides/docker-compose/acme-tls/index.md b/docs/content/user-guides/docker-compose/acme-tls/index.md index 382244c62..942d17cc1 100644 --- a/docs/content/user-guides/docker-compose/acme-tls/index.md +++ b/docs/content/user-guides/docker-compose/acme-tls/index.md @@ -6,7 +6,7 @@ description: "Learn how to create a certificate with the Let's Encrypt TLS chall # Docker-compose with Let's Encrypt: TLS Challenge This guide aims to demonstrate how to create a certificate with the Let's Encrypt TLS challenge to use https on a simple service exposed with Traefik. -Please also read the [basic example](../basic-example) for details on how to expose such a service. +Please also read the [basic example](../basic-example/) for details on how to expose such a service. ## Prerequisite 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 3e9f68fa7..842478ac8 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 @@ -1,9 +1,7 @@ -version: "3.3" - services: traefik: - image: "traefik:v3.4" + image: "traefik:v3.5" 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 f6d395b4a..6aef0c804 100644 --- a/docs/content/user-guides/docker-compose/basic-example/index.md +++ b/docs/content/user-guides/docker-compose/basic-example/index.md @@ -23,15 +23,13 @@ Create a `docker-compose.yml` file with the following content: You can use a [pre-existing network](https://docs.docker.com/compose/networking/#use-a-pre-existing-network "Link to Docker Compose networking docs") too. ```yaml - version: "3.3" - networks: traefiknet: {} services: traefik: - image: "traefik:v3.4" + image: "traefik:v3.5" ... networks: - traefiknet diff --git a/docs/content/user-guides/websocket.md b/docs/content/user-guides/websocket.md new file mode 100644 index 000000000..cb122d90f --- /dev/null +++ b/docs/content/user-guides/websocket.md @@ -0,0 +1,355 @@ +--- +title: "Traefik WebSocket Documentation" +description: "How to configure WebSocket and WebSocket Secure (WSS) connections with Traefik Proxy." +--- + +# WebSocket + +Configuring Traefik to handle WebSocket and WebSocket Secure (WSS) connections. +{: .subtitle } + +## Overview + +WebSocket is a communication protocol that provides full-duplex communication channels over a single TCP connection. +WebSocket Secure (WSS) is the encrypted version of WebSocket, using TLS/SSL encryption. + +Traefik supports WebSocket and WebSocket Secure (WSS) out of the box. This guide will walk through examples of how to configure Traefik for different WebSocket scenarios. + +## Basic WebSocket Configuration + +A basic WebSocket configuration only requires defining a router and a service that points to your WebSocket server. + +```yaml tab="Docker & Swarm" +labels: + - "traefik.http.routers.my-websocket.rule=Host(`ws.example.com`)" + - "traefik.http.routers.my-websocket.service=my-websocket-service" + - "traefik.http.services.my-websocket-service.loadbalancer.server.port=8000" +``` + +```yaml tab="Kubernetes" +apiVersion: traefik.io/v1alpha1 +kind: IngressRoute +metadata: + name: my-websocket-route +spec: + entryPoints: + - web + routes: + - match: Host(`ws.example.com`) + kind: Rule + services: + - name: my-websocket-service + port: 8000 +``` + +```yaml tab="File (YAML)" +http: + routers: + my-websocket: + rule: "Host(`ws.example.com`)" + service: my-websocket-service + + services: + my-websocket-service: + loadBalancer: + servers: + - url: "http://my-websocket-server:8000" +``` + +```toml tab="File (TOML)" +[http.routers] + [http.routers.my-websocket] + rule = "Host(`ws.example.com`)" + service = "my-websocket-service" + +[http.services] + [http.services.my-websocket-service] + [http.services.my-websocket-service.loadBalancer] + [[http.services.my-websocket-service.loadBalancer.servers]] + url = "http://my-websocket-server:8000" +``` + +## WebSocket Secure (WSS) Configuration + +WebSocket Secure (WSS) requires TLS configuration. +The client connects using the `wss://` protocol instead of `ws://`. + +```yaml tab="Docker & Swarm" +labels: + - "traefik.http.routers.my-websocket-secure.rule=Host(`wss.example.com`)" + - "traefik.http.routers.my-websocket-secure.service=my-websocket-service" + - "traefik.http.routers.my-websocket-secure.tls=true" + - "traefik.http.services.my-websocket-service.loadbalancer.server.port=8000" +``` + +```yaml tab="Kubernetes" +apiVersion: traefik.io/v1alpha1 +kind: IngressRoute +metadata: + name: my-websocket-secure-route +spec: + entryPoints: + - websecure + routes: + - match: Host(`wss.example.com`) + kind: Rule + services: + - name: my-websocket-service + port: 8000 + tls: {} +``` + +```yaml tab="File (YAML)" +http: + routers: + my-websocket-secure: + rule: "Host(`wss.example.com`)" + service: my-websocket-service + tls: {} + + services: + my-websocket-service: + loadBalancer: + servers: + - url: "http://my-websocket-server:8000" +``` + +```toml tab="File (TOML)" +[http.routers] + [http.routers.my-websocket-secure] + rule = "Host(`wss.example.com`)" + service = "my-websocket-service" + [http.routers.my-websocket-secure.tls] + +[http.services] + [http.services.my-websocket-service] + [http.services.my-websocket-service.loadBalancer] + [[http.services.my-websocket-service.loadBalancer.servers]] + url = "http://my-websocket-server:8000" +``` + +## SSL Termination for WebSockets + +In this scenario, clients connect to Traefik using WSS (encrypted), but Traefik connects to your backend server using WS (unencrypted). +This is called SSL termination. + +```yaml tab="Docker & Swarm" +labels: + - "traefik.http.routers.my-wss-termination.rule=Host(`wss.example.com`)" + - "traefik.http.routers.my-wss-termination.service=my-ws-service" + - "traefik.http.routers.my-wss-termination.tls=true" + - "traefik.http.services.my-ws-service.loadbalancer.server.port=8000" +``` + +```yaml tab="Kubernetes" +apiVersion: traefik.io/v1alpha1 +kind: IngressRoute +metadata: + name: my-wss-termination-route +spec: + entryPoints: + - websecure + routes: + - match: Host(`wss.example.com`) + kind: Rule + services: + - name: my-ws-service + port: 8000 + tls: {} +``` + +```yaml tab="File (YAML)" +http: + routers: + my-wss-termination: + rule: "Host(`wss.example.com`)" + service: my-ws-service + tls: {} + + services: + my-ws-service: + loadBalancer: + servers: + - url: "http://my-ws-server:8000" +``` + +```toml tab="File (TOML)" +[http.routers] + [http.routers.my-wss-termination] + rule = "Host(`wss.example.com`)" + service = "my-ws-service" + [http.routers.my-wss-termination.tls] + +[http.services] + [http.services.my-ws-service] + [http.services.my-ws-service.loadBalancer] + [[http.services.my-ws-service.loadBalancer.servers]] + url = "http://my-ws-server:8000" +``` + +## End-to-End WebSocket Secure (WSS) + +For end-to-end encryption, Traefik can be configured to connect to your backend using HTTPS. + +```yaml tab="Docker & Swarm" +labels: + - "traefik.http.routers.my-wss-e2e.rule=Host(`wss.example.com`)" + - "traefik.http.routers.my-wss-e2e.service=my-wss-service" + - "traefik.http.routers.my-wss-e2e.tls=true" + - "traefik.http.services.my-wss-service.loadbalancer.server.port=8443" + # If the backend uses a self-signed certificate + - "traefik.http.serversTransports.insecureTransport.insecureSkipVerify=true" + - "traefik.http.services.my-wss-service.loadBalancer.serversTransport=insecureTransport" +``` + +```yaml tab="Kubernetes" +apiVersion: traefik.io/v1alpha1 +kind: ServersTransport +metadata: + name: insecure-transport +spec: + insecureSkipVerify: true + +--- +apiVersion: traefik.io/v1alpha1 +kind: IngressRoute +metadata: + name: my-wss-e2e-route +spec: + entryPoints: + - websecure + routes: + - match: Host(`wss.example.com`) + kind: Rule + services: + - name: my-wss-service + port: 8443 + serversTransport: insecure-transport + tls: {} +``` + +```yaml tab="File (YAML)" +http: + serversTransports: + insecureTransport: + insecureSkipVerify: true + + routers: + my-wss-e2e: + rule: "Host(`wss.example.com`)" + service: my-wss-service + tls: {} + + services: + my-wss-service: + loadBalancer: + serversTransport: insecureTransport + servers: + - url: "https://my-wss-server:8443" +``` + +```toml tab="File (TOML)" +[http.serversTransports] + [http.serversTransports.insecureTransport] + insecureSkipVerify = true + +[http.routers] + [http.routers.my-wss-e2e] + rule = "Host(`wss.example.com`)" + service = "my-wss-service" + [http.routers.my-wss-e2e.tls] + +[http.services] + [http.services.my-wss-service] + [http.services.my-wss-service.loadBalancer] + serversTransport = "insecureTransport" + [[http.services.my-wss-service.loadBalancer.servers]] + url = "https://my-wss-server:8443" +``` + +## EntryPoints Configuration for WebSockets + +In your Traefik static configuration, you'll need to define entryPoints for both WS and WSS: + +```yaml tab="File (YAML)" +entryPoints: + web: + address: ":80" + websecure: + address: ":443" +``` + +```toml tab="File (TOML)" +[entryPoints] + [entryPoints.web] + address = ":80" + [entryPoints.websecure] + address = ":443" +``` + +## Testing WebSocket Connections + +You can test your WebSocket configuration using various tools: + +1. Browser Developer Tools: Most modern browsers include WebSocket debugging in their developer tools. +2. WebSocket client tools like [wscat](https://github.com/websockets/wscat) or online tools like [Piesocket's WebSocket Tester](https://www.piesocket.com/websocket-tester). + +Example wscat commands: + +```bash +# Test standard WebSocket +wscat -c ws://ws.example.com + +# Test WebSocket Secure +wscat -c wss://wss.example.com +``` + +## Common Issues and Solutions + +### Headers and Origin Checks + +Some WebSocket servers implement origin checking. Traefik passes the original headers to your backend, including the `Origin` header. + +If you need to manipulate headers for WebSocket connections, you can use Traefik's Headers middleware: + +```yaml tab="Docker & Swarm" +labels: + - "traefik.http.middlewares.my-headers.headers.customrequestheaders.Origin=https://allowed-origin.com" + - "traefik.http.routers.my-websocket.middlewares=my-headers" +``` + +```yaml tab="Kubernetes" +apiVersion: traefik.io/v1alpha1 +kind: Middleware +metadata: + name: my-headers +spec: + headers: + customRequestHeaders: + Origin: "https://allowed-origin.com" + +--- +apiVersion: traefik.io/v1alpha1 +kind: IngressRoute +metadata: + name: my-websocket-route +spec: + routes: + - match: Host(`ws.example.com`) + kind: Rule + middlewares: + - name: my-headers + services: + - name: my-websocket-service + port: 8000 +``` + +### Certificate Issues with WSS + +If you're experiencing certificate issues with WSS: + +1. Ensure your certificates are valid and not expired +2. For testing with self-signed certificates, configure your clients to accept them +3. When using Let's Encrypt, ensure your domain is properly configured + +For backends with self-signed certificates, use the `insecureSkipVerify` option in the ServersTransport configuration as shown in the examples above. diff --git a/docs/docs.Dockerfile b/docs/docs.Dockerfile index e15440a36..99e963cf4 100644 --- a/docs/docs.Dockerfile +++ b/docs/docs.Dockerfile @@ -1,4 +1,4 @@ -FROM alpine:3.21 +FROM alpine:3.22 ENV PATH="${PATH}:/venv/bin" diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index fe10e3740..dca15910d 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -33,12 +33,132 @@ extra_javascript: - assets/js/hljs/highlight.pack.js # Download from https://highlightjs.org/download/ and enable YAML, TOML and Dockerfile - assets/js/extra.js +extra_css: + - assets/css/menu-icons.css + plugins: - search - exclude: glob: - "**/include-*.md" - + - redirects: + redirect_maps: + # Providers + 'providers/overview.md': 'reference/install-configuration/providers/overview.md' + 'providers/docker.md': 'reference/install-configuration/providers/docker.md' + 'providers/swarm.md': 'reference/install-configuration/providers/swarm.md' + 'providers/kubernetes-crd.md': 'reference/install-configuration/providers/kubernetes/kubernetes-crd.md' + 'providers/kubernetes-ingress.md': 'reference/install-configuration/providers/kubernetes/kubernetes-ingress.md' + 'providers/kubernetes-gateway.md': 'reference/install-configuration/providers/kubernetes/kubernetes-gateway.md' + 'providers/consul-catalog.md': 'reference/install-configuration/providers/hashicorp/consul-catalog.md' + 'providers/nomad.md': 'reference/install-configuration/providers/hashicorp/nomad.md' + 'providers/ecs.md': 'reference/install-configuration/providers/others/ecs.md' + 'providers/file.md': 'reference/install-configuration/providers/others/file.md' + 'providers/consul.md': 'reference/install-configuration/providers/kv/consul.md' + 'providers/etcd.md': 'reference/install-configuration/providers/kv/etcd.md' + 'providers/zookeeper.md': 'reference/install-configuration/providers/kv/zk.md' + 'providers/redis.md': 'reference/install-configuration/providers/kv/redis.md' + 'providers/http.md': 'reference/install-configuration/providers/others/http.md' + # Routing + 'routing/overview.md': 'reference/routing-configuration/dynamic-configuration-methods.md' + 'routing/entrypoints.md': 'reference/install-configuration/entrypoints.md' + 'routing/routers/index.md': 'reference/routing-configuration/http/routing/rules-and-priority.md' + 'routing/services/index.md': 'reference/routing-configuration/http/load-balancing/service.md' + 'routing/providers/docker.md': 'reference/routing-configuration/other-providers/docker.md' + 'routing/providers/swarm.md': 'reference/routing-configuration/other-providers/swarm.md' + 'routing/providers/kubernetes-crd.md': 'reference/routing-configuration/kubernetes/crd/http/ingressroute.md' + 'routing/providers/kubernetes-ingress.md': 'reference/routing-configuration/kubernetes/ingress.md' + 'routing/providers/kubernetes-gateway.md': 'reference/routing-configuration/kubernetes/gateway-api.md' + 'routing/providers/consul-catalog.md': 'reference/routing-configuration/other-providers/consul-catalog.md' + 'routing/providers/nomad.md': 'reference/routing-configuration/other-providers/nomad.md' + 'routing/providers/ecs.md': 'reference/routing-configuration/other-providers/ecs.md' + 'routing/providers/kv.md': 'reference/routing-configuration/other-providers/kv.md' + # Observability + 'observability/overview.md': 'observe/overview.md' + 'observability/logs.md': 'reference/install-configuration/observability/logs-and-accesslogs.md' + 'observability/access-logs.md': 'reference/install-configuration/observability/logs-and-accesslogs.md' + 'observability/metrics/overview.md': 'reference/install-configuration/observability/metrics.md' + 'observability/metrics/datadog.md': 'reference/install-configuration/observability/metrics.md' + 'observability/metrics/influxdb2.md': 'reference/install-configuration/observability/metrics.md' + 'observability/metrics/opentelemetry.md': 'reference/install-configuration/observability/metrics.md' + 'observability/metrics/prometheus.md': 'reference/install-configuration/observability/metrics.md' + 'observability/metrics/statsd.md': 'reference/install-configuration/observability/metrics.md' + 'observability/tracing/overview.md': 'reference/install-configuration/observability/tracing.md' + 'observability/tracing/opentelemetry.md': 'reference/install-configuration/observability/tracing.md' + # Operations + 'operations/cli.md': 'reference/install-configuration/observability/healthcheck.md' + 'operations/dashboard.md': 'reference/install-configuration/api-dashboard.md' + 'operations/api.md': 'reference/install-configuration/api-dashboard.md' + 'operations/ping.md': 'reference/install-configuration/observability/healthcheck.md' + # HTTPS & TLS + 'https/overview.md': 'reference/routing-configuration/http/tls/overview.md' + 'https/tls.md': 'reference/routing-configuration/http/tls/tls-certificates.md' + 'https/acme.md': 'reference/install-configuration/tls/certificate-resolvers/acme.md' + 'https/tailscale.md': 'reference/install-configuration/tls/certificate-resolvers/tailscale.md' + 'https/spiffe.md': 'reference/install-configuration/tls/spiffe.md' + # Middlewares + 'middlewares/overview.md': 'reference/routing-configuration/http/middlewares/overview.md' + # HTTP + 'middlewares/http/overview.md': 'reference/routing-configuration/http/middlewares/overview.md' + 'middlewares/http/addprefix.md': 'reference/routing-configuration/http/middlewares/addprefix.md' + 'middlewares/http/basicauth.md': 'reference/routing-configuration/http/middlewares/basicauth.md' + 'middlewares/http/buffering.md': 'reference/routing-configuration/http/middlewares/buffering.md' + 'middlewares/http/chain.md': 'reference/routing-configuration/http/middlewares/chain.md' + 'middlewares/http/circuitbreaker.md': 'reference/routing-configuration/http/middlewares/circuitbreaker.md' + 'middlewares/http/compress.md': 'reference/routing-configuration/http/middlewares/compress.md' + 'middlewares/http/contenttype.md': 'reference/routing-configuration/http/middlewares/contenttype.md' + 'middlewares/http/digestauth.md': 'reference/routing-configuration/http/middlewares/digestauth.md' + 'middlewares/http/errorpages.md': 'reference/routing-configuration/http/middlewares/errorpages.md' + 'middlewares/http/forwardauth.md': 'reference/routing-configuration/http/middlewares/forwardauth.md' + 'middlewares/http/grpcweb.md': 'reference/routing-configuration/http/middlewares/grpcweb.md' + 'middlewares/http/headers.md': 'reference/routing-configuration/http/middlewares/headers.md' + 'middlewares/http/ipwhitelist.md': 'reference/routing-configuration/http/middlewares/ipallowlist.md' + 'middlewares/http/ipallowlist.md': 'reference/routing-configuration/http/middlewares/ipallowlist.md' + 'middlewares/http/inflightreq.md': 'reference/routing-configuration/http/middlewares/inflightreq.md' + 'middlewares/http/passtlsclientcert.md': 'reference/routing-configuration/http/middlewares/passtlsclientcert.md' + 'middlewares/http/ratelimit.md': 'reference/routing-configuration/http/middlewares/ratelimit.md' + 'middlewares/http/redirectregex.md': 'reference/routing-configuration/http/middlewares/redirectregex.md' + 'middlewares/http/redirectscheme.md': 'reference/routing-configuration/http/middlewares/redirectscheme.md' + 'middlewares/http/replacepath.md': 'reference/routing-configuration/http/middlewares/replacepath.md' + 'middlewares/http/replacepathregex.md': 'reference/routing-configuration/http/middlewares/replacepathregex.md' + 'middlewares/http/retry.md': 'reference/routing-configuration/http/middlewares/retry.md' + 'middlewares/http/stripprefix.md': 'reference/routing-configuration/http/middlewares/stripprefix.md' + 'middlewares/http/stripprefixregex.md': 'reference/routing-configuration/http/middlewares/stripprefixregex.md' + # TCP + 'middlewares/tcp/overview.md': 'reference/routing-configuration/tcp/middlewares/overview.md' + 'middlewares/tcp/inflightconn.md': 'reference/routing-configuration/tcp/middlewares/inflightconn.md' + 'middlewares/tcp/ipwhitelist.md': 'reference/routing-configuration/tcp/middlewares/ipallowlist.md' + 'middlewares/tcp/ipallowlist.md': 'reference/routing-configuration/tcp/middlewares/ipallowlist.md' + ## User Guides + 'user-guides/crd-acme/index.md': 'expose/kubernetes.md' + 'user-guides/cert-manager.md': 'expose/kubernetes.md' + 'user-guides/docker-compose/basic-example/index.md': 'expose/docker.md' + 'user-guides/docker-compose/acme-tls/index.md': 'expose/docker.md' + 'user-guides/docker-compose/acme-http/index.md': 'expose/docker.md' + 'user-guides/docker-compose/acme-dns/index.md': 'expose/docker.md' + # References + # Static Configuration + 'reference/static-configuration/overview.md': 'reference/install-configuration/configuration-options.md' + 'reference/static-configuration/file.md': 'reference/install-configuration/configuration-options.md' + 'reference/static-configuration/cli.md': 'reference/install-configuration/configuration-options.md' + 'reference/static-configuration/env.md': 'reference/install-configuration/configuration-options.md' + # Dynamic Configuration + 'reference/dynamic-configuration/file.md': 'reference/routing-configuration/other-providers/file.md' + 'reference/dynamic-configuration/docker.md': 'reference/routing-configuration/other-providers/docker.md' + 'reference/dynamic-configuration/kubernetes-crd.md': 'reference/routing-configuration/kubernetes/crd/http/ingressroute.md' + 'reference/dynamic-configuration/kubernetes-gateway.md': 'reference/routing-configuration/kubernetes/gateway-api.md' + 'reference/dynamic-configuration/consul-catalog.md': 'reference/routing-configuration/other-providers/consul-catalog.md' + "reference/dynamic-configuration/nomad.md": 'reference/routing-configuration/other-providers/nomad.md' + 'reference/dynamic-configuration/ecs.md': 'reference/routing-configuration/other-providers/ecs.md' + 'reference/dynamic-configuration/kv.md': 'reference/routing-configuration/other-providers/kv.md' + ## Plugins + 'plugins/index.md': "extend/extend-traefik.md" + ## Migration + 'migration/v3.md': 'migrate/v3.md' + 'migration/v2-to-v3.md': 'migrate/v2-to-v3.md' + 'migration/v2-to-v3-details.md': 'migrate/v2-to-v3-details.md' + 'migration/v2.md': 'migrate/v2.md' + 'migration/v1-to-v2.md': 'migrate/v1-to-v2.md' # https://squidfunk.github.io/mkdocs-material/extensions/admonition/ # https://facelessuser.github.io/pymdown-extensions/ markdown_extensions: @@ -66,134 +186,38 @@ markdown_extensions: nav: - 'What is Traefik': 'index.md' - 'Getting Started': - - 'Concepts' : 'getting-started/concepts.md' + - 'Overview': 'getting-started/index.md' - 'Quick Start': - - 'Docker': 'getting-started/quick-start.md' - - 'Kubernetes': 'getting-started/quick-start-with-kubernetes.md' + - 'Kubernetes': 'getting-started/kubernetes.md' + - 'Docker': 'getting-started/docker.md' - 'Configuration Introduction': 'getting-started/configuration-overview.md' - - 'Install Traefik': 'getting-started/install-traefik.md' - - 'Frequently Asked Questions': 'getting-started/faq.md' - - 'Configuration Discovery': - - 'Overview': 'providers/overview.md' - - 'Docker': 'providers/docker.md' - - 'Swarm': 'providers/swarm.md' - - 'Kubernetes IngressRoute': 'providers/kubernetes-crd.md' - - 'Kubernetes Ingress': 'providers/kubernetes-ingress.md' - - 'Kubernetes Gateway API': 'providers/kubernetes-gateway.md' - - 'Consul Catalog': 'providers/consul-catalog.md' - - 'Nomad': 'providers/nomad.md' - - 'ECS': 'providers/ecs.md' - - 'File': 'providers/file.md' - - 'Consul': 'providers/consul.md' - - 'Etcd': 'providers/etcd.md' - - 'ZooKeeper': 'providers/zookeeper.md' - - 'Redis': 'providers/redis.md' - - 'HTTP': 'providers/http.md' - - 'Routing & Load Balancing': - - 'Overview': 'routing/overview.md' - - 'EntryPoints': 'routing/entrypoints.md' - - 'Routers': 'routing/routers/index.md' - - 'Services': 'routing/services/index.md' - - 'Providers': - - 'Docker': 'routing/providers/docker.md' - - 'Swarm': 'routing/providers/swarm.md' - - 'Kubernetes IngressRoute': 'routing/providers/kubernetes-crd.md' - - 'Kubernetes Ingress': 'routing/providers/kubernetes-ingress.md' - - 'Kubernetes Gateway API': 'routing/providers/kubernetes-gateway.md' - - 'Consul Catalog': 'routing/providers/consul-catalog.md' - - 'Nomad': 'routing/providers/nomad.md' - - 'ECS': 'routing/providers/ecs.md' - - 'KV': 'routing/providers/kv.md' - - 'HTTPS & TLS': - - 'Overview': 'https/overview.md' - - 'TLS': 'https/tls.md' - - 'Let''s Encrypt': 'https/acme.md' - - 'Tailscale': 'https/tailscale.md' - - 'SPIFFE': 'https/spiffe.md' - - 'Middlewares': - - 'Overview': 'middlewares/overview.md' - - 'HTTP': - - 'Overview': 'middlewares/http/overview.md' - - 'AddPrefix': 'middlewares/http/addprefix.md' - - 'BasicAuth': 'middlewares/http/basicauth.md' - - 'Buffering': 'middlewares/http/buffering.md' - - 'Chain': 'middlewares/http/chain.md' - - 'CircuitBreaker': 'middlewares/http/circuitbreaker.md' - - 'Compress': 'middlewares/http/compress.md' - - 'ContentType': 'middlewares/http/contenttype.md' - - 'DigestAuth': 'middlewares/http/digestauth.md' - - 'Errors': 'middlewares/http/errorpages.md' - - 'ForwardAuth': 'middlewares/http/forwardauth.md' - - 'GrpcWeb': 'middlewares/http/grpcweb.md' - - 'Headers': 'middlewares/http/headers.md' - - 'IPWhiteList': 'middlewares/http/ipwhitelist.md' - - 'IPAllowList': 'middlewares/http/ipallowlist.md' - - 'InFlightReq': 'middlewares/http/inflightreq.md' - - 'PassTLSClientCert': 'middlewares/http/passtlsclientcert.md' - - 'RateLimit': 'middlewares/http/ratelimit.md' - - 'RedirectRegex': 'middlewares/http/redirectregex.md' - - 'RedirectScheme': 'middlewares/http/redirectscheme.md' - - 'ReplacePath': 'middlewares/http/replacepath.md' - - 'ReplacePathRegex': 'middlewares/http/replacepathregex.md' - - 'Retry': 'middlewares/http/retry.md' - - 'StripPrefix': 'middlewares/http/stripprefix.md' - - 'StripPrefixRegex': 'middlewares/http/stripprefixregex.md' - - 'TCP': - - 'Overview': 'middlewares/tcp/overview.md' - - 'InFlightConn': 'middlewares/tcp/inflightconn.md' - - 'IPWhiteList': 'middlewares/tcp/ipwhitelist.md' - - 'IPAllowList': 'middlewares/tcp/ipallowlist.md' - - 'Plugins & Plugin Catalog': 'plugins/index.md' - - 'Operations': - - 'CLI': 'operations/cli.md' - - 'Dashboard' : 'operations/dashboard.md' - - 'API': 'operations/api.md' - - 'Ping': 'operations/ping.md' - - 'Observability': - - 'Overview': 'observability/overview.md' - - 'Logs': 'observability/logs.md' - - 'Access Logs': 'observability/access-logs.md' - - 'Metrics': - - 'Overview': 'observability/metrics/overview.md' - - 'Datadog': 'observability/metrics/datadog.md' - - 'InfluxDB2': 'observability/metrics/influxdb2.md' - - 'OpenTelemetry': 'observability/metrics/opentelemetry.md' - - 'Prometheus': 'observability/metrics/prometheus.md' - - 'StatsD': 'observability/metrics/statsd.md' - - 'Tracing': - - 'Overview': 'observability/tracing/overview.md' - - 'OpenTelemetry': 'observability/tracing/opentelemetry.md' - - 'Security': - - 'Content-Length': 'security/content-length.md' - - 'TLS in Multi-Tenant Kubernetes': 'security/tls-certs-in-multi-tenant-kubernetes.md' - - 'User Guides': - - 'FastProxy': 'user-guides/fastproxy.md' - - 'Kubernetes and Let''s Encrypt': 'user-guides/crd-acme/index.md' - - 'Kubernetes and cert-manager': 'user-guides/cert-manager.md' - - 'gRPC Examples': 'user-guides/grpc.md' - - 'Docker': - - 'Basic Example': 'user-guides/docker-compose/basic-example/index.md' - - 'HTTPS with Let''s Encrypt': - - 'TLS Challenge': 'user-guides/docker-compose/acme-tls/index.md' - - 'HTTP Challenge': 'user-guides/docker-compose/acme-http/index.md' - - 'DNS Challenge': 'user-guides/docker-compose/acme-dns/index.md' - - 'Migration': - - 'Traefik v3 minor migrations': 'migration/v3.md' + - 'Setup': + - 'Kubernetes': 'setup/kubernetes.md' + - 'Docker': 'setup/docker.md' + - 'Swarm': 'setup/swarm.md' + - 'Expose': + - 'Overview': 'expose/overview.md' + - 'Kubernetes': 'expose/kubernetes.md' + - 'Docker': 'expose/docker.md' + - 'Swarm': 'expose/swarm.md' + - 'Secure': + - 'Secure Access with JWT Traefik Hub API Gateway': 'secure/secure-api-access-with-jwt.md' + - 'Secure Access with OIDC Traefik Hub API Gateway': 'secure/secure-api-access-with-oidc.md' + - 'Secure Access with a WAF Traefik Hub API Gateway': 'secure/secure-api-access-with-waf.md' + - 'Observe': + - 'Overview': 'observe/overview.md' + - 'Logs & Access Logs': 'observe/logs-and-access-logs.md' + - 'Metrics': 'observe/metrics.md' + - 'Tracing': 'observe/tracing.md' + - 'Extend': 'extend/extend-traefik.md' + - 'Govern Traefik Hub API Gateway': 'govern/index.md' + - 'Migrate': + - 'Traefik v3 minor migrations': 'migrate/v3.md' - 'Traefik v2 to v3': - - 'Migration guide': 'migration/v2-to-v3.md' - - 'Configuration changes for v3': 'migration/v2-to-v3-details.md' - - 'Traefik v2 minor migrations': 'migration/v2.md' - - 'Traefik v1 to v2': 'migration/v1-to-v2.md' - - 'Contributing': - - 'Thank You!': 'contributing/thank-you.md' - - 'Submitting Issues': 'contributing/submitting-issues.md' - - 'Submitting PRs': 'contributing/submitting-pull-requests.md' - - 'Security': 'contributing/submitting-security-issues.md' - - 'Building and Testing': 'contributing/building-testing.md' - - 'Documentation': 'contributing/documentation.md' - - 'Data Collection': 'contributing/data-collection.md' - - 'Advocating': 'contributing/advocating.md' - - 'Maintainers': 'contributing/maintainers.md' + - 'Migration guide': 'migrate/v2-to-v3.md' + - 'Configuration changes for v3': 'migrate/v2-to-v3-details.md' + - 'Traefik v2 minor migrations': 'migrate/v2.md' + - 'Traefik v1 to v2': 'migrate/v1-to-v2.md' - 'Reference': - 'Install Configuration': - 'Boot Environment': 'reference/install-configuration/boot-environment.md' @@ -203,6 +227,7 @@ nav: - 'Kubernetes Gateway API' : 'reference/install-configuration/providers/kubernetes/kubernetes-gateway.md' - 'Kubernetes CRD' : 'reference/install-configuration/providers/kubernetes/kubernetes-crd.md' - 'Kubernetes Ingress' : 'reference/install-configuration/providers/kubernetes/kubernetes-ingress.md' + - 'Kubernetes Ingress NGINX' : 'reference/install-configuration/providers/kubernetes/kubernetes-ingress-nginx.md' - 'Docker': 'reference/install-configuration/providers/docker.md' - 'Swarm': 'reference/install-configuration/providers/swarm.md' - 'Hashicorp': @@ -226,19 +251,21 @@ nav: - "ACME" : 'reference/install-configuration/tls/certificate-resolvers/acme.md' - "Tailscale" : 'reference/install-configuration/tls/certificate-resolvers/tailscale.md' - "SPIFFE" : 'reference/install-configuration/tls/spiffe.md' + - "OCSP" : 'reference/install-configuration/tls/ocsp.md' - 'Observability': - 'Metrics' : 'reference/install-configuration/observability/metrics.md' - 'Tracing': 'reference/install-configuration/observability/tracing.md' - 'Logs & AccessLogs': 'reference/install-configuration/observability/logs-and-accesslogs.md' - 'Health Check (CLI & Ping)': 'reference/install-configuration/observability/healthcheck.md' - # - 'Options List': 'reference/install-configuration/cli-options-list.md' -- Todo + - 'Options List': 'reference/install-configuration/configuration-options.md' - 'Routing Configuration': - - 'General' : + - 'Common Configuration' : - 'Configuration Methods' : 'reference/routing-configuration/dynamic-configuration-methods.md' - 'HTTP' : - - 'Router' : - - 'Rules & Priority' : 'reference/routing-configuration/http/router/rules-and-priority.md' - - 'Observability': 'reference/routing-configuration/http/router/observability.md' + - 'Routing' : + - 'Router' : 'reference/routing-configuration/http/routing/router.md' + - 'Rules & Priority' : 'reference/routing-configuration/http/routing/rules-and-priority.md' + - 'Observability': 'reference/routing-configuration/http/routing/observability.md' - 'Load Balancing' : - 'Service' : 'reference/routing-configuration/http/load-balancing/service.md' - 'ServersTransport' : 'reference/routing-configuration/http/load-balancing/serverstransport.md' @@ -249,6 +276,7 @@ nav: - 'Middlewares' : - 'Overview' : 'reference/routing-configuration/http/middlewares/overview.md' - 'AddPrefix' : 'reference/routing-configuration/http/middlewares/addprefix.md' + - 'APIKey Traefik Hub API Gateway' : 'reference/routing-configuration/http/middlewares/apikey.md' - 'BasicAuth' : 'reference/routing-configuration/http/middlewares/basicauth.md' - 'Buffering': 'reference/routing-configuration/http/middlewares/buffering.md' - 'Chain': 'reference/routing-configuration/http/middlewares/chain.md' @@ -256,12 +284,20 @@ nav: - 'Compress': 'reference/routing-configuration/http/middlewares/compress.md' - 'ContentType': 'reference/routing-configuration/http/middlewares/contenttype.md' - 'DigestAuth': 'reference/routing-configuration/http/middlewares/digestauth.md' + - 'Distributed RateLimit Traefik Hub API Gateway' : 'reference/routing-configuration/http/middlewares/distributed-ratelimit.md' - 'Errors': 'reference/routing-configuration/http/middlewares/errorpages.md' - 'ForwardAuth': 'reference/routing-configuration/http/middlewares/forwardauth.md' - 'GrpcWeb': 'reference/routing-configuration/http/middlewares/grpcweb.md' - 'Headers': 'reference/routing-configuration/http/middlewares/headers.md' + - 'HMAC Traefik Hub API Gateway' : 'reference/routing-configuration/http/middlewares/hmac.md' - 'IPAllowList': 'reference/routing-configuration/http/middlewares/ipallowlist.md' - 'InFlightReq': 'reference/routing-configuration/http/middlewares/inflightreq.md' + - 'JWT Traefik Hub API Gateway' : 'reference/routing-configuration/http/middlewares/jwt.md' + - 'LDAP Traefik Hub API Gateway' : 'reference/routing-configuration/http/middlewares/ldap.md' + - 'Token Introspection Traefik Hub API Gateway' : 'reference/routing-configuration/http/middlewares/oauth2-token-introspection.md' + - 'Client Credentials Traefik Hub API Gateway' : 'reference/routing-configuration/http/middlewares/oauth2-client-credentials.md' + - 'OIDC Traefik Hub API Gateway' : 'reference/routing-configuration/http/middlewares/oidc.md' + - 'OPA Traefik Hub API Gateway' : 'reference/routing-configuration/http/middlewares/opa.md' - 'PassTLSClientCert': 'reference/routing-configuration/http/middlewares/passtlsclientcert.md' - 'RateLimit': 'reference/routing-configuration/http/middlewares/ratelimit.md' - 'RedirectRegex': 'reference/routing-configuration/http/middlewares/redirectregex.md' @@ -271,9 +307,11 @@ nav: - 'Retry': 'reference/routing-configuration/http/middlewares/retry.md' - 'StripPrefix': 'reference/routing-configuration/http/middlewares/stripprefix.md' - 'StripPrefixRegex': 'reference/routing-configuration/http/middlewares/stripprefixregex.md' + - 'WAF Traefik Hub API Gateway' : 'reference/routing-configuration/http/middlewares/waf.md' - 'TCP' : - - 'Router' : - - 'Rules & Priority' : 'reference/routing-configuration/tcp/router/rules-and-priority.md' + - 'Routing' : + - 'Router' : 'reference/routing-configuration/tcp/routing/router.md' + - 'Rules & Priority' : 'reference/routing-configuration/tcp/routing/rules-and-priority.md' - 'Service' : 'reference/routing-configuration/tcp/service.md' - 'ServersTransport' : 'reference/routing-configuration/tcp/serverstransport.md' - 'TLS' : 'reference/routing-configuration/tcp/tls.md' @@ -282,28 +320,31 @@ nav: - 'InFlightConn' : 'reference/routing-configuration/tcp/middlewares/inflightconn.md' - 'IPAllowList' : 'reference/routing-configuration/tcp/middlewares/ipallowlist.md' - 'UDP' : - - 'Router' : - - 'Rules & Priority' : 'reference/routing-configuration/udp/router/rules-priority.md' + - 'Routing' : + - 'Router' : 'reference/routing-configuration/udp/routing/router.md' + - 'Rules & Priority' : 'reference/routing-configuration/udp/routing/rules-priority.md' - 'Service' : 'reference/routing-configuration/udp/service.md' - 'Kubernetes': - 'Gateway API' : 'reference/routing-configuration/kubernetes/gateway-api.md' - 'Kubernetes CRD' : - 'HTTP' : - 'IngressRoute' : 'reference/routing-configuration/kubernetes/crd/http/ingressroute.md' + - 'Service' : 'reference/routing-configuration/kubernetes/crd/http/service.md' - 'TraefikService' : 'reference/routing-configuration/kubernetes/crd/http/traefikservice.md' - 'ServersTransport' : 'reference/routing-configuration/kubernetes/crd/http/serverstransport.md' - 'Middleware' : 'reference/routing-configuration/kubernetes/crd/http/middleware.md' - - 'TLSOption' : 'reference/routing-configuration/kubernetes/crd/http/tlsoption.md' - - 'TLSStore' : 'reference/routing-configuration/kubernetes/crd/http/tlsstore.md' + - 'TLSOption' : 'reference/routing-configuration/kubernetes/crd/tls/tlsoption.md' + - 'TLSStore' : 'reference/routing-configuration/kubernetes/crd/tls/tlsstore.md' - 'TCP' : - 'IngressRouteTCP' : 'reference/routing-configuration/kubernetes/crd/tcp/ingressroutetcp.md' - 'ServersTransportTCP' : 'reference/routing-configuration/kubernetes/crd/tcp/serverstransporttcp.md' - 'MiddlewareTCP' : 'reference/routing-configuration/kubernetes/crd/tcp/middlewaretcp.md' - - 'TLSOption' : 'reference/routing-configuration/kubernetes/crd/tcp/tlsoption.md' - - 'TLSStore' : 'reference/routing-configuration/kubernetes/crd/tcp/tlsstore.md' + - 'TLSOption' : 'reference/routing-configuration/kubernetes/crd/tls/tlsoption.md' + - 'TLSStore' : 'reference/routing-configuration/kubernetes/crd/tls/tlsstore.md' - 'UDP' : - 'IngressRouteUDP' : 'reference/routing-configuration/kubernetes/crd/udp/ingressrouteudp.md' - 'Ingress' : 'reference/routing-configuration/kubernetes/ingress.md' + - 'Ingress NGINX' : 'reference/routing-configuration/kubernetes/ingress-nginx.md' - 'Label & Tag Providers' : - 'Docker' : 'reference/routing-configuration/other-providers/docker.md' - 'Swarm' : 'reference/routing-configuration/other-providers/swarm.md' @@ -311,6 +352,25 @@ nav: - 'Nomad' : 'reference/routing-configuration/other-providers/nomad.md' - 'ECS' : 'reference/routing-configuration/other-providers/ecs.md' - 'KV' : 'reference/routing-configuration/other-providers/kv.md' - - 'Deprecation Notices': - - 'Releases': 'deprecation/releases.md' - - 'Features': 'deprecation/features.md' + - 'File' : 'reference/routing-configuration/other-providers/file.md' + - 'Security': + - 'Content-Length': 'security/content-length.md' + - 'TLS in Multi-Tenant Kubernetes': 'security/tls-certs-in-multi-tenant-kubernetes.md' + - 'Deprecation Notices': + - 'Releases': 'deprecation/releases.md' + - 'Features': 'deprecation/features.md' + - 'User Guides': + - 'FastProxy': 'user-guides/fastproxy.md' + - 'gRPC Examples': 'user-guides/grpc.md' + - 'WebSocket Examples': 'user-guides/websocket.md' + - 'Contributing': + - 'Thank You!': 'contributing/thank-you.md' + - 'Submitting Issues': 'contributing/submitting-issues.md' + - 'Submitting PRs': 'contributing/submitting-pull-requests.md' + - 'Security': 'contributing/submitting-security-issues.md' + - 'Building and Testing': 'contributing/building-testing.md' + - 'Documentation': 'contributing/documentation.md' + - 'Data Collection': 'contributing/data-collection.md' + - 'Advocating': 'contributing/advocating.md' + - 'Maintainers': 'contributing/maintainers.md' + - 'FAQ': 'getting-started/faq.md' diff --git a/docs/requirements.txt b/docs/requirements.txt index 68126a411..d2dff6242 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -2,6 +2,7 @@ markdown-include==0.5.1 mkdocs==1.2.4 mkdocs-exclude==1.0.2 mkdocs-traefiklabs>=100.0.7 +mkdocs-redirects==1.2.2 click==8.1.7 colorama==0.4.6 diff --git a/go.mod b/go.mod index 493de3354..3d00ed55b 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/traefik/traefik/v3 -go 1.23.0 +go 1.24.0 require ( github.com/BurntSushi/toml v1.5.0 @@ -8,22 +8,22 @@ require ( github.com/abbot/go-http-auth v0.0.0-00010101000000-000000000000 // No tag on the repo. github.com/andybalholm/brotli v1.1.1 github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 - github.com/aws/aws-sdk-go-v2 v1.36.3 - github.com/aws/aws-sdk-go-v2/config v1.29.9 - github.com/aws/aws-sdk-go-v2/credentials v1.17.62 + github.com/aws/aws-sdk-go-v2 v1.39.2 + github.com/aws/aws-sdk-go-v2/config v1.31.12 + github.com/aws/aws-sdk-go-v2/credentials v1.18.16 github.com/aws/aws-sdk-go-v2/service/ec2 v1.203.1 github.com/aws/aws-sdk-go-v2/service/ecs v1.53.15 github.com/aws/aws-sdk-go-v2/service/ssm v1.56.13 - github.com/aws/smithy-go v1.22.2 + github.com/aws/smithy-go v1.23.0 github.com/cenkalti/backoff/v4 v4.3.0 github.com/containous/alice v0.0.0-20181107144136-d83ebdd94cbd // No tag on the repo. github.com/coreos/go-systemd/v22 v22.5.0 - github.com/docker/cli v27.1.1+incompatible - github.com/docker/docker v27.1.1+incompatible + github.com/docker/cli v28.3.3+incompatible + github.com/docker/docker v28.3.3+incompatible github.com/docker/go-connections v0.5.0 github.com/fatih/structs v1.1.0 - github.com/fsnotify/fsnotify v1.8.0 - github.com/go-acme/lego/v4 v4.23.1 + github.com/fsnotify/fsnotify v1.9.0 + github.com/go-acme/lego/v4 v4.27.0 github.com/go-kit/kit v0.13.0 github.com/go-kit/log v0.2.1 github.com/golang/protobuf v1.5.4 @@ -33,36 +33,36 @@ require ( github.com/hashicorp/consul/api v1.26.1 github.com/hashicorp/go-hclog v1.6.3 github.com/hashicorp/go-multierror v1.1.1 - github.com/hashicorp/go-retryablehttp v0.7.7 + github.com/hashicorp/go-retryablehttp v0.7.8 github.com/hashicorp/go-version v1.7.0 github.com/hashicorp/nomad/api v0.0.0-20231213195942-64e3dca9274b // No tag on the repo. github.com/http-wasm/http-wasm-host-go v0.7.0 github.com/influxdata/influxdb-client-go/v2 v2.7.0 github.com/influxdata/influxdb1-client v0.0.0-20200827194710-b269163b24ab // No tag on the repo. - github.com/klauspost/compress v1.17.11 + github.com/klauspost/compress v1.18.0 github.com/kvtools/consul v1.0.2 - github.com/kvtools/etcdv3 v1.0.2 - github.com/kvtools/redis v1.1.0 + github.com/kvtools/etcdv3 v1.0.3 + github.com/kvtools/redis v1.2.0 github.com/kvtools/valkeyrie v1.0.0 github.com/kvtools/zookeeper v1.0.2 github.com/mailgun/ttlmap v0.0.0-20170619185759-c1c17f74874f // No tag on the repo. - github.com/miekg/dns v1.1.64 + github.com/miekg/dns v1.1.68 github.com/mitchellh/copystructure v1.2.0 github.com/mitchellh/hashstructure v1.0.0 - github.com/mitchellh/mapstructure v1.5.0 + github.com/mitchellh/mapstructure v1.5.1-0.20231216201459-8508981c8b6c // No tag on the repo. github.com/patrickmn/go-cache v2.1.0+incompatible - github.com/pires/go-proxyproto v0.6.1 + github.com/pires/go-proxyproto v0.8.1 github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // No tag on the repo. - github.com/prometheus/client_golang v1.19.1 + github.com/prometheus/client_golang v1.22.0 github.com/prometheus/client_model v0.6.1 - github.com/quic-go/quic-go v0.48.2 - github.com/redis/go-redis/v9 v9.7.3 + github.com/quic-go/quic-go v0.55.0 + github.com/redis/go-redis/v9 v9.8.0 github.com/rs/zerolog v1.33.0 github.com/sirupsen/logrus v1.9.3 - github.com/spiffe/go-spiffe/v2 v2.4.0 + github.com/spiffe/go-spiffe/v2 v2.5.0 github.com/stealthrocket/wasi-go v0.8.0 github.com/stealthrocket/wazergo v0.19.1 - github.com/stretchr/testify v1.10.0 + github.com/stretchr/testify v1.11.1 github.com/stvp/go-udp-testing v0.0.0-20191102171040-06b61409b154 // No tag on the repo. github.com/tailscale/tscert v0.0.0-20230806124524-28a91b69a046 // No tag on the repo. github.com/testcontainers/testcontainers-go v0.32.0 @@ -78,58 +78,59 @@ require ( github.com/vulcand/oxy/v2 v2.0.3 github.com/vulcand/predicate v1.2.0 github.com/yuin/gopher-lua v1.1.1 - go.opentelemetry.io/collector/pdata v1.10.0 - go.opentelemetry.io/contrib/bridges/otellogrus v0.7.0 - go.opentelemetry.io/contrib/propagators/autoprop v0.53.0 - go.opentelemetry.io/otel v1.34.0 - go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.8.0 - go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.8.0 - go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.28.0 - go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.28.0 - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0 - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.28.0 - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.28.0 - go.opentelemetry.io/otel/log v0.8.0 - go.opentelemetry.io/otel/metric v1.34.0 - go.opentelemetry.io/otel/sdk v1.34.0 - go.opentelemetry.io/otel/sdk/log v0.8.0 - go.opentelemetry.io/otel/sdk/metric v1.34.0 - go.opentelemetry.io/otel/trace v1.34.0 - golang.org/x/mod v0.23.0 - golang.org/x/net v0.38.0 - golang.org/x/sync v0.12.0 - golang.org/x/sys v0.31.0 - golang.org/x/text v0.23.0 - golang.org/x/time v0.11.0 - golang.org/x/tools v0.30.0 - google.golang.org/grpc v1.71.0 + go.opentelemetry.io/collector/pdata v1.41.0 + go.opentelemetry.io/contrib/bridges/otellogrus v0.13.0 + go.opentelemetry.io/contrib/propagators/autoprop v0.63.0 + go.opentelemetry.io/otel v1.38.0 + go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.14.0 + go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.14.0 + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0 + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 + go.opentelemetry.io/otel/log v0.14.0 + go.opentelemetry.io/otel/metric v1.38.0 + go.opentelemetry.io/otel/sdk v1.38.0 + go.opentelemetry.io/otel/sdk/log v0.14.0 + go.opentelemetry.io/otel/sdk/metric v1.38.0 + go.opentelemetry.io/otel/trace v1.38.0 + golang.org/x/crypto v0.43.0 + golang.org/x/mod v0.28.0 + golang.org/x/net v0.46.0 + golang.org/x/sync v0.17.0 + golang.org/x/sys v0.37.0 + golang.org/x/text v0.30.0 + golang.org/x/time v0.14.0 + golang.org/x/tools v0.37.0 + google.golang.org/grpc v1.75.1 gopkg.in/natefinch/lumberjack.v2 v2.2.1 gopkg.in/yaml.v3 v3.0.1 - k8s.io/api v0.31.1 - k8s.io/apiextensions-apiserver v0.31.1 - k8s.io/apimachinery v0.31.1 - k8s.io/client-go v0.31.1 - k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 // No tag on the repo. + k8s.io/api v0.32.3 + k8s.io/apiextensions-apiserver v0.32.3 + k8s.io/apimachinery v0.32.3 + k8s.io/client-go v0.32.3 + k8s.io/utils v0.0.0-20241210054802-24370beab758 // No tag on the repo. mvdan.cc/xurls/v2 v2.5.0 - sigs.k8s.io/controller-runtime v0.18.0 - sigs.k8s.io/gateway-api v1.2.1 + sigs.k8s.io/controller-runtime v0.20.4 + sigs.k8s.io/gateway-api v1.3.0 sigs.k8s.io/yaml v1.4.0 ) require ( - cloud.google.com/go/auth v0.15.0 // indirect - cloud.google.com/go/auth/oauth2adapt v0.2.7 // indirect - cloud.google.com/go/compute/metadata v0.6.0 // indirect - dario.cat/mergo v1.0.0 // indirect + cloud.google.com/go/auth v0.17.0 // indirect + cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect + cloud.google.com/go/compute/metadata v0.9.0 // indirect + dario.cat/mergo v1.0.1 // indirect github.com/AdamSLevy/jsonrpc2/v14 v14.1.0 // indirect github.com/Azure/azure-sdk-for-go v68.0.0+incompatible // indirect - github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.1 // indirect - github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.2 // indirect - github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect + github.com/Azure/azure-sdk-for-go/sdk/azcore v1.19.1 // indirect + github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.0 // indirect + github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 // indirect github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns v1.2.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/privatedns/armprivatedns v1.3.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resourcegraph/armresourcegraph v0.9.0 // indirect - github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect + github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect github.com/Azure/go-autorest v14.2.0+incompatible // indirect github.com/Azure/go-autorest/autorest v0.11.30 // indirect github.com/Azure/go-autorest/autorest/adal v0.9.22 // indirect @@ -139,39 +140,47 @@ require ( github.com/Azure/go-autorest/autorest/to v0.4.1 // indirect github.com/Azure/go-autorest/logger v0.2.1 // indirect github.com/Azure/go-autorest/tracing v0.6.0 // indirect - github.com/AzureAD/microsoft-authentication-library-for-go v1.3.3 // indirect + github.com/AzureAD/microsoft-authentication-library-for-go v1.5.0 // indirect github.com/HdrHistogram/hdrhistogram-go v1.1.2 // indirect github.com/Masterminds/goutils v1.1.1 // indirect - github.com/Masterminds/semver/v3 v3.2.1 // indirect + github.com/Masterminds/semver/v3 v3.3.1 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect - github.com/Microsoft/hcsshim v0.11.7 // indirect + github.com/Microsoft/hcsshim v0.13.0 // indirect github.com/OpenDNS/vegadns2client v0.0.0-20180418235048-a3fa4a771d87 // indirect github.com/VividCortex/gohistogram v1.0.0 // indirect - github.com/akamai/AkamaiOPEN-edgegrid-golang v1.2.2 // indirect - github.com/aliyun/alibaba-cloud-sdk-go v1.63.100 // indirect + github.com/akamai/AkamaiOPEN-edgegrid-golang/v11 v11.1.0 // indirect + github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.5 // indirect + github.com/alibabacloud-go/darabonba-openapi/v2 v2.1.13 // indirect + github.com/alibabacloud-go/debug v1.0.1 // indirect + github.com/alibabacloud-go/tea v1.3.13 // indirect + github.com/alibabacloud-go/tea-utils/v2 v2.0.7 // indirect + github.com/aliyun/credentials-go v1.4.7 // indirect github.com/armon/go-metrics v0.4.1 // indirect - github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.30 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.34 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.34 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.9 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.9 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.9 // indirect github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.15 // indirect - github.com/aws/aws-sdk-go-v2/service/lightsail v1.43.1 // indirect - github.com/aws/aws-sdk-go-v2/service/route53 v1.50.0 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.25.1 // indirect - github.com/aws/aws-sdk-go-v2/service/ssooidc v1.29.1 // indirect - github.com/aws/aws-sdk-go-v2/service/sts v1.33.17 // indirect - github.com/baidubce/bce-sdk-go v0.9.223 // indirect - github.com/benbjohnson/clock v1.3.0 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.1 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.9 // indirect + github.com/aws/aws-sdk-go-v2/service/lightsail v1.50.0 // indirect + github.com/aws/aws-sdk-go-v2/service/route53 v1.58.4 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.29.6 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.1 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.38.6 // indirect + github.com/aziontech/azionapi-go-sdk v0.143.0 // indirect + github.com/baidubce/bce-sdk-go v0.9.248 // indirect + github.com/benbjohnson/clock v1.3.5 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect github.com/bytedance/sonic v1.10.0 // indirect + github.com/cenkalti/backoff/v5 v5.0.3 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect - github.com/civo/civogo v0.3.11 // indirect - github.com/cloudflare/cloudflare-go v0.115.0 // indirect - github.com/containerd/containerd v1.7.20 // indirect + github.com/clbanning/mxj/v2 v2.7.0 // indirect + github.com/containerd/containerd v1.7.23 // indirect + github.com/containerd/errdefs v1.0.0 // indirect + github.com/containerd/errdefs/pkg v0.3.0 // indirect github.com/containerd/log v0.1.0 // indirect - github.com/containerd/platforms v0.2.1 // indirect + github.com/containerd/platforms v1.0.0-rc.1 // indirect github.com/coreos/go-semver v0.3.1 // indirect github.com/cpuguy83/dockercfg v0.3.1 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect @@ -180,52 +189,54 @@ require ( github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/dimchansky/utfbom v1.1.1 // indirect github.com/distribution/reference v0.6.0 // indirect - github.com/dnsimple/dnsimple-go v1.7.0 // indirect + github.com/dnsimple/dnsimple-go/v4 v4.0.0 // indirect github.com/docker/go-units v0.5.0 // indirect github.com/emicklei/go-restful/v3 v3.12.0 // indirect - github.com/evanphx/json-patch/v5 v5.9.0 // indirect - github.com/exoscale/egoscale/v3 v3.1.13 // indirect - github.com/fatih/color v1.17.0 // indirect + github.com/evanphx/json-patch/v5 v5.9.11 // indirect + github.com/exoscale/egoscale/v3 v3.1.27 // indirect + github.com/fatih/color v1.18.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fxamacker/cbor/v2 v2.7.0 // indirect - github.com/gabriel-vasile/mimetype v1.4.2 // indirect + github.com/gabriel-vasile/mimetype v1.4.3 // indirect github.com/ghodss/yaml v1.0.0 // indirect github.com/gin-gonic/gin v1.9.1 // indirect + github.com/go-acme/alidns-20150109/v4 v4.6.1 // indirect + github.com/go-acme/tencentclouddnspod v1.1.10 // indirect + github.com/go-acme/tencentedgdeone v1.1.19 // indirect github.com/go-errors/errors v1.0.1 // indirect - github.com/go-jose/go-jose/v4 v4.0.5 // indirect + github.com/go-jose/go-jose/v4 v4.1.3 // indirect github.com/go-logfmt/logfmt v0.5.1 // indirect - github.com/go-logr/logr v1.4.2 // indirect + github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-logr/zapr v1.3.0 // indirect github.com/go-ole/go-ole v1.2.6 // indirect github.com/go-openapi/jsonpointer v0.21.0 // indirect github.com/go-openapi/jsonreference v0.21.0 // indirect github.com/go-openapi/swag v0.23.0 // indirect + github.com/go-ozzo/ozzo-validation/v4 v4.3.0 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect - github.com/go-playground/validator/v10 v10.16.0 // indirect + github.com/go-playground/validator/v10 v10.23.0 // indirect github.com/go-resty/resty/v2 v2.16.5 // indirect - github.com/go-task/slim-sprig/v3 v3.0.0 // indirect - github.com/go-viper/mapstructure/v2 v2.2.1 // indirect + github.com/go-viper/mapstructure/v2 v2.4.0 // indirect github.com/go-zookeeper/zk v1.0.3 // indirect - github.com/goccy/go-json v0.10.5 // indirect + github.com/goccy/go-yaml v1.11.3 // indirect github.com/gofrs/flock v0.12.1 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang-jwt/jwt/v4 v4.5.2 // indirect - github.com/golang-jwt/jwt/v5 v5.2.2 // indirect + github.com/golang-jwt/jwt/v5 v5.3.0 // indirect github.com/google/gnostic-models v0.6.8 // indirect github.com/google/go-cmp v0.7.0 // indirect github.com/google/go-querystring v1.1.0 // indirect github.com/google/gofuzz v1.2.0 // indirect - github.com/google/pprof v0.0.0-20240910150728-a0b0bb1d4134 // indirect github.com/google/s2a-go v0.1.9 // indirect github.com/google/uuid v1.6.0 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect - github.com/googleapis/gax-go/v2 v2.14.1 // indirect + github.com/googleapis/gax-go/v2 v2.15.0 // indirect github.com/gophercloud/gophercloud v1.14.1 // indirect github.com/gophercloud/utils v0.0.0-20231010081019-80377eca5d56 // indirect github.com/gravitational/trace v1.1.16-0.20220114165159-14a9a7dd6aaf // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.23.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 // indirect github.com/hashicorp/cronexpr v1.1.2 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect @@ -236,156 +247,157 @@ require ( github.com/hashicorp/hcl v1.0.1-vault-5 // indirect github.com/hashicorp/serf v0.10.1 // indirect github.com/huandu/xstrings v1.5.0 // indirect - github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.141 // indirect + github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.172 // indirect github.com/iij/doapi v0.0.0-20190504054126-0bbf12d6d7df // indirect github.com/imdario/mergo v0.3.16 // indirect github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839 // indirect - github.com/infobloxopen/infoblox-go-client/v2 v2.9.0 // indirect - github.com/jmespath/go-jmespath v0.4.0 // indirect + github.com/infobloxopen/infoblox-go-client/v2 v2.10.0 // indirect github.com/jonboulle/clockwork v0.4.0 // indirect github.com/josharian/intern v1.0.0 // indirect - github.com/json-iterator/go v1.1.12 // indirect + github.com/json-iterator/go v1.1.13-0.20220915233716-71ac16282d12 // indirect github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213 // indirect github.com/klauspost/cpuid/v2 v2.2.5 // indirect github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b // indirect github.com/kylelemons/godebug v1.1.0 // indirect github.com/labbsr0x/bindman-dns-webhook v1.0.2 // indirect github.com/labbsr0x/goh v1.0.1 // indirect - github.com/leodido/go-urn v1.2.4 // indirect - github.com/linode/linodego v1.48.1 // indirect + github.com/leodido/go-urn v1.4.0 // indirect + github.com/linode/linodego v1.60.0 // indirect github.com/liquidweb/liquidweb-cli v0.6.9 // indirect github.com/liquidweb/liquidweb-go v1.6.4 // indirect - github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect + github.com/lufia/plan9stats v0.0.0-20250317134145-8bc96cf8fc35 // indirect github.com/magiconair/properties v1.8.7 // 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 github.com/mailru/easyjson v0.7.7 // indirect - github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mimuret/golang-iij-dpf v0.9.1 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/go-ps v1.0.0 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect + github.com/moby/go-archive v0.1.0 // indirect github.com/moby/patternmatcher v0.6.0 // indirect - github.com/moby/spdystream v0.4.0 // indirect - github.com/moby/sys/sequential v0.5.0 // indirect - github.com/moby/sys/user v0.2.0 // indirect - github.com/moby/term v0.5.0 // indirect + github.com/moby/spdystream v0.5.0 // indirect + github.com/moby/sys/atomicwriter v0.1.0 // indirect + github.com/moby/sys/sequential v0.6.0 // indirect + github.com/moby/sys/user v0.4.0 // indirect + github.com/moby/sys/userns v0.1.0 // indirect + github.com/moby/term v0.5.2 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect - github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect github.com/morikuni/aec v1.0.0 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect - github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04 // indirect + github.com/namedotcom/go/v4 v4.0.2 // indirect github.com/nrdcg/auroradns v1.1.0 // indirect - github.com/nrdcg/bunny-go v0.0.0-20240207213615-dde5bf4577a3 // indirect - github.com/nrdcg/desec v0.10.0 // indirect + github.com/nrdcg/bunny-go v0.0.0-20250327222614-988a091fc7ea // indirect + github.com/nrdcg/desec v0.11.0 // indirect github.com/nrdcg/dnspod-go v0.4.0 // indirect github.com/nrdcg/freemyip v0.3.0 // indirect github.com/nrdcg/goacmedns v0.2.0 // indirect - github.com/nrdcg/goinwx v0.10.0 // indirect + github.com/nrdcg/goinwx v0.11.0 // indirect github.com/nrdcg/mailinabox v0.2.0 // indirect - github.com/nrdcg/namesilo v0.2.1 // indirect + github.com/nrdcg/namesilo v0.5.0 // indirect github.com/nrdcg/nodion v0.1.0 // indirect + github.com/nrdcg/oci-go-sdk/common/v1065 v1065.102.0 // indirect + github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.102.0 // indirect github.com/nrdcg/porkbun v0.4.0 // indirect github.com/nzdjb/go-metaname v1.0.0 // indirect github.com/onsi/ginkgo v1.16.5 // indirect - github.com/onsi/ginkgo/v2 v2.20.2 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect - github.com/opencontainers/image-spec v1.1.0 // indirect - github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b // indirect - github.com/oracle/oci-go-sdk/v65 v65.87.0 // indirect - github.com/ovh/go-ovh v1.7.0 // indirect - github.com/pelletier/go-toml/v2 v2.1.0 // indirect + github.com/opencontainers/image-spec v1.1.1 // indirect + github.com/ovh/go-ovh v1.9.0 // indirect + github.com/pelletier/go-toml/v2 v2.2.4 // indirect github.com/peterhellberg/link v1.2.0 // indirect github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect github.com/pkg/errors v0.9.1 // indirect - github.com/power-devops/perfstat v0.0.0-20220216144756-c35f1ee13d7c // indirect - github.com/pquerna/otp v1.4.0 // indirect - github.com/prometheus/common v0.55.0 // indirect - github.com/prometheus/procfs v0.15.1 // indirect + github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect + github.com/pquerna/otp v1.5.0 // indirect + github.com/prometheus/common v0.62.0 // indirect + github.com/prometheus/procfs v0.16.1 // indirect github.com/quic-go/qpack v0.5.1 // indirect github.com/regfish/regfish-dnsapi-go v0.1.1 // indirect github.com/rs/cors v1.7.0 // indirect - github.com/sacloud/api-client-go v0.2.10 // indirect - github.com/sacloud/go-http v0.1.8 // indirect - github.com/sacloud/iaas-api-go v1.14.0 // indirect - github.com/sacloud/packages-go v0.0.10 // indirect + github.com/sacloud/api-client-go v0.3.3 // indirect + github.com/sacloud/go-http v0.1.9 // indirect + github.com/sacloud/iaas-api-go v1.19.0 // indirect + github.com/sacloud/packages-go v0.0.11 // indirect github.com/sagikazarmark/locafero v0.4.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect - github.com/scaleway/scaleway-sdk-go v1.0.0-beta.32 // indirect + github.com/scaleway/scaleway-sdk-go v1.0.0-beta.35 // indirect github.com/selectel/domains-go v1.1.0 // indirect - github.com/selectel/go-selvpcclient/v3 v3.2.1 // indirect + github.com/selectel/go-selvpcclient/v4 v4.1.0 // indirect github.com/shirou/gopsutil/v3 v3.24.4 // indirect github.com/shoenig/go-m1cpu v0.1.6 // indirect github.com/shopspring/decimal v1.4.0 // indirect github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9 // indirect - github.com/softlayer/softlayer-go v1.1.7 // indirect + github.com/softlayer/softlayer-go v1.2.1 // indirect github.com/softlayer/xmlrpc v0.0.0-20200409220501-5f089df7cb7e // indirect - github.com/sony/gobreaker v0.5.0 // indirect + github.com/sony/gobreaker v1.0.0 // indirect github.com/sourcegraph/conc v0.3.0 // indirect github.com/spf13/afero v1.11.0 // indirect github.com/spf13/cast v1.7.0 // indirect - github.com/spf13/pflag v1.0.5 // indirect + github.com/spf13/pflag v1.0.7 // indirect github.com/spf13/viper v1.18.2 // indirect + github.com/stretchr/objx v0.5.2 // indirect github.com/subosito/gotenv v1.6.0 // indirect - github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1128 // indirect - github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.1128 // indirect + github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.1.41 // indirect github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/pretty v1.2.1 // indirect github.com/tjfoc/gmsm v1.4.1 // indirect - github.com/tklauser/go-sysconf v0.3.12 // indirect - github.com/tklauser/numcpus v0.6.1 // indirect - github.com/transip/gotransip/v6 v6.26.0 // indirect - github.com/ultradns/ultradns-go-sdk v1.8.0-20241010134910-243eeec // indirect + github.com/tklauser/go-sysconf v0.3.15 // indirect + github.com/tklauser/numcpus v0.10.0 // indirect + github.com/transip/gotransip/v6 v6.26.1 // indirect + github.com/ultradns/ultradns-go-sdk v1.8.1-20250722213956-faef419 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/vinyldns/go-vinyldns v0.9.16 // indirect - github.com/volcengine/volc-sdk-golang v1.0.199 // indirect - github.com/vultr/govultr/v3 v3.17.0 // indirect + github.com/volcengine/volc-sdk-golang v1.0.223 // indirect + github.com/vultr/govultr/v3 v3.24.0 // indirect github.com/x448/float16 v0.8.4 // indirect - github.com/yandex-cloud/go-genproto v0.0.0-20250319153614-fb9d3e5eb01a // indirect - github.com/yandex-cloud/go-sdk v0.0.0-20250320143332-9cbcfc5de4ae // indirect + github.com/yandex-cloud/go-genproto v0.31.0 // indirect + github.com/yandex-cloud/go-sdk/services/dns v0.0.12 // indirect + github.com/yandex-cloud/go-sdk/v2 v2.19.0 // indirect + github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect github.com/yusufpapurcu/wmi v1.2.4 // indirect - github.com/zeebo/errs v1.3.0 // indirect - go.etcd.io/etcd/api/v3 v3.5.14 // indirect - go.etcd.io/etcd/client/pkg/v3 v3.5.14 // indirect - go.etcd.io/etcd/client/v3 v3.5.14 // indirect + github.com/zeebo/errs v1.4.0 // indirect + go.etcd.io/etcd/api/v3 v3.6.4 // indirect + go.etcd.io/etcd/client/pkg/v3 v3.6.4 // indirect + go.etcd.io/etcd/client/v3 v3.6.4 // indirect go.mongodb.org/mongo-driver v1.13.1 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0 // indirect - go.opentelemetry.io/contrib/propagators/aws v1.28.0 // indirect - go.opentelemetry.io/contrib/propagators/b3 v1.28.0 // indirect - go.opentelemetry.io/contrib/propagators/jaeger v1.28.0 // indirect - go.opentelemetry.io/contrib/propagators/ot v1.28.0 // indirect - go.opentelemetry.io/proto/otlp v1.3.1 // indirect + go.opentelemetry.io/collector/featuregate v1.41.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect + go.opentelemetry.io/contrib/propagators/aws v1.38.0 // indirect + go.opentelemetry.io/contrib/propagators/b3 v1.38.0 // indirect + go.opentelemetry.io/contrib/propagators/jaeger v1.38.0 // indirect + go.opentelemetry.io/contrib/propagators/ot v1.38.0 // indirect + go.opentelemetry.io/proto/otlp v1.7.1 // indirect go.uber.org/atomic v1.11.0 // indirect - go.uber.org/mock v0.4.0 // indirect go.uber.org/multierr v1.11.0 // indirect - go.uber.org/ratelimit v0.3.0 // indirect - go.uber.org/zap v1.26.0 // indirect + go.uber.org/ratelimit v0.3.1 // indirect + go.uber.org/zap v1.27.0 // indirect golang.org/x/arch v0.4.0 // indirect - golang.org/x/crypto v0.36.0 // indirect golang.org/x/exp v0.0.0-20241210194714-1829a127f884 // indirect - golang.org/x/oauth2 v0.28.0 // indirect - golang.org/x/term v0.30.0 // indirect - google.golang.org/api v0.227.0 // indirect - google.golang.org/genproto v0.0.0-20241021214115-324edc3d5d38 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20250313205543-e70fdf4c4cb4 // indirect - google.golang.org/protobuf v1.36.5 // indirect + golang.org/x/oauth2 v0.32.0 // indirect + golang.org/x/term v0.36.0 // indirect + golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect + google.golang.org/api v0.252.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20251002232023-7c0ddcbb5797 // indirect + google.golang.org/protobuf v1.36.10 // indirect gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect - gopkg.in/h2non/gock.v1 v1.0.16 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/ini.v1 v1.67.0 // indirect - gopkg.in/ns1/ns1-go.v2 v2.13.0 // indirect + gopkg.in/ns1/ns1-go.v2 v2.15.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect k8s.io/klog/v2 v2.130.1 // indirect - k8s.io/kube-openapi v0.0.0-20240423202451-8948a665c108 // indirect + k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f // indirect nhooyr.io/websocket v1.8.7 // indirect - sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect - sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect + sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.7.0 // indirect ) // Containous forks diff --git a/go.sum b/go.sum index aeff1bfa0..01f7b442d 100644 --- a/go.sum +++ b/go.sum @@ -13,18 +13,18 @@ cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKV cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= -cloud.google.com/go/auth v0.15.0 h1:Ly0u4aA5vG/fsSsxu98qCQBemXtAtJf+95z9HK+cxps= -cloud.google.com/go/auth v0.15.0/go.mod h1:WJDGqZ1o9E9wKIL+IwStfyn/+s59zl4Bi+1KQNVXLZ8= -cloud.google.com/go/auth/oauth2adapt v0.2.7 h1:/Lc7xODdqcEw8IrZ9SvwnlLX6j9FHQM74z6cBk9Rw6M= -cloud.google.com/go/auth/oauth2adapt v0.2.7/go.mod h1:NTbTTzfvPl1Y3V1nPpOgl2w6d/FjO7NNUQaWSox6ZMc= +cloud.google.com/go/auth v0.17.0 h1:74yCm7hCj2rUyyAocqnFzsAYXgJhrG26XCFimrc/Kz4= +cloud.google.com/go/auth v0.17.0/go.mod h1:6wv/t5/6rOPAX4fJiRjKkJCvswLwdet7G8+UGXt7nCQ= +cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc= +cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c= 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= cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= -cloud.google.com/go/compute/metadata v0.6.0 h1:A6hENjEsCDtC1k8byVsgwvVcioamEHvZ4j01OwKxG9I= -cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg= +cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs= +cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= @@ -37,23 +37,23 @@ cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0Zeo cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= -dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= -dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= +dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= +dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9vkmnHYOMsOr4WLk+Vo07yKIzd94sVoIqshQ4bU= -github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= github.com/AdamSLevy/jsonrpc2/v14 v14.1.0 h1:Dy3M9aegiI7d7PF1LUdjbVigJReo+QOceYsMyFh9qoE= github.com/AdamSLevy/jsonrpc2/v14 v14.1.0/go.mod h1:ZakZtbCXxCz82NJvq7MoREtiQesnDfrtF6RFUGzQfLo= github.com/Azure/azure-sdk-for-go v68.0.0+incompatible h1:fcYLmCpyNYRnvJbPerq7U0hS+6+I79yEDJBqVNcqUzU= github.com/Azure/azure-sdk-for-go v68.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.1 h1:DSDNVxqkoXJiko6x8a90zidoYqnYYa6c1MTzDKzKkTo= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.1/go.mod h1:zGqV2R4Cr/k8Uye5w+dgQ06WJtEcbQG/8J7BB6hnCr4= -github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.2 h1:F0gBpfdPLGsw+nsgk6aqqkZS1jiixa5WwFe3fk/T3Ys= -github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.2/go.mod h1:SqINnQ9lVVdRlyC8cd1lCI0SdX4n2paeABd2K8ggfnE= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.19.1 h1:5YTBM8QDVIBN3sxBil89WfdAAqDZbyJTgh688DSxX5w= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.19.1/go.mod h1:YD5h/ldMsG0XiIw7PdyNhLxaM317eFh5yNLccNfGdyw= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.0 h1:KpMC6LFL7mqpExyMC9jVOYRiVhLmamjeZfRsUpB7l4s= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.0/go.mod h1:J7MUC/wtRpfGVbQ5sIItY5/FuVWmvzlY21WAOfQnq/I= github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2 h1:yz1bePFlP5Vws5+8ez6T3HWXPmwOK7Yvq8QxDBD3SKY= github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2/go.mod h1:Pa9ZNPuoNu/GztvBSKk9J1cDJW6vk/n0zLtV4mgd8N8= -github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 h1:ywEEhmNahHBihViHepv3xPBn1663uRv2t2q/ESv9seY= -github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0/go.mod h1:iZDifYGJTIgIIkYRNWPENUnqx6bJ2xnSDFI2tjwZNuY= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 h1:9iefClla7iYpfYWdzPCRDozdmndjTm8DXdpCzPajMgA= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2/go.mod h1:XtLgD3ZD34DAaVIIAyG3objl5DynM3CQ/vMcbBNJZGI= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns v1.2.0 h1:lpOxwrQ919lCZoNCd69rVt8u1eLZuMORrGXqy8sNf3c= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns v1.2.0/go.mod h1:fSvRkb8d26z9dbL40Uf/OO6Vo9iExtZK3D0ulRV+8M0= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/internal/v3 v3.1.0 h1:2qsIIvxVT+uE6yrNldntJKlLRgxGbZ85kgtz5SNBhMw= @@ -64,8 +64,8 @@ github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resourcegraph/armresourceg github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resourcegraph/armresourcegraph v0.9.0/go.mod h1:wVEOJfGTj0oPAUGA1JuRAvz/lxXQsWW16axmHPP47Bk= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.2.0 h1:Dd+RhdJn0OTtVGaeDLZpcumkIVCtA/3/Fo42+eoYvVM= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.2.0/go.mod h1:5kakwfW5CjC9KK+Q4wjXAg+ShuIm2mBMua0ZFj2C8PE= -github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= -github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg= +github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs= github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= github.com/Azure/go-autorest/autorest v0.11.28/go.mod h1:MrkzG3Y3AH668QyF9KRk5neJnGgmhQ6krbhR8Q5eMvA= @@ -91,10 +91,9 @@ github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUM github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJTmL004Abzc5wDB5VtZG2PJk5ndYDgVacGqfirKxjM= github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE= -github.com/AzureAD/microsoft-authentication-library-for-go v1.3.3 h1:H5xDQaE3XowWfhZRUpnfC+rGZMEVoSiji+b+/HFAPU4= -github.com/AzureAD/microsoft-authentication-library-for-go v1.3.3/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= +github.com/AzureAD/microsoft-authentication-library-for-go v1.5.0 h1:XkkQbfMyuH2jTSjQjSoihryI8GINRcs4xp8lNawg0FI= +github.com/AzureAD/microsoft-authentication-library-for-go v1.5.0/go.mod h1:HKpQxkWaGLJ+D/5H8QRpyQXA1eKjxkFlOMwck5+33Jk= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg= github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= @@ -106,15 +105,15 @@ github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= -github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0= -github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= +github.com/Masterminds/semver/v3 v3.3.1 h1:QtNSWtVZ3nBfk8mAOu/B6v7FMJ+NHTIgUPi7rj+4nv4= +github.com/Masterminds/semver/v3 v3.3.1/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj9n6YA= github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBaRMhvYXJNkGuM= github.com/Microsoft/go-winio v0.6.0/go.mod h1:cTAf44im0RAYeL23bpB+fzCyDH2MJiz2BO69KH/soAE= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= -github.com/Microsoft/hcsshim v0.11.7 h1:vl/nj3Bar/CvJSYo7gIQPyRWc9f3c6IeSNavBTSZNZQ= -github.com/Microsoft/hcsshim v0.11.7/go.mod h1:MV8xMfmECjl5HdO7U/3/hFVnkmSBjAjmA09d4bExKcU= +github.com/Microsoft/hcsshim v0.13.0 h1:/BcXOiS6Qi7N9XqUcv27vkIuVOkBEcWstd2pMlWSeaA= +github.com/Microsoft/hcsshim v0.13.0/go.mod h1:9KWJ/8DgU+QzYGupX4tzMhRQE8h6w90lH6HAaclpEok= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= 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= @@ -126,15 +125,60 @@ github.com/VividCortex/gohistogram v1.0.0 h1:6+hBz+qvs0JOrrNhhmR7lFxo5sINxBCGXrd github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= -github.com/akamai/AkamaiOPEN-edgegrid-golang v1.2.2 h1:F1j7z+/DKEsYqZNoxC6wvfmaiDneLsQOFQmuq9NADSY= -github.com/akamai/AkamaiOPEN-edgegrid-golang v1.2.2/go.mod h1:QlXr/TrICfQ/ANa76sLeQyhAJyNR9sEcfNuZBkY9jgY= +github.com/akamai/AkamaiOPEN-edgegrid-golang/v11 v11.1.0 h1:h/33OxYLqBk0BYmEbSUy7MlvgQR/m1w1/7OJFKoPL1I= +github.com/akamai/AkamaiOPEN-edgegrid-golang/v11 v11.1.0/go.mod h1:rvh3imDA6EaQi+oM/GQHkQAOHbXPKJ7EWJvfjuw141Q= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= -github.com/aliyun/alibaba-cloud-sdk-go v1.63.100 h1:yUkCbrSM1cWtgBfRVKMQtdt22KhDvKY7g4V+92eG9wA= -github.com/aliyun/alibaba-cloud-sdk-go v1.63.100/go.mod h1:SOSDHfe1kX91v3W5QiBsWSLqeLxImobbMX1mxrFHsVQ= +github.com/alibabacloud-go/alibabacloud-gateway-pop v0.0.6 h1:eIf+iGJxdU4U9ypaUfbtOWCsZSbTb8AUHvyPrxu6mAA= +github.com/alibabacloud-go/alibabacloud-gateway-pop v0.0.6/go.mod h1:4EUIoxs/do24zMOGGqYVWgw0s9NtiylnJglOeEB5UJo= +github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.4/go.mod h1:sCavSAvdzOjul4cEqeVtvlSaSScfNsTQ+46HwlTL1hc= +github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.5 h1:zE8vH9C7JiZLNJJQ5OwjU9mSi4T9ef9u3BURT6LCLC8= +github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.5/go.mod h1:tWnyE9AjF8J8qqLk645oUmVUnFybApTQWklQmi5tY6g= +github.com/alibabacloud-go/darabonba-array v0.1.0 h1:vR8s7b1fWAQIjEjWnuF0JiKsCvclSRTfDzZHTYqfufY= +github.com/alibabacloud-go/darabonba-array v0.1.0/go.mod h1:BLKxr0brnggqOJPqT09DFJ8g3fsDshapUD3C3aOEFaI= +github.com/alibabacloud-go/darabonba-encode-util v0.0.2 h1:1uJGrbsGEVqWcWxrS9MyC2NG0Ax+GpOM5gtupki31XE= +github.com/alibabacloud-go/darabonba-encode-util v0.0.2/go.mod h1:JiW9higWHYXm7F4PKuMgEUETNZasrDM6vqVr/Can7H8= +github.com/alibabacloud-go/darabonba-map v0.0.2 h1:qvPnGB4+dJbJIxOOfawxzF3hzMnIpjmafa0qOTp6udc= +github.com/alibabacloud-go/darabonba-map v0.0.2/go.mod h1:28AJaX8FOE/ym8OUFWga+MtEzBunJwQGceGQlvaPGPc= +github.com/alibabacloud-go/darabonba-openapi/v2 v2.1.12/go.mod h1:f2wDpbM7hK9SvLIH09zSKVU1TsyemUNOqErMscMMl7c= +github.com/alibabacloud-go/darabonba-openapi/v2 v2.1.13 h1:Q00FU3H94Ts0ZIHDmY+fYGgB7dV9D/YX6FGsgorQPgw= +github.com/alibabacloud-go/darabonba-openapi/v2 v2.1.13/go.mod h1:lxFGfobinVsQ49ntjpgWghXmIF0/Sm4+wvBJ1h5RtaE= +github.com/alibabacloud-go/darabonba-signature-util v0.0.7 h1:UzCnKvsjPFzApvODDNEYqBHMFt1w98wC7FOo0InLyxg= +github.com/alibabacloud-go/darabonba-signature-util v0.0.7/go.mod h1:oUzCYV2fcCH797xKdL6BDH8ADIHlzrtKVjeRtunBNTQ= +github.com/alibabacloud-go/darabonba-string v1.0.2 h1:E714wms5ibdzCqGeYJ9JCFywE5nDyvIXIIQbZVFkkqo= +github.com/alibabacloud-go/darabonba-string v1.0.2/go.mod h1:93cTfV3vuPhhEwGGpKKqhVW4jLe7tDpo3LUM0i0g6mA= +github.com/alibabacloud-go/debug v0.0.0-20190504072949-9472017b5c68/go.mod h1:6pb/Qy8c+lqua8cFpEy7g39NRRqOWc3rOwAy8m5Y2BY= +github.com/alibabacloud-go/debug v1.0.0/go.mod h1:8gfgZCCAC3+SCzjWtY053FrOcd4/qlH6IHTI4QyICOc= +github.com/alibabacloud-go/debug v1.0.1 h1:MsW9SmUtbb1Fnt3ieC6NNZi6aEwrXfDksD4QA6GSbPg= +github.com/alibabacloud-go/debug v1.0.1/go.mod h1:8gfgZCCAC3+SCzjWtY053FrOcd4/qlH6IHTI4QyICOc= +github.com/alibabacloud-go/endpoint-util v1.1.0 h1:r/4D3VSw888XGaeNpP994zDUaxdgTSHBbVfZlzf6b5Q= +github.com/alibabacloud-go/endpoint-util v1.1.0/go.mod h1:O5FuCALmCKs2Ff7JFJMudHs0I5EBgecXXxZRyswlEjE= +github.com/alibabacloud-go/openapi-util v0.1.0/go.mod h1:sQuElr4ywwFRlCCberQwKRFhRzIyG4QTP/P4y1CJ6Ws= +github.com/alibabacloud-go/openapi-util v0.1.1 h1:ujGErJjG8ncRW6XtBBMphzHTvCxn4DjrVw4m04HsS28= +github.com/alibabacloud-go/openapi-util v0.1.1/go.mod h1:/UehBSE2cf1gYT43GV4E+RxTdLRzURImCYY0aRmlXpw= +github.com/alibabacloud-go/tea v1.1.0/go.mod h1:IkGyUSX4Ba1V+k4pCtJUc6jDpZLFph9QMy2VUPTwukg= +github.com/alibabacloud-go/tea v1.1.7/go.mod h1:/tmnEaQMyb4Ky1/5D+SE1BAsa5zj/KeGOFfwYm3N/p4= +github.com/alibabacloud-go/tea v1.1.8/go.mod h1:/tmnEaQMyb4Ky1/5D+SE1BAsa5zj/KeGOFfwYm3N/p4= +github.com/alibabacloud-go/tea v1.1.11/go.mod h1:/tmnEaQMyb4Ky1/5D+SE1BAsa5zj/KeGOFfwYm3N/p4= +github.com/alibabacloud-go/tea v1.1.17/go.mod h1:nXxjm6CIFkBhwW4FQkNrolwbfon8Svy6cujmKFUq98A= +github.com/alibabacloud-go/tea v1.1.20/go.mod h1:nXxjm6CIFkBhwW4FQkNrolwbfon8Svy6cujmKFUq98A= +github.com/alibabacloud-go/tea v1.2.2/go.mod h1:CF3vOzEMAG+bR4WOql8gc2G9H3EkH3ZLAQdpmpXMgwk= +github.com/alibabacloud-go/tea v1.3.12/go.mod h1:A560v/JTQ1n5zklt2BEpurJzZTI8TUT+Psg2drWlxRg= +github.com/alibabacloud-go/tea v1.3.13 h1:WhGy6LIXaMbBM6VBYcsDCz6K/TPsT1Ri2hPmmZffZ94= +github.com/alibabacloud-go/tea v1.3.13/go.mod h1:A560v/JTQ1n5zklt2BEpurJzZTI8TUT+Psg2drWlxRg= +github.com/alibabacloud-go/tea-utils v1.3.1/go.mod h1:EI/o33aBfj3hETm4RLiAxF/ThQdSngxrpF8rKUDJjPE= +github.com/alibabacloud-go/tea-utils/v2 v2.0.5/go.mod h1:dL6vbUT35E4F4bFTHL845eUloqaerYBYPsdWR2/jhe4= +github.com/alibabacloud-go/tea-utils/v2 v2.0.7 h1:WDx5qW3Xa5ZgJ1c8NfqJkF6w+AU5wB8835UdhPr6Ax0= +github.com/alibabacloud-go/tea-utils/v2 v2.0.7/go.mod h1:qxn986l+q33J5VkialKMqT/TTs3E+U9MJpd001iWQ9I= +github.com/aliyun/credentials-go v1.1.2/go.mod h1:ozcZaMR5kLM7pwtCMEpVmQ242suV6qTJya2bDq4X1Tw= +github.com/aliyun/credentials-go v1.3.1/go.mod h1:8jKYhQuDawt8x2+fusqa1Y6mPxemTsBEN04dgcAcYz0= +github.com/aliyun/credentials-go v1.3.6/go.mod h1:1LxUuX7L5YrZUWzBrRyk0SwSdH4OmPrib8NVePL3fxM= +github.com/aliyun/credentials-go v1.4.5/go.mod h1:Jm6d+xIgwJVLVWT561vy67ZRP4lPTQxMbEYRuT2Ti1U= +github.com/aliyun/credentials-go v1.4.7 h1:T17dLqEtPUFvjDRRb5giVvLh6dFT8IcNFJJb7MeyCxw= +github.com/aliyun/credentials-go v1.4.7/go.mod h1:Jm6d+xIgwJVLVWT561vy67ZRP4lPTQxMbEYRuT2Ti1U= github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA= github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= @@ -147,21 +191,24 @@ github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= +github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg= +github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= +github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/avast/retry-go v3.0.0+incompatible/go.mod h1:XtSnn+n/sHqQIpZ10K1qAevBhOOCWBLXXy3hyiqqBrY= github.com/aws/aws-sdk-go v1.40.45/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q= github.com/aws/aws-sdk-go-v2 v1.9.1/go.mod h1:cK/D0BBs0b/oWPIcX/Z/obahJK1TT7IPVjy53i/mX/4= -github.com/aws/aws-sdk-go-v2 v1.36.3 h1:mJoei2CxPutQVxaATCzDUjcZEjVRdpsiiXi2o38yqWM= -github.com/aws/aws-sdk-go-v2 v1.36.3/go.mod h1:LLXuLpgzEbD766Z5ECcRmi8AzSwfZItDtmABVkRLGzg= -github.com/aws/aws-sdk-go-v2/config v1.29.9 h1:Kg+fAYNaJeGXp1vmjtidss8O2uXIsXwaRqsQJKXVr+0= -github.com/aws/aws-sdk-go-v2/config v1.29.9/go.mod h1:oU3jj2O53kgOU4TXq/yipt6ryiooYjlkqqVaZk7gY/U= -github.com/aws/aws-sdk-go-v2/credentials v1.17.62 h1:fvtQY3zFzYJ9CfixuAQ96IxDrBajbBWGqjNTCa79ocU= -github.com/aws/aws-sdk-go-v2/credentials v1.17.62/go.mod h1:ElETBxIQqcxej++Cs8GyPBbgMys5DgQPTwo7cUPDKt8= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.30 h1:x793wxmUWVDhshP8WW2mlnXuFrO4cOd3HLBroh1paFw= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.30/go.mod h1:Jpne2tDnYiFascUEs2AWHJL9Yp7A5ZVy3TNyxaAjD6M= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.34 h1:ZK5jHhnrioRkUNOc+hOgQKlUL5JeC3S6JgLxtQ+Rm0Q= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.34/go.mod h1:p4VfIceZokChbA9FzMbRGz5OV+lekcVtHlPKEO0gSZY= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.34 h1:SZwFm17ZUNNg5Np0ioo/gq8Mn6u9w19Mri8DnJ15Jf0= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.34/go.mod h1:dFZsC0BLo346mvKQLWmoJxT+Sjp+qcVR1tRVHQGOH9Q= +github.com/aws/aws-sdk-go-v2 v1.39.2 h1:EJLg8IdbzgeD7xgvZ+I8M1e0fL0ptn/M47lianzth0I= +github.com/aws/aws-sdk-go-v2 v1.39.2/go.mod h1:sDioUELIUO9Znk23YVmIk86/9DOpkbyyVb1i/gUNFXY= +github.com/aws/aws-sdk-go-v2/config v1.31.12 h1:pYM1Qgy0dKZLHX2cXslNacbcEFMkDMl+Bcj5ROuS6p8= +github.com/aws/aws-sdk-go-v2/config v1.31.12/go.mod h1:/MM0dyD7KSDPR+39p9ZNVKaHDLb9qnfDurvVS2KAhN8= +github.com/aws/aws-sdk-go-v2/credentials v1.18.16 h1:4JHirI4zp958zC026Sm+V4pSDwW4pwLefKrc0bF2lwI= +github.com/aws/aws-sdk-go-v2/credentials v1.18.16/go.mod h1:qQMtGx9OSw7ty1yLclzLxXCRbrkjWAM7JnObZjmCB7I= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.9 h1:Mv4Bc0mWmv6oDuSWTKnk+wgeqPL5DRFu5bQL9BGPQ8Y= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.9/go.mod h1:IKlKfRppK2a1y0gy1yH6zD+yX5uplJ6UuPlgd48dJiQ= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.9 h1:se2vOWGD3dWQUtfn4wEjRQJb1HK1XsNIt825gskZ970= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.9/go.mod h1:hijCGH2VfbZQxqCDN7bwz/4dzxV+hkyhjawAtdPWKZA= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.9 h1:6RBnKZLkJM4hQ+kN6E7yWFveOTg8NLPHAkqrs4ZPlTU= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.9/go.mod h1:V9rQKRmK7AWuEsOMnHzKj8WyrIir1yUJbZxDuZLFvXI= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 h1:bIqFDwgGXXN1Kpp99pDOdKMTTb5d2KyU5X/BZxjOkRo= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3/go.mod h1:H5O/EsxDWyU+LP/V8i5sm8cxoZgc2fdNR9bxlOFrQTo= github.com/aws/aws-sdk-go-v2/service/cloudwatch v1.8.1/go.mod h1:CM+19rL1+4dFWnOQKwDc7H1KwXTz+h61oUSHyhV0b3o= @@ -169,30 +216,32 @@ github.com/aws/aws-sdk-go-v2/service/ec2 v1.203.1 h1:ZgY9zeVAe+54Qa7o1GXKRNTez79 github.com/aws/aws-sdk-go-v2/service/ec2 v1.203.1/go.mod h1:0naMk66LtdeTmE+1CWQTKwtzOQ2t8mavOhMhR0Pv1m0= github.com/aws/aws-sdk-go-v2/service/ecs v1.53.15 h1:uH0DMwDjLGgjjYMk3M1MXHggk37trTiJIvwyJNP17Ig= github.com/aws/aws-sdk-go-v2/service/ecs v1.53.15/go.mod h1:49tE5yYdlAHqZIO8u5+u9Xy9k8IaV0v5cstZrjnX5+c= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3 h1:eAh2A4b5IzM/lum78bZ590jy36+d/aFLgKF/4Vd1xPE= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3/go.mod h1:0yKJC/kb8sAnmlYa6Zs3QVYqaC8ug2AbnNChv5Ox3uA= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.15 h1:dM9/92u2F1JbDaGooxTq18wmmFzbJRfXfVfy96/1CXM= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.15/go.mod h1:SwFBy2vjtA0vZbjjaFtfN045boopadnoVPhu4Fv66vY= -github.com/aws/aws-sdk-go-v2/service/lightsail v1.43.1 h1:0j58UseBtLuBcP6nY2z4SM1qZEvLF0ylyH6+ggnphLg= -github.com/aws/aws-sdk-go-v2/service/lightsail v1.43.1/go.mod h1:Qy22QnQSdHbZwMZrarsWZBIuK51isPlkD+Z4sztxX0o= -github.com/aws/aws-sdk-go-v2/service/route53 v1.50.0 h1:/nkJHXtJXJeelXHqG0898+fWKgvfaXBhGzbCsSmn9j8= -github.com/aws/aws-sdk-go-v2/service/route53 v1.50.0/go.mod h1:kGYOjvTa0Vw0qxrqrOLut1vMnui6qLxqv/SX3vYeM8Y= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.1 h1:oegbebPEMA/1Jny7kvwejowCaHz1FWZAQ94WXFNCyTM= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.1/go.mod h1:kemo5Myr9ac0U9JfSjMo9yHLtw+pECEHsFtJ9tqCEI8= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.9 h1:5r34CgVOD4WZudeEKZ9/iKpiT6cM1JyEROpXjOcdWv8= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.9/go.mod h1:dB12CEbNWPbzO2uC6QSWHteqOg4JfBVJOojbAoAUb5I= +github.com/aws/aws-sdk-go-v2/service/lightsail v1.50.0 h1:JOLRYFWMMKUABCp94HHfo0JBVQDVTLXOvWWphjpBBiQ= +github.com/aws/aws-sdk-go-v2/service/lightsail v1.50.0/go.mod h1:WEOSRNyfIfvgrD9MuSIGrogKyuFahaVMziVq1pHI0NQ= +github.com/aws/aws-sdk-go-v2/service/route53 v1.58.4 h1:KycXrohD5OxAZ5h02YechO2gevvoHfAPAaJM5l8zqb0= +github.com/aws/aws-sdk-go-v2/service/route53 v1.58.4/go.mod h1:xNLZLn4SusktBQ5moqUOgiDKGz3a7vHwF4W0KD+WBPc= github.com/aws/aws-sdk-go-v2/service/ssm v1.56.13 h1:JfPeW7F6Y+VqBg6p+8zQv4wlgceguYu5ZT0USEGZ89g= github.com/aws/aws-sdk-go-v2/service/ssm v1.56.13/go.mod h1:EonGQFn66wZkJJrrKXrryrxoS3V30rcHvaWvc6oGHCI= -github.com/aws/aws-sdk-go-v2/service/sso v1.25.1 h1:8JdC7Gr9NROg1Rusk25IcZeTO59zLxsKgE0gkh5O6h0= -github.com/aws/aws-sdk-go-v2/service/sso v1.25.1/go.mod h1:qs4a9T5EMLl/Cajiw2TcbNt2UNo/Hqlyp+GiuG4CFDI= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.29.1 h1:KwuLovgQPcdjNMfFt9OhUd9a2OwcOKhxfvF4glTzLuA= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.29.1/go.mod h1:MlYRNmYu/fGPoxBQVvBYr9nyr948aY/WLUvwBMBJubs= -github.com/aws/aws-sdk-go-v2/service/sts v1.33.17 h1:PZV5W8yk4OtH1JAuhV2PXwwO9v5G5Aoj+eMCn4T+1Kc= -github.com/aws/aws-sdk-go-v2/service/sts v1.33.17/go.mod h1:cQnB8CUnxbMU82JvlqjKR2HBOm3fe9pWorWBza6MBJ4= +github.com/aws/aws-sdk-go-v2/service/sso v1.29.6 h1:A1oRkiSQOWstGh61y4Wc/yQ04sqrQZr1Si/oAXj20/s= +github.com/aws/aws-sdk-go-v2/service/sso v1.29.6/go.mod h1:5PfYspyCU5Vw1wNPsxi15LZovOnULudOQuVxphSflQA= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.1 h1:5fm5RTONng73/QA73LhCNR7UT9RpFH3hR6HWL6bIgVY= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.1/go.mod h1:xBEjWD13h+6nq+z4AkqSfSvqRKFgDIQeaMguAJndOWo= +github.com/aws/aws-sdk-go-v2/service/sts v1.38.6 h1:p3jIvqYwUZgu/XYeI48bJxOhvm47hZb5HUQ0tn6Q9kA= +github.com/aws/aws-sdk-go-v2/service/sts v1.38.6/go.mod h1:WtKK+ppze5yKPkZ0XwqIVWD4beCwv056ZbPQNoeHqM8= github.com/aws/smithy-go v1.8.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E= -github.com/aws/smithy-go v1.22.2 h1:6D9hW43xKFrRx/tXXfAlIZc4JI+yQe6snnWcQyxSyLQ= -github.com/aws/smithy-go v1.22.2/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg= -github.com/baidubce/bce-sdk-go v0.9.223 h1:vvDeIemf7ePPP59nLHCntQ/vS++ok2HKbRPgmz1VZKU= -github.com/baidubce/bce-sdk-go v0.9.223/go.mod h1:zbYJMQwE4IZuyrJiFO8tO8NbtYiKTFTbwh4eIsqjVdg= +github.com/aws/smithy-go v1.23.0 h1:8n6I3gXzWJB2DxBDnfxgBaSX6oe0d/t10qGz7OKqMCE= +github.com/aws/smithy-go v1.23.0/go.mod h1:t1ufH5HMublsJYulve2RKmHDC15xu1f26kHCp/HgceI= +github.com/aziontech/azionapi-go-sdk v0.143.0 h1:4eEBlYT10prgeCVTNR9FIc7f59Crbl2zrH1a4D1BUqU= +github.com/aziontech/azionapi-go-sdk v0.143.0/go.mod h1:cA5DY/VP4X5Eu11LpQNzNn83ziKjja7QVMIl4J45feA= +github.com/baidubce/bce-sdk-go v0.9.248 h1:vB5OMuEC2xnO197M6OWUi24C8mYOZHKXT/8HuKQJUhU= +github.com/baidubce/bce-sdk-go v0.9.248/go.mod h1:zbYJMQwE4IZuyrJiFO8tO8NbtYiKTFTbwh4eIsqjVdg= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= -github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A= -github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +github.com/benbjohnson/clock v1.3.5 h1:VvXlSJBzZpA/zum6Sj74hxwYI2DIxRWuNIoXAzHZz5o= +github.com/benbjohnson/clock v1.3.5/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= @@ -215,6 +264,8 @@ github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInq github.com/cenkalti/backoff/v4 v4.1.2/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= +github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= @@ -232,21 +283,23 @@ github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5P github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= -github.com/civo/civogo v0.3.11 h1:mON/fyrV946Sbk6paRtOSGsN+asCgCmHCgArf5xmGxM= -github.com/civo/civogo v0.3.11/go.mod h1:7+GeeFwc4AYTULaEshpT2vIcl3Qq8HPoxA17viX3l6g= github.com/clbanning/mxj v1.8.4/go.mod h1:BVjHeAH+rl9rs6f+QIpeRl0tfu10SXn1pUSa5PVGJng= +github.com/clbanning/mxj/v2 v2.7.0 h1:WA/La7UGCanFe5NpHF0Q3DNtnCsVoxbPKuyBNHWRyME= +github.com/clbanning/mxj/v2 v2.7.0/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cloudflare/cloudflare-go v0.115.0 h1:84/dxeeXweCc0PN5Cto44iTA8AkG1fyT11yPO5ZB7sM= -github.com/cloudflare/cloudflare-go v0.115.0/go.mod h1:Ds6urDwn/TF2uIU24mu7H91xkKP8gSAHxQ44DSZgVmU= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/containerd/containerd v1.7.20 h1:Sl6jQYk3TRavaU83h66QMbI2Nqg9Jm6qzwX57Vsn1SQ= -github.com/containerd/containerd v1.7.20/go.mod h1:52GsS5CwquuqPuLncsXwG0t2CiUce+KsNHJZQJvAgR0= +github.com/containerd/containerd v1.7.23 h1:H2CClyUkmpKAGlhQp95g2WXHfLYc7whAuvZGBNYOOwQ= +github.com/containerd/containerd v1.7.23/go.mod h1:7QUzfURqZWCZV7RLNEn1XjUCQLEf0bkaK4GjUaZehxw= +github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI= +github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= +github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE= +github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= -github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A= -github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw= +github.com/containerd/platforms v1.0.0-rc.1 h1:83KIq4yy1erSRgOVHNk1HYdPvzdJ5CnsWaRoJX4C41E= +github.com/containerd/platforms v1.0.0-rc.1/go.mod h1:J71L7B+aiM5SdIEqmd9wp6THLVRzJGXfNuWCZCllLA4= github.com/containous/alice v0.0.0-20181107144136-d83ebdd94cbd h1:0n+lFLh5zU0l6KSk3KpnDwfbPGAR44aRLgTbCnhRBHU= github.com/containous/alice v0.0.0-20181107144136-d83ebdd94cbd/go.mod h1:BbQgeDS5i0tNvypwEoF1oNjOJw8knRAE1DnVvjDstcQ= github.com/containous/go-http-auth v0.4.1-0.20200324110947-a37a7636d23e h1:D+uTEzDZc1Fhmd0Pq06c+O9+KkAyExw0eVmu/NOqaHU= @@ -292,12 +345,12 @@ github.com/dimchansky/utfbom v1.1.1 h1:vV6w1AhK4VMnhBno/TPVCoK9U/LP0PkLCS9tbxHdi github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE= github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= -github.com/dnsimple/dnsimple-go v1.7.0 h1:JKu9xJtZ3SqOC+BuYgAWeab7+EEx0sz422vu8j611ZY= -github.com/dnsimple/dnsimple-go v1.7.0/go.mod h1:EKpuihlWizqYafSnQHGCd/gyvy3HkEQJ7ODB4KdV8T8= -github.com/docker/cli v27.1.1+incompatible h1:goaZxOqs4QKxznZjjBWKONQci/MywhtRv2oNn0GkeZE= -github.com/docker/cli v27.1.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= -github.com/docker/docker v27.1.1+incompatible h1:hO/M4MtV36kzKldqnA37IWhebRA+LnqqcqDja6kVaKY= -github.com/docker/docker v27.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/dnsimple/dnsimple-go/v4 v4.0.0 h1:nUCICZSyZDiiqimAAL+E8XL+0sKGks5VRki5S8XotRo= +github.com/dnsimple/dnsimple-go/v4 v4.0.0/go.mod h1:AXT2yfAFOntJx6iMeo1J/zKBw0ggXFYBt4e97dqqPnc= +github.com/docker/cli v28.3.3+incompatible h1:fp9ZHAr1WWPGdIWBM1b3zLtgCF+83gRdVMTJsUeiyAo= +github.com/docker/cli v28.3.3+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/docker v28.3.3+incompatible h1:Dypm25kh4rmk49v1eiVbsAtpAsYURjYkaKubwuBdxEI= +github.com/docker/docker v28.3.3+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= @@ -319,18 +372,17 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.m github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/evanphx/json-patch v5.7.0+incompatible h1:vgGkfT/9f8zE6tvSCe74nfpAVDQ2tG6yudJd8LBksgI= -github.com/evanphx/json-patch v5.7.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= -github.com/evanphx/json-patch/v5 v5.9.0 h1:kcBlZQbplgElYIlo/n1hJbls2z/1awpXxpRi0/FOJfg= -github.com/evanphx/json-patch/v5 v5.9.0/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ= -github.com/exoscale/egoscale/v3 v3.1.13 h1:CAGC7QRjp2AiGj01agsSD0VKCp4OZmW5f51vV2IguNQ= -github.com/exoscale/egoscale/v3 v3.1.13/go.mod h1:t9+MpSEam94na48O/xgvvPFpQPRiwZ3kBN4/UuQtKco= +github.com/evanphx/json-patch/v5 v5.9.11 h1:/8HVnzMq13/3x9TPvjG08wUGqBTmZBsCWzjTM0wiaDU= +github.com/evanphx/json-patch/v5 v5.9.11/go.mod h1:3j+LviiESTElxA4p3EMKAB9HXj3/XEtnUf6OZxqIQTM= +github.com/exoscale/egoscale/v3 v3.1.27 h1:vKdWZG8QFDc7rY7lCfcuudO+ovyp5psYjFwKVqmkhCE= +github.com/exoscale/egoscale/v3 v3.1.27/go.mod h1:0iY8OxgHJCS5TKqDNhwOW95JBKCnBZl3YGU4Yt+NqkU= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= +github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= github.com/fatih/color v1.12.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= -github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4= -github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI= +github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= +github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= @@ -345,12 +397,12 @@ github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7z github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU= -github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M= -github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= +github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= +github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= -github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= -github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= +github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= +github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= github.com/getkin/kin-openapi v0.61.0/go.mod h1:7Yn5whZr5kJi6t+kShccXS8ae1APpYTW6yheSwk8Yi4= github.com/getkin/kin-openapi v0.87.0/go.mod h1:660oXbgy5JFMKreazJaQTw7o+X00qeSyhcnluiMv+Xg= github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= @@ -361,8 +413,14 @@ github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwv github.com/gin-gonic/gin v1.7.4/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY= github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg= github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU= -github.com/go-acme/lego/v4 v4.23.1 h1:lZ5fGtGESA2L9FB8dNTvrQUq3/X4QOb8ExkKyY7LSV4= -github.com/go-acme/lego/v4 v4.23.1/go.mod h1:7UMVR7oQbIYw6V7mTgGwi4Er7B6Ww0c+c8feiBM0EgI= +github.com/go-acme/alidns-20150109/v4 v4.6.1 h1:Dch3aWRcw4U62+jKPjPQN3iW3TPvgIywATbvHzojXeo= +github.com/go-acme/alidns-20150109/v4 v4.6.1/go.mod h1:RBcqBA5IvUWtlpjx6dC6EkPVyBNLQ+mR18XoaP38BFY= +github.com/go-acme/lego/v4 v4.27.0 h1:cIhWd7Uj4BNFLEF3IpwuMkukVVRs5qjlp4KdUGa75yU= +github.com/go-acme/lego/v4 v4.27.0/go.mod h1:9FfNZHZmg6hf5CWOp4Lzo4gU8aBEvqZvrwdkBboa+4g= +github.com/go-acme/tencentclouddnspod v1.1.10 h1:ERVJ4mc3cT4Nb3+n6H/c1AwZnChGBqLoymE0NVYscKI= +github.com/go-acme/tencentclouddnspod v1.1.10/go.mod h1:Bo/0YQJ/99FM+44HmCQkByuptX1tJsJ9V14MGV/2Qco= +github.com/go-acme/tencentedgdeone v1.1.19 h1:1jdEpMITrDuXHnu7QLOy2hpLW0BlDof70/KuRT+EiTo= +github.com/go-acme/tencentedgdeone v1.1.19/go.mod h1:gpu7HvXfcKWBrq5HAEBowZNkdk7JFPkagRzC/infUY0= 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= github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w= @@ -370,8 +428,8 @@ github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-jose/go-jose/v4 v4.0.5 h1:M6T8+mKZl/+fNNuFHvGIzDz7BTLQPIounk/b9dw3AaE= -github.com/go-jose/go-jose/v4 v4.0.5/go.mod h1:s3P1lRrkT8igV8D9OjyL4WRyHvjB6a4JSllnOrmmBOA= +github.com/go-jose/go-jose/v4 v4.1.3 h1:CVLmWDhDVRa6Mi/IgCgaopNosCaHz7zrMeF9MlZRkrs= +github.com/go-jose/go-jose/v4 v4.1.3/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.12.0/go.mod h1:lHd+EkCZPIwYItmGDDRdhinkzX2A1sj+M9biaEaizzs= @@ -387,8 +445,8 @@ github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG github.com/go-logfmt/logfmt v0.5.1 h1:otpy5pqBCBZ1ng9RQ0dPu4PN7ba75Y/aA+UpowDyNVA= github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= -github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= @@ -403,6 +461,8 @@ github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDsl github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= +github.com/go-ozzo/ozzo-validation/v4 v4.3.0 h1:byhDUpfEwjsVQb1vBunvIjh2BHQ9ead57VkAEY4V+Es= +github.com/go-ozzo/ozzo-validation/v4 v4.3.0/go.mod h1:2NKgrcHl3z6cJs+3Oo940FPRiTzuqKbvfrL2RxCj6Ew= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= @@ -417,16 +477,17 @@ github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91 github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI= github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= github.com/go-playground/validator/v10 v10.9.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos= -github.com/go-playground/validator/v10 v10.16.0 h1:x+plE831WK4vaKHO/jpgUGsvLKIqRRkz6M78GuJAfGE= -github.com/go-playground/validator/v10 v10.16.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= +github.com/go-playground/validator/v10 v10.23.0 h1:/PwmTwZhS0dPkav3cdK9kV1FsAmrL8sThn8IHr/sO+o= +github.com/go-playground/validator/v10 v10.23.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= github.com/go-resty/resty/v2 v2.16.5 h1:hBKqmWrr7uRc3euHVqmh1HTHcKn99Smr7o5spptdhTM= github.com/go-resty/resty/v2 v2.16.5/go.mod h1:hkJtXbA2iKHzJheXYvQ8snQES5ZLGKMwQ07xAwp/fiA= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= -github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss= -github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= +github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs= +github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/go-zookeeper/zk v1.0.2/go.mod h1:nOB03cncLtlp4t+UAkGSV+9beXP/akpekBwL+UX1Qcw= github.com/go-zookeeper/zk v1.0.3 h1:7M2kwOsc//9VeeFiPtf+uSJlVpU66x9Ba5+8XK7/TDg= github.com/go-zookeeper/zk v1.0.3/go.mod h1:nOB03cncLtlp4t+UAkGSV+9beXP/akpekBwL+UX1Qcw= @@ -439,25 +500,26 @@ github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6Wezm github.com/gobwas/ws v1.0.2 h1:CoAavW/wd/kulfZmSIBt6p24n4j7tHgNVCjsfHVNUbo= github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM= github.com/goccy/go-json v0.7.8/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= -github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= -github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= +github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= +github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/goccy/go-yaml v1.9.8/go.mod h1:JubOolP3gh0HpiBc4BLRD4YmjEjHAmIIB2aaXKkTfoE= +github.com/goccy/go-yaml v1.11.3 h1:B3W9IdWbvrUu2OYQGwvU1nZtvMQJPBKgBUuweJjLj6I= +github.com/goccy/go-yaml v1.11.3/go.mod h1:wKnAMd44+9JAAnGQpWVEgBzGt3YuTaQ4uXoHvE4m7WU= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= github.com/gofrs/flock v0.12.1 h1:MTLVXXHf8ekldpJk3AKicLij9MdwOWkZ+a/jHHZby9E= github.com/gofrs/flock v0.12.1/go.mod h1:9zxTsyu5xtJ9DK+1tFZyibEV7y3uwDxPPfbxeeHCoD0= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptGaCkuDUx6wNykzzlUixGxvkme+H/lnzb+A= github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI= github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= -github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8= -github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= +github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo= +github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -497,8 +559,8 @@ github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEW github.com/golangci/lint-1 v0.0.0-20181222135242-d2cdd8c08219/go.mod h1:/X8TswGSh1pIozq4ZwCfxS0WA5JGXguxk94ar/4c87Y= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -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/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= +github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= @@ -537,8 +599,8 @@ github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hf github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20240910150728-a0b0bb1d4134 h1:c5FlPPgxOn7kJz3VoPLkQYQXGBS3EklQ4Zfi57uOuqQ= -github.com/google/pprof v0.0.0-20240910150728-a0b0bb1d4134/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= +github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad h1:a6HEuzUHeKH6hwfN/ZoQgRgVIWFJljSWa/zetS2WTvg= +github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0= github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM= @@ -551,14 +613,15 @@ github.com/googleapis/enterprise-certificate-proxy v0.3.6 h1:GW/XbdyBFQ8Qe+YAmFU github.com/googleapis/enterprise-certificate-proxy v0.3.6/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/googleapis/gax-go/v2 v2.14.1 h1:hb0FFeiPaQskmvakKu5EbCbpntQn48jyHuvrkurSS/Q= -github.com/googleapis/gax-go/v2 v2.14.1/go.mod h1:Hb/NubMaVM88SrNkvl8X/o8XWwDJEPqouaLeN2IUxoA= +github.com/googleapis/gax-go/v2 v2.15.0 h1:SyjDc1mGgZU5LncH8gimWo9lW1DtIfPibOG81vgd/bo= +github.com/googleapis/gax-go/v2 v2.15.0/go.mod h1:zVVkkxAQHa1RQpg9z2AUCMnKhi0Qld9rcmyfL1OZhoc= github.com/gophercloud/gophercloud v1.3.0/go.mod h1:aAVqcocTSXh2vYFZ1JTvx4EQmfgzxRcNupUfxZbBNDM= github.com/gophercloud/gophercloud v1.14.1 h1:DTCNaTVGl8/cFu58O1JwWgis9gtISAFONqpMKNg/Vpw= github.com/gophercloud/gophercloud v1.14.1/go.mod h1:aAVqcocTSXh2vYFZ1JTvx4EQmfgzxRcNupUfxZbBNDM= github.com/gophercloud/utils v0.0.0-20231010081019-80377eca5d56 h1:sH7xkTfYzxIEgzq1tDHIMKRh1vThOEOGNsettdEeLbE= github.com/gophercloud/utils v0.0.0-20231010081019-80377eca5d56/go.mod h1:VSalo4adEk+3sNkmVJLnhHoOyOYYS8sTWLG4mv5BKto= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= @@ -572,10 +635,8 @@ github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmg github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.9.0/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.23.0 h1:ad0vkEBuk23VJzZR9nkLVG0YAoN9coASF1GusYX6AlU= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.23.0/go.mod h1:igFoXX2ELCW06bol23DWPB5BEWfZISOzSP5K2sbLea0= -github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw= -github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 h1:8Tjv8EJ+pM1xP8mK6egEbD1OgnVTyacbefKhmbLhIhU= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2/go.mod h1:pkJQ2tZHJ0aFOVEEot6oZmaVEZcRme73eIFmhiVuRWs= github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= github.com/hashicorp/consul/api v1.10.1/go.mod h1:XjsvQN+RJGWI2TWy1/kqaE16HrR2J/FWgkYjdZQsX9M= github.com/hashicorp/consul/api v1.26.1 h1:5oSXOO5fboPZeW5SN+TdGFP/BILDgBm19OrPZ/pICIM= @@ -608,8 +669,8 @@ github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+ github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= -github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU= -github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk= +github.com/hashicorp/go-retryablehttp v0.7.8 h1:ylXZWnqa7Lhqpk0L1P1LzDtGcCR0rPVUrx/c8Unxc48= +github.com/hashicorp/go-retryablehttp v0.7.8/go.mod h1:rjiScheydd+CxvumBsIrFKlx3iS0jrZ7LvzFGFmuKbw= github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc= github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= @@ -653,8 +714,8 @@ github.com/http-wasm/http-wasm-host-go v0.7.0/go.mod h1:adXKcLmL7yuavH/e0kBAp7b3 github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI= github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= -github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.141 h1:8i57QAi5u+iPAYze92bkIvZoHiS0J45ndul5glr/NE8= -github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.141/go.mod h1:Y/+YLCFCJtS29i2MbYPTUlNNfwXvkzEsZKR0imY/2aY= +github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.172 h1:68VUVbLKwBxPh8tjCXwnLO/d8/thdZ+ExpxdFMEdK5A= +github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.172/go.mod h1:M+yna96Fx9o5GbIUnF3OvVvQGjgfVSyeJbV9Yb1z/wI= github.com/hudl/fargo v1.4.0/go.mod h1:9Ai6uvFy5fQNq6VPKtg+Ceq1+eTY4nKUlR2JElEOcDo= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= @@ -670,20 +731,18 @@ github.com/influxdata/influxdb1-client v0.0.0-20200827194710-b269163b24ab h1:HqW github.com/influxdata/influxdb1-client v0.0.0-20200827194710-b269163b24ab/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839 h1:W9WBk7wlPfJLvMCdtV4zPulc4uCPrlywQOmbFOhgQNU= github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839/go.mod h1:xaLFMmpvUxqXtVkUJfg9QmT88cDaCJ3ZKgdZ78oO8Qo= -github.com/infobloxopen/infoblox-go-client/v2 v2.9.0 h1:wS8kTlQVeVbrepeY83s9X+XdSa6Qah5KO+tdW+zRQXU= -github.com/infobloxopen/infoblox-go-client/v2 v2.9.0/go.mod h1:NeNJpz09efw/edzqkVivGv1bWqBXTomqYBRFbP+XBqg= +github.com/infobloxopen/infoblox-go-client/v2 v2.10.0 h1:AKsihjFT/t6Y0keEv3p59DACcOuh0inWXdUB0ZOzYH0= +github.com/infobloxopen/infoblox-go-client/v2 v2.10.0/go.mod h1:NeNJpz09efw/edzqkVivGv1bWqBXTomqYBRFbP+XBqg= github.com/jarcoal/httpmock v1.0.8/go.mod h1:ATjnClrvW/3tijVmpL/va5Z3aAyGvqU3gCT8nX0Txik= -github.com/jarcoal/httpmock v1.3.1 h1:iUx3whfZWVf3jT01hQTO/Eo5sAYtB2/rqaUuOtpInww= -github.com/jarcoal/httpmock v1.3.1/go.mod h1:3yb8rc4BI7TCBhFY8ng0gjuLKJNquuDNiPaZjnENuYg= +github.com/jarcoal/httpmock v1.4.1 h1:0Ju+VCFuARfFlhVXFc2HxlcQkfB+Xq12/EotHko+x2A= +github.com/jarcoal/httpmock v1.4.1/go.mod h1:ftW1xULwo+j0R0JJkJIIi7UKigZUXCLLanykgjwBXL0= github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs= github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM= github.com/jcmturner/gofork v1.0.0/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o= github.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg= github.com/jcmturner/gokrb5/v8 v8.4.2/go.mod h1:sb+Xq/fTY5yktf/VxLsE3wlfPqQjp0aWNYyvBVK62bc= github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc= -github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= -github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= @@ -699,8 +758,9 @@ github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/u github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/json-iterator/go v1.1.13-0.20220915233716-71ac16282d12 h1:9Nu54bhS/H/Kgo2/7xNSUuC5G28VR8ljfrLKU2G4IjU= +github.com/json-iterator/go v1.1.13-0.20220915233716-71ac16282d12/go.mod h1:TBzl5BIHNXfS9+C35ZyJaklL7mLDbgUkcgXzSLa8Tk0= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= @@ -709,17 +769,16 @@ github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8 github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213 h1:qGQQKEcAR99REcMpsXCp3lJ03zYT1PkRd3kQGPn9GVg= github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213/go.mod h1:vNUNkEQ1e29fT/6vq2aBdFsgNPmy8qMdSay1npru+Sw= -github.com/keybase/go-keychain v0.0.0-20231219164618-57a3676c3af6 h1:IsMZxCuZqKuao2vNdfD82fjjgPLfyHLpR41Z88viRWs= -github.com/keybase/go-keychain v0.0.0-20231219164618-57a3676c3af6/go.mod h1:3VeWNIJaW+O5xpRQbPp0Ybqu1vJd/pm7s2F473HRrkw= +github.com/keybase/go-keychain v0.0.1 h1:way+bWYa6lDppZoZcgMbYsvC7GxljxrskdNInRtuthU= +github.com/keybase/go-keychain v0.0.1/go.mod h1:PdEILRW3i9D8JcdM+FmY6RwkHGnhHxXwkPPMeUgOK1k= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.13.4/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= -github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= -github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= -github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= +github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= +github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg= github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= @@ -742,10 +801,10 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kvtools/consul v1.0.2 h1:ltPgs4Ld09Xaa7zrOJ/TewBYKAsr11/LRFpErdkb8AA= github.com/kvtools/consul v1.0.2/go.mod h1:bFnzfGJ5ZIRRXCBGBmwhJlLdEWOlrjOcS1WjyAQzaJA= -github.com/kvtools/etcdv3 v1.0.2 h1:EB0mAtzqe1folE7m7Q6wnCXcGwaOmrYmsVmF3hNsTKI= -github.com/kvtools/etcdv3 v1.0.2/go.mod h1:Xr6DbwqjuCEcXAIWmXxw0DX+N5BhuvablXgN90XeqMM= -github.com/kvtools/redis v1.1.0 h1:nXRAyh2nsaWiJyrX449/qHMc3SvGUqRqRXcrA/MplEo= -github.com/kvtools/redis v1.1.0/go.mod h1:cqg3esJOIYMQ1qy5LVIbPZz9kuiBBcFREP2N5b9+Dn0= +github.com/kvtools/etcdv3 v1.0.3 h1:bsaGf8Jsi8Xq6h/KVV/D7F/c1IuVQv2f7tuVxeA//fk= +github.com/kvtools/etcdv3 v1.0.3/go.mod h1:ID4AIRgCuCRzzdITo9O5RKUtLwfu/zJvCvosrFcBK4U= +github.com/kvtools/redis v1.2.0 h1:l2wT//fjNPXS66kENuUdIQrSReq4OQxmL4pKH4T65c4= +github.com/kvtools/redis v1.2.0/go.mod h1:EPXcbf7IfiIH05eBDSrR9RKQQmxI83JKL4tsUhPECuk= github.com/kvtools/valkeyrie v1.0.0 h1:LAITop2wPoYCMitR24GZZsW0b57hmI+ePD18VRTtOf0= github.com/kvtools/valkeyrie v1.0.0/go.mod h1:bDi/OdhJCSbGPMsCgUQl881yuEweKCSItAtTBI+ZjpU= github.com/kvtools/zookeeper v1.0.2 h1:uK0CzQa+mtKGxDDH+DeqXo2HC1Kx4hWXZ7pX/zS4aTo= @@ -762,8 +821,8 @@ github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL github.com/labstack/gommon v0.3.1/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= -github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= -github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= +github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= +github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/lestrrat-go/backoff/v2 v2.0.8/go.mod h1:rHP/q/r9aT27n24JQLa7JhSQZCKBBOiM/uP402WwN8Y= github.com/lestrrat-go/blackmagic v1.0.0/go.mod h1:TNgH//0vYSs8VXDCfkZLgIrVTTXQELZffUV0tz3MtdQ= github.com/lestrrat-go/codegen v1.0.2/go.mod h1:JhJw6OQAuPEfVKUCLItpaVLumDGWQznd1VaXrBk9TdM= @@ -771,15 +830,16 @@ github.com/lestrrat-go/httpcc v1.0.0/go.mod h1:tGS/u00Vh5N6FHNkExqGGNId8e0Big+++ github.com/lestrrat-go/iter v1.0.1/go.mod h1:zIdgO1mRKhn8l9vrZJZz9TUMMFbQbLeTsbqPDrJ/OJc= github.com/lestrrat-go/jwx v1.2.7/go.mod h1:bw24IXWbavc0R2RsOtpXL7RtMyP589yZ1+L7kd09ZGA= github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= -github.com/linode/linodego v1.48.1 h1:Ojw1S+K5jJr1dggO8/H6r4FINxXnJbOU5GkbpaTfmhU= -github.com/linode/linodego v1.48.1/go.mod h1:fc3t60If8X+yZTFAebhCnNDFrhwQhq9HDU92WnBousQ= +github.com/linode/linodego v1.60.0 h1:SgsebJFRCi+lSmYy+C40wmKZeJllGGm+W12Qw4+yVdI= +github.com/linode/linodego v1.60.0/go.mod h1:1+Bt0oTz5rBnDOJbGhccxn7LYVytXTIIfAy7QYmijDs= github.com/liquidweb/go-lwApi v0.0.0-20190605172801-52a4864d2738/go.mod h1:0sYF9rMXb0vlG+4SzdiGMXHheCZxjguMq+Zb4S2BfBs= github.com/liquidweb/liquidweb-cli v0.6.9 h1:acbIvdRauiwbxIsOCEMXGwF75aSJDbDiyAWPjVnwoYM= github.com/liquidweb/liquidweb-cli v0.6.9/go.mod h1:cE1uvQ+x24NGUL75D0QagOFCG8Wdvmwu8aL9TLmA/eQ= github.com/liquidweb/liquidweb-go v1.6.4 h1:6S0m3hHSpiLqGD7AFSb7lH/W/qr1wx+tKil9fgIbjMc= github.com/liquidweb/liquidweb-go v1.6.4/go.mod h1:B934JPIIcdA+uTq2Nz5PgOtG6CuCaEvQKe/Ge/5GgZ4= -github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= +github.com/lufia/plan9stats v0.0.0-20250317134145-8bc96cf8fc35 h1:PpXWgLPs+Fqr325bN2FD2ISlRRztXibcX6e8f5FR5Dc= +github.com/lufia/plan9stats v0.0.0-20250317134145-8bc96cf8fc35/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg= 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.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= @@ -804,8 +864,9 @@ github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= -github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= @@ -828,8 +889,8 @@ github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKju github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI= github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4= github.com/miekg/dns v1.1.47/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME= -github.com/miekg/dns v1.1.64 h1:wuZgD9wwCE6XMT05UU/mlSko71eRSXEAm2EbjQXLKnQ= -github.com/miekg/dns v1.1.64/go.mod h1:Dzw9769uoKVaLuODMDZz9M6ynFU6Em65csPuoi8G0ck= +github.com/miekg/dns v1.1.68 h1:jsSRkNozw7G/mnmXULynzMNIsgY2dHC8LO6U6Ij2JEA= +github.com/miekg/dns v1.1.68/go.mod h1:fujopn7TB3Pu3JM69XaawiU0wqjpL9/8xGop5UrTPps= github.com/mimuret/golang-iij-dpf v0.9.1 h1:Gj6EhHJkOhr+q2RnvRPJsPMcjuVnWPSccEHyoEehU34= github.com/mimuret/golang-iij-dpf v0.9.1/go.mod h1:sl9KyOkESib9+KRD3HaGpgi1xk7eoN2+d96LCLsME2M= github.com/minio/highwayhash v1.0.1/go.mod h1:BQskDq+xkJ12lmlUUi7U0M5Swg3EWR+dLTk+kldvVxY= @@ -855,30 +916,37 @@ github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:F github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.4.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= -github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/mapstructure v1.5.1-0.20231216201459-8508981c8b6c h1:cqn374mizHuIWj+OSJCajGr/phAmuMug9qIX3l9CflE= +github.com/mitchellh/mapstructure v1.5.1-0.20231216201459-8508981c8b6c/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/reflectwalk v1.0.0/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/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= +github.com/moby/go-archive v0.1.0 h1:Kk/5rdW/g+H8NHdJW2gsXyZ7UnzvJNOy6VKJqueWdcQ= +github.com/moby/go-archive v0.1.0/go.mod h1:G9B+YoujNohJmrIYFBpSd54GTUB4lt9S+xVQvsJyFuo= github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk= github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= -github.com/moby/spdystream v0.4.0 h1:Vy79D6mHeJJjiPdFEL2yku1kl0chZpJfZcPpb16BRl8= -github.com/moby/spdystream v0.4.0/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI= -github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc= -github.com/moby/sys/sequential v0.5.0/go.mod h1:tH2cOOs5V9MlPiXcQzRC+eEyab644PWKGRYaaV5ZZlo= -github.com/moby/sys/user v0.2.0 h1:OnpapJsRp25vkhw8TFG6OLJODNh/3rEwRWtJ3kakwRM= -github.com/moby/sys/user v0.2.0/go.mod h1:RYstrcWOJpVh+6qzUqp2bU3eaRpdiQeKGlKitaH0PM8= -github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= -github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= +github.com/moby/spdystream v0.5.0 h1:7r0J1Si3QO/kjRitvSLVVFUjxMEb/YLj6S9FF62JBCU= +github.com/moby/spdystream v0.5.0/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI= +github.com/moby/sys/atomicwriter v0.1.0 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w3A14Sw= +github.com/moby/sys/atomicwriter v0.1.0/go.mod h1:Ul8oqv2ZMNHOceF643P6FKPXeCmYtlQMvpizfsSoaWs= +github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU= +github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko= +github.com/moby/sys/user v0.4.0 h1:jhcMKit7SA80hivmFJcbB1vqmw//wU61Zdui2eQXuMs= +github.com/moby/sys/user v0.4.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs= +github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g= +github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28= +github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ= +github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8= +github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= @@ -889,8 +957,8 @@ github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f h1:KUppIJq7/+ github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= -github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04 h1:o6uBwrhM5C8Ll3MAAxrQxRHEu7FkapwTuI2WmL1rw4g= -github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04/go.mod h1:5sN+Lt1CaY4wsPvgQH/jsuJi4XO2ssZbdsIizr4CVC8= +github.com/namedotcom/go/v4 v4.0.2 h1:4gNkPaPRG/2tqFNUUof7jAVsA6vDutFutEOd7ivnDwA= +github.com/namedotcom/go/v4 v4.0.2/go.mod h1:J6sVueHMb0qbarPgdhrzEVhEaYp+R1SCaTGl2s6/J1Q= github.com/nats-io/jwt v1.2.2/go.mod h1:/xX356yQA6LuXI9xWW7mZNpxgF2mBmGecH+Fj34sP5Q= github.com/nats-io/jwt/v2 v2.0.3/go.mod h1:VRP+deawSXyhNjXmxPCHskrR6Mq50BqpEI5SEcNiGlY= github.com/nats-io/nats-server/v2 v2.5.0/go.mod h1:Kj86UtrXAL6LwYRA6H4RqzkHhK0Vcv2ZnKD5WbQ1t3g= @@ -898,28 +966,31 @@ github.com/nats-io/nats.go v1.12.1/go.mod h1:BPko4oXsySz4aSWeFgOHLZs3G4Jq4ZAyE6/ github.com/nats-io/nkeys v0.2.0/go.mod h1:XdZpAbhgyyODYqjTawOnIOI7VlbKSarI9Gfy1tqEu/s= github.com/nats-io/nkeys v0.3.0/go.mod h1:gvUNGjVcM2IPr5rCsRsC6Wb3Hr2CQAm08dsxtV6A5y4= github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= -github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nrdcg/auroradns v1.1.0 h1:KekGh8kmf2MNwqZVVYo/fw/ZONt8QMEmbMFOeljteWo= github.com/nrdcg/auroradns v1.1.0/go.mod h1:O7tViUZbAcnykVnrGkXzIJTHoQCHcgalgAe6X1mzHfk= -github.com/nrdcg/bunny-go v0.0.0-20240207213615-dde5bf4577a3 h1:ouZ2JWDl8IW5k1qugYbmpbmW8hn85Ig6buSMBRlz3KI= -github.com/nrdcg/bunny-go v0.0.0-20240207213615-dde5bf4577a3/go.mod h1:ZwadWt7mVhMHMbAQ1w8IhDqtWO3eWqWq72W7trnaiE8= -github.com/nrdcg/desec v0.10.0 h1:qrEDiqnsvNU9QE7lXIXi/tIHAfyaFXKxF2/8/52O8uM= -github.com/nrdcg/desec v0.10.0/go.mod h1:5+4vyhMRTs49V9CNoODF/HwT8Mwxv9DJ6j+7NekUnBs= +github.com/nrdcg/bunny-go v0.0.0-20250327222614-988a091fc7ea h1:OSgRS4kqOs/WuxuFOObP2gwrenL4/qiKXQbQugr/Two= +github.com/nrdcg/bunny-go v0.0.0-20250327222614-988a091fc7ea/go.mod h1:IDRRngAngb2eTEaWgpO0hukQFI/vJId46fT1KErMytA= +github.com/nrdcg/desec v0.11.0 h1:XZVHy07sg12y8FozMp+l7XvzPsdzog0AYXuQMaHBsfs= +github.com/nrdcg/desec v0.11.0/go.mod h1:5+4vyhMRTs49V9CNoODF/HwT8Mwxv9DJ6j+7NekUnBs= github.com/nrdcg/dnspod-go v0.4.0 h1:c/jn1mLZNKF3/osJ6mz3QPxTudvPArXTjpkmYj0uK6U= github.com/nrdcg/dnspod-go v0.4.0/go.mod h1:vZSoFSFeQVm2gWLMkyX61LZ8HI3BaqtHZWgPTGKr6KQ= github.com/nrdcg/freemyip v0.3.0 h1:0D2rXgvLwe2RRaVIjyUcQ4S26+cIS2iFwnhzDsEuuwc= github.com/nrdcg/freemyip v0.3.0/go.mod h1:c1PscDvA0ukBF0dwelU/IwOakNKnVxetpAQ863RMJoM= github.com/nrdcg/goacmedns v0.2.0 h1:ADMbThobzEMnr6kg2ohs4KGa3LFqmgiBA22/6jUWJR0= github.com/nrdcg/goacmedns v0.2.0/go.mod h1:T5o6+xvSLrQpugmwHvrSNkzWht0UGAwj2ACBMhh73Cg= -github.com/nrdcg/goinwx v0.10.0 h1:6W630bjDxQD6OuXKqrFRYVpTt0G/9GXXm3CeOrN0zJM= -github.com/nrdcg/goinwx v0.10.0/go.mod h1:mnMSTi7CXBu2io4DzdOBoGFA1XclD0sEPWJaDhNgkA4= +github.com/nrdcg/goinwx v0.11.0 h1:GER0SE3POub7rxARt3Y3jRy1OON1hwF1LRxHz5xsFBw= +github.com/nrdcg/goinwx v0.11.0/go.mod h1:0BXSC0FxVtU4aTjX0Zw3x0DK32tjugLzeNIAGtwXvPQ= github.com/nrdcg/mailinabox v0.2.0 h1:IKq8mfKiVwNW2hQii/ng1dJ4yYMMv3HAP3fMFIq2CFk= github.com/nrdcg/mailinabox v0.2.0/go.mod h1:0yxqeYOiGyxAu7Sb94eMxHPIOsPYXAjTeA9ZhePhGnc= -github.com/nrdcg/namesilo v0.2.1 h1:kLjCjsufdW/IlC+iSfAqj0iQGgKjlbUUeDJio5Y6eMg= -github.com/nrdcg/namesilo v0.2.1/go.mod h1:lwMvfQTyYq+BbjJd30ylEG4GPSS6PII0Tia4rRpRiyw= +github.com/nrdcg/namesilo v0.5.0 h1:6QNxT/XxE+f5B+7QlfWorthNzOzcGlBLRQxqi6YeBrE= +github.com/nrdcg/namesilo v0.5.0/go.mod h1:4UkwlwQfDt74kSGmhLaDylnBrD94IfflnpoEaj6T2qw= github.com/nrdcg/nodion v0.1.0 h1:zLKaqTn2X0aDuBHHfyA1zFgeZfiCpmu/O9DM73okavw= github.com/nrdcg/nodion v0.1.0/go.mod h1:inbuh3neCtIWlMPZHtEpe43TmRXxHV6+hk97iCZicms= +github.com/nrdcg/oci-go-sdk/common/v1065 v1065.102.0 h1:W28ZizQSS2aRWkFA3iAP9eiZS4OLFaiv35nXtq2lW/s= +github.com/nrdcg/oci-go-sdk/common/v1065 v1065.102.0/go.mod h1:cVbzGjRhtXgrduaQbR1GR1x+VDU60NcXPMZ3+eQuiiY= +github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.102.0 h1:gAOs1dkE7LFoWflzqrDqAhOprc0kF1a0fyV8C4HUPj4= +github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.102.0/go.mod h1:EUBSYwop1K40VpcKy1haIK6kFK/gPT1atEk89OkY0Kg= github.com/nrdcg/porkbun v0.4.0 h1:rWweKlwo1PToQ3H+tEO9gPRW0wzzgmI/Ob3n2Guticw= github.com/nrdcg/porkbun v0.4.0/go.mod h1:/QMskrHEIM0IhC/wY7iTCUgINsxdT2WcOphktJ9+Q54= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= @@ -937,30 +1008,25 @@ 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.20.2 h1:7NVCeyIWROIAheY21RLS+3j2bb52W0W82tkberYytp4= -github.com/onsi/ginkgo/v2 v2.20.2/go.mod h1:K9gyxPIlb+aIvnZ8bd9Ak+YP18w3APlR+5coaZoE2ag= +github.com/onsi/ginkgo/v2 v2.23.3 h1:edHxnszytJ4lD9D5Jjc4tiDkPBZ3siDeJJkUZJJVkp0= +github.com/onsi/ginkgo/v2 v2.23.3/go.mod h1:zXTP6xIp3U8aVuXN8ENK9IXRaTjFnpVB9mGmaSRvxnM= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.13.0/go.mod h1:lRk9szgn8TxENtWd0Tp4c3wjlRfMTMH27I+3Je41yGY= github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs= -github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= -github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k= -github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY= +github.com/onsi/gomega v1.37.0 h1:CdEG8g0S133B4OswTDC/5XPSzE1OeP29QOioj2PID2Y= +github.com/onsi/gomega v1.37.0/go.mod h1:8D9+Txp43QWKhM24yyOBEdpkzN8FvJyAwecBgsU4KU0= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= -github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= -github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= +github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= +github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M= github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= -github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b h1:FfH+VrHHk6Lxt9HdVS0PXzSXFyS2NbZKXv33FYPol0A= -github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b/go.mod h1:AC62GU6hc0BrNm+9RK9VSiwa/EUe1bkIeFORAMcHvJU= github.com/openzipkin/zipkin-go v0.2.5/go.mod h1:KpXfKdgRDnnhsxw4pNIH9Md5lyFqKUa4YDFlwRYAMyE= -github.com/oracle/oci-go-sdk/v65 v65.87.0 h1:CeVuK8t0dYODGT3P9IDhz4vyXF8poYE1ijoiO5vrKl0= -github.com/oracle/oci-go-sdk/v65 v65.87.0/go.mod h1:IBEV9l1qBzUpo7zgGaRUhbB05BVfcDGYRFBCPlTcPp0= -github.com/ovh/go-ovh v1.7.0 h1:V14nF7FwDjQrZt9g7jzcvAAQ3HN6DNShRFRMC3jLoPw= -github.com/ovh/go-ovh v1.7.0/go.mod h1:cTVDnl94z4tl8pP1uZ/8jlVxntjSIf09bNcQ5TJSC7c= +github.com/ovh/go-ovh v1.9.0 h1:6K8VoL3BYjVV3In9tPJUdT7qMx9h0GExN9EXx1r2kKE= +github.com/ovh/go-ovh v1.9.0/go.mod h1:cTVDnl94z4tl8pP1uZ/8jlVxntjSIf09bNcQ5TJSC7c= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY= github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= @@ -968,15 +1034,15 @@ github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaR github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc= -github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4= -github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= +github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= +github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= github.com/performancecopilot/speed/v4 v4.0.0/go.mod h1:qxrSyuDGrTOWfV+uKRFhfxw6h/4HXRGUiZiufxo49BM= github.com/peterhellberg/link v1.2.0 h1:UA5pg3Gp/E0F2WdX7GERiNrPQrM1K6CVJUUWfHa4t6c= github.com/peterhellberg/link v1.2.0/go.mod h1:gYfAh+oJgQu2SrZHg5hROVRQe1ICoK0/HHJTcE0edxc= github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= github.com/pierrec/lz4 v2.6.1+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= -github.com/pires/go-proxyproto v0.6.1 h1:EBupykFmo22SDjv4fQVQd2J9NOoLPmyZA/15ldOGkPw= -github.com/pires/go-proxyproto v0.6.1/go.mod h1:Odh9VFOZJCf9G8cLW5o435Xf1J95Jw9Gw5rnCjcwzAY= +github.com/pires/go-proxyproto v0.8.1 h1:9KEixbdJfhrbtjpz/ZwCdWDD2Xem0NZ38qMYaASJgp0= +github.com/pires/go-proxyproto v0.8.1/go.mod h1:ZKAAyp3cgy5Y5Mo4n9AlScrkCZwUy0g3Jf+slqQVcuU= github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= @@ -993,10 +1059,10 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s= github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= -github.com/power-devops/perfstat v0.0.0-20220216144756-c35f1ee13d7c h1:NRoLoZvkBTKvR5gQLgA3e0hqjkY9u1wm+iOL45VN/qI= -github.com/power-devops/perfstat v0.0.0-20220216144756-c35f1ee13d7c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= -github.com/pquerna/otp v1.4.0 h1:wZvl1TIVxKRThZIBiwOOHOGP/1+nZyWBil9Y2XNEDzg= -github.com/pquerna/otp v1.4.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg= +github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU= +github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= +github.com/pquerna/otp v1.5.0 h1:NMMR+WrmaqXU4EzdGJEE1aUUI0AMRzsp96fFFWNPwxs= +github.com/pquerna/otp v1.5.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= @@ -1004,8 +1070,8 @@ github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQ github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= -github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE= -github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho= +github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q= +github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= @@ -1020,8 +1086,8 @@ github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8b github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= github.com/prometheus/common v0.30.0/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= -github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc= -github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8= +github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io= +github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= @@ -1030,17 +1096,17 @@ github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+Gx github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= -github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= -github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= +github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= +github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI= github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg= -github.com/quic-go/quic-go v0.48.2 h1:wsKXZPeGWpMpCGSWqOcqpW2wZYic/8T3aqiOID0/KWE= -github.com/quic-go/quic-go v0.48.2/go.mod h1:yBgs3rWBOADpga7F+jJsb6Ybg1LSYiQvwWlLX+/6HMs= +github.com/quic-go/quic-go v0.55.0 h1:zccPQIqYCXDt5NmcEabyYvOnomjs8Tlwl7tISjJh9Mk= +github.com/quic-go/quic-go v0.55.0/go.mod h1:DR51ilwU1uE164KuWXhinFcKWGlEjzys2l8zUl5Ss1U= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= -github.com/redis/go-redis/v9 v9.7.3 h1:YpPyAayJV+XErNsatSElgRZZVCwXX9QzkKYNvO7x0wM= -github.com/redis/go-redis/v9 v9.7.3/go.mod h1:bGUrSggJ9X9GUmZpZNEOQKaANxSGgOEBRltRTZHSvrA= +github.com/redis/go-redis/v9 v9.8.0 h1:q3nRvjrlge/6UD7eTu/DSg2uYiU2mCL0G/uzBWqhicI= +github.com/redis/go-redis/v9 v9.8.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw= github.com/regfish/regfish-dnsapi-go v0.1.1 h1:TJFtbePHkd47q5GZwYl1h3DIYXmoxdLjW/SBsPtB5IE= github.com/regfish/regfish-dnsapi-go v0.1.1/go.mod h1:ubIgXSfqarSnl3XHSn8hIFwFF3h0yrq0ZiWD93Y2VjY= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= @@ -1048,8 +1114,8 @@ github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6L github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= -github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= -github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik= github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= @@ -1057,28 +1123,28 @@ github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8= github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= -github.com/sacloud/api-client-go v0.2.10 h1:+rv3jDohD+pkdYwOTBiB+jZsM0xK3AxadXRzhp3q66c= -github.com/sacloud/api-client-go v0.2.10/go.mod h1:Jj3CTy2+O4bcMedVDXlbHuqqche85HEPuVXoQFhLaRc= -github.com/sacloud/go-http v0.1.8 h1:ynreWA/vnM8G2ksbMlmefBHsXURKPz49qlPRqQ9IQdw= -github.com/sacloud/go-http v0.1.8/go.mod h1:7TL7TN1fnPKHsMifIqURDkGujnKViCgEz5Ei/LQdFK8= -github.com/sacloud/iaas-api-go v1.14.0 h1:xjkFWqdo4ilTrKPNNYBNWR/CZ/kVRsJrdAHAad6J/AQ= -github.com/sacloud/iaas-api-go v1.14.0/go.mod h1:C8os2Mnj0TOmMdSllwhaDWKMVG2ysFnpe69kyA4M3V0= -github.com/sacloud/packages-go v0.0.10 h1:UiQGjy8LretewkRhsuna1TBM9Vz/l9FoYpQx+D+AOck= -github.com/sacloud/packages-go v0.0.10/go.mod h1:f8QITBh9z4IZc4yE9j21Q8b0sXEMwRlRmhhjWeDVTYs= +github.com/sacloud/api-client-go v0.3.3 h1:ZpSAyGpITA8UFO3Hq4qMHZLGuNI1FgxAxo4sqBnCKDs= +github.com/sacloud/api-client-go v0.3.3/go.mod h1:0p3ukcWYXRCc2AUWTl1aA+3sXLvurvvDqhRaLZRLBwo= +github.com/sacloud/go-http v0.1.9 h1:Xa5PY8/pb7XWhwG9nAeXSrYXPbtfBWqawgzxD5co3VE= +github.com/sacloud/go-http v0.1.9/go.mod h1:DpDG+MSyxYaBwPJ7l3aKLMzwYdTVtC5Bo63HActcgoE= +github.com/sacloud/iaas-api-go v1.19.0 h1:Bw4uygqukcvlblhWrITp94nikXqy2fnKoAC6929LkIA= +github.com/sacloud/iaas-api-go v1.19.0/go.mod h1:XV995RM1I7k5AHb7UZrCVyDF/8bZXDxa+uk1EXoj/Zs= +github.com/sacloud/packages-go v0.0.11 h1:hrRWLmfPM9w7GBs6xb5/ue6pEMl8t1UuDKyR/KfteHo= +github.com/sacloud/packages-go v0.0.11/go.mod h1:XNF5MCTWcHo9NiqWnYctVbASSSZR3ZOmmQORIzcurJ8= github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= -github.com/scaleway/scaleway-sdk-go v1.0.0-beta.32 h1:4+LP7qmsLSGbmc66m1s5dKRMBwztRppfxFKlYqYte/c= -github.com/scaleway/scaleway-sdk-go v1.0.0-beta.32/go.mod h1:kzh+BSAvpoyHHdHBCDhmSWtBc1NbLMZ2lWHqnBoxFks= +github.com/scaleway/scaleway-sdk-go v1.0.0-beta.35 h1:8xfn1RzeI9yoCUuEwDy08F+No6PcKZGEDOQ6hrRyLts= +github.com/scaleway/scaleway-sdk-go v1.0.0-beta.35/go.mod h1:47B1d/YXmSAxlJxUJxClzHR6b3T4M1WyCvwENPQNBWc= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/segmentio/fasthash v1.0.3 h1:EI9+KE1EwvMLBWwjpRDc+fEM+prwxDYbslddQGtrmhM= github.com/segmentio/fasthash v1.0.3/go.mod h1:waKX8l2N8yckOgmSsXJi7x1ZfdKZ4x7KRMzBtS3oedY= github.com/selectel/domains-go v1.1.0 h1:futG50J43ALLKQAnZk9H9yOtLGnSUh7c5hSvuC5gSHo= github.com/selectel/domains-go v1.1.0/go.mod h1:SugRKfq4sTpnOHquslCpzda72wV8u0cMBHx0C0l+bzA= -github.com/selectel/go-selvpcclient/v3 v3.2.1 h1:ny6WIAMiHzKxOgOEnwcWE79wIQij1AHHylzPA41MXCw= -github.com/selectel/go-selvpcclient/v3 v3.2.1/go.mod h1:3EfSf8aEWyhspOGbvZ6mvnFg7JN5uckxNyBFPGWsXNQ= +github.com/selectel/go-selvpcclient/v4 v4.1.0 h1:22lBp+rzg9g2MP4iiGhpVAcCt0kMv7I7uV1W3taLSvQ= +github.com/selectel/go-selvpcclient/v4 v4.1.0/go.mod h1:eFhL1KUW159KOJVeGO7k/Uxl0TYd/sBkWXjuF5WxmYk= github.com/shirou/gopsutil/v3 v3.24.4 h1:dEHgzZXt4LMNm+oYELpzl9YCqV65Yr/6SfrvgRBtXeU= github.com/shirou/gopsutil/v3 v3.24.4/go.mod h1:lTd2mdiOspcqLgAnr9/nGi71NkeMpWKdmhuxm9GusH8= github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= @@ -1098,21 +1164,21 @@ github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/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/assertions v1.0.1/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM= +github.com/smartystreets/assertions v1.1.0 h1:MkTeG1DMwsrdH7QtLXy5W+fUxWq+vmb6cLmyJ7aRtF0= +github.com/smartystreets/assertions v1.1.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo= github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9 h1:hp2CYQUINdZMHdvTdXtPOY2ainKl4IoMcpAXEf2xj3Q= github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9/go.mod h1:SnhjPscd9TpLiy1LpzGSKh3bXCfxxXuqd9xmQJy3slM= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/smartystreets/gunit v1.0.4 h1:tpTjnuH7MLlqhoD21vRoMZbMIi5GmBsAJDFyF67GhZA= github.com/smartystreets/gunit v1.0.4/go.mod h1:EH5qMBab2UclzXUcpR8b93eHsIlp9u+pDQIRp5DZNzQ= -github.com/softlayer/softlayer-go v1.1.7 h1:SgTL+pQZt1h+5QkAhVmHORM/7N9c1X0sljJhuOIHxWE= -github.com/softlayer/softlayer-go v1.1.7/go.mod h1:WeJrBLoTJcaT8nO1azeyHyNpo/fDLtbpbvh+pzts+Qw= +github.com/softlayer/softlayer-go v1.2.1 h1:8ucHxn5laVsVPb0/aMGnr6tOMt1I9BgEtU5mn70OGKw= +github.com/softlayer/softlayer-go v1.2.1/go.mod h1:Gz9/ktcmB7Z8EJlu+QEJJpkv8lAmnhYdB9Tc6gedjmo= github.com/softlayer/xmlrpc v0.0.0-20200409220501-5f089df7cb7e h1:3OgWYFw7jxCZPcvAg+4R8A50GZ+CCkARF10lxu2qDsQ= github.com/softlayer/xmlrpc v0.0.0-20200409220501-5f089df7cb7e/go.mod h1:fKZCUVdirrxrBpwd9wb+lSoVixvpwAu8eHzbQB2tums= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= -github.com/sony/gobreaker v0.5.0 h1:dRCvqm0P490vZPmy7ppEk2qCnCieBooFJ+YoXGYB+yg= -github.com/sony/gobreaker v0.5.0/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= +github.com/sony/gobreaker v1.0.0 h1:feX5fGGXSl3dYd4aHZItw+FpHLvvoaqkawKjVNiFMNQ= +github.com/sony/gobreaker v1.0.0/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= @@ -1128,14 +1194,15 @@ github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJ github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/pflag v1.0.7 h1:vN6T9TfwStFPFM5XzjsvmzZkLuaLX+HS+0SeFLRgU6M= +github.com/spf13/pflag v1.0.7/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/spf13/viper v1.7.1/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ= github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk= -github.com/spiffe/go-spiffe/v2 v2.4.0 h1:j/FynG7hi2azrBG5cvjRcnQ4sux/VNj8FAVc99Fl66c= -github.com/spiffe/go-spiffe/v2 v2.4.0/go.mod h1:m5qJ1hGzjxjtrkGHZupoXHo/FDWwCB1MdSyBzfHugx0= +github.com/spiffe/go-spiffe/v2 v2.5.0 h1:N2I01KCUkv1FAjZXJMwh95KK1ZIQLYbPfhaxw8WS0hE= +github.com/spiffe/go-spiffe/v2 v2.5.0/go.mod h1:P+NxobPc6wXhVtINNtFjNWGBTreew1GBUCwT2wPmb7g= github.com/stealthrocket/wasi-go v0.8.0 h1:Hwnv3CUoMhhRyero9vt1vfwaYa9tu/Z5kmCW4WeAmVI= github.com/stealthrocket/wasi-go v0.8.0/go.mod h1:PJ5oVs2E1ciOJnsTnav4nvTtEcJ4D1jUZAewS9pzuZg= github.com/stealthrocket/wazergo v0.19.1 h1:BPrITETPgSFwiytwmToO0MbUC/+RGC39JScz1JmmG6c= @@ -1145,6 +1212,7 @@ github.com/streadway/amqp v1.0.0/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1Sd github.com/streadway/handy v0.0.0-20200128134331-0f66f006fb2e/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI= 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/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= @@ -1157,14 +1225,13 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ 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.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= -github.com/stretchr/testify v1.7.5/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= 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/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= @@ -1172,10 +1239,10 @@ github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8 github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/tailscale/tscert v0.0.0-20230806124524-28a91b69a046 h1:8rUlviSVOEe7TMk7W0gIPrW8MqEzYfZHpsNWSf8s2vg= github.com/tailscale/tscert v0.0.0-20230806124524-28a91b69a046/go.mod h1:kNGUQ3VESx3VZwRwA9MSCUegIl6+saPL8Noq82ozCaU= -github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1128 h1:NGnqDc8FQL0YdiCHgTO4Wkso6ToD8rE3JW9VOzoPBNA= -github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1128/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0= -github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.1128 h1:mrJ5Fbkd7sZIJ5F6oRfh5zebPQaudPH9Y0+GUmFytYU= -github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.1128/go.mod h1:zbsYIBT+VTX4z4ocjTAdLBIWyNYj3z0BRqd0iPdnjsk= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.1.10/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.1.19/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.1.41 h1:XQDGrLX6v4McMP+2myhgQcy5JaPqSgwpLM1qa7ngUII= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.1.41/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0= github.com/testcontainers/testcontainers-go v0.32.0 h1:ug1aK08L3gCHdhknlTTwWjPHPS+/alvLJU/DRxTD/ME= github.com/testcontainers/testcontainers-go v0.32.0/go.mod h1:CRHrzHLQhlXUsa5gXjTOfqIEJcrK5+xMDmBr/WMI88E= github.com/testcontainers/testcontainers-go/modules/k3s v0.32.0 h1:Z3DTMveNUqeGJZ+CXZhpvI7OF1BS71Ywi3SwoXLZ4Lc= @@ -1189,12 +1256,15 @@ github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JT github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tjfoc/gmsm v1.3.2/go.mod h1:HaUcFuY0auTiaHB9MHFGCPx5IaLhTUd2atbCFBQXn9w= github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho= github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE= -github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU= github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= -github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= +github.com/tklauser/go-sysconf v0.3.15 h1:VE89k0criAymJ/Os65CSn1IXaol+1wrsFHEB8Ol49K4= +github.com/tklauser/go-sysconf v0.3.15/go.mod h1:Dmjwr6tYFIseJw7a3dRLJfsHAMXZ3nEnL/aZY+0IuI4= github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= +github.com/tklauser/numcpus v0.10.0 h1:18njr6LDBk1zuna922MgdjQuJFjrdppsZG60sHGfjso= +github.com/tklauser/numcpus v0.10.0/go.mod h1:BiTKazU708GQTYF4mB+cmlpT2Is1gLk7XVuEeem8LsQ= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/traefik/grpc-web v0.16.0 h1:eeUWZaFg6ZU0I9dWOYE2D5qkNzRBmXzzuRlxdltascY= github.com/traefik/grpc-web v0.16.0/go.mod h1:2ttniSv7pTgBWIU2HZLokxRfFX3SA60c/DTmQQgVml4= @@ -1202,15 +1272,11 @@ github.com/traefik/paerser v0.2.2 h1:cpzW/ZrQrBh3mdwD/jnp6aXASiUFKOVr6ldP+keJTcQ github.com/traefik/paerser v0.2.2/go.mod h1:7BBDd4FANoVgaTZG+yh26jI6CA2nds7D/4VTEdIsh24= github.com/traefik/yaegi v0.16.1 h1:f1De3DVJqIDKmnasUF6MwmWv1dSEEat0wcpXhD2On3E= github.com/traefik/yaegi v0.16.1/go.mod h1:4eVhbPb3LnD2VigQjhYbEJ69vDRFdT2HQNrXx8eEwUY= -github.com/transip/gotransip/v6 v6.26.0 h1:Aejfvh8rSp8Mj2GX/RpdBjMCv+Iy/DmgfNgczPDP550= -github.com/transip/gotransip/v6 v6.26.0/go.mod h1:x0/RWGRK/zob817O3tfO2xhFoP1vu8YOHORx6Jpk80s= +github.com/transip/gotransip/v6 v6.26.1 h1:MeqIjkTBBsZwWAK6giZyMkqLmKMclVHEuTNmoBdx4MA= +github.com/transip/gotransip/v6 v6.26.1/go.mod h1:x0/RWGRK/zob817O3tfO2xhFoP1vu8YOHORx6Jpk80s= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= -github.com/uber/jaeger-client-go v2.30.0+incompatible h1:D6wyKGCecFaSRUpo8lCVbaOOb6ThwMmTEbhRwtKR97o= -github.com/uber/jaeger-client-go v2.30.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk= -github.com/uber/jaeger-lib v2.4.1+incompatible h1:td4jdvLcExb4cBISKIpHuGoVXh+dVKhn2Um6rjCsSsg= -github.com/uber/jaeger-lib v2.4.1+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= github.com/ugorji/go v1.2.6 h1:tGiWC9HENWE2tqYycIqFTNorMmFRVhNwCpDOpWqnk8E= github.com/ugorji/go v1.2.6/go.mod h1:anCg0y61KIhDlPZmnH+so+RQbysYVyDko0IMgJv0Nn0= @@ -1218,8 +1284,8 @@ github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLY github.com/ugorji/go/codec v1.2.6/go.mod h1:V6TCNZ4PHqoHGFZuSG1W8nrCzzdgA2DozYxWFFpvxTw= github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= -github.com/ultradns/ultradns-go-sdk v1.8.0-20241010134910-243eeec h1:2s/ghQ8wKE+UzD/hf3P4Gd1j0JI9ncbxv+nsypPoUYI= -github.com/ultradns/ultradns-go-sdk v1.8.0-20241010134910-243eeec/go.mod h1:BZr7Qs3ku1ckpqed8tCRSqTlp8NAeZfAVpfx4OzXMss= +github.com/ultradns/ultradns-go-sdk v1.8.1-20250722213956-faef419 h1:/VaznPrb/b68e3iMvkr27fU7JqPKU4j7tIITZnjQX1k= +github.com/ultradns/ultradns-go-sdk v1.8.1-20250722213956-faef419/go.mod h1:QN0/PdenvYWB0GRMz6JJbPeZz2Lph2iys1p8AFVHm2c= github.com/unrolled/render v1.0.2 h1:dGS3EmChQP3yOi1YeFNO/Dx+MbWZhdvhQJTXochM5bs= github.com/unrolled/render v1.0.2/go.mod h1:gN9T0NhL4Bfbwu8ann7Ry/TGHYfosul+J0obPf6NBdM= github.com/unrolled/secure v1.0.9 h1:BWRuEb1vDrBFFDdbCnKkof3gZ35I/bnHGyt0LB0TNyQ= @@ -1235,14 +1301,14 @@ github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPU github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= github.com/vinyldns/go-vinyldns v0.9.16 h1:GZJStDkcCk1F1AcRc64LuuMh+ENL8pHA0CVd4ulRMcQ= github.com/vinyldns/go-vinyldns v0.9.16/go.mod h1:5qIJOdmzAnatKjurI+Tl4uTus7GJKJxb+zitufjHs3Q= -github.com/volcengine/volc-sdk-golang v1.0.199 h1:zv9QOqTl/IsLwtfC37GlJtcz6vMAHi+pjq8ILWjLYUc= -github.com/volcengine/volc-sdk-golang v1.0.199/go.mod h1:stZX+EPgv1vF4nZwOlEe8iGcriUPRBKX8zA19gXycOQ= +github.com/volcengine/volc-sdk-golang v1.0.223 h1:1EEK6VOUaA2Tu0VBD4VC5iSTTFag+KuNo+Vix469Tz4= +github.com/volcengine/volc-sdk-golang v1.0.223/go.mod h1:zHJlaqiMbIB+0mcrsZPTwOb3FB7S/0MCfqlnO8R7hlM= github.com/vulcand/oxy/v2 v2.0.3 h1:CPWVPfW4hVZXzwwiQzpFidbnJKpahjPHezM+7TkZRNw= github.com/vulcand/oxy/v2 v2.0.3/go.mod h1:k3t+xjyqmXVh88FdFDbYmUKMEvNpaejvBW14es6H70A= 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/v3 v3.17.0 h1:His5Jh5N8KKqaJxfy3uG6jQbLXy0TmQhNxOiRvkKk00= -github.com/vultr/govultr/v3 v3.17.0/go.mod h1:q34Wd76upKmf+vxFMgaNMH3A8BbsPBmSYZUGC8oZa5w= +github.com/vultr/govultr/v3 v3.24.0 h1:fTTTj0VBve+Miy+wGhlb90M2NMDfpGFi6Frlj3HVy6M= +github.com/vultr/govultr/v3 v3.24.0/go.mod h1:9WwnWGCKnwDlNjHjtt+j+nP+0QWq6hQXzaHgddqrLWY= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= @@ -1250,19 +1316,21 @@ github.com/xdg-go/scram v1.0.2/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+ github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4= github.com/xdg-go/stringprep v1.0.2/go.mod h1:8F9zXuvzgwmyT5DUm4GUfZGDdT3W+LCvS6+da4O5kxM= github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM= -github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= -github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= -github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= -github.com/yandex-cloud/go-genproto v0.0.0-20250319153614-fb9d3e5eb01a h1:YO8gGyAV4N5SR3NzloZ1128IahSpXWr78oU7aEe7f04= -github.com/yandex-cloud/go-genproto v0.0.0-20250319153614-fb9d3e5eb01a/go.mod h1:0LDD/IZLIUIV4iPH+YcF+jysO3jkSvADFGm4dCAuwQo= -github.com/yandex-cloud/go-sdk v0.0.0-20250320143332-9cbcfc5de4ae h1:x+uGuST05LVlgCxF5TsP8kQCCTW7uIeAQJ1dKtSmWqE= -github.com/yandex-cloud/go-sdk v0.0.0-20250320143332-9cbcfc5de4ae/go.mod h1:V71iJlJnS/NtNNdg/B7SwccBS19aXxwY3fv/wut9D74= +github.com/yandex-cloud/go-genproto v0.31.0 h1:mFMS5SD1Lt8qErwefR8ChK3d0jg0tvbDLq57IqenpTg= +github.com/yandex-cloud/go-genproto v0.31.0/go.mod h1:0LDD/IZLIUIV4iPH+YcF+jysO3jkSvADFGm4dCAuwQo= +github.com/yandex-cloud/go-sdk/services/dns v0.0.12 h1:c5TNaX7r3DqY37YJFbr7HyQFRcSe1WzCbR81LVwxXyk= +github.com/yandex-cloud/go-sdk/services/dns v0.0.12/go.mod h1:6CRtIkxq6iTSZIOT42EFns54CEr35ncECy4ix9lXUd4= +github.com/yandex-cloud/go-sdk/v2 v2.19.0 h1:Cuzjn6kkOD/KrBF/QyDbKS7b5GAu8fC2ZUjdBjit60A= +github.com/yandex-cloud/go-sdk/v2 v2.19.0/go.mod h1:4SwghU8RB4v2OQzhESgq5SF8XmCXIP80WhgrrNpetJ8= github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= +github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM= +github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.30/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= @@ -1271,19 +1339,19 @@ github.com/yuin/gopher-lua v1.1.1 h1:kYKnWBjvbNP4XLT3+bPEwAXJx262OhaHDWDVOPjL46M github.com/yuin/gopher-lua v1.1.1/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw= github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= -github.com/zeebo/errs v1.3.0 h1:hmiaKqgYZzcVgRL1Vkc1Mn2914BbzB0IBxs+ebeutGs= -github.com/zeebo/errs v1.3.0/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4= +github.com/zeebo/errs v1.4.0 h1:XNdoD/RRMKP7HD0UhJnIzUy74ISdGGxURlYG8HSWSfM= +github.com/zeebo/errs v1.4.0/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= -go.etcd.io/etcd/api/v3 v3.5.14 h1:vHObSCxyB9zlF60w7qzAdTcGaglbJOpSj1Xj9+WGxq0= -go.etcd.io/etcd/api/v3 v3.5.14/go.mod h1:BmtWcRlQvwa1h3G2jvKYwIQy4PkHlDej5t7uLMUdJUU= +go.etcd.io/etcd/api/v3 v3.6.4 h1:7F6N7toCKcV72QmoUKa23yYLiiljMrT4xCeBL9BmXdo= +go.etcd.io/etcd/api/v3 v3.6.4/go.mod h1:eFhhvfR8Px1P6SEuLT600v+vrhdDTdcfMzmnxVXXSbk= go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= -go.etcd.io/etcd/client/pkg/v3 v3.5.14 h1:SaNH6Y+rVEdxfpA2Jr5wkEvN6Zykme5+YnbCkxvuWxQ= -go.etcd.io/etcd/client/pkg/v3 v3.5.14/go.mod h1:8uMgAokyG1czCtIdsq+AGyYQMvpIKnSvPjFMunkgeZI= +go.etcd.io/etcd/client/pkg/v3 v3.6.4 h1:9HBYrjppeOfFjBjaMTRxT3R7xT0GLK8EJMVC4xg6ok0= +go.etcd.io/etcd/client/pkg/v3 v3.6.4/go.mod h1:sbdzr2cl3HzVmxNw//PH7aLGVtY4QySjQFuaCgcRFAI= go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= go.etcd.io/etcd/client/v3 v3.5.0/go.mod h1:AIKXXVX/DQXtfTEqBryiLTUXwON+GuvO6Z7lLS/oTh0= -go.etcd.io/etcd/client/v3 v3.5.14 h1:CWfRs4FDaDoSz81giL7zPpZH2Z35tbOrAJkkjMqOupg= -go.etcd.io/etcd/client/v3 v3.5.14/go.mod h1:k3XfdV/VIHy/97rqWjoUzrj9tk7GgJGH9J8L4dNXmAk= +go.etcd.io/etcd/client/v3 v3.6.4 h1:YOMrCfMhRzY8NgtzUsHl8hC2EBSnuqbR3dh84Uryl7A= +go.etcd.io/etcd/client/v3 v3.6.4/go.mod h1:jaNNHCyg2FdALyKWnd7hxZXZxZANb0+KGY+YQaEMISo= go.mongodb.org/mongo-driver v1.13.1 h1:YIc7HTYsKndGK4RFzJ3covLz1byri52x0IoMB0Pt/vk= go.mongodb.org/mongo-driver v1.13.1/go.mod h1:wcDf1JBCXy2mOW0bWHwO/IOYqdca1MPCwDtFu/Z9+eo= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= @@ -1294,53 +1362,67 @@ go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= -go.opentelemetry.io/collector/pdata v1.10.0 h1:oLyPLGvPTQrcRT64ZVruwvmH/u3SHTfNo01pteS4WOE= -go.opentelemetry.io/collector/pdata v1.10.0/go.mod h1:IHxHsp+Jq/xfjORQMDJjSH6jvedOSTOyu3nbxqhWSYE= -go.opentelemetry.io/contrib/bridges/otellogrus v0.7.0 h1:vPSzn6dQvdPq9ZiXFs+jUSJnzoKJkADD9yBdx/a1WgI= -go.opentelemetry.io/contrib/bridges/otellogrus v0.7.0/go.mod h1:yZFNJIjn97IBhuMB3tTGPti9xasYLIdh3ChZIzyhz8A= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0 h1:CV7UdSGJt/Ao6Gp4CXckLxVRRsRgDHoI8XjbL3PDl8s= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0/go.mod h1:FRmFuRJfag1IZ2dPkHnEoSFVgTVPUd2qf5Vi69hLb8I= -go.opentelemetry.io/contrib/propagators/autoprop v0.53.0 h1:4zaVLcJ5mvYw0vlk63TX62qS4qty/4jAY1BKZ1usu18= -go.opentelemetry.io/contrib/propagators/autoprop v0.53.0/go.mod h1:RPlvYtxp5D8PKnRzyPM+rwMQrvzdlfA49Sgworkg7aQ= -go.opentelemetry.io/contrib/propagators/aws v1.28.0 h1:acyTl4oyin/iLr5Nz3u7p/PKHUbLh42w/fqg9LblExk= -go.opentelemetry.io/contrib/propagators/aws v1.28.0/go.mod h1:5WgIv6yG9DvLlSY2uIHrYSeVVwCDCqp4jhwinNNyeT4= -go.opentelemetry.io/contrib/propagators/b3 v1.28.0 h1:XR6CFQrQ/ttAYmTBX2loUEFGdk1h17pxYI8828dk/1Y= -go.opentelemetry.io/contrib/propagators/b3 v1.28.0/go.mod h1:DWRkzJONLquRz7OJPh2rRbZ7MugQj62rk7g6HRnEqh0= -go.opentelemetry.io/contrib/propagators/jaeger v1.28.0 h1:xQ3ktSVS128JWIaN1DiPGIjcH+GsvkibIAVRWFjS9eM= -go.opentelemetry.io/contrib/propagators/jaeger v1.28.0/go.mod h1:O9HIyI2kVBrFoEwQZ0IN6PHXykGoit4mZV2aEjkTRH4= -go.opentelemetry.io/contrib/propagators/ot v1.28.0 h1:rmlG+2pc5k5M7Y7izDrxAHZUIwDERdGMTD9oMV7llMk= -go.opentelemetry.io/contrib/propagators/ot v1.28.0/go.mod h1:MNgXIn+UrMbNGpd7xyckyo2LCHIgCdmdjEE7YNZGG+w= -go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY= -go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI= -go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.8.0 h1:WzNab7hOOLzdDF/EoWCt4glhrbMPVMOO5JYTmpz36Ls= -go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.8.0/go.mod h1:hKvJwTzJdp90Vh7p6q/9PAOd55dI6WA6sWj62a/JvSs= -go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.8.0 h1:S+LdBGiQXtJdowoJoQPEtI52syEP/JYBUpjO49EQhV8= -go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.8.0/go.mod h1:5KXybFvPGds3QinJWQT7pmXf+TN5YIa7CNYObWRkj50= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.28.0 h1:U2guen0GhqH8o/G2un8f/aG/y++OuW6MyCo6hT9prXk= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.28.0/go.mod h1:yeGZANgEcpdx/WK0IvvRFC+2oLiMS2u4L/0Rj2M2Qr0= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.28.0 h1:aLmmtjRke7LPDQ3lvpFz+kNEH43faFhzW7v8BFIEydg= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.28.0/go.mod h1:TC1pyCt6G9Sjb4bQpShH+P5R53pO6ZuGnHuuln9xMeE= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0 h1:3Q/xZUyC1BBkualc9ROb4G8qkH90LXEIICcs5zv1OYY= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0/go.mod h1:s75jGIWA9OfCMzF0xr+ZgfrB5FEbbV7UuYo32ahUiFI= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.28.0 h1:R3X6ZXmNPRR8ul6i3WgFURCHzaXjHdm0karRG/+dj3s= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.28.0/go.mod h1:QWFXnDavXWwMx2EEcZsf3yxgEKAqsxQ+Syjp+seyInw= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.28.0 h1:j9+03ymgYhPKmeXGk5Zu+cIZOlVzd9Zv7QIiyItjFBU= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.28.0/go.mod h1:Y5+XiUG4Emn1hTfciPzGPJaSI+RpDts6BnCIir0SLqk= -go.opentelemetry.io/otel/log v0.8.0 h1:egZ8vV5atrUWUbnSsHn6vB8R21G2wrKqNiDt3iWertk= -go.opentelemetry.io/otel/log v0.8.0/go.mod h1:M9qvDdUTRCopJcGRKg57+JSQ9LgLBrwwfC32epk5NX8= -go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ= -go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE= -go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A= -go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU= -go.opentelemetry.io/otel/sdk/log v0.8.0 h1:zg7GUYXqxk1jnGF/dTdLPrK06xJdrXgqgFLnI4Crxvs= -go.opentelemetry.io/otel/sdk/log v0.8.0/go.mod h1:50iXr0UVwQrYS45KbruFrEt4LvAdCaWWgIrsN3ZQggo= -go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk= -go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w= -go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k= -go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE= +go.opentelemetry.io/collector/featuregate v1.41.0 h1:CL4UMsMQj35nMJC3/jUu8VvYB4MHirbAX4B0Z/fCVLY= +go.opentelemetry.io/collector/featuregate v1.41.0/go.mod h1:A72x92glpH3zxekaUybml1vMSv94BH6jQRn5+/htcjw= +go.opentelemetry.io/collector/pdata v1.41.0 h1:2zurAaY0FkURbLa1x7f7ag6HaNZYZKSmI4wgzDegLgo= +go.opentelemetry.io/collector/pdata v1.41.0/go.mod h1:h0OghaTYe4oRvLxK31Ny7gkyjJ1p8oniM5MiCzluQjc= +go.opentelemetry.io/contrib/bridges/otellogrus v0.13.0 h1:Nzvgkys5xSchtkWEeTQNixr9EVo+cbYCpSey2zMftXw= +go.opentelemetry.io/contrib/bridges/otellogrus v0.13.0/go.mod h1:nvmPavMmeFjktIIxQAsE265cQ9nQ5qhDV2mN5kfdPog= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 h1:q4XOmH/0opmeuJtPsbFNivyl7bCt7yRBbeEm2sC/XtQ= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0/go.mod h1:snMWehoOh2wsEwnvvwtDyFCxVeDAODenXHtn5vzrKjo= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q= +go.opentelemetry.io/contrib/propagators/autoprop v0.63.0 h1:S3+4UwR3Y1tUKklruMwOacAFInNvtuOexz4ZTmJNAyw= +go.opentelemetry.io/contrib/propagators/autoprop v0.63.0/go.mod h1:qpIuOggbbw2T9nKRaO1je/oTRKd4zslAcJonN8LYbTg= +go.opentelemetry.io/contrib/propagators/aws v1.38.0 h1:eRZ7asSbLc5dH7+TBzL6hFKb1dabz0IV51uUUwYRZts= +go.opentelemetry.io/contrib/propagators/aws v1.38.0/go.mod h1:wXqc9NTGcXapBExHBDVLEZlByu6quiQL8w7Tjgv8TCg= +go.opentelemetry.io/contrib/propagators/b3 v1.38.0 h1:uHsCCOSKl0kLrV2dLkFK+8Ywk9iKa/fptkytc6aFFEo= +go.opentelemetry.io/contrib/propagators/b3 v1.38.0/go.mod h1:wMRSZJZcY8ya9mApLLhwIMjqmApy2o/Ml+62lhvxyHU= +go.opentelemetry.io/contrib/propagators/jaeger v1.38.0 h1:nXGeLvT1QtCAhkASkP/ksjkTKZALIaQBIW+JSIw1KIc= +go.opentelemetry.io/contrib/propagators/jaeger v1.38.0/go.mod h1:oMvOXk78ZR3KEuPMBgp/ThAMDy9ku/eyUVztr+3G6Wo= +go.opentelemetry.io/contrib/propagators/ot v1.38.0 h1:k4gSyyohaDXI8F9BDXYC3uO2vr5sRNeQFMsN9Zn0EoI= +go.opentelemetry.io/contrib/propagators/ot v1.38.0/go.mod h1:2hDsuiHRO39SRUMhYGqmj64z/IuMRoxE4bBSFR82Lo8= +go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= +go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.14.0 h1:OMqPldHt79PqWKOMYIAQs3CxAi7RLgPxwfFSwr4ZxtM= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.14.0/go.mod h1:1biG4qiqTxKiUCtoWDPpL3fB3KxVwCiGw81j3nKMuHE= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.14.0 h1:QQqYw3lkrzwVsoEX0w//EhH/TCnpRdEenKBOOEIMjWc= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.14.0/go.mod h1:gSVQcr17jk2ig4jqJ2DX30IdWH251JcNAecvrqTxH1s= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0 h1:vl9obrcoWVKp/lwl8tRE33853I8Xru9HFbw/skNeLs8= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0/go.mod h1:GAXRxmLJcVM3u22IjTg74zWBrRCKq8BnOqUVLodpcpw= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0 h1:Oe2z/BCg5q7k4iXC3cqJxKYg0ieRiOqF0cecFYdPTwk= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.38.0/go.mod h1:ZQM5lAJpOsKnYagGg/zV2krVqTtaVdYdDkhMoX6Oalg= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 h1:GqRJVj7UmLjCVyVJ3ZFLdPRmhDUp2zFmQe3RHIOsw24= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0/go.mod h1:ri3aaHSmCTVYu2AWv44YMauwAQc0aqI9gHKIcSbI1pU= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 h1:lwI4Dc5leUqENgGuQImwLo4WnuXFPetmPpkLi2IrX54= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0/go.mod h1:Kz/oCE7z5wuyhPxsXDuaPteSWqjSBD5YaSdbxZYGbGk= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 h1:aTL7F04bJHUlztTsNGJ2l+6he8c+y/b//eR0jjjemT4= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0/go.mod h1:kldtb7jDTeol0l3ewcmd8SDvx3EmIE7lyvqbasU3QC4= +go.opentelemetry.io/otel/log v0.14.0 h1:2rzJ+pOAZ8qmZ3DDHg73NEKzSZkhkGIua9gXtxNGgrM= +go.opentelemetry.io/otel/log v0.14.0/go.mod h1:5jRG92fEAgx0SU/vFPxmJvhIuDU9E1SUnEQrMlJpOno= +go.opentelemetry.io/otel/log/logtest v0.14.0 h1:BGTqNeluJDK2uIHAY8lRqxjVAYfqgcaTbVk1n3MWe5A= +go.opentelemetry.io/otel/log/logtest v0.14.0/go.mod h1:IuguGt8XVP4XA4d2oEEDMVDBBCesMg8/tSGWDjuKfoA= +go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= +go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= +go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= +go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= +go.opentelemetry.io/otel/sdk/log v0.14.0 h1:JU/U3O7N6fsAXj0+CXz21Czg532dW2V4gG1HE/e8Zrg= +go.opentelemetry.io/otel/sdk/log v0.14.0/go.mod h1:imQvII+0ZylXfKU7/wtOND8Hn4OpT3YUoIgqJVksUkM= +go.opentelemetry.io/otel/sdk/log/logtest v0.14.0 h1:Ijbtz+JKXl8T2MngiwqBlPaHqc4YCaP/i13Qrow6gAM= +go.opentelemetry.io/otel/sdk/log/logtest v0.14.0/go.mod h1:dCU8aEL6q+L9cYTqcVOk8rM9Tp8WdnHOPLiBgp0SGOA= +go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= +go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= +go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= +go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= -go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= -go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= +go.opentelemetry.io/proto/otlp v1.7.1 h1:gTOMpGDb0WTBOP8JaO72iL3auEZhVmAQg4ipjOVAtj4= +go.opentelemetry.io/proto/otlp v1.7.1/go.mod h1:b2rVh6rfI/s2pHWNlB7ILJcRALpcNDzKhACevjI+ZnE= +go.opentelemetry.io/proto/slim/otlp v1.7.1 h1:lZ11gEokjIWYM3JWOUrIILr2wcf6RX+rq5SPObV9oyc= +go.opentelemetry.io/proto/slim/otlp v1.7.1/go.mod h1:uZ6LJWa49eNM/EXnnvJGTTu8miokU8RQdnO980LJ57g= +go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.0.1 h1:Tr/eXq6N7ZFjN+THBF/BtGLUz8dciA7cuzGRsCEkZ88= +go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.0.1/go.mod h1:riqUmAOJFDFuIAzZu/3V6cOrTyfWzpgNJnG5UwrapCk= +go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.0.1 h1:z/oMlrCv3Kopwh/dtdRagJy+qsRRPA86/Ux3g7+zFXM= +go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.0.1/go.mod h1:C7EHYSIiaALi9RnNORCVaPCQDuJgJEn/XxkctaTez1E= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= @@ -1349,20 +1431,20 @@ go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0 go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= -go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU= -go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= +go.uber.org/mock v0.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko= +go.uber.org/mock v0.5.2/go.mod h1:wLlUxC2vVTPTaE3UD51E0BGOAElKrILxhVSDYQLld5o= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/multierr v1.7.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= -go.uber.org/ratelimit v0.3.0 h1:IdZd9wqvFXnvLvSEBo0KPcGfkoBGNkpTHlrE3Rcjkjw= -go.uber.org/ratelimit v0.3.0/go.mod h1:So5LG7CV1zWpY1sHe+DXTJqQvOx+FFPFaAs2SnoyBaI= +go.uber.org/ratelimit v0.3.1 h1:K4qVE+byfv/B3tC+4nYWP7v/6SimcO7HzHekoMNBma0= +go.uber.org/ratelimit v0.3.1/go.mod h1:6euWsTB6U/Nb3X++xEUXA8ciPJvr19Q/0h1+oDcJhRk= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI= -go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= -go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= +go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= +go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/arch v0.4.0 h1:A8WCeEWhLwPBKNbFi5Wv5UTCBx5zzubnXDlMOFAzFMc= golang.org/x/arch v0.4.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= @@ -1374,8 +1456,10 @@ golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191219195013-becbf705a915/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200317142112-1b76d66859c6/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= @@ -1395,9 +1479,16 @@ golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0 golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio= +golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= +golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= -golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= -golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= +golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= +golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= +golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= +golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04= +golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0= 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= @@ -1439,8 +1530,11 @@ golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.23.0 h1:Zb7khfcRGKk+kqfxFaP5tZqCnDZMjC5VtUBs87Hr6QM= -golang.org/x/mod v0.23.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= +golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.28.0 h1:gQBtGhjxykdjY9YhZpSlZIsbnaE2+PgjfLWUQTnoZ1U= +golang.org/x/mod v0.28.0/go.mod h1:yfB/L0NOf/kmEbXjzCPOx1iK1fRutOydrCMsqRhEBxI= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1460,7 +1554,6 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191021144547-ec77196f6094/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -1491,22 +1584,30 @@ golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20210913180222-943fd674d43e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210917221730-978cfadd31cf/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= -golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= -golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= +golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= +golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= +golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4= +golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/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.28.0 h1:CrgCKl8PPAVtLnU3c+EDw6x11699EWlsDeWNWKdIOkc= -golang.org/x/oauth2 v0.28.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= +golang.org/x/oauth2 v0.32.0 h1:jsCblLleRMDrxMN29H3z/k1KliIvpLgCkE6R8FXXNgY= +golang.org/x/oauth2 v0.32.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -1520,8 +1621,11 @@ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= -golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= +golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -1565,6 +1669,7 @@ golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200509044756-6aff5f38e54f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1595,6 +1700,7 @@ golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBc 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= golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220406163625-3f8b81556e12/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/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-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -1609,10 +1715,17 @@ golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= -golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ= +golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -1620,9 +1733,16 @@ golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o= +golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= +golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= -golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y= -golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g= +golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= +golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= +golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= +golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= +golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0= +golang.org/x/term v0.36.0 h1:zMPR+aF8gfksFprF/Nc/rd1wRS1EI6nDBGyWAvDzx2Q= +golang.org/x/term v0.36.0/go.mod h1:Qu394IJq6V6dCBRgwqshf3mPF85AqzYEzofzRdZkWss= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1637,9 +1757,12 @@ golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= -golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= +golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k= +golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -1648,8 +1771,8 @@ golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxb 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.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= -golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= +golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= +golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= 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-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -1690,6 +1813,7 @@ golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjs golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200509030707-2212a7e161a5/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= @@ -1706,15 +1830,20 @@ 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/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.30.0 h1:BgcpHewrV5AUp2G9MebG4XPFI1E2W41zU1SaqVA9vJY= -golang.org/x/tools v0.30.0/go.mod h1:c347cR/OJfw5TI+GfX7RUPNMdDRRbjvYTS0jPyvsVtY= +golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= +golang.org/x/tools v0.37.0 h1:DVSRzp7FwePZW356yEAChSdNcQo6Nsp+fex1SUW09lE= +golang.org/x/tools v0.37.0/go.mod h1:MBN5QPQtLMHVdvsbtarmTNukZDdgwdwlO5qGacAzF0w= 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= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU= +golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= -gonum.org/v1/gonum v0.8.2 h1:CCXrcPKiGGotvnN6jfUsKk4rRqm7q09/YbKb5xCEvtM= gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0= +gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= +gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= @@ -1733,8 +1862,8 @@ google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0M google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= -google.golang.org/api v0.227.0 h1:QvIHF9IuyG6d6ReE+BNd11kIB8hZvjN8Z5xY5t21zYc= -google.golang.org/api v0.227.0/go.mod h1:EIpaG6MbTgQarWF5xJvX0eOJPK9n/5D4Bynb9j2HXvQ= +google.golang.org/api v0.252.0 h1:xfKJeAJaMwb8OC9fesr369rjciQ704AjU/psjkKURSI= +google.golang.org/api v0.252.0/go.mod h1:dnHOv81x5RAmumZ7BWLShB/u7JZNeyalImxHmtTHxqw= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -1773,12 +1902,12 @@ google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= google.golang.org/genproto v0.0.0-20210917145530-b395a37504d4/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20241021214115-324edc3d5d38 h1:Q3nlH8iSQSRUwOskjbcSMcF2jiYMNiQYZ0c2KEJLKKU= -google.golang.org/genproto v0.0.0-20241021214115-324edc3d5d38/go.mod h1:xBI+tzfqGGN2JBeSebfKXFSdBpWVQ7sLW40PTupVRm4= -google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422 h1:GVIKPyP/kLIyVOgOnTwFOrvQaQUzOzGMCxgFUOEmm24= -google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422/go.mod h1:b6h1vNKhxaSoEI+5jc3PJUCustfli/mRab7295pY7rw= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250313205543-e70fdf4c4cb4 h1:iK2jbkWL86DXjEx0qiHcRE9dE4/Ahua5k6V8OWFb//c= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250313205543-e70fdf4c4cb4/go.mod h1:LuRYeWDFV6WOn90g357N17oMCaxpgCnbi/44qJvDn2I= +google.golang.org/genproto v0.0.0-20250603155806-513f23925822 h1:rHWScKit0gvAPuOnu87KpaYtjK5zBMLcULh7gxkCXu4= +google.golang.org/genproto v0.0.0-20250603155806-513f23925822/go.mod h1:HubltRL7rMh0LfnQPkMH4NPDFEWp0jw3vixw7jEM53s= +google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5 h1:BIRfGDEjiHRrk0QKZe3Xv2ieMhtgRGeLcZQ0mIVn4EY= +google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5/go.mod h1:j3QtIyytwqGr1JUDtYXwtMXWPKsEa5LtzIFN1Wn5WvE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251002232023-7c0ddcbb5797 h1:CirRxTOwnRWVLKzDNrs0CXAaVozJoR4G9xvdRecrdpk= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251002232023-7c0ddcbb5797/go.mod h1:HSkG/KdJWusxU1F6CNrwNDjBMgisKxGnc5dAZfT0mjQ= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -1796,8 +1925,8 @@ google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= -google.golang.org/grpc v1.71.0 h1:kF77BGdPTQ4/JZWMlb9VpJ5pa25aqvVqogsxNHHdeBg= -google.golang.org/grpc v1.71.0/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec= +google.golang.org/grpc v1.75.1 h1:/ODCNEuf9VghjgO3rqLcfg8fiOP0nSluljWFlDxELLI= +google.golang.org/grpc v1.75.1/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ= 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= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -1812,8 +1941,8 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= -google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= +google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -1826,22 +1955,18 @@ gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSP gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= -gopkg.in/h2non/gentleman.v1 v1.0.4/go.mod h1:JYuHVdFzS4MKOXe0o+chKJ4hCe6tqKKw9XH9YP6WFrg= -gopkg.in/h2non/gock.v1 v1.0.15/go.mod h1:sX4zAkdYX1TRGJ2JY156cFspQn4yRWn6p9EMdODlynE= -gopkg.in/h2non/gock.v1 v1.0.16 h1:F11k+OafeuFENsjei5t2vMTSTs9L62AdyTe4E1cgdG8= -gopkg.in/h2non/gock.v1 v1.0.16/go.mod h1:XVuDAssexPLwgxCLMvDTWNU5eqklsydR6I5phZ9oPB8= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/ini.v1 v1.51.1/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.56.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= -gopkg.in/ns1/ns1-go.v2 v2.13.0 h1:I5NNqI9Bi1SGK92TVkOvLTwux5LNrix/99H2datVh48= -gopkg.in/ns1/ns1-go.v2 v2.13.0/go.mod h1:pfaU0vECVP7DIOr453z03HXS6dFJpXdNRwOyRzwmPSc= +gopkg.in/ns1/ns1-go.v2 v2.15.0 h1:cE3xSMdSCV8kf9SQldzqgW/Ueh7sv3yO2JwKtYxxz3E= +gopkg.in/ns1/ns1-go.v2 v2.15.0/go.mod h1:pfaU0vECVP7DIOr453z03HXS6dFJpXdNRwOyRzwmPSc= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= @@ -1858,11 +1983,10 @@ gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= -gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= +gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q= +gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= @@ -1870,20 +1994,20 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -k8s.io/api v0.31.1 h1:Xe1hX/fPW3PXYYv8BlozYqw63ytA92snr96zMW9gWTU= -k8s.io/api v0.31.1/go.mod h1:sbN1g6eY6XVLeqNsZGLnI5FwVseTrZX7Fv3O26rhAaI= -k8s.io/apiextensions-apiserver v0.31.1 h1:L+hwULvXx+nvTYX/MKM3kKMZyei+UiSXQWciX/N6E40= -k8s.io/apiextensions-apiserver v0.31.1/go.mod h1:tWMPR3sgW+jsl2xm9v7lAyRF1rYEK71i9G5dRtkknoQ= -k8s.io/apimachinery v0.31.1 h1:mhcUBbj7KUjaVhyXILglcVjuS4nYXiwC+KKFBgIVy7U= -k8s.io/apimachinery v0.31.1/go.mod h1:rsPdaZJfTfLsNJSQzNHQvYoTmxhoOEofxtOsF3rtsMo= -k8s.io/client-go v0.31.1 h1:f0ugtWSbWpxHR7sjVpQwuvw9a3ZKLXX0u0itkFXufb0= -k8s.io/client-go v0.31.1/go.mod h1:sKI8871MJN2OyeqRlmA4W4KM9KBdBUpDLu/43eGemCg= +k8s.io/api v0.32.3 h1:Hw7KqxRusq+6QSplE3NYG4MBxZw1BZnq4aP4cJVINls= +k8s.io/api v0.32.3/go.mod h1:2wEDTXADtm/HA7CCMD8D8bK4yuBUptzaRhYcYEEYA3k= +k8s.io/apiextensions-apiserver v0.32.3 h1:4D8vy+9GWerlErCwVIbcQjsWunF9SUGNu7O7hiQTyPY= +k8s.io/apiextensions-apiserver v0.32.3/go.mod h1:8YwcvVRMVzw0r1Stc7XfGAzB/SIVLunqApySV5V7Dss= +k8s.io/apimachinery v0.32.3 h1:JmDuDarhDmA/Li7j3aPrwhpNBA94Nvk5zLeOge9HH1U= +k8s.io/apimachinery v0.32.3/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE= +k8s.io/client-go v0.32.3 h1:RKPVltzopkSgHS7aS98QdscAgtgah/+zmpAogooIqVU= +k8s.io/client-go v0.32.3/go.mod h1:3v0+3k4IcT9bXTc4V2rt+d2ZPPG700Xy6Oi0Gdl2PaY= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= -k8s.io/kube-openapi v0.0.0-20240423202451-8948a665c108 h1:Q8Z7VlGhcJgBHJHYugJ/K/7iB8a2eSxCyxdVjJp+lLY= -k8s.io/kube-openapi v0.0.0-20240423202451-8948a665c108/go.mod h1:yD4MZYeKMBwQKVht279WycxKyM84kkAx2DPrTXaeb98= -k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 h1:pUdcCO1Lk/tbT5ztQWOBi5HBgbBP1J8+AsQnQCKsi8A= -k8s.io/utils v0.0.0-20240711033017-18e509b52bc8/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f h1:GA7//TjRY9yWGy1poLzYYJJ4JRdzg3+O6e8I+e+8T5Y= +k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f/go.mod h1:R/HEjbvWI0qdfb8viZUeVZm0X6IZnxAydC7YU42CMw4= +k8s.io/utils v0.0.0-20241210054802-24370beab758 h1:sdbE21q2nlQtFh65saZY+rRM6x6aJJI8IUa1AmH/qa0= +k8s.io/utils v0.0.0-20241210054802-24370beab758/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= mvdan.cc/xurls/v2 v2.5.0 h1:lyBNOm8Wo71UknhUs4QTFUNNMyxy2JEIaKKo0RWOh+8= mvdan.cc/xurls/v2 v2.5.0/go.mod h1:yQgaGQ1rFtJUzkmKiHYSSfuQxqfYmd//X6PxvholpeE= nhooyr.io/websocket v1.8.7 h1:usjR2uOr/zjjkVMy0lW+PPohFok7PCow5sDjLgX4P4g= @@ -1893,14 +2017,16 @@ rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8 rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= -sigs.k8s.io/controller-runtime v0.18.0 h1:Z7jKuX784TQSUL1TIyeuF7j8KXZ4RtSX0YgtjKcSTME= -sigs.k8s.io/controller-runtime v0.18.0/go.mod h1:tuAt1+wbVsXIT8lPtk5RURxqAnq7xkpv2Mhttslg7Hw= -sigs.k8s.io/gateway-api v1.2.1 h1:fZZ/+RyRb+Y5tGkwxFKuYuSRQHu9dZtbjenblleOLHM= -sigs.k8s.io/gateway-api v1.2.1/go.mod h1:EpNfEXNjiYfUJypf0eZ0P5iXA9ekSGWaS1WgPaM42X0= -sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= -sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= -sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4= -sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08= +sigs.k8s.io/controller-runtime v0.20.4 h1:X3c+Odnxz+iPTRobG4tp092+CvBU9UK0t/bRf+n0DGU= +sigs.k8s.io/controller-runtime v0.20.4/go.mod h1:xg2XB0K5ShQzAgsoujxuKN4LNXR2LfwwHsPj7Iaw+XY= +sigs.k8s.io/gateway-api v1.3.0 h1:q6okN+/UKDATola4JY7zXzx40WO4VISk7i9DIfOvr9M= +sigs.k8s.io/gateway-api v1.3.0/go.mod h1:d8NV8nJbaRbEKem+5IuxkL8gJGOZ+FJ+NvOIltV8gDk= +sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 h1:/Rv+M11QRah1itp8VhT6HoVx1Ray9eB4DBr+K+/sCJ8= +sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3/go.mod h1:18nIHnGi6636UCz6m8i4DhaJ65T6EruyzmoQqI2BVDo= +sigs.k8s.io/randfill v0.0.0-20250304075658-069ef1bbf016 h1:kXv6kKdoEtedwuqMmkqhbkgvYKeycVbC8+iPCP9j5kQ= +sigs.k8s.io/randfill v0.0.0-20250304075658-069ef1bbf016/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= +sigs.k8s.io/structured-merge-diff/v4 v4.7.0 h1:qPeWmscJcXP0snki5IYF79Z8xrl8ETFxgMd7wez1XkI= +sigs.k8s.io/structured-merge-diff/v4 v4.7.0/go.mod h1:dDy58f92j70zLsuZVuUX5Wp9vtxXpaZnkPGWeqDfCps= sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= diff --git a/integration/access_log_test.go b/integration/access_log_test.go index e1c4003e9..2f480b524 100644 --- a/integration/access_log_test.go +++ b/integration/access_log_test.go @@ -648,25 +648,6 @@ func (s *AccessLogSuite) TestAccessLogDisabledForInternals() { require.Equal(s.T(), 0, count) - // Make some requests on the custom ping router in error. - req, err = http.NewRequest(http.MethodGet, "http://127.0.0.1:8010/ping-error", nil) - require.NoError(s.T(), err) - req.Host = "ping-error.docker.local" - - err = try.Request(req, 500*time.Millisecond, try.StatusCodeIs(http.StatusUnauthorized), try.BodyContains("X-Forwarded-Host: ping-error.docker.local")) - require.NoError(s.T(), err) - err = try.Request(req, 500*time.Millisecond, try.StatusCodeIs(http.StatusUnauthorized), try.BodyContains("X-Forwarded-Host: ping-error.docker.local")) - require.NoError(s.T(), err) - - // Here we verify that the remove of observability doesn't break the metrics for the error page service. - req, err = http.NewRequest(http.MethodGet, "http://127.0.0.1:8080/metrics", nil) - require.NoError(s.T(), err) - - err = try.Request(req, 500*time.Millisecond, try.StatusCodeIs(http.StatusOK), try.BodyContains("service3")) - require.NoError(s.T(), err) - err = try.Request(req, 500*time.Millisecond, try.StatusCodeIs(http.StatusOK), try.BodyNotContains("service=\"ping")) - require.NoError(s.T(), err) - // Verify no other Traefik problems. s.checkNoOtherTraefikProblems() } diff --git a/integration/conformance-reports/v1.2.1/experimental-v3.4-default-report.yaml b/integration/conformance-reports/v1.3.0/experimental-v3.5-default-report.yaml similarity index 93% rename from integration/conformance-reports/v1.2.1/experimental-v3.4-default-report.yaml rename to integration/conformance-reports/v1.3.0/experimental-v3.5-default-report.yaml index 2025f9c40..0facbd714 100644 --- a/integration/conformance-reports/v1.2.1/experimental-v3.4-default-report.yaml +++ b/integration/conformance-reports/v1.3.0/experimental-v3.5-default-report.yaml @@ -1,14 +1,14 @@ apiVersion: gateway.networking.k8s.io/v1 date: '-' gatewayAPIChannel: experimental -gatewayAPIVersion: v1.2.1 +gatewayAPIVersion: v1.3.0 implementation: contact: - '@traefik/maintainers' organization: traefik project: traefik url: https://traefik.io/ - version: v3.4 + version: v3.5 kind: ConformanceReport mode: default profiles: @@ -46,6 +46,7 @@ profiles: - HTTPRouteResponseHeaderModification - HTTPRouteSchemeRedirect unsupportedFeatures: + - GatewayAddressEmpty - GatewayHTTPListenerIsolation - GatewayInfrastructurePropagation - GatewayStaticAddresses @@ -54,6 +55,7 @@ profiles: - HTTPRouteParentRefPort - HTTPRouteRequestMirror - HTTPRouteRequestMultipleMirrors + - HTTPRouteRequestPercentageMirror - HTTPRouteRequestTimeout name: GATEWAY-HTTP summary: Core tests succeeded. Extended tests succeeded. diff --git a/integration/consul_test.go b/integration/consul_test.go index 076fd4560..31908556e 100644 --- a/integration/consul_test.go +++ b/integration/consul_test.go @@ -2,7 +2,6 @@ package integration import ( "bytes" - "context" "encoding/json" "errors" "fmt" @@ -43,7 +42,7 @@ func (s *ConsulSuite) SetupSuite() { s.consulURL = fmt.Sprintf("http://%s", consulAddr) kv, err := valkeyrie.NewStore( - context.Background(), + s.T().Context(), consul.StoreName, []string{consulAddr}, &consul.Config{ @@ -63,7 +62,7 @@ func (s *ConsulSuite) TearDownSuite() { } func (s *ConsulSuite) TearDownTest() { - err := s.kvClient.DeleteTree(context.Background(), "traefik") + err := s.kvClient.DeleteTree(s.T().Context(), "traefik") if err != nil && !errors.Is(err, store.ErrKeyNotFound) { require.ErrorIs(s.T(), err, store.ErrKeyNotFound) } @@ -118,7 +117,7 @@ func (s *ConsulSuite) TestSimpleConfiguration() { } for k, v := range data { - err := s.kvClient.Put(context.Background(), k, []byte(v), nil) + err := s.kvClient.Put(s.T().Context(), k, []byte(v), nil) require.NoError(s.T(), err) } @@ -178,7 +177,7 @@ func (s *ConsulSuite) TestDeleteRootKey() { file := s.adaptFile("fixtures/consul/simple.toml", struct{ ConsulAddress string }{s.consulURL}) - ctx := context.Background() + ctx := s.T().Context() svcaddr := net.JoinHostPort(s.getComposeServiceIP("whoami"), "80") data := map[string]string{ diff --git a/integration/dual_logging_test.go b/integration/dual_logging_test.go new file mode 100644 index 000000000..fef4cbd54 --- /dev/null +++ b/integration/dual_logging_test.go @@ -0,0 +1,134 @@ +package integration + +import ( + "compress/gzip" + "encoding/json" + "io" + "net/http" + "net/http/httptest" + "os" + "strings" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/suite" + "go.opentelemetry.io/collector/pdata/plog/plogotlp" +) + +const traefikTestOTLPLogFile = "traefik_otlp.log" + +// DualLoggingSuite tests that both OTLP and stdout logging can work together. +type DualLoggingSuite struct { + BaseSuite + otlpLogs []string + collector *httptest.Server +} + +func TestDualLoggingSuite(t *testing.T) { + suite.Run(t, new(DualLoggingSuite)) +} + +func (s *DualLoggingSuite) SetupSuite() { + s.BaseSuite.SetupSuite() + + // Clean up any existing log files + os.Remove(traefikTestLogFile) + os.Remove(traefikTestOTLPLogFile) +} + +func (s *DualLoggingSuite) TearDownSuite() { + s.BaseSuite.TearDownSuite() + + // Clean up log files + generatedFiles := []string{ + traefikTestLogFile, + traefikTestOTLPLogFile, + } + + for _, filename := range generatedFiles { + if err := os.Remove(filename); err != nil { + s.T().Logf("Failed to remove %s: %v", filename, err) + } + } +} + +func (s *DualLoggingSuite) SetupTest() { + s.otlpLogs = []string{} + + // Create mock OTLP collector + s.collector = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + defer r.Body.Close() + + gzr, err := gzip.NewReader(r.Body) + if err != nil { + s.T().Logf("Error creating gzip reader: %v", err) + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + defer gzr.Close() + + body, err := io.ReadAll(gzr) + if err != nil { + s.T().Logf("Error reading gzipped body: %v", err) + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + req := plogotlp.NewExportRequest() + err = req.UnmarshalProto(body) + if err != nil { + s.T().Logf("Error unmarshaling protobuf: %v", err) + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + marshalledReq, err := json.Marshal(req) + if err != nil { + s.T().Logf("Error marshaling to JSON: %v", err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + s.otlpLogs = append(s.otlpLogs, string(marshalledReq)) + + w.WriteHeader(http.StatusOK) + })) +} + +func (s *DualLoggingSuite) TearDownTest() { + if s.collector != nil { + s.collector.Close() + s.collector = nil + } +} + +func (s *DualLoggingSuite) TestOTLPAndStdoutLogging() { + tempObjects := struct { + CollectorURL string + }{ + CollectorURL: s.collector.URL + "/v1/logs", + } + + file := s.adaptFile("fixtures/dual_logging/otlp_and_stdout.toml", tempObjects) + + cmd, display := s.cmdTraefik(withConfigFile(file)) + defer s.displayTraefikLogFile(traefikTestLogFile) + + s.waitForTraefik("dashboard") + + time.Sleep(3 * time.Second) + + s.killCmd(cmd) + time.Sleep(1 * time.Second) + + assert.NotEmpty(s.T(), s.otlpLogs) + + output := display.String() + otlpOutput := strings.Join(s.otlpLogs, "\n") + + foundStdoutLog := strings.Contains(output, "Starting provider") + assert.True(s.T(), foundStdoutLog) + foundOTLPLog := strings.Contains(otlpOutput, "Starting provider") + assert.True(s.T(), foundOTLPLog) +} diff --git a/integration/etcd_test.go b/integration/etcd_test.go index 01132ba88..067c4d61c 100644 --- a/integration/etcd_test.go +++ b/integration/etcd_test.go @@ -2,7 +2,6 @@ package integration import ( "bytes" - "context" "encoding/json" "net" "net/http" @@ -41,7 +40,7 @@ func (s *EtcdSuite) SetupSuite() { var err error s.etcdAddr = net.JoinHostPort(s.getComposeServiceIP("etcd"), "2379") s.kvClient, err = valkeyrie.NewStore( - context.Background(), + s.T().Context(), etcdv3.StoreName, []string{s.etcdAddr}, &etcdv3.Config{ @@ -108,7 +107,7 @@ func (s *EtcdSuite) TestSimpleConfiguration() { } for k, v := range data { - err := s.kvClient.Put(context.Background(), k, []byte(v), nil) + err := s.kvClient.Put(s.T().Context(), k, []byte(v), nil) require.NoError(s.T(), err) } diff --git a/integration/fixtures/dual_logging/otlp_and_stdout.toml b/integration/fixtures/dual_logging/otlp_and_stdout.toml new file mode 100644 index 000000000..4e31afbf1 --- /dev/null +++ b/integration/fixtures/dual_logging/otlp_and_stdout.toml @@ -0,0 +1,24 @@ +[global] + checkNewVersion = false + sendAnonymousUsage = false + +[log] + level = "INFO" + +[experimental] + otlpLogs = true + +[log.otlp] + serviceName = "traefik-test" + [log.otlp.http] + endpoint = "{{ .CollectorURL }}" + +[api] + insecure = true + +[entryPoints] + [entryPoints.web] + address = ":8000" + +[providers.docker] + exposedByDefault = false diff --git a/integration/fixtures/k8s-conformance/00-experimental-v1.2.1.yml b/integration/fixtures/k8s-conformance/00-experimental-v1.3.0.yml similarity index 83% rename from integration/fixtures/k8s-conformance/00-experimental-v1.2.1.yml rename to integration/fixtures/k8s-conformance/00-experimental-v1.3.0.yml index 69b689846..75725cc0f 100644 --- a/integration/fixtures/k8s-conformance/00-experimental-v1.2.1.yml +++ b/integration/fixtures/k8s-conformance/00-experimental-v1.3.0.yml @@ -1,4 +1,4 @@ -# Copyright 2024 The Kubernetes Authors. +# Copyright 2025 The Kubernetes Authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -17,507 +17,6 @@ # --- # -# config/crd/experimental/gateway.networking.k8s.io_backendlbpolicies.yaml -# -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/3328 - gateway.networking.k8s.io/bundle-version: v1.2.1 - gateway.networking.k8s.io/channel: experimental - creationTimestamp: null - labels: - gateway.networking.k8s.io/policy: Direct - name: backendlbpolicies.gateway.networking.k8s.io -spec: - group: gateway.networking.k8s.io - names: - categories: - - gateway-api - kind: BackendLBPolicy - listKind: BackendLBPolicyList - plural: backendlbpolicies - shortNames: - - blbpolicy - singular: backendlbpolicy - scope: Namespaced - versions: - - additionalPrinterColumns: - - jsonPath: .metadata.creationTimestamp - name: Age - type: date - name: v1alpha2 - schema: - openAPIV3Schema: - description: |- - BackendLBPolicy provides a way to define load balancing rules - for a backend. - properties: - apiVersion: - description: |- - APIVersion defines the versioned schema of this representation of an object. - Servers should convert recognized schemas to the latest internal value, and - may reject unrecognized values. - More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources - type: string - kind: - description: |- - Kind is a string value representing the REST resource this object represents. - Servers may infer this from the endpoint the client submits requests to. - Cannot be updated. - In CamelCase. - More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds - type: string - metadata: - type: object - spec: - description: Spec defines the desired state of BackendLBPolicy. - properties: - sessionPersistence: - description: |- - SessionPersistence defines and configures session persistence - for the backend. - - Support: Extended - properties: - absoluteTimeout: - description: |- - AbsoluteTimeout defines the absolute timeout of the persistent - session. Once the AbsoluteTimeout duration has elapsed, the - session becomes invalid. - - Support: Extended - pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ - type: string - cookieConfig: - description: |- - CookieConfig provides configuration settings that are specific - to cookie-based session persistence. - - Support: Core - properties: - lifetimeType: - default: Session - description: |- - LifetimeType specifies whether the cookie has a permanent or - session-based lifetime. A permanent cookie persists until its - specified expiry time, defined by the Expires or Max-Age cookie - attributes, while a session cookie is deleted when the current - session ends. - - When set to "Permanent", AbsoluteTimeout indicates the - cookie's lifetime via the Expires or Max-Age cookie attributes - and is required. - - When set to "Session", AbsoluteTimeout indicates the - absolute lifetime of the cookie tracked by the gateway and - is optional. - - Support: Core for "Session" type - - Support: Extended for "Permanent" type - enum: - - Permanent - - Session - type: string - type: object - idleTimeout: - description: |- - IdleTimeout defines the idle timeout of the persistent session. - Once the session has been idle for more than the specified - IdleTimeout duration, the session becomes invalid. - - Support: Extended - pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ - type: string - sessionName: - description: |- - SessionName defines the name of the persistent session token - which may be reflected in the cookie or the header. Users - should avoid reusing session names to prevent unintended - consequences, such as rejection or unpredictable behavior. - - Support: Implementation-specific - maxLength: 128 - type: string - type: - default: Cookie - description: |- - Type defines the type of session persistence such as through - the use a header or cookie. Defaults to cookie based session - persistence. - - Support: Core for "Cookie" type - - Support: Extended for "Header" type - enum: - - Cookie - - Header - type: string - type: object - x-kubernetes-validations: - - message: AbsoluteTimeout must be specified when cookie lifetimeType - is Permanent - rule: '!has(self.cookieConfig) || !has(self.cookieConfig.lifetimeType) - || self.cookieConfig.lifetimeType != ''Permanent'' || has(self.absoluteTimeout)' - targetRefs: - description: |- - TargetRef identifies an API object to apply policy to. - Currently, Backends (i.e. Service, ServiceImport, or any - implementation-specific backendRef) are the only valid API - target references. - items: - description: |- - LocalPolicyTargetReference identifies an API object to apply a direct or - inherited policy to. This should be used as part of Policy resources - that can target Gateway API resources. For more information on how this - policy attachment model works, and a sample Policy resource, refer to - the policy attachment documentation for Gateway API. - properties: - group: - description: Group is the group of the target resource. - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - description: Kind is kind of the target resource. - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - name: - description: Name is the name of the target resource. - maxLength: 253 - minLength: 1 - type: string - required: - - group - - kind - - name - type: object - maxItems: 16 - minItems: 1 - type: array - x-kubernetes-list-map-keys: - - group - - kind - - name - x-kubernetes-list-type: map - required: - - targetRefs - type: object - status: - description: Status defines the current state of BackendLBPolicy. - properties: - ancestors: - description: |- - Ancestors is a list of ancestor resources (usually Gateways) that are - associated with the policy, and the status of the policy with respect to - each ancestor. When this policy attaches to a parent, the controller that - manages the parent and the ancestors MUST add an entry to this list when - the controller first sees the policy and SHOULD update the entry as - appropriate when the relevant ancestor is modified. - - Note that choosing the relevant ancestor is left to the Policy designers; - an important part of Policy design is designing the right object level at - which to namespace this status. - - Note also that implementations MUST ONLY populate ancestor status for - the Ancestor resources they are responsible for. Implementations MUST - use the ControllerName field to uniquely identify the entries in this list - that they are responsible for. - - Note that to achieve this, the list of PolicyAncestorStatus structs - MUST be treated as a map with a composite key, made up of the AncestorRef - and ControllerName fields combined. - - A maximum of 16 ancestors will be represented in this list. An empty list - means the Policy is not relevant for any ancestors. - - If this slice is full, implementations MUST NOT add further entries. - Instead they MUST consider the policy unimplementable and signal that - on any related resources such as the ancestor that would be referenced - here. For example, if this list was full on BackendTLSPolicy, no - additional Gateways would be able to reference the Service targeted by - the BackendTLSPolicy. - items: - description: |- - PolicyAncestorStatus describes the status of a route with respect to an - associated Ancestor. - - Ancestors refer to objects that are either the Target of a policy or above it - in terms of object hierarchy. For example, if a policy targets a Service, the - Policy's Ancestors are, in order, the Service, the HTTPRoute, the Gateway, and - the GatewayClass. Almost always, in this hierarchy, the Gateway will be the most - useful object to place Policy status on, so we recommend that implementations - SHOULD use Gateway as the PolicyAncestorStatus object unless the designers - have a _very_ good reason otherwise. - - In the context of policy attachment, the Ancestor is used to distinguish which - resource results in a distinct application of this policy. For example, if a policy - targets a Service, it may have a distinct result per attached Gateway. - - Policies targeting the same resource may have different effects depending on the - ancestors of those resources. For example, different Gateways targeting the same - Service may have different capabilities, especially if they have different underlying - implementations. - - For example, in BackendTLSPolicy, the Policy attaches to a Service that is - used as a backend in a HTTPRoute that is itself attached to a Gateway. - In this case, the relevant object for status is the Gateway, and that is the - ancestor object referred to in this status. - - Note that a parent is also an ancestor, so for objects where the parent is the - relevant object for status, this struct SHOULD still be used. - - This struct is intended to be used in a slice that's effectively a map, - with a composite key made up of the AncestorRef and the ControllerName. - properties: - ancestorRef: - description: |- - AncestorRef corresponds with a ParentRef in the spec that this - PolicyAncestorStatus struct describes the status of. - properties: - group: - default: gateway.networking.k8s.io - description: |- - Group is the group of the referent. - When unspecified, "gateway.networking.k8s.io" is inferred. - To set the core API group (such as for a "Service" kind referent), - Group must be explicitly set to "" (empty string). - - Support: Core - maxLength: 253 - pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - kind: - default: Gateway - description: |- - Kind is kind of the referent. - - There are two kinds of parent resources with "Core" support: - - * Gateway (Gateway conformance profile) - * Service (Mesh conformance profile, ClusterIP Services only) - - Support for other resources is Implementation-Specific. - maxLength: 63 - minLength: 1 - pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ - type: string - name: - description: |- - Name is the name of the referent. - - Support: Core - maxLength: 253 - minLength: 1 - type: string - namespace: - description: |- - Namespace is the namespace of the referent. When unspecified, this refers - to the local namespace of the Route. - - Note that there are specific rules for ParentRefs which cross namespace - boundaries. Cross-namespace references are only valid if they are explicitly - allowed by something in the namespace they are referring to. For example: - Gateway has the AllowedRoutes field, and ReferenceGrant provides a - generic way to enable any other kind of cross-namespace reference. - - - ParentRefs from a Route to a Service in the same namespace are "producer" - routes, which apply default routing rules to inbound connections from - any namespace to the Service. - - ParentRefs from a Route to a Service in a different namespace are - "consumer" routes, and these routing rules are only applied to outbound - connections originating from the same namespace as the Route, for which - the intended destination of the connections are a Service targeted as a - ParentRef of the Route. - - - Support: Core - maxLength: 63 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ - type: string - port: - description: |- - Port is the network port this Route targets. It can be interpreted - differently based on the type of parent resource. - - When the parent resource is a Gateway, this targets all listeners - listening on the specified port that also support this kind of Route(and - select this Route). It's not recommended to set `Port` unless the - networking behaviors specified in a Route must apply to a specific port - as opposed to a listener(s) whose port(s) may be changed. When both Port - and SectionName are specified, the name and port of the selected listener - must match both specified values. - - - When the parent resource is a Service, this targets a specific port in the - Service spec. When both Port (experimental) and SectionName are specified, - the name and port of the selected port must match both specified values. - - - Implementations MAY choose to support other parent resources. - Implementations supporting other types of parent resources MUST clearly - document how/if Port is interpreted. - - For the purpose of status, an attachment is considered successful as - long as the parent resource accepts it partially. For example, Gateway - listeners can restrict which Routes can attach to them by Route kind, - namespace, or hostname. If 1 of 2 Gateway listeners accept attachment - from the referencing Route, the Route MUST be considered successfully - attached. If no Gateway listeners accept attachment from this Route, - the Route MUST be considered detached from the Gateway. - - Support: Extended - format: int32 - maximum: 65535 - minimum: 1 - type: integer - sectionName: - description: |- - SectionName is the name of a section within the target resource. In the - following resources, SectionName is interpreted as the following: - - * Gateway: Listener name. When both Port (experimental) and SectionName - are specified, the name and port of the selected listener must match - both specified values. - * Service: Port name. When both Port (experimental) and SectionName - are specified, the name and port of the selected listener must match - both specified values. - - Implementations MAY choose to support attaching Routes to other resources. - If that is the case, they MUST clearly document how SectionName is - interpreted. - - When unspecified (empty string), this will reference the entire resource. - For the purpose of status, an attachment is considered successful if at - least one section in the parent resource accepts it. For example, Gateway - listeners can restrict which Routes can attach to them by Route kind, - namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from - the referencing Route, the Route MUST be considered successfully - attached. If no Gateway listeners accept attachment from this Route, the - Route MUST be considered detached from the Gateway. - - Support: Core - maxLength: 253 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ - type: string - required: - - name - type: object - conditions: - description: Conditions describes the status of the Policy with - respect to the given Ancestor. - items: - description: Condition contains details for one aspect of - the current state of this API Resource. - properties: - lastTransitionTime: - description: |- - lastTransitionTime is the last time the condition transitioned from one status to another. - This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. - format: date-time - type: string - message: - description: |- - message is a human readable message indicating details about the transition. - This may be an empty string. - maxLength: 32768 - type: string - observedGeneration: - description: |- - observedGeneration represents the .metadata.generation that the condition was set based upon. - For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date - with respect to the current state of the instance. - format: int64 - minimum: 0 - type: integer - reason: - description: |- - reason contains a programmatic identifier indicating the reason for the condition's last transition. - Producers of specific condition types may define expected values and meanings for this field, - and whether the values are considered a guaranteed API. - The value should be a CamelCase string. - This field may not be empty. - maxLength: 1024 - minLength: 1 - pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ - type: string - status: - description: status of the condition, one of True, False, - Unknown. - enum: - - "True" - - "False" - - Unknown - type: string - type: - description: type of condition in CamelCase or in foo.example.com/CamelCase. - maxLength: 316 - pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ - type: string - required: - - lastTransitionTime - - message - - reason - - status - - type - type: object - maxItems: 8 - minItems: 1 - type: array - x-kubernetes-list-map-keys: - - type - x-kubernetes-list-type: map - controllerName: - description: |- - ControllerName is a domain/path string that indicates the name of the - controller that wrote this status. This corresponds with the - controllerName field on GatewayClass. - - Example: "example.net/gateway-controller". - - The format of this field is DOMAIN "/" PATH, where DOMAIN and PATH are - valid Kubernetes names - (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names). - - Controllers MUST populate this field when writing status. Controllers should ensure that - entries to status populated with their ControllerName are cleaned up when they are no - longer necessary. - maxLength: 253 - minLength: 1 - pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ - type: string - required: - - ancestorRef - - controllerName - type: object - maxItems: 16 - type: array - required: - - ancestors - type: object - required: - - spec - type: object - served: true - storage: true - subresources: - status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: null - storedVersions: null ---- -# # config/crd/experimental/gateway.networking.k8s.io_backendtlspolicies.yaml # apiVersion: apiextensions.k8s.io/v1 @@ -525,7 +24,7 @@ kind: CustomResourceDefinition metadata: annotations: api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/3328 - gateway.networking.k8s.io/bundle-version: v1.2.1 + gateway.networking.k8s.io/bundle-version: v1.3.0 gateway.networking.k8s.io/channel: experimental creationTimestamp: null labels: @@ -607,6 +106,14 @@ spec: by default, but this default may change in the future to provide a more granular application of the policy. + TargetRefs must be _distinct_. This means either that: + + * They select different targets. If this is the case, then targetRef + entries are distinct. In terms of fields, this means that the + multi-part key defined by `group`, `kind`, and `name` must + be unique across all targetRef entries in the BackendTLSPolicy. + * They select different sectionNames in the same target. + Support: Extended for Kubernetes Service Support: Implementation-specific for any other resource @@ -663,6 +170,20 @@ spec: maxItems: 16 minItems: 1 type: array + x-kubernetes-validations: + - message: sectionName must be specified when targetRefs includes + 2 or more references to the same target + rule: 'self.all(p1, self.all(p2, p1.group == p2.group && p1.kind + == p2.kind && p1.name == p2.name ? ((!has(p1.sectionName) || p1.sectionName + == '''') == (!has(p2.sectionName) || p2.sectionName == '''')) + : true))' + - message: sectionName must be unique when targetRefs includes 2 or + more references to the same target + rule: self.all(p1, self.exists_one(p2, p1.group == p2.group && p1.kind + == p2.kind && p1.name == p2.name && (((!has(p1.sectionName) || + p1.sectionName == '') && (!has(p2.sectionName) || p2.sectionName + == '')) || (has(p1.sectionName) && has(p2.sectionName) && p1.sectionName + == p2.sectionName)))) validation: description: Validation contains backend TLS validation configuration. properties: @@ -674,7 +195,7 @@ spec: If CACertificateRefs is empty or unspecified, then WellKnownCACertificates must be specified. Only one of CACertificateRefs or WellKnownCACertificates may be specified, - not both. If CACertifcateRefs is empty or unspecified, the configuration for + not both. If CACertificateRefs is empty or unspecified, the configuration for WellKnownCACertificates MUST be honored instead if supported by the implementation. References to a resource in a different namespace are invalid for the @@ -732,7 +253,7 @@ spec: backends: 1. Hostname MUST be used as the SNI to connect to the backend (RFC 6066). - 2. If SubjectAltNames is not specified, Hostname MUST be used for + 2. Hostname MUST be used for authentication and MUST match the certificate served by the matching backend, unless SubjectAltNames is specified. authentication and MUST match the certificate served by the matching backend. @@ -744,10 +265,10 @@ spec: subjectAltNames: description: |- SubjectAltNames contains one or more Subject Alternative Names. - When specified, the certificate served from the backend MUST have at least one - Subject Alternate Name matching one of the specified SubjectAltNames. + When specified the certificate served from the backend MUST + have at least one Subject Alternate Name matching one of the specified SubjectAltNames. - Support: Core + Support: Extended items: description: SubjectAltName represents Subject Alternative Name. properties: @@ -1154,7 +675,7 @@ kind: CustomResourceDefinition metadata: annotations: api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/3328 - gateway.networking.k8s.io/bundle-version: v1.2.1 + gateway.networking.k8s.io/bundle-version: v1.3.0 gateway.networking.k8s.io/channel: experimental creationTimestamp: null name: gatewayclasses.gateway.networking.k8s.io @@ -1389,7 +910,7 @@ spec: - type x-kubernetes-list-type: map supportedFeatures: - description: | + description: |- SupportedFeatures is the set of features the GatewayClass support. It MUST be sorted in ascending alphabetical order by the Name key. items: @@ -1633,7 +1154,7 @@ spec: - type x-kubernetes-list-type: map supportedFeatures: - description: | + description: |- SupportedFeatures is the set of features the GatewayClass support. It MUST be sorted in ascending alphabetical order by the Name key. items: @@ -1674,7 +1195,7 @@ kind: CustomResourceDefinition metadata: annotations: api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/3328 - gateway.networking.k8s.io/bundle-version: v1.2.1 + gateway.networking.k8s.io/bundle-version: v1.3.0 gateway.networking.k8s.io/channel: experimental creationTimestamp: null name: gateways.gateway.networking.k8s.io @@ -1732,7 +1253,7 @@ spec: description: Spec defines the desired state of Gateway. properties: addresses: - description: |+ + description: |- Addresses requested for this Gateway. This is optional and behavior can depend on the implementation. If a value is set in the spec and the requested address is invalid or unavailable, the implementation MUST @@ -1753,10 +1274,9 @@ spec: GatewayStatus.Addresses. Support: Extended - items: - description: GatewayAddress describes an address that can be bound - to a Gateway. + description: GatewaySpecAddress describes an address that can be + bound to a Gateway. oneOf: - properties: type: @@ -1781,15 +1301,15 @@ spec: type: string value: description: |- - Value of the address. The validity of the values will depend - on the type and support by the controller. + When a value is unspecified, an implementation SHOULD automatically + assign an address matching the requested type if possible. + + If an implementation does not support an empty value, they MUST set the + "Programmed" condition in status to False with a reason of "AddressNotAssigned". Examples: `1.2.3.4`, `128::1`, `my-ip-address`. maxLength: 253 - minLength: 1 type: string - required: - - value type: object x-kubernetes-validations: - message: Hostname value must only contain valid characters (matching @@ -1805,16 +1325,96 @@ spec: - message: Hostname values must be unique rule: 'self.all(a1, a1.type == ''Hostname'' ? self.exists_one(a2, a2.type == a1.type && a2.value == a1.value) : true )' + allowedListeners: + description: |- + AllowedListeners defines which ListenerSets can be attached to this Gateway. + While this feature is experimental, the default value is to allow no ListenerSets. + properties: + namespaces: + default: + from: None + description: |- + Namespaces defines which namespaces ListenerSets can be attached to this Gateway. + While this feature is experimental, the default value is to allow no ListenerSets. + properties: + from: + default: None + description: |- + From indicates where ListenerSets can attach to this Gateway. Possible + values are: + + * Same: Only ListenerSets in the same namespace may be attached to this Gateway. + * Selector: ListenerSets in namespaces selected by the selector may be attached to this Gateway. + * All: ListenerSets in all namespaces may be attached to this Gateway. + * None: Only listeners defined in the Gateway's spec are allowed + + While this feature is experimental, the default value None + enum: + - All + - Selector + - Same + - None + type: string + selector: + description: |- + Selector must be specified when From is set to "Selector". In that case, + only ListenerSets in Namespaces matching this Selector will be selected by this + Gateway. This field is ignored for other values of "From". + properties: + matchExpressions: + description: matchExpressions is a list of label selector + requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + type: object + type: object backendTLS: - description: |+ + description: |- BackendTLS configures TLS settings for when this Gateway is connecting to backends with TLS. Support: Core - properties: clientCertificateRef: - description: |+ + description: |- ClientCertificateRef is a reference to an object that contains a Client Certificate and the associated private key. @@ -1830,7 +1430,6 @@ spec: This setting can be overridden on the service level by use of BackendTLSPolicy. Support: Core - properties: group: default: "" @@ -1965,6 +1564,11 @@ spec: the merging behavior is implementation specific. It is generally recommended that GatewayClass provides defaults that can be overridden by a Gateway. + If the referent cannot be found, refers to an unsupported kind, or when + the data within that resource is malformed, the Gateway SHOULD be + rejected with the "Accepted" status condition set to "False" and an + "InvalidParameters" reason. + Support: Implementation-specific properties: group: @@ -1995,6 +1599,8 @@ spec: logical endpoints that are bound on this Gateway's addresses. At least one Listener MUST be specified. + ## Distinct Listeners + Each Listener in a set of Listeners (for example, in a single Gateway) MUST be _distinct_, in that a traffic flow MUST be able to be assigned to exactly one listener. (This section uses "set of Listeners" rather than @@ -2006,55 +1612,76 @@ spec: combination of Port, Protocol, and, if supported by the protocol, Hostname. Some combinations of port, protocol, and TLS settings are considered - Core support and MUST be supported by implementations based on their - targeted conformance profile: + Core support and MUST be supported by implementations based on the objects + they support: - HTTP Profile + HTTPRoute 1. HTTPRoute, Port: 80, Protocol: HTTP 2. HTTPRoute, Port: 443, Protocol: HTTPS, TLS Mode: Terminate, TLS keypair provided - TLS Profile + TLSRoute 1. TLSRoute, Port: 443, Protocol: TLS, TLS Mode: Passthrough "Distinct" Listeners have the following property: - The implementation can match inbound requests to a single distinct - Listener. When multiple Listeners share values for fields (for + **The implementation can match inbound requests to a single distinct + Listener**. + + When multiple Listeners share values for fields (for example, two Listeners with the same Port value), the implementation can match requests to only one of the Listeners using other Listener fields. - For example, the following Listener scenarios are distinct: + When multiple listeners have the same value for the Protocol field, then + each of the Listeners with matching Protocol values MUST have different + values for other fields. - 1. Multiple Listeners with the same Port that all use the "HTTP" - Protocol that all have unique Hostname values. - 2. Multiple Listeners with the same Port that use either the "HTTPS" or - "TLS" Protocol that all have unique Hostname values. - 3. A mixture of "TCP" and "UDP" Protocol Listeners, where no Listener - with the same Protocol has the same Port value. + The set of fields that MUST be different for a Listener differs per protocol. + The following rules define the rules for what fields MUST be considered for + Listeners to be distinct with each protocol currently defined in the + Gateway API spec. - Some fields in the Listener struct have possible values that affect - whether the Listener is distinct. Hostname is particularly relevant - for HTTP or HTTPS protocols. + The set of listeners that all share a protocol value MUST have _different_ + values for _at least one_ of these fields to be distinct: - When using the Hostname value to select between same-Port, same-Protocol - Listeners, the Hostname value must be different on each Listener for the - Listener to be distinct. + * **HTTP, HTTPS, TLS**: Port, Hostname + * **TCP, UDP**: Port - When the Listeners are distinct based on Hostname, inbound request + One **very** important rule to call out involves what happens when an + implementation: + + * Supports TCP protocol Listeners, as well as HTTP, HTTPS, or TLS protocol + Listeners, and + * sees HTTP, HTTPS, or TLS protocols with the same `port` as one with TCP + Protocol. + + In this case all the Listeners that share a port with the + TCP Listener are not distinct and so MUST NOT be accepted. + + If an implementation does not support TCP Protocol Listeners, then the + previous rule does not apply, and the TCP Listeners SHOULD NOT be + accepted. + + Note that the `tls` field is not used for determining if a listener is distinct, because + Listeners that _only_ differ on TLS config will still conflict in all cases. + + ### Listeners that are distinct only by Hostname + + When the Listeners are distinct based only on Hostname, inbound request hostnames MUST match from the most specific to least specific Hostname values to choose the correct Listener and its associated set of Routes. - Exact matches must be processed before wildcard matches, and wildcard - matches must be processed before fallback (empty Hostname value) + Exact matches MUST be processed before wildcard matches, and wildcard + matches MUST be processed before fallback (empty Hostname value) matches. For example, `"foo.example.com"` takes precedence over `"*.example.com"`, and `"*.example.com"` takes precedence over `""`. Additionally, if there are multiple wildcard entries, more specific wildcard entries must be processed before less specific wildcard entries. For example, `"*.foo.example.com"` takes precedence over `"*.example.com"`. + The precise definition here is that the higher the number of dots in the hostname to the right of the wildcard character, the higher the precedence. @@ -2062,18 +1689,26 @@ spec: the left, however, so `"*.example.com"` will match both `"foo.bar.example.com"` _and_ `"bar.example.com"`. + ## Handling indistinct Listeners + If a set of Listeners contains Listeners that are not distinct, then those - Listeners are Conflicted, and the implementation MUST set the "Conflicted" + Listeners are _Conflicted_, and the implementation MUST set the "Conflicted" condition in the Listener Status to "True". + The words "indistinct" and "conflicted" are considered equivalent for the + purpose of this documentation. + Implementations MAY choose to accept a Gateway with some Conflicted Listeners only if they only accept the partial Listener set that contains - no Conflicted Listeners. To put this another way, implementations may - accept a partial Listener set only if they throw out *all* the conflicting - Listeners. No picking one of the conflicting listeners as the winner. - This also means that the Gateway must have at least one non-conflicting - Listener in this case, otherwise it violates the requirement that at - least one Listener must be present. + no Conflicted Listeners. + + Specifically, an implementation MAY accept a partial Listener set subject to + the following rules: + + * The implementation MUST NOT pick one conflicting Listener as the winner. + ALL indistinct Listeners must not be accepted for processing. + * At least one distinct Listener MUST be present, or else the Gateway effectively + contains _no_ Listeners, and must be rejected from processing as a whole. The implementation MUST set a "ListenersNotValid" condition on the Gateway Status when the Gateway contains Conflicted Listeners whether or @@ -2082,7 +1717,25 @@ spec: Accepted. Additionally, the Listener status for those listeners SHOULD indicate which Listeners are conflicted and not Accepted. - A Gateway's Listeners are considered "compatible" if: + ## General Listener behavior + + Note that, for all distinct Listeners, requests SHOULD match at most one Listener. + For example, if Listeners are defined for "foo.example.com" and "*.example.com", a + request to "foo.example.com" SHOULD only be routed using routes attached + to the "foo.example.com" Listener (and not the "*.example.com" Listener). + + This concept is known as "Listener Isolation", and it is an Extended feature + of Gateway API. Implementations that do not support Listener Isolation MUST + clearly document this, and MUST NOT claim support for the + `GatewayHTTPListenerIsolation` feature. + + Implementations that _do_ support Listener Isolation SHOULD claim support + for the Extended `GatewayHTTPListenerIsolation` feature and pass the associated + conformance tests. + + ## Compatible Listeners + + A Gateway's Listeners are considered _compatible_ if: 1. They are distinct. 2. The implementation can serve them in compliance with the Addresses @@ -2097,16 +1750,11 @@ spec: on the same address, or cannot mix HTTPS and generic TLS listens on the same port would not consider those cases compatible, even though they are distinct. - Note that requests SHOULD match at most one Listener. For example, if - Listeners are defined for "foo.example.com" and "*.example.com", a - request to "foo.example.com" SHOULD only be routed using routes attached - to the "foo.example.com" Listener (and not the "*.example.com" Listener). - This concept is known as "Listener Isolation". Implementations that do - not support Listener Isolation MUST clearly document this. - Implementations MAY merge separate Gateways onto a single set of Addresses if all Listeners across all Gateways are compatible. + In a future release the MinItems=1 requirement MAY be dropped. + Support: Core items: description: |- @@ -2268,10 +1916,31 @@ spec: * TLS: The Listener Hostname MUST match the SNI. * HTTP: The Listener Hostname MUST match the Host header of the request. - * HTTPS: The Listener Hostname SHOULD match at both the TLS and HTTP - protocol layers as described above. If an implementation does not - ensure that both the SNI and Host header match the Listener hostname, - it MUST clearly document that. + * HTTPS: The Listener Hostname SHOULD match both the SNI and Host header. + Note that this does not require the SNI and Host header to be the same. + The semantics of this are described in more detail below. + + To ensure security, Section 11.1 of RFC-6066 emphasizes that server + implementations that rely on SNI hostname matching MUST also verify + hostnames within the application protocol. + + Section 9.1.2 of RFC-7540 provides a mechanism for servers to reject the + reuse of a connection by responding with the HTTP 421 Misdirected Request + status code. This indicates that the origin server has rejected the + request because it appears to have been misdirected. + + To detect misdirected requests, Gateways SHOULD match the authority of + the requests with all the SNI hostname(s) configured across all the + Gateway Listeners on the same port and protocol: + + * If another Listener has an exact match or more specific wildcard entry, + the Gateway SHOULD return a 421. + * If the current Listener (selected by SNI matching during ClientHello) + does not match the Host: + * If another Listener does match the Host the Gateway SHOULD return a + 421. + * If no other Listener matches the Host, the Gateway MUST return a + 404. For HTTPRoute and TLSRoute resources, there is an interaction with the `spec.hostnames` array. When both listener and route specify hostnames, @@ -2411,7 +2080,7 @@ spec: maxItems: 64 type: array frontendValidation: - description: |+ + description: |- FrontendValidation holds configuration information for validating the frontend (client). Setting this field will require clients to send a client certificate required for validation during the TLS handshake. In browsers this may result in a dialog appearing @@ -2419,7 +2088,6 @@ spec: The maximum depth of a certificate chain accepted in verification is Implementation specific. Support: Extended - properties: caCertificateRefs: description: |- @@ -2458,7 +2126,7 @@ spec: group: description: |- Group is the group of the referent. For example, "gateway.networking.k8s.io". - When unspecified or empty string, core API group is inferred. + When set to the empty string, core API group is inferred. maxLength: 253 pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ type: string @@ -2596,7 +2264,7 @@ spec: description: Status defines the current state of Gateway. properties: addresses: - description: |+ + description: |- Addresses lists the network addresses that have been bound to the Gateway. @@ -2606,7 +2274,6 @@ spec: * no addresses are specified, all addresses are dynamically assigned * a combination of specified and dynamic addresses are assigned * a specified address was unusable (e.g. already in use) - items: description: GatewayStatusAddress describes a network address that is bound to a Gateway. @@ -2924,7 +2591,7 @@ spec: description: Spec defines the desired state of Gateway. properties: addresses: - description: |+ + description: |- Addresses requested for this Gateway. This is optional and behavior can depend on the implementation. If a value is set in the spec and the requested address is invalid or unavailable, the implementation MUST @@ -2945,10 +2612,9 @@ spec: GatewayStatus.Addresses. Support: Extended - items: - description: GatewayAddress describes an address that can be bound - to a Gateway. + description: GatewaySpecAddress describes an address that can be + bound to a Gateway. oneOf: - properties: type: @@ -2973,15 +2639,15 @@ spec: type: string value: description: |- - Value of the address. The validity of the values will depend - on the type and support by the controller. + When a value is unspecified, an implementation SHOULD automatically + assign an address matching the requested type if possible. + + If an implementation does not support an empty value, they MUST set the + "Programmed" condition in status to False with a reason of "AddressNotAssigned". Examples: `1.2.3.4`, `128::1`, `my-ip-address`. maxLength: 253 - minLength: 1 type: string - required: - - value type: object x-kubernetes-validations: - message: Hostname value must only contain valid characters (matching @@ -2997,16 +2663,96 @@ spec: - message: Hostname values must be unique rule: 'self.all(a1, a1.type == ''Hostname'' ? self.exists_one(a2, a2.type == a1.type && a2.value == a1.value) : true )' + allowedListeners: + description: |- + AllowedListeners defines which ListenerSets can be attached to this Gateway. + While this feature is experimental, the default value is to allow no ListenerSets. + properties: + namespaces: + default: + from: None + description: |- + Namespaces defines which namespaces ListenerSets can be attached to this Gateway. + While this feature is experimental, the default value is to allow no ListenerSets. + properties: + from: + default: None + description: |- + From indicates where ListenerSets can attach to this Gateway. Possible + values are: + + * Same: Only ListenerSets in the same namespace may be attached to this Gateway. + * Selector: ListenerSets in namespaces selected by the selector may be attached to this Gateway. + * All: ListenerSets in all namespaces may be attached to this Gateway. + * None: Only listeners defined in the Gateway's spec are allowed + + While this feature is experimental, the default value None + enum: + - All + - Selector + - Same + - None + type: string + selector: + description: |- + Selector must be specified when From is set to "Selector". In that case, + only ListenerSets in Namespaces matching this Selector will be selected by this + Gateway. This field is ignored for other values of "From". + properties: + matchExpressions: + description: matchExpressions is a list of label selector + requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the selector + applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + type: object + type: object backendTLS: - description: |+ + description: |- BackendTLS configures TLS settings for when this Gateway is connecting to backends with TLS. Support: Core - properties: clientCertificateRef: - description: |+ + description: |- ClientCertificateRef is a reference to an object that contains a Client Certificate and the associated private key. @@ -3022,7 +2768,6 @@ spec: This setting can be overridden on the service level by use of BackendTLSPolicy. Support: Core - properties: group: default: "" @@ -3157,6 +2902,11 @@ spec: the merging behavior is implementation specific. It is generally recommended that GatewayClass provides defaults that can be overridden by a Gateway. + If the referent cannot be found, refers to an unsupported kind, or when + the data within that resource is malformed, the Gateway SHOULD be + rejected with the "Accepted" status condition set to "False" and an + "InvalidParameters" reason. + Support: Implementation-specific properties: group: @@ -3187,6 +2937,8 @@ spec: logical endpoints that are bound on this Gateway's addresses. At least one Listener MUST be specified. + ## Distinct Listeners + Each Listener in a set of Listeners (for example, in a single Gateway) MUST be _distinct_, in that a traffic flow MUST be able to be assigned to exactly one listener. (This section uses "set of Listeners" rather than @@ -3198,55 +2950,76 @@ spec: combination of Port, Protocol, and, if supported by the protocol, Hostname. Some combinations of port, protocol, and TLS settings are considered - Core support and MUST be supported by implementations based on their - targeted conformance profile: + Core support and MUST be supported by implementations based on the objects + they support: - HTTP Profile + HTTPRoute 1. HTTPRoute, Port: 80, Protocol: HTTP 2. HTTPRoute, Port: 443, Protocol: HTTPS, TLS Mode: Terminate, TLS keypair provided - TLS Profile + TLSRoute 1. TLSRoute, Port: 443, Protocol: TLS, TLS Mode: Passthrough "Distinct" Listeners have the following property: - The implementation can match inbound requests to a single distinct - Listener. When multiple Listeners share values for fields (for + **The implementation can match inbound requests to a single distinct + Listener**. + + When multiple Listeners share values for fields (for example, two Listeners with the same Port value), the implementation can match requests to only one of the Listeners using other Listener fields. - For example, the following Listener scenarios are distinct: + When multiple listeners have the same value for the Protocol field, then + each of the Listeners with matching Protocol values MUST have different + values for other fields. - 1. Multiple Listeners with the same Port that all use the "HTTP" - Protocol that all have unique Hostname values. - 2. Multiple Listeners with the same Port that use either the "HTTPS" or - "TLS" Protocol that all have unique Hostname values. - 3. A mixture of "TCP" and "UDP" Protocol Listeners, where no Listener - with the same Protocol has the same Port value. + The set of fields that MUST be different for a Listener differs per protocol. + The following rules define the rules for what fields MUST be considered for + Listeners to be distinct with each protocol currently defined in the + Gateway API spec. - Some fields in the Listener struct have possible values that affect - whether the Listener is distinct. Hostname is particularly relevant - for HTTP or HTTPS protocols. + The set of listeners that all share a protocol value MUST have _different_ + values for _at least one_ of these fields to be distinct: - When using the Hostname value to select between same-Port, same-Protocol - Listeners, the Hostname value must be different on each Listener for the - Listener to be distinct. + * **HTTP, HTTPS, TLS**: Port, Hostname + * **TCP, UDP**: Port - When the Listeners are distinct based on Hostname, inbound request + One **very** important rule to call out involves what happens when an + implementation: + + * Supports TCP protocol Listeners, as well as HTTP, HTTPS, or TLS protocol + Listeners, and + * sees HTTP, HTTPS, or TLS protocols with the same `port` as one with TCP + Protocol. + + In this case all the Listeners that share a port with the + TCP Listener are not distinct and so MUST NOT be accepted. + + If an implementation does not support TCP Protocol Listeners, then the + previous rule does not apply, and the TCP Listeners SHOULD NOT be + accepted. + + Note that the `tls` field is not used for determining if a listener is distinct, because + Listeners that _only_ differ on TLS config will still conflict in all cases. + + ### Listeners that are distinct only by Hostname + + When the Listeners are distinct based only on Hostname, inbound request hostnames MUST match from the most specific to least specific Hostname values to choose the correct Listener and its associated set of Routes. - Exact matches must be processed before wildcard matches, and wildcard - matches must be processed before fallback (empty Hostname value) + Exact matches MUST be processed before wildcard matches, and wildcard + matches MUST be processed before fallback (empty Hostname value) matches. For example, `"foo.example.com"` takes precedence over `"*.example.com"`, and `"*.example.com"` takes precedence over `""`. Additionally, if there are multiple wildcard entries, more specific wildcard entries must be processed before less specific wildcard entries. For example, `"*.foo.example.com"` takes precedence over `"*.example.com"`. + The precise definition here is that the higher the number of dots in the hostname to the right of the wildcard character, the higher the precedence. @@ -3254,18 +3027,26 @@ spec: the left, however, so `"*.example.com"` will match both `"foo.bar.example.com"` _and_ `"bar.example.com"`. + ## Handling indistinct Listeners + If a set of Listeners contains Listeners that are not distinct, then those - Listeners are Conflicted, and the implementation MUST set the "Conflicted" + Listeners are _Conflicted_, and the implementation MUST set the "Conflicted" condition in the Listener Status to "True". + The words "indistinct" and "conflicted" are considered equivalent for the + purpose of this documentation. + Implementations MAY choose to accept a Gateway with some Conflicted Listeners only if they only accept the partial Listener set that contains - no Conflicted Listeners. To put this another way, implementations may - accept a partial Listener set only if they throw out *all* the conflicting - Listeners. No picking one of the conflicting listeners as the winner. - This also means that the Gateway must have at least one non-conflicting - Listener in this case, otherwise it violates the requirement that at - least one Listener must be present. + no Conflicted Listeners. + + Specifically, an implementation MAY accept a partial Listener set subject to + the following rules: + + * The implementation MUST NOT pick one conflicting Listener as the winner. + ALL indistinct Listeners must not be accepted for processing. + * At least one distinct Listener MUST be present, or else the Gateway effectively + contains _no_ Listeners, and must be rejected from processing as a whole. The implementation MUST set a "ListenersNotValid" condition on the Gateway Status when the Gateway contains Conflicted Listeners whether or @@ -3274,7 +3055,25 @@ spec: Accepted. Additionally, the Listener status for those listeners SHOULD indicate which Listeners are conflicted and not Accepted. - A Gateway's Listeners are considered "compatible" if: + ## General Listener behavior + + Note that, for all distinct Listeners, requests SHOULD match at most one Listener. + For example, if Listeners are defined for "foo.example.com" and "*.example.com", a + request to "foo.example.com" SHOULD only be routed using routes attached + to the "foo.example.com" Listener (and not the "*.example.com" Listener). + + This concept is known as "Listener Isolation", and it is an Extended feature + of Gateway API. Implementations that do not support Listener Isolation MUST + clearly document this, and MUST NOT claim support for the + `GatewayHTTPListenerIsolation` feature. + + Implementations that _do_ support Listener Isolation SHOULD claim support + for the Extended `GatewayHTTPListenerIsolation` feature and pass the associated + conformance tests. + + ## Compatible Listeners + + A Gateway's Listeners are considered _compatible_ if: 1. They are distinct. 2. The implementation can serve them in compliance with the Addresses @@ -3289,16 +3088,11 @@ spec: on the same address, or cannot mix HTTPS and generic TLS listens on the same port would not consider those cases compatible, even though they are distinct. - Note that requests SHOULD match at most one Listener. For example, if - Listeners are defined for "foo.example.com" and "*.example.com", a - request to "foo.example.com" SHOULD only be routed using routes attached - to the "foo.example.com" Listener (and not the "*.example.com" Listener). - This concept is known as "Listener Isolation". Implementations that do - not support Listener Isolation MUST clearly document this. - Implementations MAY merge separate Gateways onto a single set of Addresses if all Listeners across all Gateways are compatible. + In a future release the MinItems=1 requirement MAY be dropped. + Support: Core items: description: |- @@ -3460,10 +3254,31 @@ spec: * TLS: The Listener Hostname MUST match the SNI. * HTTP: The Listener Hostname MUST match the Host header of the request. - * HTTPS: The Listener Hostname SHOULD match at both the TLS and HTTP - protocol layers as described above. If an implementation does not - ensure that both the SNI and Host header match the Listener hostname, - it MUST clearly document that. + * HTTPS: The Listener Hostname SHOULD match both the SNI and Host header. + Note that this does not require the SNI and Host header to be the same. + The semantics of this are described in more detail below. + + To ensure security, Section 11.1 of RFC-6066 emphasizes that server + implementations that rely on SNI hostname matching MUST also verify + hostnames within the application protocol. + + Section 9.1.2 of RFC-7540 provides a mechanism for servers to reject the + reuse of a connection by responding with the HTTP 421 Misdirected Request + status code. This indicates that the origin server has rejected the + request because it appears to have been misdirected. + + To detect misdirected requests, Gateways SHOULD match the authority of + the requests with all the SNI hostname(s) configured across all the + Gateway Listeners on the same port and protocol: + + * If another Listener has an exact match or more specific wildcard entry, + the Gateway SHOULD return a 421. + * If the current Listener (selected by SNI matching during ClientHello) + does not match the Host: + * If another Listener does match the Host the Gateway SHOULD return a + 421. + * If no other Listener matches the Host, the Gateway MUST return a + 404. For HTTPRoute and TLSRoute resources, there is an interaction with the `spec.hostnames` array. When both listener and route specify hostnames, @@ -3603,7 +3418,7 @@ spec: maxItems: 64 type: array frontendValidation: - description: |+ + description: |- FrontendValidation holds configuration information for validating the frontend (client). Setting this field will require clients to send a client certificate required for validation during the TLS handshake. In browsers this may result in a dialog appearing @@ -3611,7 +3426,6 @@ spec: The maximum depth of a certificate chain accepted in verification is Implementation specific. Support: Extended - properties: caCertificateRefs: description: |- @@ -3650,7 +3464,7 @@ spec: group: description: |- Group is the group of the referent. For example, "gateway.networking.k8s.io". - When unspecified or empty string, core API group is inferred. + When set to the empty string, core API group is inferred. maxLength: 253 pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ type: string @@ -3788,7 +3602,7 @@ spec: description: Status defines the current state of Gateway. properties: addresses: - description: |+ + description: |- Addresses lists the network addresses that have been bound to the Gateway. @@ -3798,7 +3612,6 @@ spec: * no addresses are specified, all addresses are dynamically assigned * a combination of specified and dynamic addresses are assigned * a specified address was unusable (e.g. already in use) - items: description: GatewayStatusAddress describes a network address that is bound to a Gateway. @@ -4090,7 +3903,7 @@ kind: CustomResourceDefinition metadata: annotations: api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/3328 - gateway.networking.k8s.io/bundle-version: v1.2.1 + gateway.networking.k8s.io/bundle-version: v1.3.0 gateway.networking.k8s.io/channel: experimental creationTimestamp: null name: grpcroutes.gateway.networking.k8s.io @@ -4239,7 +4052,7 @@ spec: maxItems: 16 type: array parentRefs: - description: |+ + description: |- ParentRefs references the resources (usually Gateways) that a Route wants to be attached to. Note that the referenced parent resource needs to allow this for the attachment to be complete. For Gateways, that means @@ -4301,11 +4114,6 @@ spec: connections originating from the same namespace as the Route, for which the intended destination of the connections are a Service targeted as a ParentRef of the Route. - - - - - items: description: |- ParentReference identifies an API object (usually a Gateway) that can be considered @@ -4479,9 +4287,7 @@ spec: || p2.port == 0)) || (has(p1.port) && has(p2.port) && p1.port == p2.port)))) rules: - description: |+ - Rules are a list of GRPC matchers, filters and actions. - + description: Rules are a list of GRPC matchers, filters and actions. items: description: |- GRPCRouteRule defines the semantics for matching a gRPC request based on @@ -4527,7 +4333,6 @@ spec: namespace's owner to accept the reference. See the ReferenceGrant documentation for details. - When the BackendRef points to a Kubernetes Service, implementations SHOULD honor the appProtocol field if it is set for the target Service Port. @@ -4542,8 +4347,6 @@ spec: If a Route is not able to send traffic to the backend using the specified protocol then the backend is considered invalid. Implementations MUST set the "ResolvedRefs" condition to "False" with the "UnsupportedProtocol" reason. - - properties: filters: description: |- @@ -4629,7 +4432,7 @@ spec: name: description: |- Name is the name of the HTTP Header to be matched. Name matching MUST be - case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries @@ -4704,7 +4507,7 @@ spec: name: description: |- Name is the name of the HTTP Header to be matched. Name matching MUST be - case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries @@ -4732,7 +4535,7 @@ spec: x-kubernetes-list-type: map type: object requestMirror: - description: |+ + description: |- RequestMirror defines a schema for a filter that mirrors requests. Requests are sent to the specified destination, but responses from that destination are ignored. @@ -4742,7 +4545,6 @@ spec: backends. Support: Extended - properties: backendRef: description: |- @@ -4838,13 +4640,12 @@ spec: rule: '(size(self.group) == 0 && self.kind == ''Service'') ? has(self.port) : true' fraction: - description: |+ + description: |- Fraction represents the fraction of requests that should be mirrored to BackendRef. Only one of Fraction or Percent may be specified. If neither field is specified, 100% of requests will be mirrored. - properties: denominator: default: 100 @@ -4863,14 +4664,13 @@ spec: to denominator rule: self.numerator <= self.denominator percent: - description: |+ + description: |- Percent represents the percentage of requests that should be mirrored to BackendRef. Its minimum value is 0 (indicating 0% of requests) and its maximum value is 100 (indicating 100% of requests). Only one of Fraction or Percent may be specified. If neither field is specified, 100% of requests will be mirrored. - format: int32 maximum: 100 minimum: 0 @@ -4915,7 +4715,7 @@ spec: name: description: |- Name is the name of the HTTP Header to be matched. Name matching MUST be - case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries @@ -4990,7 +4790,7 @@ spec: name: description: |- Name is the name of the HTTP Header to be matched. Name matching MUST be - case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries @@ -5018,7 +4818,7 @@ spec: x-kubernetes-list-type: map type: object type: - description: |+ + description: |- Type identifies the type of filter to apply. As with other API fields, types are classified into three conformance levels: @@ -5043,7 +4843,6 @@ spec: If a reference to a custom filter type cannot be resolved, the filter MUST NOT be skipped. Instead, requests that would have been processed by that filter MUST receive a HTTP error response. - enum: - ResponseHeaderModifier - RequestHeaderModifier @@ -5200,7 +4999,7 @@ spec: Specifying the same filter multiple times is not supported unless explicitly indicated in the filter. - If an implementation can not support a combination of filters, it must clearly + If an implementation cannot support a combination of filters, it must clearly document that limitation. In cases where incompatible or unsupported filters are specified and cause the `Accepted` condition to be set to status `False`, implementations may use the `IncompatibleFilters` reason to specify @@ -5283,7 +5082,7 @@ spec: name: description: |- Name is the name of the HTTP Header to be matched. Name matching MUST be - case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries @@ -5357,7 +5156,7 @@ spec: name: description: |- Name is the name of the HTTP Header to be matched. Name matching MUST be - case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries @@ -5385,7 +5184,7 @@ spec: x-kubernetes-list-type: map type: object requestMirror: - description: |+ + description: |- RequestMirror defines a schema for a filter that mirrors requests. Requests are sent to the specified destination, but responses from that destination are ignored. @@ -5395,7 +5194,6 @@ spec: backends. Support: Extended - properties: backendRef: description: |- @@ -5491,13 +5289,12 @@ spec: rule: '(size(self.group) == 0 && self.kind == ''Service'') ? has(self.port) : true' fraction: - description: |+ + description: |- Fraction represents the fraction of requests that should be mirrored to BackendRef. Only one of Fraction or Percent may be specified. If neither field is specified, 100% of requests will be mirrored. - properties: denominator: default: 100 @@ -5516,14 +5313,13 @@ spec: denominator rule: self.numerator <= self.denominator percent: - description: |+ + description: |- Percent represents the percentage of requests that should be mirrored to BackendRef. Its minimum value is 0 (indicating 0% of requests) and its maximum value is 100 (indicating 100% of requests). Only one of Fraction or Percent may be specified. If neither field is specified, 100% of requests will be mirrored. - format: int32 maximum: 100 minimum: 0 @@ -5567,7 +5363,7 @@ spec: name: description: |- Name is the name of the HTTP Header to be matched. Name matching MUST be - case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries @@ -5641,7 +5437,7 @@ spec: name: description: |- Name is the name of the HTTP Header to be matched. Name matching MUST be - case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries @@ -5669,7 +5465,7 @@ spec: x-kubernetes-list-type: map type: object type: - description: |+ + description: |- Type identifies the type of filter to apply. As with other API fields, types are classified into three conformance levels: @@ -5694,7 +5490,6 @@ spec: If a reference to a custom filter type cannot be resolved, the filter MUST NOT be skipped. Instead, requests that would have been processed by that filter MUST receive a HTTP error response. - enum: - ResponseHeaderModifier - RequestHeaderModifier @@ -5910,10 +5705,10 @@ spec: has(self.method) ? self.method.matches(r"""^[A-Za-z_][A-Za-z_0-9]*$"""): true' type: object - maxItems: 8 + maxItems: 64 type: array name: - description: | + description: |- Name is the name of the route rule. This name MUST be unique within a Route if it is set. Support: Extended @@ -5922,12 +5717,11 @@ spec: pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ type: string sessionPersistence: - description: |+ + description: |- SessionPersistence defines and configures session persistence for the route rule. Support: Extended - properties: absoluteTimeout: description: |- @@ -5962,6 +5756,8 @@ spec: absolute lifetime of the cookie tracked by the gateway and is optional. + Defaults to "Session". + Support: Core for "Session" type Support: Extended for "Permanent" type @@ -6079,7 +5875,7 @@ spec: There are a number of cases where the "Accepted" condition may not be set due to lack of controller visibility, that includes when: - * The Route refers to a non-existent parent. + * The Route refers to a nonexistent parent. * The Route is of a type that the controller does not support. * The Route is in a namespace the controller does not have access to. items: @@ -6328,7 +6124,7 @@ kind: CustomResourceDefinition metadata: annotations: api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/3328 - gateway.networking.k8s.io/bundle-version: v1.2.1 + gateway.networking.k8s.io/bundle-version: v1.3.0 gateway.networking.k8s.io/channel: experimental creationTimestamp: null name: httproutes.gateway.networking.k8s.io @@ -6457,7 +6253,7 @@ spec: maxItems: 16 type: array parentRefs: - description: |+ + description: |- ParentRefs references the resources (usually Gateways) that a Route wants to be attached to. Note that the referenced parent resource needs to allow this for the attachment to be complete. For Gateways, that means @@ -6519,11 +6315,6 @@ spec: connections originating from the same namespace as the Route, for which the intended destination of the connections are a Service targeted as a ParentRef of the Route. - - - - - items: description: |- ParentReference identifies an API object (usually a Gateway) that can be considered @@ -6702,9 +6493,7 @@ spec: - path: type: PathPrefix value: / - description: |+ - Rules are a list of HTTP matchers, filters and actions. - + description: Rules are a list of HTTP matchers, filters and actions. items: description: |- HTTPRouteRule defines semantics for matching an HTTP request based on @@ -6757,7 +6546,6 @@ spec: namespace's owner to accept the reference. See the ReferenceGrant documentation for details. - When the BackendRef points to a Kubernetes Service, implementations SHOULD honor the appProtocol field if it is set for the target Service Port. @@ -6772,8 +6560,6 @@ spec: If a Route is not able to send traffic to the backend using the specified protocol then the backend is considered invalid. Implementations MUST set the "ResolvedRefs" condition to "False" with the "UnsupportedProtocol" reason. - - properties: filters: description: |- @@ -6791,6 +6577,289 @@ spec: authentication strategies, rate-limiting, and traffic shaping. API guarantee/conformance is defined based on the type of the filter. properties: + cors: + description: |- + CORS defines a schema for a filter that responds to the + cross-origin request based on HTTP response header. + + Support: Extended + properties: + allowCredentials: + description: |- + AllowCredentials indicates whether the actual cross-origin request allows + to include credentials. + + The only valid value for the `Access-Control-Allow-Credentials` response + header is true (case-sensitive). + + If the credentials are not allowed in cross-origin requests, the gateway + will omit the header `Access-Control-Allow-Credentials` entirely rather + than setting its value to false. + + Support: Extended + enum: + - true + type: boolean + allowHeaders: + description: |- + AllowHeaders indicates which HTTP request headers are supported for + accessing the requested resource. + + Header names are not case sensitive. + + Multiple header names in the value of the `Access-Control-Allow-Headers` + response header are separated by a comma (","). + + When the `AllowHeaders` field is configured with one or more headers, the + gateway must return the `Access-Control-Allow-Headers` response header + which value is present in the `AllowHeaders` field. + + If any header name in the `Access-Control-Request-Headers` request header + is not included in the list of header names specified by the response + header `Access-Control-Allow-Headers`, it will present an error on the + client side. + + If any header name in the `Access-Control-Allow-Headers` response header + does not recognize by the client, it will also occur an error on the + client side. + + A wildcard indicates that the requests with all HTTP headers are allowed. + The `Access-Control-Allow-Headers` response header can only use `*` + wildcard as value when the `AllowCredentials` field is unspecified. + + When the `AllowCredentials` field is specified and `AllowHeaders` field + specified with the `*` wildcard, the gateway must specify one or more + HTTP headers in the value of the `Access-Control-Allow-Headers` response + header. The value of the header `Access-Control-Allow-Headers` is same as + the `Access-Control-Request-Headers` header provided by the client. If + the header `Access-Control-Request-Headers` is not included in the + request, the gateway will omit the `Access-Control-Allow-Headers` + response header, instead of specifying the `*` wildcard. A Gateway + implementation may choose to add implementation-specific default headers. + + Support: Extended + items: + description: |- + HTTPHeaderName is the name of an HTTP header. + + Valid values include: + + * "Authorization" + * "Set-Cookie" + + Invalid values include: + + - ":method" - ":" is an invalid character. This means that HTTP/2 pseudo + headers are not currently supported by this type. + - "/invalid" - "/ " is an invalid character + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + maxItems: 64 + type: array + x-kubernetes-list-type: set + allowMethods: + description: |- + AllowMethods indicates which HTTP methods are supported for accessing the + requested resource. + + Valid values are any method defined by RFC9110, along with the special + value `*`, which represents all HTTP methods are allowed. + + Method names are case sensitive, so these values are also case-sensitive. + (See https://www.rfc-editor.org/rfc/rfc2616#section-5.1.1) + + Multiple method names in the value of the `Access-Control-Allow-Methods` + response header are separated by a comma (","). + + A CORS-safelisted method is a method that is `GET`, `HEAD`, or `POST`. + (See https://fetch.spec.whatwg.org/#cors-safelisted-method) The + CORS-safelisted methods are always allowed, regardless of whether they + are specified in the `AllowMethods` field. + + When the `AllowMethods` field is configured with one or more methods, the + gateway must return the `Access-Control-Allow-Methods` response header + which value is present in the `AllowMethods` field. + + If the HTTP method of the `Access-Control-Request-Method` request header + is not included in the list of methods specified by the response header + `Access-Control-Allow-Methods`, it will present an error on the client + side. + + The `Access-Control-Allow-Methods` response header can only use `*` + wildcard as value when the `AllowCredentials` field is unspecified. + + When the `AllowCredentials` field is specified and `AllowMethods` field + specified with the `*` wildcard, the gateway must specify one HTTP method + in the value of the Access-Control-Allow-Methods response header. The + value of the header `Access-Control-Allow-Methods` is same as the + `Access-Control-Request-Method` header provided by the client. If the + header `Access-Control-Request-Method` is not included in the request, + the gateway will omit the `Access-Control-Allow-Methods` response header, + instead of specifying the `*` wildcard. A Gateway implementation may + choose to add implementation-specific default methods. + + Support: Extended + items: + enum: + - GET + - HEAD + - POST + - PUT + - DELETE + - CONNECT + - OPTIONS + - TRACE + - PATCH + - '*' + type: string + maxItems: 9 + type: array + x-kubernetes-list-type: set + x-kubernetes-validations: + - message: AllowMethods cannot contain '*' alongside + other methods + rule: '!(''*'' in self && self.size() > 1)' + allowOrigins: + description: |- + AllowOrigins indicates whether the response can be shared with requested + resource from the given `Origin`. + + The `Origin` consists of a scheme and a host, with an optional port, and + takes the form `://(:)`. + + Valid values for scheme are: `http` and `https`. + + Valid values for port are any integer between 1 and 65535 (the list of + available TCP/UDP ports). Note that, if not included, port `80` is + assumed for `http` scheme origins, and port `443` is assumed for `https` + origins. This may affect origin matching. + + The host part of the origin may contain the wildcard character `*`. These + wildcard characters behave as follows: + + * `*` is a greedy match to the _left_, including any number of + DNS labels to the left of its position. This also means that + `*` will include any number of period `.` characters to the + left of its position. + * A wildcard by itself matches all hosts. + + An origin value that includes _only_ the `*` character indicates requests + from all `Origin`s are allowed. + + When the `AllowOrigins` field is configured with multiple origins, it + means the server supports clients from multiple origins. If the request + `Origin` matches the configured allowed origins, the gateway must return + the given `Origin` and sets value of the header + `Access-Control-Allow-Origin` same as the `Origin` header provided by the + client. + + The status code of a successful response to a "preflight" request is + always an OK status (i.e., 204 or 200). + + If the request `Origin` does not match the configured allowed origins, + the gateway returns 204/200 response but doesn't set the relevant + cross-origin response headers. Alternatively, the gateway responds with + 403 status to the "preflight" request is denied, coupled with omitting + the CORS headers. The cross-origin request fails on the client side. + Therefore, the client doesn't attempt the actual cross-origin request. + + The `Access-Control-Allow-Origin` response header can only use `*` + wildcard as value when the `AllowCredentials` field is unspecified. + + When the `AllowCredentials` field is specified and `AllowOrigins` field + specified with the `*` wildcard, the gateway must return a single origin + in the value of the `Access-Control-Allow-Origin` response header, + instead of specifying the `*` wildcard. The value of the header + `Access-Control-Allow-Origin` is same as the `Origin` header provided by + the client. + + Support: Extended + items: + description: |- + The AbsoluteURI MUST NOT be a relative URI, and it MUST follow the URI syntax and + encoding rules specified in RFC3986. The AbsoluteURI MUST include both a + scheme (e.g., "http" or "spiffe") and a scheme-specific-part. URIs that + include an authority MUST include a fully qualified domain name or + IP address as the host. + maxLength: 253 + minLength: 1 + pattern: ^(([^:/?#]+):)(//([^/?#]*))([^?#]*)(\?([^#]*))?(#(.*))? + type: string + maxItems: 64 + type: array + x-kubernetes-list-type: set + exposeHeaders: + description: |- + ExposeHeaders indicates which HTTP response headers can be exposed + to client-side scripts in response to a cross-origin request. + + A CORS-safelisted response header is an HTTP header in a CORS response + that it is considered safe to expose to the client scripts. + The CORS-safelisted response headers include the following headers: + `Cache-Control` + `Content-Language` + `Content-Length` + `Content-Type` + `Expires` + `Last-Modified` + `Pragma` + (See https://fetch.spec.whatwg.org/#cors-safelisted-response-header-name) + The CORS-safelisted response headers are exposed to client by default. + + When an HTTP header name is specified using the `ExposeHeaders` field, + this additional header will be exposed as part of the response to the + client. + + Header names are not case sensitive. + + Multiple header names in the value of the `Access-Control-Expose-Headers` + response header are separated by a comma (","). + + A wildcard indicates that the responses with all HTTP headers are exposed + to clients. The `Access-Control-Expose-Headers` response header can only + use `*` wildcard as value when the `AllowCredentials` field is + unspecified. + + Support: Extended + items: + description: |- + HTTPHeaderName is the name of an HTTP header. + + Valid values include: + + * "Authorization" + * "Set-Cookie" + + Invalid values include: + + - ":method" - ":" is an invalid character. This means that HTTP/2 pseudo + headers are not currently supported by this type. + - "/invalid" - "/ " is an invalid character + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + maxItems: 64 + type: array + x-kubernetes-list-type: set + maxAge: + default: 5 + description: |- + MaxAge indicates the duration (in seconds) for the client to cache the + results of a "preflight" request. + + The information provided by the `Access-Control-Allow-Methods` and + `Access-Control-Allow-Headers` response headers can be cached by the + client until the time specified by `Access-Control-Max-Age` elapses. + + The default value of `Access-Control-Max-Age` response header is 5 + (seconds). + format: int32 + minimum: 1 + type: integer + type: object extensionRef: description: |- ExtensionRef is an optional, implementation-specific extension to the @@ -6859,7 +6928,7 @@ spec: name: description: |- Name is the name of the HTTP Header to be matched. Name matching MUST be - case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries @@ -6934,7 +7003,7 @@ spec: name: description: |- Name is the name of the HTTP Header to be matched. Name matching MUST be - case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries @@ -6962,7 +7031,7 @@ spec: x-kubernetes-list-type: map type: object requestMirror: - description: |+ + description: |- RequestMirror defines a schema for a filter that mirrors requests. Requests are sent to the specified destination, but responses from that destination are ignored. @@ -6972,7 +7041,6 @@ spec: backends. Support: Extended - properties: backendRef: description: |- @@ -7068,13 +7136,12 @@ spec: rule: '(size(self.group) == 0 && self.kind == ''Service'') ? has(self.port) : true' fraction: - description: |+ + description: |- Fraction represents the fraction of requests that should be mirrored to BackendRef. Only one of Fraction or Percent may be specified. If neither field is specified, 100% of requests will be mirrored. - properties: denominator: default: 100 @@ -7093,14 +7160,13 @@ spec: to denominator rule: self.numerator <= self.denominator percent: - description: |+ + description: |- Percent represents the percentage of requests that should be mirrored to BackendRef. Its minimum value is 0 (indicating 0% of requests) and its maximum value is 100 (indicating 100% of requests). Only one of Fraction or Percent may be specified. If neither field is specified, 100% of requests will be mirrored. - format: int32 maximum: 100 minimum: 0 @@ -7298,7 +7364,7 @@ spec: name: description: |- Name is the name of the HTTP Header to be matched. Name matching MUST be - case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries @@ -7373,7 +7439,7 @@ spec: name: description: |- Name is the name of the HTTP Header to be matched. Name matching MUST be - case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries @@ -7441,6 +7507,7 @@ spec: - RequestRedirect - URLRewrite - ExtensionRef + - CORS type: string urlRewrite: description: |- @@ -7573,6 +7640,11 @@ spec: - message: filter.extensionRef must be specified for ExtensionRef filter.type rule: '!(!has(self.extensionRef) && self.type == ''ExtensionRef'')' + - message: filter.cors must be nil if the filter.type + is not CORS + rule: '!(has(self.cors) && self.type != ''CORS'')' + - message: filter.cors must be specified for CORS filter.type + rule: '!(!has(self.cors) && self.type == ''CORS'')' maxItems: 16 type: array x-kubernetes-validations: @@ -7694,7 +7766,7 @@ spec: they are specified. Implementations MAY choose to implement this ordering strictly, rejecting - any combination or order of filters that can not be supported. If implementations + any combination or order of filters that cannot be supported. If implementations choose a strict interpretation of filter ordering, they MUST clearly document that behavior. @@ -7716,7 +7788,7 @@ spec: All filters are expected to be compatible with each other except for the URLRewrite and RequestRedirect filters, which may not be combined. If an - implementation can not support other combinations of filters, they must clearly + implementation cannot support other combinations of filters, they must clearly document that limitation. In cases where incompatible or unsupported filters are specified and cause the `Accepted` condition to be set to status `False`, implementations may use the `IncompatibleFilters` reason to specify @@ -7732,6 +7804,289 @@ spec: authentication strategies, rate-limiting, and traffic shaping. API guarantee/conformance is defined based on the type of the filter. properties: + cors: + description: |- + CORS defines a schema for a filter that responds to the + cross-origin request based on HTTP response header. + + Support: Extended + properties: + allowCredentials: + description: |- + AllowCredentials indicates whether the actual cross-origin request allows + to include credentials. + + The only valid value for the `Access-Control-Allow-Credentials` response + header is true (case-sensitive). + + If the credentials are not allowed in cross-origin requests, the gateway + will omit the header `Access-Control-Allow-Credentials` entirely rather + than setting its value to false. + + Support: Extended + enum: + - true + type: boolean + allowHeaders: + description: |- + AllowHeaders indicates which HTTP request headers are supported for + accessing the requested resource. + + Header names are not case sensitive. + + Multiple header names in the value of the `Access-Control-Allow-Headers` + response header are separated by a comma (","). + + When the `AllowHeaders` field is configured with one or more headers, the + gateway must return the `Access-Control-Allow-Headers` response header + which value is present in the `AllowHeaders` field. + + If any header name in the `Access-Control-Request-Headers` request header + is not included in the list of header names specified by the response + header `Access-Control-Allow-Headers`, it will present an error on the + client side. + + If any header name in the `Access-Control-Allow-Headers` response header + does not recognize by the client, it will also occur an error on the + client side. + + A wildcard indicates that the requests with all HTTP headers are allowed. + The `Access-Control-Allow-Headers` response header can only use `*` + wildcard as value when the `AllowCredentials` field is unspecified. + + When the `AllowCredentials` field is specified and `AllowHeaders` field + specified with the `*` wildcard, the gateway must specify one or more + HTTP headers in the value of the `Access-Control-Allow-Headers` response + header. The value of the header `Access-Control-Allow-Headers` is same as + the `Access-Control-Request-Headers` header provided by the client. If + the header `Access-Control-Request-Headers` is not included in the + request, the gateway will omit the `Access-Control-Allow-Headers` + response header, instead of specifying the `*` wildcard. A Gateway + implementation may choose to add implementation-specific default headers. + + Support: Extended + items: + description: |- + HTTPHeaderName is the name of an HTTP header. + + Valid values include: + + * "Authorization" + * "Set-Cookie" + + Invalid values include: + + - ":method" - ":" is an invalid character. This means that HTTP/2 pseudo + headers are not currently supported by this type. + - "/invalid" - "/ " is an invalid character + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + maxItems: 64 + type: array + x-kubernetes-list-type: set + allowMethods: + description: |- + AllowMethods indicates which HTTP methods are supported for accessing the + requested resource. + + Valid values are any method defined by RFC9110, along with the special + value `*`, which represents all HTTP methods are allowed. + + Method names are case sensitive, so these values are also case-sensitive. + (See https://www.rfc-editor.org/rfc/rfc2616#section-5.1.1) + + Multiple method names in the value of the `Access-Control-Allow-Methods` + response header are separated by a comma (","). + + A CORS-safelisted method is a method that is `GET`, `HEAD`, or `POST`. + (See https://fetch.spec.whatwg.org/#cors-safelisted-method) The + CORS-safelisted methods are always allowed, regardless of whether they + are specified in the `AllowMethods` field. + + When the `AllowMethods` field is configured with one or more methods, the + gateway must return the `Access-Control-Allow-Methods` response header + which value is present in the `AllowMethods` field. + + If the HTTP method of the `Access-Control-Request-Method` request header + is not included in the list of methods specified by the response header + `Access-Control-Allow-Methods`, it will present an error on the client + side. + + The `Access-Control-Allow-Methods` response header can only use `*` + wildcard as value when the `AllowCredentials` field is unspecified. + + When the `AllowCredentials` field is specified and `AllowMethods` field + specified with the `*` wildcard, the gateway must specify one HTTP method + in the value of the Access-Control-Allow-Methods response header. The + value of the header `Access-Control-Allow-Methods` is same as the + `Access-Control-Request-Method` header provided by the client. If the + header `Access-Control-Request-Method` is not included in the request, + the gateway will omit the `Access-Control-Allow-Methods` response header, + instead of specifying the `*` wildcard. A Gateway implementation may + choose to add implementation-specific default methods. + + Support: Extended + items: + enum: + - GET + - HEAD + - POST + - PUT + - DELETE + - CONNECT + - OPTIONS + - TRACE + - PATCH + - '*' + type: string + maxItems: 9 + type: array + x-kubernetes-list-type: set + x-kubernetes-validations: + - message: AllowMethods cannot contain '*' alongside + other methods + rule: '!(''*'' in self && self.size() > 1)' + allowOrigins: + description: |- + AllowOrigins indicates whether the response can be shared with requested + resource from the given `Origin`. + + The `Origin` consists of a scheme and a host, with an optional port, and + takes the form `://(:)`. + + Valid values for scheme are: `http` and `https`. + + Valid values for port are any integer between 1 and 65535 (the list of + available TCP/UDP ports). Note that, if not included, port `80` is + assumed for `http` scheme origins, and port `443` is assumed for `https` + origins. This may affect origin matching. + + The host part of the origin may contain the wildcard character `*`. These + wildcard characters behave as follows: + + * `*` is a greedy match to the _left_, including any number of + DNS labels to the left of its position. This also means that + `*` will include any number of period `.` characters to the + left of its position. + * A wildcard by itself matches all hosts. + + An origin value that includes _only_ the `*` character indicates requests + from all `Origin`s are allowed. + + When the `AllowOrigins` field is configured with multiple origins, it + means the server supports clients from multiple origins. If the request + `Origin` matches the configured allowed origins, the gateway must return + the given `Origin` and sets value of the header + `Access-Control-Allow-Origin` same as the `Origin` header provided by the + client. + + The status code of a successful response to a "preflight" request is + always an OK status (i.e., 204 or 200). + + If the request `Origin` does not match the configured allowed origins, + the gateway returns 204/200 response but doesn't set the relevant + cross-origin response headers. Alternatively, the gateway responds with + 403 status to the "preflight" request is denied, coupled with omitting + the CORS headers. The cross-origin request fails on the client side. + Therefore, the client doesn't attempt the actual cross-origin request. + + The `Access-Control-Allow-Origin` response header can only use `*` + wildcard as value when the `AllowCredentials` field is unspecified. + + When the `AllowCredentials` field is specified and `AllowOrigins` field + specified with the `*` wildcard, the gateway must return a single origin + in the value of the `Access-Control-Allow-Origin` response header, + instead of specifying the `*` wildcard. The value of the header + `Access-Control-Allow-Origin` is same as the `Origin` header provided by + the client. + + Support: Extended + items: + description: |- + The AbsoluteURI MUST NOT be a relative URI, and it MUST follow the URI syntax and + encoding rules specified in RFC3986. The AbsoluteURI MUST include both a + scheme (e.g., "http" or "spiffe") and a scheme-specific-part. URIs that + include an authority MUST include a fully qualified domain name or + IP address as the host. + maxLength: 253 + minLength: 1 + pattern: ^(([^:/?#]+):)(//([^/?#]*))([^?#]*)(\?([^#]*))?(#(.*))? + type: string + maxItems: 64 + type: array + x-kubernetes-list-type: set + exposeHeaders: + description: |- + ExposeHeaders indicates which HTTP response headers can be exposed + to client-side scripts in response to a cross-origin request. + + A CORS-safelisted response header is an HTTP header in a CORS response + that it is considered safe to expose to the client scripts. + The CORS-safelisted response headers include the following headers: + `Cache-Control` + `Content-Language` + `Content-Length` + `Content-Type` + `Expires` + `Last-Modified` + `Pragma` + (See https://fetch.spec.whatwg.org/#cors-safelisted-response-header-name) + The CORS-safelisted response headers are exposed to client by default. + + When an HTTP header name is specified using the `ExposeHeaders` field, + this additional header will be exposed as part of the response to the + client. + + Header names are not case sensitive. + + Multiple header names in the value of the `Access-Control-Expose-Headers` + response header are separated by a comma (","). + + A wildcard indicates that the responses with all HTTP headers are exposed + to clients. The `Access-Control-Expose-Headers` response header can only + use `*` wildcard as value when the `AllowCredentials` field is + unspecified. + + Support: Extended + items: + description: |- + HTTPHeaderName is the name of an HTTP header. + + Valid values include: + + * "Authorization" + * "Set-Cookie" + + Invalid values include: + + - ":method" - ":" is an invalid character. This means that HTTP/2 pseudo + headers are not currently supported by this type. + - "/invalid" - "/ " is an invalid character + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + maxItems: 64 + type: array + x-kubernetes-list-type: set + maxAge: + default: 5 + description: |- + MaxAge indicates the duration (in seconds) for the client to cache the + results of a "preflight" request. + + The information provided by the `Access-Control-Allow-Methods` and + `Access-Control-Allow-Headers` response headers can be cached by the + client until the time specified by `Access-Control-Max-Age` elapses. + + The default value of `Access-Control-Max-Age` response header is 5 + (seconds). + format: int32 + minimum: 1 + type: integer + type: object extensionRef: description: |- ExtensionRef is an optional, implementation-specific extension to the @@ -7799,7 +8154,7 @@ spec: name: description: |- Name is the name of the HTTP Header to be matched. Name matching MUST be - case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries @@ -7873,7 +8228,7 @@ spec: name: description: |- Name is the name of the HTTP Header to be matched. Name matching MUST be - case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries @@ -7901,7 +8256,7 @@ spec: x-kubernetes-list-type: map type: object requestMirror: - description: |+ + description: |- RequestMirror defines a schema for a filter that mirrors requests. Requests are sent to the specified destination, but responses from that destination are ignored. @@ -7911,7 +8266,6 @@ spec: backends. Support: Extended - properties: backendRef: description: |- @@ -8007,13 +8361,12 @@ spec: rule: '(size(self.group) == 0 && self.kind == ''Service'') ? has(self.port) : true' fraction: - description: |+ + description: |- Fraction represents the fraction of requests that should be mirrored to BackendRef. Only one of Fraction or Percent may be specified. If neither field is specified, 100% of requests will be mirrored. - properties: denominator: default: 100 @@ -8032,14 +8385,13 @@ spec: denominator rule: self.numerator <= self.denominator percent: - description: |+ + description: |- Percent represents the percentage of requests that should be mirrored to BackendRef. Its minimum value is 0 (indicating 0% of requests) and its maximum value is 100 (indicating 100% of requests). Only one of Fraction or Percent may be specified. If neither field is specified, 100% of requests will be mirrored. - format: int32 maximum: 100 minimum: 0 @@ -8236,7 +8588,7 @@ spec: name: description: |- Name is the name of the HTTP Header to be matched. Name matching MUST be - case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries @@ -8310,7 +8662,7 @@ spec: name: description: |- Name is the name of the HTTP Header to be matched. Name matching MUST be - case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries @@ -8378,6 +8730,7 @@ spec: - RequestRedirect - URLRewrite - ExtensionRef + - CORS type: string urlRewrite: description: |- @@ -8507,6 +8860,11 @@ spec: - message: filter.extensionRef must be specified for ExtensionRef filter.type rule: '!(!has(self.extensionRef) && self.type == ''ExtensionRef'')' + - message: filter.cors must be nil if the filter.type is not + CORS + rule: '!(has(self.cors) && self.type != ''CORS'')' + - message: filter.cors must be specified for CORS filter.type + rule: '!(!has(self.cors) && self.type == ''CORS'')' maxItems: 16 type: array x-kubernetes-validations: @@ -8610,7 +8968,7 @@ spec: name: description: |- Name is the name of the HTTP Header to be matched. Name matching MUST be - case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). If multiple entries specify equivalent header names, only the first entry with an equivalent name MUST be considered for a match. Subsequent @@ -8821,7 +9179,7 @@ spec: maxItems: 64 type: array name: - description: | + description: |- Name is the name of the route rule. This name MUST be unique within a Route if it is set. Support: Extended @@ -8830,15 +9188,14 @@ spec: pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ type: string retry: - description: |+ + description: |- Retry defines the configuration for when to retry an HTTP request. Support: Extended - properties: attempts: description: |- - Attempts specifies the maxmimum number of times an individual request + Attempts specifies the maximum number of times an individual request from the gateway to a backend should be retried. If the maximum number of retries has been attempted without a successful @@ -8912,20 +9269,17 @@ spec: Implementations MAY support specifying discrete values in the 400-499 range, which are often inadvisable to retry. - - maximum: 599 minimum: 400 type: integer type: array type: object sessionPersistence: - description: |+ + description: |- SessionPersistence defines and configures session persistence for the route rule. Support: Extended - properties: absoluteTimeout: description: |- @@ -8960,6 +9314,8 @@ spec: absolute lifetime of the cookie tracked by the gateway and is optional. + Defaults to "Session". + Support: Core for "Session" type Support: Extended for "Permanent" type @@ -9173,7 +9529,7 @@ spec: There are a number of cases where the "Accepted" condition may not be set due to lack of controller visibility, that includes when: - * The Route refers to a non-existent parent. + * The Route refers to a nonexistent parent. * The Route is of a type that the controller does not support. * The Route is in a namespace the controller does not have access to. items: @@ -9523,7 +9879,7 @@ spec: maxItems: 16 type: array parentRefs: - description: |+ + description: |- ParentRefs references the resources (usually Gateways) that a Route wants to be attached to. Note that the referenced parent resource needs to allow this for the attachment to be complete. For Gateways, that means @@ -9585,11 +9941,6 @@ spec: connections originating from the same namespace as the Route, for which the intended destination of the connections are a Service targeted as a ParentRef of the Route. - - - - - items: description: |- ParentReference identifies an API object (usually a Gateway) that can be considered @@ -9768,9 +10119,7 @@ spec: - path: type: PathPrefix value: / - description: |+ - Rules are a list of HTTP matchers, filters and actions. - + description: Rules are a list of HTTP matchers, filters and actions. items: description: |- HTTPRouteRule defines semantics for matching an HTTP request based on @@ -9823,7 +10172,6 @@ spec: namespace's owner to accept the reference. See the ReferenceGrant documentation for details. - When the BackendRef points to a Kubernetes Service, implementations SHOULD honor the appProtocol field if it is set for the target Service Port. @@ -9838,8 +10186,6 @@ spec: If a Route is not able to send traffic to the backend using the specified protocol then the backend is considered invalid. Implementations MUST set the "ResolvedRefs" condition to "False" with the "UnsupportedProtocol" reason. - - properties: filters: description: |- @@ -9857,6 +10203,289 @@ spec: authentication strategies, rate-limiting, and traffic shaping. API guarantee/conformance is defined based on the type of the filter. properties: + cors: + description: |- + CORS defines a schema for a filter that responds to the + cross-origin request based on HTTP response header. + + Support: Extended + properties: + allowCredentials: + description: |- + AllowCredentials indicates whether the actual cross-origin request allows + to include credentials. + + The only valid value for the `Access-Control-Allow-Credentials` response + header is true (case-sensitive). + + If the credentials are not allowed in cross-origin requests, the gateway + will omit the header `Access-Control-Allow-Credentials` entirely rather + than setting its value to false. + + Support: Extended + enum: + - true + type: boolean + allowHeaders: + description: |- + AllowHeaders indicates which HTTP request headers are supported for + accessing the requested resource. + + Header names are not case sensitive. + + Multiple header names in the value of the `Access-Control-Allow-Headers` + response header are separated by a comma (","). + + When the `AllowHeaders` field is configured with one or more headers, the + gateway must return the `Access-Control-Allow-Headers` response header + which value is present in the `AllowHeaders` field. + + If any header name in the `Access-Control-Request-Headers` request header + is not included in the list of header names specified by the response + header `Access-Control-Allow-Headers`, it will present an error on the + client side. + + If any header name in the `Access-Control-Allow-Headers` response header + does not recognize by the client, it will also occur an error on the + client side. + + A wildcard indicates that the requests with all HTTP headers are allowed. + The `Access-Control-Allow-Headers` response header can only use `*` + wildcard as value when the `AllowCredentials` field is unspecified. + + When the `AllowCredentials` field is specified and `AllowHeaders` field + specified with the `*` wildcard, the gateway must specify one or more + HTTP headers in the value of the `Access-Control-Allow-Headers` response + header. The value of the header `Access-Control-Allow-Headers` is same as + the `Access-Control-Request-Headers` header provided by the client. If + the header `Access-Control-Request-Headers` is not included in the + request, the gateway will omit the `Access-Control-Allow-Headers` + response header, instead of specifying the `*` wildcard. A Gateway + implementation may choose to add implementation-specific default headers. + + Support: Extended + items: + description: |- + HTTPHeaderName is the name of an HTTP header. + + Valid values include: + + * "Authorization" + * "Set-Cookie" + + Invalid values include: + + - ":method" - ":" is an invalid character. This means that HTTP/2 pseudo + headers are not currently supported by this type. + - "/invalid" - "/ " is an invalid character + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + maxItems: 64 + type: array + x-kubernetes-list-type: set + allowMethods: + description: |- + AllowMethods indicates which HTTP methods are supported for accessing the + requested resource. + + Valid values are any method defined by RFC9110, along with the special + value `*`, which represents all HTTP methods are allowed. + + Method names are case sensitive, so these values are also case-sensitive. + (See https://www.rfc-editor.org/rfc/rfc2616#section-5.1.1) + + Multiple method names in the value of the `Access-Control-Allow-Methods` + response header are separated by a comma (","). + + A CORS-safelisted method is a method that is `GET`, `HEAD`, or `POST`. + (See https://fetch.spec.whatwg.org/#cors-safelisted-method) The + CORS-safelisted methods are always allowed, regardless of whether they + are specified in the `AllowMethods` field. + + When the `AllowMethods` field is configured with one or more methods, the + gateway must return the `Access-Control-Allow-Methods` response header + which value is present in the `AllowMethods` field. + + If the HTTP method of the `Access-Control-Request-Method` request header + is not included in the list of methods specified by the response header + `Access-Control-Allow-Methods`, it will present an error on the client + side. + + The `Access-Control-Allow-Methods` response header can only use `*` + wildcard as value when the `AllowCredentials` field is unspecified. + + When the `AllowCredentials` field is specified and `AllowMethods` field + specified with the `*` wildcard, the gateway must specify one HTTP method + in the value of the Access-Control-Allow-Methods response header. The + value of the header `Access-Control-Allow-Methods` is same as the + `Access-Control-Request-Method` header provided by the client. If the + header `Access-Control-Request-Method` is not included in the request, + the gateway will omit the `Access-Control-Allow-Methods` response header, + instead of specifying the `*` wildcard. A Gateway implementation may + choose to add implementation-specific default methods. + + Support: Extended + items: + enum: + - GET + - HEAD + - POST + - PUT + - DELETE + - CONNECT + - OPTIONS + - TRACE + - PATCH + - '*' + type: string + maxItems: 9 + type: array + x-kubernetes-list-type: set + x-kubernetes-validations: + - message: AllowMethods cannot contain '*' alongside + other methods + rule: '!(''*'' in self && self.size() > 1)' + allowOrigins: + description: |- + AllowOrigins indicates whether the response can be shared with requested + resource from the given `Origin`. + + The `Origin` consists of a scheme and a host, with an optional port, and + takes the form `://(:)`. + + Valid values for scheme are: `http` and `https`. + + Valid values for port are any integer between 1 and 65535 (the list of + available TCP/UDP ports). Note that, if not included, port `80` is + assumed for `http` scheme origins, and port `443` is assumed for `https` + origins. This may affect origin matching. + + The host part of the origin may contain the wildcard character `*`. These + wildcard characters behave as follows: + + * `*` is a greedy match to the _left_, including any number of + DNS labels to the left of its position. This also means that + `*` will include any number of period `.` characters to the + left of its position. + * A wildcard by itself matches all hosts. + + An origin value that includes _only_ the `*` character indicates requests + from all `Origin`s are allowed. + + When the `AllowOrigins` field is configured with multiple origins, it + means the server supports clients from multiple origins. If the request + `Origin` matches the configured allowed origins, the gateway must return + the given `Origin` and sets value of the header + `Access-Control-Allow-Origin` same as the `Origin` header provided by the + client. + + The status code of a successful response to a "preflight" request is + always an OK status (i.e., 204 or 200). + + If the request `Origin` does not match the configured allowed origins, + the gateway returns 204/200 response but doesn't set the relevant + cross-origin response headers. Alternatively, the gateway responds with + 403 status to the "preflight" request is denied, coupled with omitting + the CORS headers. The cross-origin request fails on the client side. + Therefore, the client doesn't attempt the actual cross-origin request. + + The `Access-Control-Allow-Origin` response header can only use `*` + wildcard as value when the `AllowCredentials` field is unspecified. + + When the `AllowCredentials` field is specified and `AllowOrigins` field + specified with the `*` wildcard, the gateway must return a single origin + in the value of the `Access-Control-Allow-Origin` response header, + instead of specifying the `*` wildcard. The value of the header + `Access-Control-Allow-Origin` is same as the `Origin` header provided by + the client. + + Support: Extended + items: + description: |- + The AbsoluteURI MUST NOT be a relative URI, and it MUST follow the URI syntax and + encoding rules specified in RFC3986. The AbsoluteURI MUST include both a + scheme (e.g., "http" or "spiffe") and a scheme-specific-part. URIs that + include an authority MUST include a fully qualified domain name or + IP address as the host. + maxLength: 253 + minLength: 1 + pattern: ^(([^:/?#]+):)(//([^/?#]*))([^?#]*)(\?([^#]*))?(#(.*))? + type: string + maxItems: 64 + type: array + x-kubernetes-list-type: set + exposeHeaders: + description: |- + ExposeHeaders indicates which HTTP response headers can be exposed + to client-side scripts in response to a cross-origin request. + + A CORS-safelisted response header is an HTTP header in a CORS response + that it is considered safe to expose to the client scripts. + The CORS-safelisted response headers include the following headers: + `Cache-Control` + `Content-Language` + `Content-Length` + `Content-Type` + `Expires` + `Last-Modified` + `Pragma` + (See https://fetch.spec.whatwg.org/#cors-safelisted-response-header-name) + The CORS-safelisted response headers are exposed to client by default. + + When an HTTP header name is specified using the `ExposeHeaders` field, + this additional header will be exposed as part of the response to the + client. + + Header names are not case sensitive. + + Multiple header names in the value of the `Access-Control-Expose-Headers` + response header are separated by a comma (","). + + A wildcard indicates that the responses with all HTTP headers are exposed + to clients. The `Access-Control-Expose-Headers` response header can only + use `*` wildcard as value when the `AllowCredentials` field is + unspecified. + + Support: Extended + items: + description: |- + HTTPHeaderName is the name of an HTTP header. + + Valid values include: + + * "Authorization" + * "Set-Cookie" + + Invalid values include: + + - ":method" - ":" is an invalid character. This means that HTTP/2 pseudo + headers are not currently supported by this type. + - "/invalid" - "/ " is an invalid character + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + maxItems: 64 + type: array + x-kubernetes-list-type: set + maxAge: + default: 5 + description: |- + MaxAge indicates the duration (in seconds) for the client to cache the + results of a "preflight" request. + + The information provided by the `Access-Control-Allow-Methods` and + `Access-Control-Allow-Headers` response headers can be cached by the + client until the time specified by `Access-Control-Max-Age` elapses. + + The default value of `Access-Control-Max-Age` response header is 5 + (seconds). + format: int32 + minimum: 1 + type: integer + type: object extensionRef: description: |- ExtensionRef is an optional, implementation-specific extension to the @@ -9925,7 +10554,7 @@ spec: name: description: |- Name is the name of the HTTP Header to be matched. Name matching MUST be - case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries @@ -10000,7 +10629,7 @@ spec: name: description: |- Name is the name of the HTTP Header to be matched. Name matching MUST be - case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries @@ -10028,7 +10657,7 @@ spec: x-kubernetes-list-type: map type: object requestMirror: - description: |+ + description: |- RequestMirror defines a schema for a filter that mirrors requests. Requests are sent to the specified destination, but responses from that destination are ignored. @@ -10038,7 +10667,6 @@ spec: backends. Support: Extended - properties: backendRef: description: |- @@ -10134,13 +10762,12 @@ spec: rule: '(size(self.group) == 0 && self.kind == ''Service'') ? has(self.port) : true' fraction: - description: |+ + description: |- Fraction represents the fraction of requests that should be mirrored to BackendRef. Only one of Fraction or Percent may be specified. If neither field is specified, 100% of requests will be mirrored. - properties: denominator: default: 100 @@ -10159,14 +10786,13 @@ spec: to denominator rule: self.numerator <= self.denominator percent: - description: |+ + description: |- Percent represents the percentage of requests that should be mirrored to BackendRef. Its minimum value is 0 (indicating 0% of requests) and its maximum value is 100 (indicating 100% of requests). Only one of Fraction or Percent may be specified. If neither field is specified, 100% of requests will be mirrored. - format: int32 maximum: 100 minimum: 0 @@ -10364,7 +10990,7 @@ spec: name: description: |- Name is the name of the HTTP Header to be matched. Name matching MUST be - case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries @@ -10439,7 +11065,7 @@ spec: name: description: |- Name is the name of the HTTP Header to be matched. Name matching MUST be - case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries @@ -10507,6 +11133,7 @@ spec: - RequestRedirect - URLRewrite - ExtensionRef + - CORS type: string urlRewrite: description: |- @@ -10639,6 +11266,11 @@ spec: - message: filter.extensionRef must be specified for ExtensionRef filter.type rule: '!(!has(self.extensionRef) && self.type == ''ExtensionRef'')' + - message: filter.cors must be nil if the filter.type + is not CORS + rule: '!(has(self.cors) && self.type != ''CORS'')' + - message: filter.cors must be specified for CORS filter.type + rule: '!(!has(self.cors) && self.type == ''CORS'')' maxItems: 16 type: array x-kubernetes-validations: @@ -10760,7 +11392,7 @@ spec: they are specified. Implementations MAY choose to implement this ordering strictly, rejecting - any combination or order of filters that can not be supported. If implementations + any combination or order of filters that cannot be supported. If implementations choose a strict interpretation of filter ordering, they MUST clearly document that behavior. @@ -10782,7 +11414,7 @@ spec: All filters are expected to be compatible with each other except for the URLRewrite and RequestRedirect filters, which may not be combined. If an - implementation can not support other combinations of filters, they must clearly + implementation cannot support other combinations of filters, they must clearly document that limitation. In cases where incompatible or unsupported filters are specified and cause the `Accepted` condition to be set to status `False`, implementations may use the `IncompatibleFilters` reason to specify @@ -10798,6 +11430,289 @@ spec: authentication strategies, rate-limiting, and traffic shaping. API guarantee/conformance is defined based on the type of the filter. properties: + cors: + description: |- + CORS defines a schema for a filter that responds to the + cross-origin request based on HTTP response header. + + Support: Extended + properties: + allowCredentials: + description: |- + AllowCredentials indicates whether the actual cross-origin request allows + to include credentials. + + The only valid value for the `Access-Control-Allow-Credentials` response + header is true (case-sensitive). + + If the credentials are not allowed in cross-origin requests, the gateway + will omit the header `Access-Control-Allow-Credentials` entirely rather + than setting its value to false. + + Support: Extended + enum: + - true + type: boolean + allowHeaders: + description: |- + AllowHeaders indicates which HTTP request headers are supported for + accessing the requested resource. + + Header names are not case sensitive. + + Multiple header names in the value of the `Access-Control-Allow-Headers` + response header are separated by a comma (","). + + When the `AllowHeaders` field is configured with one or more headers, the + gateway must return the `Access-Control-Allow-Headers` response header + which value is present in the `AllowHeaders` field. + + If any header name in the `Access-Control-Request-Headers` request header + is not included in the list of header names specified by the response + header `Access-Control-Allow-Headers`, it will present an error on the + client side. + + If any header name in the `Access-Control-Allow-Headers` response header + does not recognize by the client, it will also occur an error on the + client side. + + A wildcard indicates that the requests with all HTTP headers are allowed. + The `Access-Control-Allow-Headers` response header can only use `*` + wildcard as value when the `AllowCredentials` field is unspecified. + + When the `AllowCredentials` field is specified and `AllowHeaders` field + specified with the `*` wildcard, the gateway must specify one or more + HTTP headers in the value of the `Access-Control-Allow-Headers` response + header. The value of the header `Access-Control-Allow-Headers` is same as + the `Access-Control-Request-Headers` header provided by the client. If + the header `Access-Control-Request-Headers` is not included in the + request, the gateway will omit the `Access-Control-Allow-Headers` + response header, instead of specifying the `*` wildcard. A Gateway + implementation may choose to add implementation-specific default headers. + + Support: Extended + items: + description: |- + HTTPHeaderName is the name of an HTTP header. + + Valid values include: + + * "Authorization" + * "Set-Cookie" + + Invalid values include: + + - ":method" - ":" is an invalid character. This means that HTTP/2 pseudo + headers are not currently supported by this type. + - "/invalid" - "/ " is an invalid character + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + maxItems: 64 + type: array + x-kubernetes-list-type: set + allowMethods: + description: |- + AllowMethods indicates which HTTP methods are supported for accessing the + requested resource. + + Valid values are any method defined by RFC9110, along with the special + value `*`, which represents all HTTP methods are allowed. + + Method names are case sensitive, so these values are also case-sensitive. + (See https://www.rfc-editor.org/rfc/rfc2616#section-5.1.1) + + Multiple method names in the value of the `Access-Control-Allow-Methods` + response header are separated by a comma (","). + + A CORS-safelisted method is a method that is `GET`, `HEAD`, or `POST`. + (See https://fetch.spec.whatwg.org/#cors-safelisted-method) The + CORS-safelisted methods are always allowed, regardless of whether they + are specified in the `AllowMethods` field. + + When the `AllowMethods` field is configured with one or more methods, the + gateway must return the `Access-Control-Allow-Methods` response header + which value is present in the `AllowMethods` field. + + If the HTTP method of the `Access-Control-Request-Method` request header + is not included in the list of methods specified by the response header + `Access-Control-Allow-Methods`, it will present an error on the client + side. + + The `Access-Control-Allow-Methods` response header can only use `*` + wildcard as value when the `AllowCredentials` field is unspecified. + + When the `AllowCredentials` field is specified and `AllowMethods` field + specified with the `*` wildcard, the gateway must specify one HTTP method + in the value of the Access-Control-Allow-Methods response header. The + value of the header `Access-Control-Allow-Methods` is same as the + `Access-Control-Request-Method` header provided by the client. If the + header `Access-Control-Request-Method` is not included in the request, + the gateway will omit the `Access-Control-Allow-Methods` response header, + instead of specifying the `*` wildcard. A Gateway implementation may + choose to add implementation-specific default methods. + + Support: Extended + items: + enum: + - GET + - HEAD + - POST + - PUT + - DELETE + - CONNECT + - OPTIONS + - TRACE + - PATCH + - '*' + type: string + maxItems: 9 + type: array + x-kubernetes-list-type: set + x-kubernetes-validations: + - message: AllowMethods cannot contain '*' alongside + other methods + rule: '!(''*'' in self && self.size() > 1)' + allowOrigins: + description: |- + AllowOrigins indicates whether the response can be shared with requested + resource from the given `Origin`. + + The `Origin` consists of a scheme and a host, with an optional port, and + takes the form `://(:)`. + + Valid values for scheme are: `http` and `https`. + + Valid values for port are any integer between 1 and 65535 (the list of + available TCP/UDP ports). Note that, if not included, port `80` is + assumed for `http` scheme origins, and port `443` is assumed for `https` + origins. This may affect origin matching. + + The host part of the origin may contain the wildcard character `*`. These + wildcard characters behave as follows: + + * `*` is a greedy match to the _left_, including any number of + DNS labels to the left of its position. This also means that + `*` will include any number of period `.` characters to the + left of its position. + * A wildcard by itself matches all hosts. + + An origin value that includes _only_ the `*` character indicates requests + from all `Origin`s are allowed. + + When the `AllowOrigins` field is configured with multiple origins, it + means the server supports clients from multiple origins. If the request + `Origin` matches the configured allowed origins, the gateway must return + the given `Origin` and sets value of the header + `Access-Control-Allow-Origin` same as the `Origin` header provided by the + client. + + The status code of a successful response to a "preflight" request is + always an OK status (i.e., 204 or 200). + + If the request `Origin` does not match the configured allowed origins, + the gateway returns 204/200 response but doesn't set the relevant + cross-origin response headers. Alternatively, the gateway responds with + 403 status to the "preflight" request is denied, coupled with omitting + the CORS headers. The cross-origin request fails on the client side. + Therefore, the client doesn't attempt the actual cross-origin request. + + The `Access-Control-Allow-Origin` response header can only use `*` + wildcard as value when the `AllowCredentials` field is unspecified. + + When the `AllowCredentials` field is specified and `AllowOrigins` field + specified with the `*` wildcard, the gateway must return a single origin + in the value of the `Access-Control-Allow-Origin` response header, + instead of specifying the `*` wildcard. The value of the header + `Access-Control-Allow-Origin` is same as the `Origin` header provided by + the client. + + Support: Extended + items: + description: |- + The AbsoluteURI MUST NOT be a relative URI, and it MUST follow the URI syntax and + encoding rules specified in RFC3986. The AbsoluteURI MUST include both a + scheme (e.g., "http" or "spiffe") and a scheme-specific-part. URIs that + include an authority MUST include a fully qualified domain name or + IP address as the host. + maxLength: 253 + minLength: 1 + pattern: ^(([^:/?#]+):)(//([^/?#]*))([^?#]*)(\?([^#]*))?(#(.*))? + type: string + maxItems: 64 + type: array + x-kubernetes-list-type: set + exposeHeaders: + description: |- + ExposeHeaders indicates which HTTP response headers can be exposed + to client-side scripts in response to a cross-origin request. + + A CORS-safelisted response header is an HTTP header in a CORS response + that it is considered safe to expose to the client scripts. + The CORS-safelisted response headers include the following headers: + `Cache-Control` + `Content-Language` + `Content-Length` + `Content-Type` + `Expires` + `Last-Modified` + `Pragma` + (See https://fetch.spec.whatwg.org/#cors-safelisted-response-header-name) + The CORS-safelisted response headers are exposed to client by default. + + When an HTTP header name is specified using the `ExposeHeaders` field, + this additional header will be exposed as part of the response to the + client. + + Header names are not case sensitive. + + Multiple header names in the value of the `Access-Control-Expose-Headers` + response header are separated by a comma (","). + + A wildcard indicates that the responses with all HTTP headers are exposed + to clients. The `Access-Control-Expose-Headers` response header can only + use `*` wildcard as value when the `AllowCredentials` field is + unspecified. + + Support: Extended + items: + description: |- + HTTPHeaderName is the name of an HTTP header. + + Valid values include: + + * "Authorization" + * "Set-Cookie" + + Invalid values include: + + - ":method" - ":" is an invalid character. This means that HTTP/2 pseudo + headers are not currently supported by this type. + - "/invalid" - "/ " is an invalid character + maxLength: 256 + minLength: 1 + pattern: ^[A-Za-z0-9!#$%&'*+\-.^_\x60|~]+$ + type: string + maxItems: 64 + type: array + x-kubernetes-list-type: set + maxAge: + default: 5 + description: |- + MaxAge indicates the duration (in seconds) for the client to cache the + results of a "preflight" request. + + The information provided by the `Access-Control-Allow-Methods` and + `Access-Control-Allow-Headers` response headers can be cached by the + client until the time specified by `Access-Control-Max-Age` elapses. + + The default value of `Access-Control-Max-Age` response header is 5 + (seconds). + format: int32 + minimum: 1 + type: integer + type: object extensionRef: description: |- ExtensionRef is an optional, implementation-specific extension to the @@ -10865,7 +11780,7 @@ spec: name: description: |- Name is the name of the HTTP Header to be matched. Name matching MUST be - case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries @@ -10939,7 +11854,7 @@ spec: name: description: |- Name is the name of the HTTP Header to be matched. Name matching MUST be - case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries @@ -10967,7 +11882,7 @@ spec: x-kubernetes-list-type: map type: object requestMirror: - description: |+ + description: |- RequestMirror defines a schema for a filter that mirrors requests. Requests are sent to the specified destination, but responses from that destination are ignored. @@ -10977,7 +11892,6 @@ spec: backends. Support: Extended - properties: backendRef: description: |- @@ -11073,13 +11987,12 @@ spec: rule: '(size(self.group) == 0 && self.kind == ''Service'') ? has(self.port) : true' fraction: - description: |+ + description: |- Fraction represents the fraction of requests that should be mirrored to BackendRef. Only one of Fraction or Percent may be specified. If neither field is specified, 100% of requests will be mirrored. - properties: denominator: default: 100 @@ -11098,14 +12011,13 @@ spec: denominator rule: self.numerator <= self.denominator percent: - description: |+ + description: |- Percent represents the percentage of requests that should be mirrored to BackendRef. Its minimum value is 0 (indicating 0% of requests) and its maximum value is 100 (indicating 100% of requests). Only one of Fraction or Percent may be specified. If neither field is specified, 100% of requests will be mirrored. - format: int32 maximum: 100 minimum: 0 @@ -11302,7 +12214,7 @@ spec: name: description: |- Name is the name of the HTTP Header to be matched. Name matching MUST be - case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries @@ -11376,7 +12288,7 @@ spec: name: description: |- Name is the name of the HTTP Header to be matched. Name matching MUST be - case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). If multiple entries specify equivalent header names, the first entry with an equivalent name MUST be considered for a match. Subsequent entries @@ -11444,6 +12356,7 @@ spec: - RequestRedirect - URLRewrite - ExtensionRef + - CORS type: string urlRewrite: description: |- @@ -11573,6 +12486,11 @@ spec: - message: filter.extensionRef must be specified for ExtensionRef filter.type rule: '!(!has(self.extensionRef) && self.type == ''ExtensionRef'')' + - message: filter.cors must be nil if the filter.type is not + CORS + rule: '!(has(self.cors) && self.type != ''CORS'')' + - message: filter.cors must be specified for CORS filter.type + rule: '!(!has(self.cors) && self.type == ''CORS'')' maxItems: 16 type: array x-kubernetes-validations: @@ -11676,7 +12594,7 @@ spec: name: description: |- Name is the name of the HTTP Header to be matched. Name matching MUST be - case insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). + case-insensitive. (See https://tools.ietf.org/html/rfc7230#section-3.2). If multiple entries specify equivalent header names, only the first entry with an equivalent name MUST be considered for a match. Subsequent @@ -11887,7 +12805,7 @@ spec: maxItems: 64 type: array name: - description: | + description: |- Name is the name of the route rule. This name MUST be unique within a Route if it is set. Support: Extended @@ -11896,15 +12814,14 @@ spec: pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ type: string retry: - description: |+ + description: |- Retry defines the configuration for when to retry an HTTP request. Support: Extended - properties: attempts: description: |- - Attempts specifies the maxmimum number of times an individual request + Attempts specifies the maximum number of times an individual request from the gateway to a backend should be retried. If the maximum number of retries has been attempted without a successful @@ -11978,20 +12895,17 @@ spec: Implementations MAY support specifying discrete values in the 400-499 range, which are often inadvisable to retry. - - maximum: 599 minimum: 400 type: integer type: array type: object sessionPersistence: - description: |+ + description: |- SessionPersistence defines and configures session persistence for the route rule. Support: Extended - properties: absoluteTimeout: description: |- @@ -12026,6 +12940,8 @@ spec: absolute lifetime of the cookie tracked by the gateway and is optional. + Defaults to "Session". + Support: Core for "Session" type Support: Extended for "Permanent" type @@ -12239,7 +13155,7 @@ spec: There are a number of cases where the "Accepted" condition may not be set due to lack of controller visibility, that includes when: - * The Route refers to a non-existent parent. + * The Route refers to a nonexistent parent. * The Route is of a type that the controller does not support. * The Route is in a namespace the controller does not have access to. items: @@ -12490,7 +13406,7 @@ kind: CustomResourceDefinition metadata: annotations: api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/3328 - gateway.networking.k8s.io/bundle-version: v1.2.1 + gateway.networking.k8s.io/bundle-version: v1.3.0 gateway.networking.k8s.io/channel: experimental creationTimestamp: null name: referencegrants.gateway.networking.k8s.io @@ -12683,7 +13599,7 @@ kind: CustomResourceDefinition metadata: annotations: api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/3328 - gateway.networking.k8s.io/bundle-version: v1.2.1 + gateway.networking.k8s.io/bundle-version: v1.3.0 gateway.networking.k8s.io/channel: experimental creationTimestamp: null name: tcproutes.gateway.networking.k8s.io @@ -12731,7 +13647,7 @@ spec: description: Spec defines the desired state of TCPRoute. properties: parentRefs: - description: |+ + description: |- ParentRefs references the resources (usually Gateways) that a Route wants to be attached to. Note that the referenced parent resource needs to allow this for the attachment to be complete. For Gateways, that means @@ -12793,11 +13709,6 @@ spec: connections originating from the same namespace as the Route, for which the intended destination of the connections are a Service targeted as a ParentRef of the Route. - - - - - items: description: |- ParentReference identifies an API object (usually a Gateway) that can be considered @@ -12971,16 +13882,14 @@ spec: || p2.port == 0)) || (has(p1.port) && has(p2.port) && p1.port == p2.port)))) rules: - description: |+ - Rules are a list of TCP matchers and actions. - + description: Rules are a list of TCP matchers and actions. items: description: TCPRouteRule is the configuration for a given rule. properties: backendRefs: description: |- BackendRefs defines the backend(s) where matching requests should be - sent. If unspecified or invalid (refers to a non-existent resource or a + sent. If unspecified or invalid (refers to a nonexistent resource or a Service with no endpoints), the underlying implementation MUST actively reject connection attempts to this backend. Connection rejections must respect weight; if an invalid backend is requested to have 80% of @@ -13003,7 +13912,6 @@ spec: namespace's owner to accept the reference. See the ReferenceGrant documentation for details. - When the BackendRef points to a Kubernetes Service, implementations SHOULD honor the appProtocol field if it is set for the target Service Port. @@ -13019,7 +13927,6 @@ spec: protocol then the backend is considered invalid. Implementations MUST set the "ResolvedRefs" condition to "False" with the "UnsupportedProtocol" reason. - Note that when the BackendTLSPolicy object is enabled by the implementation, there are some extra rules about validity to consider here. See the fields @@ -13177,7 +14084,7 @@ spec: There are a number of cases where the "Accepted" condition may not be set due to lack of controller visibility, that includes when: - * The Route refers to a non-existent parent. + * The Route refers to a nonexistent parent. * The Route is of a type that the controller does not support. * The Route is in a namespace the controller does not have access to. items: @@ -13428,7 +14335,7 @@ kind: CustomResourceDefinition metadata: annotations: api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/3328 - gateway.networking.k8s.io/bundle-version: v1.2.1 + gateway.networking.k8s.io/bundle-version: v1.3.0 gateway.networking.k8s.io/channel: experimental creationTimestamp: null name: tlsroutes.gateway.networking.k8s.io @@ -13536,7 +14443,7 @@ spec: maxItems: 16 type: array parentRefs: - description: |+ + description: |- ParentRefs references the resources (usually Gateways) that a Route wants to be attached to. Note that the referenced parent resource needs to allow this for the attachment to be complete. For Gateways, that means @@ -13598,11 +14505,6 @@ spec: connections originating from the same namespace as the Route, for which the intended destination of the connections are a Service targeted as a ParentRef of the Route. - - - - - items: description: |- ParentReference identifies an API object (usually a Gateway) that can be considered @@ -13776,16 +14678,14 @@ spec: || p2.port == 0)) || (has(p1.port) && has(p2.port) && p1.port == p2.port)))) rules: - description: |+ - Rules are a list of TLS matchers and actions. - + description: Rules are a list of TLS matchers and actions. items: description: TLSRouteRule is the configuration for a given rule. properties: backendRefs: description: |- BackendRefs defines the backend(s) where matching requests should be - sent. If unspecified or invalid (refers to a non-existent resource or + sent. If unspecified or invalid (refers to a nonexistent resource or a Service with no endpoints), the rule performs no forwarding; if no filters are specified that would result in a response being sent, the underlying implementation must actively reject request attempts to this @@ -13811,7 +14711,6 @@ spec: namespace's owner to accept the reference. See the ReferenceGrant documentation for details. - When the BackendRef points to a Kubernetes Service, implementations SHOULD honor the appProtocol field if it is set for the target Service Port. @@ -13827,7 +14726,6 @@ spec: protocol then the backend is considered invalid. Implementations MUST set the "ResolvedRefs" condition to "False" with the "UnsupportedProtocol" reason. - Note that when the BackendTLSPolicy object is enabled by the implementation, there are some extra rules about validity to consider here. See the fields @@ -13985,7 +14883,7 @@ spec: There are a number of cases where the "Accepted" condition may not be set due to lack of controller visibility, that includes when: - * The Route refers to a non-existent parent. + * The Route refers to a nonexistent parent. * The Route is of a type that the controller does not support. * The Route is in a namespace the controller does not have access to. items: @@ -14236,7 +15134,7 @@ kind: CustomResourceDefinition metadata: annotations: api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/3328 - gateway.networking.k8s.io/bundle-version: v1.2.1 + gateway.networking.k8s.io/bundle-version: v1.3.0 gateway.networking.k8s.io/channel: experimental creationTimestamp: null name: udproutes.gateway.networking.k8s.io @@ -14284,7 +15182,7 @@ spec: description: Spec defines the desired state of UDPRoute. properties: parentRefs: - description: |+ + description: |- ParentRefs references the resources (usually Gateways) that a Route wants to be attached to. Note that the referenced parent resource needs to allow this for the attachment to be complete. For Gateways, that means @@ -14346,11 +15244,6 @@ spec: connections originating from the same namespace as the Route, for which the intended destination of the connections are a Service targeted as a ParentRef of the Route. - - - - - items: description: |- ParentReference identifies an API object (usually a Gateway) that can be considered @@ -14524,16 +15417,14 @@ spec: || p2.port == 0)) || (has(p1.port) && has(p2.port) && p1.port == p2.port)))) rules: - description: |+ - Rules are a list of UDP matchers and actions. - + description: Rules are a list of UDP matchers and actions. items: description: UDPRouteRule is the configuration for a given rule. properties: backendRefs: description: |- BackendRefs defines the backend(s) where matching requests should be - sent. If unspecified or invalid (refers to a non-existent resource or a + sent. If unspecified or invalid (refers to a nonexistent resource or a Service with no endpoints), the underlying implementation MUST actively reject connection attempts to this backend. Packet drops must respect weight; if an invalid backend is requested to have 80% of @@ -14556,7 +15447,6 @@ spec: namespace's owner to accept the reference. See the ReferenceGrant documentation for details. - When the BackendRef points to a Kubernetes Service, implementations SHOULD honor the appProtocol field if it is set for the target Service Port. @@ -14572,7 +15462,6 @@ spec: protocol then the backend is considered invalid. Implementations MUST set the "ResolvedRefs" condition to "False" with the "UnsupportedProtocol" reason. - Note that when the BackendTLSPolicy object is enabled by the implementation, there are some extra rules about validity to consider here. See the fields @@ -14730,7 +15619,7 @@ spec: There are a number of cases where the "Accepted" condition may not be set due to lack of controller visibility, that includes when: - * The Route refers to a non-existent parent. + * The Route refers to a nonexistent parent. * The Route is of a type that the controller does not support. * The Route is in a namespace the controller does not have access to. items: @@ -14972,3 +15861,1458 @@ status: plural: "" conditions: null storedVersions: null +--- +# +# config/crd/experimental/gateway.networking.x-k8s.io_xbackendtrafficpolicies.yaml +# +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/3328 + gateway.networking.k8s.io/bundle-version: v1.3.0 + gateway.networking.k8s.io/channel: experimental + creationTimestamp: null + labels: + gateway.networking.k8s.io/policy: Direct + name: xbackendtrafficpolicies.gateway.networking.x-k8s.io +spec: + group: gateway.networking.x-k8s.io + names: + categories: + - gateway-api + kind: XBackendTrafficPolicy + listKind: XBackendTrafficPolicyList + plural: xbackendtrafficpolicies + shortNames: + - xbtrafficpolicy + singular: xbackendtrafficpolicy + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha1 + schema: + openAPIV3Schema: + description: |- + XBackendTrafficPolicy defines the configuration for how traffic to a + target backend should be handled. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of BackendTrafficPolicy. + properties: + retryConstraint: + description: |- + RetryConstraint defines the configuration for when to allow or prevent + further retries to a target backend, by dynamically calculating a 'retry + budget'. This budget is calculated based on the percentage of incoming + traffic composed of retries over a given time interval. Once the budget + is exceeded, additional retries will be rejected. + + For example, if the retry budget interval is 10 seconds, there have been + 1000 active requests in the past 10 seconds, and the allowed percentage + of requests that can be retried is 20% (the default), then 200 of those + requests may be composed of retries. Active requests will only be + considered for the duration of the interval when calculating the retry + budget. Retrying the same original request multiple times within the + retry budget interval will lead to each retry being counted towards + calculating the budget. + + Configuring a RetryConstraint in BackendTrafficPolicy is compatible with + HTTPRoute Retry settings for each HTTPRouteRule that targets the same + backend. While the HTTPRouteRule Retry stanza can specify whether a + request will be retried, and the number of retry attempts each client + may perform, RetryConstraint helps prevent cascading failures such as + retry storms during periods of consistent failures. + + After the retry budget has been exceeded, additional retries to the + backend MUST return a 503 response to the client. + + Additional configurations for defining a constraint on retries MAY be + defined in the future. + + Support: Extended + properties: + budget: + default: + interval: 10s + percent: 20 + description: Budget holds the details of the retry budget configuration. + properties: + interval: + default: 10s + description: |- + Interval defines the duration in which requests will be considered + for calculating the budget for retries. + + Support: Extended + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + x-kubernetes-validations: + - message: interval can not be greater than one hour or less + than one second + rule: '!(duration(self) < duration(''1s'') || duration(self) + > duration(''1h''))' + percent: + default: 20 + description: |- + Percent defines the maximum percentage of active requests that may + be made up of retries. + + Support: Extended + maximum: 100 + minimum: 0 + type: integer + type: object + minRetryRate: + default: + count: 10 + interval: 1s + description: |- + MinRetryRate defines the minimum rate of retries that will be allowable + over a specified duration of time. + + The effective overall minimum rate of retries targeting the backend + service may be much higher, as there can be any number of clients which + are applying this setting locally. + + This ensures that requests can still be retried during periods of low + traffic, where the budget for retries may be calculated as a very low + value. + + Support: Extended + properties: + count: + description: |- + Count specifies the number of requests per time interval. + + Support: Extended + maximum: 1000000 + minimum: 1 + type: integer + interval: + description: |- + Interval specifies the divisor of the rate of requests, the amount of + time during which the given count of requests occur. + + Support: Extended + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + x-kubernetes-validations: + - message: interval can not be greater than one hour + rule: '!(duration(self) == duration(''0s'') || duration(self) + > duration(''1h''))' + type: object + type: object + sessionPersistence: + description: |- + SessionPersistence defines and configures session persistence + for the backend. + + Support: Extended + properties: + absoluteTimeout: + description: |- + AbsoluteTimeout defines the absolute timeout of the persistent + session. Once the AbsoluteTimeout duration has elapsed, the + session becomes invalid. + + Support: Extended + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + cookieConfig: + description: |- + CookieConfig provides configuration settings that are specific + to cookie-based session persistence. + + Support: Core + properties: + lifetimeType: + default: Session + description: |- + LifetimeType specifies whether the cookie has a permanent or + session-based lifetime. A permanent cookie persists until its + specified expiry time, defined by the Expires or Max-Age cookie + attributes, while a session cookie is deleted when the current + session ends. + + When set to "Permanent", AbsoluteTimeout indicates the + cookie's lifetime via the Expires or Max-Age cookie attributes + and is required. + + When set to "Session", AbsoluteTimeout indicates the + absolute lifetime of the cookie tracked by the gateway and + is optional. + + Defaults to "Session". + + Support: Core for "Session" type + + Support: Extended for "Permanent" type + enum: + - Permanent + - Session + type: string + type: object + idleTimeout: + description: |- + IdleTimeout defines the idle timeout of the persistent session. + Once the session has been idle for more than the specified + IdleTimeout duration, the session becomes invalid. + + Support: Extended + pattern: ^([0-9]{1,5}(h|m|s|ms)){1,4}$ + type: string + sessionName: + description: |- + SessionName defines the name of the persistent session token + which may be reflected in the cookie or the header. Users + should avoid reusing session names to prevent unintended + consequences, such as rejection or unpredictable behavior. + + Support: Implementation-specific + maxLength: 128 + type: string + type: + default: Cookie + description: |- + Type defines the type of session persistence such as through + the use a header or cookie. Defaults to cookie based session + persistence. + + Support: Core for "Cookie" type + + Support: Extended for "Header" type + enum: + - Cookie + - Header + type: string + type: object + x-kubernetes-validations: + - message: AbsoluteTimeout must be specified when cookie lifetimeType + is Permanent + rule: '!has(self.cookieConfig) || !has(self.cookieConfig.lifetimeType) + || self.cookieConfig.lifetimeType != ''Permanent'' || has(self.absoluteTimeout)' + targetRefs: + description: |- + TargetRefs identifies API object(s) to apply this policy to. + Currently, Backends (A grouping of like endpoints such as Service, + ServiceImport, or any implementation-specific backendRef) are the only + valid API target references. + + Currently, a TargetRef can not be scoped to a specific port on a + Service. + items: + description: |- + LocalPolicyTargetReference identifies an API object to apply a direct or + inherited policy to. This should be used as part of Policy resources + that can target Gateway API resources. For more information on how this + policy attachment model works, and a sample Policy resource, refer to + the policy attachment documentation for Gateway API. + properties: + group: + description: Group is the group of the target resource. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the target resource. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the target resource. + maxLength: 253 + minLength: 1 + type: string + required: + - group + - kind + - name + type: object + maxItems: 16 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - group + - kind + - name + x-kubernetes-list-type: map + required: + - targetRefs + type: object + status: + description: Status defines the current state of BackendTrafficPolicy. + properties: + ancestors: + description: |- + Ancestors is a list of ancestor resources (usually Gateways) that are + associated with the policy, and the status of the policy with respect to + each ancestor. When this policy attaches to a parent, the controller that + manages the parent and the ancestors MUST add an entry to this list when + the controller first sees the policy and SHOULD update the entry as + appropriate when the relevant ancestor is modified. + + Note that choosing the relevant ancestor is left to the Policy designers; + an important part of Policy design is designing the right object level at + which to namespace this status. + + Note also that implementations MUST ONLY populate ancestor status for + the Ancestor resources they are responsible for. Implementations MUST + use the ControllerName field to uniquely identify the entries in this list + that they are responsible for. + + Note that to achieve this, the list of PolicyAncestorStatus structs + MUST be treated as a map with a composite key, made up of the AncestorRef + and ControllerName fields combined. + + A maximum of 16 ancestors will be represented in this list. An empty list + means the Policy is not relevant for any ancestors. + + If this slice is full, implementations MUST NOT add further entries. + Instead they MUST consider the policy unimplementable and signal that + on any related resources such as the ancestor that would be referenced + here. For example, if this list was full on BackendTLSPolicy, no + additional Gateways would be able to reference the Service targeted by + the BackendTLSPolicy. + items: + description: |- + PolicyAncestorStatus describes the status of a route with respect to an + associated Ancestor. + + Ancestors refer to objects that are either the Target of a policy or above it + in terms of object hierarchy. For example, if a policy targets a Service, the + Policy's Ancestors are, in order, the Service, the HTTPRoute, the Gateway, and + the GatewayClass. Almost always, in this hierarchy, the Gateway will be the most + useful object to place Policy status on, so we recommend that implementations + SHOULD use Gateway as the PolicyAncestorStatus object unless the designers + have a _very_ good reason otherwise. + + In the context of policy attachment, the Ancestor is used to distinguish which + resource results in a distinct application of this policy. For example, if a policy + targets a Service, it may have a distinct result per attached Gateway. + + Policies targeting the same resource may have different effects depending on the + ancestors of those resources. For example, different Gateways targeting the same + Service may have different capabilities, especially if they have different underlying + implementations. + + For example, in BackendTLSPolicy, the Policy attaches to a Service that is + used as a backend in a HTTPRoute that is itself attached to a Gateway. + In this case, the relevant object for status is the Gateway, and that is the + ancestor object referred to in this status. + + Note that a parent is also an ancestor, so for objects where the parent is the + relevant object for status, this struct SHOULD still be used. + + This struct is intended to be used in a slice that's effectively a map, + with a composite key made up of the AncestorRef and the ControllerName. + properties: + ancestorRef: + description: |- + AncestorRef corresponds with a ParentRef in the spec that this + PolicyAncestorStatus struct describes the status of. + properties: + group: + default: gateway.networking.k8s.io + description: |- + Group is the group of the referent. + When unspecified, "gateway.networking.k8s.io" is inferred. + To set the core API group (such as for a "Service" kind referent), + Group must be explicitly set to "" (empty string). + + Support: Core + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: |- + Kind is kind of the referent. + + There are two kinds of parent resources with "Core" support: + + * Gateway (Gateway conformance profile) + * Service (Mesh conformance profile, ClusterIP Services only) + + Support for other resources is Implementation-Specific. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: |- + Name is the name of the referent. + + Support: Core + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referent. When unspecified, this refers + to the local namespace of the Route. + + Note that there are specific rules for ParentRefs which cross namespace + boundaries. Cross-namespace references are only valid if they are explicitly + allowed by something in the namespace they are referring to. For example: + Gateway has the AllowedRoutes field, and ReferenceGrant provides a + generic way to enable any other kind of cross-namespace reference. + + + ParentRefs from a Route to a Service in the same namespace are "producer" + routes, which apply default routing rules to inbound connections from + any namespace to the Service. + + ParentRefs from a Route to a Service in a different namespace are + "consumer" routes, and these routing rules are only applied to outbound + connections originating from the same namespace as the Route, for which + the intended destination of the connections are a Service targeted as a + ParentRef of the Route. + + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + port: + description: |- + Port is the network port this Route targets. It can be interpreted + differently based on the type of parent resource. + + When the parent resource is a Gateway, this targets all listeners + listening on the specified port that also support this kind of Route(and + select this Route). It's not recommended to set `Port` unless the + networking behaviors specified in a Route must apply to a specific port + as opposed to a listener(s) whose port(s) may be changed. When both Port + and SectionName are specified, the name and port of the selected listener + must match both specified values. + + + When the parent resource is a Service, this targets a specific port in the + Service spec. When both Port (experimental) and SectionName are specified, + the name and port of the selected port must match both specified values. + + + Implementations MAY choose to support other parent resources. + Implementations supporting other types of parent resources MUST clearly + document how/if Port is interpreted. + + For the purpose of status, an attachment is considered successful as + long as the parent resource accepts it partially. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment + from the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, + the Route MUST be considered detached from the Gateway. + + Support: Extended + format: int32 + maximum: 65535 + minimum: 1 + type: integer + sectionName: + description: |- + SectionName is the name of a section within the target resource. In the + following resources, SectionName is interpreted as the following: + + * Gateway: Listener name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + * Service: Port name. When both Port (experimental) and SectionName + are specified, the name and port of the selected listener must match + both specified values. + + Implementations MAY choose to support attaching Routes to other resources. + If that is the case, they MUST clearly document how SectionName is + interpreted. + + When unspecified (empty string), this will reference the entire resource. + For the purpose of status, an attachment is considered successful if at + least one section in the parent resource accepts it. For example, Gateway + listeners can restrict which Routes can attach to them by Route kind, + namespace, or hostname. If 1 of 2 Gateway listeners accept attachment from + the referencing Route, the Route MUST be considered successfully + attached. If no Gateway listeners accept attachment from this Route, the + Route MUST be considered detached from the Gateway. + + Support: Core + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + required: + - name + type: object + conditions: + description: Conditions describes the status of the Policy with + respect to the given Ancestor. + items: + description: Condition contains details for one aspect of + the current state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, + Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + controllerName: + description: |- + ControllerName is a domain/path string that indicates the name of the + controller that wrote this status. This corresponds with the + controllerName field on GatewayClass. + + Example: "example.net/gateway-controller". + + The format of this field is DOMAIN "/" PATH, where DOMAIN and PATH are + valid Kubernetes names + (https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names). + + Controllers MUST populate this field when writing status. Controllers should ensure that + entries to status populated with their ControllerName are cleaned up when they are no + longer necessary. + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9\/\-._~%!$&'()*+,;=:]+$ + type: string + required: + - ancestorRef + - controllerName + type: object + maxItems: 16 + type: array + required: + - ancestors + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: null + storedVersions: null +--- +# +# config/crd/experimental/gateway.networking.x-k8s.io_xlistenersets.yaml +# +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + api-approved.kubernetes.io: https://github.com/kubernetes-sigs/gateway-api/pull/3328 + gateway.networking.k8s.io/bundle-version: v1.3.0 + gateway.networking.k8s.io/channel: experimental + creationTimestamp: null + name: xlistenersets.gateway.networking.x-k8s.io +spec: + group: gateway.networking.x-k8s.io + names: + categories: + - gateway-api + kind: XListenerSet + listKind: XListenerSetList + plural: xlistenersets + shortNames: + - lset + singular: xlistenerset + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.conditions[?(@.type=="Accepted")].status + name: Accepted + type: string + - jsonPath: .status.conditions[?(@.type=="Programmed")].status + name: Programmed + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha1 + schema: + openAPIV3Schema: + description: |- + XListenerSet defines a set of additional listeners + to attach to an existing Gateway. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of ListenerSet. + properties: + listeners: + description: |- + Listeners associated with this ListenerSet. Listeners define + logical endpoints that are bound on this referenced parent Gateway's addresses. + + Listeners in a `Gateway` and their attached `ListenerSets` are concatenated + as a list when programming the underlying infrastructure. Each listener + name does not need to be unique across the Gateway and ListenerSets. + See ListenerEntry.Name for more details. + + Implementations MUST treat the parent Gateway as having the merged + list of all listeners from itself and attached ListenerSets using + the following precedence: + + 1. "parent" Gateway + 2. ListenerSet ordered by creation time (oldest first) + 3. ListenerSet ordered alphabetically by “{namespace}/{name}”. + + An implementation MAY reject listeners by setting the ListenerEntryStatus + `Accepted`` condition to False with the Reason `TooManyListeners` + + If a listener has a conflict, this will be reported in the + Status.ListenerEntryStatus setting the `Conflicted` condition to True. + + Implementations SHOULD be cautious about what information from the + parent or siblings are reported to avoid accidentally leaking + sensitive information that the child would not otherwise have access + to. This can include contents of secrets etc. + items: + properties: + allowedRoutes: + default: + namespaces: + from: Same + description: |- + AllowedRoutes defines the types of routes that MAY be attached to a + Listener and the trusted namespaces where those Route resources MAY be + present. + + Although a client request may match multiple route rules, only one rule + may ultimately receive the request. Matching precedence MUST be + determined in order of the following criteria: + + * The most specific match as defined by the Route type. + * The oldest Route based on creation timestamp. For example, a Route with + a creation timestamp of "2020-09-08 01:02:03" is given precedence over + a Route with a creation timestamp of "2020-09-08 01:02:04". + * If everything else is equivalent, the Route appearing first in + alphabetical order (namespace/name) should be given precedence. For + example, foo/bar is given precedence over foo/baz. + + All valid rules within a Route attached to this Listener should be + implemented. Invalid Route rules can be ignored (sometimes that will mean + the full Route). If a Route rule transitions from valid to invalid, + support for that Route rule should be dropped to ensure consistency. For + example, even if a filter specified by a Route rule is invalid, the rest + of the rules within that Route should still be supported. + properties: + kinds: + description: |- + Kinds specifies the groups and kinds of Routes that are allowed to bind + to this Gateway Listener. When unspecified or empty, the kinds of Routes + selected are determined using the Listener protocol. + + A RouteGroupKind MUST correspond to kinds of Routes that are compatible + with the application protocol specified in the Listener's Protocol field. + If an implementation does not support or recognize this resource type, it + MUST set the "ResolvedRefs" condition to False for this Listener with the + "InvalidRouteKinds" reason. + + Support: Core + items: + description: RouteGroupKind indicates the group and kind + of a Route resource. + properties: + group: + default: gateway.networking.k8s.io + description: Group is the group of the Route. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is the kind of the Route. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + required: + - kind + type: object + maxItems: 8 + type: array + namespaces: + default: + from: Same + description: |- + Namespaces indicates namespaces from which Routes may be attached to this + Listener. This is restricted to the namespace of this Gateway by default. + + Support: Core + properties: + from: + default: Same + description: |- + From indicates where Routes will be selected for this Gateway. Possible + values are: + + * All: Routes in all namespaces may be used by this Gateway. + * Selector: Routes in namespaces selected by the selector may be used by + this Gateway. + * Same: Only Routes in the same namespace may be used by this Gateway. + + Support: Core + enum: + - All + - Selector + - Same + type: string + selector: + description: |- + Selector must be specified when From is set to "Selector". In that case, + only Routes in Namespaces matching this Selector will be selected by this + Gateway. This field is ignored for other values of "From". + + Support: Core + properties: + matchExpressions: + description: matchExpressions is a list of label + selector requirements. The requirements are ANDed. + items: + description: |- + A label selector requirement is a selector that contains values, a key, and an operator that + relates the key and values. + properties: + key: + description: key is the label key that the + selector applies to. + type: string + operator: + description: |- + operator represents a key's relationship to a set of values. + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + values: + description: |- + values is an array of string values. If the operator is In or NotIn, + the values array must be non-empty. If the operator is Exists or DoesNotExist, + the values array must be empty. This array is replaced during a strategic + merge patch. + items: + type: string + type: array + x-kubernetes-list-type: atomic + required: + - key + - operator + type: object + type: array + x-kubernetes-list-type: atomic + matchLabels: + additionalProperties: + type: string + description: |- + matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, whose key field is "key", the + operator is "In", and the values array contains only "value". The requirements are ANDed. + type: object + type: object + x-kubernetes-map-type: atomic + type: object + type: object + hostname: + description: |- + Hostname specifies the virtual hostname to match for protocol types that + define this concept. When unspecified, all hostnames are matched. This + field is ignored for protocols that don't require hostname based + matching. + + Implementations MUST apply Hostname matching appropriately for each of + the following protocols: + + * TLS: The Listener Hostname MUST match the SNI. + * HTTP: The Listener Hostname MUST match the Host header of the request. + * HTTPS: The Listener Hostname SHOULD match at both the TLS and HTTP + protocol layers as described above. If an implementation does not + ensure that both the SNI and Host header match the Listener hostname, + it MUST clearly document that. + + For HTTPRoute and TLSRoute resources, there is an interaction with the + `spec.hostnames` array. When both listener and route specify hostnames, + there MUST be an intersection between the values for a Route to be + accepted. For more information, refer to the Route specific Hostnames + documentation. + + Hostnames that are prefixed with a wildcard label (`*.`) are interpreted + as a suffix match. That means that a match for `*.example.com` would match + both `test.example.com`, and `foo.test.example.com`, but not `example.com`. + maxLength: 253 + minLength: 1 + pattern: ^(\*\.)?[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + name: + description: |- + Name is the name of the Listener. This name MUST be unique within a + ListenerSet. + + Name is not required to be unique across a Gateway and ListenerSets. + Routes can attach to a Listener by having a ListenerSet as a parentRef + and setting the SectionName + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + port: + description: |- + Port is the network port. Multiple listeners may use the + same port, subject to the Listener compatibility rules. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + protocol: + description: Protocol specifies the network protocol this listener + expects to receive. + maxLength: 255 + minLength: 1 + pattern: ^[a-zA-Z0-9]([-a-zA-Z0-9]*[a-zA-Z0-9])?$|[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*\/[A-Za-z0-9]+$ + type: string + tls: + description: |- + TLS is the TLS configuration for the Listener. This field is required if + the Protocol field is "HTTPS" or "TLS". It is invalid to set this field + if the Protocol field is "HTTP", "TCP", or "UDP". + + The association of SNIs to Certificate defined in GatewayTLSConfig is + defined based on the Hostname field for this listener. + + The GatewayClass MUST use the longest matching SNI out of all + available certificates for any TLS handshake. + properties: + certificateRefs: + description: |- + CertificateRefs contains a series of references to Kubernetes objects that + contains TLS certificates and private keys. These certificates are used to + establish a TLS handshake for requests that match the hostname of the + associated listener. + + A single CertificateRef to a Kubernetes Secret has "Core" support. + Implementations MAY choose to support attaching multiple certificates to + a Listener, but this behavior is implementation-specific. + + References to a resource in different namespace are invalid UNLESS there + is a ReferenceGrant in the target namespace that allows the certificate + to be attached. If a ReferenceGrant does not allow this reference, the + "ResolvedRefs" condition MUST be set to False for this listener with the + "RefNotPermitted" reason. + + This field is required to have at least one element when the mode is set + to "Terminate" (default) and is optional otherwise. + + CertificateRefs can reference to standard Kubernetes resources, i.e. + Secret, or implementation-specific custom resources. + + Support: Core - A single reference to a Kubernetes Secret of type kubernetes.io/tls + + Support: Implementation-specific (More than one reference or other resource types) + items: + description: |- + SecretObjectReference identifies an API object including its namespace, + defaulting to Secret. + + The API object must be valid in the cluster; the Group and Kind must + be registered in the cluster for this reference to be valid. + + References to objects with invalid Group and Kind are not valid, and must + be rejected by the implementation, with appropriate Conditions set + on the containing object. + properties: + group: + default: "" + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When unspecified or empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Secret + description: Kind is kind of the referent. For example + "Secret". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referenced object. When unspecified, the local + namespace is inferred. + + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - name + type: object + maxItems: 64 + type: array + frontendValidation: + description: |- + FrontendValidation holds configuration information for validating the frontend (client). + Setting this field will require clients to send a client certificate + required for validation during the TLS handshake. In browsers this may result in a dialog appearing + that requests a user to specify the client certificate. + The maximum depth of a certificate chain accepted in verification is Implementation specific. + + Support: Extended + properties: + caCertificateRefs: + description: |- + CACertificateRefs contains one or more references to + Kubernetes objects that contain TLS certificates of + the Certificate Authorities that can be used + as a trust anchor to validate the certificates presented by the client. + + A single CA certificate reference to a Kubernetes ConfigMap + has "Core" support. + Implementations MAY choose to support attaching multiple CA certificates to + a Listener, but this behavior is implementation-specific. + + Support: Core - A single reference to a Kubernetes ConfigMap + with the CA certificate in a key named `ca.crt`. + + Support: Implementation-specific (More than one reference, or other kinds + of resources). + + References to a resource in a different namespace are invalid UNLESS there + is a ReferenceGrant in the target namespace that allows the certificate + to be attached. If a ReferenceGrant does not allow this reference, the + "ResolvedRefs" condition MUST be set to False for this listener with the + "RefNotPermitted" reason. + items: + description: |- + ObjectReference identifies an API object including its namespace. + + The API object must be valid in the cluster; the Group and Kind must + be registered in the cluster for this reference to be valid. + + References to objects with invalid Group and Kind are not valid, and must + be rejected by the implementation, with appropriate Conditions set + on the containing object. + properties: + group: + description: |- + Group is the group of the referent. For example, "gateway.networking.k8s.io". + When set to the empty string, core API group is inferred. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is kind of the referent. For + example "ConfigMap" or "Service". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referenced object. When unspecified, the local + namespace is inferred. + + Note that when a namespace different than the local namespace is specified, + a ReferenceGrant object is required in the referent namespace to allow that + namespace's owner to accept the reference. See the ReferenceGrant + documentation for details. + + Support: Core + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - group + - kind + - name + type: object + maxItems: 8 + minItems: 1 + type: array + type: object + mode: + default: Terminate + description: |- + Mode defines the TLS behavior for the TLS session initiated by the client. + There are two possible modes: + + - Terminate: The TLS session between the downstream client and the + Gateway is terminated at the Gateway. This mode requires certificates + to be specified in some way, such as populating the certificateRefs + field. + - Passthrough: The TLS session is NOT terminated by the Gateway. This + implies that the Gateway can't decipher the TLS stream except for + the ClientHello message of the TLS protocol. The certificateRefs field + is ignored in this mode. + + Support: Core + enum: + - Terminate + - Passthrough + type: string + options: + additionalProperties: + description: |- + AnnotationValue is the value of an annotation in Gateway API. This is used + for validation of maps such as TLS options. This roughly matches Kubernetes + annotation validation, although the length validation in that case is based + on the entire size of the annotations struct. + maxLength: 4096 + minLength: 0 + type: string + description: |- + Options are a list of key/value pairs to enable extended TLS + configuration for each implementation. For example, configuring the + minimum TLS version or supported cipher suites. + + A set of common keys MAY be defined by the API in the future. To avoid + any ambiguity, implementation-specific definitions MUST use + domain-prefixed names, such as `example.com/my-custom-option`. + Un-prefixed names are reserved for key names defined by Gateway API. + + Support: Implementation-specific + maxProperties: 16 + type: object + type: object + x-kubernetes-validations: + - message: certificateRefs or options must be specified when + mode is Terminate + rule: 'self.mode == ''Terminate'' ? size(self.certificateRefs) + > 0 || size(self.options) > 0 : true' + required: + - name + - port + - protocol + type: object + maxItems: 64 + minItems: 1 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + x-kubernetes-validations: + - message: tls must not be specified for protocols ['HTTP', 'TCP', + 'UDP'] + rule: 'self.all(l, l.protocol in [''HTTP'', ''TCP'', ''UDP''] ? + !has(l.tls) : true)' + - message: tls mode must be Terminate for protocol HTTPS + rule: 'self.all(l, (l.protocol == ''HTTPS'' && has(l.tls)) ? (l.tls.mode + == '''' || l.tls.mode == ''Terminate'') : true)' + - message: hostname must not be specified for protocols ['TCP', 'UDP'] + rule: 'self.all(l, l.protocol in [''TCP'', ''UDP''] ? (!has(l.hostname) + || l.hostname == '''') : true)' + - message: Listener name must be unique within the Gateway + rule: self.all(l1, self.exists_one(l2, l1.name == l2.name)) + - message: Combination of port, protocol and hostname must be unique + for each listener + rule: 'self.all(l1, !has(l1.port) || self.exists_one(l2, has(l2.port) + && l1.port == l2.port && l1.protocol == l2.protocol && (has(l1.hostname) + && has(l2.hostname) ? l1.hostname == l2.hostname : !has(l1.hostname) + && !has(l2.hostname))))' + parentRef: + description: ParentRef references the Gateway that the listeners are + attached to. + properties: + group: + default: gateway.networking.k8s.io + description: Group is the group of the referent. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + default: Gateway + description: Kind is kind of the referent. For example "Gateway". + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + name: + description: Name is the name of the referent. + maxLength: 253 + minLength: 1 + type: string + namespace: + description: |- + Namespace is the namespace of the referent. If not present, + the namespace of the referent is assumed to be the same as + the namespace of the referring object. + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + type: string + required: + - name + type: object + required: + - listeners + - parentRef + type: object + status: + default: + conditions: + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Pending + status: Unknown + type: Accepted + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Pending + status: Unknown + type: Programmed + description: Status defines the current state of ListenerSet. + properties: + conditions: + default: + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Pending + status: Unknown + type: Accepted + - lastTransitionTime: "1970-01-01T00:00:00Z" + message: Waiting for controller + reason: Pending + status: Unknown + type: Programmed + description: |- + Conditions describe the current conditions of the ListenerSet. + + Implementations MUST express ListenerSet conditions using the + `ListenerSetConditionType` and `ListenerSetConditionReason` + constants so that operators and tools can converge on a common + vocabulary to describe ListenerSet state. + + Known condition types are: + + * "Accepted" + * "Programmed" + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + listeners: + description: Listeners provide status for each unique listener port + defined in the Spec. + items: + description: ListenerStatus is the status associated with a Listener. + properties: + attachedRoutes: + description: |- + AttachedRoutes represents the total number of Routes that have been + successfully attached to this Listener. + + Successful attachment of a Route to a Listener is based solely on the + combination of the AllowedRoutes field on the corresponding Listener + and the Route's ParentRefs field. A Route is successfully attached to + a Listener when it is selected by the Listener's AllowedRoutes field + AND the Route has a valid ParentRef selecting the whole Gateway + resource or a specific Listener as a parent resource (more detail on + attachment semantics can be found in the documentation on the various + Route kinds ParentRefs fields). Listener or Route status does not impact + successful attachment, i.e. the AttachedRoutes field count MUST be set + for Listeners with condition Accepted: false and MUST count successfully + attached Routes that may themselves have Accepted: false conditions. + + Uses for this field include troubleshooting Route attachment and + measuring blast radius/impact of changes to a Listener. + format: int32 + type: integer + conditions: + description: Conditions describe the current condition of this + listener. + items: + description: Condition contains details for one aspect of + the current state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, + Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + maxItems: 8 + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + name: + description: Name is the name of the Listener that this status + corresponds to. + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + port: + description: Port is the network port the listener is configured + to listen on. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + supportedKinds: + description: |- + SupportedKinds is the list indicating the Kinds supported by this + listener. This MUST represent the kinds an implementation supports for + that Listener configuration. + + If kinds are specified in Spec that are not supported, they MUST NOT + appear in this list and an implementation MUST set the "ResolvedRefs" + condition to "False" with the "InvalidRouteKinds" reason. If both valid + and invalid Route kinds are specified, the implementation MUST + reference the valid Route kinds that have been specified. + items: + description: RouteGroupKind indicates the group and kind of + a Route resource. + properties: + group: + default: gateway.networking.k8s.io + description: Group is the group of the Route. + maxLength: 253 + pattern: ^$|^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + kind: + description: Kind is the kind of the Route. + maxLength: 63 + minLength: 1 + pattern: ^[a-zA-Z]([-a-zA-Z0-9]*[a-zA-Z0-9])?$ + type: string + required: + - kind + type: object + maxItems: 8 + type: array + required: + - attachedRoutes + - conditions + - name + - port + - supportedKinds + type: object + maxItems: 64 + type: array + x-kubernetes-list-map-keys: + - name + x-kubernetes-list-type: map + type: object + required: + - spec + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: null + storedVersions: null diff --git a/integration/fixtures/k8s/01-traefik-crd.yml b/integration/fixtures/k8s/01-traefik-crd.yml index 4a471058f..ea2618637 100644 --- a/integration/fixtures/k8s/01-traefik-crd.yml +++ b/integration/fixtures/k8s/01-traefik-crd.yml @@ -43,7 +43,7 @@ spec: 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/v3.4/routing/entrypoints/ + More info: https://doc.traefik.io/traefik/v3.5/reference/install-configuration/entrypoints/ Default: all. items: type: string @@ -64,12 +64,12 @@ spec: match: description: |- Match defines the router's rule. - More info: https://doc.traefik.io/traefik/v3.4/routing/routers/#rule + More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/routing/rules-and-priority/ type: string middlewares: description: |- Middlewares defines the list of references to Middleware resources. - More info: https://doc.traefik.io/traefik/v3.4/routing/providers/kubernetes-crd/#kind-middleware + More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/kubernetes/crd/http/middleware/ items: description: MiddlewareRef is a reference to a Middleware resource. @@ -89,19 +89,30 @@ spec: observability: description: |- Observability defines the observability configuration for a router. - More info: https://doc.traefik.io/traefik/v3.4/routing/routers/#observability + More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/routing/observability/ properties: accessLogs: + description: AccessLogs enables access logs for this router. type: boolean metrics: + description: Metrics enables metrics for this router. type: boolean + traceVerbosity: + default: minimal + description: TraceVerbosity defines the verbosity level + of the tracing for this router. + enum: + - minimal + - detailed + type: string tracing: + description: Tracing enables tracing for this router. type: boolean type: object priority: description: |- Priority defines the router's priority. - More info: https://doc.traefik.io/traefik/v3.4/routing/routers/#priority + More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/routing/rules-and-priority/#priority maximum: 9223372036854775000 type: integer services: @@ -136,7 +147,7 @@ spec: - type: integer - type: string description: |- - Interval defines the frequency of the health check calls. + Interval defines the frequency of the health check calls for healthy targets. Default: 30s x-kubernetes-int-or-string: true method: @@ -172,6 +183,15 @@ spec: Timeout defines the maximum duration Traefik will wait for a health check request before considering the server unhealthy. Default: 5s x-kubernetes-int-or-string: true + unhealthyInterval: + anyOf: + - type: integer + - type: string + description: |- + UnhealthyInterval defines the frequency of the health check calls for unhealthy targets. + When UnhealthyInterval is not defined, it defaults to the Interval value. + Default: 30s + x-kubernetes-int-or-string: true type: object kind: description: Kind defines the kind of the Service. @@ -243,7 +263,7 @@ spec: sticky: description: |- Sticky defines the sticky sessions configuration. - More info: https://doc.traefik.io/traefik/v3.4/routing/services/#sticky-sessions + More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/load-balancing/service/#sticky-sessions properties: cookie: description: Cookie defines the sticky cookie configuration. @@ -312,7 +332,7 @@ spec: syntax: description: |- Syntax defines the router's rule syntax. - More info: https://doc.traefik.io/traefik/v3.4/routing/routers/#rulesyntax + More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/routing/rules-and-priority/#rulesyntax Deprecated: Please do not use this field and rewrite the router rules to use the v3 syntax. type: string required: @@ -322,18 +342,18 @@ spec: tls: description: |- TLS defines the TLS configuration. - More info: https://doc.traefik.io/traefik/v3.4/routing/routers/#tls + More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/routing/router/#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/v3.4/https/acme/#certificate-resolvers + More info: https://doc.traefik.io/traefik/v3.5/reference/install-configuration/tls/certificate-resolvers/acme/ type: string domains: description: |- Domains defines the list of domains that will be used to issue certificates. - More info: https://doc.traefik.io/traefik/v3.4/routing/routers/#domains + More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/tls/tls-certificates/#domains items: description: Domain holds a domain name with SANs. properties: @@ -352,17 +372,17 @@ spec: 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/v3.4/https/tls/#tls-options + More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/tls/tls-options/ properties: name: description: |- Name defines the name of the referenced TLSOption. - More info: https://doc.traefik.io/traefik/v3.4/routing/providers/kubernetes-crd/#kind-tlsoption + More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/kubernetes/crd/http/tlsoption/ type: string namespace: description: |- Namespace defines the namespace of the referenced TLSOption. - More info: https://doc.traefik.io/traefik/v3.4/routing/providers/kubernetes-crd/#kind-tlsoption + More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/kubernetes/crd/http/tlsoption/ type: string required: - name @@ -379,12 +399,12 @@ spec: name: description: |- Name defines the name of the referenced TLSStore. - More info: https://doc.traefik.io/traefik/v3.4/routing/providers/kubernetes-crd/#kind-tlsstore + More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/kubernetes/crd/http/tlsstore/ type: string namespace: description: |- Namespace defines the namespace of the referenced TLSStore. - More info: https://doc.traefik.io/traefik/v3.4/routing/providers/kubernetes-crd/#kind-tlsstore + More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/kubernetes/crd/http/tlsstore/ type: string required: - name @@ -444,7 +464,7 @@ spec: 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/v3.4/routing/entrypoints/ + More info: https://doc.traefik.io/traefik/v3.5/reference/install-configuration/entrypoints/ Default: all. items: type: string @@ -457,7 +477,7 @@ spec: match: description: |- Match defines the router's rule. - More info: https://doc.traefik.io/traefik/v3.4/routing/routers/#rule_1 + More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/tcp/routing/rules-and-priority/ type: string middlewares: description: Middlewares defines the list of references to MiddlewareTCP @@ -481,7 +501,7 @@ spec: priority: description: |- Priority defines the router's priority. - More info: https://doc.traefik.io/traefik/v3.4/routing/routers/#priority_1 + More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/tcp/routing/rules-and-priority/#priority maximum: 9223372036854775000 type: integer services: @@ -523,7 +543,8 @@ spec: proxyProtocol: description: |- ProxyProtocol defines the PROXY protocol configuration. - More info: https://doc.traefik.io/traefik/v3.4/routing/services/#proxy-protocol + More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/tcp/service/#proxy-protocol + Deprecated: ProxyProtocol will not be supported in future APIVersions, please use ServersTransport to configure ProxyProtocol instead. properties: version: description: Version defines the PROXY Protocol version @@ -564,7 +585,7 @@ spec: syntax: description: |- Syntax defines the router's rule syntax. - More info: https://doc.traefik.io/traefik/v3.4/routing/routers/#rulesyntax_1 + More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/tcp/routing/rules-and-priority/#rulesyntax Deprecated: Please do not use this field and rewrite the router rules to use the v3 syntax. enum: - v3 @@ -577,18 +598,18 @@ spec: tls: description: |- TLS defines the TLS configuration on a layer 4 / TCP Route. - More info: https://doc.traefik.io/traefik/v3.4/routing/routers/#tls_1 + More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/tcp/routing/router/#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/v3.4/https/acme/#certificate-resolvers + More info: https://doc.traefik.io/traefik/v3.5/reference/install-configuration/tls/certificate-resolvers/acme/ type: string domains: description: |- Domains defines the list of domains that will be used to issue certificates. - More info: https://doc.traefik.io/traefik/v3.4/routing/routers/#domains + More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/tcp/tls/#domains items: description: Domain holds a domain name with SANs. properties: @@ -607,7 +628,7 @@ spec: 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/v3.4/https/tls/#tls-options + More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/tcp/tls/#tls-options properties: name: description: Name defines the name of the referenced Traefik @@ -699,7 +720,7 @@ spec: 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/v3.4/routing/entrypoints/ + More info: https://doc.traefik.io/traefik/v3.5/reference/install-configuration/entrypoints/ Default: all. items: type: string @@ -787,7 +808,7 @@ spec: openAPIV3Schema: description: |- Middleware is the CRD implementation of a Traefik Middleware. - More info: https://doc.traefik.io/traefik/v3.4/middlewares/http/overview/ + More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/middlewares/overview/ properties: apiVersion: description: |- @@ -813,7 +834,7 @@ spec: 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/v3.4/middlewares/http/addprefix/ + More info: https://doc.traefik.io/traefik/v3.5/middlewares/http/addprefix/ properties: prefix: description: |- @@ -828,12 +849,12 @@ spec: 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/v3.4/middlewares/http/basicauth/ + More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/middlewares/basicauth/ properties: headerField: description: |- HeaderField defines a header field to store the authenticated user. - More info: https://doc.traefik.io/traefik/v3.4/middlewares/http/basicauth/#headerfield + More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/middlewares/basicauth/#headerfield type: string realm: description: |- @@ -854,7 +875,7 @@ spec: 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/v3.4/middlewares/http/buffering/#maxrequestbodybytes + More info: https://doc.traefik.io/traefik/v3.5/middlewares/http/buffering/#maxrequestbodybytes properties: maxRequestBodyBytes: description: |- @@ -886,14 +907,14 @@ spec: 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/v3.4/middlewares/http/buffering/#retryexpression + More info: https://doc.traefik.io/traefik/v3.5/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/v3.4/middlewares/http/chain/ + More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/middlewares/chain/ properties: middlewares: description: Middlewares is the list of MiddlewareRef which composes @@ -956,7 +977,7 @@ spec: description: |- Compress holds the compress middleware configuration. This middleware compresses responses before sending them to the client, using gzip, brotli, or zstd compression. - More info: https://doc.traefik.io/traefik/v3.4/middlewares/http/compress/ + More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/middlewares/compress/ properties: defaultEncoding: description: DefaultEncoding specifies the default encoding if @@ -1006,12 +1027,12 @@ spec: 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/v3.4/middlewares/http/digestauth/ + More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/middlewares/digestauth/ properties: headerField: description: |- HeaderField defines a header field to store the authenticated user. - More info: https://doc.traefik.io/traefik/v3.4/middlewares/http/basicauth/#headerfield + More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/middlewares/digestauth/#headerfield type: string realm: description: |- @@ -1031,7 +1052,7 @@ spec: 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/v3.4/middlewares/http/errorpages/ + More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/middlewares/errorpages/ properties: query: description: |- @@ -1043,7 +1064,7 @@ spec: service: description: |- Service defines the reference to a Kubernetes Service that will serve the error page. - More info: https://doc.traefik.io/traefik/v3.4/middlewares/http/errorpages/#service + More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/middlewares/errorpages/#service properties: healthCheck: description: Healthcheck defines health checks for ExternalName @@ -1069,7 +1090,7 @@ spec: - type: integer - type: string description: |- - Interval defines the frequency of the health check calls. + Interval defines the frequency of the health check calls for healthy targets. Default: 30s x-kubernetes-int-or-string: true method: @@ -1105,6 +1126,15 @@ spec: Timeout defines the maximum duration Traefik will wait for a health check request before considering the server unhealthy. Default: 5s x-kubernetes-int-or-string: true + unhealthyInterval: + anyOf: + - type: integer + - type: string + description: |- + UnhealthyInterval defines the frequency of the health check calls for unhealthy targets. + When UnhealthyInterval is not defined, it defaults to the Interval value. + Default: 30s + x-kubernetes-int-or-string: true type: object kind: description: Kind defines the kind of the Service. @@ -1176,7 +1206,7 @@ spec: sticky: description: |- Sticky defines the sticky sessions configuration. - More info: https://doc.traefik.io/traefik/v3.4/routing/services/#sticky-sessions + More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/load-balancing/service/#sticky-sessions properties: cookie: description: Cookie defines the sticky cookie configuration. @@ -1263,7 +1293,7 @@ spec: description: |- ForwardAuth holds the forward auth middleware configuration. This middleware delegates the request authentication to a Service. - More info: https://doc.traefik.io/traefik/v3.4/middlewares/http/forwardauth/ + More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/middlewares/forwardauth/ properties: addAuthCookiesToResponse: description: AddAuthCookiesToResponse defines the list of cookies @@ -1291,7 +1321,7 @@ spec: authResponseHeadersRegex: 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/v3.4/middlewares/http/forwardauth/#authresponseheadersregex + More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/middlewares/forwardauth/#authresponseheadersregex type: string forwardBody: description: ForwardBody defines whether to send the request body @@ -1300,7 +1330,7 @@ spec: headerField: description: |- HeaderField defines a header field to store the authenticated user. - More info: https://doc.traefik.io/traefik/v3.4/middlewares/http/forwardauth/#headerfield + More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/middlewares/forwardauth/#headerfield type: string maxBodySize: description: MaxBodySize defines the maximum body size in bytes @@ -1362,7 +1392,7 @@ spec: description: |- Headers holds the headers middleware configuration. This middleware manages the requests and responses headers. - More info: https://doc.traefik.io/traefik/v3.4/middlewares/http/headers/#customrequestheaders + More info: https://doc.traefik.io/traefik/v3.5/middlewares/http/headers/#customrequestheaders properties: accessControlAllowCredentials: description: AccessControlAllowCredentials defines whether the @@ -1534,7 +1564,7 @@ spec: 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/v3.4/middlewares/http/inflightreq/ + More info: https://doc.traefik.io/traefik/v3.5/middlewares/http/inflightreq/ properties: amount: description: |- @@ -1548,12 +1578,12 @@ spec: 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/v3.4/middlewares/http/inflightreq/#sourcecriterion + More info: https://doc.traefik.io/traefik/v3.5/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/v3.4/middlewares/http/ipallowlist/#ipstrategy + More info: https://doc.traefik.io/traefik/v3.5/middlewares/http/ipallowlist/#ipstrategy properties: depth: description: Depth tells Traefik to use the X-Forwarded-For @@ -1589,12 +1619,12 @@ spec: description: |- IPAllowList holds the IP allowlist middleware configuration. This middleware limits allowed requests based on the client IP. - More info: https://doc.traefik.io/traefik/v3.4/middlewares/http/ipallowlist/ + More info: https://doc.traefik.io/traefik/v3.5/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/v3.4/middlewares/http/ipallowlist/#ipstrategy + More info: https://doc.traefik.io/traefik/v3.5/middlewares/http/ipallowlist/#ipstrategy properties: depth: description: Depth tells Traefik to use the X-Forwarded-For @@ -1632,7 +1662,7 @@ spec: ipStrategy: description: |- IPStrategy holds the IP strategy configuration used by Traefik to determine the client IP. - More info: https://doc.traefik.io/traefik/v3.4/middlewares/http/ipallowlist/#ipstrategy + More info: https://doc.traefik.io/traefik/v3.5/middlewares/http/ipallowlist/#ipstrategy properties: depth: description: Depth tells Traefik to use the X-Forwarded-For @@ -1663,7 +1693,7 @@ spec: 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/v3.4/middlewares/http/passtlsclientcert/ + More info: https://doc.traefik.io/traefik/v3.5/middlewares/http/passtlsclientcert/ properties: info: description: Info selects the specific client certificate details @@ -1766,13 +1796,13 @@ spec: x-kubernetes-preserve-unknown-fields: true description: |- Plugin defines the middleware plugin configuration. - More info: https://doc.traefik.io/traefik/plugins/ + More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/middlewares/overview/#community-middlewares type: object 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/v3.4/middlewares/http/ratelimit/ + More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/middlewares/ratelimit/ properties: average: description: |- @@ -1891,7 +1921,7 @@ spec: ipStrategy: description: |- IPStrategy holds the IP strategy configuration used by Traefik to determine the client IP. - More info: https://doc.traefik.io/traefik/v3.4/middlewares/http/ipallowlist/#ipstrategy + More info: https://doc.traefik.io/traefik/v3.5/middlewares/http/ipallowlist/#ipstrategy properties: depth: description: Depth tells Traefik to use the X-Forwarded-For @@ -1927,11 +1957,11 @@ spec: 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/v3.4/middlewares/http/redirectregex/#regex + More info: https://doc.traefik.io/traefik/v3.5/middlewares/http/redirectregex/#regex properties: permanent: description: Permanent defines whether the redirection is permanent - (301). + (308). type: boolean regex: description: Regex defines the regex used to match and capture @@ -1946,11 +1976,11 @@ spec: 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/v3.4/middlewares/http/redirectscheme/ + More info: https://doc.traefik.io/traefik/v3.5/middlewares/http/redirectscheme/ properties: permanent: description: Permanent defines whether the redirection is permanent - (301). + (308). type: boolean port: description: Port defines the port of the new URL. @@ -1963,7 +1993,7 @@ spec: 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/v3.4/middlewares/http/replacepath/ + More info: https://doc.traefik.io/traefik/v3.5/middlewares/http/replacepath/ properties: path: description: Path defines the path to use as replacement in the @@ -1974,7 +2004,7 @@ spec: 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/v3.4/middlewares/http/replacepathregex/ + More info: https://doc.traefik.io/traefik/v3.5/middlewares/http/replacepathregex/ properties: regex: description: Regex defines the regular expression used to match @@ -1990,7 +2020,7 @@ spec: 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/v3.4/middlewares/http/retry/ + More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/middlewares/retry/ properties: attempts: description: Attempts defines how many times the request should @@ -2014,7 +2044,7 @@ spec: 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/v3.4/middlewares/http/stripprefix/ + More info: https://doc.traefik.io/traefik/v3.5/middlewares/http/stripprefix/ properties: forceSlash: description: |- @@ -2033,7 +2063,7 @@ spec: 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/v3.4/middlewares/http/stripprefixregex/ + More info: https://doc.traefik.io/traefik/v3.5/middlewares/http/stripprefixregex/ properties: regex: description: Regex defines the regular expression to match the @@ -2070,7 +2100,7 @@ spec: openAPIV3Schema: description: |- MiddlewareTCP is the CRD implementation of a Traefik TCP middleware. - More info: https://doc.traefik.io/traefik/v3.4/middlewares/overview/ + More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/tcp/middlewares/overview/ properties: apiVersion: description: |- @@ -2107,7 +2137,7 @@ spec: description: |- IPAllowList defines the IPAllowList middleware configuration. This middleware accepts/refuses connections based on the client IP. - More info: https://doc.traefik.io/traefik/v3.4/middlewares/tcp/ipallowlist/ + More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/tcp/middlewares/ipallowlist/ properties: sourceRange: description: SourceRange defines the allowed IPs (or ranges of @@ -2121,7 +2151,7 @@ spec: IPWhiteList defines the IPWhiteList middleware configuration. This middleware accepts/refuses connections based on the client IP. Deprecated: please use IPAllowList instead. - More info: https://doc.traefik.io/traefik/v3.4/middlewares/tcp/ipwhitelist/ + More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/tcp/middlewares/ipwhitelist/ properties: sourceRange: description: SourceRange defines the allowed IPs (or ranges of @@ -2160,7 +2190,7 @@ spec: 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/v3.4/routing/services/#serverstransport_1 + More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/load-balancing/serverstransport/ properties: apiVersion: description: |- @@ -2246,7 +2276,7 @@ spec: maxIdleConnsPerHost: description: MaxIdleConnsPerHost controls the maximum idle (keep-alive) to keep per-host. - minimum: 0 + minimum: -1 type: integer peerCertURI: description: PeerCertURI defines the peer cert URI used to match against @@ -2329,7 +2359,7 @@ spec: ServersTransportTCP is the CRD implementation of a TCPServersTransport. If no tcpServersTransport is specified, a default one named default@internal will be used. The default@internal tcpServersTransport can be configured in the static configuration. - More info: https://doc.traefik.io/traefik/v3.4/routing/services/#serverstransport_3 + More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/tcp/serverstransport/ properties: apiVersion: description: |- @@ -2371,6 +2401,15 @@ spec: to a backend server can be established. pattern: ^([0-9]+(ns|us|µs|ms|s|m|h)?)+$ x-kubernetes-int-or-string: true + proxyProtocol: + description: ProxyProtocol holds the PROXY Protocol configuration. + properties: + version: + description: Version defines the PROXY Protocol version to use. + maximum: 2 + minimum: 1 + type: integer + type: object terminationDelay: anyOf: - type: integer @@ -2474,7 +2513,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/v3.4/https/tls/#tls-options + More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/tls/tls-certificates/#certificates-stores#tls-options properties: apiVersion: description: |- @@ -2499,14 +2538,14 @@ 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/v3.4/https/tls/#alpn-protocols + More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/tls/tls-certificates/#certificates-stores#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/v3.4/https/tls/#cipher-suites + More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/tls/tls-certificates/#certificates-stores#cipher-suites items: type: string type: array @@ -2533,8 +2572,8 @@ spec: type: object curvePreferences: description: |- - CurvePreferences defines the preferred elliptic curves in a specific order. - More info: https://doc.traefik.io/traefik/v3.4/https/tls/#curve-preferences + CurvePreferences defines the preferred elliptic curves. + More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/tls/tls-certificates/#certificates-stores#curve-preferences items: type: string type: array @@ -2594,7 +2633,7 @@ spec: 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/v3.4/https/tls/#certificates-stores + More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/tls/tls-certificates/#certificates-stores#certificates-stores properties: apiVersion: description: |- @@ -2692,7 +2731,7 @@ spec: TraefikService object allows to: - Apply weight to Services on load-balancing - Mirror traffic on services - More info: https://doc.traefik.io/traefik/v3.4/routing/providers/kubernetes-crd/#kind-traefikservice + More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/kubernetes/crd/http/traefikservice/ properties: apiVersion: description: |- @@ -2741,7 +2780,7 @@ spec: - type: integer - type: string description: |- - Interval defines the frequency of the health check calls. + Interval defines the frequency of the health check calls for healthy targets. Default: 30s x-kubernetes-int-or-string: true method: @@ -2777,6 +2816,15 @@ spec: Timeout defines the maximum duration Traefik will wait for a health check request before considering the server unhealthy. Default: 5s x-kubernetes-int-or-string: true + unhealthyInterval: + anyOf: + - type: integer + - type: string + description: |- + UnhealthyInterval defines the frequency of the health check calls for unhealthy targets. + When UnhealthyInterval is not defined, it defaults to the Interval value. + Default: 30s + x-kubernetes-int-or-string: true type: object kind: description: Kind defines the kind of the Service. @@ -2826,7 +2874,7 @@ spec: - type: integer - type: string description: |- - Interval defines the frequency of the health check calls. + Interval defines the frequency of the health check calls for healthy targets. Default: 30s x-kubernetes-int-or-string: true method: @@ -2862,6 +2910,15 @@ spec: Timeout defines the maximum duration Traefik will wait for a health check request before considering the server unhealthy. Default: 5s x-kubernetes-int-or-string: true + unhealthyInterval: + anyOf: + - type: integer + - type: string + description: |- + UnhealthyInterval defines the frequency of the health check calls for unhealthy targets. + When UnhealthyInterval is not defined, it defaults to the Interval value. + Default: 30s + x-kubernetes-int-or-string: true type: object kind: description: Kind defines the kind of the Service. @@ -2938,7 +2995,7 @@ spec: sticky: description: |- Sticky defines the sticky sessions configuration. - More info: https://doc.traefik.io/traefik/v3.4/routing/services/#sticky-sessions + More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/load-balancing/service/#sticky-sessions properties: cookie: description: Cookie defines the sticky cookie configuration. @@ -3066,7 +3123,7 @@ spec: sticky: description: |- Sticky defines the sticky sessions configuration. - More info: https://doc.traefik.io/traefik/v3.4/routing/services/#sticky-sessions + More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/load-balancing/service/#sticky-sessions properties: cookie: description: Cookie defines the sticky cookie configuration. @@ -3163,7 +3220,7 @@ spec: - type: integer - type: string description: |- - Interval defines the frequency of the health check calls. + Interval defines the frequency of the health check calls for healthy targets. Default: 30s x-kubernetes-int-or-string: true method: @@ -3199,6 +3256,15 @@ spec: Timeout defines the maximum duration Traefik will wait for a health check request before considering the server unhealthy. Default: 5s x-kubernetes-int-or-string: true + unhealthyInterval: + anyOf: + - type: integer + - type: string + description: |- + UnhealthyInterval defines the frequency of the health check calls for unhealthy targets. + When UnhealthyInterval is not defined, it defaults to the Interval value. + Default: 30s + x-kubernetes-int-or-string: true type: object kind: description: Kind defines the kind of the Service. @@ -3270,7 +3336,7 @@ spec: sticky: description: |- Sticky defines the sticky sessions configuration. - More info: https://doc.traefik.io/traefik/v3.4/routing/services/#sticky-sessions + More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/load-balancing/service/#sticky-sessions properties: cookie: description: Cookie defines the sticky cookie configuration. @@ -3338,7 +3404,7 @@ spec: sticky: description: |- Sticky defines whether sticky sessions are enabled. - More info: https://doc.traefik.io/traefik/v3.4/routing/providers/kubernetes-crd/#stickiness-and-load-balancing + More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/kubernetes/crd/http/traefikservice/#stickiness-and-load-balancing properties: cookie: description: Cookie defines the sticky cookie configuration. diff --git a/integration/fixtures/ocsp/ca.crt b/integration/fixtures/ocsp/ca.crt new file mode 100644 index 000000000..bc620de75 --- /dev/null +++ b/integration/fixtures/ocsp/ca.crt @@ -0,0 +1,11 @@ +-----BEGIN CERTIFICATE----- +MIIBhjCCASygAwIBAgIBATAKBggqhkjOPQQDAjASMRAwDgYDVQQDEwdUZXN0IENB +MB4XDTI1MDQyNDEzNTIzOFoXDTM1MDQyMjEzNTIzOFowEjEQMA4GA1UEAxMHVGVz +dCBDQTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABPp8MoNUBbUxp3jW6FcDH+lg +Zft1SIpnGjkMVjLSbW9EzmRQ/oMRHQqJvE7wJbwDs/JUTigRtfZL0vOojnhHcPej +czBxMA4GA1UdDwEB/wQEAwIBBjASBgNVHRMBAf8ECDAGAQH/AgEBMB0GA1UdDgQW +BBQXRlWLK295lmDy+931a4Ha8XVNNjAsBggrBgEFBQcBAQQgMB4wHAYIKwYBBQUH +MAGGEG9jc3AuZXhhbXBsZS5jb20wCgYIKoZIzj0EAwIDSAAwRQIgYH6lnce9jxcp +YIVhY4z55rnOKXqaI/5rUQKwjJ3dRsUCIQDThtkFgOPT/67xOYCTCEVSMSTwh2Gq +jbeucU+4c/InVg== +-----END CERTIFICATE----- diff --git a/integration/fixtures/ocsp/ca.key b/integration/fixtures/ocsp/ca.key new file mode 100644 index 000000000..ac7b3aa04 --- /dev/null +++ b/integration/fixtures/ocsp/ca.key @@ -0,0 +1,5 @@ +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIGV6FPfHeA42xfjVtpnyATG6tKCCu0QoY0OlBR/0xn2toAoGCCqGSM49 +AwEHoUQDQgAE+nwyg1QFtTGneNboVwMf6WBl+3VIimcaOQxWMtJtb0TOZFD+gxEd +Com8TvAlvAOz8lROKBG19kvS86iOeEdw9w== +-----END EC PRIVATE KEY----- diff --git a/integration/fixtures/ocsp/default.crt b/integration/fixtures/ocsp/default.crt new file mode 100644 index 000000000..85a651f66 --- /dev/null +++ b/integration/fixtures/ocsp/default.crt @@ -0,0 +1,11 @@ +-----BEGIN CERTIFICATE----- +MIIBjzCCATWgAwIBAgIIGDlFgswljYAwCgYIKoZIzj0EAwIwEjEQMA4GA1UEAxMH +VGVzdCBDQTAeFw0yNTA0MjQxMzUyMzhaFw0yNjA0MjQxMzUyMzhaMBgxFjAUBgNV +BAMTDWRlZmF1bHQubG9jYWwwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAQGABZ/ +zezTMQBwmmw3aifU0OkDQ4ZzxGG7dR93svJPgYnP7TpBVtPrxy0WgVZbbCHv0Srl +PlpO9rFkKf3D4E6Qo28wbTAOBgNVHQ8BAf8EBAMCBaAwDAYDVR0TAQH/BAIwADAf +BgNVHSMEGDAWgBQXRlWLK295lmDy+931a4Ha8XVNNjAsBggrBgEFBQcBAQQgMB4w +HAYIKwYBBQUHMAGGEG9jc3AuZXhhbXBsZS5jb20wCgYIKoZIzj0EAwIDSAAwRQIh +AJMF7RkU0BtNZlHf//PPgpPfDJybnYMIoX1Ek4I8JZ+QAiBpxjzeFE9jwqcJnx5X +KnOJMbgfvJliZZgVSuXBbulzAA== +-----END CERTIFICATE----- diff --git a/integration/fixtures/ocsp/default.key b/integration/fixtures/ocsp/default.key new file mode 100644 index 000000000..96471e10d --- /dev/null +++ b/integration/fixtures/ocsp/default.key @@ -0,0 +1,5 @@ +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIO4UluA82wXVkaVH0m6oFGWyC8mzVcc7H9MI0ltXgkNuoAoGCCqGSM49 +AwEHoUQDQgAEBgAWf83s0zEAcJpsN2on1NDpA0OGc8Rhu3Ufd7LyT4GJz+06QVbT +68ctFoFWW2wh79Eq5T5aTvaxZCn9w+BOkA== +-----END EC PRIVATE KEY----- diff --git a/integration/fixtures/ocsp/gencert.go b/integration/fixtures/ocsp/gencert.go new file mode 100644 index 000000000..1c1277641 --- /dev/null +++ b/integration/fixtures/ocsp/gencert.go @@ -0,0 +1,100 @@ +package main + +import ( + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" + "math/big" + "os" + "time" +) + +func main() { + // generate CA + caKey, caCert := generateCA("Test CA") + saveKeyAndCert("integration/fixtures/ocsp/ca.key", "integration/fixtures/ocsp/ca.crt", caKey, caCert) + + // server certificate + serverKey, serverCert := generateCert("server.local", caKey, caCert) + saveKeyAndCert("integration/fixtures/ocsp/server.key", "integration/fixtures/ocsp/server.crt", serverKey, serverCert) + + // default certificate + defaultKey, defaultCert := generateCert("default.local", caKey, caCert) + saveKeyAndCert("integration/fixtures/ocsp/default.key", "integration/fixtures/ocsp/default.crt", defaultKey, defaultCert) +} + +func generateCA(commonName string) (*ecdsa.PrivateKey, *x509.Certificate) { + // generate a private key for the CA + caKey, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + + // create a self-signed CA certificate + caTemplate := &x509.Certificate{ + SerialNumber: big.NewInt(1), + Subject: pkix.Name{ + CommonName: commonName, + }, + NotBefore: time.Now(), + NotAfter: time.Now().Add(10 * 365 * 24 * time.Hour), // 10 ans + KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign, + BasicConstraintsValid: true, + IsCA: true, + MaxPathLen: 1, + OCSPServer: []string{"ocsp.example.com"}, + } + + caCertDER, _ := x509.CreateCertificate(rand.Reader, caTemplate, caTemplate, &caKey.PublicKey, caKey) + caCert, _ := x509.ParseCertificate(caCertDER) + + return caKey, caCert +} + +func generateCert(commonName string, caKey *ecdsa.PrivateKey, caCert *x509.Certificate) (*ecdsa.PrivateKey, *x509.Certificate) { + // create a private key for the certificate + certKey, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + + // create a certificate signed by the CA + certTemplate := &x509.Certificate{ + SerialNumber: big.NewInt(time.Now().UnixNano()), + Subject: pkix.Name{ + CommonName: commonName, + }, + NotBefore: time.Now(), + NotAfter: time.Now().Add(1 * 365 * 24 * time.Hour), // 1 an + KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment, + BasicConstraintsValid: true, + OCSPServer: []string{"ocsp.example.com"}, + } + + certDER, _ := x509.CreateCertificate(rand.Reader, certTemplate, caCert, &certKey.PublicKey, caKey) + cert, _ := x509.ParseCertificate(certDER) + + return certKey, cert +} + +func saveKeyAndCert(keyFile, certFile string, key *ecdsa.PrivateKey, cert *x509.Certificate) { + // save the private key + keyOut, _ := os.Create(keyFile) + defer keyOut.Close() + + // Marshal the private key to ASN.1 DER format + privateKey, err := x509.MarshalECPrivateKey(key) + if err != nil { + panic(err) + } + + err = pem.Encode(keyOut, &pem.Block{Type: "EC PRIVATE KEY", Bytes: privateKey}) + if err != nil { + panic(err) + } + + // save the certificate + certOut, _ := os.Create(certFile) + defer certOut.Close() + err = pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: cert.Raw}) + if err != nil { + panic(err) + } +} diff --git a/integration/fixtures/ocsp/server.crt b/integration/fixtures/ocsp/server.crt new file mode 100644 index 000000000..9e05c6e5c --- /dev/null +++ b/integration/fixtures/ocsp/server.crt @@ -0,0 +1,11 @@ +-----BEGIN CERTIFICATE----- +MIIBjjCCATSgAwIBAgIIGDlFgswgB3AwCgYIKoZIzj0EAwIwEjEQMA4GA1UEAxMH +VGVzdCBDQTAeFw0yNTA0MjQxMzUyMzhaFw0yNjA0MjQxMzUyMzhaMBcxFTATBgNV +BAMTDHNlcnZlci5sb2NhbDBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABHpjZoVk +Qh15gTa26KMJfvzfVgGHGicUDg1UYppKAMY83rxSXqRHcVFAFRqWDTgCQRy6hPq+ +6p5OwBziC2X/SOejbzBtMA4GA1UdDwEB/wQEAwIFoDAMBgNVHRMBAf8EAjAAMB8G +A1UdIwQYMBaAFBdGVYsrb3mWYPL73fVrgdrxdU02MCwGCCsGAQUFBwEBBCAwHjAc +BggrBgEFBQcwAYYQb2NzcC5leGFtcGxlLmNvbTAKBggqhkjOPQQDAgNIADBFAiEA +mp5LQixMUFh5h8yF1EtFsi4MKrO+dzD68TqIhq1rKjUCIEbB++M8qO4gtqjv8d06 +AzSLTEfgNCmM574JI46YAKVx +-----END CERTIFICATE----- diff --git a/integration/fixtures/ocsp/server.key b/integration/fixtures/ocsp/server.key new file mode 100644 index 000000000..3cd45737a --- /dev/null +++ b/integration/fixtures/ocsp/server.key @@ -0,0 +1,5 @@ +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIFpVKKKxvw6cZe7hwRLHgXIsWiJYUQ66PKzO6iXINUH0oAoGCCqGSM49 +AwEHoUQDQgAEemNmhWRCHXmBNrboowl+/N9WAYcaJxQODVRimkoAxjzevFJepEdx +UUAVGpYNOAJBHLqE+r7qnk7AHOILZf9I5w== +-----END EC PRIVATE KEY----- diff --git a/integration/fixtures/ocsp/simple.toml b/integration/fixtures/ocsp/simple.toml new file mode 100644 index 000000000..13222b694 --- /dev/null +++ b/integration/fixtures/ocsp/simple.toml @@ -0,0 +1,27 @@ +[global] + checkNewVersion = false + sendAnonymousUsage = false + +[entryPoints] + [entryPoints.web] + address = ":8000" + +[providers.file] + filename = "{{ .SelfFilename }}" + +[ocsp.responderOverrides] + ocsp.example.com = "{{ .ResponderURL }}" + +[log] + level="debug" + +## dynamic configuration ## + +[[tls.certificates]] + certFile = "fixtures/ocsp/server.crt" + keyFile = "fixtures/ocsp/server.key" + +[tls.stores] + [tls.stores.default.defaultCertificate] + certFile = "fixtures/ocsp/default.crt" + keyFile = "fixtures/ocsp/default.key" diff --git a/integration/fixtures/tracing/simple-opentelemetry.toml b/integration/fixtures/tracing/simple-opentelemetry.toml index a8643ebd7..61aaebfa8 100644 --- a/integration/fixtures/tracing/simple-opentelemetry.toml +++ b/integration/fixtures/tracing/simple-opentelemetry.toml @@ -14,6 +14,10 @@ [entryPoints] [entryPoints.web] address = ":8000" + [entryPoints.web.observability] + traceVerbosity = "detailed" + [entryPoints.web-minimal] + address = ":8001" # Adding metrics to confirm that there is no wrong interaction with tracing. [metrics] @@ -44,9 +48,13 @@ ## dynamic configuration ## [http.routers] + [http.routers.routerBasicMinimal] + Service = "service0" + Rule = "Path(`/basic-minimal`)" + [http.routers.routerBasicMinimal.observability] + traceVerbosity = "minimal" [http.routers.router0] Service = "service0" - Middlewares = [] Rule = "Path(`/basic`)" [http.routers.router1] Service = "service1" diff --git a/integration/grpc_test.go b/integration/grpc_test.go index 856b1980d..a624310f2 100644 --- a/integration/grpc_test.go +++ b/integration/grpc_test.go @@ -108,7 +108,9 @@ func getHelloClientGRPCh2c() (helloworld.GreeterClient, func() error, error) { return helloworld.NewGreeterClient(conn), conn.Close, nil } -func callHelloClientGRPC(name string, secure bool) (string, error) { +func callHelloClientGRPC(t *testing.T, name string, secure bool) (string, error) { + t.Helper() + var client helloworld.GreeterClient var closer func() error var err error @@ -123,24 +125,26 @@ func callHelloClientGRPC(name string, secure bool) (string, error) { if err != nil { return "", err } - r, err := client.SayHello(context.Background(), &helloworld.HelloRequest{Name: name}) + r, err := client.SayHello(t.Context(), &helloworld.HelloRequest{Name: name}) if err != nil { return "", err } return r.GetMessage(), nil } -func callStreamExampleClientGRPC() (helloworld.Greeter_StreamExampleClient, func() error, error) { +func callStreamExampleClientGRPC(t *testing.T) (helloworld.Greeter_StreamExampleClient, func() error, error) { + t.Helper() + client, closer, err := getHelloClientGRPC() if err != nil { return nil, closer, err } - t, err := client.StreamExample(context.Background(), &helloworld.StreamExampleRequest{}) + s, err := client.StreamExample(t.Context(), &helloworld.StreamExampleRequest{}) if err != nil { return nil, closer, err } - return t, closer, nil + return s, closer, nil } func (s *GRPCSuite) TestGRPC() { @@ -172,7 +176,7 @@ func (s *GRPCSuite) TestGRPC() { var response string err = try.Do(1*time.Second, func() error { - response, err = callHelloClientGRPC("World", true) + response, err = callHelloClientGRPC(s.T(), "World", true) return err }) assert.NoError(s.T(), err) @@ -204,7 +208,7 @@ func (s *GRPCSuite) TestGRPCh2c() { var response string err = try.Do(1*time.Second, func() error { - response, err = callHelloClientGRPC("World", false) + response, err = callHelloClientGRPC(s.T(), "World", false) return err }) assert.NoError(s.T(), err) @@ -240,7 +244,7 @@ func (s *GRPCSuite) TestGRPCh2cTermination() { var response string err = try.Do(1*time.Second, func() error { - response, err = callHelloClientGRPC("World", true) + response, err = callHelloClientGRPC(s.T(), "World", true) return err }) assert.NoError(s.T(), err) @@ -276,7 +280,7 @@ func (s *GRPCSuite) TestGRPCInsecure() { var response string err = try.Do(1*time.Second, func() error { - response, err = callHelloClientGRPC("World", true) + response, err = callHelloClientGRPC(s.T(), "World", true) return err }) assert.NoError(s.T(), err) @@ -314,7 +318,7 @@ func (s *GRPCSuite) TestGRPCBuffer() { err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("Host(`127.0.0.1`)")) assert.NoError(s.T(), err) var client helloworld.Greeter_StreamExampleClient - client, closer, err := callStreamExampleClientGRPC() + client, closer, err := callStreamExampleClientGRPC(s.T()) defer func() { _ = closer() }() assert.NoError(s.T(), err) @@ -367,7 +371,7 @@ func (s *GRPCSuite) TestGRPCBufferWithFlushInterval() { assert.NoError(s.T(), err) var client helloworld.Greeter_StreamExampleClient - client, closer, err := callStreamExampleClientGRPC() + client, closer, err := callStreamExampleClientGRPC(s.T()) defer func() { _ = closer() stopStreamExample <- true @@ -422,7 +426,7 @@ func (s *GRPCSuite) TestGRPCWithRetry() { var response string err = try.Do(1*time.Second, func() error { - response, err = callHelloClientGRPC("World", true) + response, err = callHelloClientGRPC(s.T(), "World", true) return err }) assert.NoError(s.T(), err) diff --git a/integration/integration_test.go b/integration/integration_test.go index 039f1aae4..bb0fc2bfd 100644 --- a/integration/integration_test.go +++ b/integration/integration_test.go @@ -106,7 +106,7 @@ func (s *BaseSuite) displayTraefikLogFile(path string) { } func (s *BaseSuite) SetupSuite() { - if isDockerDesktop(context.Background(), s.T()) { + if isDockerDesktop(s.T()) { _, err := os.Stat(tailscaleSecretFilePath) require.NoError(s.T(), err, "Tailscale need to be configured when running integration tests with Docker Desktop: (https://doc.traefik.io/traefik/v2.11/contributing/building-testing/#testing)") } @@ -116,7 +116,6 @@ func (s *BaseSuite) SetupSuite() { // TODO // stdlog.SetOutput(log.Logger) - ctx := context.Background() // Create docker network // docker network create traefik-test-network --driver bridge --subnet 172.31.42.0/24 var opts []network.NetworkCustomizer @@ -129,18 +128,18 @@ func (s *BaseSuite) SetupSuite() { }, }, })) - dockerNetwork, err := network.New(ctx, opts...) + dockerNetwork, err := network.New(s.T().Context(), opts...) require.NoError(s.T(), err) s.network = dockerNetwork s.hostIP = "172.31.42.1" - if isDockerDesktop(ctx, s.T()) { - s.hostIP = getDockerDesktopHostIP(ctx, s.T()) + if isDockerDesktop(s.T()) { + s.hostIP = getDockerDesktopHostIP(s.T()) s.setupVPN(tailscaleSecretFilePath) } } -func getDockerDesktopHostIP(ctx context.Context, t *testing.T) string { +func getDockerDesktopHostIP(t *testing.T) string { t.Helper() req := testcontainers.ContainerRequest{ @@ -151,13 +150,13 @@ func getDockerDesktopHostIP(ctx context.Context, t *testing.T) string { Cmd: []string{"getent", "hosts", "host.docker.internal"}, } - con, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ + con, err := testcontainers.GenericContainer(t.Context(), testcontainers.GenericContainerRequest{ ContainerRequest: req, Started: true, }) require.NoError(t, err) - closer, err := con.Logs(ctx) + closer, err := con.Logs(t.Context()) require.NoError(t, err) all, err := io.ReadAll(closer) @@ -170,15 +169,15 @@ func getDockerDesktopHostIP(ctx context.Context, t *testing.T) string { return matches[0] } -func isDockerDesktop(ctx context.Context, t *testing.T) bool { +func isDockerDesktop(t *testing.T) bool { t.Helper() - cli, err := testcontainers.NewDockerClientWithOpts(ctx) + cli, err := testcontainers.NewDockerClientWithOpts(t.Context()) if err != nil { t.Fatalf("failed to create docker client: %s", err) } - info, err := cli.Info(ctx) + info, err := cli.Info(t.Context()) if err != nil { t.Fatalf("failed to get docker info: %s", err) } @@ -191,7 +190,7 @@ func (s *BaseSuite) TearDownSuite() { err := try.Do(5*time.Second, func() error { if s.network != nil { - err := s.network.Remove(context.Background()) + err := s.network.Remove(s.T().Context()) if err != nil { return err } @@ -218,7 +217,7 @@ func (s *BaseSuite) createComposeProject(name string) { s.containers = map[string]testcontainers.Container{} } - ctx := context.Background() + ctx := s.T().Context() for id, containerConfig := range composeConfigData.Services { var mounts []mount.Mount @@ -273,7 +272,7 @@ func (s *BaseSuite) createContainer(ctx context.Context, containerConfig compose if containerConfig.CapAdd != nil { config.CapAdd = containerConfig.CapAdd } - if !isDockerDesktop(ctx, s.T()) { + if !isDockerDesktop(s.T()) { config.ExtraHosts = append(config.ExtraHosts, "host.docker.internal:"+s.hostIP) } config.Mounts = mounts @@ -292,7 +291,7 @@ func (s *BaseSuite) createContainer(ctx context.Context, containerConfig compose func (s *BaseSuite) composeUp(services ...string) { for name, con := range s.containers { if len(services) == 0 || slices.Contains(services, name) { - err := con.Start(context.Background()) + err := con.Start(s.T().Context()) require.NoError(s.T(), err) } } @@ -303,7 +302,7 @@ func (s *BaseSuite) composeStop(services ...string) { for name, con := range s.containers { if len(services) == 0 || slices.Contains(services, name) { timeout := 10 * time.Second - err := con.Stop(context.Background(), &timeout) + err := con.Stop(s.T().Context(), &timeout) require.NoError(s.T(), err) } } @@ -312,7 +311,7 @@ func (s *BaseSuite) composeStop(services ...string) { // composeDown stops all compose project services and removes the corresponding containers. func (s *BaseSuite) composeDown() { for _, c := range s.containers { - err := c.Terminate(context.Background()) + err := c.Terminate(s.T().Context()) require.NoError(s.T(), err) } s.containers = map[string]testcontainers.Container{} @@ -383,7 +382,7 @@ func (s *BaseSuite) displayLogK3S() { func (s *BaseSuite) displayLogCompose() { for name, ctn := range s.containers { - readCloser, err := ctn.Logs(context.Background()) + readCloser, err := ctn.Logs(s.T().Context()) require.NoError(s.T(), err) for { b := make([]byte, 1024) @@ -451,7 +450,7 @@ func (s *BaseSuite) getComposeServiceIP(name string) string { if !ok { return "" } - ip, err := container.ContainerIP(context.Background()) + ip, err := container.ContainerIP(s.T().Context()) if err != nil { return "" } @@ -501,7 +500,7 @@ func (s *BaseSuite) setupVPN(keyFile string) { func (s *BaseSuite) composeExec(service string, args ...string) string { require.Contains(s.T(), s.containers, service) - _, reader, err := s.containers[service].Exec(context.Background(), args) + _, reader, err := s.containers[service].Exec(s.T().Context(), args) require.NoError(s.T(), err) content, err := io.ReadAll(reader) diff --git a/integration/k8s_conformance_test.go b/integration/k8s_conformance_test.go index 959443be6..1e03f223b 100644 --- a/integration/k8s_conformance_test.go +++ b/integration/k8s_conformance_test.go @@ -1,7 +1,6 @@ package integration import ( - "context" "fmt" "io" "io/fs" @@ -74,7 +73,7 @@ func (s *K8sConformanceSuite) SetupSuite() { s.T().Fatal(err) } - ctx := context.Background() + ctx := s.T().Context() // Ensure image is available locally. images, err := provider.ListImages(ctx) @@ -90,7 +89,7 @@ func (s *K8sConformanceSuite) SetupSuite() { s.k3sContainer, err = k3s.Run(ctx, k3sImage, - k3s.WithManifest("./fixtures/k8s-conformance/00-experimental-v1.2.1.yml"), + k3s.WithManifest("./fixtures/k8s-conformance/00-experimental-v1.3.0.yml"), k3s.WithManifest("./fixtures/k8s-conformance/01-rbac.yml"), k3s.WithManifest("./fixtures/k8s-conformance/02-traefik.yml"), network.WithNetwork(nil, s.network), @@ -146,7 +145,7 @@ func (s *K8sConformanceSuite) SetupSuite() { } func (s *K8sConformanceSuite) TearDownSuite() { - ctx := context.Background() + ctx := s.T().Context() if s.T().Failed() || *showLog { k3sLogs, err := s.k3sContainer.Logs(ctx) @@ -173,7 +172,7 @@ func (s *K8sConformanceSuite) TearDownSuite() { func (s *K8sConformanceSuite) TestK8sGatewayAPIConformance() { // Wait for traefik to start - k3sContainerIP, err := s.k3sContainer.ContainerIP(context.Background()) + k3sContainerIP, err := s.k3sContainer.ContainerIP(s.T().Context()) require.NoError(s.T(), err) err = try.GetRequest("http://"+k3sContainerIP+":9000/api/entrypoints", 10*time.Second, try.BodyContains(`"name":"web"`)) diff --git a/integration/redis_sentinel_test.go b/integration/redis_sentinel_test.go index e229ceb60..542a63728 100644 --- a/integration/redis_sentinel_test.go +++ b/integration/redis_sentinel_test.go @@ -2,7 +2,6 @@ package integration import ( "bytes" - "context" "encoding/json" "fmt" "net" @@ -51,7 +50,7 @@ func (s *RedisSentinelSuite) SetupSuite() { net.JoinHostPort(s.getComposeServiceIP("sentinel3"), "26379"), } kv, err := valkeyrie.NewStore( - context.Background(), + s.T().Context(), redis.StoreName, s.redisEndpoints, &redis.Config{ @@ -157,7 +156,7 @@ func (s *RedisSentinelSuite) TestSentinelConfiguration() { } for k, v := range data { - err := s.kvClient.Put(context.Background(), k, []byte(v), nil) + err := s.kvClient.Put(s.T().Context(), k, []byte(v), nil) require.NoError(s.T(), err) } diff --git a/integration/redis_test.go b/integration/redis_test.go index 6685b7645..e1ed10899 100644 --- a/integration/redis_test.go +++ b/integration/redis_test.go @@ -2,7 +2,6 @@ package integration import ( "bytes" - "context" "encoding/json" "net" "net/http" @@ -43,7 +42,7 @@ func (s *RedisSuite) SetupSuite() { s.redisEndpoints = append(s.redisEndpoints, net.JoinHostPort(s.getComposeServiceIP("redis"), "6379")) kv, err := valkeyrie.NewStore( - context.Background(), + s.T().Context(), redis.StoreName, s.redisEndpoints, &redis.Config{}, @@ -112,7 +111,7 @@ func (s *RedisSuite) TestSimpleConfiguration() { } for k, v := range data { - err := s.kvClient.Put(context.Background(), k, []byte(v), nil) + err := s.kvClient.Put(s.T().Context(), k, []byte(v), nil) require.NoError(s.T(), err) } diff --git a/integration/resources/compose/access_log.yml b/integration/resources/compose/access_log.yml index c5be8f9e4..1447f4cba 100644 --- a/integration/resources/compose/access_log.yml +++ b/integration/resources/compose/access_log.yml @@ -1,4 +1,3 @@ -version: "3.8" services: server0: image: traefik/whoami @@ -102,13 +101,3 @@ services: traefik.http.routers.ping.entryPoints: ping traefik.http.routers.ping.rule: PathPrefix(`/ping`) traefik.http.routers.ping.service: ping@internal - - traefik.http.routers.ping-error.entryPoints: ping - traefik.http.routers.ping-error.rule: PathPrefix(`/ping-error`) - traefik.http.routers.ping-error.middlewares: errors, basicauth - traefik.http.routers.ping-error.service: ping@internal - traefik.http.middlewares.basicauth.basicauth.users: "test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/" - traefik.http.middlewares.errors.errors.status: 401 - traefik.http.middlewares.errors.errors.service: service3 - traefik.http.middlewares.errors.errors.query: / - traefik.http.services.service3.loadbalancer.server.port: 80 diff --git a/integration/resources/compose/allowlist.yml b/integration/resources/compose/allowlist.yml index 0fd241322..f5cd10680 100644 --- a/integration/resources/compose/allowlist.yml +++ b/integration/resources/compose/allowlist.yml @@ -1,4 +1,3 @@ -version: "3.8" services: noOverrideAllowlist: image: traefik/whoami diff --git a/integration/resources/compose/base.yml b/integration/resources/compose/base.yml index 2d6380051..54a5f2f8e 100644 --- a/integration/resources/compose/base.yml +++ b/integration/resources/compose/base.yml @@ -1,4 +1,3 @@ -version: "3.8" services: whoami1: image: traefik/whoami diff --git a/integration/resources/compose/consul.yml b/integration/resources/compose/consul.yml index 041ba0b45..0d1f343ba 100644 --- a/integration/resources/compose/consul.yml +++ b/integration/resources/compose/consul.yml @@ -1,4 +1,3 @@ -version: "3.8" services: consul: image: consul:1.6 diff --git a/integration/resources/compose/consul_catalog.yml b/integration/resources/compose/consul_catalog.yml index 6a0fd279c..949333a39 100644 --- a/integration/resources/compose/consul_catalog.yml +++ b/integration/resources/compose/consul_catalog.yml @@ -1,4 +1,3 @@ -version: "3.8" services: consul: image: consul:1.6.2 diff --git a/integration/resources/compose/docker.yml b/integration/resources/compose/docker.yml index b16571a4b..7ee4492cf 100644 --- a/integration/resources/compose/docker.yml +++ b/integration/resources/compose/docker.yml @@ -1,4 +1,3 @@ -version: "3.8" services: simple: image: swarm:1.0.0 diff --git a/integration/resources/compose/error_pages.yml b/integration/resources/compose/error_pages.yml index 03cc13f80..3f2c40e6b 100644 --- a/integration/resources/compose/error_pages.yml +++ b/integration/resources/compose/error_pages.yml @@ -1,4 +1,3 @@ -version: "3.8" services: nginx1: image: nginx:1.25.3-alpine3.18 diff --git a/integration/resources/compose/etcd.yml b/integration/resources/compose/etcd.yml index 6a3b34fe9..02529171a 100644 --- a/integration/resources/compose/etcd.yml +++ b/integration/resources/compose/etcd.yml @@ -1,4 +1,3 @@ -version: "3.8" services: etcd: image: quay.io/coreos/etcd:v3.5.14 diff --git a/integration/resources/compose/file.yml b/integration/resources/compose/file.yml index 52e973e53..3e37129b2 100644 --- a/integration/resources/compose/file.yml +++ b/integration/resources/compose/file.yml @@ -1,4 +1,3 @@ -version: "3.8" services: whoami1: image: traefik/whoami diff --git a/integration/resources/compose/healthcheck.yml b/integration/resources/compose/healthcheck.yml index 9419f4bce..ee9be620f 100644 --- a/integration/resources/compose/healthcheck.yml +++ b/integration/resources/compose/healthcheck.yml @@ -1,4 +1,3 @@ -version: "3.8" services: whoami1: image: traefik/whoami diff --git a/integration/resources/compose/hostresolver.yml b/integration/resources/compose/hostresolver.yml index 680962006..424b832ce 100644 --- a/integration/resources/compose/hostresolver.yml +++ b/integration/resources/compose/hostresolver.yml @@ -1,4 +1,3 @@ -version: "3.8" services: server1: image: traefik/whoami diff --git a/integration/resources/compose/k8s.yml b/integration/resources/compose/k8s.yml index f14b7abdb..51e46b0be 100644 --- a/integration/resources/compose/k8s.yml +++ b/integration/resources/compose/k8s.yml @@ -1,4 +1,3 @@ -version: "3.8" services: server: image: rancher/k3s:v1.21.14-k3s1 diff --git a/integration/resources/compose/minimal.yml b/integration/resources/compose/minimal.yml index acb12804e..911b12d38 100644 --- a/integration/resources/compose/minimal.yml +++ b/integration/resources/compose/minimal.yml @@ -1,4 +1,3 @@ -version: "3.8" services: whoami1: image: traefik/whoami diff --git a/integration/resources/compose/pebble.yml b/integration/resources/compose/pebble.yml index f39dc1378..4e9eac58b 100644 --- a/integration/resources/compose/pebble.yml +++ b/integration/resources/compose/pebble.yml @@ -1,4 +1,3 @@ -version: "3.8" services: pebble: image: letsencrypt/pebble:v2.3.1 diff --git a/integration/resources/compose/proxy-protocol.yml b/integration/resources/compose/proxy-protocol.yml index 8fa69a9ba..3d8339330 100644 --- a/integration/resources/compose/proxy-protocol.yml +++ b/integration/resources/compose/proxy-protocol.yml @@ -1,4 +1,3 @@ -version: "3.8" services: whoami: image: traefik/whoami diff --git a/integration/resources/compose/ratelimit.yml b/integration/resources/compose/ratelimit.yml index 5d9d6ec1e..414aff839 100644 --- a/integration/resources/compose/ratelimit.yml +++ b/integration/resources/compose/ratelimit.yml @@ -1,4 +1,3 @@ -version: "3.8" services: whoami1: image: traefik/whoami diff --git a/integration/resources/compose/redis.yml b/integration/resources/compose/redis.yml index 09bbeacad..bc5e657b3 100644 --- a/integration/resources/compose/redis.yml +++ b/integration/resources/compose/redis.yml @@ -1,4 +1,3 @@ -version: "3.8" services: redis: image: redis:5.0 diff --git a/integration/resources/compose/redis_sentinel.yml b/integration/resources/compose/redis_sentinel.yml index 1737c2a1a..3f76aa12f 100644 --- a/integration/resources/compose/redis_sentinel.yml +++ b/integration/resources/compose/redis_sentinel.yml @@ -1,4 +1,3 @@ -version: "3.8" services: master: image: redis diff --git a/integration/resources/compose/reqacceptgrace.yml b/integration/resources/compose/reqacceptgrace.yml index 8fa69a9ba..3d8339330 100644 --- a/integration/resources/compose/reqacceptgrace.yml +++ b/integration/resources/compose/reqacceptgrace.yml @@ -1,4 +1,3 @@ -version: "3.8" services: whoami: image: traefik/whoami diff --git a/integration/resources/compose/rest.yml b/integration/resources/compose/rest.yml index 251568165..fd0dedb04 100644 --- a/integration/resources/compose/rest.yml +++ b/integration/resources/compose/rest.yml @@ -1,4 +1,3 @@ -version: "3.8" services: whoami1: image: traefik/whoami diff --git a/integration/resources/compose/retry.yml b/integration/resources/compose/retry.yml index 8fa69a9ba..3d8339330 100644 --- a/integration/resources/compose/retry.yml +++ b/integration/resources/compose/retry.yml @@ -1,4 +1,3 @@ -version: "3.8" services: whoami: image: traefik/whoami diff --git a/integration/resources/compose/stats.yml b/integration/resources/compose/stats.yml index 599fed311..206fa5187 100644 --- a/integration/resources/compose/stats.yml +++ b/integration/resources/compose/stats.yml @@ -1,4 +1,3 @@ -version: "3.8" services: whoami1: image: traefik/whoami diff --git a/integration/resources/compose/tailscale.yml b/integration/resources/compose/tailscale.yml index dbad56561..70e5d796c 100644 --- a/integration/resources/compose/tailscale.yml +++ b/integration/resources/compose/tailscale.yml @@ -1,4 +1,3 @@ -version: "3.8" services: tailscaled: hostname: traefik-tests-gw # This will become the tailscale device name diff --git a/integration/resources/compose/tcp.yml b/integration/resources/compose/tcp.yml index cd7fc0627..14e2d413c 100644 --- a/integration/resources/compose/tcp.yml +++ b/integration/resources/compose/tcp.yml @@ -1,4 +1,3 @@ -version: "3.8" services: whoami-a: image: traefik/whoamitcp diff --git a/integration/resources/compose/timeout.yml b/integration/resources/compose/timeout.yml index 6e415b9b0..de629c4db 100644 --- a/integration/resources/compose/timeout.yml +++ b/integration/resources/compose/timeout.yml @@ -1,4 +1,3 @@ -version: "3.8" services: timeoutEndpoint: image: yaman/timeout diff --git a/integration/resources/compose/tlsclientheaders.yml b/integration/resources/compose/tlsclientheaders.yml index ef16f5f36..ae03ecbae 100644 --- a/integration/resources/compose/tlsclientheaders.yml +++ b/integration/resources/compose/tlsclientheaders.yml @@ -1,4 +1,3 @@ -version: "3.8" services: whoami: image: traefik/whoami diff --git a/integration/resources/compose/tracing.yml b/integration/resources/compose/tracing.yml index 314dd66dc..8a2a6d514 100644 --- a/integration/resources/compose/tracing.yml +++ b/integration/resources/compose/tracing.yml @@ -1,4 +1,3 @@ -version: "3.8" services: tempo: hostname: tempo diff --git a/integration/resources/compose/udp.yml b/integration/resources/compose/udp.yml index ce2633199..0c001c829 100644 --- a/integration/resources/compose/udp.yml +++ b/integration/resources/compose/udp.yml @@ -1,4 +1,3 @@ -version: "3.8" services: whoami-a: image: traefik/whoamiudp:latest diff --git a/integration/resources/compose/whitelist.yml b/integration/resources/compose/whitelist.yml index 790ce52b7..ee08b935e 100644 --- a/integration/resources/compose/whitelist.yml +++ b/integration/resources/compose/whitelist.yml @@ -1,4 +1,3 @@ -version: "3.8" services: noOverrideWhitelist: image: traefik/whoami diff --git a/integration/resources/compose/zookeeper.yml b/integration/resources/compose/zookeeper.yml index 9861c1437..b086f24f3 100644 --- a/integration/resources/compose/zookeeper.yml +++ b/integration/resources/compose/zookeeper.yml @@ -1,4 +1,3 @@ -version: "3.8" services: zookeeper: image: zookeeper:3.5 diff --git a/integration/simple_test.go b/integration/simple_test.go index 791ee514c..675e21c3d 100644 --- a/integration/simple_test.go +++ b/integration/simple_test.go @@ -3,7 +3,9 @@ package integration import ( "bufio" "bytes" + "crypto" "crypto/rand" + "crypto/tls" "encoding/json" "fmt" "io" @@ -24,6 +26,7 @@ import ( "github.com/stretchr/testify/suite" "github.com/traefik/traefik/v3/integration/try" "github.com/traefik/traefik/v3/pkg/config/dynamic" + "golang.org/x/crypto/ocsp" ) // SimpleSuite tests suite. @@ -1598,6 +1601,132 @@ func (s *SimpleSuite) TestMaxHeaderBytes() { } } +func (s *SimpleSuite) TestSimpleOCSP() { + defaultCert, err := tls.LoadX509KeyPair("fixtures/ocsp/default.crt", "fixtures/ocsp/default.key") + require.NoError(s.T(), err) + + serverCert, err := tls.LoadX509KeyPair("fixtures/ocsp/server.crt", "fixtures/ocsp/server.key") + require.NoError(s.T(), err) + + defaultOCSPResponseTmpl := ocsp.Response{ + SerialNumber: defaultCert.Leaf.SerialNumber, + Status: ocsp.Good, + ThisUpdate: defaultCert.Leaf.NotBefore, + NextUpdate: defaultCert.Leaf.NotAfter, + } + defaultOCSPResponse, err := ocsp.CreateResponse(defaultCert.Leaf, defaultCert.Leaf, defaultOCSPResponseTmpl, defaultCert.PrivateKey.(crypto.Signer)) + require.NoError(s.T(), err) + + serverOCSPResponseTmpl := ocsp.Response{ + SerialNumber: serverCert.Leaf.SerialNumber, + Status: ocsp.Good, + ThisUpdate: serverCert.Leaf.NotBefore, + NextUpdate: serverCert.Leaf.NotAfter, + } + serverOCSPResponse, err := ocsp.CreateResponse(serverCert.Leaf, serverCert.Leaf, serverOCSPResponseTmpl, serverCert.PrivateKey.(crypto.Signer)) + require.NoError(s.T(), err) + + responderCalled := make(chan struct{}) + responder := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + ct := req.Header.Get("Content-Type") + assert.Equal(s.T(), "application/ocsp-request", ct) + + reqBytes, err := io.ReadAll(req.Body) + require.NoError(s.T(), err) + + ocspReq, err := ocsp.ParseRequest(reqBytes) + require.NoError(s.T(), err) + + var ocspResponse []byte + switch ocspReq.SerialNumber.String() { + case defaultCert.Leaf.SerialNumber.String(): + ocspResponse = defaultOCSPResponse + case serverCert.Leaf.SerialNumber.String(): + ocspResponse = serverOCSPResponse + default: + s.T().Fatalf("Unexpected OCSP request for serial number: %s", ocspReq.SerialNumber) + } + + rw.Header().Set("Content-Type", "application/ocsp-response") + + _, err = rw.Write(ocspResponse) + require.NoError(s.T(), err) + + responderCalled <- struct{}{} + })) + s.T().Cleanup(responder.Close) + + file := s.adaptFile("fixtures/ocsp/simple.toml", struct { + ResponderURL string + }{responder.URL}) + + s.traefikCmd(withConfigFile(file)) + + select { + case <-responderCalled: + case <-time.After(5 * time.Second): + s.T().Fatal("OCSP responder was not called") + } + + select { + case <-responderCalled: + case <-time.After(5 * time.Second): + s.T().Fatal("OCSP responder was not called") + } + + // Check that the response is stapled. + + // Create a TLS client configuration that checks for OCSP stapling for the default cert. + var verifyCallCount int + clientConfig := &tls.Config{ + InsecureSkipVerify: true, + ServerName: "unknown", + VerifyConnection: func(state tls.ConnectionState) error { + s.T().Helper() + + verifyCallCount++ + assert.Equal(s.T(), "default.local", state.PeerCertificates[0].Subject.CommonName) + assert.Equal(s.T(), defaultOCSPResponse, state.OCSPResponse) + return nil + }, + } + + // Connect to the server and verify OCSP stapling. + conn, err := tls.DialWithDialer(&net.Dialer{Timeout: 5 * time.Second}, "tcp", "127.0.0.1:8000", clientConfig) + require.NoError(s.T(), err) + + s.T().Cleanup(func() { + _ = conn.Close() + }) + + assert.Equal(s.T(), 1, verifyCallCount) + + // Create a TLS client configuration that checks for OCSP stapling for a cert in the store. + verifyCallCount = 0 + clientConfig = &tls.Config{ + InsecureSkipVerify: true, + ServerName: "server.local", + VerifyConnection: func(state tls.ConnectionState) error { + s.T().Helper() + + verifyCallCount++ + assert.Equal(s.T(), "server.local", state.PeerCertificates[0].Subject.CommonName) + assert.Equal(s.T(), serverOCSPResponse, state.OCSPResponse) + return nil + }, + } + + // Connect to the server and verify OCSP stapling. + conn, err = tls.DialWithDialer(&net.Dialer{Timeout: 5 * time.Second}, "tcp", "127.0.0.1:8000", clientConfig) + require.NoError(s.T(), err) + + s.T().Cleanup(func() { + _ = conn.Close() + }) + + assert.Equal(s.T(), 1, verifyCallCount) +} + func (s *SimpleSuite) TestSanitizePath() { s.createComposeProject("base") diff --git a/integration/testdata/rawdata-consul.json b/integration/testdata/rawdata-consul.json index 90806c2ac..9b27203a7 100644 --- a/integration/testdata/rawdata-consul.json +++ b/integration/testdata/rawdata-consul.json @@ -14,8 +14,9 @@ "tls": {}, "observability": { "accessLogs": true, + "metrics": true, "tracing": true, - "metrics": true + "traceVerbosity": "minimal" }, "status": "enabled", "using": [ @@ -49,8 +50,9 @@ }, "observability": { "accessLogs": true, + "metrics": true, "tracing": true, - "metrics": true + "traceVerbosity": "minimal" }, "status": "enabled", "using": [ @@ -67,8 +69,9 @@ "priority": 9223372036854775806, "observability": { "accessLogs": true, + "metrics": true, "tracing": true, - "metrics": true + "traceVerbosity": "minimal" }, "status": "enabled", "using": [ @@ -89,8 +92,9 @@ "priority": 9223372036854775805, "observability": { "accessLogs": true, + "metrics": true, "tracing": true, - "metrics": true + "traceVerbosity": "minimal" }, "status": "enabled", "using": [ @@ -100,7 +104,13 @@ }, "middlewares": { "compressor@consul": { - "compress": {}, + "compress": { + "encodings": [ + "gzip", + "br", + "zstd" + ] + }, "status": "enabled", "usedBy": [ "Router0@consul" @@ -173,6 +183,7 @@ "mirror@consul": { "mirroring": { "service": "simplesvc", + "mirrorBody": true, "maxBodySize": -1, "mirrors": [ { diff --git a/integration/testdata/rawdata-crd-label-selector.json b/integration/testdata/rawdata-crd-label-selector.json index 8ff23a25a..88219a627 100644 --- a/integration/testdata/rawdata-crd-label-selector.json +++ b/integration/testdata/rawdata-crd-label-selector.json @@ -9,8 +9,9 @@ "priority": 18, "observability": { "accessLogs": true, + "metrics": true, "tracing": true, - "metrics": true + "traceVerbosity": "minimal" }, "status": "enabled", "using": [ @@ -29,8 +30,9 @@ }, "observability": { "accessLogs": true, + "metrics": true, "tracing": true, - "metrics": true + "traceVerbosity": "minimal" }, "status": "enabled", "using": [ diff --git a/integration/testdata/rawdata-crd.json b/integration/testdata/rawdata-crd.json index 334b6c698..9f1b2d33c 100644 --- a/integration/testdata/rawdata-crd.json +++ b/integration/testdata/rawdata-crd.json @@ -9,8 +9,9 @@ "priority": 18, "observability": { "accessLogs": true, + "metrics": true, "tracing": true, - "metrics": true + "traceVerbosity": "minimal" }, "status": "enabled", "using": [ @@ -29,8 +30,9 @@ }, "observability": { "accessLogs": true, + "metrics": true, "tracing": true, - "metrics": true + "traceVerbosity": "minimal" }, "status": "enabled", "using": [ @@ -49,8 +51,9 @@ "priority": 46, "observability": { "accessLogs": true, + "metrics": true, "tracing": true, - "metrics": true + "traceVerbosity": "minimal" }, "status": "enabled", "using": [ @@ -66,8 +69,9 @@ "priority": 38, "observability": { "accessLogs": true, + "metrics": true, "tracing": true, - "metrics": true + "traceVerbosity": "minimal" }, "status": "enabled", "using": [ @@ -83,8 +87,9 @@ "priority": 50, "observability": { "accessLogs": true, + "metrics": true, "tracing": true, - "metrics": true + "traceVerbosity": "minimal" }, "status": "enabled", "using": [ @@ -100,8 +105,9 @@ "priority": 35, "observability": { "accessLogs": true, + "metrics": true, "tracing": true, - "metrics": true + "traceVerbosity": "minimal" }, "error": [ "the service \"other-ns-wrr3@kubernetescrd\" does not exist" diff --git a/integration/testdata/rawdata-etcd.json b/integration/testdata/rawdata-etcd.json index f03d1b67e..360488433 100644 --- a/integration/testdata/rawdata-etcd.json +++ b/integration/testdata/rawdata-etcd.json @@ -14,8 +14,9 @@ "tls": {}, "observability": { "accessLogs": true, + "metrics": true, "tracing": true, - "metrics": true + "traceVerbosity": "minimal" }, "status": "enabled", "using": [ @@ -49,8 +50,9 @@ }, "observability": { "accessLogs": true, + "metrics": true, "tracing": true, - "metrics": true + "traceVerbosity": "minimal" }, "status": "enabled", "using": [ @@ -67,8 +69,9 @@ "priority": 9223372036854775806, "observability": { "accessLogs": true, + "metrics": true, "tracing": true, - "metrics": true + "traceVerbosity": "minimal" }, "status": "enabled", "using": [ @@ -89,8 +92,9 @@ "priority": 9223372036854775805, "observability": { "accessLogs": true, + "metrics": true, "tracing": true, - "metrics": true + "traceVerbosity": "minimal" }, "status": "enabled", "using": [ @@ -100,7 +104,13 @@ }, "middlewares": { "compressor@etcd": { - "compress": {}, + "compress": { + "encodings": [ + "gzip", + "br", + "zstd" + ] + }, "status": "enabled", "usedBy": [ "Router0@etcd" @@ -173,6 +183,7 @@ "mirror@etcd": { "mirroring": { "service": "simplesvc", + "mirrorBody": true, "maxBodySize": -1, "mirrors": [ { diff --git a/integration/testdata/rawdata-gateway.json b/integration/testdata/rawdata-gateway.json index b6206c121..8a6e1bc8c 100644 --- a/integration/testdata/rawdata-gateway.json +++ b/integration/testdata/rawdata-gateway.json @@ -10,8 +10,9 @@ "priority": 9223372036854775806, "observability": { "accessLogs": true, + "metrics": true, "tracing": true, - "metrics": true + "traceVerbosity": "minimal" }, "status": "enabled", "using": [ @@ -32,8 +33,9 @@ "priority": 9223372036854775805, "observability": { "accessLogs": true, + "metrics": true, "tracing": true, - "metrics": true + "traceVerbosity": "minimal" }, "status": "enabled", "using": [ @@ -50,8 +52,9 @@ "priority": 100008, "observability": { "accessLogs": true, + "metrics": true, "tracing": true, - "metrics": true + "traceVerbosity": "minimal" }, "status": "enabled", "using": [ @@ -69,8 +72,9 @@ "tls": {}, "observability": { "accessLogs": true, + "metrics": true, "tracing": true, - "metrics": true + "traceVerbosity": "minimal" }, "status": "enabled", "using": [ diff --git a/integration/testdata/rawdata-ingress-label-selector.json b/integration/testdata/rawdata-ingress-label-selector.json index 23305a847..a4081171c 100644 --- a/integration/testdata/rawdata-ingress-label-selector.json +++ b/integration/testdata/rawdata-ingress-label-selector.json @@ -10,8 +10,9 @@ "priority": 9223372036854775806, "observability": { "accessLogs": true, + "metrics": true, "tracing": true, - "metrics": true + "traceVerbosity": "minimal" }, "status": "enabled", "using": [ @@ -32,8 +33,9 @@ "priority": 9223372036854775805, "observability": { "accessLogs": true, + "metrics": true, "tracing": true, - "metrics": true + "traceVerbosity": "minimal" }, "status": "enabled", "using": [ @@ -49,8 +51,9 @@ "priority": 44, "observability": { "accessLogs": true, + "metrics": true, "tracing": true, - "metrics": true + "traceVerbosity": "minimal" }, "status": "enabled", "using": [ diff --git a/integration/testdata/rawdata-ingress.json b/integration/testdata/rawdata-ingress.json index ba2ad706b..a317099fb 100644 --- a/integration/testdata/rawdata-ingress.json +++ b/integration/testdata/rawdata-ingress.json @@ -10,8 +10,9 @@ "priority": 9223372036854775806, "observability": { "accessLogs": true, + "metrics": true, "tracing": true, - "metrics": true + "traceVerbosity": "minimal" }, "status": "enabled", "using": [ @@ -32,8 +33,9 @@ "priority": 9223372036854775805, "observability": { "accessLogs": true, + "metrics": true, "tracing": true, - "metrics": true + "traceVerbosity": "minimal" }, "status": "enabled", "using": [ @@ -49,8 +51,9 @@ "priority": 50, "observability": { "accessLogs": true, + "metrics": true, "tracing": true, - "metrics": true + "traceVerbosity": "minimal" }, "status": "enabled", "using": [ @@ -66,8 +69,9 @@ "priority": 44, "observability": { "accessLogs": true, + "metrics": true, "tracing": true, - "metrics": true + "traceVerbosity": "minimal" }, "status": "enabled", "using": [ @@ -83,8 +87,9 @@ "priority": 47, "observability": { "accessLogs": true, + "metrics": true, "tracing": true, - "metrics": true + "traceVerbosity": "minimal" }, "status": "enabled", "using": [ @@ -100,8 +105,9 @@ "priority": 47, "observability": { "accessLogs": true, + "metrics": true, "tracing": true, - "metrics": true + "traceVerbosity": "minimal" }, "status": "enabled", "using": [ diff --git a/integration/testdata/rawdata-ingressclass-disabled.json b/integration/testdata/rawdata-ingressclass-disabled.json index a26369282..5f56a981f 100644 --- a/integration/testdata/rawdata-ingressclass-disabled.json +++ b/integration/testdata/rawdata-ingressclass-disabled.json @@ -10,8 +10,9 @@ "priority": 9223372036854775806, "observability": { "accessLogs": true, + "metrics": true, "tracing": true, - "metrics": true + "traceVerbosity": "minimal" }, "status": "enabled", "using": [ @@ -32,8 +33,9 @@ "priority": 9223372036854775805, "observability": { "accessLogs": true, + "metrics": true, "tracing": true, - "metrics": true + "traceVerbosity": "minimal" }, "status": "enabled", "using": [ diff --git a/integration/testdata/rawdata-ingressclass.json b/integration/testdata/rawdata-ingressclass.json index 05649bfa1..c860c8862 100644 --- a/integration/testdata/rawdata-ingressclass.json +++ b/integration/testdata/rawdata-ingressclass.json @@ -10,8 +10,9 @@ "priority": 9223372036854775806, "observability": { "accessLogs": true, + "metrics": true, "tracing": true, - "metrics": true + "traceVerbosity": "minimal" }, "status": "enabled", "using": [ @@ -32,8 +33,9 @@ "priority": 9223372036854775805, "observability": { "accessLogs": true, + "metrics": true, "tracing": true, - "metrics": true + "traceVerbosity": "minimal" }, "status": "enabled", "using": [ @@ -49,8 +51,9 @@ "priority": 47, "observability": { "accessLogs": true, + "metrics": true, "tracing": true, - "metrics": true + "traceVerbosity": "minimal" }, "status": "enabled", "using": [ diff --git a/integration/testdata/rawdata-redis.json b/integration/testdata/rawdata-redis.json index 381d92df5..84e094947 100644 --- a/integration/testdata/rawdata-redis.json +++ b/integration/testdata/rawdata-redis.json @@ -14,8 +14,9 @@ "tls": {}, "observability": { "accessLogs": true, + "metrics": true, "tracing": true, - "metrics": true + "traceVerbosity": "minimal" }, "status": "enabled", "using": [ @@ -49,8 +50,9 @@ }, "observability": { "accessLogs": true, + "metrics": true, "tracing": true, - "metrics": true + "traceVerbosity": "minimal" }, "status": "enabled", "using": [ @@ -67,8 +69,9 @@ "priority": 9223372036854775806, "observability": { "accessLogs": true, + "metrics": true, "tracing": true, - "metrics": true + "traceVerbosity": "minimal" }, "status": "enabled", "using": [ @@ -89,8 +92,9 @@ "priority": 9223372036854775805, "observability": { "accessLogs": true, + "metrics": true, "tracing": true, - "metrics": true + "traceVerbosity": "minimal" }, "status": "enabled", "using": [ @@ -100,7 +104,13 @@ }, "middlewares": { "compressor@redis": { - "compress": {}, + "compress": { + "encodings": [ + "gzip", + "br", + "zstd" + ] + }, "status": "enabled", "usedBy": [ "Router0@redis" @@ -173,6 +183,7 @@ "mirror@redis": { "mirroring": { "service": "simplesvc", + "mirrorBody": true, "maxBodySize": -1, "mirrors": [ { @@ -244,6 +255,7 @@ "url": "http://10.0.1.3:8889" } ], + "strategy": "wrr", "passHostHeader": true, "responseForwarding": { "flushInterval": "100ms" diff --git a/integration/testdata/rawdata-zk.json b/integration/testdata/rawdata-zk.json index 265afbdbc..3401d4c32 100644 --- a/integration/testdata/rawdata-zk.json +++ b/integration/testdata/rawdata-zk.json @@ -14,8 +14,9 @@ "tls": {}, "observability": { "accessLogs": true, + "metrics": true, "tracing": true, - "metrics": true + "traceVerbosity": "minimal" }, "status": "enabled", "using": [ @@ -49,8 +50,9 @@ }, "observability": { "accessLogs": true, + "metrics": true, "tracing": true, - "metrics": true + "traceVerbosity": "minimal" }, "status": "enabled", "using": [ @@ -67,8 +69,9 @@ "priority": 9223372036854775806, "observability": { "accessLogs": true, + "metrics": true, "tracing": true, - "metrics": true + "traceVerbosity": "minimal" }, "status": "enabled", "using": [ @@ -89,8 +92,9 @@ "priority": 9223372036854775805, "observability": { "accessLogs": true, + "metrics": true, "tracing": true, - "metrics": true + "traceVerbosity": "minimal" }, "status": "enabled", "using": [ @@ -100,7 +104,13 @@ }, "middlewares": { "compressor@zookeeper": { - "compress": {}, + "compress": { + "encodings": [ + "gzip", + "br", + "zstd" + ] + }, "status": "enabled", "usedBy": [ "Router0@zookeeper" @@ -173,6 +183,7 @@ "mirror@zookeeper": { "mirroring": { "service": "simplesvc", + "mirrorBody": true, "maxBodySize": -1, "mirrors": [ { diff --git a/integration/tracing_test.go b/integration/tracing_test.go index d44ed0d0b..71e0b1b36 100644 --- a/integration/tracing_test.go +++ b/integration/tracing_test.go @@ -77,6 +77,104 @@ func (s *TracingSuite) TearDownTest() { s.composeStop("tempo") } +func (s *TracingSuite) TestOpenTelemetryBasic_HTTP_router_minimalVerbosity() { + file := s.adaptFile("fixtures/tracing/simple-opentelemetry.toml", TracingTemplate{ + WhoamiIP: s.whoamiIP, + WhoamiPort: s.whoamiPort, + IP: s.otelCollectorIP, + IsHTTP: true, + }) + + s.traefikCmd(withConfigFile(file)) + + // wait for traefik + err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", time.Second, try.BodyContains("basic-auth")) + require.NoError(s.T(), err) + + err = try.GetRequest("http://127.0.0.1:8000/basic-minimal", 500*time.Millisecond, try.StatusCodeIs(http.StatusOK)) + require.NoError(s.T(), err) + + contains := []map[string]string{ + { + "batches.0.scopeSpans.0.scope.name": "github.com/traefik/traefik", + + "batches.0.scopeSpans.0.spans.0.name": "ReverseProxy", + "batches.0.scopeSpans.0.spans.0.kind": "SPAN_KIND_CLIENT", + "batches.0.scopeSpans.0.spans.0.attributes.#(key=\"http.request.method\").value.stringValue": "GET", + "batches.0.scopeSpans.0.spans.0.attributes.#(key=\"network.protocol.version\").value.stringValue": "1.1", + "batches.0.scopeSpans.0.spans.0.attributes.#(key=\"url.full\").value.stringValue": fmt.Sprintf("http://%s/basic-minimal", net.JoinHostPort(s.whoamiIP, "80")), + "batches.0.scopeSpans.0.spans.0.attributes.#(key=\"user_agent.original\").value.stringValue": "Go-http-client/1.1", + "batches.0.scopeSpans.0.spans.0.attributes.#(key=\"network.peer.address\").value.stringValue": s.whoamiIP, + "batches.0.scopeSpans.0.spans.0.attributes.#(key=\"network.peer.port\").value.intValue": "80", + "batches.0.scopeSpans.0.spans.0.attributes.#(key=\"server.address\").value.stringValue": s.whoamiIP, + "batches.0.scopeSpans.0.spans.0.attributes.#(key=\"server.port\").value.intValue": "80", + "batches.0.scopeSpans.0.spans.0.attributes.#(key=\"http.response.status_code\").value.intValue": "200", + + "batches.0.scopeSpans.0.spans.1.name": "GET", + "batches.0.scopeSpans.0.spans.1.kind": "SPAN_KIND_SERVER", + "batches.0.scopeSpans.0.spans.1.attributes.#(key=\"entry_point\").value.stringValue": "web", + "batches.0.scopeSpans.0.spans.1.attributes.#(key=\"http.request.method\").value.stringValue": "GET", + "batches.0.scopeSpans.0.spans.1.attributes.#(key=\"url.path\").value.stringValue": "/basic-minimal", + "batches.0.scopeSpans.0.spans.1.attributes.#(key=\"url.query\").value.stringValue": "", + "batches.0.scopeSpans.0.spans.1.attributes.#(key=\"user_agent.original\").value.stringValue": "Go-http-client/1.1", + "batches.0.scopeSpans.0.spans.1.attributes.#(key=\"server.address\").value.stringValue": "127.0.0.1:8000", + "batches.0.scopeSpans.0.spans.1.attributes.#(key=\"network.peer.address\").value.stringValue": "127.0.0.1", + "batches.0.scopeSpans.0.spans.1.attributes.#(key=\"http.response.status_code\").value.intValue": "200", + }, + } + + s.checkTraceContent(contains) +} + +func (s *TracingSuite) TestOpenTelemetryBasic_HTTP_entrypoint_minimalVerbosity() { + file := s.adaptFile("fixtures/tracing/simple-opentelemetry.toml", TracingTemplate{ + WhoamiIP: s.whoamiIP, + WhoamiPort: s.whoamiPort, + IP: s.otelCollectorIP, + IsHTTP: true, + }) + + s.traefikCmd(withConfigFile(file)) + + // wait for traefik + err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", time.Second, try.BodyContains("basic-auth")) + require.NoError(s.T(), err) + + err = try.GetRequest("http://127.0.0.1:8001/basic", 500*time.Millisecond, try.StatusCodeIs(http.StatusOK)) + require.NoError(s.T(), err) + + contains := []map[string]string{ + { + "batches.0.scopeSpans.0.scope.name": "github.com/traefik/traefik", + + "batches.0.scopeSpans.0.spans.0.name": "ReverseProxy", + "batches.0.scopeSpans.0.spans.0.kind": "SPAN_KIND_CLIENT", + "batches.0.scopeSpans.0.spans.0.attributes.#(key=\"http.request.method\").value.stringValue": "GET", + "batches.0.scopeSpans.0.spans.0.attributes.#(key=\"network.protocol.version\").value.stringValue": "1.1", + "batches.0.scopeSpans.0.spans.0.attributes.#(key=\"url.full\").value.stringValue": fmt.Sprintf("http://%s/basic", net.JoinHostPort(s.whoamiIP, "80")), + "batches.0.scopeSpans.0.spans.0.attributes.#(key=\"user_agent.original\").value.stringValue": "Go-http-client/1.1", + "batches.0.scopeSpans.0.spans.0.attributes.#(key=\"network.peer.address\").value.stringValue": s.whoamiIP, + "batches.0.scopeSpans.0.spans.0.attributes.#(key=\"network.peer.port\").value.intValue": "80", + "batches.0.scopeSpans.0.spans.0.attributes.#(key=\"server.address\").value.stringValue": s.whoamiIP, + "batches.0.scopeSpans.0.spans.0.attributes.#(key=\"server.port\").value.intValue": "80", + "batches.0.scopeSpans.0.spans.0.attributes.#(key=\"http.response.status_code\").value.intValue": "200", + + "batches.0.scopeSpans.0.spans.1.name": "GET", + "batches.0.scopeSpans.0.spans.1.kind": "SPAN_KIND_SERVER", + "batches.0.scopeSpans.0.spans.1.attributes.#(key=\"entry_point\").value.stringValue": "web-minimal", + "batches.0.scopeSpans.0.spans.1.attributes.#(key=\"http.request.method\").value.stringValue": "GET", + "batches.0.scopeSpans.0.spans.1.attributes.#(key=\"url.path\").value.stringValue": "/basic", + "batches.0.scopeSpans.0.spans.1.attributes.#(key=\"url.query\").value.stringValue": "", + "batches.0.scopeSpans.0.spans.1.attributes.#(key=\"user_agent.original\").value.stringValue": "Go-http-client/1.1", + "batches.0.scopeSpans.0.spans.1.attributes.#(key=\"server.address\").value.stringValue": "127.0.0.1:8001", + "batches.0.scopeSpans.0.spans.1.attributes.#(key=\"network.peer.address\").value.stringValue": "127.0.0.1", + "batches.0.scopeSpans.0.spans.1.attributes.#(key=\"http.response.status_code\").value.intValue": "200", + }, + } + + s.checkTraceContent(contains) +} + func (s *TracingSuite) TestOpenTelemetryBasic_HTTP() { file := s.adaptFile("fixtures/tracing/simple-opentelemetry.toml", TracingTemplate{ WhoamiIP: s.whoamiIP, @@ -121,14 +219,14 @@ func (s *TracingSuite) TestOpenTelemetryBasic_HTTP() { "batches.0.scopeSpans.0.spans.3.name": "Router", "batches.0.scopeSpans.0.spans.3.kind": "SPAN_KIND_INTERNAL", "batches.0.scopeSpans.0.spans.3.attributes.#(key=\"traefik.service.name\").value.stringValue": "service0@file", - "batches.0.scopeSpans.0.spans.3.attributes.#(key=\"traefik.router.name\").value.stringValue": "router0@file", + "batches.0.scopeSpans.0.spans.3.attributes.#(key=\"traefik.router.name\").value.stringValue": "web-router0@file", "batches.0.scopeSpans.0.spans.3.attributes.#(key=\"http.route\").value.stringValue": "Path(`/basic`)", "batches.0.scopeSpans.0.spans.4.name": "Metrics", "batches.0.scopeSpans.0.spans.4.kind": "SPAN_KIND_INTERNAL", "batches.0.scopeSpans.0.spans.4.attributes.#(key=\"traefik.middleware.name\").value.stringValue": "metrics-entrypoint", - "batches.0.scopeSpans.0.spans.5.name": "EntryPoint", + "batches.0.scopeSpans.0.spans.5.name": "GET", "batches.0.scopeSpans.0.spans.5.kind": "SPAN_KIND_SERVER", "batches.0.scopeSpans.0.spans.5.attributes.#(key=\"entry_point\").value.stringValue": "web", "batches.0.scopeSpans.0.spans.5.attributes.#(key=\"http.request.method\").value.stringValue": "GET", @@ -189,7 +287,7 @@ func (s *TracingSuite) TestOpenTelemetryBasic_gRPC() { "batches.0.scopeSpans.0.spans.3.name": "Router", "batches.0.scopeSpans.0.spans.3.kind": "SPAN_KIND_INTERNAL", "batches.0.scopeSpans.0.spans.3.attributes.#(key=\"traefik.service.name\").value.stringValue": "service0@file", - "batches.0.scopeSpans.0.spans.3.attributes.#(key=\"traefik.router.name\").value.stringValue": "router0@file", + "batches.0.scopeSpans.0.spans.3.attributes.#(key=\"traefik.router.name\").value.stringValue": "web-router0@file", "batches.0.scopeSpans.0.spans.3.attributes.#(key=\"http.route\").value.stringValue": "Path(`/basic`)", "batches.0.scopeSpans.0.spans.4.name": "Metrics", @@ -251,14 +349,14 @@ func (s *TracingSuite) TestOpenTelemetryRateLimit() { "batches.0.scopeSpans.0.spans.1.name": "Router", "batches.0.scopeSpans.0.spans.1.kind": "SPAN_KIND_INTERNAL", "batches.0.scopeSpans.0.spans.1.attributes.#(key=\"traefik.service.name\").value.stringValue": "service1@file", - "batches.0.scopeSpans.0.spans.1.attributes.#(key=\"traefik.router.name\").value.stringValue": "router1@file", + "batches.0.scopeSpans.0.spans.1.attributes.#(key=\"traefik.router.name\").value.stringValue": "web-router1@file", "batches.0.scopeSpans.0.spans.1.attributes.#(key=\"http.route\").value.stringValue": "Path(`/ratelimit`)", "batches.0.scopeSpans.0.spans.2.name": "Metrics", "batches.0.scopeSpans.0.spans.2.kind": "SPAN_KIND_INTERNAL", "batches.0.scopeSpans.0.spans.2.attributes.#(key=\"traefik.middleware.name\").value.stringValue": "metrics-entrypoint", - "batches.0.scopeSpans.0.spans.3.name": "EntryPoint", + "batches.0.scopeSpans.0.spans.3.name": "GET", "batches.0.scopeSpans.0.spans.3.kind": "SPAN_KIND_SERVER", "batches.0.scopeSpans.0.spans.3.attributes.#(key=\"entry_point\").value.stringValue": "web", "batches.0.scopeSpans.0.spans.3.attributes.#(key=\"http.request.method\").value.stringValue": "GET", @@ -299,14 +397,14 @@ func (s *TracingSuite) TestOpenTelemetryRateLimit() { "batches.0.scopeSpans.0.spans.4.name": "Router", "batches.0.scopeSpans.0.spans.4.kind": "SPAN_KIND_INTERNAL", "batches.0.scopeSpans.0.spans.4.attributes.#(key=\"traefik.service.name\").value.stringValue": "service1@file", - "batches.0.scopeSpans.0.spans.4.attributes.#(key=\"traefik.router.name\").value.stringValue": "router1@file", + "batches.0.scopeSpans.0.spans.4.attributes.#(key=\"traefik.router.name\").value.stringValue": "web-router1@file", "batches.0.scopeSpans.0.spans.4.attributes.#(key=\"http.route\").value.stringValue": "Path(`/ratelimit`)", "batches.0.scopeSpans.0.spans.5.name": "Metrics", "batches.0.scopeSpans.0.spans.5.kind": "SPAN_KIND_INTERNAL", "batches.0.scopeSpans.0.spans.5.attributes.#(key=\"traefik.middleware.name\").value.stringValue": "metrics-entrypoint", - "batches.0.scopeSpans.0.spans.6.name": "EntryPoint", + "batches.0.scopeSpans.0.spans.6.name": "GET", "batches.0.scopeSpans.0.spans.6.kind": "SPAN_KIND_SERVER", "batches.0.scopeSpans.0.spans.6.attributes.#(key=\"entry_point\").value.stringValue": "web", "batches.0.scopeSpans.0.spans.6.attributes.#(key=\"http.request.method\").value.stringValue": "GET", @@ -423,13 +521,13 @@ func (s *TracingSuite) TestOpenTelemetryRetry() { "batches.0.scopeSpans.0.spans.12.name": "Router", "batches.0.scopeSpans.0.spans.12.kind": "SPAN_KIND_INTERNAL", "batches.0.scopeSpans.0.spans.12.attributes.#(key=\"traefik.service.name\").value.stringValue": "service2@file", - "batches.0.scopeSpans.0.spans.12.attributes.#(key=\"traefik.router.name\").value.stringValue": "router2@file", + "batches.0.scopeSpans.0.spans.12.attributes.#(key=\"traefik.router.name\").value.stringValue": "web-router2@file", "batches.0.scopeSpans.0.spans.13.name": "Metrics", "batches.0.scopeSpans.0.spans.13.kind": "SPAN_KIND_INTERNAL", "batches.0.scopeSpans.0.spans.13.attributes.#(key=\"traefik.middleware.name\").value.stringValue": "metrics-entrypoint", - "batches.0.scopeSpans.0.spans.14.name": "EntryPoint", + "batches.0.scopeSpans.0.spans.14.name": "GET", "batches.0.scopeSpans.0.spans.14.kind": "SPAN_KIND_SERVER", "batches.0.scopeSpans.0.spans.14.attributes.#(key=\"entry_point\").value.stringValue": "web", "batches.0.scopeSpans.0.spans.14.attributes.#(key=\"http.request.method\").value.stringValue": "GET", @@ -475,14 +573,14 @@ func (s *TracingSuite) TestOpenTelemetryAuth() { "batches.0.scopeSpans.0.spans.1.name": "Router", "batches.0.scopeSpans.0.spans.1.kind": "SPAN_KIND_INTERNAL", "batches.0.scopeSpans.0.spans.1.attributes.#(key=\"traefik.service.name\").value.stringValue": "service3@file", - "batches.0.scopeSpans.0.spans.1.attributes.#(key=\"traefik.router.name\").value.stringValue": "router3@file", + "batches.0.scopeSpans.0.spans.1.attributes.#(key=\"traefik.router.name\").value.stringValue": "web-router3@file", "batches.0.scopeSpans.0.spans.1.attributes.#(key=\"http.route\").value.stringValue": "Path(`/auth`)", "batches.0.scopeSpans.0.spans.2.name": "Metrics", "batches.0.scopeSpans.0.spans.2.kind": "SPAN_KIND_INTERNAL", "batches.0.scopeSpans.0.spans.2.attributes.#(key=\"traefik.middleware.name\").value.stringValue": "metrics-entrypoint", - "batches.0.scopeSpans.0.spans.3.name": "EntryPoint", + "batches.0.scopeSpans.0.spans.3.name": "GET", "batches.0.scopeSpans.0.spans.3.kind": "SPAN_KIND_SERVER", "batches.0.scopeSpans.0.spans.3.attributes.#(key=\"entry_point\").value.stringValue": "web", "batches.0.scopeSpans.0.spans.3.attributes.#(key=\"http.request.method\").value.stringValue": "GET", @@ -532,14 +630,14 @@ func (s *TracingSuite) TestOpenTelemetryAuthWithRetry() { "batches.0.scopeSpans.0.spans.2.name": "Router", "batches.0.scopeSpans.0.spans.2.kind": "SPAN_KIND_INTERNAL", "batches.0.scopeSpans.0.spans.2.attributes.#(key=\"traefik.service.name\").value.stringValue": "service4@file", - "batches.0.scopeSpans.0.spans.2.attributes.#(key=\"traefik.router.name\").value.stringValue": "router4@file", + "batches.0.scopeSpans.0.spans.2.attributes.#(key=\"traefik.router.name\").value.stringValue": "web-router4@file", "batches.0.scopeSpans.0.spans.2.attributes.#(key=\"http.route\").value.stringValue": "Path(`/retry-auth`)", "batches.0.scopeSpans.0.spans.3.name": "Metrics", "batches.0.scopeSpans.0.spans.3.kind": "SPAN_KIND_INTERNAL", "batches.0.scopeSpans.0.spans.3.attributes.#(key=\"traefik.middleware.name\").value.stringValue": "metrics-entrypoint", - "batches.0.scopeSpans.0.spans.4.name": "EntryPoint", + "batches.0.scopeSpans.0.spans.4.name": "GET", "batches.0.scopeSpans.0.spans.4.kind": "SPAN_KIND_SERVER", "batches.0.scopeSpans.0.spans.4.attributes.#(key=\"entry_point\").value.stringValue": "web", "batches.0.scopeSpans.0.spans.4.attributes.#(key=\"http.request.method\").value.stringValue": "GET", @@ -601,14 +699,14 @@ func (s *TracingSuite) TestOpenTelemetrySafeURL() { "batches.0.scopeSpans.0.spans.4.name": "Router", "batches.0.scopeSpans.0.spans.4.kind": "SPAN_KIND_INTERNAL", "batches.0.scopeSpans.0.spans.4.attributes.#(key=\"traefik.service.name\").value.stringValue": "service3@file", - "batches.0.scopeSpans.0.spans.4.attributes.#(key=\"traefik.router.name\").value.stringValue": "router3@file", + "batches.0.scopeSpans.0.spans.4.attributes.#(key=\"traefik.router.name\").value.stringValue": "web-router3@file", "batches.0.scopeSpans.0.spans.4.attributes.#(key=\"http.route\").value.stringValue": "Path(`/auth`)", "batches.0.scopeSpans.0.spans.5.name": "Metrics", "batches.0.scopeSpans.0.spans.5.kind": "SPAN_KIND_INTERNAL", "batches.0.scopeSpans.0.spans.5.attributes.#(key=\"traefik.middleware.name\").value.stringValue": "metrics-entrypoint", - "batches.0.scopeSpans.0.spans.6.name": "EntryPoint", + "batches.0.scopeSpans.0.spans.6.name": "GET", "batches.0.scopeSpans.0.spans.6.kind": "SPAN_KIND_SERVER", "batches.0.scopeSpans.0.spans.6.attributes.#(key=\"entry_point\").value.stringValue": "web", "batches.0.scopeSpans.0.spans.6.attributes.#(key=\"http.request.method\").value.stringValue": "GET", diff --git a/integration/zk_test.go b/integration/zk_test.go index f971ca79c..8955c530e 100644 --- a/integration/zk_test.go +++ b/integration/zk_test.go @@ -2,7 +2,6 @@ package integration import ( "bytes" - "context" "encoding/json" "net" "net/http" @@ -43,7 +42,7 @@ func (s *ZookeeperSuite) SetupSuite() { var err error s.kvClient, err = valkeyrie.NewStore( - context.Background(), + s.T().Context(), zookeeper.StoreName, []string{s.zookeeperAddr}, &zookeeper.Config{ @@ -110,7 +109,7 @@ func (s *ZookeeperSuite) TestSimpleConfiguration() { } for k, v := range data { - err := s.kvClient.Put(context.Background(), k, []byte(v), nil) + err := s.kvClient.Put(s.T().Context(), k, []byte(v), nil) require.NoError(s.T(), err) } diff --git a/internal/anchors.go b/internal/anchors.go new file mode 100644 index 000000000..6e184b97a --- /dev/null +++ b/internal/anchors.go @@ -0,0 +1,281 @@ +package main + +import ( + "bufio" + "fmt" + "log" + "os" + "path/filepath" + "regexp" + "strings" +) + +var ( + // detect any existing tag in the cell (case-insensitive). + reExistingAnchor = regexp.MustCompile(`(?i)<\s*a\b[^>]*>.*?`) + // separator cell like --- or :---: (3+ dashes, optional leading/trailing colon). + reSepCell = regexp.MustCompile(`^\s*:?-{3,}:?\s*$`) + // markdown link [text](url) → text (used to strip link wrappers in id). + reMdLink = regexp.MustCompile(`\[(.*?)\]\((.*?)\)`) + // collapse multiple hyphens. + reMultiHyphens = regexp.MustCompile(`-+`) +) + +// splitTableRow splits a markdown table line on pipes, while keeping escaped pipes. +// parts[1] will be the first data cell for lines that start with '|'. +func splitTableRow(line string) []string { + var parts []string + var b strings.Builder + escaped := false + for _, r := range line { + if escaped { + b.WriteRune(r) + escaped = false + continue + } + if r == '\\' { + escaped = true + b.WriteRune(r) + continue + } + if r == '|' { + parts = append(parts, b.String()) + b.Reset() + continue + } + b.WriteRune(r) + } + parts = append(parts, b.String()) + return parts +} + +func isTableRow(line string) bool { + s := strings.TrimSpace(line) + if !strings.HasPrefix(s, "|") { + return false + } + parts := splitTableRow(line) + return len(parts) >= 3 +} + +func isSeparatorRow(line string) bool { + if !isTableRow(line) { + return false + } + parts := splitTableRow(line) + // check all middle cells (skip first and last which are outside pipes) + for i := 1; i < len(parts)-1; i++ { + cell := strings.TrimSpace(parts[i]) + if cell == "" { + continue + } + if !reSepCell.MatchString(cell) { + return false + } + } + return true +} + +// Create ID from cell text, preserving letter case, removing
and markdown decorations. +func makeID(text string) string { + id := strings.TrimSpace(text) + + // remove BR tags (common in table cells) + id = strings.ReplaceAll(id, "
", " ") + id = strings.ReplaceAll(id, "
", " ") + id = strings.ReplaceAll(id, "
", " ") + + // remove the dots + id = strings.ReplaceAll(id, ".", "-") + + // strip markdown link wrappers [text](url) -> text + id = reMdLink.ReplaceAllString(id, "$1") + + // remove inline markdown characters + id = strings.ReplaceAll(id, "`", "") + id = strings.ReplaceAll(id, "*", "") + id = strings.ReplaceAll(id, "~", "") + + // replace spaces/underscores with hyphen + id = strings.ReplaceAll(id, " ", "-") + id = strings.ReplaceAll(id, "_", "-") + + // keep only letters (both cases), digits and hyphens + var clean []rune + for _, r := range id { + if (r >= 'a' && r <= 'z') || + (r >= 'A' && r <= 'Z') || + (r >= '0' && r <= '9') || + r == '-' || r == '.' { + // keep dot as you requested (we won't replace it) + clean = append(clean, r) + } + } + id = string(clean) + + // collapse multiple hyphens and trim + id = reMultiHyphens.ReplaceAllString(id, "-") + id = strings.Trim(id, "-") + if id == "" { + id = "row" + } + return id +} + +// Dedupe ID within a file: if id already seen, append -2, -3... +// Use "opt-" prefix to avoid conflicts with section headings. +func dedupeID(base string, seen map[string]int) string { + if base == "" { + base = "row" + } + + // Add prefix to avoid conflicts with section headings. + optID := "opt-" + base + + count, ok := seen[optID] + if !ok { + seen[optID] = 1 + return optID + } + + seen[optID] = count + 1 + return fmt.Sprintf("%s-%d", optID, count+1) +} + +// Clean existing anchors from cell content. +func cleanExistingAnchors(text string) string { + return reExistingAnchor.ReplaceAllStringFunc(text, func(match string) string { + // Extract content between
tags. + start := strings.Index(match, ">") + end := strings.LastIndex(match, "= 0 && end > start { + return strings.TrimSpace(match[start+1 : end]) + } + return strings.TrimSpace(match) + }) +} + +// Inject clickable link that is also the target (id + href on same element). +func injectClickableFirstCell(line string, seen map[string]int) string { + parts := splitTableRow(line) + // first data cell is index 1 + firstCellRaw := parts[1] + + // Clean any existing anchors first. + firstCellRaw = cleanExistingAnchors(firstCellRaw) + firstTrimmed := strings.TrimSpace(firstCellRaw) + + id := makeID(firstTrimmed) + if id == "" { + return strings.Join(parts, "|") + } + id = dedupeID(id, seen) + + // wrap the visible cell content in a link that is also the target + // keep the cell inner HTML/text (firstCellRaw) as-is inside the + parts[1] = fmt.Sprintf(" %s ", id, id, id, strings.TrimSpace(firstCellRaw)) + return strings.Join(parts, "|") +} + +func processFile(path string) error { + // read file + f, err := os.Open(path) + if err != nil { + return err + } + var lines []string + sc := bufio.NewScanner(f) + for sc.Scan() { + lines = append(lines, sc.Text()) + } + if err := sc.Err(); err != nil { + _ = f.Close() + return err + } + _ = f.Close() + + inFence := false + seen := make(map[string]int) + out := make([]string, len(lines)) + + for i, line := range lines { + trim := strings.TrimSpace(line) + + // toggle code fence (``` or ~~~) + if strings.HasPrefix(trim, "```") || strings.HasPrefix(trim, "~~~") { + inFence = !inFence + out[i] = line + continue + } + if inFence { + out[i] = line + continue + } + + // not a table row -> copy as-is + if !isTableRow(line) { + out[i] = line + continue + } + + // separator row -> copy as-is + if isSeparatorRow(line) { + out[i] = line + continue + } + + // detect header row (the row immediately before a separator) and skip it + isHeader := false + for j := i + 1; j < len(lines); j++ { + if strings.TrimSpace(lines[j]) == "" { + continue + } + if isSeparatorRow(lines[j]) { + isHeader = true + } + break + } + if isHeader { + out[i] = line + continue + } + + // otherwise inject clickable link in first cell + out[i] = injectClickableFirstCell(line, seen) + } + + // overwrite file in place + wf, err := os.Create(path) + if err != nil { + return err + } + bw := bufio.NewWriter(wf) + for _, l := range out { + fmt.Fprintln(bw, l) + } + if err := bw.Flush(); err != nil { + _ = wf.Close() + return err + } + return wf.Close() +} + +func genAnchors() { + root := "./docs/content/reference/" + err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + if !info.IsDir() && strings.HasSuffix(strings.ToLower(info.Name()), ".md") { + if perr := processFile(path); perr != nil { + fmt.Printf("⚠️ Error processing %s: %v\n", path, perr) + } else { + fmt.Printf("✅ Processed %s\n", path) + } + } + return nil + }) + if err != nil { + log.Fatalf("walk error: %v", err) + } +} diff --git a/internal/gendoc.go b/internal/gendoc.go index 67eb64003..34c9e0002 100644 --- a/internal/gendoc.go +++ b/internal/gendoc.go @@ -5,23 +5,17 @@ import ( "fmt" "io" "os" - "path" - "path/filepath" "reflect" "sort" - "strconv" "strings" "github.com/BurntSushi/toml" "github.com/rs/zerolog/log" - "github.com/traefik/paerser/env" "github.com/traefik/paerser/flag" "github.com/traefik/paerser/generator" - "github.com/traefik/paerser/parser" "github.com/traefik/traefik/v3/cmd" "github.com/traefik/traefik/v3/pkg/collector/hydratation" "github.com/traefik/traefik/v3/pkg/config/dynamic" - "github.com/traefik/traefik/v3/pkg/config/static" "gopkg.in/yaml.v3" ) @@ -30,6 +24,13 @@ var commentGenerated = `## CODE GENERATED AUTOMATICALLY ` func main() { + genRoutingConfDoc() + genInstallConfDoc() + genAnchors() +} + +// Generate the Routing Configuration YAML and TOML files. +func genRoutingConfDoc() { logger := log.With().Logger() dynConf := &dynamic.Configuration{} @@ -46,118 +47,14 @@ func main() { clean(dynConf.TCP.Services) clean(dynConf.UDP.Services) - err = tomlWrite("./docs/content/reference/dynamic-configuration/file.toml", dynConf) + err = tomlWrite("./docs/content/reference/routing-configuration/other-providers/file.toml", dynConf) if err != nil { logger.Fatal().Err(err).Send() } - err = yamlWrite("./docs/content/reference/dynamic-configuration/file.yaml", dynConf) + err = yamlWrite("./docs/content/reference/routing-configuration/other-providers/file.yaml", dynConf) if err != nil { logger.Fatal().Err(err).Send() } - - err = labelsWrite("./docs/content/reference/dynamic-configuration", dynConf) - if err != nil { - logger.Fatal().Err(err).Send() - } - - staticConf := &static.Configuration{} - - err = hydratation.Hydrate(staticConf) - if err != nil { - logger.Fatal().Err(err).Send() - } - - delete(staticConf.EntryPoints, "EntryPoint1") - - err = tomlWrite("./docs/content/reference/static-configuration/file.toml", staticConf) - if err != nil { - logger.Fatal().Err(err).Send() - } - err = yamlWrite("./docs/content/reference/static-configuration/file.yaml", staticConf) - if err != nil { - logger.Fatal().Err(err).Send() - } - - genStaticConfDoc("./docs/content/reference/static-configuration/env-ref.md", "", func(i interface{}) ([]parser.Flat, error) { - return env.Encode(env.DefaultNamePrefix, i) - }) - genStaticConfDoc("./docs/content/reference/static-configuration/cli-ref.md", "--", flag.Encode) - genKVDynConfDoc("./docs/content/reference/dynamic-configuration/kv-ref.md") -} - -func labelsWrite(outputDir string, element *dynamic.Configuration) error { - cleanServers(element) - - etnOpts := parser.EncoderToNodeOpts{OmitEmpty: true, TagName: parser.TagLabel, AllowSliceAsStruct: true} - node, err := parser.EncodeToNode(element, parser.DefaultRootName, etnOpts) - if err != nil { - return err - } - - metaOpts := parser.MetadataOpts{TagName: parser.TagLabel, AllowSliceAsStruct: true} - err = parser.AddMetadata(element, node, metaOpts) - if err != nil { - return err - } - - labels := make(map[string]string) - encodeNode(labels, node.Name, node) - - var keys []string - for k := range labels { - keys = append(keys, k) - } - - sort.Strings(keys) - - dockerLabels, err := os.Create(filepath.Join(outputDir, "docker-labels.yml")) - if err != nil { - return err - } - defer dockerLabels.Close() - - // Write the comment at the beginning of the file - if _, err := dockerLabels.WriteString(commentGenerated); err != nil { - return err - } - - for _, k := range keys { - v := labels[k] - if v != "" { - if v == "42000000000" { - v = "42s" - } - fmt.Fprintln(dockerLabels, `- "`+strings.ToLower(k)+`=`+v+`"`) - } - } - - return nil -} - -func cleanServers(element *dynamic.Configuration) { - for _, svc := range element.HTTP.Services { - if svc.LoadBalancer != nil { - server := svc.LoadBalancer.Servers[0] - svc.LoadBalancer.Servers = nil - svc.LoadBalancer.Servers = append(svc.LoadBalancer.Servers, server) - } - } - - for _, svc := range element.TCP.Services { - if svc.LoadBalancer != nil { - server := svc.LoadBalancer.Servers[0] - svc.LoadBalancer.Servers = nil - svc.LoadBalancer.Servers = append(svc.LoadBalancer.Servers, server) - } - } - - for _, svc := range element.UDP.Services { - if svc.LoadBalancer != nil { - server := svc.LoadBalancer.Servers[0] - svc.LoadBalancer.Servers = nil - svc.LoadBalancer.Servers = append(svc.LoadBalancer.Servers, server) - } - } } func yamlWrite(outputFile string, element any) error { @@ -167,7 +64,7 @@ func yamlWrite(outputFile string, element any) error { } defer file.Close() - // Write the comment at the beginning of the file + // Write the comment at the beginning of the file. if _, err := file.WriteString(commentGenerated); err != nil { return err } @@ -191,7 +88,7 @@ func tomlWrite(outputFile string, element any) error { } defer file.Close() - // Write the comment at the beginning of the file + // Write the comment at the beginning of the file. if _, err := file.WriteString(commentGenerated); err != nil { return err } @@ -229,14 +126,16 @@ func clean(element any) { valSvcs.SetMapIndex(reflect.ValueOf(fmt.Sprintf("%s1", valueSvcRoot.Type().Name())), reflect.Value{}) } -func genStaticConfDoc(outputFile, prefix string, encodeFn func(interface{}) ([]parser.Flat, error)) { +// Generate the Install Configuration in a table. +func genInstallConfDoc() { + outputFile := "./docs/content/reference/install-configuration/configuration-options.md" logger := log.With().Str("file", outputFile).Logger() element := &cmd.NewTraefikConfiguration().Configuration generator.Generate(element) - flats, err := encodeFn(element) + flats, err := flag.Encode(element) if err != nil { logger.Fatal().Err(err).Send() } @@ -259,9 +158,14 @@ func genStaticConfDoc(outputFile, prefix string, encodeFn func(interface{}) ([]p CODE GENERATED AUTOMATICALLY THIS FILE MUST NOT BE EDITED BY HAND -->`) - w.writeln() + w.writeln(`# Install Configuration Options`) + w.writeln(`## Configuration Options`) - for i, flat := range flats { + w.writeln(` +| Field | Description | Default | +|:-------|:------------|:-------|`) + + for _, flat := range flats { // TODO must be move into the flats creation. if flat.Name == "experimental.plugins." || flat.Name == "TRAEFIK_EXPERIMENTAL_PLUGINS_" { continue @@ -271,21 +175,15 @@ THIS FILE MUST NOT BE EDITED BY HAND continue } - if prefix == "" { - w.writeln("`" + prefix + strings.ReplaceAll(flat.Name, "[0]", "_n") + "`: ") - } else { - w.writeln("`" + prefix + strings.ReplaceAll(flat.Name, "[0]", "[n]") + "`: ") - } + line := "| " + strings.ReplaceAll(strings.ReplaceAll(flat.Name, "<", "_"), ">", "_") + " | " + flat.Description + " | " if flat.Default == "" { - w.writeln(flat.Description) + line += "|" } else { - w.writeln(flat.Description + " (Default: ```" + flat.Default + "```)") + line += flat.Default + " |" } - if i < len(flats)-1 { - w.writeln() - } + w.writeln(line) } if w.err != nil { @@ -305,101 +203,3 @@ func (ew *errWriter) writeln(a ...interface{}) { _, ew.err = fmt.Fprintln(ew.w, a...) } - -func genKVDynConfDoc(outputFile string) { - dynConfPath := "./docs/content/reference/dynamic-configuration/file.toml" - conf := map[string]interface{}{} - _, err := toml.DecodeFile(dynConfPath, &conf) - if err != nil { - log.Fatal().Err(err).Send() - } - - file, err := os.Create(outputFile) - if err != nil { - log.Fatal().Err(err).Send() - } - - store := storeWriter{data: map[string]string{}} - - c := client{store: store} - err = c.load("traefik", conf) - if err != nil { - log.Fatal().Err(err).Send() - } - - var keys []string - for k := range store.data { - keys = append(keys, k) - } - - sort.Strings(keys) - - _, _ = fmt.Fprintf(file, ` -`) - - for _, k := range keys { - _, _ = fmt.Fprintf(file, "| `%s` | `%s` |\n", k, store.data[k]) - } -} - -type storeWriter struct { - data map[string]string -} - -func (f storeWriter) Put(key string, value []byte, _ []string) error { - f.data[key] = string(value) - return nil -} - -type client struct { - store storeWriter -} - -func (c client) load(parentKey string, conf map[string]interface{}) error { - for k, v := range conf { - switch entry := v.(type) { - case map[string]interface{}: - key := path.Join(parentKey, k) - - if len(entry) == 0 { - err := c.store.Put(key, nil, nil) - if err != nil { - return err - } - } else { - err := c.load(key, entry) - if err != nil { - return err - } - } - case []map[string]interface{}: - for i, o := range entry { - key := path.Join(parentKey, k, strconv.Itoa(i)) - - if err := c.load(key, o); err != nil { - return err - } - } - case []interface{}: - for i, o := range entry { - key := path.Join(parentKey, k, strconv.Itoa(i)) - - err := c.store.Put(key, []byte(fmt.Sprintf("%v", o)), nil) - if err != nil { - return err - } - } - default: - key := path.Join(parentKey, k) - - err := c.store.Put(key, []byte(fmt.Sprintf("%v", v)), nil) - if err != nil { - return err - } - } - } - return nil -} diff --git a/internal/parser.go b/internal/parser.go deleted file mode 100644 index 3fa85617e..000000000 --- a/internal/parser.go +++ /dev/null @@ -1,66 +0,0 @@ -package main - -import ( - "fmt" - "reflect" - "strings" - - "github.com/traefik/paerser/parser" -) - -func encodeNode(labels map[string]string, root string, node *parser.Node) { - for _, child := range node.Children { - if child.Disabled { - continue - } - - var sep string - if child.Name[0] != '[' { - sep = "." - } - - childName := root + sep + child.Name - - if child.RawValue != nil { - encodeRawValue(labels, childName, child.RawValue) - continue - } - - if strings.Contains(child.Tag.Get(parser.TagLabel), parser.TagLabelAllowEmpty) { - labels[childName] = "true" - } - - if len(child.Children) > 0 { - encodeNode(labels, childName, child) - } else if len(child.Name) > 0 { - labels[childName] = child.Value - } - } -} - -func encodeRawValue(labels map[string]string, root string, rawValue interface{}) { - if rawValue == nil { - return - } - - tValue := reflect.TypeOf(rawValue) - - if tValue.Kind() == reflect.Map && tValue.Elem().Kind() == reflect.Interface { - r := reflect.ValueOf(rawValue). - Convert(reflect.TypeOf((map[string]interface{})(nil))). - Interface().(map[string]interface{}) - - for k, v := range r { - switch tv := v.(type) { - case string: - labels[root+"."+k] = tv - case []interface{}: - for i, e := range tv { - encodeRawValue(labels, fmt.Sprintf("%s.%s[%d]", root, k, i), e) - } - default: - encodeRawValue(labels, root+"."+k, v) - } - } - } -} diff --git a/internal/testsci/genmatrix.go b/internal/testsci/genmatrix.go new file mode 100644 index 000000000..01ebdabcd --- /dev/null +++ b/internal/testsci/genmatrix.go @@ -0,0 +1,64 @@ +package main + +import ( + "encoding/json" + "fmt" + "os" + "strings" + + "github.com/rs/zerolog/log" + "golang.org/x/tools/go/packages" +) + +const groupCount = 12 + +type group struct { + Group string `json:"group"` +} + +func main() { + cfg := &packages.Config{ + Mode: packages.NeedName, + Dir: ".", + } + + pkgs, err := packages.Load(cfg, "./cmd/...", "./pkg/...") + if err != nil { + log.Fatal().Err(err).Msg("Loading packages") + } + + var packageNames []string + for _, pkg := range pkgs { + if pkg.PkgPath != "" { + packageNames = append(packageNames, pkg.PkgPath) + } + } + + total := len(packageNames) + perGroup := (total + groupCount - 1) / groupCount + + fmt.Fprintf(os.Stderr, "Total packages: %d\n", total) + fmt.Fprintf(os.Stderr, "Packages per group: %d\n", perGroup) + + var matrix []group + for i := range groupCount { + start := i * perGroup + end := start + perGroup + if start >= total { + break + } + if end > total { + end = total + } + g := strings.Join(packageNames[start:end], " ") + matrix = append(matrix, group{Group: g}) + } + + jsonBytes, err := json.Marshal(matrix) + if err != nil { + log.Fatal().Err(err).Msg("Failed to marshal matrix") + } + + // Output for GitHub Actions + fmt.Printf("matrix=%s\n", string(jsonBytes)) +} diff --git a/pkg/api/handler.go b/pkg/api/handler.go index 922779e61..6c99ca12e 100644 --- a/pkg/api/handler.go +++ b/pkg/api/handler.go @@ -156,7 +156,7 @@ func extractType(element interface{}) string { for i := range v.NumField() { field := v.Field(i) - if field.Kind() == reflect.Map && field.Type().Elem() == reflect.TypeOf(dynamic.PluginConf{}) { + if field.Kind() == reflect.Map && field.Type().Elem() == reflect.TypeFor[dynamic.PluginConf]() { if keys := field.MapKeys(); len(keys) == 1 { return keys[0].String() } diff --git a/pkg/api/handler_http_test.go b/pkg/api/handler_http_test.go index 715a58676..e34b1bfe2 100644 --- a/pkg/api/handler_http_test.go +++ b/pkg/api/handler_http_test.go @@ -1,7 +1,6 @@ package api import ( - "context" "encoding/json" "fmt" "io" @@ -1004,8 +1003,8 @@ func TestHandler_HTTP(t *testing.T) { rtConf := &test.conf // To lazily initialize the Statuses. rtConf.PopulateUsedBy() - rtConf.GetRoutersByEntryPoints(context.Background(), []string{"web"}, false) - rtConf.GetRoutersByEntryPoints(context.Background(), []string{"web"}, true) + rtConf.GetRoutersByEntryPoints(t.Context(), []string{"web"}, false) + rtConf.GetRoutersByEntryPoints(t.Context(), []string{"web"}, true) handler := New(static.Configuration{API: &static.API{}, Global: &static.Global{}}, rtConf) server := httptest.NewServer(handler.createRouter()) diff --git a/pkg/api/handler_overview.go b/pkg/api/handler_overview.go index 7b15d513e..9279370a6 100644 --- a/pkg/api/handler_overview.go +++ b/pkg/api/handler_overview.go @@ -232,7 +232,7 @@ func getProviders(conf static.Configuration) []string { if !field.IsNil() { providers = append(providers, v.Type().Field(i).Name) } - } else if field.Kind() == reflect.Map && field.Type().Elem() == reflect.TypeOf(static.PluginConf{}) { + } else if field.Kind() == reflect.Map && field.Type().Elem() == reflect.TypeFor[static.PluginConf]() { for _, value := range field.MapKeys() { providers = append(providers, "plugin-"+value.String()) } diff --git a/pkg/api/handler_overview_test.go b/pkg/api/handler_overview_test.go index d3ef01f72..7b07106b9 100644 --- a/pkg/api/handler_overview_test.go +++ b/pkg/api/handler_overview_test.go @@ -13,12 +13,12 @@ import ( "github.com/traefik/traefik/v3/pkg/config/dynamic" "github.com/traefik/traefik/v3/pkg/config/runtime" "github.com/traefik/traefik/v3/pkg/config/static" + otypes "github.com/traefik/traefik/v3/pkg/observability/types" "github.com/traefik/traefik/v3/pkg/provider/docker" "github.com/traefik/traefik/v3/pkg/provider/file" "github.com/traefik/traefik/v3/pkg/provider/kubernetes/crd" "github.com/traefik/traefik/v3/pkg/provider/kubernetes/ingress" "github.com/traefik/traefik/v3/pkg/provider/rest" - "github.com/traefik/traefik/v3/pkg/types" ) func TestHandler_Overview(t *testing.T) { @@ -255,8 +255,8 @@ func TestHandler_Overview(t *testing.T) { confStatic: static.Configuration{ Global: &static.Global{}, API: &static.API{}, - Metrics: &types.Metrics{ - Prometheus: &types.Prometheus{}, + Metrics: &otypes.Metrics{ + Prometheus: &otypes.Prometheus{}, }, Tracing: &static.Tracing{}, }, diff --git a/pkg/api/handler_tcp_test.go b/pkg/api/handler_tcp_test.go index e08897880..387c39050 100644 --- a/pkg/api/handler_tcp_test.go +++ b/pkg/api/handler_tcp_test.go @@ -1,7 +1,6 @@ package api import ( - "context" "encoding/json" "io" "net/http" @@ -880,7 +879,7 @@ func TestHandler_TCP(t *testing.T) { rtConf := &test.conf // To lazily initialize the Statuses. rtConf.PopulateUsedBy() - rtConf.GetTCPRoutersByEntryPoints(context.Background(), []string{"web"}) + rtConf.GetTCPRoutersByEntryPoints(t.Context(), []string{"web"}) handler := New(static.Configuration{API: &static.API{}, Global: &static.Global{}}, rtConf) server := httptest.NewServer(handler.createRouter()) diff --git a/pkg/api/handler_udp_test.go b/pkg/api/handler_udp_test.go index dd3067314..bd8625dfe 100644 --- a/pkg/api/handler_udp_test.go +++ b/pkg/api/handler_udp_test.go @@ -1,7 +1,6 @@ package api import ( - "context" "encoding/json" "io" "net/http" @@ -570,7 +569,7 @@ func TestHandler_UDP(t *testing.T) { rtConf := &test.conf // To lazily initialize the Statuses. rtConf.PopulateUsedBy() - rtConf.GetUDPRoutersByEntryPoints(context.Background(), []string{"web"}) + rtConf.GetUDPRoutersByEntryPoints(t.Context(), []string{"web"}) handler := New(static.Configuration{API: &static.API{}, Global: &static.Global{}}, rtConf) server := httptest.NewServer(handler.createRouter()) diff --git a/pkg/cli/deprecation.go b/pkg/cli/deprecation.go index 38b13aba7..c7c262ba7 100644 --- a/pkg/cli/deprecation.go +++ b/pkg/cli/deprecation.go @@ -195,7 +195,7 @@ func (c *configuration) deprecationNotice(logger zerolog.Logger) bool { if c.Pilot != nil { incompatible = true logger.Error().Msg("Pilot configuration has been removed in v3, please remove all Pilot-related static configuration for Traefik to start." + - " For more information please read the migration guide: https://doc.traefik.io/traefik/v3.4/migration/v2-to-v3/#pilot") + " For more information please read the migration guide: https://doc.traefik.io/traefik/v3.5/migration/v2-to-v3/#pilot") } incompatibleCore := c.Core.deprecationNotice(logger) @@ -213,7 +213,7 @@ func (c *core) deprecationNotice(logger zerolog.Logger) bool { if c != nil && c.DefaultRuleSyntax != "" { logger.Error().Msg("`Core.DefaultRuleSyntax` option has been deprecated in v3.4, and will be removed in the next major version." + " Please consider migrating all router rules to v3 syntax." + - " For more information please read the migration guide: https://doc.traefik.io/traefik/v3.4/migration/v3/#rule-syntax") + " For more information please read the migration guide: https://doc.traefik.io/traefik/v3.5/migration/v3/#rule-syntax") } return false @@ -243,13 +243,13 @@ func (p *providers) deprecationNotice(logger zerolog.Logger) bool { if p.Marathon != nil { incompatible = true logger.Error().Msg("Marathon provider has been removed in v3, please remove all Marathon-related static configuration for Traefik to start." + - " For more information please read the migration guide: https://doc.traefik.io/traefik/v3.4/migration/v2-to-v3/#marathon-provider") + " For more information please read the migration guide: https://doc.traefik.io/traefik/v3.5/migration/v2-to-v3/#marathon-provider") } if p.Rancher != nil { incompatible = true logger.Error().Msg("Rancher provider has been removed in v3, please remove all Rancher-related static configuration for Traefik to start." + - " For more information please read the migration guide: https://doc.traefik.io/traefik/v3.4/migration/v2-to-v3/#rancher-v1-provider") + " For more information please read the migration guide: https://doc.traefik.io/traefik/v3.5/migration/v2-to-v3/#rancher-v1-provider") } dockerIncompatible := p.Docker.deprecationNotice(logger) @@ -291,14 +291,14 @@ func (d *docker) deprecationNotice(logger zerolog.Logger) bool { if d.SwarmMode != nil { incompatible = true logger.Error().Msg("Docker provider `swarmMode` option has been removed in v3, please use the Swarm Provider instead." + - "For more information please read the migration guide: https://doc.traefik.io/traefik/v3.4/migration/v2-to-v3/#docker-docker-swarm") + "For more information please read the migration guide: https://doc.traefik.io/traefik/v3.5/migration/v2-to-v3/#docker-docker-swarm") } if d.TLS != nil && d.TLS.CAOptional != nil { incompatible = true logger.Error().Msg("Docker provider `tls.CAOptional` option has been removed in v3, as TLS client authentication is a server side option (see https://github.com/golang/go/blob/740a490f71d026bb7d2d13cb8fa2d6d6e0572b70/src/crypto/tls/common.go#L634)." + "Please remove all occurrences from the static configuration for Traefik to start." + - "For more information please read the migration guide: https://doc.traefik.io/traefik/v3.4/migration/v2-to-v3/#tlscaoptional") + "For more information please read the migration guide: https://doc.traefik.io/traefik/v3.5/migration/v2-to-v3/#tlscaoptional") } return incompatible @@ -339,7 +339,7 @@ func (e *etcd) deprecationNotice(logger zerolog.Logger) bool { incompatible = true logger.Error().Msg("ETCD provider `tls.CAOptional` option has been removed in v3, as TLS client authentication is a server side option (see https://github.com/golang/go/blob/740a490f71d026bb7d2d13cb8fa2d6d6e0572b70/src/crypto/tls/common.go#L634)." + "Please remove all occurrences from the static configuration for Traefik to start." + - "For more information please read the migration guide: https://doc.traefik.io/traefik/v3.4/migration/v2-to-v3/#tlscaoptional_3") + "For more information please read the migration guide: https://doc.traefik.io/traefik/v3.5/migration/v2-to-v3/#tlscaoptional_3") } return incompatible @@ -360,7 +360,7 @@ func (r *redis) deprecationNotice(logger zerolog.Logger) bool { incompatible = true logger.Error().Msg("Redis provider `tls.CAOptional` option has been removed in v3, as TLS client authentication is a server side option (see https://github.com/golang/go/blob/740a490f71d026bb7d2d13cb8fa2d6d6e0572b70/src/crypto/tls/common.go#L634)." + "Please remove all occurrences from the static configuration for Traefik to start." + - "For more information please read the migration guide: https://doc.traefik.io/traefik/v3.4/migration/v2-to-v3/#tlscaoptional_4") + "For more information please read the migration guide: https://doc.traefik.io/traefik/v3.5/migration/v2-to-v3/#tlscaoptional_4") } return incompatible @@ -381,14 +381,14 @@ func (c *consul) deprecationNotice(logger zerolog.Logger) bool { if c.Namespace != nil { incompatible = true logger.Error().Msg("Consul provider `namespace` option has been removed, please use the `namespaces` option instead." + - "For more information please read the migration guide: https://doc.traefik.io/traefik/v3.4/migration/v2-to-v3/#consul-provider") + "For more information please read the migration guide: https://doc.traefik.io/traefik/v3.5/migration/v2-to-v3/#consul-provider") } if c.TLS != nil && c.TLS.CAOptional != nil { incompatible = true logger.Error().Msg("Consul provider `tls.CAOptional` option has been removed in v3, as TLS client authentication is a server side option (see https://github.com/golang/go/blob/740a490f71d026bb7d2d13cb8fa2d6d6e0572b70/src/crypto/tls/common.go#L634)." + "Please remove all occurrences from the static configuration for Traefik to start." + - "For more information please read the migration guide: https://doc.traefik.io/traefik/v3.4/migration/v2-to-v3/#tlscaoptional_1") + "For more information please read the migration guide: https://doc.traefik.io/traefik/v3.5/migration/v2-to-v3/#tlscaoptional_1") } return incompatible @@ -413,14 +413,14 @@ func (c *consulCatalog) deprecationNotice(logger zerolog.Logger) bool { if c.Namespace != nil { incompatible = true logger.Error().Msg("ConsulCatalog provider `namespace` option has been removed, please use the `namespaces` option instead." + - "For more information please read the migration guide: https://doc.traefik.io/traefik/v3.4/migration/v2-to-v3/#consulcatalog-provider") + "For more information please read the migration guide: https://doc.traefik.io/traefik/v3.5/migration/v2-to-v3/#consulcatalog-provider") } if c.Endpoint != nil && c.Endpoint.TLS != nil && c.Endpoint.TLS.CAOptional != nil { incompatible = true logger.Error().Msg("ConsulCatalog provider `tls.CAOptional` option has been removed in v3, as TLS client authentication is a server side option (see https://github.com/golang/go/blob/740a490f71d026bb7d2d13cb8fa2d6d6e0572b70/src/crypto/tls/common.go#L634)." + "Please remove all occurrences from the static configuration for Traefik to start." + - "For more information please read the migration guide: https://doc.traefik.io/traefik/v3.4/migration/v2-to-v3/#endpointtlscaoptional") + "For more information please read the migration guide: https://doc.traefik.io/traefik/v3.5/migration/v2-to-v3/#endpointtlscaoptional") } return incompatible @@ -441,14 +441,14 @@ func (n *nomad) deprecationNotice(logger zerolog.Logger) bool { if n.Namespace != nil { incompatible = true logger.Error().Msg("Nomad provider `namespace` option has been removed, please use the `namespaces` option instead." + - "For more information please read the migration guide: https://doc.traefik.io/traefik/v3.4/migration/v2-to-v3/#nomad-provider") + "For more information please read the migration guide: https://doc.traefik.io/traefik/v3.5/migration/v2-to-v3/#nomad-provider") } if n.Endpoint != nil && n.Endpoint.TLS != nil && n.Endpoint.TLS.CAOptional != nil { incompatible = true logger.Error().Msg("Nomad provider `tls.CAOptional` option has been removed in v3, as TLS client authentication is a server side option (see https://github.com/golang/go/blob/740a490f71d026bb7d2d13cb8fa2d6d6e0572b70/src/crypto/tls/common.go#L634)." + "Please remove all occurrences from the static configuration for Traefik to start." + - "For more information please read the migration guide: https://doc.traefik.io/traefik/v3.4/migration/v2-to-v3/#endpointtlscaoptional_1") + "For more information please read the migration guide: https://doc.traefik.io/traefik/v3.5/migration/v2-to-v3/#endpointtlscaoptional_1") } return incompatible @@ -469,7 +469,7 @@ func (h *http) deprecationNotice(logger zerolog.Logger) bool { incompatible = true logger.Error().Msg("HTTP provider `tls.CAOptional` option has been removed in v3, as TLS client authentication is a server side option (see https://github.com/golang/go/blob/740a490f71d026bb7d2d13cb8fa2d6d6e0572b70/src/crypto/tls/common.go#L634)." + "Please remove all occurrences from the static configuration for Traefik to start." + - "For more information please read the migration guide: https://doc.traefik.io/traefik/v3.4/migration/v2-to-v3/#tlscaoptional_2") + "For more information please read the migration guide: https://doc.traefik.io/traefik/v3.5/migration/v2-to-v3/#tlscaoptional_2") } return incompatible @@ -487,7 +487,7 @@ func (i *ingress) deprecationNotice(logger zerolog.Logger) { if i.DisableIngressClassLookup != nil { logger.Error().Msg("Kubernetes Ingress provider `disableIngressClassLookup` option has been deprecated in v3.1, and will be removed in the next major version." + "Please use the `disableClusterScopeResources` option instead." + - "For more information please read the migration guide: https://doc.traefik.io/traefik/v3.4/migration/v3/#ingressclasslookup") + "For more information please read the migration guide: https://doc.traefik.io/traefik/v3.5/migration/v3/#ingressclasslookup") } } @@ -504,7 +504,7 @@ func (e *experimental) deprecationNotice(logger zerolog.Logger) bool { if e.HTTP3 != nil { logger.Error().Msg("HTTP3 is not an experimental feature in v3 and the associated enablement has been removed." + "Please remove its usage from the static configuration for Traefik to start." + - "For more information please read the migration guide: https://doc.traefik.io/traefik/v3.4/migration/v2-to-v3-details/#http3") + "For more information please read the migration guide: https://doc.traefik.io/traefik/v3.5/migration/v2-to-v3-details/#http3") return true } @@ -512,7 +512,7 @@ func (e *experimental) deprecationNotice(logger zerolog.Logger) bool { if e.KubernetesGateway != nil { logger.Error().Msg("KubernetesGateway provider is not an experimental feature starting with v3.1." + "Please remove its usage from the static configuration." + - "For more information please read the migration guide: https://doc.traefik.io/traefik/v3.4/migration/v3/#gateway-api-kubernetesgateway-provider") + "For more information please read the migration guide: https://doc.traefik.io/traefik/v3.5/migration/v3/#gateway-api-kubernetesgateway-provider") } return false @@ -539,7 +539,7 @@ func (t *tracing) deprecationNotice(logger zerolog.Logger) bool { if t.SpanNameLimit != nil { incompatible = true logger.Error().Msg("SpanNameLimit option for Tracing has been removed in v3, as Span names are now of a fixed length." + - "For more information please read the migration guide: https://doc.traefik.io/traefik/v3.4/migration/v2-to-v3/#tracing") + "For more information please read the migration guide: https://doc.traefik.io/traefik/v3.5/migration/v2-to-v3/#tracing") } if t.GlobalAttributes != nil { @@ -547,49 +547,49 @@ func (t *tracing) deprecationNotice(logger zerolog.Logger) bool { logger.Error().Msg("`tracing.globalAttributes` option has been deprecated in v3.3, and will be removed in the next major version." + "Please use the `tracing.resourceAttributes` option instead." + - "For more information please read the migration guide: https://doc.traefik.io/traefik/v3.4/migration/v3/#tracing-global-attributes") + "For more information please read the migration guide: https://doc.traefik.io/traefik/v3.5/migration/v3/#tracing-global-attributes") } if t.Jaeger != nil { incompatible = true logger.Error().Msg("Jaeger Tracing backend has been removed in v3, please remove all Jaeger-related Tracing static configuration for Traefik to start." + "In v3, Open Telemetry replaces specific tracing backend implementations, and an collector/exporter can be used to export metrics in a vendor specific format." + - "For more information please read the migration guide: https://doc.traefik.io/traefik/v3.4/migration/v2-to-v3/#tracing") + "For more information please read the migration guide: https://doc.traefik.io/traefik/v3.5/migration/v2-to-v3/#tracing") } if t.Zipkin != nil { incompatible = true logger.Error().Msg("Zipkin Tracing backend has been removed in v3, please remove all Zipkin-related Tracing static configuration for Traefik to start." + "In v3, Open Telemetry replaces specific tracing backend implementations, and an collector/exporter can be used to export metrics in a vendor specific format." + - "For more information please read the migration guide: https://doc.traefik.io/traefik/v3.4/migration/v2-to-v3/#tracing") + "For more information please read the migration guide: https://doc.traefik.io/traefik/v3.5/migration/v2-to-v3/#tracing") } if t.Datadog != nil { incompatible = true logger.Error().Msg("Datadog Tracing backend has been removed in v3, please remove all Datadog-related Tracing static configuration for Traefik to start." + "In v3, Open Telemetry replaces specific tracing backend implementations, and an collector/exporter can be used to export metrics in a vendor specific format." + - "For more information please read the migration guide: https://doc.traefik.io/traefik/v3.4/migration/v2-to-v3/#tracing") + "For more information please read the migration guide: https://doc.traefik.io/traefik/v3.5/migration/v2-to-v3/#tracing") } if t.Instana != nil { incompatible = true logger.Error().Msg("Instana Tracing backend has been removed in v3, please remove all Instana-related Tracing static configuration for Traefik to start." + "In v3, Open Telemetry replaces specific tracing backend implementations, and an collector/exporter can be used to export metrics in a vendor specific format." + - "For more information please read the migration guide: https://doc.traefik.io/traefik/v3.4/migration/v2-to-v3/#tracing") + "For more information please read the migration guide: https://doc.traefik.io/traefik/v3.5/migration/v2-to-v3/#tracing") } if t.Haystack != nil { incompatible = true logger.Error().Msg("Haystack Tracing backend has been removed in v3, please remove all Haystack-related Tracing static configuration for Traefik to start." + "In v3, Open Telemetry replaces specific tracing backend implementations, and an collector/exporter can be used to export metrics in a vendor specific format." + - "For more information please read the migration guide: https://doc.traefik.io/traefik/v3.4/migration/v2-to-v3/#tracing") + "For more information please read the migration guide: https://doc.traefik.io/traefik/v3.5/migration/v2-to-v3/#tracing") } if t.Elastic != nil { incompatible = true logger.Error().Msg("Elastic Tracing backend has been removed in v3, please remove all Elastic-related Tracing static configuration for Traefik to start." + "In v3, Open Telemetry replaces specific tracing backend implementations, and an collector/exporter can be used to export metrics in a vendor specific format." + - "For more information please read the migration guide: https://doc.traefik.io/traefik/v3.4/migration/v2-to-v3/#tracing") + "For more information please read the migration guide: https://doc.traefik.io/traefik/v3.5/migration/v2-to-v3/#tracing") } return incompatible diff --git a/pkg/collector/hydratation/hydration.go b/pkg/collector/hydratation/hydration.go index c45cfcfbb..74009817a 100644 --- a/pkg/collector/hydratation/hydration.go +++ b/pkg/collector/hydratation/hydration.go @@ -55,7 +55,7 @@ func fill(field reflect.Value) error { setTyped(field, int32(defaultNumber)) case reflect.Int64: switch field.Type() { - case reflect.TypeOf(types.Duration(time.Second)): + case reflect.TypeFor[types.Duration](): setTyped(field, types.Duration(defaultNumber*time.Second)) default: setTyped(field, int64(defaultNumber)) diff --git a/pkg/config/dynamic/http_config.go b/pkg/config/dynamic/http_config.go index d91f60340..96e16f7b4 100644 --- a/pkg/config/dynamic/http_config.go +++ b/pkg/config/dynamic/http_config.go @@ -5,6 +5,7 @@ import ( "time" ptypes "github.com/traefik/paerser/types" + otypes "github.com/traefik/traefik/v3/pkg/observability/types" traefiktls "github.com/traefik/traefik/v3/pkg/tls" "github.com/traefik/traefik/v3/pkg/types" "google.golang.org/grpc/codes" @@ -88,9 +89,21 @@ type RouterTLSConfig struct { // RouterObservabilityConfig holds the observability configuration for a router. type RouterObservabilityConfig struct { + // AccessLogs enables access logs for this router. AccessLogs *bool `json:"accessLogs,omitempty" toml:"accessLogs,omitempty" yaml:"accessLogs,omitempty" export:"true"` - Tracing *bool `json:"tracing,omitempty" toml:"tracing,omitempty" yaml:"tracing,omitempty" export:"true"` - Metrics *bool `json:"metrics,omitempty" toml:"metrics,omitempty" yaml:"metrics,omitempty" export:"true"` + // Metrics enables metrics for this router. + Metrics *bool `json:"metrics,omitempty" toml:"metrics,omitempty" yaml:"metrics,omitempty" export:"true"` + // Tracing enables tracing for this router. + Tracing *bool `json:"tracing,omitempty" toml:"tracing,omitempty" yaml:"tracing,omitempty" export:"true"` + // TraceVerbosity defines the verbosity level of the tracing for this router. + // +kubebuilder:validation:Enum=minimal;detailed + // +kubebuilder:default=minimal + TraceVerbosity otypes.TracingVerbosity `json:"traceVerbosity,omitempty" toml:"traceVerbosity,omitempty" yaml:"traceVerbosity,omitempty" export:"true"` +} + +// SetDefaults Default values for a RouterObservabilityConfig. +func (r *RouterObservabilityConfig) SetDefaults() { + r.TraceVerbosity = otypes.MinimalVerbosity } // +k8s:deepcopy-gen=true @@ -299,17 +312,18 @@ type Server struct { // ServerHealthCheck holds the HealthCheck configuration. type ServerHealthCheck struct { - Scheme string `json:"scheme,omitempty" toml:"scheme,omitempty" yaml:"scheme,omitempty" export:"true"` - Mode string `json:"mode,omitempty" toml:"mode,omitempty" yaml:"mode,omitempty" export:"true"` - Path string `json:"path,omitempty" toml:"path,omitempty" yaml:"path,omitempty" export:"true"` - Method string `json:"method,omitempty" toml:"method,omitempty" yaml:"method,omitempty" export:"true"` - Status int `json:"status,omitempty" toml:"status,omitempty" yaml:"status,omitempty" export:"true"` - Port int `json:"port,omitempty" toml:"port,omitempty,omitzero" yaml:"port,omitempty" export:"true"` - Interval ptypes.Duration `json:"interval,omitempty" toml:"interval,omitempty" yaml:"interval,omitempty" export:"true"` - Timeout ptypes.Duration `json:"timeout,omitempty" toml:"timeout,omitempty" yaml:"timeout,omitempty" export:"true"` - Hostname string `json:"hostname,omitempty" toml:"hostname,omitempty" yaml:"hostname,omitempty"` - FollowRedirects *bool `json:"followRedirects,omitempty" toml:"followRedirects,omitempty" yaml:"followRedirects,omitempty" export:"true"` - Headers map[string]string `json:"headers,omitempty" toml:"headers,omitempty" yaml:"headers,omitempty" export:"true"` + Scheme string `json:"scheme,omitempty" toml:"scheme,omitempty" yaml:"scheme,omitempty" export:"true"` + Mode string `json:"mode,omitempty" toml:"mode,omitempty" yaml:"mode,omitempty" export:"true"` + Path string `json:"path,omitempty" toml:"path,omitempty" yaml:"path,omitempty" export:"true"` + Method string `json:"method,omitempty" toml:"method,omitempty" yaml:"method,omitempty" export:"true"` + Status int `json:"status,omitempty" toml:"status,omitempty" yaml:"status,omitempty" export:"true"` + Port int `json:"port,omitempty" toml:"port,omitempty,omitzero" yaml:"port,omitempty" export:"true"` + Interval ptypes.Duration `json:"interval,omitempty" toml:"interval,omitempty" yaml:"interval,omitempty" export:"true"` + UnhealthyInterval *ptypes.Duration `json:"unhealthyInterval,omitempty" toml:"unhealthyInterval,omitempty" yaml:"unhealthyInterval,omitempty" export:"true"` + Timeout ptypes.Duration `json:"timeout,omitempty" toml:"timeout,omitempty" yaml:"timeout,omitempty" export:"true"` + Hostname string `json:"hostname,omitempty" toml:"hostname,omitempty" yaml:"hostname,omitempty"` + FollowRedirects *bool `json:"followRedirects,omitempty" toml:"followRedirects,omitempty" yaml:"followRedirects,omitempty" export:"true"` + Headers map[string]string `json:"headers,omitempty" toml:"headers,omitempty" yaml:"headers,omitempty" export:"true"` } // SetDefaults Default values for a HealthCheck. @@ -334,7 +348,7 @@ type ServersTransport struct { InsecureSkipVerify bool `description:"Disables SSL certificate verification." json:"insecureSkipVerify,omitempty" toml:"insecureSkipVerify,omitempty" yaml:"insecureSkipVerify,omitempty" export:"true"` RootCAs []types.FileOrContent `description:"Defines a list of CA certificates used to validate server certificates." json:"rootCAs,omitempty" toml:"rootCAs,omitempty" yaml:"rootCAs,omitempty"` Certificates traefiktls.Certificates `description:"Defines a list of client certificates for mTLS." json:"certificates,omitempty" toml:"certificates,omitempty" yaml:"certificates,omitempty" export:"true"` - MaxIdleConnsPerHost int `description:"If non-zero, controls the maximum idle (keep-alive) to keep per-host. If zero, DefaultMaxIdleConnsPerHost is used" json:"maxIdleConnsPerHost,omitempty" toml:"maxIdleConnsPerHost,omitempty" yaml:"maxIdleConnsPerHost,omitempty" export:"true"` + MaxIdleConnsPerHost int `description:"If non-zero, controls the maximum idle (keep-alive) to keep per-host. If zero, DefaultMaxIdleConnsPerHost is used. If negative, disables connection reuse." json:"maxIdleConnsPerHost,omitempty" toml:"maxIdleConnsPerHost,omitempty" yaml:"maxIdleConnsPerHost,omitempty" export:"true"` ForwardingTimeouts *ForwardingTimeouts `description:"Defines the timeouts for requests forwarded to the backend servers." json:"forwardingTimeouts,omitempty" toml:"forwardingTimeouts,omitempty" yaml:"forwardingTimeouts,omitempty" export:"true"` DisableHTTP2 bool `description:"Disables HTTP/2 for connections with backend servers." json:"disableHTTP2,omitempty" toml:"disableHTTP2,omitempty" yaml:"disableHTTP2,omitempty" export:"true"` PeerCertURI string `description:"Defines the URI used to match against SAN URI during the peer certificate verification." json:"peerCertURI,omitempty" toml:"peerCertURI,omitempty" yaml:"peerCertURI,omitempty" export:"true"` diff --git a/pkg/config/dynamic/middlewares.go b/pkg/config/dynamic/middlewares.go index 0f45e62de..02f371948 100644 --- a/pkg/config/dynamic/middlewares.go +++ b/pkg/config/dynamic/middlewares.go @@ -77,7 +77,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/v3.4/middlewares/http/addprefix/ +// More info: https://doc.traefik.io/traefik/v3.5/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 (/). @@ -89,7 +89,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/v3.4/middlewares/http/basicauth/ +// More info: https://doc.traefik.io/traefik/v3.5/middlewares/http/basicauth/ type BasicAuth struct { // Users is an array of authorized users. // Each user must be declared using the name:hashed-password format. @@ -104,7 +104,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/v3.4/middlewares/http/basicauth/#headerfield + // More info: https://doc.traefik.io/traefik/v3.5/middlewares/http/basicauth/#headerfield HeaderField string `json:"headerField,omitempty" toml:"headerField,omitempty" yaml:"headerField,omitempty" export:"true"` } @@ -112,7 +112,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/v3.4/middlewares/http/buffering/#maxrequestbodybytes +// More info: https://doc.traefik.io/traefik/v3.5/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. @@ -130,7 +130,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/v3.4/middlewares/http/buffering/#retryexpression + // More info: https://doc.traefik.io/traefik/v3.5/middlewares/http/buffering/#retryexpression RetryExpression string `json:"retryExpression,omitempty" toml:"retryExpression,omitempty" yaml:"retryExpression,omitempty" export:"true"` } @@ -147,7 +147,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/v3.4/middlewares/http/circuitbreaker/ +// More info: https://doc.traefik.io/traefik/v3.5/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"` @@ -197,7 +197,7 @@ func (c *Compress) SetDefaults() { // DigestAuth holds the digest auth middleware configuration. // This middleware restricts access to your services to known users. -// More info: https://doc.traefik.io/traefik/v3.4/middlewares/http/digestauth/ +// More info: https://doc.traefik.io/traefik/v3.5/middlewares/http/digestauth/ type DigestAuth struct { // Users defines the authorized users. // Each user should be declared using the name:realm:encoded-password format. @@ -210,7 +210,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/v3.4/middlewares/http/basicauth/#headerfield + // More info: https://doc.traefik.io/traefik/v3.5/middlewares/http/basicauth/#headerfield HeaderField string `json:"headerField,omitempty" toml:"headerField,omitempty" yaml:"headerField,omitempty" export:"true"` } @@ -241,7 +241,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/v3.4/middlewares/http/forwardauth/ +// More info: https://doc.traefik.io/traefik/v3.5/middlewares/http/forwardauth/ type ForwardAuth struct { // Address defines the authentication server address. Address string `json:"address,omitempty" toml:"address,omitempty" yaml:"address,omitempty"` @@ -252,7 +252,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/v3.4/middlewares/http/forwardauth/#authresponseheadersregex + // More info: https://doc.traefik.io/traefik/v3.5/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. @@ -260,7 +260,7 @@ type ForwardAuth struct { // AddAuthCookiesToResponse defines the list of cookies to copy from the authentication server response to the response. AddAuthCookiesToResponse []string `json:"addAuthCookiesToResponse,omitempty" toml:"addAuthCookiesToResponse,omitempty" yaml:"addAuthCookiesToResponse,omitempty" export:"true"` // HeaderField defines a header field to store the authenticated user. - // More info: https://doc.traefik.io/traefik/v3.4/middlewares/http/forwardauth/#headerfield + // More info: https://doc.traefik.io/traefik/v3.5/middlewares/http/forwardauth/#headerfield HeaderField string `json:"headerField,omitempty" toml:"headerField,omitempty" yaml:"headerField,omitempty" export:"true"` // ForwardBody defines whether to send the request body to the authentication server. ForwardBody bool `json:"forwardBody,omitempty" toml:"forwardBody,omitempty" yaml:"forwardBody,omitempty" export:"true"` @@ -295,7 +295,7 @@ type ClientTLS struct { // Headers holds the headers middleware configuration. // This middleware manages the requests and responses headers. -// More info: https://doc.traefik.io/traefik/v3.4/middlewares/http/headers/#customrequestheaders +// More info: https://doc.traefik.io/traefik/v3.5/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"` @@ -425,7 +425,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/v3.4/middlewares/http/ipallowlist/#ipstrategy +// More info: https://doc.traefik.io/traefik/v3.5/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). // +kubebuilder:validation:Minimum=0 @@ -480,7 +480,7 @@ func (s *IPStrategy) Get() (ip.Strategy, error) { // IPWhiteList holds the IP whitelist middleware configuration. // This middleware limits allowed requests based on the client IP. -// More info: https://doc.traefik.io/traefik/v3.4/middlewares/http/ipwhitelist/ +// More info: https://doc.traefik.io/traefik/v3.5/middlewares/http/ipwhitelist/ // Deprecated: please use IPAllowList instead. type IPWhiteList struct { // SourceRange defines the set of allowed IPs (or ranges of allowed IPs by using CIDR notation). Required. @@ -492,7 +492,7 @@ type IPWhiteList struct { // IPAllowList holds the IP allowlist middleware configuration. // This middleware limits allowed requests based on the client IP. -// More info: https://doc.traefik.io/traefik/v3.4/middlewares/http/ipallowlist/ +// More info: https://doc.traefik.io/traefik/v3.5/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"` @@ -506,7 +506,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/v3.4/middlewares/http/inflightreq/ +// More info: https://doc.traefik.io/traefik/v3.5/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). @@ -515,7 +515,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/v3.4/middlewares/http/inflightreq/#sourcecriterion + // More info: https://doc.traefik.io/traefik/v3.5/middlewares/http/inflightreq/#sourcecriterion SourceCriterion *SourceCriterion `json:"sourceCriterion,omitempty" toml:"sourceCriterion,omitempty" yaml:"sourceCriterion,omitempty" export:"true"` } @@ -523,7 +523,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/v3.4/middlewares/http/passtlsclientcert/ +// More info: https://doc.traefik.io/traefik/v3.5/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"` @@ -635,13 +635,13 @@ func (r *Redis) 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/v3.4/middlewares/http/redirectregex/#regex +// More info: https://doc.traefik.io/traefik/v3.5/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"` // Replacement defines how to modify the URL to have the new target URL. Replacement string `json:"replacement,omitempty" toml:"replacement,omitempty" yaml:"replacement,omitempty"` - // Permanent defines whether the redirection is permanent (301). + // Permanent defines whether the redirection is permanent (308). Permanent bool `json:"permanent,omitempty" toml:"permanent,omitempty" yaml:"permanent,omitempty" export:"true"` } @@ -649,13 +649,13 @@ 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/v3.4/middlewares/http/redirectscheme/ +// More info: https://doc.traefik.io/traefik/v3.5/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"` // Port defines the port of the new URL. Port string `json:"port,omitempty" toml:"port,omitempty" yaml:"port,omitempty" export:"true"` - // Permanent defines whether the redirection is permanent (301). + // Permanent defines whether the redirection is permanent (308). Permanent bool `json:"permanent,omitempty" toml:"permanent,omitempty" yaml:"permanent,omitempty" export:"true"` } @@ -663,7 +663,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/v3.4/middlewares/http/replacepath/ +// More info: https://doc.traefik.io/traefik/v3.5/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"` @@ -673,7 +673,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/v3.4/middlewares/http/replacepathregex/ +// More info: https://doc.traefik.io/traefik/v3.5/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"` @@ -686,7 +686,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/v3.4/middlewares/http/retry/ +// More info: https://doc.traefik.io/traefik/v3.5/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"` @@ -702,7 +702,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/v3.4/middlewares/http/stripprefix/ +// More info: https://doc.traefik.io/traefik/v3.5/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"` @@ -717,7 +717,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/v3.4/middlewares/http/stripprefixregex/ +// More info: https://doc.traefik.io/traefik/v3.5/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 eec8c7d38..207c90045 100644 --- a/pkg/config/dynamic/tcp_config.go +++ b/pkg/config/dynamic/tcp_config.go @@ -84,10 +84,12 @@ type RouterTCPTLSConfig struct { // TCPServersLoadBalancer holds the LoadBalancerService configuration. type TCPServersLoadBalancer struct { - ProxyProtocol *ProxyProtocol `json:"proxyProtocol,omitempty" toml:"proxyProtocol,omitempty" yaml:"proxyProtocol,omitempty" label:"allowEmpty" file:"allowEmpty" kv:"allowEmpty" export:"true"` - Servers []TCPServer `json:"servers,omitempty" toml:"servers,omitempty" yaml:"servers,omitempty" label-slice-as-struct:"server" export:"true"` - ServersTransport string `json:"serversTransport,omitempty" toml:"serversTransport,omitempty" yaml:"serversTransport,omitempty" export:"true"` + Servers []TCPServer `json:"servers,omitempty" toml:"servers,omitempty" yaml:"servers,omitempty" label-slice-as-struct:"server" export:"true"` + ServersTransport string `json:"serversTransport,omitempty" toml:"serversTransport,omitempty" yaml:"serversTransport,omitempty" export:"true"` + // ProxyProtocol holds the PROXY Protocol configuration. + // Deprecated: use ServersTransport to configure ProxyProtocol instead. + ProxyProtocol *ProxyProtocol `json:"proxyProtocol,omitempty" toml:"proxyProtocol,omitempty" yaml:"proxyProtocol,omitempty" label:"allowEmpty" file:"allowEmpty" kv:"allowEmpty" export:"true"` // TerminationDelay, corresponds to the deadline that the proxy sets, after one // of its connected peers indicates it has closed the writing capability of its // connection, to close the reading capability as well, hence fully terminating the @@ -126,12 +128,12 @@ type TCPServer struct { // +k8s:deepcopy-gen=true // ProxyProtocol holds the PROXY Protocol configuration. -// More info: https://doc.traefik.io/traefik/v3.4/routing/services/#proxy-protocol +// More info: https://doc.traefik.io/traefik/v3.5/routing/services/#proxy-protocol type ProxyProtocol struct { // Version defines the PROXY Protocol version to use. // +kubebuilder:validation:Minimum=1 // +kubebuilder:validation:Maximum=2 - Version int `json:"version,omitempty" toml:"version,omitempty" yaml:"version,omitempty" export:"true"` + Version int `description:"Defines the PROXY Protocol version to use." json:"version,omitempty" toml:"version,omitempty" yaml:"version,omitempty" export:"true"` } // SetDefaults Default values for a ProxyProtocol. @@ -145,6 +147,8 @@ func (p *ProxyProtocol) SetDefaults() { type TCPServersTransport struct { DialKeepAlive ptypes.Duration `description:"Defines the interval between keep-alive probes for an active network connection. If zero, keep-alive probes are sent with a default value (currently 15 seconds), if supported by the protocol and operating system. Network protocols or operating systems that do not support keep-alives ignore this field. If negative, keep-alive probes are disabled" json:"dialKeepAlive,omitempty" toml:"dialKeepAlive,omitempty" yaml:"dialKeepAlive,omitempty" export:"true"` DialTimeout ptypes.Duration `description:"Defines the amount of time to wait until a connection to a backend server can be established. If zero, no timeout exists." json:"dialTimeout,omitempty" toml:"dialTimeout,omitempty" yaml:"dialTimeout,omitempty" export:"true"` + // ProxyProtocol holds the PROXY Protocol configuration. + ProxyProtocol *ProxyProtocol `description:"Defines the PROXY Protocol configuration." json:"proxyProtocol,omitempty" toml:"proxyProtocol,omitempty" yaml:"proxyProtocol,omitempty" label:"allowEmpty" file:"allowEmpty" kv:"allowEmpty" export:"true"` // TerminationDelay, corresponds to the deadline that the proxy sets, after one // of its connected peers indicates it has closed the writing capability of its // connection, to close the reading capability as well, hence fully terminating the @@ -154,6 +158,13 @@ type TCPServersTransport struct { TLS *TLSClientConfig `description:"Defines the TLS configuration." json:"tls,omitempty" toml:"tls,omitempty" yaml:"tls,omitempty" label:"allowEmpty" file:"allowEmpty" kv:"allowEmpty" export:"true"` } +// SetDefaults sets the default values for a TCPServersTransport. +func (t *TCPServersTransport) SetDefaults() { + t.DialTimeout = ptypes.Duration(30 * time.Second) + t.DialKeepAlive = ptypes.Duration(15 * time.Second) + t.TerminationDelay = ptypes.Duration(100 * time.Millisecond) +} + // +k8s:deepcopy-gen=true // TLSClientConfig options to configure TLS communication between Traefik and the servers. @@ -165,10 +176,3 @@ type TLSClientConfig struct { PeerCertURI string `description:"Defines the URI used to match against SAN URI during the peer certificate verification." json:"peerCertURI,omitempty" toml:"peerCertURI,omitempty" yaml:"peerCertURI,omitempty" export:"true"` Spiffe *Spiffe `description:"Defines the SPIFFE TLS configuration." json:"spiffe,omitempty" toml:"spiffe,omitempty" yaml:"spiffe,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` } - -// SetDefaults sets the default values for a TCPServersTransport. -func (t *TCPServersTransport) SetDefaults() { - t.DialTimeout = ptypes.Duration(30 * time.Second) - t.DialKeepAlive = ptypes.Duration(15 * time.Second) - t.TerminationDelay = ptypes.Duration(100 * time.Millisecond) -} diff --git a/pkg/config/dynamic/tcp_middlewares.go b/pkg/config/dynamic/tcp_middlewares.go index cfa401132..3d9624d08 100644 --- a/pkg/config/dynamic/tcp_middlewares.go +++ b/pkg/config/dynamic/tcp_middlewares.go @@ -15,7 +15,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/v3.4/middlewares/tcp/inflightconn/ +// More info: https://doc.traefik.io/traefik/v3.5/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. @@ -36,7 +36,7 @@ type TCPIPWhiteList struct { // TCPIPAllowList holds the TCP IPAllowList middleware configuration. // This middleware limits allowed requests based on the client IP. -// More info: https://doc.traefik.io/traefik/v3.4/middlewares/tcp/ipallowlist/ +// More info: https://doc.traefik.io/traefik/v3.5/middlewares/tcp/ipallowlist/ type TCPIPAllowList struct { // SourceRange defines the allowed IPs (or ranges of allowed IPs by using CIDR notation). SourceRange []string `json:"sourceRange,omitempty" toml:"sourceRange,omitempty" yaml:"sourceRange,omitempty"` diff --git a/pkg/config/dynamic/zz_generated.deepcopy.go b/pkg/config/dynamic/zz_generated.deepcopy.go index 2a680897c..fb4cea3e9 100644 --- a/pkg/config/dynamic/zz_generated.deepcopy.go +++ b/pkg/config/dynamic/zz_generated.deepcopy.go @@ -1335,13 +1335,13 @@ func (in *RouterObservabilityConfig) DeepCopyInto(out *RouterObservabilityConfig *out = new(bool) **out = **in } - if in.Tracing != nil { - in, out := &in.Tracing, &out.Tracing + if in.Metrics != nil { + in, out := &in.Metrics, &out.Metrics *out = new(bool) **out = **in } - if in.Metrics != nil { - in, out := &in.Metrics, &out.Metrics + if in.Tracing != nil { + in, out := &in.Tracing, &out.Tracing *out = new(bool) **out = **in } @@ -1428,6 +1428,11 @@ func (in *Server) DeepCopy() *Server { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ServerHealthCheck) DeepCopyInto(out *ServerHealthCheck) { *out = *in + if in.UnhealthyInterval != nil { + in, out := &in.UnhealthyInterval, &out.UnhealthyInterval + *out = new(paersertypes.Duration) + **out = **in + } if in.FollowRedirects != nil { in, out := &in.FollowRedirects, &out.FollowRedirects *out = new(bool) @@ -1924,16 +1929,16 @@ func (in *TCPServer) DeepCopy() *TCPServer { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *TCPServersLoadBalancer) DeepCopyInto(out *TCPServersLoadBalancer) { *out = *in - if in.ProxyProtocol != nil { - in, out := &in.ProxyProtocol, &out.ProxyProtocol - *out = new(ProxyProtocol) - **out = **in - } if in.Servers != nil { in, out := &in.Servers, &out.Servers *out = make([]TCPServer, len(*in)) copy(*out, *in) } + if in.ProxyProtocol != nil { + in, out := &in.ProxyProtocol, &out.ProxyProtocol + *out = new(ProxyProtocol) + **out = **in + } if in.TerminationDelay != nil { in, out := &in.TerminationDelay, &out.TerminationDelay *out = new(int) @@ -1955,6 +1960,11 @@ func (in *TCPServersLoadBalancer) DeepCopy() *TCPServersLoadBalancer { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *TCPServersTransport) DeepCopyInto(out *TCPServersTransport) { *out = *in + if in.ProxyProtocol != nil { + in, out := &in.ProxyProtocol, &out.ProxyProtocol + *out = new(ProxyProtocol) + **out = **in + } if in.TLS != nil { in, out := &in.TLS, &out.TLS *out = new(TLSClientConfig) diff --git a/pkg/config/label/label_test.go b/pkg/config/label/label_test.go index bd763ba08..b17222410 100644 --- a/pkg/config/label/label_test.go +++ b/pkg/config/label/label_test.go @@ -162,6 +162,7 @@ func TestDecodeConfiguration(t *testing.T) { "traefik.http.services.Service0.loadbalancer.healthcheck.headers.name1": "foobar", "traefik.http.services.Service0.loadbalancer.healthcheck.hostname": "foobar", "traefik.http.services.Service0.loadbalancer.healthcheck.interval": "1s", + "traefik.http.services.Service0.loadbalancer.healthcheck.unhealthyinterval": "1s", "traefik.http.services.Service0.loadbalancer.healthcheck.path": "foobar", "traefik.http.services.Service0.loadbalancer.healthcheck.method": "foobar", "traefik.http.services.Service0.loadbalancer.healthcheck.status": "401", @@ -186,6 +187,7 @@ func TestDecodeConfiguration(t *testing.T) { "traefik.http.services.Service1.loadbalancer.healthcheck.headers.name1": "foobar", "traefik.http.services.Service1.loadbalancer.healthcheck.hostname": "foobar", "traefik.http.services.Service1.loadbalancer.healthcheck.interval": "1s", + "traefik.http.services.Service1.loadbalancer.healthcheck.unhealthyinterval": "1s", "traefik.http.services.Service1.loadbalancer.healthcheck.path": "foobar", "traefik.http.services.Service1.loadbalancer.healthcheck.method": "foobar", "traefik.http.services.Service1.loadbalancer.healthcheck.status": "401", @@ -701,15 +703,16 @@ func TestDecodeConfiguration(t *testing.T) { }, }, HealthCheck: &dynamic.ServerHealthCheck{ - Scheme: "foobar", - Mode: "foobar", - Path: "foobar", - Method: "foobar", - Status: 401, - Port: 42, - Interval: ptypes.Duration(time.Second), - Timeout: ptypes.Duration(time.Second), - Hostname: "foobar", + Scheme: "foobar", + Mode: "foobar", + Path: "foobar", + Method: "foobar", + Status: 401, + Port: 42, + Interval: ptypes.Duration(time.Second), + UnhealthyInterval: pointer(ptypes.Duration(time.Second)), + Timeout: ptypes.Duration(time.Second), + Hostname: "foobar", Headers: map[string]string{ "name0": "foobar", "name1": "foobar", @@ -735,15 +738,16 @@ func TestDecodeConfiguration(t *testing.T) { }, }, HealthCheck: &dynamic.ServerHealthCheck{ - Scheme: "foobar", - Mode: "foobar", - Path: "foobar", - Method: "foobar", - Status: 401, - Port: 42, - Interval: ptypes.Duration(time.Second), - Timeout: ptypes.Duration(time.Second), - Hostname: "foobar", + Scheme: "foobar", + Mode: "foobar", + Path: "foobar", + Method: "foobar", + Status: 401, + Port: 42, + Interval: ptypes.Duration(time.Second), + UnhealthyInterval: pointer(ptypes.Duration(time.Second)), + Timeout: ptypes.Duration(time.Second), + Hostname: "foobar", Headers: map[string]string{ "name0": "foobar", "name1": "foobar", @@ -1244,14 +1248,15 @@ func TestEncodeConfiguration(t *testing.T) { }, }, HealthCheck: &dynamic.ServerHealthCheck{ - Scheme: "foobar", - Path: "foobar", - Method: "foobar", - Status: 401, - Port: 42, - Interval: ptypes.Duration(time.Second), - Timeout: ptypes.Duration(time.Second), - Hostname: "foobar", + Scheme: "foobar", + Path: "foobar", + Method: "foobar", + Status: 401, + Port: 42, + Interval: ptypes.Duration(time.Second), + UnhealthyInterval: pointer(ptypes.Duration(time.Second)), + Timeout: ptypes.Duration(time.Second), + Hostname: "foobar", Headers: map[string]string{ "name0": "foobar", "name1": "foobar", @@ -1276,14 +1281,15 @@ func TestEncodeConfiguration(t *testing.T) { }, }, HealthCheck: &dynamic.ServerHealthCheck{ - Scheme: "foobar", - Path: "foobar", - Method: "foobar", - Status: 401, - Port: 42, - Interval: ptypes.Duration(time.Second), - Timeout: ptypes.Duration(time.Second), - Hostname: "foobar", + Scheme: "foobar", + Path: "foobar", + Method: "foobar", + Status: 401, + Port: 42, + Interval: ptypes.Duration(time.Second), + UnhealthyInterval: pointer(ptypes.Duration(time.Second)), + Timeout: ptypes.Duration(time.Second), + Hostname: "foobar", Headers: map[string]string{ "name0": "foobar", "name1": "foobar", @@ -1471,6 +1477,7 @@ func TestEncodeConfiguration(t *testing.T) { "traefik.HTTP.Services.Service0.LoadBalancer.HealthCheck.Headers.name1": "foobar", "traefik.HTTP.Services.Service0.LoadBalancer.HealthCheck.Hostname": "foobar", "traefik.HTTP.Services.Service0.LoadBalancer.HealthCheck.Interval": "1000000000", + "traefik.HTTP.Services.Service0.LoadBalancer.HealthCheck.UnhealthyInterval": "1000000000", "traefik.HTTP.Services.Service0.LoadBalancer.HealthCheck.Path": "foobar", "traefik.HTTP.Services.Service0.LoadBalancer.HealthCheck.Method": "foobar", "traefik.HTTP.Services.Service0.LoadBalancer.HealthCheck.Status": "401", @@ -1495,6 +1502,7 @@ func TestEncodeConfiguration(t *testing.T) { "traefik.HTTP.Services.Service1.LoadBalancer.HealthCheck.Headers.name1": "foobar", "traefik.HTTP.Services.Service1.LoadBalancer.HealthCheck.Hostname": "foobar", "traefik.HTTP.Services.Service1.LoadBalancer.HealthCheck.Interval": "1000000000", + "traefik.HTTP.Services.Service1.LoadBalancer.HealthCheck.UnhealthyInterval": "1000000000", "traefik.HTTP.Services.Service1.LoadBalancer.HealthCheck.Path": "foobar", "traefik.HTTP.Services.Service1.LoadBalancer.HealthCheck.Method": "foobar", "traefik.HTTP.Services.Service1.LoadBalancer.HealthCheck.Status": "401", diff --git a/pkg/config/runtime/runtime.go b/pkg/config/runtime/runtime.go index d67233887..7e8db0a5e 100644 --- a/pkg/config/runtime/runtime.go +++ b/pkg/config/runtime/runtime.go @@ -6,7 +6,7 @@ import ( "github.com/rs/zerolog/log" "github.com/traefik/traefik/v3/pkg/config/dynamic" - "github.com/traefik/traefik/v3/pkg/logs" + "github.com/traefik/traefik/v3/pkg/observability/logs" ) // Status of the router/service. @@ -26,9 +26,10 @@ const ( type Configuration struct { Routers map[string]*RouterInfo `json:"routers,omitempty"` Middlewares map[string]*MiddlewareInfo `json:"middlewares,omitempty"` - TCPMiddlewares map[string]*TCPMiddlewareInfo `json:"tcpMiddlewares,omitempty"` Services map[string]*ServiceInfo `json:"services,omitempty"` + Models map[string]*dynamic.Model `json:"-"` TCPRouters map[string]*TCPRouterInfo `json:"tcpRouters,omitempty"` + TCPMiddlewares map[string]*TCPMiddlewareInfo `json:"tcpMiddlewares,omitempty"` TCPServices map[string]*TCPServiceInfo `json:"tcpServices,omitempty"` UDPRouters map[string]*UDPRouterInfo `json:"udpRouters,omitempty"` UDPServices map[string]*UDPServiceInfo `json:"udpServices,omitempty"` @@ -66,6 +67,8 @@ func NewConfig(conf dynamic.Configuration) *Configuration { runtimeConfig.Middlewares[k] = &MiddlewareInfo{Middleware: v, Status: StatusEnabled} } } + + runtimeConfig.Models = conf.HTTP.Models } if conf.TCP != nil { diff --git a/pkg/config/runtime/runtime_http.go b/pkg/config/runtime/runtime_http.go index 97a89f066..69d7e540f 100644 --- a/pkg/config/runtime/runtime_http.go +++ b/pkg/config/runtime/runtime_http.go @@ -10,7 +10,7 @@ import ( "github.com/rs/zerolog/log" "github.com/traefik/traefik/v3/pkg/config/dynamic" - "github.com/traefik/traefik/v3/pkg/logs" + "github.com/traefik/traefik/v3/pkg/observability/logs" ) // GetRoutersByEntryPoints returns all the http routers by entry points name and routers name. diff --git a/pkg/config/runtime/runtime_http_test.go b/pkg/config/runtime/runtime_http_test.go index 57938b616..ad3f44618 100644 --- a/pkg/config/runtime/runtime_http_test.go +++ b/pkg/config/runtime/runtime_http_test.go @@ -1,7 +1,6 @@ package runtime import ( - "context" "testing" "github.com/stretchr/testify/assert" @@ -211,7 +210,7 @@ func TestGetRoutersByEntryPoints(t *testing.T) { t.Run(test.desc, func(t *testing.T) { t.Parallel() runtimeConfig := NewConfig(test.conf) - actual := runtimeConfig.GetRoutersByEntryPoints(context.Background(), test.entryPoints, false) + actual := runtimeConfig.GetRoutersByEntryPoints(t.Context(), test.entryPoints, false) assert.Equal(t, test.expected, actual) }) } diff --git a/pkg/config/runtime/runtime_tcp.go b/pkg/config/runtime/runtime_tcp.go index 1c213f7b1..b8bc08981 100644 --- a/pkg/config/runtime/runtime_tcp.go +++ b/pkg/config/runtime/runtime_tcp.go @@ -8,7 +8,7 @@ import ( "github.com/rs/zerolog/log" "github.com/traefik/traefik/v3/pkg/config/dynamic" - "github.com/traefik/traefik/v3/pkg/logs" + "github.com/traefik/traefik/v3/pkg/observability/logs" ) // GetTCPRoutersByEntryPoints returns all the tcp routers by entry points name and routers name. diff --git a/pkg/config/runtime/runtime_tcp_test.go b/pkg/config/runtime/runtime_tcp_test.go index c4726ac72..644e57384 100644 --- a/pkg/config/runtime/runtime_tcp_test.go +++ b/pkg/config/runtime/runtime_tcp_test.go @@ -1,7 +1,6 @@ package runtime import ( - "context" "testing" "github.com/stretchr/testify/assert" @@ -211,7 +210,7 @@ func TestGetTCPRoutersByEntryPoints(t *testing.T) { t.Run(test.desc, func(t *testing.T) { t.Parallel() runtimeConfig := NewConfig(test.conf) - actual := runtimeConfig.GetTCPRoutersByEntryPoints(context.Background(), test.entryPoints) + actual := runtimeConfig.GetTCPRoutersByEntryPoints(t.Context(), test.entryPoints) assert.Equal(t, test.expected, actual) }) } diff --git a/pkg/config/runtime/runtime_udp.go b/pkg/config/runtime/runtime_udp.go index e1a867506..912f986f1 100644 --- a/pkg/config/runtime/runtime_udp.go +++ b/pkg/config/runtime/runtime_udp.go @@ -8,7 +8,7 @@ import ( "github.com/rs/zerolog/log" "github.com/traefik/traefik/v3/pkg/config/dynamic" - "github.com/traefik/traefik/v3/pkg/logs" + "github.com/traefik/traefik/v3/pkg/observability/logs" ) // GetUDPRoutersByEntryPoints returns all the UDP routers by entry points name and routers name. diff --git a/pkg/config/runtime/runtime_udp_test.go b/pkg/config/runtime/runtime_udp_test.go index 5531bb934..67bcb8602 100644 --- a/pkg/config/runtime/runtime_udp_test.go +++ b/pkg/config/runtime/runtime_udp_test.go @@ -1,7 +1,6 @@ package runtime import ( - "context" "testing" "github.com/stretchr/testify/assert" @@ -192,7 +191,7 @@ func TestGetUDPRoutersByEntryPoints(t *testing.T) { t.Run(test.desc, func(t *testing.T) { t.Parallel() runtimeConfig := NewConfig(test.conf) - actual := runtimeConfig.GetUDPRoutersByEntryPoints(context.Background(), test.entryPoints) + actual := runtimeConfig.GetUDPRoutersByEntryPoints(t.Context(), test.entryPoints) assert.Equal(t, test.expected, actual) }) } diff --git a/pkg/config/static/entrypoints.go b/pkg/config/static/entrypoints.go index d98cdb1b3..ed1eb008b 100644 --- a/pkg/config/static/entrypoints.go +++ b/pkg/config/static/entrypoints.go @@ -7,6 +7,7 @@ import ( "strings" ptypes "github.com/traefik/paerser/types" + otypes "github.com/traefik/traefik/v3/pkg/observability/types" "github.com/traefik/traefik/v3/pkg/types" ) @@ -165,15 +166,17 @@ func (u *UDPConfig) SetDefaults() { // ObservabilityConfig holds the observability configuration for an entry point. type ObservabilityConfig struct { - AccessLogs *bool `json:"accessLogs,omitempty" toml:"accessLogs,omitempty" yaml:"accessLogs,omitempty" export:"true"` - Tracing *bool `json:"tracing,omitempty" toml:"tracing,omitempty" yaml:"tracing,omitempty" export:"true"` - Metrics *bool `json:"metrics,omitempty" toml:"metrics,omitempty" yaml:"metrics,omitempty" export:"true"` + AccessLogs *bool `description:"Enables access-logs for this entryPoint." json:"accessLogs,omitempty" toml:"accessLogs,omitempty" yaml:"accessLogs,omitempty" export:"true"` + Metrics *bool `description:"Enables metrics for this entryPoint." json:"metrics,omitempty" toml:"metrics,omitempty" yaml:"metrics,omitempty" export:"true"` + Tracing *bool `description:"Enables tracing for this entryPoint." json:"tracing,omitempty" toml:"tracing,omitempty" yaml:"tracing,omitempty" export:"true"` + TraceVerbosity otypes.TracingVerbosity `description:"Defines the tracing verbosity level for this entryPoint." json:"traceVerbosity,omitempty" toml:"traceVerbosity,omitempty" yaml:"traceVerbosity,omitempty" export:"true"` } // SetDefaults sets the default values. func (o *ObservabilityConfig) SetDefaults() { defaultValue := true o.AccessLogs = &defaultValue - o.Tracing = &defaultValue o.Metrics = &defaultValue + o.Tracing = &defaultValue + o.TraceVerbosity = otypes.MinimalVerbosity } diff --git a/pkg/config/static/experimental.go b/pkg/config/static/experimental.go index f88564637..dba89fec8 100644 --- a/pkg/config/static/experimental.go +++ b/pkg/config/static/experimental.go @@ -4,11 +4,12 @@ import "github.com/traefik/traefik/v3/pkg/plugins" // Experimental experimental Traefik features. type Experimental struct { - Plugins map[string]plugins.Descriptor `description:"Plugins configuration." json:"plugins,omitempty" toml:"plugins,omitempty" yaml:"plugins,omitempty" export:"true"` - LocalPlugins map[string]plugins.LocalDescriptor `description:"Local plugins configuration." json:"localPlugins,omitempty" toml:"localPlugins,omitempty" yaml:"localPlugins,omitempty" export:"true"` - AbortOnPluginFailure bool `description:"Defines whether all plugins must be loaded successfully for Traefik to start." json:"abortOnPluginFailure,omitempty" toml:"abortOnPluginFailure,omitempty" yaml:"abortOnPluginFailure,omitempty" export:"true"` - FastProxy *FastProxyConfig `description:"Enables the FastProxy implementation." json:"fastProxy,omitempty" toml:"fastProxy,omitempty" yaml:"fastProxy,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` - OTLPLogs bool `description:"Enables the OpenTelemetry logs integration." json:"otlplogs,omitempty" toml:"otlplogs,omitempty" yaml:"otlplogs,omitempty" export:"true"` + Plugins map[string]plugins.Descriptor `description:"Plugins configuration." json:"plugins,omitempty" toml:"plugins,omitempty" yaml:"plugins,omitempty" export:"true"` + LocalPlugins map[string]plugins.LocalDescriptor `description:"Local plugins configuration." json:"localPlugins,omitempty" toml:"localPlugins,omitempty" yaml:"localPlugins,omitempty" export:"true"` + AbortOnPluginFailure bool `description:"Defines whether all plugins must be loaded successfully for Traefik to start." json:"abortOnPluginFailure,omitempty" toml:"abortOnPluginFailure,omitempty" yaml:"abortOnPluginFailure,omitempty" export:"true"` + FastProxy *FastProxyConfig `description:"Enables the FastProxy implementation." json:"fastProxy,omitempty" toml:"fastProxy,omitempty" yaml:"fastProxy,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` + OTLPLogs bool `description:"Enables the OpenTelemetry logs integration." json:"otlplogs,omitempty" toml:"otlplogs,omitempty" yaml:"otlplogs,omitempty" export:"true"` + KubernetesIngressNGINX bool `description:"Allow the Kubernetes Ingress NGINX provider usage." json:"kubernetesIngressNGINX,omitempty" toml:"kubernetesIngressNGINX,omitempty" yaml:"kubernetesIngressNGINX,omitempty" export:"true"` // Deprecated: KubernetesGateway provider is not an experimental feature starting with v3.1. Please remove its usage from the static configuration. KubernetesGateway bool `description:"(Deprecated) Allow the Kubernetes gateway api provider usage." json:"kubernetesGateway,omitempty" toml:"kubernetesGateway,omitempty" yaml:"kubernetesGateway,omitempty" export:"true"` diff --git a/pkg/config/static/static_config.go b/pkg/config/static/static_config.go index af60efbb8..e64118963 100644 --- a/pkg/config/static/static_config.go +++ b/pkg/config/static/static_config.go @@ -11,7 +11,8 @@ import ( "github.com/rs/zerolog" "github.com/rs/zerolog/log" ptypes "github.com/traefik/paerser/types" - "github.com/traefik/traefik/v3/pkg/logs" + "github.com/traefik/traefik/v3/pkg/observability/logs" + otypes "github.com/traefik/traefik/v3/pkg/observability/types" "github.com/traefik/traefik/v3/pkg/ping" acmeprovider "github.com/traefik/traefik/v3/pkg/provider/acme" "github.com/traefik/traefik/v3/pkg/provider/consulcatalog" @@ -22,12 +23,14 @@ import ( "github.com/traefik/traefik/v3/pkg/provider/kubernetes/crd" "github.com/traefik/traefik/v3/pkg/provider/kubernetes/gateway" "github.com/traefik/traefik/v3/pkg/provider/kubernetes/ingress" + ingressnginx "github.com/traefik/traefik/v3/pkg/provider/kubernetes/ingress-nginx" "github.com/traefik/traefik/v3/pkg/provider/kv/consul" "github.com/traefik/traefik/v3/pkg/provider/kv/etcd" "github.com/traefik/traefik/v3/pkg/provider/kv/redis" "github.com/traefik/traefik/v3/pkg/provider/kv/zk" "github.com/traefik/traefik/v3/pkg/provider/nomad" "github.com/traefik/traefik/v3/pkg/provider/rest" + "github.com/traefik/traefik/v3/pkg/tls" "github.com/traefik/traefik/v3/pkg/types" ) @@ -62,13 +65,13 @@ type Configuration struct { EntryPoints EntryPoints `description:"Entry points definition." json:"entryPoints,omitempty" toml:"entryPoints,omitempty" yaml:"entryPoints,omitempty" export:"true"` Providers *Providers `description:"Providers configuration." json:"providers,omitempty" toml:"providers,omitempty" yaml:"providers,omitempty" export:"true"` - API *API `description:"Enable api/dashboard." json:"api,omitempty" toml:"api,omitempty" yaml:"api,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` - Metrics *types.Metrics `description:"Enable a metrics exporter." json:"metrics,omitempty" toml:"metrics,omitempty" yaml:"metrics,omitempty" export:"true"` - Ping *ping.Handler `description:"Enable ping." json:"ping,omitempty" toml:"ping,omitempty" yaml:"ping,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` + API *API `description:"Enable api/dashboard." json:"api,omitempty" toml:"api,omitempty" yaml:"api,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` + Metrics *otypes.Metrics `description:"Enable a metrics exporter." json:"metrics,omitempty" toml:"metrics,omitempty" yaml:"metrics,omitempty" export:"true"` + Ping *ping.Handler `description:"Enable ping." json:"ping,omitempty" toml:"ping,omitempty" yaml:"ping,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` - Log *types.TraefikLog `description:"Traefik log settings." json:"log,omitempty" toml:"log,omitempty" yaml:"log,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` - AccessLog *types.AccessLog `description:"Access log settings." json:"accessLog,omitempty" toml:"accessLog,omitempty" yaml:"accessLog,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` - Tracing *Tracing `description:"Tracing configuration." json:"tracing,omitempty" toml:"tracing,omitempty" yaml:"tracing,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` + Log *otypes.TraefikLog `description:"Traefik log settings." json:"log,omitempty" toml:"log,omitempty" yaml:"log,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` + AccessLog *otypes.AccessLog `description:"Access log settings." json:"accessLog,omitempty" toml:"accessLog,omitempty" yaml:"accessLog,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` + Tracing *Tracing `description:"Tracing configuration." json:"tracing,omitempty" toml:"tracing,omitempty" yaml:"tracing,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` HostResolver *types.HostResolverConfig `description:"Enable CNAME Flattening." json:"hostResolver,omitempty" toml:"hostResolver,omitempty" yaml:"hostResolver,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` @@ -80,6 +83,8 @@ type Configuration struct { Core *Core `description:"Core controls." json:"core,omitempty" toml:"core,omitempty" yaml:"core,omitempty" export:"true"` Spiffe *SpiffeClientConfig `description:"SPIFFE integration configuration." json:"spiffe,omitempty" toml:"spiffe,omitempty" yaml:"spiffe,omitempty" export:"true"` + + OCSP *tls.OCSPConfig `description:"OCSP configuration." json:"ocsp,omitempty" toml:"ocsp,omitempty" yaml:"ocsp,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` } // Core configures Traefik core behavior. @@ -108,13 +113,14 @@ type CertificateResolver struct { type Global struct { CheckNewVersion bool `description:"Periodically check if a new version has been released." json:"checkNewVersion,omitempty" toml:"checkNewVersion,omitempty" yaml:"checkNewVersion,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` SendAnonymousUsage bool `description:"Periodically send anonymous usage statistics. If the option is not specified, it will be disabled by default." json:"sendAnonymousUsage,omitempty" toml:"sendAnonymousUsage,omitempty" yaml:"sendAnonymousUsage,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` + UpdaterCallbacks []string `description:"Callback urls for updater script (example: https://localhost:8080/callback)" json:"updaterCallbacks,omitempty" toml:"updaterCallbacks,omitempty" yaml:"updaterCallbacks,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` } // ServersTransport options to configure communication between Traefik and the servers. type ServersTransport struct { InsecureSkipVerify bool `description:"Disable SSL certificate verification." json:"insecureSkipVerify,omitempty" toml:"insecureSkipVerify,omitempty" yaml:"insecureSkipVerify,omitempty" export:"true"` RootCAs []types.FileOrContent `description:"Add cert file for self-signed certificate." json:"rootCAs,omitempty" toml:"rootCAs,omitempty" yaml:"rootCAs,omitempty"` - MaxIdleConnsPerHost int `description:"If non-zero, controls the maximum idle (keep-alive) to keep per-host. If zero, DefaultMaxIdleConnsPerHost is used" json:"maxIdleConnsPerHost,omitempty" toml:"maxIdleConnsPerHost,omitempty" yaml:"maxIdleConnsPerHost,omitempty" export:"true"` + MaxIdleConnsPerHost int `description:"If non-zero, controls the maximum idle (keep-alive) to keep per-host. If zero, DefaultMaxIdleConnsPerHost is used. If negative, disables connection reuse." json:"maxIdleConnsPerHost,omitempty" toml:"maxIdleConnsPerHost,omitempty" yaml:"maxIdleConnsPerHost,omitempty" export:"true"` ForwardingTimeouts *ForwardingTimeouts `description:"Timeouts for requests forwarded to the backend servers." json:"forwardingTimeouts,omitempty" toml:"forwardingTimeouts,omitempty" yaml:"forwardingTimeouts,omitempty" export:"true"` Spiffe *Spiffe `description:"Defines the SPIFFE configuration." json:"spiffe,omitempty" toml:"spiffe,omitempty" yaml:"spiffe,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` } @@ -201,14 +207,14 @@ func (a *LifeCycle) SetDefaults() { // Tracing holds the tracing configuration. type Tracing struct { - ServiceName string `description:"Sets the name for this service." json:"serviceName,omitempty" toml:"serviceName,omitempty" yaml:"serviceName,omitempty" export:"true"` - ResourceAttributes map[string]string `description:"Defines additional resource attributes (key:value)." json:"resourceAttributes,omitempty" toml:"resourceAttributes,omitempty" yaml:"resourceAttributes,omitempty" export:"true"` - CapturedRequestHeaders []string `description:"Request headers to add as attributes for server and client spans." json:"capturedRequestHeaders,omitempty" toml:"capturedRequestHeaders,omitempty" yaml:"capturedRequestHeaders,omitempty" export:"true"` - CapturedResponseHeaders []string `description:"Response headers to add as attributes for server and client spans." json:"capturedResponseHeaders,omitempty" toml:"capturedResponseHeaders,omitempty" yaml:"capturedResponseHeaders,omitempty" export:"true"` - SafeQueryParams []string `description:"Query params to not redact." json:"safeQueryParams,omitempty" toml:"safeQueryParams,omitempty" yaml:"safeQueryParams,omitempty" export:"true"` - SampleRate float64 `description:"Sets the rate between 0.0 and 1.0 of requests to trace." json:"sampleRate,omitempty" toml:"sampleRate,omitempty" yaml:"sampleRate,omitempty" export:"true"` - AddInternals bool `description:"Enables tracing for internal services (ping, dashboard, etc...)." json:"addInternals,omitempty" toml:"addInternals,omitempty" yaml:"addInternals,omitempty" export:"true"` - OTLP *types.OTelTracing `description:"Settings for OpenTelemetry." json:"otlp,omitempty" toml:"otlp,omitempty" yaml:"otlp,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` + ServiceName string `description:"Defines the service name resource attribute." json:"serviceName,omitempty" toml:"serviceName,omitempty" yaml:"serviceName,omitempty" export:"true"` + ResourceAttributes map[string]string `description:"Defines additional resource attributes (key:value)." json:"resourceAttributes,omitempty" toml:"resourceAttributes,omitempty" yaml:"resourceAttributes,omitempty" export:"true"` + CapturedRequestHeaders []string `description:"Request headers to add as attributes for server and client spans." json:"capturedRequestHeaders,omitempty" toml:"capturedRequestHeaders,omitempty" yaml:"capturedRequestHeaders,omitempty" export:"true"` + CapturedResponseHeaders []string `description:"Response headers to add as attributes for server and client spans." json:"capturedResponseHeaders,omitempty" toml:"capturedResponseHeaders,omitempty" yaml:"capturedResponseHeaders,omitempty" export:"true"` + SafeQueryParams []string `description:"Query params to not redact." json:"safeQueryParams,omitempty" toml:"safeQueryParams,omitempty" yaml:"safeQueryParams,omitempty" export:"true"` + SampleRate float64 `description:"Sets the rate between 0.0 and 1.0 of requests to trace." json:"sampleRate,omitempty" toml:"sampleRate,omitempty" yaml:"sampleRate,omitempty" export:"true"` + AddInternals bool `description:"Enables tracing for internal services (ping, dashboard, etc...)." json:"addInternals,omitempty" toml:"addInternals,omitempty" yaml:"addInternals,omitempty" export:"true"` + OTLP *otypes.OTelTracing `description:"Settings for OpenTelemetry." json:"otlp,omitempty" toml:"otlp,omitempty" yaml:"otlp,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` // Deprecated: please use ResourceAttributes instead. GlobalAttributes map[string]string `description:"(Deprecated) Defines additional resource attributes (key:value)." json:"globalAttributes,omitempty" toml:"globalAttributes,omitempty" yaml:"globalAttributes,omitempty" export:"true"` @@ -219,7 +225,7 @@ func (t *Tracing) SetDefaults() { t.ServiceName = "traefik" t.SampleRate = 1.0 - t.OTLP = &types.OTelTracing{} + t.OTLP = &otypes.OTelTracing{} t.OTLP.SetDefaults() } @@ -227,22 +233,22 @@ func (t *Tracing) SetDefaults() { type Providers struct { ProvidersThrottleDuration ptypes.Duration `description:"Backends throttle duration: minimum duration between 2 events from providers before applying a new configuration. It avoids unnecessary reloads if multiples events are sent in a short amount of time." json:"providersThrottleDuration,omitempty" toml:"providersThrottleDuration,omitempty" yaml:"providersThrottleDuration,omitempty" export:"true"` - Docker *docker.Provider `description:"Enable Docker backend with default settings." json:"docker,omitempty" toml:"docker,omitempty" yaml:"docker,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` - Swarm *docker.SwarmProvider `description:"Enable Docker Swarm backend with default settings." json:"swarm,omitempty" toml:"swarm,omitempty" yaml:"swarm,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` - - File *file.Provider `description:"Enable File backend with default settings." json:"file,omitempty" toml:"file,omitempty" yaml:"file,omitempty" export:"true"` - KubernetesIngress *ingress.Provider `description:"Enable Kubernetes backend with default settings." json:"kubernetesIngress,omitempty" toml:"kubernetesIngress,omitempty" yaml:"kubernetesIngress,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` - KubernetesCRD *crd.Provider `description:"Enable Kubernetes backend with default settings." json:"kubernetesCRD,omitempty" toml:"kubernetesCRD,omitempty" yaml:"kubernetesCRD,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` - KubernetesGateway *gateway.Provider `description:"Enable Kubernetes gateway api provider with default settings." json:"kubernetesGateway,omitempty" toml:"kubernetesGateway,omitempty" yaml:"kubernetesGateway,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` - Rest *rest.Provider `description:"Enable Rest backend with default settings." json:"rest,omitempty" toml:"rest,omitempty" yaml:"rest,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` - ConsulCatalog *consulcatalog.ProviderBuilder `description:"Enable ConsulCatalog backend with default settings." json:"consulCatalog,omitempty" toml:"consulCatalog,omitempty" yaml:"consulCatalog,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` - Nomad *nomad.ProviderBuilder `description:"Enable Nomad backend with default settings." json:"nomad,omitempty" toml:"nomad,omitempty" yaml:"nomad,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` - Ecs *ecs.Provider `description:"Enable AWS ECS backend with default settings." json:"ecs,omitempty" toml:"ecs,omitempty" yaml:"ecs,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` - Consul *consul.ProviderBuilder `description:"Enable Consul backend with default settings." json:"consul,omitempty" toml:"consul,omitempty" yaml:"consul,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` - Etcd *etcd.Provider `description:"Enable Etcd backend with default settings." json:"etcd,omitempty" toml:"etcd,omitempty" yaml:"etcd,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` - ZooKeeper *zk.Provider `description:"Enable ZooKeeper backend with default settings." json:"zooKeeper,omitempty" toml:"zooKeeper,omitempty" yaml:"zooKeeper,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` - Redis *redis.Provider `description:"Enable Redis backend with default settings." json:"redis,omitempty" toml:"redis,omitempty" yaml:"redis,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` - HTTP *http.Provider `description:"Enable HTTP backend with default settings." json:"http,omitempty" toml:"http,omitempty" yaml:"http,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` + Docker *docker.Provider `description:"Enables Docker provider." json:"docker,omitempty" toml:"docker,omitempty" yaml:"docker,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` + Swarm *docker.SwarmProvider `description:"Enables Docker Swarm provider." json:"swarm,omitempty" toml:"swarm,omitempty" yaml:"swarm,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` + File *file.Provider `description:"Enables File provider." json:"file,omitempty" toml:"file,omitempty" yaml:"file,omitempty" export:"true"` + KubernetesIngress *ingress.Provider `description:"Enables Kubernetes Ingress provider." json:"kubernetesIngress,omitempty" toml:"kubernetesIngress,omitempty" yaml:"kubernetesIngress,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` + KubernetesIngressNGINX *ingressnginx.Provider `description:"Enables Kubernetes Ingress NGINX provider." json:"kubernetesIngressNGINX,omitempty" toml:"kubernetesIngressNGINX,omitempty" yaml:"kubernetesIngressNGINX,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` + KubernetesCRD *crd.Provider `description:"Enables Kubernetes CRD provider." json:"kubernetesCRD,omitempty" toml:"kubernetesCRD,omitempty" yaml:"kubernetesCRD,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` + KubernetesGateway *gateway.Provider `description:"Enables Kubernetes Gateway API provider." json:"kubernetesGateway,omitempty" toml:"kubernetesGateway,omitempty" yaml:"kubernetesGateway,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` + Rest *rest.Provider `description:"Enables Rest provider." json:"rest,omitempty" toml:"rest,omitempty" yaml:"rest,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` + ConsulCatalog *consulcatalog.ProviderBuilder `description:"Enables Consul Catalog provider." json:"consulCatalog,omitempty" toml:"consulCatalog,omitempty" yaml:"consulCatalog,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` + Nomad *nomad.ProviderBuilder `description:"Enables Nomad provider." json:"nomad,omitempty" toml:"nomad,omitempty" yaml:"nomad,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` + Ecs *ecs.Provider `description:"Enables AWS ECS provider." json:"ecs,omitempty" toml:"ecs,omitempty" yaml:"ecs,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` + Consul *consul.ProviderBuilder `description:"Enables Consul provider." json:"consul,omitempty" toml:"consul,omitempty" yaml:"consul,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` + Etcd *etcd.Provider `description:"Enables Etcd provider." json:"etcd,omitempty" toml:"etcd,omitempty" yaml:"etcd,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` + ZooKeeper *zk.Provider `description:"Enables ZooKeeper provider." json:"zooKeeper,omitempty" toml:"zooKeeper,omitempty" yaml:"zooKeeper,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` + Redis *redis.Provider `description:"Enables Redis provider." json:"redis,omitempty" toml:"redis,omitempty" yaml:"redis,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` + HTTP *http.Provider `description:"Enables HTTP provider." json:"http,omitempty" toml:"http,omitempty" yaml:"http,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` Plugin map[string]PluginConf `description:"Plugins configuration." json:"plugin,omitempty" toml:"plugin,omitempty" yaml:"plugin,omitempty"` } @@ -322,6 +328,15 @@ func (c *Configuration) SetEffectiveConfiguration() { continue } + switch resolver.ACME.DNSChallenge.Provider { + case "googledomains", "cloudxns", "brandit": + log.Warn().Msgf("%s DNS provider is deprecated.", resolver.ACME.DNSChallenge.Provider) + case "dnspod": + log.Warn().Msgf("%s provider is deprecated, please use 'tencentcloud' provider instead.", resolver.ACME.DNSChallenge.Provider) + case "azure": + log.Warn().Msgf("%s provider is deprecated, please use 'azuredns' provider instead.", resolver.ACME.DNSChallenge.Provider) + } + if resolver.ACME.DNSChallenge.DisablePropagationCheck { log.Warn().Msgf("disablePropagationCheck is now deprecated, please use propagation.disableChecks instead.") @@ -343,6 +358,25 @@ func (c *Configuration) SetEffectiveConfiguration() { } } + for _, resolver := range c.CertificatesResolvers { + if resolver.ACME == nil { + continue + } + + if resolver.ACME.DNSChallenge == nil { + continue + } + + switch resolver.ACME.DNSChallenge.Provider { + case "googledomains", "cloudxns", "brandit": + log.Warn().Msgf("%s DNS provider is deprecated.", resolver.ACME.DNSChallenge.Provider) + case "dnspod": + log.Warn().Msgf("%s provider is deprecated, please use 'tencentcloud' provider instead.", resolver.ACME.DNSChallenge.Provider) + case "azure": + log.Warn().Msgf("%s provider is deprecated, please use 'azuredns' provider instead.", resolver.ACME.DNSChallenge.Provider) + } + } + c.initACMEProvider() } @@ -388,6 +422,16 @@ func (c *Configuration) ValidateConfiguration() error { } } + if c.Providers != nil && c.Providers.KubernetesIngressNGINX != nil { + if c.Experimental == nil || !c.Experimental.KubernetesIngressNGINX { + return errors.New("the experimental KubernetesIngressNGINX feature must be enabled to use the KubernetesIngressNGINX provider") + } + + if c.Providers.KubernetesIngressNGINX.WatchNamespace != "" && c.Providers.KubernetesIngressNGINX.WatchNamespaceSelector != "" { + return errors.New("watchNamespace and watchNamespaceSelector options are mutually exclusive") + } + } + if c.AccessLog != nil && c.AccessLog.OTLP != nil { if c.Experimental == nil || !c.Experimental.OTLPLogs { return errors.New("the experimental OTLPLogs feature must be enabled to use OTLP access logging") @@ -424,6 +468,14 @@ func (c *Configuration) ValidateConfiguration() error { return errors.New("API basePath must be a valid absolute path") } + if c.OCSP != nil { + for responderURL, url := range c.OCSP.ResponderOverrides { + if url == "" { + return fmt.Errorf("OCSP responder override value for %s cannot be empty", responderURL) + } + } + } + return nil } diff --git a/pkg/healthcheck/healthcheck.go b/pkg/healthcheck/healthcheck.go index c9d3e7e36..e2679d73c 100644 --- a/pkg/healthcheck/healthcheck.go +++ b/pkg/healthcheck/healthcheck.go @@ -40,18 +40,27 @@ type metricsHealthCheck interface { ServiceServerUpGauge() gokitmetrics.Gauge } +type target struct { + targetURL *url.URL + name string +} + type ServiceHealthChecker struct { balancer StatusSetter info *runtime.ServiceInfo - config *dynamic.ServerHealthCheck - interval time.Duration - timeout time.Duration + config *dynamic.ServerHealthCheck + interval time.Duration + unhealthyInterval time.Duration + timeout time.Duration metrics metricsHealthCheck - client *http.Client - targets map[string]*url.URL + client *http.Client + + healthyTargets chan target + unhealthyTargets chan target + serviceName string } @@ -60,13 +69,26 @@ func NewServiceHealthChecker(ctx context.Context, metrics metricsHealthCheck, co interval := time.Duration(config.Interval) if interval <= 0 { - logger.Error().Msg("Health check interval smaller than zero") + logger.Error().Msg("Health check interval smaller than zero, default value will be used instead.") interval = time.Duration(dynamic.DefaultHealthCheckInterval) } + // If the unhealthyInterval option is not set, we use the interval option value, + // to check the unhealthy targets as often as the healthy ones. + var unhealthyInterval time.Duration + if config.UnhealthyInterval == nil { + unhealthyInterval = interval + } else { + unhealthyInterval = time.Duration(*config.UnhealthyInterval) + if unhealthyInterval <= 0 { + logger.Error().Msg("Health check unhealthy interval smaller than zero, default value will be used instead.") + unhealthyInterval = time.Duration(dynamic.DefaultHealthCheckInterval) + } + } + timeout := time.Duration(config.Timeout) if timeout <= 0 { - logger.Error().Msg("Health check timeout smaller than zero") + logger.Error().Msg("Health check timeout smaller than zero, default value will be used instead.") timeout = time.Duration(dynamic.DefaultHealthCheckTimeout) } @@ -80,21 +102,38 @@ func NewServiceHealthChecker(ctx context.Context, metrics metricsHealthCheck, co } } + healthyTargets := make(chan target, len(targets)) + for name, targetURL := range targets { + healthyTargets <- target{ + targetURL: targetURL, + name: name, + } + } + unhealthyTargets := make(chan target, len(targets)) + return &ServiceHealthChecker{ - balancer: service, - info: info, - config: config, - interval: interval, - timeout: timeout, - targets: targets, - serviceName: serviceName, - client: client, - metrics: metrics, + balancer: service, + info: info, + config: config, + interval: interval, + unhealthyInterval: unhealthyInterval, + timeout: timeout, + healthyTargets: healthyTargets, + unhealthyTargets: unhealthyTargets, + serviceName: serviceName, + client: client, + metrics: metrics, } } func (shc *ServiceHealthChecker) Launch(ctx context.Context) { - ticker := time.NewTicker(shc.interval) + go shc.healthcheck(ctx, shc.unhealthyTargets, shc.unhealthyInterval) + + shc.healthcheck(ctx, shc.healthyTargets, shc.interval) +} + +func (shc *ServiceHealthChecker) healthcheck(ctx context.Context, targets chan target, interval time.Duration) { + ticker := time.NewTicker(interval) defer ticker.Stop() for { @@ -103,7 +142,23 @@ func (shc *ServiceHealthChecker) Launch(ctx context.Context) { return case <-ticker.C: - for proxyName, target := range shc.targets { + // We collect the targets to check once for all, + // to avoid rechecking a target that has been moved during the health check. + var targetsToCheck []target + hasMoreTargets := true + for hasMoreTargets { + select { + case <-ctx.Done(): + return + case target := <-targets: + targetsToCheck = append(targetsToCheck, target) + default: + hasMoreTargets = false + } + } + + // Now we can check the targets. + for _, target := range targetsToCheck { select { case <-ctx.Done(): return @@ -113,14 +168,14 @@ func (shc *ServiceHealthChecker) Launch(ctx context.Context) { up := true serverUpMetricValue := float64(1) - if err := shc.executeHealthCheck(ctx, shc.config, target); err != nil { + if err := shc.executeHealthCheck(ctx, shc.config, target.targetURL); err != nil { // The context is canceled when the dynamic configuration is refreshed. if errors.Is(err, context.Canceled) { return } log.Ctx(ctx).Warn(). - Str("targetURL", target.String()). + Str("targetURL", target.targetURL.String()). Err(err). Msg("Health check failed.") @@ -128,17 +183,21 @@ func (shc *ServiceHealthChecker) Launch(ctx context.Context) { serverUpMetricValue = float64(0) } - shc.balancer.SetStatus(ctx, proxyName, up) + shc.balancer.SetStatus(ctx, target.name, up) - statusStr := runtime.StatusDown + var statusStr string if up { statusStr = runtime.StatusUp + shc.healthyTargets <- target + } else { + statusStr = runtime.StatusDown + shc.unhealthyTargets <- target } - shc.info.UpdateServerStatus(target.String(), statusStr) + shc.info.UpdateServerStatus(target.targetURL.String(), statusStr) shc.metrics.ServiceServerUpGauge(). - With("service", shc.serviceName, "url", target.String()). + With("service", shc.serviceName, "url", target.targetURL.String()). Set(serverUpMetricValue) } } diff --git a/pkg/healthcheck/healthcheck_test.go b/pkg/healthcheck/healthcheck_test.go index a977635d1..056a62a49 100644 --- a/pkg/healthcheck/healthcheck_test.go +++ b/pkg/healthcheck/healthcheck_test.go @@ -66,7 +66,7 @@ func TestNewServiceHealthChecker_durations(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { - healthChecker := NewServiceHealthChecker(context.Background(), nil, test.config, nil, nil, http.DefaultTransport, nil, "") + healthChecker := NewServiceHealthChecker(t.Context(), nil, test.config, nil, nil, http.DefaultTransport, nil, "") assert.Equal(t, test.expInterval, healthChecker.interval) assert.Equal(t, test.expTimeout, healthChecker.timeout) }) @@ -251,7 +251,7 @@ func TestServiceHealthChecker_newRequest(t *testing.T) { shc := ServiceHealthChecker{config: &test.config} u := testhelpers.MustParseURL(test.targetURL) - req, err := shc.newRequest(context.Background(), u) + req, err := shc.newRequest(t.Context(), u) if test.expError { require.Error(t, err) @@ -276,7 +276,7 @@ func TestServiceHealthChecker_checkHealthHTTP_NotFollowingRedirects(t *testing.T })) defer redirectTestServer.Close() - ctx, cancel := context.WithTimeout(context.Background(), time.Duration(dynamic.DefaultHealthCheckTimeout)) + ctx, cancel := context.WithTimeout(t.Context(), time.Duration(dynamic.DefaultHealthCheckTimeout)) defer cancel() server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { @@ -411,7 +411,7 @@ func TestServiceHealthChecker_Launch(t *testing.T) { // The context is passed to the health check and // canonically canceled by the test server once all expected requests have been received. - ctx, cancel := context.WithCancel(context.Background()) + ctx, cancel := context.WithCancel(t.Context()) t.Cleanup(cancel) targetURL, timeout := test.server.Start(t, cancel) @@ -419,11 +419,12 @@ func TestServiceHealthChecker_Launch(t *testing.T) { lb := &testLoadBalancer{RWMutex: &sync.RWMutex{}} config := &dynamic.ServerHealthCheck{ - Mode: test.mode, - Status: test.status, - Path: "/path", - Interval: ptypes.Duration(500 * time.Millisecond), - Timeout: ptypes.Duration(499 * time.Millisecond), + Mode: test.mode, + Status: test.status, + Path: "/path", + Interval: ptypes.Duration(500 * time.Millisecond), + UnhealthyInterval: pointer(ptypes.Duration(500 * time.Millisecond)), + Timeout: ptypes.Duration(499 * time.Millisecond), } gauge := &testhelpers.CollectingGauge{} @@ -456,3 +457,54 @@ func TestServiceHealthChecker_Launch(t *testing.T) { }) } } + +func TestDifferentIntervals(t *testing.T) { + // The context is passed to the health check and + // canonically canceled by the test server once all expected requests have been received. + ctx, cancel := context.WithCancel(t.Context()) + t.Cleanup(cancel) + + healthyServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + w.WriteHeader(http.StatusOK) + })) + healthyURL := testhelpers.MustParseURL(healthyServer.URL) + + unhealthyServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + w.WriteHeader(http.StatusServiceUnavailable) + })) + unhealthyURL := testhelpers.MustParseURL(unhealthyServer.URL) + + lb := &testLoadBalancer{RWMutex: &sync.RWMutex{}} + + config := &dynamic.ServerHealthCheck{ + Mode: "http", + Path: "/path", + Interval: ptypes.Duration(500 * time.Millisecond), + UnhealthyInterval: pointer(ptypes.Duration(50 * time.Millisecond)), + Timeout: ptypes.Duration(499 * time.Millisecond), + } + + gauge := &testhelpers.CollectingGauge{} + serviceInfo := &runtime.ServiceInfo{} + hc := NewServiceHealthChecker(ctx, &MetricsMock{gauge}, config, lb, serviceInfo, http.DefaultTransport, map[string]*url.URL{"healthy": healthyURL, "unhealthy": unhealthyURL}, "foobar") + + wg := sync.WaitGroup{} + wg.Add(1) + + go func() { + hc.Launch(ctx) + wg.Done() + }() + + select { + case <-time.After(2 * time.Second): + break + case <-ctx.Done(): + wg.Wait() + } + + lb.Lock() + defer lb.Unlock() + + assert.Greater(t, lb.numRemovedServers, lb.numUpsertedServers, "removed servers greater than upserted servers") +} diff --git a/pkg/healthcheck/mock_test.go b/pkg/healthcheck/mock_test.go index ba288cc26..483a5385d 100644 --- a/pkg/healthcheck/mock_test.go +++ b/pkg/healthcheck/mock_test.go @@ -64,6 +64,10 @@ func newGRPCServer(healthSequence ...healthpb.HealthCheckResponse_ServingStatus) return gRPCService } +func (s *GRPCServer) List(_ context.Context, _ *healthpb.HealthListRequest) (*healthpb.HealthListResponse, error) { + return nil, nil +} + func (s *GRPCServer) Check(_ context.Context, _ *healthpb.HealthCheckRequest) (*healthpb.HealthCheckResponse, error) { if s.status.IsEmpty() { s.done() diff --git a/pkg/middlewares/accesslog/field_middleware.go b/pkg/middlewares/accesslog/field_middleware.go index d1b39438f..4d439bc25 100644 --- a/pkg/middlewares/accesslog/field_middleware.go +++ b/pkg/middlewares/accesslog/field_middleware.go @@ -5,8 +5,8 @@ import ( "time" "github.com/rs/zerolog/log" - "github.com/traefik/traefik/v3/pkg/logs" "github.com/traefik/traefik/v3/pkg/middlewares/capture" + "github.com/traefik/traefik/v3/pkg/observability/logs" "github.com/vulcand/oxy/v2/utils" ) diff --git a/pkg/middlewares/accesslog/logger.go b/pkg/middlewares/accesslog/logger.go index 6ec8ea4c9..5f21afc09 100644 --- a/pkg/middlewares/accesslog/logger.go +++ b/pkg/middlewares/accesslog/logger.go @@ -19,8 +19,10 @@ import ( "github.com/rs/zerolog/log" "github.com/sirupsen/logrus" ptypes "github.com/traefik/paerser/types" - "github.com/traefik/traefik/v3/pkg/logs" "github.com/traefik/traefik/v3/pkg/middlewares/capture" + "github.com/traefik/traefik/v3/pkg/middlewares/observability" + "github.com/traefik/traefik/v3/pkg/observability/logs" + otypes "github.com/traefik/traefik/v3/pkg/observability/types" traefiktls "github.com/traefik/traefik/v3/pkg/tls" "github.com/traefik/traefik/v3/pkg/types" "go.opentelemetry.io/contrib/bridges/otellogrus" @@ -36,6 +38,9 @@ const ( // CommonFormat is the common logging format (CLF). CommonFormat string = "common" + // GenericCLFFormat is the generic CLF format. + GenericCLFFormat string = "genericCLF" + // JSONFormat is the JSON logging format. JSONFormat string = "json" ) @@ -60,7 +65,7 @@ type handlerParams struct { // Handler will write each request and its response to the access log. type Handler struct { - config *types.AccessLog + config *otypes.AccessLog logger *logrus.Logger file io.WriteCloser mu sync.Mutex @@ -69,17 +74,22 @@ type Handler struct { wg sync.WaitGroup } -// WrapHandler Wraps access log handler into an Alice Constructor. -func WrapHandler(handler *Handler) alice.Constructor { +// AliceConstructor returns an alice.Constructor that wraps the Handler (conditionally) in a middleware chain. +func (h *Handler) AliceConstructor() alice.Constructor { return func(next http.Handler) (http.Handler, error) { return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { - handler.ServeHTTP(rw, req, next) + if h == nil { + next.ServeHTTP(rw, req) + return + } + + h.ServeHTTP(rw, req, next) }), nil } } // NewHandler creates a new Handler. -func NewHandler(config *types.AccessLog) (*Handler, error) { +func NewHandler(ctx context.Context, config *otypes.AccessLog) (*Handler, error) { var file io.WriteCloser = noopCloser{os.Stdout} if len(config.FilePath) > 0 { f, err := openAccessLogFile(config.FilePath) @@ -95,6 +105,8 @@ func NewHandler(config *types.AccessLog) (*Handler, error) { switch config.Format { case CommonFormat: formatter = new(CommonLogFormatter) + case GenericCLFFormat: + formatter = new(GenericCLFLogFormatter) case JSONFormat: formatter = new(logrus.JSONFormatter) default: @@ -110,7 +122,7 @@ func NewHandler(config *types.AccessLog) (*Handler, error) { } if config.OTLP != nil { - otelLoggerProvider, err := config.OTLP.NewLoggerProvider() + otelLoggerProvider, err := config.OTLP.NewLoggerProvider(ctx) if err != nil { return nil, fmt.Errorf("setting up OpenTelemetry logger provider: %w", err) } @@ -196,6 +208,12 @@ func GetLogData(req *http.Request) *LogData { } func (h *Handler) ServeHTTP(rw http.ResponseWriter, req *http.Request, next http.Handler) { + if !observability.AccessLogsEnabled(req.Context()) { + next.ServeHTTP(rw, req) + + return + } + now := time.Now().UTC() core := CoreLogData{ @@ -352,46 +370,63 @@ func (h *Handler) logTheRoundTrip(ctx context.Context, logDataTable *LogData) { totalDuration := time.Now().UTC().Sub(core[StartUTC].(time.Time)) core[Duration] = totalDuration - if h.keepAccessLog(status, retryAttempts, totalDuration) { - size := logDataTable.DownstreamResponse.size - core[DownstreamContentSize] = size - if original, ok := core[OriginContentSize]; ok { - o64 := original.(int64) - if size != o64 && size != 0 { - core[GzipRatio] = float64(o64) / float64(size) - } - } - - core[Overhead] = totalDuration - if origin, ok := core[OriginDuration]; ok { - core[Overhead] = totalDuration - origin.(time.Duration) - } - - fields := logrus.Fields{} - - for k, v := range logDataTable.Core { - if h.config.Fields.Keep(strings.ToLower(k)) { - fields[k] = v - } - } - - h.redactHeaders(logDataTable.Request.headers, fields, "request_") - h.redactHeaders(logDataTable.OriginResponse, fields, "origin_") - h.redactHeaders(logDataTable.DownstreamResponse.headers, fields, "downstream_") - - h.mu.Lock() - defer h.mu.Unlock() - h.logger.WithContext(ctx).WithFields(fields).Println() + if !h.keepAccessLog(status, retryAttempts, totalDuration) { + return } + + size := logDataTable.DownstreamResponse.size + core[DownstreamContentSize] = size + if original, ok := core[OriginContentSize]; ok { + o64 := original.(int64) + if size != o64 && size != 0 { + core[GzipRatio] = float64(o64) / float64(size) + } + } + + core[Overhead] = totalDuration + if origin, ok := core[OriginDuration]; ok { + core[Overhead] = totalDuration - origin.(time.Duration) + } + + fields := logrus.Fields{} + + for k, v := range logDataTable.Core { + if h.config.Fields.Keep(strings.ToLower(k)) { + fields[k] = v + } + } + + h.redactHeaders(logDataTable.Request.headers, fields, "request_") + h.redactHeaders(logDataTable.OriginResponse, fields, "origin_") + h.redactHeaders(logDataTable.DownstreamResponse.headers, fields, "downstream_") + + h.mu.Lock() + defer h.mu.Unlock() + + entry := h.logger.WithContext(ctx).WithFields(fields) + + var message string + if h.config.OTLP != nil { + // If the logger is configured to use OpenTelemetry, + // we compute the log body with the formatter. + mBytes, err := h.logger.Formatter.Format(entry) + if err != nil { + message = fmt.Sprintf("Failed to format access log entry: %v", err) + } else { + message = string(mBytes) + } + } + + entry.Println(message) } func (h *Handler) redactHeaders(headers http.Header, fields logrus.Fields, prefix string) { for k := range headers { v := h.config.Fields.KeepHeader(k) switch v { - case types.AccessLogKeep: + case otypes.AccessLogKeep: fields[prefix+k] = strings.Join(headers.Values(k), ",") - case types.AccessLogRedact: + case otypes.AccessLogRedact: fields[prefix+k] = "REDACTED" } } diff --git a/pkg/middlewares/accesslog/logger_formatters.go b/pkg/middlewares/accesslog/logger_formatters.go index c714ef146..3da18c239 100644 --- a/pkg/middlewares/accesslog/logger_formatters.go +++ b/pkg/middlewares/accesslog/logger_formatters.go @@ -52,6 +52,35 @@ func (f *CommonLogFormatter) Format(entry *logrus.Entry) ([]byte, error) { return b.Bytes(), err } +// GenericCLFLogFormatter provides formatting in the generic CLF log format. +type GenericCLFLogFormatter struct{} + +// Format formats the log entry in the generic CLF log format. +func (f *GenericCLFLogFormatter) Format(entry *logrus.Entry) ([]byte, error) { + b := &bytes.Buffer{} + + timestamp := defaultValue + if v, ok := entry.Data[StartUTC]; ok { + timestamp = v.(time.Time).Format(commonLogTimeFormat) + } else if v, ok := entry.Data[StartLocal]; ok { + timestamp = v.(time.Time).Local().Format(commonLogTimeFormat) + } + + _, err := fmt.Fprintf(b, "%s - %s [%s] \"%s %s %s\" %v %v %s %s\n", + toLog(entry.Data, ClientHost, defaultValue, false), + toLog(entry.Data, ClientUsername, defaultValue, false), + timestamp, + toLog(entry.Data, RequestMethod, defaultValue, false), + toLog(entry.Data, RequestPath, defaultValue, false), + toLog(entry.Data, RequestProtocol, defaultValue, false), + toLog(entry.Data, DownstreamStatus, defaultValue, true), + toLog(entry.Data, DownstreamContentSize, defaultValue, true), + toLog(entry.Data, "request_Referer", `"-"`, true), + toLog(entry.Data, "request_User-Agent", `"-"`, true)) + + return b.Bytes(), err +} + func toLog(fields logrus.Fields, key, defaultValue string, quoted bool) interface{} { if v, ok := fields[key]; ok { if v == nil { diff --git a/pkg/middlewares/accesslog/logger_formatters_test.go b/pkg/middlewares/accesslog/logger_formatters_test.go index 028e3dbf7..a7e85c5d4 100644 --- a/pkg/middlewares/accesslog/logger_formatters_test.go +++ b/pkg/middlewares/accesslog/logger_formatters_test.go @@ -7,6 +7,7 @@ import ( "github.com/sirupsen/logrus" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestCommonLogFormatter_Format(t *testing.T) { @@ -82,8 +83,100 @@ func TestCommonLogFormatter_Format(t *testing.T) { }, } - // Set timezone to Etc/GMT+9 to have a constant behavior - t.Setenv("TZ", "Etc/GMT+9") + var err error + time.Local, err = time.LoadLocation("Etc/GMT+9") + require.NoError(t, err) + + for _, test := range testCases { + t.Run(test.name, func(t *testing.T) { + t.Parallel() + + entry := &logrus.Entry{Data: test.data} + + raw, err := clf.Format(entry) + assert.NoError(t, err) + + assert.Equal(t, test.expectedLog, string(raw)) + }) + } +} + +func TestGenericCLFLogFormatter_Format(t *testing.T) { + clf := GenericCLFLogFormatter{} + + testCases := []struct { + name string + data map[string]interface{} + expectedLog string + }{ + { + name: "DownstreamStatus & DownstreamContentSize are nil", + data: map[string]interface{}{ + StartUTC: time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC), + Duration: 123 * time.Second, + ClientHost: "10.0.0.1", + ClientUsername: "Client", + RequestMethod: http.MethodGet, + RequestPath: "/foo", + RequestProtocol: "http", + DownstreamStatus: nil, + DownstreamContentSize: nil, + RequestRefererHeader: "", + RequestUserAgentHeader: "", + RequestCount: 0, + RouterName: "", + ServiceURL: "", + }, + expectedLog: `10.0.0.1 - Client [10/Nov/2009:23:00:00 +0000] "GET /foo http" - - "-" "-" +`, + }, + { + name: "all data", + data: map[string]interface{}{ + StartUTC: time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC), + Duration: 123 * time.Second, + ClientHost: "10.0.0.1", + ClientUsername: "Client", + RequestMethod: http.MethodGet, + RequestPath: "/foo", + RequestProtocol: "http", + DownstreamStatus: 123, + DownstreamContentSize: 132, + RequestRefererHeader: "referer", + RequestUserAgentHeader: "agent", + RequestCount: nil, + RouterName: "foo", + ServiceURL: "http://10.0.0.2/toto", + }, + expectedLog: `10.0.0.1 - Client [10/Nov/2009:23:00:00 +0000] "GET /foo http" 123 132 "referer" "agent" +`, + }, + { + name: "all data with local time", + data: map[string]interface{}{ + StartLocal: time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC), + Duration: 123 * time.Second, + ClientHost: "10.0.0.1", + ClientUsername: "Client", + RequestMethod: http.MethodGet, + RequestPath: "/foo", + RequestProtocol: "http", + DownstreamStatus: 123, + DownstreamContentSize: 132, + RequestRefererHeader: "referer", + RequestUserAgentHeader: "agent", + RequestCount: nil, + RouterName: "foo", + ServiceURL: "http://10.0.0.2/toto", + }, + expectedLog: `10.0.0.1 - Client [10/Nov/2009:14:00:00 -0900] "GET /foo http" 123 132 "referer" "agent" +`, + }, + } + + var err error + time.Local, err = time.LoadLocation("Etc/GMT+9") + require.NoError(t, err) for _, test := range testCases { t.Run(test.name, func(t *testing.T) { diff --git a/pkg/middlewares/accesslog/logger_test.go b/pkg/middlewares/accesslog/logger_test.go index 6d6a8188f..2da72991e 100644 --- a/pkg/middlewares/accesslog/logger_test.go +++ b/pkg/middlewares/accesslog/logger_test.go @@ -25,7 +25,8 @@ import ( "github.com/stretchr/testify/require" ptypes "github.com/traefik/paerser/types" "github.com/traefik/traefik/v3/pkg/middlewares/capture" - "github.com/traefik/traefik/v3/pkg/types" + "github.com/traefik/traefik/v3/pkg/middlewares/observability" + otypes "github.com/traefik/traefik/v3/pkg/observability/types" "go.opentelemetry.io/collector/pdata/plog/plogotlp" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" @@ -55,72 +56,131 @@ var ( testStart = time.Now() ) -func TestOTelAccessLog(t *testing.T) { - logCh := make(chan string) - collector := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - gzr, err := gzip.NewReader(r.Body) - require.NoError(t, err) +func TestOTelAccessLogWithBody(t *testing.T) { + testCases := []struct { + desc string + format string + bodyCheckFn func(*testing.T, string) + }{ + { + desc: "Common format with log body", + format: CommonFormat, + bodyCheckFn: func(t *testing.T, log string) { + t.Helper() - body, err := io.ReadAll(gzr) - require.NoError(t, err) + // For common format, verify the body contains the Traefik common log formatted string + assert.Regexp(t, `"body":{"stringValue":".*- /health -.*200.*[0-9]+ms.*"}`, log) + }, + }, + { + desc: "Generic CLF format with log body", + format: GenericCLFFormat, + bodyCheckFn: func(t *testing.T, log string) { + t.Helper() - req := plogotlp.NewExportRequest() - err = req.UnmarshalProto(body) - require.NoError(t, err) + // For generic CLF format, verify the body contains the CLF formatted string + assert.Regexp(t, `"body":{"stringValue":".*- /health -.*200.*"}`, log) + }, + }, + { + desc: "JSON format with log body", + format: JSONFormat, + bodyCheckFn: func(t *testing.T, log string) { + t.Helper() - marshalledReq, err := json.Marshal(req) - require.NoError(t, err) - - logCh <- string(marshalledReq) - })) - t.Cleanup(collector.Close) - - config := &types.AccessLog{ - OTLP: &types.OTelLog{ - ServiceName: "test", - ResourceAttributes: map[string]string{"resource": "attribute"}, - HTTP: &types.OTelHTTP{ - Endpoint: collector.URL, + // For JSON format, verify the body contains the JSON formatted string + assert.Regexp(t, `"body":{"stringValue":".*DownstreamStatus.*:200.*"}`, log) }, }, } - logHandler, err := NewHandler(config) - require.NoError(t, err) - t.Cleanup(func() { - err := logHandler.Close() - require.NoError(t, err) - }) - req := &http.Request{ - Header: map[string][]string{}, - URL: &url.URL{ - Path: testPath, - }, - } - ctx := trace.ContextWithSpanContext(context.Background(), trace.NewSpanContext(trace.SpanContextConfig{ - TraceID: trace.TraceID{0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8}, - SpanID: trace.SpanID{0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8}, - })) - req = req.WithContext(ctx) + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + t.Parallel() - chain := alice.New() - chain = chain.Append(capture.Wrap) - chain = chain.Append(WrapHandler(logHandler)) - handler, err := chain.Then(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { - rw.WriteHeader(http.StatusOK) - })) - require.NoError(t, err) - handler.ServeHTTP(httptest.NewRecorder(), req) + logCh := make(chan string) + collector := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + gzr, err := gzip.NewReader(r.Body) + require.NoError(t, err) - select { - case <-time.After(5 * time.Second): - t.Error("AccessLog not exported") + body, err := io.ReadAll(gzr) + require.NoError(t, err) - case log := <-logCh: - assert.Regexp(t, `{"key":"resource","value":{"stringValue":"attribute"}}`, log) - assert.Regexp(t, `{"key":"service.name","value":{"stringValue":"test"}}`, log) - assert.Regexp(t, `{"key":"DownstreamStatus","value":{"intValue":"200"}}`, log) - assert.Regexp(t, `"traceId":"01020304050607080000000000000000","spanId":"0102030405060708"`, log) + req := plogotlp.NewExportRequest() + err = req.UnmarshalProto(body) + require.NoError(t, err) + + marshalledReq, err := json.Marshal(req) + require.NoError(t, err) + + logCh <- string(marshalledReq) + })) + t.Cleanup(collector.Close) + + config := &otypes.AccessLog{ + Format: test.format, + OTLP: &otypes.OTelLog{ + ServiceName: "test", + ResourceAttributes: map[string]string{"resource": "attribute"}, + HTTP: &otypes.OTelHTTP{ + Endpoint: collector.URL, + }, + }, + } + logHandler, err := NewHandler(t.Context(), config) + require.NoError(t, err) + t.Cleanup(func() { + err := logHandler.Close() + require.NoError(t, err) + }) + + req := &http.Request{ + Header: map[string][]string{}, + URL: &url.URL{ + Path: "/health", + }, + } + ctx := trace.ContextWithSpanContext(t.Context(), trace.NewSpanContext(trace.SpanContextConfig{ + TraceID: trace.TraceID{0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8}, + SpanID: trace.SpanID{0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8}, + })) + req = req.WithContext(ctx) + + chain := alice.New() + chain = chain.Append(capture.Wrap) + + // Injection of the observability variables in the request context. + chain = chain.Append(func(next http.Handler) (http.Handler, error) { + return observability.WithObservabilityHandler(next, observability.Observability{ + AccessLogsEnabled: true, + }), nil + }) + + chain = chain.Append(logHandler.AliceConstructor()) + handler, err := chain.Then(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + rw.WriteHeader(http.StatusOK) + })) + require.NoError(t, err) + handler.ServeHTTP(httptest.NewRecorder(), req) + + select { + case <-time.After(5 * time.Second): + t.Error("AccessLog not exported") + + case log := <-logCh: + // Verify basic OTLP structure + assert.Regexp(t, `{"key":"resource","value":{"stringValue":"attribute"}}`, log) + assert.Regexp(t, `{"key":"service.name","value":{"stringValue":"test"}}`, log) + assert.Regexp(t, `{"key":"DownstreamStatus","value":{"intValue":"200"}}`, log) + assert.Regexp(t, `"traceId":"01020304050607080000000000000000","spanId":"0102030405060708"`, log) + + // Most importantly, verify the log body is populated (not empty) + assert.NotRegexp(t, `"body":{"stringValue":""}`, log, "Log body should not be empty when OTLP is configured") + + // Run format-specific body checks + test.bodyCheckFn(t, log) + } + }) } } @@ -128,8 +188,8 @@ func TestLogRotation(t *testing.T) { fileName := filepath.Join(t.TempDir(), "traefik.log") rotatedFileName := fileName + ".rotated" - config := &types.AccessLog{FilePath: fileName, Format: CommonFormat} - logHandler, err := NewHandler(config) + config := &otypes.AccessLog{FilePath: fileName, Format: CommonFormat} + logHandler, err := NewHandler(t.Context(), config) require.NoError(t, err) t.Cleanup(func() { err := logHandler.Close() @@ -138,7 +198,15 @@ func TestLogRotation(t *testing.T) { chain := alice.New() chain = chain.Append(capture.Wrap) - chain = chain.Append(WrapHandler(logHandler)) + + // Injection of the observability variables in the request context. + chain = chain.Append(func(next http.Handler) (http.Handler, error) { + return observability.WithObservabilityHandler(next, observability.Observability{ + AccessLogsEnabled: true, + }), nil + }) + + chain = chain.Append(logHandler.AliceConstructor()) handler, err := chain.Then(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { rw.WriteHeader(http.StatusOK) })) @@ -208,18 +276,18 @@ func TestLoggerHeaderFields(t *testing.T) { testCases := []struct { desc string - accessLogFields types.AccessLogFields + accessLogFields otypes.AccessLogFields header string expected string }{ { desc: "with default mode", header: "User-Agent", - expected: types.AccessLogDrop, - accessLogFields: types.AccessLogFields{ - DefaultMode: types.AccessLogDrop, - Headers: &types.FieldHeaders{ - DefaultMode: types.AccessLogDrop, + expected: otypes.AccessLogDrop, + accessLogFields: otypes.AccessLogFields{ + DefaultMode: otypes.AccessLogDrop, + Headers: &otypes.FieldHeaders{ + DefaultMode: otypes.AccessLogDrop, Names: map[string]string{}, }, }, @@ -227,13 +295,13 @@ func TestLoggerHeaderFields(t *testing.T) { { desc: "with exact header name", header: "User-Agent", - expected: types.AccessLogKeep, - accessLogFields: types.AccessLogFields{ - DefaultMode: types.AccessLogDrop, - Headers: &types.FieldHeaders{ - DefaultMode: types.AccessLogDrop, + expected: otypes.AccessLogKeep, + accessLogFields: otypes.AccessLogFields{ + DefaultMode: otypes.AccessLogDrop, + Headers: &otypes.FieldHeaders{ + DefaultMode: otypes.AccessLogDrop, Names: map[string]string{ - "User-Agent": types.AccessLogKeep, + "User-Agent": otypes.AccessLogKeep, }, }, }, @@ -241,13 +309,13 @@ func TestLoggerHeaderFields(t *testing.T) { { desc: "with case-insensitive match on header name", header: "User-Agent", - expected: types.AccessLogKeep, - accessLogFields: types.AccessLogFields{ - DefaultMode: types.AccessLogDrop, - Headers: &types.FieldHeaders{ - DefaultMode: types.AccessLogDrop, + expected: otypes.AccessLogKeep, + accessLogFields: otypes.AccessLogFields{ + DefaultMode: otypes.AccessLogDrop, + Headers: &otypes.FieldHeaders{ + DefaultMode: otypes.AccessLogDrop, Names: map[string]string{ - "user-agent": types.AccessLogKeep, + "user-agent": otypes.AccessLogKeep, }, }, }, @@ -259,13 +327,13 @@ func TestLoggerHeaderFields(t *testing.T) { logFile, err := os.CreateTemp(t.TempDir(), "*.log") require.NoError(t, err) - config := &types.AccessLog{ + config := &otypes.AccessLog{ FilePath: logFile.Name(), Format: CommonFormat, Fields: &test.accessLogFields, } - logger, err := NewHandler(config) + logger, err := NewHandler(t.Context(), config) require.NoError(t, err) t.Cleanup(func() { err := logger.Close() @@ -290,7 +358,15 @@ func TestLoggerHeaderFields(t *testing.T) { chain := alice.New() chain = chain.Append(capture.Wrap) - chain = chain.Append(WrapHandler(logger)) + + // Injection of the observability variables in the request context. + chain = chain.Append(func(next http.Handler) (http.Handler, error) { + return observability.WithObservabilityHandler(next, observability.Observability{ + AccessLogsEnabled: true, + }), nil + }) + + chain = chain.Append(logger.AliceConstructor()) handler, err := chain.Then(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { rw.WriteHeader(http.StatusOK) })) @@ -300,7 +376,7 @@ func TestLoggerHeaderFields(t *testing.T) { logData, err := os.ReadFile(logFile.Name()) require.NoError(t, err) - if test.expected == types.AccessLogDrop { + if test.expected == otypes.AccessLogDrop { assert.NotContains(t, string(logData), strings.Join(expectedValues, ",")) } else { assert.Contains(t, string(logData), strings.Join(expectedValues, ",")) @@ -309,21 +385,21 @@ func TestLoggerHeaderFields(t *testing.T) { } } -func TestLoggerCLF(t *testing.T) { +func TestCommonLogger(t *testing.T) { logFilePath := filepath.Join(t.TempDir(), logFileNameSuffix) - config := &types.AccessLog{FilePath: logFilePath, Format: CommonFormat} + config := &otypes.AccessLog{FilePath: logFilePath, Format: CommonFormat} doLogging(t, config, false) logData, err := os.ReadFile(logFilePath) require.NoError(t, err) expectedLog := ` TestHost - TestUser [13/Apr/2016:07:14:19 -0700] "POST testpath HTTP/0.0" 123 12 "testReferer" "testUserAgent" 1 "testRouter" "http://127.0.0.1/testService" 1ms` - assertValidLogData(t, expectedLog, logData) + assertValidCommonLogData(t, expectedLog, logData) } -func TestLoggerCLFWithBufferingSize(t *testing.T) { +func TestCommonLoggerWithBufferingSize(t *testing.T) { logFilePath := filepath.Join(t.TempDir(), logFileNameSuffix) - config := &types.AccessLog{FilePath: logFilePath, Format: CommonFormat, BufferingSize: 1024} + config := &otypes.AccessLog{FilePath: logFilePath, Format: CommonFormat, BufferingSize: 1024} doLogging(t, config, false) // wait a bit for the buffer to be written in the file. @@ -333,7 +409,34 @@ func TestLoggerCLFWithBufferingSize(t *testing.T) { require.NoError(t, err) expectedLog := ` TestHost - TestUser [13/Apr/2016:07:14:19 -0700] "POST testpath HTTP/0.0" 123 12 "testReferer" "testUserAgent" 1 "testRouter" "http://127.0.0.1/testService" 1ms` - assertValidLogData(t, expectedLog, logData) + assertValidCommonLogData(t, expectedLog, logData) +} + +func TestLoggerGenericCLF(t *testing.T) { + logFilePath := filepath.Join(t.TempDir(), logFileNameSuffix) + config := &otypes.AccessLog{FilePath: logFilePath, Format: GenericCLFFormat} + doLogging(t, config, false) + + logData, err := os.ReadFile(logFilePath) + require.NoError(t, err) + + expectedLog := ` TestHost - TestUser [13/Apr/2016:07:14:19 -0700] "POST testpath HTTP/0.0" 123 12 "testReferer" "testUserAgent"` + assertValidGenericCLFLogData(t, expectedLog, logData) +} + +func TestLoggerGenericCLFWithBufferingSize(t *testing.T) { + logFilePath := filepath.Join(t.TempDir(), logFileNameSuffix) + config := &otypes.AccessLog{FilePath: logFilePath, Format: GenericCLFFormat, BufferingSize: 1024} + doLogging(t, config, false) + + // wait a bit for the buffer to be written in the file. + time.Sleep(50 * time.Millisecond) + + logData, err := os.ReadFile(logFilePath) + require.NoError(t, err) + + expectedLog := ` TestHost - TestUser [13/Apr/2016:07:14:19 -0700] "POST testpath HTTP/0.0" 123 12 "testReferer" "testUserAgent"` + assertValidGenericCLFLogData(t, expectedLog, logData) } func assertString(exp string) func(t *testing.T, actual interface{}) { @@ -371,14 +474,14 @@ func assertFloat64NotZero() func(t *testing.T, actual interface{}) { func TestLoggerJSON(t *testing.T) { testCases := []struct { desc string - config *types.AccessLog + config *otypes.AccessLog tls bool tracing bool expected map[string]func(t *testing.T, value interface{}) }{ { desc: "default config without tracing", - config: &types.AccessLog{ + config: &otypes.AccessLog{ FilePath: "", Format: JSONFormat, }, @@ -417,7 +520,7 @@ func TestLoggerJSON(t *testing.T) { }, { desc: "default config with tracing", - config: &types.AccessLog{ + config: &otypes.AccessLog{ FilePath: "", Format: JSONFormat, }, @@ -459,7 +562,7 @@ func TestLoggerJSON(t *testing.T) { }, { desc: "default config, with TLS request", - config: &types.AccessLog{ + config: &otypes.AccessLog{ FilePath: "", Format: JSONFormat, }, @@ -502,10 +605,10 @@ func TestLoggerJSON(t *testing.T) { }, { desc: "default config drop all fields", - config: &types.AccessLog{ + config: &otypes.AccessLog{ FilePath: "", Format: JSONFormat, - Fields: &types.AccessLogFields{ + Fields: &otypes.AccessLogFields{ DefaultMode: "drop", }, }, @@ -520,12 +623,12 @@ func TestLoggerJSON(t *testing.T) { }, { desc: "default config drop all fields and headers", - config: &types.AccessLog{ + config: &otypes.AccessLog{ FilePath: "", Format: JSONFormat, - Fields: &types.AccessLogFields{ + Fields: &otypes.AccessLogFields{ DefaultMode: "drop", - Headers: &types.FieldHeaders{ + Headers: &otypes.FieldHeaders{ DefaultMode: "drop", }, }, @@ -538,12 +641,12 @@ func TestLoggerJSON(t *testing.T) { }, { desc: "default config drop all fields and redact headers", - config: &types.AccessLog{ + config: &otypes.AccessLog{ FilePath: "", Format: JSONFormat, - Fields: &types.AccessLogFields{ + Fields: &otypes.AccessLogFields{ DefaultMode: "drop", - Headers: &types.FieldHeaders{ + Headers: &otypes.FieldHeaders{ DefaultMode: "redact", }, }, @@ -559,15 +662,15 @@ func TestLoggerJSON(t *testing.T) { }, { desc: "default config drop all fields and headers but kept someone", - config: &types.AccessLog{ + config: &otypes.AccessLog{ FilePath: "", Format: JSONFormat, - Fields: &types.AccessLogFields{ + Fields: &otypes.AccessLogFields{ DefaultMode: "drop", Names: map[string]string{ RequestHost: "keep", }, - Headers: &types.FieldHeaders{ + Headers: &otypes.FieldHeaders{ DefaultMode: "drop", Names: map[string]string{ "Referer": "keep", @@ -585,15 +688,15 @@ func TestLoggerJSON(t *testing.T) { }, { desc: "fields and headers with unconventional letter case", - config: &types.AccessLog{ + config: &otypes.AccessLog{ FilePath: "", Format: JSONFormat, - Fields: &types.AccessLogFields{ + Fields: &otypes.AccessLogFields{ DefaultMode: "drop", Names: map[string]string{ "rEqUeStHoSt": "keep", }, - Headers: &types.FieldHeaders{ + Headers: &otypes.FieldHeaders{ DefaultMode: "drop", Names: map[string]string{ "ReFeReR": "keep", @@ -675,7 +778,7 @@ func TestLogger_AbortedRequest(t *testing.T) { "downstream_Cache-Control": assertString("no-cache"), } - config := &types.AccessLog{ + config := &otypes.AccessLog{ FilePath: filepath.Join(t.TempDir(), logFileNameSuffix), Format: JSONFormat, } @@ -701,12 +804,12 @@ func TestLogger_AbortedRequest(t *testing.T) { func TestNewLogHandlerOutputStdout(t *testing.T) { testCases := []struct { desc string - config *types.AccessLog + config *otypes.AccessLog expectedLog string }{ { desc: "default config", - config: &types.AccessLog{ + config: &otypes.AccessLog{ FilePath: "", Format: CommonFormat, }, @@ -714,19 +817,19 @@ func TestNewLogHandlerOutputStdout(t *testing.T) { }, { desc: "default config with empty filters", - config: &types.AccessLog{ + config: &otypes.AccessLog{ FilePath: "", Format: CommonFormat, - Filters: &types.AccessLogFilters{}, + Filters: &otypes.AccessLogFilters{}, }, expectedLog: `TestHost - TestUser [13/Apr/2016:07:14:19 -0700] "POST testpath HTTP/0.0" 123 12 "testReferer" "testUserAgent" 23 "testRouter" "http://127.0.0.1/testService" 1ms`, }, { desc: "Status code filter not matching", - config: &types.AccessLog{ + config: &otypes.AccessLog{ FilePath: "", Format: CommonFormat, - Filters: &types.AccessLogFilters{ + Filters: &otypes.AccessLogFilters{ StatusCodes: []string{"200"}, }, }, @@ -734,10 +837,10 @@ func TestNewLogHandlerOutputStdout(t *testing.T) { }, { desc: "Status code filter matching", - config: &types.AccessLog{ + config: &otypes.AccessLog{ FilePath: "", Format: CommonFormat, - Filters: &types.AccessLogFilters{ + Filters: &otypes.AccessLogFilters{ StatusCodes: []string{"123"}, }, }, @@ -745,10 +848,10 @@ func TestNewLogHandlerOutputStdout(t *testing.T) { }, { desc: "Duration filter not matching", - config: &types.AccessLog{ + config: &otypes.AccessLog{ FilePath: "", Format: CommonFormat, - Filters: &types.AccessLogFilters{ + Filters: &otypes.AccessLogFilters{ MinDuration: ptypes.Duration(1 * time.Hour), }, }, @@ -756,10 +859,10 @@ func TestNewLogHandlerOutputStdout(t *testing.T) { }, { desc: "Duration filter matching", - config: &types.AccessLog{ + config: &otypes.AccessLog{ FilePath: "", Format: CommonFormat, - Filters: &types.AccessLogFilters{ + Filters: &otypes.AccessLogFilters{ MinDuration: ptypes.Duration(1 * time.Millisecond), }, }, @@ -767,10 +870,10 @@ func TestNewLogHandlerOutputStdout(t *testing.T) { }, { desc: "Retry attempts filter matching", - config: &types.AccessLog{ + config: &otypes.AccessLog{ FilePath: "", Format: CommonFormat, - Filters: &types.AccessLogFilters{ + Filters: &otypes.AccessLogFilters{ RetryAttempts: true, }, }, @@ -778,10 +881,10 @@ func TestNewLogHandlerOutputStdout(t *testing.T) { }, { desc: "Default mode keep", - config: &types.AccessLog{ + config: &otypes.AccessLog{ FilePath: "", Format: CommonFormat, - Fields: &types.AccessLogFields{ + Fields: &otypes.AccessLogFields{ DefaultMode: "keep", }, }, @@ -789,10 +892,10 @@ func TestNewLogHandlerOutputStdout(t *testing.T) { }, { desc: "Default mode keep with override", - config: &types.AccessLog{ + config: &otypes.AccessLog{ FilePath: "", Format: CommonFormat, - Fields: &types.AccessLogFields{ + Fields: &otypes.AccessLogFields{ DefaultMode: "keep", Names: map[string]string{ ClientHost: "drop", @@ -803,10 +906,10 @@ func TestNewLogHandlerOutputStdout(t *testing.T) { }, { desc: "Default mode drop", - config: &types.AccessLog{ + config: &otypes.AccessLog{ FilePath: "", Format: CommonFormat, - Fields: &types.AccessLogFields{ + Fields: &otypes.AccessLogFields{ DefaultMode: "drop", }, }, @@ -814,10 +917,10 @@ func TestNewLogHandlerOutputStdout(t *testing.T) { }, { desc: "Default mode drop with override", - config: &types.AccessLog{ + config: &otypes.AccessLog{ FilePath: "", Format: CommonFormat, - Fields: &types.AccessLogFields{ + Fields: &otypes.AccessLogFields{ DefaultMode: "drop", Names: map[string]string{ ClientHost: "drop", @@ -829,16 +932,16 @@ func TestNewLogHandlerOutputStdout(t *testing.T) { }, { desc: "Default mode drop with header dropped", - config: &types.AccessLog{ + config: &otypes.AccessLog{ FilePath: "", Format: CommonFormat, - Fields: &types.AccessLogFields{ + Fields: &otypes.AccessLogFields{ DefaultMode: "drop", Names: map[string]string{ ClientHost: "drop", ClientUsername: "keep", }, - Headers: &types.FieldHeaders{ + Headers: &otypes.FieldHeaders{ DefaultMode: "drop", }, }, @@ -847,16 +950,16 @@ func TestNewLogHandlerOutputStdout(t *testing.T) { }, { desc: "Default mode drop with header redacted", - config: &types.AccessLog{ + config: &otypes.AccessLog{ FilePath: "", Format: CommonFormat, - Fields: &types.AccessLogFields{ + Fields: &otypes.AccessLogFields{ DefaultMode: "drop", Names: map[string]string{ ClientHost: "drop", ClientUsername: "keep", }, - Headers: &types.FieldHeaders{ + Headers: &otypes.FieldHeaders{ DefaultMode: "redact", }, }, @@ -865,16 +968,16 @@ func TestNewLogHandlerOutputStdout(t *testing.T) { }, { desc: "Default mode drop with header redacted", - config: &types.AccessLog{ + config: &otypes.AccessLog{ FilePath: "", Format: CommonFormat, - Fields: &types.AccessLogFields{ + Fields: &otypes.AccessLogFields{ DefaultMode: "drop", Names: map[string]string{ ClientHost: "drop", ClientUsername: "keep", }, - Headers: &types.FieldHeaders{ + Headers: &otypes.FieldHeaders{ DefaultMode: "keep", Names: map[string]string{ "Referer": "redact", @@ -897,12 +1000,12 @@ func TestNewLogHandlerOutputStdout(t *testing.T) { written, err := os.ReadFile(file.Name()) require.NoError(t, err, "unable to read captured stdout from file") - assertValidLogData(t, test.expectedLog, written) + assertValidCommonLogData(t, test.expectedLog, written) }) } } -func assertValidLogData(t *testing.T, expected string, logData []byte) { +func assertValidCommonLogData(t *testing.T, expected string, logData []byte) { t.Helper() if len(expected) == 0 { @@ -935,6 +1038,35 @@ func assertValidLogData(t *testing.T, expected string, logData []byte) { assert.Regexp(t, `\d*ms`, result[Duration], formatErrMessage) } +func assertValidGenericCLFLogData(t *testing.T, expected string, logData []byte) { + t.Helper() + + if len(expected) == 0 { + assert.Empty(t, logData) + t.Log(string(logData)) + return + } + + result, err := ParseAccessLog(string(logData)) + require.NoError(t, err) + + resultExpected, err := ParseAccessLog(expected) + require.NoError(t, err) + + formatErrMessage := fmt.Sprintf("Expected:\t%q\nActual:\t%q", expected, string(logData)) + + require.Len(t, result, len(resultExpected), formatErrMessage) + assert.Equal(t, resultExpected[ClientHost], result[ClientHost], formatErrMessage) + assert.Equal(t, resultExpected[ClientUsername], result[ClientUsername], formatErrMessage) + assert.Equal(t, resultExpected[RequestMethod], result[RequestMethod], formatErrMessage) + assert.Equal(t, resultExpected[RequestPath], result[RequestPath], formatErrMessage) + assert.Equal(t, resultExpected[RequestProtocol], result[RequestProtocol], formatErrMessage) + assert.Equal(t, resultExpected[OriginStatus], result[OriginStatus], formatErrMessage) + assert.Equal(t, resultExpected[OriginContentSize], result[OriginContentSize], formatErrMessage) + assert.Equal(t, resultExpected[RequestRefererHeader], result[RequestRefererHeader], formatErrMessage) + assert.Equal(t, resultExpected[RequestUserAgentHeader], result[RequestUserAgentHeader], formatErrMessage) +} + func captureStdout(t *testing.T) (out *os.File, restoreStdout func()) { t.Helper() @@ -952,9 +1084,9 @@ func captureStdout(t *testing.T) (out *os.File, restoreStdout func()) { return file, restoreStdout } -func doLoggingTLSOpt(t *testing.T, config *types.AccessLog, enableTLS, tracing bool) { +func doLoggingTLSOpt(t *testing.T, config *otypes.AccessLog, enableTLS, tracing bool) { t.Helper() - logger, err := NewHandler(config) + logger, err := NewHandler(t.Context(), config) require.NoError(t, err) t.Cleanup(func() { err := logger.Close() @@ -998,20 +1130,28 @@ func doLoggingTLSOpt(t *testing.T, config *types.AccessLog, enableTLS, tracing b chain := alice.New() chain = chain.Append(capture.Wrap) - chain = chain.Append(WrapHandler(logger)) + + // Injection of the observability variables in the request context. + chain = chain.Append(func(next http.Handler) (http.Handler, error) { + return observability.WithObservabilityHandler(next, observability.Observability{ + AccessLogsEnabled: true, + }), nil + }) + + chain = chain.Append(logger.AliceConstructor()) handler, err := chain.Then(http.HandlerFunc(logWriterTestHandlerFunc)) require.NoError(t, err) handler.ServeHTTP(httptest.NewRecorder(), req) } -func doLoggingTLS(t *testing.T, config *types.AccessLog, tracing bool) { +func doLoggingTLS(t *testing.T, config *otypes.AccessLog, tracing bool) { t.Helper() doLoggingTLSOpt(t, config, true, tracing) } -func doLogging(t *testing.T, config *types.AccessLog, tracing bool) { +func doLogging(t *testing.T, config *otypes.AccessLog, tracing bool) { t.Helper() doLoggingTLSOpt(t, config, false, tracing) @@ -1040,10 +1180,10 @@ func logWriterTestHandlerFunc(rw http.ResponseWriter, r *http.Request) { rw.WriteHeader(testStatus) } -func doLoggingWithAbortedStream(t *testing.T, config *types.AccessLog) { +func doLoggingWithAbortedStream(t *testing.T, config *otypes.AccessLog) { t.Helper() - logger, err := NewHandler(config) + logger, err := NewHandler(t.Context(), config) require.NoError(t, err) t.Cleanup(func() { err := logger.Close() @@ -1055,7 +1195,7 @@ func doLoggingWithAbortedStream(t *testing.T, config *types.AccessLog) { require.NoError(t, err, "logger should create "+config.FilePath) } - reqContext, cancelRequest := context.WithCancel(context.Background()) + reqContext, cancelRequest := context.WithCancel(t.Context()) req := &http.Request{ Header: map[string][]string{ @@ -1085,7 +1225,15 @@ func doLoggingWithAbortedStream(t *testing.T, config *types.AccessLog) { }), nil }) chain = chain.Append(capture.Wrap) - chain = chain.Append(WrapHandler(logger)) + + // Injection of the observability variables in the request context. + chain = chain.Append(func(next http.Handler) (http.Handler, error) { + return observability.WithObservabilityHandler(next, observability.Observability{ + AccessLogsEnabled: true, + }), nil + }) + + chain = chain.Append(logger.AliceConstructor()) service := NewFieldHandler(http.HandlerFunc(streamBackend), ServiceURL, "http://stream", nil) service = NewFieldHandler(service, ServiceAddr, "127.0.0.1", nil) diff --git a/pkg/middlewares/addprefix/add_prefix.go b/pkg/middlewares/addprefix/add_prefix.go index 46f8d98d0..b698cc9ce 100644 --- a/pkg/middlewares/addprefix/add_prefix.go +++ b/pkg/middlewares/addprefix/add_prefix.go @@ -7,7 +7,6 @@ import ( "github.com/traefik/traefik/v3/pkg/config/dynamic" "github.com/traefik/traefik/v3/pkg/middlewares" - "go.opentelemetry.io/otel/trace" ) const ( @@ -39,8 +38,8 @@ func New(ctx context.Context, next http.Handler, config dynamic.AddPrefix, name return result, nil } -func (a *addPrefix) GetTracingInformation() (string, string, trace.SpanKind) { - return a.name, typeName, trace.SpanKindInternal +func (a *addPrefix) GetTracingInformation() (string, string) { + return a.name, typeName } func (a *addPrefix) ServeHTTP(rw http.ResponseWriter, req *http.Request) { diff --git a/pkg/middlewares/addprefix/add_prefix_test.go b/pkg/middlewares/addprefix/add_prefix_test.go index 2bcc2ad7f..37caf08e2 100644 --- a/pkg/middlewares/addprefix/add_prefix_test.go +++ b/pkg/middlewares/addprefix/add_prefix_test.go @@ -1,7 +1,6 @@ package addprefix import ( - "context" "net/http" "testing" @@ -34,7 +33,7 @@ func TestNewAddPrefix(t *testing.T) { next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}) - _, err := New(context.Background(), next, test.prefix, "foo-add-prefix") + _, err := New(t.Context(), next, test.prefix, "foo-add-prefix") if test.expectsError { assert.Error(t, err) } else { @@ -87,7 +86,7 @@ func TestAddPrefix(t *testing.T) { req := testhelpers.MustNewRequest(http.MethodGet, "http://localhost"+test.path, nil) - handler, err := New(context.Background(), next, test.prefix, "foo-add-prefix") + handler, err := New(t.Context(), next, test.prefix, "foo-add-prefix") require.NoError(t, err) handler.ServeHTTP(nil, req) diff --git a/pkg/middlewares/auth/basic_auth.go b/pkg/middlewares/auth/basic_auth.go index e6c175bcb..863c968f3 100644 --- a/pkg/middlewares/auth/basic_auth.go +++ b/pkg/middlewares/auth/basic_auth.go @@ -12,7 +12,6 @@ import ( "github.com/traefik/traefik/v3/pkg/middlewares" "github.com/traefik/traefik/v3/pkg/middlewares/accesslog" "github.com/traefik/traefik/v3/pkg/middlewares/observability" - "go.opentelemetry.io/otel/trace" "golang.org/x/sync/singleflight" ) @@ -61,8 +60,8 @@ func NewBasic(ctx context.Context, next http.Handler, authConfig dynamic.BasicAu return ba, nil } -func (b *basicAuth) GetTracingInformation() (string, string, trace.SpanKind) { - return b.name, typeNameBasic, trace.SpanKindInternal +func (b *basicAuth) GetTracingInformation() (string, string) { + return b.name, typeNameBasic } func (b *basicAuth) ServeHTTP(rw http.ResponseWriter, req *http.Request) { diff --git a/pkg/middlewares/auth/basic_auth_test.go b/pkg/middlewares/auth/basic_auth_test.go index 6a59f0111..b81a22c03 100644 --- a/pkg/middlewares/auth/basic_auth_test.go +++ b/pkg/middlewares/auth/basic_auth_test.go @@ -1,7 +1,6 @@ package auth import ( - "context" "fmt" "io" "net/http" @@ -25,13 +24,13 @@ func TestBasicAuthFail(t *testing.T) { auth := dynamic.BasicAuth{ Users: []string{"test"}, } - _, err := NewBasic(context.Background(), next, auth, "authName") + _, err := NewBasic(t.Context(), next, auth, "authName") require.Error(t, err) auth2 := dynamic.BasicAuth{ Users: []string{"test:test"}, } - authMiddleware, err := NewBasic(context.Background(), next, auth2, "authTest") + authMiddleware, err := NewBasic(t.Context(), next, auth2, "authTest") require.NoError(t, err) ts := httptest.NewServer(authMiddleware) @@ -54,7 +53,7 @@ func TestBasicAuthSuccess(t *testing.T) { auth := dynamic.BasicAuth{ Users: []string{"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/"}, } - authMiddleware, err := NewBasic(context.Background(), next, auth, "authName") + authMiddleware, err := NewBasic(t.Context(), next, auth, "authName") require.NoError(t, err) ts := httptest.NewServer(authMiddleware) @@ -85,7 +84,7 @@ func TestBasicAuthUserHeader(t *testing.T) { Users: []string{"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/"}, HeaderField: "X-Webauth-User", } - middleware, err := NewBasic(context.Background(), next, auth, "authName") + middleware, err := NewBasic(t.Context(), next, auth, "authName") require.NoError(t, err) ts := httptest.NewServer(middleware) @@ -116,7 +115,7 @@ func TestBasicAuthHeaderRemoved(t *testing.T) { RemoveHeader: true, Users: []string{"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/"}, } - middleware, err := NewBasic(context.Background(), next, auth, "authName") + middleware, err := NewBasic(t.Context(), next, auth, "authName") require.NoError(t, err) ts := httptest.NewServer(middleware) @@ -147,7 +146,7 @@ func TestBasicAuthHeaderPresent(t *testing.T) { auth := dynamic.BasicAuth{ Users: []string{"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/"}, } - middleware, err := NewBasic(context.Background(), next, auth, "authName") + middleware, err := NewBasic(t.Context(), next, auth, "authName") require.NoError(t, err) ts := httptest.NewServer(middleware) @@ -177,7 +176,7 @@ func TestBasicAuthConcurrentHashOnce(t *testing.T) { Users: []string{"test:$2a$04$.8sTYfcxbSplCtoxt5TdJOgpBYkarKtZYsYfYxQ1edbYRuO1DNi0e"}, } - authMiddleware, err := NewBasic(context.Background(), next, auth, "authName") + authMiddleware, err := NewBasic(t.Context(), next, auth, "authName") require.NoError(t, err) hashCount := 0 @@ -277,7 +276,7 @@ func TestBasicAuthUsersFromFile(t *testing.T) { fmt.Fprintln(w, "traefik") }) - authenticator, err := NewBasic(context.Background(), next, authenticatorConfiguration, "authName") + authenticator, err := NewBasic(t.Context(), next, authenticatorConfiguration, "authName") require.NoError(t, err) ts := httptest.NewServer(authenticator) diff --git a/pkg/middlewares/auth/digest_auth.go b/pkg/middlewares/auth/digest_auth.go index 25c22865f..af64a134b 100644 --- a/pkg/middlewares/auth/digest_auth.go +++ b/pkg/middlewares/auth/digest_auth.go @@ -12,7 +12,6 @@ import ( "github.com/traefik/traefik/v3/pkg/middlewares" "github.com/traefik/traefik/v3/pkg/middlewares/accesslog" "github.com/traefik/traefik/v3/pkg/middlewares/observability" - "go.opentelemetry.io/otel/trace" ) const ( @@ -54,8 +53,8 @@ func NewDigest(ctx context.Context, next http.Handler, authConfig dynamic.Digest return da, nil } -func (d *digestAuth) GetTracingInformation() (string, string, trace.SpanKind) { - return d.name, typeNameDigest, trace.SpanKindInternal +func (d *digestAuth) GetTracingInformation() (string, string) { + return d.name, typeNameDigest } func (d *digestAuth) ServeHTTP(rw http.ResponseWriter, req *http.Request) { diff --git a/pkg/middlewares/auth/digest_auth_test.go b/pkg/middlewares/auth/digest_auth_test.go index ae472e3d0..b0f485b38 100644 --- a/pkg/middlewares/auth/digest_auth_test.go +++ b/pkg/middlewares/auth/digest_auth_test.go @@ -1,7 +1,6 @@ package auth import ( - "context" "fmt" "io" "net/http" @@ -23,7 +22,7 @@ func TestDigestAuthError(t *testing.T) { auth := dynamic.DigestAuth{ Users: []string{"test"}, } - _, err := NewDigest(context.Background(), next, auth, "authName") + _, err := NewDigest(t.Context(), next, auth, "authName") assert.Error(t, err) } @@ -35,7 +34,7 @@ func TestDigestAuthFail(t *testing.T) { auth := dynamic.DigestAuth{ Users: []string{"test:traefik:a2688e031edb4be6a3797f3882655c05"}, } - authMiddleware, err := NewDigest(context.Background(), next, auth, "authName") + authMiddleware, err := NewDigest(t.Context(), next, auth, "authName") require.NoError(t, err) assert.NotNil(t, authMiddleware, "this should not be nil") @@ -109,7 +108,7 @@ func TestDigestAuthUsersFromFile(t *testing.T) { fmt.Fprintln(w, "traefik") }) - authenticator, err := NewDigest(context.Background(), next, authenticatorConfiguration, "authName") + authenticator, err := NewDigest(t.Context(), next, authenticatorConfiguration, "authName") require.NoError(t, err) ts := httptest.NewServer(authenticator) diff --git a/pkg/middlewares/auth/forward.go b/pkg/middlewares/auth/forward.go index 0ade40b7d..4438a486e 100644 --- a/pkg/middlewares/auth/forward.go +++ b/pkg/middlewares/auth/forward.go @@ -17,7 +17,8 @@ import ( "github.com/traefik/traefik/v3/pkg/middlewares" "github.com/traefik/traefik/v3/pkg/middlewares/accesslog" "github.com/traefik/traefik/v3/pkg/middlewares/observability" - "github.com/traefik/traefik/v3/pkg/tracing" + "github.com/traefik/traefik/v3/pkg/observability/tracing" + "github.com/traefik/traefik/v3/pkg/proxy/httputil" "github.com/traefik/traefik/v3/pkg/types" "github.com/vulcand/oxy/v2/forward" "github.com/vulcand/oxy/v2/utils" @@ -130,8 +131,8 @@ func NewForward(ctx context.Context, next http.Handler, config dynamic.ForwardAu return fa, nil } -func (fa *forwardAuth) GetTracingInformation() (string, string, trace.SpanKind) { - return fa.name, typeNameForward, trace.SpanKindInternal +func (fa *forwardAuth) GetTracingInformation() (string, string) { + return fa.name, typeNameForward } func (fa *forwardAuth) ServeHTTP(rw http.ResponseWriter, req *http.Request) { @@ -179,7 +180,7 @@ func (fa *forwardAuth) ServeHTTP(rw http.ResponseWriter, req *http.Request) { var forwardSpan trace.Span var tracer *tracing.Tracer - if tracer = tracing.TracerFromContext(req.Context()); tracer != nil { + if tracer = tracing.TracerFromContext(req.Context()); tracer != nil && observability.TracingEnabled(req.Context()) { var tracingCtx context.Context tracingCtx, forwardSpan = tracer.Start(req.Context(), "AuthRequest", trace.WithSpanKind(trace.SpanKindClient)) defer forwardSpan.End() @@ -195,7 +196,12 @@ func (fa *forwardAuth) ServeHTTP(rw http.ResponseWriter, req *http.Request) { logger.Debug().Err(forwardErr).Msgf("Error calling %s", fa.address) observability.SetStatusErrorf(req.Context(), "Error calling %s. Cause: %s", fa.address, forwardErr) - rw.WriteHeader(http.StatusInternalServerError) + statusCode := http.StatusInternalServerError + if errors.Is(forwardErr, context.Canceled) { + statusCode = httputil.StatusClientClosedRequest + } + + rw.WriteHeader(statusCode) return } defer forwardResponse.Body.Close() diff --git a/pkg/middlewares/auth/forward_test.go b/pkg/middlewares/auth/forward_test.go index 82ccb7346..95e3d7910 100644 --- a/pkg/middlewares/auth/forward_test.go +++ b/pkg/middlewares/auth/forward_test.go @@ -11,12 +11,15 @@ import ( "net/url" "strconv" "testing" + "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/traefik/traefik/v3/pkg/config/dynamic" + "github.com/traefik/traefik/v3/pkg/middlewares/observability" + "github.com/traefik/traefik/v3/pkg/observability/tracing" + "github.com/traefik/traefik/v3/pkg/proxy/httputil" "github.com/traefik/traefik/v3/pkg/testhelpers" - "github.com/traefik/traefik/v3/pkg/tracing" "github.com/vulcand/oxy/v2/forward" "go.opentelemetry.io/contrib/propagators/autoprop" "go.opentelemetry.io/otel" @@ -37,7 +40,7 @@ func TestForwardAuthFail(t *testing.T) { })) t.Cleanup(server.Close) - middleware, err := NewForward(context.Background(), next, dynamic.ForwardAuth{ + middleware, err := NewForward(t.Context(), next, dynamic.ForwardAuth{ Address: server.URL, }, "authTest") require.NoError(t, err) @@ -90,7 +93,7 @@ func TestForwardAuthSuccess(t *testing.T) { AuthResponseHeadersRegex: "^Foo-", AddAuthCookiesToResponse: []string{"authCookie"}, } - middleware, err := NewForward(context.Background(), next, auth, "authTest") + middleware, err := NewForward(t.Context(), next, auth, "authTest") require.NoError(t, err) ts := httptest.NewServer(middleware) @@ -135,7 +138,7 @@ func TestForwardAuthForwardBody(t *testing.T) { maxBodySize := int64(len(data)) auth := dynamic.ForwardAuth{Address: server.URL, ForwardBody: true, MaxBodySize: &maxBodySize} - middleware, err := NewForward(context.Background(), next, auth, "authTest") + middleware, err := NewForward(t.Context(), next, auth, "authTest") require.NoError(t, err) ts := httptest.NewServer(middleware) @@ -170,7 +173,7 @@ func TestForwardAuthForwardBodyEmptyBody(t *testing.T) { auth := dynamic.ForwardAuth{Address: server.URL, ForwardBody: true} - middleware, err := NewForward(context.Background(), next, auth, "authTest") + middleware, err := NewForward(t.Context(), next, auth, "authTest") require.NoError(t, err) ts := httptest.NewServer(middleware) @@ -208,7 +211,7 @@ func TestForwardAuthForwardBodySizeLimit(t *testing.T) { maxBodySize := int64(len(data)) - 1 auth := dynamic.ForwardAuth{Address: server.URL, ForwardBody: true, MaxBodySize: &maxBodySize} - middleware, err := NewForward(context.Background(), next, auth, "authTest") + middleware, err := NewForward(t.Context(), next, auth, "authTest") require.NoError(t, err) ts := httptest.NewServer(middleware) @@ -245,7 +248,7 @@ func TestForwardAuthNotForwardBody(t *testing.T) { auth := dynamic.ForwardAuth{Address: server.URL} - middleware, err := NewForward(context.Background(), next, auth, "authTest") + middleware, err := NewForward(t.Context(), next, auth, "authTest") require.NoError(t, err) ts := httptest.NewServer(middleware) @@ -273,7 +276,7 @@ func TestForwardAuthRedirect(t *testing.T) { auth := dynamic.ForwardAuth{Address: authTs.URL} - authMiddleware, err := NewForward(context.Background(), next, auth, "authTest") + authMiddleware, err := NewForward(t.Context(), next, auth, "authTest") require.NoError(t, err) ts := httptest.NewServer(authMiddleware) @@ -324,7 +327,7 @@ func TestForwardAuthRemoveHopByHopHeaders(t *testing.T) { auth := dynamic.ForwardAuth{Address: authTs.URL} - authMiddleware, err := NewForward(context.Background(), next, auth, "authTest") + authMiddleware, err := NewForward(t.Context(), next, auth, "authTest") require.NoError(t, err) ts := httptest.NewServer(authMiddleware) @@ -370,7 +373,7 @@ func TestForwardAuthFailResponseHeaders(t *testing.T) { auth := dynamic.ForwardAuth{ Address: authTs.URL, } - authMiddleware, err := NewForward(context.Background(), next, auth, "authTest") + authMiddleware, err := NewForward(t.Context(), next, auth, "authTest") require.NoError(t, err) ts := httptest.NewServer(authMiddleware) @@ -408,6 +411,75 @@ func TestForwardAuthFailResponseHeaders(t *testing.T) { assert.Equal(t, "Forbidden\n", string(body)) } +func TestForwardAuthClientClosedRequest(t *testing.T) { + requestStarted := make(chan struct{}) + requestCancelled := make(chan struct{}) + responseComplete := make(chan struct{}) + + authTs := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + close(requestStarted) + <-requestCancelled + })) + t.Cleanup(authTs.Close) + + next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // next should not be called. + t.Fail() + }) + + auth := dynamic.ForwardAuth{ + Address: authTs.URL, + } + authMiddleware, err := NewForward(t.Context(), next, auth, "authTest") + require.NoError(t, err) + + ctx, cancel := context.WithCancel(t.Context()) + req := httptest.NewRequestWithContext(ctx, "GET", "http://foo", http.NoBody) + + recorder := httptest.NewRecorder() + go func() { + authMiddleware.ServeHTTP(recorder, req) + close(responseComplete) + }() + + <-requestStarted + + cancel() + close(requestCancelled) + + <-responseComplete + + assert.Equal(t, httputil.StatusClientClosedRequest, recorder.Result().StatusCode) +} + +func TestForwardAuthForwardError(t *testing.T) { + next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // next should not be called. + t.Fail() + }) + + auth := dynamic.ForwardAuth{ + Address: "http://non-existing-server", + } + authMiddleware, err := NewForward(t.Context(), next, auth, "authTest") + require.NoError(t, err) + + ctx, cancel := context.WithTimeout(t.Context(), 1*time.Microsecond) + defer cancel() + req := httptest.NewRequestWithContext(ctx, http.MethodGet, "http://foo", nil) + + recorder := httptest.NewRecorder() + responseComplete := make(chan struct{}) + go func() { + authMiddleware.ServeHTTP(recorder, req) + close(responseComplete) + }() + + <-responseComplete + + assert.Equal(t, http.StatusInternalServerError, recorder.Result().StatusCode) +} + func Test_writeHeader(t *testing.T) { testCases := []struct { name string @@ -682,9 +754,13 @@ func TestForwardAuthTracing(t *testing.T) { Address: server.URL, AuthRequestHeaders: []string{"X-Foo"}, } - next, err := NewForward(context.Background(), next, auth, "authTest") + next, err := NewForward(t.Context(), next, auth, "authTest") require.NoError(t, err) + next = observability.WithObservabilityHandler(next, observability.Observability{ + TracingEnabled: true, + }) + req := httptest.NewRequest(http.MethodGet, "http://www.test.com/search?q=Opentelemetry", nil) req.RemoteAddr = "10.0.0.1:1234" req.Header.Set("User-Agent", "forward-test") @@ -725,7 +801,7 @@ func TestForwardAuthPreserveLocationHeader(t *testing.T) { Address: server.URL, PreserveLocationHeader: true, } - middleware, err := NewForward(context.Background(), next, auth, "authTest") + middleware, err := NewForward(t.Context(), next, auth, "authTest") require.NoError(t, err) ts := httptest.NewServer(middleware) @@ -779,7 +855,7 @@ func TestForwardAuthPreserveRequestMethod(t *testing.T) { PreserveRequestMethod: test.preserveRequestMethod, } - middleware, err := NewForward(context.Background(), next, auth, "authTest") + middleware, err := NewForward(t.Context(), next, auth, "authTest") require.NoError(t, err) ts := httptest.NewServer(middleware) diff --git a/pkg/middlewares/buffering/buffering.go b/pkg/middlewares/buffering/buffering.go index ec9100a98..753436add 100644 --- a/pkg/middlewares/buffering/buffering.go +++ b/pkg/middlewares/buffering/buffering.go @@ -6,10 +6,9 @@ import ( "github.com/rs/zerolog" "github.com/traefik/traefik/v3/pkg/config/dynamic" - "github.com/traefik/traefik/v3/pkg/logs" "github.com/traefik/traefik/v3/pkg/middlewares" + "github.com/traefik/traefik/v3/pkg/observability/logs" oxybuffer "github.com/vulcand/oxy/v2/buffer" - "go.opentelemetry.io/otel/trace" ) const ( @@ -48,8 +47,8 @@ func New(ctx context.Context, next http.Handler, config dynamic.Buffering, name }, nil } -func (b *buffer) GetTracingInformation() (string, string, trace.SpanKind) { - return b.name, typeName, trace.SpanKindInternal +func (b *buffer) GetTracingInformation() (string, string) { + return b.name, typeName } func (b *buffer) ServeHTTP(rw http.ResponseWriter, req *http.Request) { diff --git a/pkg/middlewares/buffering/buffering_test.go b/pkg/middlewares/buffering/buffering_test.go index de6f50170..b070a180f 100644 --- a/pkg/middlewares/buffering/buffering_test.go +++ b/pkg/middlewares/buffering/buffering_test.go @@ -2,7 +2,6 @@ package buffering import ( "bytes" - "context" "crypto/rand" "math" "net/http" @@ -57,7 +56,7 @@ func TestBuffering(t *testing.T) { require.NoError(t, err) }) - buffMiddleware, err := New(context.Background(), next, test.config, "foo") + buffMiddleware, err := New(t.Context(), next, test.config, "foo") require.NoError(t, err) req := httptest.NewRequest(http.MethodPost, "http://localhost", bytes.NewBuffer(test.body)) diff --git a/pkg/middlewares/circuitbreaker/circuit_breaker.go b/pkg/middlewares/circuitbreaker/circuit_breaker.go index ceaa93de4..72eecd428 100644 --- a/pkg/middlewares/circuitbreaker/circuit_breaker.go +++ b/pkg/middlewares/circuitbreaker/circuit_breaker.go @@ -8,11 +8,10 @@ import ( "github.com/rs/zerolog" "github.com/rs/zerolog/log" "github.com/traefik/traefik/v3/pkg/config/dynamic" - "github.com/traefik/traefik/v3/pkg/logs" "github.com/traefik/traefik/v3/pkg/middlewares" "github.com/traefik/traefik/v3/pkg/middlewares/observability" + "github.com/traefik/traefik/v3/pkg/observability/logs" "github.com/vulcand/oxy/v2/cbreaker" - "go.opentelemetry.io/otel/trace" ) const typeName = "CircuitBreaker" @@ -68,8 +67,8 @@ func New(ctx context.Context, next http.Handler, confCircuitBreaker dynamic.Circ }, nil } -func (c *circuitBreaker) GetTracingInformation() (string, string, trace.SpanKind) { - return c.name, typeName, trace.SpanKindInternal +func (c *circuitBreaker) GetTracingInformation() (string, string) { + return c.name, typeName } func (c *circuitBreaker) ServeHTTP(rw http.ResponseWriter, req *http.Request) { diff --git a/pkg/middlewares/compress/acceptencoding_test.go b/pkg/middlewares/compress/acceptencoding_test.go index d3059af6e..6cacbb951 100644 --- a/pkg/middlewares/compress/acceptencoding_test.go +++ b/pkg/middlewares/compress/acceptencoding_test.go @@ -1,7 +1,6 @@ package compress import ( - "context" "testing" "github.com/stretchr/testify/assert" @@ -153,7 +152,7 @@ func Test_getCompressionEncoding(t *testing.T) { DefaultEncoding: test.defaultEncoding, } - h, err := New(context.Background(), nil, conf, "test") + h, err := New(t.Context(), nil, conf, "test") require.NoError(t, err) c, ok := h.(*compress) diff --git a/pkg/middlewares/compress/compress.go b/pkg/middlewares/compress/compress.go index 0bb3788d2..4ab312cc2 100644 --- a/pkg/middlewares/compress/compress.go +++ b/pkg/middlewares/compress/compress.go @@ -13,7 +13,6 @@ import ( "github.com/klauspost/compress/zstd" "github.com/traefik/traefik/v3/pkg/config/dynamic" "github.com/traefik/traefik/v3/pkg/middlewares" - "go.opentelemetry.io/otel/trace" ) const typeName = "Compress" @@ -181,8 +180,8 @@ func (c *compress) chooseHandler(typ string, rw http.ResponseWriter, req *http.R } } -func (c *compress) GetTracingInformation() (string, string, trace.SpanKind) { - return c.name, typeName, trace.SpanKindInternal +func (c *compress) GetTracingInformation() (string, string) { + return c.name, typeName } func (c *compress) newGzipHandler() (http.Handler, error) { diff --git a/pkg/middlewares/compress/compress_test.go b/pkg/middlewares/compress/compress_test.go index 3c53c4c30..9754de238 100644 --- a/pkg/middlewares/compress/compress_test.go +++ b/pkg/middlewares/compress/compress_test.go @@ -2,7 +2,6 @@ package compress import ( "compress/gzip" - "context" "io" "net/http" "net/http/httptest" @@ -116,7 +115,7 @@ func TestNegotiation(t *testing.T) { MinResponseBodyBytes: 1, Encodings: defaultSupportedEncodings, } - handler, err := New(context.Background(), next, cfg, "testing") + handler, err := New(t.Context(), next, cfg, "testing") require.NoError(t, err) rw := httptest.NewRecorder() @@ -137,7 +136,7 @@ func TestShouldCompressWhenNoContentEncodingHeader(t *testing.T) { _, err := rw.Write(baseBody) assert.NoError(t, err) }) - handler, err := New(context.Background(), next, dynamic.Compress{Encodings: defaultSupportedEncodings}, "testing") + handler, err := New(t.Context(), next, dynamic.Compress{Encodings: defaultSupportedEncodings}, "testing") require.NoError(t, err) rw := httptest.NewRecorder() @@ -167,7 +166,7 @@ func TestShouldNotCompressWhenContentEncodingHeader(t *testing.T) { http.Error(rw, err.Error(), http.StatusInternalServerError) } }) - handler, err := New(context.Background(), next, dynamic.Compress{Encodings: defaultSupportedEncodings}, "testing") + handler, err := New(t.Context(), next, dynamic.Compress{Encodings: defaultSupportedEncodings}, "testing") require.NoError(t, err) rw := httptest.NewRecorder() @@ -189,7 +188,7 @@ func TestShouldNotCompressWhenNoAcceptEncodingHeader(t *testing.T) { http.Error(rw, err.Error(), http.StatusInternalServerError) } }) - handler, err := New(context.Background(), next, dynamic.Compress{Encodings: defaultSupportedEncodings}, "testing") + handler, err := New(t.Context(), next, dynamic.Compress{Encodings: defaultSupportedEncodings}, "testing") require.NoError(t, err) rw := httptest.NewRecorder() @@ -211,7 +210,7 @@ func TestEmptyAcceptEncoding(t *testing.T) { http.Error(rw, err.Error(), http.StatusInternalServerError) } }) - handler, err := New(context.Background(), next, dynamic.Compress{Encodings: defaultSupportedEncodings}, "testing") + handler, err := New(t.Context(), next, dynamic.Compress{Encodings: defaultSupportedEncodings}, "testing") require.NoError(t, err) rw := httptest.NewRecorder() @@ -238,7 +237,7 @@ func TestShouldNotCompressWhenIdentityAcceptEncodingHeader(t *testing.T) { http.Error(rw, err.Error(), http.StatusInternalServerError) } }) - handler, err := New(context.Background(), next, dynamic.Compress{Encodings: defaultSupportedEncodings}, "testing") + handler, err := New(t.Context(), next, dynamic.Compress{Encodings: defaultSupportedEncodings}, "testing") require.NoError(t, err) rw := httptest.NewRecorder() @@ -265,7 +264,7 @@ func TestShouldNotCompressWhenEmptyAcceptEncodingHeader(t *testing.T) { http.Error(rw, err.Error(), http.StatusInternalServerError) } }) - handler, err := New(context.Background(), next, dynamic.Compress{Encodings: defaultSupportedEncodings}, "testing") + handler, err := New(t.Context(), next, dynamic.Compress{Encodings: defaultSupportedEncodings}, "testing") require.NoError(t, err) rw := httptest.NewRecorder() @@ -287,7 +286,7 @@ func TestShouldNotCompressHeadRequest(t *testing.T) { http.Error(rw, err.Error(), http.StatusInternalServerError) } }) - handler, err := New(context.Background(), next, dynamic.Compress{Encodings: defaultSupportedEncodings}, "testing") + handler, err := New(t.Context(), next, dynamic.Compress{Encodings: defaultSupportedEncodings}, "testing") require.NoError(t, err) rw := httptest.NewRecorder() @@ -377,7 +376,7 @@ func TestShouldNotCompressWhenSpecificContentType(t *testing.T) { } }) - handler, err := New(context.Background(), next, test.conf, "test") + handler, err := New(t.Context(), next, test.conf, "test") require.NoError(t, err) rw := httptest.NewRecorder() @@ -423,7 +422,7 @@ func TestShouldCompressWhenSpecificContentType(t *testing.T) { } }) - handler, err := New(context.Background(), next, test.conf, "test") + handler, err := New(t.Context(), next, test.conf, "test") require.NoError(t, err) rw := httptest.NewRecorder() @@ -473,7 +472,7 @@ func TestIntegrationShouldNotCompress(t *testing.T) { for _, test := range testCases { t.Run(test.name, func(t *testing.T) { - compress, err := New(context.Background(), test.handler, dynamic.Compress{Encodings: defaultSupportedEncodings}, "testing") + compress, err := New(t.Context(), test.handler, dynamic.Compress{Encodings: defaultSupportedEncodings}, "testing") require.NoError(t, err) ts := httptest.NewServer(compress) @@ -508,7 +507,7 @@ func TestShouldWriteHeaderWhenFlush(t *testing.T) { http.Error(rw, err.Error(), http.StatusInternalServerError) } }) - handler, err := New(context.Background(), next, dynamic.Compress{Encodings: defaultSupportedEncodings}, "testing") + handler, err := New(t.Context(), next, dynamic.Compress{Encodings: defaultSupportedEncodings}, "testing") require.NoError(t, err) ts := httptest.NewServer(handler) @@ -559,7 +558,7 @@ func TestIntegrationShouldCompress(t *testing.T) { for _, test := range testCases { t.Run(test.name, func(t *testing.T) { - compress, err := New(context.Background(), test.handler, dynamic.Compress{Encodings: defaultSupportedEncodings}, "testing") + compress, err := New(t.Context(), test.handler, dynamic.Compress{Encodings: defaultSupportedEncodings}, "testing") require.NoError(t, err) ts := httptest.NewServer(compress) @@ -619,7 +618,7 @@ func TestMinResponseBodyBytes(t *testing.T) { MinResponseBodyBytes: test.minResponseBodyBytes, Encodings: defaultSupportedEncodings, } - handler, err := New(context.Background(), next, cfg, "testing") + handler, err := New(t.Context(), next, cfg, "testing") require.NoError(t, err) rw := httptest.NewRecorder() @@ -679,7 +678,7 @@ func Test1xxResponses(t *testing.T) { MinResponseBodyBytes: 1024, Encodings: defaultSupportedEncodings, } - compress, err := New(context.Background(), next, cfg, "testing") + compress, err := New(t.Context(), next, cfg, "testing") require.NoError(t, err) server := httptest.NewServer(compress) @@ -723,7 +722,7 @@ func Test1xxResponses(t *testing.T) { return nil }, } - req, _ := http.NewRequestWithContext(httptrace.WithClientTrace(context.Background(), trace), http.MethodGet, server.URL, nil) + req, _ := http.NewRequestWithContext(httptrace.WithClientTrace(t.Context(), trace), http.MethodGet, server.URL, nil) req.Header.Add(acceptEncodingHeader, test.encoding) res, err := frontendClient.Do(req) @@ -779,7 +778,7 @@ func runCompressionBenchmark(b *testing.B, algorithm string) { _, err := rw.Write(baseBody) assert.NoError(b, err) }) - handler, _ := New(context.Background(), next, dynamic.Compress{}, "testing") + handler, _ := New(b.Context(), next, dynamic.Compress{}, "testing") req, _ := http.NewRequest(http.MethodGet, "/whatever", nil) req.Header.Set("Accept-Encoding", algorithm) diff --git a/pkg/middlewares/contenttype/content_type_test.go b/pkg/middlewares/contenttype/content_type_test.go index cfe8ba502..89e3a4866 100644 --- a/pkg/middlewares/contenttype/content_type_test.go +++ b/pkg/middlewares/contenttype/content_type_test.go @@ -1,7 +1,6 @@ package contenttype import ( - "context" "net/http" "net/http/httptest" "testing" @@ -60,7 +59,7 @@ func TestAutoDetection(t *testing.T) { if test.autoDetect { var err error - next, err = New(context.Background(), next, dynamic.ContentType{}, "foo-content-type") + next, err = New(t.Context(), next, dynamic.ContentType{}, "foo-content-type") require.NoError(t, err) } diff --git a/pkg/middlewares/customerrors/custom_errors.go b/pkg/middlewares/customerrors/custom_errors.go index 3cd866a74..6c1a91c4b 100644 --- a/pkg/middlewares/customerrors/custom_errors.go +++ b/pkg/middlewares/customerrors/custom_errors.go @@ -15,7 +15,6 @@ import ( "github.com/traefik/traefik/v3/pkg/middlewares/observability" "github.com/traefik/traefik/v3/pkg/types" "github.com/vulcand/oxy/v2/utils" - "go.opentelemetry.io/otel/trace" ) // Compile time validation that the response recorder implements http interfaces correctly. @@ -83,8 +82,8 @@ func New(ctx context.Context, next http.Handler, config dynamic.ErrorPage, servi }, nil } -func (c *customErrors) GetTracingInformation() (string, string, trace.SpanKind) { - return c.name, typeName, trace.SpanKindInternal +func (c *customErrors) GetTracingInformation() (string, string) { + return c.name, typeName } func (c *customErrors) ServeHTTP(rw http.ResponseWriter, req *http.Request) { @@ -123,11 +122,18 @@ func (c *customErrors) ServeHTTP(rw http.ResponseWriter, req *http.Request) { } var query string + + scheme := "http" + if req.TLS != nil { + scheme = "https" + } + orig := &url.URL{Scheme: scheme, Host: req.Host, Path: req.URL.Path, RawPath: req.URL.RawPath, RawQuery: req.URL.RawQuery, Fragment: req.URL.Fragment} + if len(c.backendQuery) > 0 { query = "/" + strings.TrimPrefix(c.backendQuery, "/") query = strings.ReplaceAll(query, "{status}", strconv.Itoa(code)) query = strings.ReplaceAll(query, "{originalStatus}", strconv.Itoa(originalCode)) - query = strings.ReplaceAll(query, "{url}", url.QueryEscape(req.URL.String())) + query = strings.ReplaceAll(query, "{url}", url.QueryEscape(orig.String())) } pageReq, err := newRequest("http://" + req.Host + query) diff --git a/pkg/middlewares/customerrors/custom_errors_test.go b/pkg/middlewares/customerrors/custom_errors_test.go index 37c13e53d..ec8eff153 100644 --- a/pkg/middlewares/customerrors/custom_errors_test.go +++ b/pkg/middlewares/customerrors/custom_errors_test.go @@ -170,11 +170,15 @@ func TestHandler(t *testing.T) { } _, _ = fmt.Fprintln(w, http.StatusText(test.backendCode)) }) - errorPageHandler, err := New(context.Background(), handler, *test.errorPage, serviceBuilderMock, "test") + errorPageHandler, err := New(t.Context(), handler, *test.errorPage, serviceBuilderMock, "test") require.NoError(t, err) req := testhelpers.MustNewRequest(http.MethodGet, "http://localhost/test?foo=bar&baz=buz", nil) + // Client like browser and curl will issue a relative HTTP request, which not have a host and scheme in the URL. But the http.NewRequest will set them automatically. + req.URL.Host = "" + req.URL.Scheme = "" + recorder := httptest.NewRecorder() errorPageHandler.ServeHTTP(recorder, req) @@ -205,7 +209,7 @@ func Test1xxResponses(t *testing.T) { config := dynamic.ErrorPage{Service: "error", Query: "/", Status: []string{"200"}} - errorPageHandler, err := New(context.Background(), next, config, serviceBuilderMock, "test") + errorPageHandler, err := New(t.Context(), next, config, serviceBuilderMock, "test") require.NoError(t, err) server := httptest.NewServer(errorPageHandler) @@ -249,7 +253,7 @@ func Test1xxResponses(t *testing.T) { return nil }, } - req, _ := http.NewRequestWithContext(httptrace.WithClientTrace(context.Background(), trace), http.MethodGet, server.URL, nil) + req, _ := http.NewRequestWithContext(httptrace.WithClientTrace(t.Context(), trace), http.MethodGet, server.URL, nil) res, err := frontendClient.Do(req) assert.NoError(t, err) diff --git a/pkg/middlewares/denyrouterrecursion/deny_router_recursion.go b/pkg/middlewares/denyrouterrecursion/deny_router_recursion.go index 34c6cd12d..a33f8c04b 100644 --- a/pkg/middlewares/denyrouterrecursion/deny_router_recursion.go +++ b/pkg/middlewares/denyrouterrecursion/deny_router_recursion.go @@ -8,7 +8,7 @@ import ( "github.com/containous/alice" "github.com/rs/zerolog/log" - "github.com/traefik/traefik/v3/pkg/logs" + "github.com/traefik/traefik/v3/pkg/observability/logs" ) const xTraefikRouter = "X-Traefik-Router" diff --git a/pkg/middlewares/gatewayapi/headermodifier/request_header_modifier.go b/pkg/middlewares/gatewayapi/headermodifier/request_header_modifier.go index 94d43211a..8808f3e7e 100644 --- a/pkg/middlewares/gatewayapi/headermodifier/request_header_modifier.go +++ b/pkg/middlewares/gatewayapi/headermodifier/request_header_modifier.go @@ -6,7 +6,6 @@ import ( "github.com/traefik/traefik/v3/pkg/config/dynamic" "github.com/traefik/traefik/v3/pkg/middlewares" - "go.opentelemetry.io/otel/trace" ) const requestHeaderModifierTypeName = "RequestHeaderModifier" @@ -35,8 +34,8 @@ func NewRequestHeaderModifier(ctx context.Context, next http.Handler, config dyn } } -func (r *requestHeaderModifier) GetTracingInformation() (string, string, trace.SpanKind) { - return r.name, requestHeaderModifierTypeName, trace.SpanKindUnspecified +func (r *requestHeaderModifier) GetTracingInformation() (string, string) { + return r.name, requestHeaderModifierTypeName } func (r *requestHeaderModifier) ServeHTTP(rw http.ResponseWriter, req *http.Request) { diff --git a/pkg/middlewares/gatewayapi/headermodifier/request_header_modifier_test.go b/pkg/middlewares/gatewayapi/headermodifier/request_header_modifier_test.go index a9c19980f..36ebbd9db 100644 --- a/pkg/middlewares/gatewayapi/headermodifier/request_header_modifier_test.go +++ b/pkg/middlewares/gatewayapi/headermodifier/request_header_modifier_test.go @@ -1,7 +1,6 @@ package headermodifier import ( - "context" "net/http" "net/http/httptest" "testing" @@ -103,7 +102,7 @@ func TestRequestHeaderModifier(t *testing.T) { gotHeaders = r.Header }) - handler := NewRequestHeaderModifier(context.Background(), next, test.config, "foo-request-header-modifier") + handler := NewRequestHeaderModifier(t.Context(), next, test.config, "foo-request-header-modifier") req := testhelpers.MustNewRequest(http.MethodGet, "http://localhost", nil) for h, v := range test.requestHeaders { diff --git a/pkg/middlewares/gatewayapi/headermodifier/response_header_modifier.go b/pkg/middlewares/gatewayapi/headermodifier/response_header_modifier.go index 2d55b686b..e47d23758 100644 --- a/pkg/middlewares/gatewayapi/headermodifier/response_header_modifier.go +++ b/pkg/middlewares/gatewayapi/headermodifier/response_header_modifier.go @@ -6,7 +6,6 @@ import ( "github.com/traefik/traefik/v3/pkg/config/dynamic" "github.com/traefik/traefik/v3/pkg/middlewares" - "go.opentelemetry.io/otel/trace" ) const responseHeaderModifierTypeName = "ResponseHeaderModifier" @@ -35,8 +34,8 @@ func NewResponseHeaderModifier(ctx context.Context, next http.Handler, config dy } } -func (r *responseHeaderModifier) GetTracingInformation() (string, string, trace.SpanKind) { - return r.name, responseHeaderModifierTypeName, trace.SpanKindUnspecified +func (r *responseHeaderModifier) GetTracingInformation() (string, string) { + return r.name, responseHeaderModifierTypeName } func (r *responseHeaderModifier) ServeHTTP(rw http.ResponseWriter, req *http.Request) { diff --git a/pkg/middlewares/gatewayapi/headermodifier/response_header_modifier_test.go b/pkg/middlewares/gatewayapi/headermodifier/response_header_modifier_test.go index ceea62ca6..47a0f3c82 100644 --- a/pkg/middlewares/gatewayapi/headermodifier/response_header_modifier_test.go +++ b/pkg/middlewares/gatewayapi/headermodifier/response_header_modifier_test.go @@ -1,7 +1,6 @@ package headermodifier import ( - "context" "net/http" "net/http/httptest" "testing" @@ -104,7 +103,7 @@ func TestResponseHeaderModifier(t *testing.T) { rw.WriteHeader(http.StatusOK) }) - handler := NewResponseHeaderModifier(context.Background(), next, test.config, "foo-response-header-modifier") + handler := NewResponseHeaderModifier(t.Context(), next, test.config, "foo-response-header-modifier") req := testhelpers.MustNewRequest(http.MethodGet, "http://localhost", nil) resp := httptest.NewRecorder() diff --git a/pkg/middlewares/gatewayapi/redirect/request_redirect.go b/pkg/middlewares/gatewayapi/redirect/request_redirect.go index e2d32e518..1305a2372 100644 --- a/pkg/middlewares/gatewayapi/redirect/request_redirect.go +++ b/pkg/middlewares/gatewayapi/redirect/request_redirect.go @@ -10,7 +10,6 @@ import ( "github.com/traefik/traefik/v3/pkg/config/dynamic" "github.com/traefik/traefik/v3/pkg/middlewares" - "go.opentelemetry.io/otel/trace" ) const typeName = "RequestRedirect" @@ -52,8 +51,8 @@ func NewRequestRedirect(ctx context.Context, next http.Handler, conf dynamic.Req }, nil } -func (r redirect) GetTracingInformation() (string, string, trace.SpanKind) { - return r.name, typeName, trace.SpanKindInternal +func (r redirect) GetTracingInformation() (string, string) { + return r.name, typeName } func (r redirect) ServeHTTP(rw http.ResponseWriter, req *http.Request) { diff --git a/pkg/middlewares/gatewayapi/redirect/request_redirect_test.go b/pkg/middlewares/gatewayapi/redirect/request_redirect_test.go index 68eb19bd3..530027adb 100644 --- a/pkg/middlewares/gatewayapi/redirect/request_redirect_test.go +++ b/pkg/middlewares/gatewayapi/redirect/request_redirect_test.go @@ -1,7 +1,6 @@ package redirect import ( - "context" "net/http" "net/http/httptest" "testing" @@ -185,7 +184,7 @@ func TestRequestRedirectHandler(t *testing.T) { next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}) - handler, err := NewRequestRedirect(context.Background(), next, test.config, "traefikTest") + handler, err := NewRequestRedirect(t.Context(), next, test.config, "traefikTest") if test.wantErr { require.Error(t, err) require.Nil(t, handler) diff --git a/pkg/middlewares/gatewayapi/urlrewrite/url_rewrite.go b/pkg/middlewares/gatewayapi/urlrewrite/url_rewrite.go index 2960a5583..bcddb8137 100644 --- a/pkg/middlewares/gatewayapi/urlrewrite/url_rewrite.go +++ b/pkg/middlewares/gatewayapi/urlrewrite/url_rewrite.go @@ -8,7 +8,6 @@ import ( "github.com/traefik/traefik/v3/pkg/config/dynamic" "github.com/traefik/traefik/v3/pkg/middlewares" - "go.opentelemetry.io/otel/trace" ) const ( @@ -38,8 +37,8 @@ func NewURLRewrite(ctx context.Context, next http.Handler, conf dynamic.URLRewri } } -func (u urlRewrite) GetTracingInformation() (string, string, trace.SpanKind) { - return u.name, typeName, trace.SpanKindInternal +func (u urlRewrite) GetTracingInformation() (string, string) { + return u.name, typeName } func (u urlRewrite) ServeHTTP(rw http.ResponseWriter, req *http.Request) { diff --git a/pkg/middlewares/gatewayapi/urlrewrite/url_rewrite_test.go b/pkg/middlewares/gatewayapi/urlrewrite/url_rewrite_test.go index 6b2b9edab..07b085c65 100644 --- a/pkg/middlewares/gatewayapi/urlrewrite/url_rewrite_test.go +++ b/pkg/middlewares/gatewayapi/urlrewrite/url_rewrite_test.go @@ -1,7 +1,6 @@ package urlrewrite import ( - "context" "net/http" "net/http/httptest" "testing" @@ -113,7 +112,7 @@ func TestURLRewriteHandler(t *testing.T) { next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}) - handler := NewURLRewrite(context.Background(), next, test.config, "traefikTest") + handler := NewURLRewrite(t.Context(), next, test.config, "traefikTest") recorder := httptest.NewRecorder() req := httptest.NewRequest(http.MethodGet, test.url, nil) diff --git a/pkg/middlewares/headers/headers.go b/pkg/middlewares/headers/headers.go index 861d1066d..cba8b6401 100644 --- a/pkg/middlewares/headers/headers.go +++ b/pkg/middlewares/headers/headers.go @@ -8,7 +8,6 @@ import ( "github.com/traefik/traefik/v3/pkg/config/dynamic" "github.com/traefik/traefik/v3/pkg/middlewares" - "go.opentelemetry.io/otel/trace" ) const ( @@ -58,8 +57,8 @@ func New(ctx context.Context, next http.Handler, cfg dynamic.Headers, name strin }, nil } -func (h *headers) GetTracingInformation() (string, string, trace.SpanKind) { - return h.name, typeName, trace.SpanKindInternal +func (h *headers) GetTracingInformation() (string, string) { + return h.name, typeName } func (h *headers) ServeHTTP(rw http.ResponseWriter, req *http.Request) { diff --git a/pkg/middlewares/headers/headers_test.go b/pkg/middlewares/headers/headers_test.go index 194975e36..df71e6896 100644 --- a/pkg/middlewares/headers/headers_test.go +++ b/pkg/middlewares/headers/headers_test.go @@ -3,7 +3,6 @@ package headers // Middleware tests based on https://github.com/unrolled/secure import ( - "context" "io" "net/http" "net/http/httptest" @@ -14,13 +13,12 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/traefik/traefik/v3/pkg/config/dynamic" - "go.opentelemetry.io/otel/trace" ) func TestNew_withoutOptions(t *testing.T) { next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) }) - mid, err := New(context.Background(), next, dynamic.Headers{}, "testing") + mid, err := New(t.Context(), next, dynamic.Headers{}, "testing") require.Errorf(t, err, "headers configuration not valid") assert.Nil(t, mid) @@ -55,7 +53,7 @@ func TestNew_allowedHosts(t *testing.T) { AllowedHosts: []string{"foo.com", "bar.com"}, } - mid, err := New(context.Background(), emptyHandler, cfg, "foo") + mid, err := New(t.Context(), emptyHandler, cfg, "foo") require.NoError(t, err) for _, test := range testCases { @@ -86,7 +84,7 @@ func TestNew_customHeaders(t *testing.T) { }, } - mid, err := New(context.Background(), next, cfg, "testing") + mid, err := New(t.Context(), next, cfg, "testing") require.NoError(t, err) req := httptest.NewRequest(http.MethodGet, "/foo", nil) @@ -108,11 +106,10 @@ func Test_headers_getTracingInformation(t *testing.T) { name: "testing", } - name, typeName, spanKind := mid.GetTracingInformation() + name, typeName := mid.GetTracingInformation() assert.Equal(t, "testing", name) assert.Equal(t, "Headers", typeName) - assert.Equal(t, trace.SpanKindInternal, spanKind) } // This test is an adapted version of net/http/httputil.Test1xxResponses test. @@ -135,7 +132,7 @@ func Test1xxResponses(t *testing.T) { }, } - mid, err := New(context.Background(), next, cfg, "testing") + mid, err := New(t.Context(), next, cfg, "testing") require.NoError(t, err) server := httptest.NewServer(mid) @@ -179,7 +176,7 @@ func Test1xxResponses(t *testing.T) { return nil }, } - req, _ := http.NewRequestWithContext(httptrace.WithClientTrace(context.Background(), trace), http.MethodGet, server.URL, nil) + req, _ := http.NewRequestWithContext(httptrace.WithClientTrace(t.Context(), trace), http.MethodGet, server.URL, nil) res, err := frontendClient.Do(req) assert.NoError(t, err) diff --git a/pkg/middlewares/inflightreq/inflight_req.go b/pkg/middlewares/inflightreq/inflight_req.go index c05193607..e2df0bcbb 100644 --- a/pkg/middlewares/inflightreq/inflight_req.go +++ b/pkg/middlewares/inflightreq/inflight_req.go @@ -7,10 +7,9 @@ import ( "github.com/rs/zerolog" "github.com/traefik/traefik/v3/pkg/config/dynamic" - "github.com/traefik/traefik/v3/pkg/logs" "github.com/traefik/traefik/v3/pkg/middlewares" + "github.com/traefik/traefik/v3/pkg/observability/logs" "github.com/vulcand/oxy/v2/connlimit" - "go.opentelemetry.io/otel/trace" ) const ( @@ -53,8 +52,8 @@ func New(ctx context.Context, next http.Handler, config dynamic.InFlightReq, nam return &inFlightReq{handler: handler, name: name}, nil } -func (i *inFlightReq) GetTracingInformation() (string, string, trace.SpanKind) { - return i.name, typeName, trace.SpanKindInternal +func (i *inFlightReq) GetTracingInformation() (string, string) { + return i.name, typeName } func (i *inFlightReq) ServeHTTP(rw http.ResponseWriter, req *http.Request) { diff --git a/pkg/middlewares/ipallowlist/ip_allowlist.go b/pkg/middlewares/ipallowlist/ip_allowlist.go index 46f17c74c..6d9cf0232 100644 --- a/pkg/middlewares/ipallowlist/ip_allowlist.go +++ b/pkg/middlewares/ipallowlist/ip_allowlist.go @@ -11,7 +11,6 @@ import ( "github.com/traefik/traefik/v3/pkg/ip" "github.com/traefik/traefik/v3/pkg/middlewares" "github.com/traefik/traefik/v3/pkg/middlewares/observability" - "go.opentelemetry.io/otel/trace" ) const ( @@ -65,8 +64,8 @@ func New(ctx context.Context, next http.Handler, config dynamic.IPAllowList, nam }, nil } -func (al *ipAllowLister) GetTracingInformation() (string, string, trace.SpanKind) { - return al.name, typeName, trace.SpanKindInternal +func (al *ipAllowLister) GetTracingInformation() (string, string) { + return al.name, typeName } func (al *ipAllowLister) ServeHTTP(rw http.ResponseWriter, req *http.Request) { diff --git a/pkg/middlewares/ipallowlist/ip_allowlist_test.go b/pkg/middlewares/ipallowlist/ip_allowlist_test.go index 3dddcc353..fac72dd1c 100644 --- a/pkg/middlewares/ipallowlist/ip_allowlist_test.go +++ b/pkg/middlewares/ipallowlist/ip_allowlist_test.go @@ -1,7 +1,6 @@ package ipallowlist import ( - "context" "net/http" "net/http/httptest" "testing" @@ -45,7 +44,7 @@ func TestNewIPAllowLister(t *testing.T) { t.Parallel() next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}) - allowLister, err := New(context.Background(), next, test.allowList, "traefikTest") + allowLister, err := New(t.Context(), next, test.allowList, "traefikTest") if test.expectedError { assert.Error(t, err) @@ -105,7 +104,7 @@ func TestIPAllowLister_ServeHTTP(t *testing.T) { t.Parallel() next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}) - allowLister, err := New(context.Background(), next, test.allowList, "traefikTest") + allowLister, err := New(t.Context(), next, test.allowList, "traefikTest") require.NoError(t, err) recorder := httptest.NewRecorder() diff --git a/pkg/middlewares/ipwhitelist/ip_whitelist.go b/pkg/middlewares/ipwhitelist/ip_whitelist.go index d8e3dadb3..7458b4de6 100644 --- a/pkg/middlewares/ipwhitelist/ip_whitelist.go +++ b/pkg/middlewares/ipwhitelist/ip_whitelist.go @@ -11,7 +11,6 @@ import ( "github.com/traefik/traefik/v3/pkg/ip" "github.com/traefik/traefik/v3/pkg/middlewares" "github.com/traefik/traefik/v3/pkg/middlewares/observability" - "go.opentelemetry.io/otel/trace" ) const ( @@ -55,8 +54,8 @@ func New(ctx context.Context, next http.Handler, config dynamic.IPWhiteList, nam }, nil } -func (wl *ipWhiteLister) GetTracingInformation() (string, string, trace.SpanKind) { - return wl.name, typeName, trace.SpanKindInternal +func (wl *ipWhiteLister) GetTracingInformation() (string, string) { + return wl.name, typeName } func (wl *ipWhiteLister) ServeHTTP(rw http.ResponseWriter, req *http.Request) { diff --git a/pkg/middlewares/ipwhitelist/ip_whitelist_test.go b/pkg/middlewares/ipwhitelist/ip_whitelist_test.go index 9cf88ef32..ce2158fb9 100644 --- a/pkg/middlewares/ipwhitelist/ip_whitelist_test.go +++ b/pkg/middlewares/ipwhitelist/ip_whitelist_test.go @@ -1,7 +1,6 @@ package ipwhitelist import ( - "context" "net/http" "net/http/httptest" "testing" @@ -37,7 +36,7 @@ func TestNewIPWhiteLister(t *testing.T) { t.Parallel() next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}) - whiteLister, err := New(context.Background(), next, test.whiteList, "traefikTest") + whiteLister, err := New(t.Context(), next, test.whiteList, "traefikTest") if test.expectedError { assert.Error(t, err) @@ -79,7 +78,7 @@ func TestIPWhiteLister_ServeHTTP(t *testing.T) { t.Parallel() next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}) - whiteLister, err := New(context.Background(), next, test.whiteList, "traefikTest") + whiteLister, err := New(t.Context(), next, test.whiteList, "traefikTest") require.NoError(t, err) recorder := httptest.NewRecorder() diff --git a/pkg/middlewares/metrics/metrics.go b/pkg/middlewares/metrics/metrics.go index e8a1c6dba..646220a13 100644 --- a/pkg/middlewares/metrics/metrics.go +++ b/pkg/middlewares/metrics/metrics.go @@ -12,13 +12,12 @@ import ( "github.com/containous/alice" gokitmetrics "github.com/go-kit/kit/metrics" "github.com/rs/zerolog/log" - "github.com/traefik/traefik/v3/pkg/metrics" "github.com/traefik/traefik/v3/pkg/middlewares" "github.com/traefik/traefik/v3/pkg/middlewares/capture" "github.com/traefik/traefik/v3/pkg/middlewares/observability" "github.com/traefik/traefik/v3/pkg/middlewares/retry" + "github.com/traefik/traefik/v3/pkg/observability/metrics" traefiktls "github.com/traefik/traefik/v3/pkg/tls" - "go.opentelemetry.io/otel/trace" "google.golang.org/grpc/codes" ) @@ -93,33 +92,45 @@ func NewServiceMiddleware(ctx context.Context, next http.Handler, registry metri } } -// WrapEntryPointHandler Wraps metrics entrypoint to alice.Constructor. -func WrapEntryPointHandler(ctx context.Context, registry metrics.Registry, entryPointName string) alice.Constructor { +// EntryPointMetricsHandler returns the metrics entrypoint handler. +func EntryPointMetricsHandler(ctx context.Context, registry metrics.Registry, entryPointName string) alice.Constructor { return func(next http.Handler) (http.Handler, error) { + if registry == nil || !registry.IsEpEnabled() { + return next, nil + } + return NewEntryPointMiddleware(ctx, next, registry, entryPointName), nil } } -// WrapRouterHandler Wraps metrics router to alice.Constructor. -func WrapRouterHandler(ctx context.Context, registry metrics.Registry, routerName string, serviceName string) alice.Constructor { +// RouterMetricsHandler returns the metrics router handler. +func RouterMetricsHandler(ctx context.Context, registry metrics.Registry, routerName string, serviceName string) alice.Constructor { return func(next http.Handler) (http.Handler, error) { + if registry == nil || !registry.IsRouterEnabled() { + return next, nil + } + return NewRouterMiddleware(ctx, next, registry, routerName, serviceName), nil } } -// WrapServiceHandler Wraps metrics service to alice.Constructor. -func WrapServiceHandler(ctx context.Context, registry metrics.Registry, serviceName string) alice.Constructor { +// ServiceMetricsHandler returns the metrics service handler. +func ServiceMetricsHandler(ctx context.Context, registry metrics.Registry, serviceName string) alice.Constructor { return func(next http.Handler) (http.Handler, error) { + if registry == nil || !registry.IsSvcEnabled() { + return next, nil + } + return NewServiceMiddleware(ctx, next, registry, serviceName), nil } } -func (m *metricsMiddleware) GetTracingInformation() (string, string, trace.SpanKind) { - return m.name, typeName, trace.SpanKindInternal +func (m *metricsMiddleware) GetTracingInformation() (string, string) { + return m.name, typeName } func (m *metricsMiddleware) ServeHTTP(rw http.ResponseWriter, req *http.Request) { - if val := req.Context().Value(observability.DisableMetricsKey); val != nil { + if !observability.MetricsEnabled(req.Context()) { m.next.ServeHTTP(rw, req) return } diff --git a/pkg/middlewares/middleware.go b/pkg/middlewares/middleware.go index 928a9982e..5b7cd6d70 100644 --- a/pkg/middlewares/middleware.go +++ b/pkg/middlewares/middleware.go @@ -5,7 +5,7 @@ import ( "github.com/rs/zerolog" "github.com/rs/zerolog/log" - "github.com/traefik/traefik/v3/pkg/logs" + "github.com/traefik/traefik/v3/pkg/observability/logs" ) // GetLogger creates a logger with the middleware fields. diff --git a/pkg/middlewares/observability/entrypoint.go b/pkg/middlewares/observability/entrypoint.go index 64b89b921..e9cd3f10f 100644 --- a/pkg/middlewares/observability/entrypoint.go +++ b/pkg/middlewares/observability/entrypoint.go @@ -8,7 +8,7 @@ import ( "github.com/containous/alice" "github.com/rs/zerolog/log" "github.com/traefik/traefik/v3/pkg/middlewares" - "github.com/traefik/traefik/v3/pkg/tracing" + "github.com/traefik/traefik/v3/pkg/observability/tracing" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/trace" "go.opentelemetry.io/otel/trace/noop" @@ -48,11 +48,20 @@ func newEntryPoint(ctx context.Context, tracer *tracing.Tracer, entryPointName s } func (e *entryPointTracing) ServeHTTP(rw http.ResponseWriter, req *http.Request) { + if e.tracer == nil || !TracingEnabled(req.Context()) { + e.next.ServeHTTP(rw, req) + return + } + tracingCtx := tracing.ExtractCarrierIntoContext(req.Context(), req.Header) start := time.Now() - tracingCtx, span := e.tracer.Start(tracingCtx, "EntryPoint", trace.WithSpanKind(trace.SpanKindServer), trace.WithTimestamp(start)) + + // Follow semantic conventions defined by OTEL: https://opentelemetry.io/docs/specs/semconv/http/http-spans/#name + // At the moment this implementation only gets the {method} as there is no guarantee {target} is low cardinality. + tracingCtx, span := e.tracer.Start(tracingCtx, req.Method, trace.WithSpanKind(trace.SpanKindServer), trace.WithTimestamp(start)) // Associate the request context with the logger. + // This allows the logger to be aware of the tracing context and log accordingly (TraceID, SpanID, etc.). logger := log.Ctx(tracingCtx).With().Ctx(tracingCtx).Logger() loggerCtx := logger.WithContext(tracingCtx) diff --git a/pkg/middlewares/observability/entrypoint_test.go b/pkg/middlewares/observability/entrypoint_test.go index d39d3b842..2e02e15ce 100644 --- a/pkg/middlewares/observability/entrypoint_test.go +++ b/pkg/middlewares/observability/entrypoint_test.go @@ -1,13 +1,13 @@ package observability import ( - "context" "net/http" "net/http/httptest" "testing" "github.com/stretchr/testify/assert" - "github.com/traefik/traefik/v3/pkg/tracing" + "github.com/stretchr/testify/require" + "github.com/traefik/traefik/v3/pkg/observability/tracing" "go.opentelemetry.io/otel/attribute" ) @@ -20,13 +20,15 @@ func TestEntryPointMiddleware_tracing(t *testing.T) { testCases := []struct { desc string entryPoint string + method string expected expected }{ { - desc: "basic test", + desc: "GET", entryPoint: "test", + method: http.MethodGet, expected: expected{ - name: "EntryPoint", + name: "GET", attributes: []attribute.KeyValue{ attribute.String("span.kind", "server"), attribute.String("entry_point", "test"), @@ -48,11 +50,38 @@ func TestEntryPointMiddleware_tracing(t *testing.T) { }, }, }, + { + desc: "POST", + entryPoint: "test", + method: http.MethodPost, + expected: expected{ + name: "POST", + attributes: []attribute.KeyValue{ + attribute.String("span.kind", "server"), + attribute.String("entry_point", "test"), + attribute.String("http.request.method", "POST"), + attribute.String("network.protocol.version", "1.1"), + attribute.Int64("http.request.body.size", int64(0)), + attribute.String("url.path", "/search"), + attribute.String("url.query", "q=Opentelemetry&token=REDACTED"), + attribute.String("url.scheme", "http"), + attribute.String("user_agent.original", "entrypoint-test"), + attribute.String("server.address", "www.test.com"), + attribute.String("network.peer.address", "10.0.0.1"), + attribute.String("client.address", "10.0.0.1"), + attribute.Int64("client.port", int64(1234)), + attribute.Int64("network.peer.port", int64(1234)), + attribute.StringSlice("http.request.header.x-foo", []string{"foo", "bar"}), + attribute.Int64("http.response.status_code", int64(404)), + attribute.StringSlice("http.response.header.x-bar", []string{"foo", "bar"}), + }, + }, + }, } for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { - req := httptest.NewRequest(http.MethodGet, "http://www.test.com/search?q=Opentelemetry&token=123", nil) + req := httptest.NewRequest(test.method, "http://www.test.com/search?q=Opentelemetry&token=123", nil) rw := httptest.NewRecorder() req.RemoteAddr = "10.0.0.1:1234" req.Header.Set("User-Agent", "entrypoint-test") @@ -68,13 +97,17 @@ func TestEntryPointMiddleware_tracing(t *testing.T) { tracer := &mockTracer{} - handler := newEntryPoint(context.Background(), tracing.NewTracer(tracer, []string{"X-Foo"}, []string{"X-Bar"}, []string{"q"}), test.entryPoint, next) + // Injection of the observability variables in the request context. + req = req.WithContext(WithObservability(req.Context(), Observability{ + TracingEnabled: true, + })) + + handler := newEntryPoint(t.Context(), tracing.NewTracer(tracer, []string{"X-Foo"}, []string{"X-Bar"}, []string{"q"}), test.entryPoint, next) handler.ServeHTTP(rw, req) - for _, span := range tracer.spans { - assert.Equal(t, test.expected.name, span.name) - assert.Equal(t, test.expected.attributes, span.attributes) - } + require.Len(t, tracer.spans, 1) + assert.Equal(t, test.expected.name, tracer.spans[0].name) + assert.Equal(t, test.expected.attributes, tracer.spans[0].attributes) }) } } diff --git a/pkg/middlewares/observability/middleware.go b/pkg/middlewares/observability/middleware.go index 51e4b6d50..2d677ff82 100644 --- a/pkg/middlewares/observability/middleware.go +++ b/pkg/middlewares/observability/middleware.go @@ -6,15 +6,15 @@ import ( "github.com/containous/alice" "github.com/rs/zerolog/log" - "github.com/traefik/traefik/v3/pkg/logs" - "github.com/traefik/traefik/v3/pkg/tracing" + "github.com/traefik/traefik/v3/pkg/observability/logs" + "github.com/traefik/traefik/v3/pkg/observability/tracing" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/trace" ) // Traceable embeds tracing information. type Traceable interface { - GetTracingInformation() (name string, typeName string, spanKind trace.SpanKind) + GetTracingInformation() (name string, typeName string) } // WrapMiddleware adds traceability to an alice.Constructor. @@ -29,21 +29,20 @@ func WrapMiddleware(ctx context.Context, constructor alice.Constructor) alice.Co } if traceableHandler, ok := handler.(Traceable); ok { - name, typeName, spanKind := traceableHandler.GetTracingInformation() + name, typeName := traceableHandler.GetTracingInformation() log.Ctx(ctx).Debug().Str(logs.MiddlewareName, name).Msg("Adding tracing to middleware") - return NewMiddleware(handler, name, typeName, spanKind), nil + return NewMiddleware(handler, name, typeName), nil } return handler, nil } } // NewMiddleware returns a http.Handler struct. -func NewMiddleware(next http.Handler, name string, typeName string, spanKind trace.SpanKind) http.Handler { +func NewMiddleware(next http.Handler, name string, typeName string) http.Handler { return &middlewareTracing{ next: next, name: name, typeName: typeName, - spanKind: spanKind, } } @@ -52,12 +51,11 @@ type middlewareTracing struct { next http.Handler name string typeName string - spanKind trace.SpanKind } func (w *middlewareTracing) ServeHTTP(rw http.ResponseWriter, req *http.Request) { - if tracer := tracing.TracerFromContext(req.Context()); tracer != nil { - tracingCtx, span := tracer.Start(req.Context(), w.typeName, trace.WithSpanKind(w.spanKind)) + if tracer := tracing.TracerFromContext(req.Context()); tracer != nil && DetailedTracingEnabled(req.Context()) { + tracingCtx, span := tracer.Start(req.Context(), w.typeName, trace.WithSpanKind(trace.SpanKindInternal)) defer span.End() req = req.WithContext(tracingCtx) diff --git a/pkg/middlewares/observability/mock_tracing_test.go b/pkg/middlewares/observability/mock_tracing_test.go index 4a70b77bc..3a2ffd520 100644 --- a/pkg/middlewares/observability/mock_tracing_test.go +++ b/pkg/middlewares/observability/mock_tracing_test.go @@ -37,7 +37,7 @@ func (t *mockTracer) Start(ctx context.Context, name string, opts ...trace.SpanS return trace.ContextWithSpan(ctx, span), span } -// mockSpan is an implementation of Span that preforms no operations. +// mockSpan is an implementation of Span that performs no operations. type mockSpan struct { embedded.Span diff --git a/pkg/middlewares/observability/observability.go b/pkg/middlewares/observability/observability.go index 1ee9f3b99..1490a1e8e 100644 --- a/pkg/middlewares/observability/observability.go +++ b/pkg/middlewares/observability/observability.go @@ -3,6 +3,7 @@ package observability import ( "context" "fmt" + "net/http" "go.opentelemetry.io/otel/codes" "go.opentelemetry.io/otel/trace" @@ -10,8 +11,58 @@ import ( type contextKey int -// DisableMetricsKey is a context key used to disable the metrics. -const DisableMetricsKey contextKey = iota +const observabilityKey contextKey = iota + +type Observability struct { + AccessLogsEnabled bool + MetricsEnabled bool + SemConvMetricsEnabled bool + TracingEnabled bool + DetailedTracingEnabled bool +} + +// WithObservabilityHandler sets the observability state in the context for the next handler. +// This is also used for testing purposes to control whether access logs are enabled or not. +func WithObservabilityHandler(next http.Handler, obs Observability) http.Handler { + return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + next.ServeHTTP(rw, req.WithContext(WithObservability(req.Context(), obs))) + }) +} + +// WithObservability injects the observability state into the context. +func WithObservability(ctx context.Context, obs Observability) context.Context { + return context.WithValue(ctx, observabilityKey, obs) +} + +// AccessLogsEnabled returns whether access-logs are enabled. +func AccessLogsEnabled(ctx context.Context) bool { + obs, ok := ctx.Value(observabilityKey).(Observability) + return ok && obs.AccessLogsEnabled +} + +// MetricsEnabled returns whether metrics are enabled. +func MetricsEnabled(ctx context.Context) bool { + obs, ok := ctx.Value(observabilityKey).(Observability) + return ok && obs.MetricsEnabled +} + +// SemConvMetricsEnabled returns whether metrics are enabled. +func SemConvMetricsEnabled(ctx context.Context) bool { + obs, ok := ctx.Value(observabilityKey).(Observability) + return ok && obs.SemConvMetricsEnabled +} + +// TracingEnabled returns whether tracing is enabled. +func TracingEnabled(ctx context.Context) bool { + obs, ok := ctx.Value(observabilityKey).(Observability) + return ok && obs.TracingEnabled +} + +// DetailedTracingEnabled returns whether detailed tracing is enabled. +func DetailedTracingEnabled(ctx context.Context) bool { + obs, ok := ctx.Value(observabilityKey).(Observability) + return ok && obs.DetailedTracingEnabled +} // SetStatusErrorf flags the span as in error and log an event. func SetStatusErrorf(ctx context.Context, format string, args ...interface{}) { diff --git a/pkg/middlewares/observability/router.go b/pkg/middlewares/observability/router.go index 5339726ff..e840a1aa4 100644 --- a/pkg/middlewares/observability/router.go +++ b/pkg/middlewares/observability/router.go @@ -5,11 +5,11 @@ import ( "net/http" "github.com/containous/alice" - "github.com/traefik/traefik/v3/pkg/logs" "github.com/traefik/traefik/v3/pkg/middlewares" - "github.com/traefik/traefik/v3/pkg/tracing" + "github.com/traefik/traefik/v3/pkg/observability/logs" + "github.com/traefik/traefik/v3/pkg/observability/tracing" "go.opentelemetry.io/otel/attribute" - semconv "go.opentelemetry.io/otel/semconv/v1.26.0" + semconv "go.opentelemetry.io/otel/semconv/v1.37.0" "go.opentelemetry.io/otel/trace" ) @@ -45,7 +45,7 @@ func newRouter(ctx context.Context, router, routerRule, service string, next htt } func (f *routerTracing) ServeHTTP(rw http.ResponseWriter, req *http.Request) { - if tracer := tracing.TracerFromContext(req.Context()); tracer != nil { + if tracer := tracing.TracerFromContext(req.Context()); tracer != nil && DetailedTracingEnabled(req.Context()) { tracingCtx, span := tracer.Start(req.Context(), "Router", trace.WithSpanKind(trace.SpanKindInternal)) defer span.End() diff --git a/pkg/middlewares/observability/router_test.go b/pkg/middlewares/observability/router_test.go index 23bb46ab9..b519e6172 100644 --- a/pkg/middlewares/observability/router_test.go +++ b/pkg/middlewares/observability/router_test.go @@ -1,7 +1,6 @@ package observability import ( - "context" "net/http" "net/http/httptest" "testing" @@ -31,7 +30,7 @@ func TestNewRouter(t *testing.T) { routerRule: "Path(`/`)", expected: []expected{ { - name: "EntryPoint", + name: "GET", attributes: []attribute.KeyValue{ attribute.String("span.kind", "server"), }, @@ -64,7 +63,7 @@ func TestNewRouter(t *testing.T) { req.Header.Set("User-Agent", "router-test") tracer := &mockTracer{} - tracingCtx, entryPointSpan := tracer.Start(req.Context(), "EntryPoint", trace.WithSpanKind(trace.SpanKindServer)) + tracingCtx, entryPointSpan := tracer.Start(req.Context(), http.MethodGet, trace.WithSpanKind(trace.SpanKindServer)) defer entryPointSpan.End() req = req.WithContext(tracingCtx) @@ -74,7 +73,7 @@ func TestNewRouter(t *testing.T) { rw.WriteHeader(http.StatusNotFound) }) - handler := newRouter(context.Background(), test.router, test.routerRule, test.service, next) + handler := newRouter(t.Context(), test.router, test.routerRule, test.service, next) handler.ServeHTTP(rw, req) for i, span := range tracer.spans { diff --git a/pkg/middlewares/observability/semconv.go b/pkg/middlewares/observability/semconv.go index 51f4480b5..41aa52696 100644 --- a/pkg/middlewares/observability/semconv.go +++ b/pkg/middlewares/observability/semconv.go @@ -10,13 +10,13 @@ import ( "github.com/containous/alice" "github.com/rs/zerolog/log" - "github.com/traefik/traefik/v3/pkg/logs" - "github.com/traefik/traefik/v3/pkg/metrics" "github.com/traefik/traefik/v3/pkg/middlewares" "github.com/traefik/traefik/v3/pkg/middlewares/capture" + "github.com/traefik/traefik/v3/pkg/observability/logs" + "github.com/traefik/traefik/v3/pkg/observability/metrics" "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/metric" - semconv "go.opentelemetry.io/otel/semconv/v1.26.0" + semconv "go.opentelemetry.io/otel/semconv/v1.37.0" + "go.opentelemetry.io/otel/semconv/v1.37.0/httpconv" ) const ( @@ -46,7 +46,7 @@ func newServerMetricsSemConv(ctx context.Context, semConvMetricRegistry *metrics } func (e *semConvServerMetrics) ServeHTTP(rw http.ResponseWriter, req *http.Request) { - if e.semConvMetricRegistry == nil || e.semConvMetricRegistry.HTTPServerRequestDuration() == nil { + if e.semConvMetricRegistry == nil || !SemConvMetricsEnabled(req.Context()) { e.next.ServeHTTP(rw, req) return } @@ -70,12 +70,12 @@ func (e *semConvServerMetrics) ServeHTTP(rw http.ResponseWriter, req *http.Reque attrs = append(attrs, attribute.Key("error.type").String(strconv.Itoa(capt.StatusCode()))) } - attrs = append(attrs, semconv.HTTPRequestMethodKey.String(req.Method)) + // Additional optional attributes. attrs = append(attrs, semconv.HTTPResponseStatusCode(capt.StatusCode())) attrs = append(attrs, semconv.NetworkProtocolName(strings.ToLower(req.Proto))) attrs = append(attrs, semconv.NetworkProtocolVersion(Proto(req.Proto))) attrs = append(attrs, semconv.ServerAddress(req.Host)) - attrs = append(attrs, semconv.URLScheme(req.Header.Get("X-Forwarded-Proto"))) - e.semConvMetricRegistry.HTTPServerRequestDuration().Record(req.Context(), end.Sub(start).Seconds(), metric.WithAttributes(attrs...)) + e.semConvMetricRegistry.HTTPServerRequestDuration().Record(req.Context(), end.Sub(start).Seconds(), + httpconv.RequestMethodAttr(req.Method), req.Header.Get("X-Forwarded-Proto"), attrs...) } diff --git a/pkg/middlewares/observability/semconv_test.go b/pkg/middlewares/observability/semconv_test.go index 08846c4d7..960d4b98a 100644 --- a/pkg/middlewares/observability/semconv_test.go +++ b/pkg/middlewares/observability/semconv_test.go @@ -1,7 +1,6 @@ package observability import ( - "context" "net/http" "net/http/httptest" "testing" @@ -9,9 +8,9 @@ import ( "github.com/stretchr/testify/require" ptypes "github.com/traefik/paerser/types" - "github.com/traefik/traefik/v3/pkg/metrics" "github.com/traefik/traefik/v3/pkg/middlewares/capture" - "github.com/traefik/traefik/v3/pkg/types" + "github.com/traefik/traefik/v3/pkg/observability/metrics" + otypes "github.com/traefik/traefik/v3/pkg/observability/types" "go.opentelemetry.io/otel/attribute" sdkmetric "go.opentelemetry.io/otel/sdk/metric" "go.opentelemetry.io/otel/sdk/metric/metricdata" @@ -55,7 +54,7 @@ func TestSemConvServerMetrics(t *testing.T) { t.Run(test.desc, func(t *testing.T) { t.Parallel() - var cfg types.OTLP + var cfg otypes.OTLP (&cfg).SetDefaults() cfg.AddRoutersLabels = true cfg.PushInterval = ptypes.Duration(10 * time.Millisecond) @@ -65,7 +64,7 @@ func TestSemConvServerMetrics(t *testing.T) { // force the meter provider with manual reader to collect metrics for the test. metrics.SetMeterProvider(meterProvider) - semConvMetricRegistry, err := metrics.NewSemConvMetricRegistry(context.Background(), &cfg) + semConvMetricRegistry, err := metrics.NewSemConvMetricRegistry(t.Context(), &cfg) require.NoError(t, err) require.NotNil(t, semConvMetricRegistry) @@ -79,15 +78,20 @@ func TestSemConvServerMetrics(t *testing.T) { rw.WriteHeader(test.statusCode) }) - handler := newServerMetricsSemConv(context.Background(), semConvMetricRegistry, next) + handler := newServerMetricsSemConv(t.Context(), semConvMetricRegistry, next) handler, err = capture.Wrap(handler) require.NoError(t, err) + // Injection of the observability variables in the request context. + handler = WithObservabilityHandler(handler, Observability{ + SemConvMetricsEnabled: true, + }) + handler.ServeHTTP(rw, req) got := metricdata.ResourceMetrics{} - err = rdr.Collect(context.Background(), &got) + err = rdr.Collect(t.Context(), &got) require.NoError(t, err) require.Len(t, got.ScopeMetrics, 1) diff --git a/pkg/middlewares/observability/service.go b/pkg/middlewares/observability/service.go index cacd3ef1b..ef580f9bf 100644 --- a/pkg/middlewares/observability/service.go +++ b/pkg/middlewares/observability/service.go @@ -4,9 +4,9 @@ import ( "context" "net/http" - "github.com/traefik/traefik/v3/pkg/logs" "github.com/traefik/traefik/v3/pkg/middlewares" - "github.com/traefik/traefik/v3/pkg/tracing" + "github.com/traefik/traefik/v3/pkg/observability/logs" + "github.com/traefik/traefik/v3/pkg/observability/tracing" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/trace" ) @@ -32,7 +32,7 @@ func NewService(ctx context.Context, service string, next http.Handler) http.Han } func (t *serviceTracing) ServeHTTP(rw http.ResponseWriter, req *http.Request) { - if tracer := tracing.TracerFromContext(req.Context()); tracer != nil { + if tracer := tracing.TracerFromContext(req.Context()); tracer != nil && DetailedTracingEnabled(req.Context()) { tracingCtx, span := tracer.Start(req.Context(), "Service", trace.WithSpanKind(trace.SpanKindInternal)) defer span.End() diff --git a/pkg/middlewares/observability/service_test.go b/pkg/middlewares/observability/service_test.go index db411e718..99197ed9b 100644 --- a/pkg/middlewares/observability/service_test.go +++ b/pkg/middlewares/observability/service_test.go @@ -1,7 +1,6 @@ package observability import ( - "context" "net/http" "net/http/httptest" "testing" @@ -27,7 +26,7 @@ func TestNewService(t *testing.T) { service: "myService", expected: []expected{ { - name: "EntryPoint", + name: "GET", attributes: []attribute.KeyValue{ attribute.String("span.kind", "server"), }, @@ -58,7 +57,7 @@ func TestNewService(t *testing.T) { req.Header.Set("User-Agent", "service-test") tracer := &mockTracer{} - tracingCtx, entryPointSpan := tracer.Start(req.Context(), "EntryPoint", trace.WithSpanKind(trace.SpanKindServer)) + tracingCtx, entryPointSpan := tracer.Start(req.Context(), http.MethodGet, trace.WithSpanKind(trace.SpanKindServer)) defer entryPointSpan.End() req = req.WithContext(tracingCtx) @@ -68,7 +67,7 @@ func TestNewService(t *testing.T) { rw.WriteHeader(http.StatusNotFound) }) - handler := NewService(context.Background(), test.service, next) + handler := NewService(t.Context(), test.service, next) handler.ServeHTTP(rw, req) for i, span := range tracer.spans { diff --git a/pkg/middlewares/passtlsclientcert/pass_tls_client_cert.go b/pkg/middlewares/passtlsclientcert/pass_tls_client_cert.go index cadc375af..6f892a779 100644 --- a/pkg/middlewares/passtlsclientcert/pass_tls_client_cert.go +++ b/pkg/middlewares/passtlsclientcert/pass_tls_client_cert.go @@ -14,7 +14,6 @@ import ( "github.com/rs/zerolog/log" "github.com/traefik/traefik/v3/pkg/config/dynamic" "github.com/traefik/traefik/v3/pkg/middlewares" - "go.opentelemetry.io/otel/trace" ) const typeName = "PassClientTLSCert" @@ -139,8 +138,8 @@ func New(ctx context.Context, next http.Handler, config dynamic.PassTLSClientCer }, nil } -func (p *passTLSClientCert) GetTracingInformation() (string, string, trace.SpanKind) { - return p.name, typeName, trace.SpanKindInternal +func (p *passTLSClientCert) GetTracingInformation() (string, string) { + return p.name, typeName } func (p *passTLSClientCert) ServeHTTP(rw http.ResponseWriter, req *http.Request) { diff --git a/pkg/middlewares/passtlsclientcert/pass_tls_client_cert_test.go b/pkg/middlewares/passtlsclientcert/pass_tls_client_cert_test.go index 20b4f087c..7165e9dd3 100644 --- a/pkg/middlewares/passtlsclientcert/pass_tls_client_cert_test.go +++ b/pkg/middlewares/passtlsclientcert/pass_tls_client_cert_test.go @@ -1,7 +1,6 @@ package passtlsclientcert import ( - "context" "crypto/tls" "crypto/x509" "encoding/pem" @@ -313,7 +312,7 @@ func TestPassTLSClientCert_PEM(t *testing.T) { t.Run(test.desc, func(t *testing.T) { t.Parallel() - tlsClientHeaders, err := New(context.Background(), next, test.config, "foo") + tlsClientHeaders, err := New(t.Context(), next, test.config, "foo") require.NoError(t, err) res := httptest.NewRecorder() @@ -535,7 +534,7 @@ func TestPassTLSClientCert_certInfo(t *testing.T) { t.Run(test.desc, func(t *testing.T) { t.Parallel() - tlsClientHeaders, err := New(context.Background(), next, test.config, "foo") + tlsClientHeaders, err := New(t.Context(), next, test.config, "foo") require.NoError(t, err) res := httptest.NewRecorder() diff --git a/pkg/middlewares/ratelimiter/rate_limiter.go b/pkg/middlewares/ratelimiter/rate_limiter.go index 043974d47..fcc553e15 100755 --- a/pkg/middlewares/ratelimiter/rate_limiter.go +++ b/pkg/middlewares/ratelimiter/rate_limiter.go @@ -14,7 +14,6 @@ import ( "github.com/traefik/traefik/v3/pkg/middlewares" "github.com/traefik/traefik/v3/pkg/middlewares/observability" "github.com/vulcand/oxy/v2/utils" - "go.opentelemetry.io/otel/trace" "golang.org/x/time/rate" ) @@ -127,8 +126,8 @@ func New(ctx context.Context, next http.Handler, config dynamic.RateLimit, name }, nil } -func (rl *rateLimiter) GetTracingInformation() (string, string, trace.SpanKind) { - return rl.name, typeName, trace.SpanKindInternal +func (rl *rateLimiter) GetTracingInformation() (string, string) { + return rl.name, typeName } func (rl *rateLimiter) ServeHTTP(rw http.ResponseWriter, req *http.Request) { diff --git a/pkg/middlewares/ratelimiter/rate_limiter_test.go b/pkg/middlewares/ratelimiter/rate_limiter_test.go index a741be4e4..a724c6fe6 100644 --- a/pkg/middlewares/ratelimiter/rate_limiter_test.go +++ b/pkg/middlewares/ratelimiter/rate_limiter_test.go @@ -110,7 +110,7 @@ func TestNewRateLimiter(t *testing.T) { next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}) - h, err := New(context.Background(), next, test.config, "rate-limiter") + h, err := New(t.Context(), next, test.config, "rate-limiter") if test.expectedError != "" { assert.EqualError(t, err, test.expectedError) } else { @@ -274,7 +274,7 @@ func TestInMemoryRateLimit(t *testing.T) { next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { reqCount++ }) - h, err := New(context.Background(), next, test.config, "rate-limiter") + h, err := New(t.Context(), next, test.config, "rate-limiter") require.NoError(t, err) loadPeriod := time.Duration(1e9 / test.incomingLoad) @@ -477,7 +477,7 @@ func TestRedisRateLimit(t *testing.T) { test.config.Redis = &dynamic.Redis{ Endpoints: []string{"localhost:6379"}, } - h, err := New(context.Background(), next, test.config, "rate-limiter") + h, err := New(t.Context(), next, test.config, "rate-limiter") require.NoError(t, err) l := h.(*rateLimiter) diff --git a/pkg/middlewares/recovery/recovery_test.go b/pkg/middlewares/recovery/recovery_test.go index 1929f0b54..d93cb77eb 100644 --- a/pkg/middlewares/recovery/recovery_test.go +++ b/pkg/middlewares/recovery/recovery_test.go @@ -1,7 +1,6 @@ package recovery import ( - "context" "errors" "io" "net/http" @@ -47,7 +46,7 @@ func TestRecoverHandler(t *testing.T) { } panic(test.panicErr) } - recovery, err := New(context.Background(), http.HandlerFunc(fn)) + recovery, err := New(t.Context(), http.HandlerFunc(fn)) require.NoError(t, err) server := httptest.NewServer(recovery) diff --git a/pkg/middlewares/redirect/redirect.go b/pkg/middlewares/redirect/redirect.go index 25ca6a2ac..d8499d1aa 100644 --- a/pkg/middlewares/redirect/redirect.go +++ b/pkg/middlewares/redirect/redirect.go @@ -6,7 +6,6 @@ import ( "regexp" "github.com/vulcand/oxy/v2/utils" - "go.opentelemetry.io/otel/trace" ) const ( @@ -46,8 +45,8 @@ func newRedirect(next http.Handler, regex, replacement string, permanent bool, r }, nil } -func (r *redirect) GetTracingInformation() (string, string, trace.SpanKind) { - return r.name, typeName, trace.SpanKindInternal +func (r *redirect) GetTracingInformation() (string, string) { + return r.name, typeName } func (r *redirect) ServeHTTP(rw http.ResponseWriter, req *http.Request) { diff --git a/pkg/middlewares/redirect/redirect_regex_test.go b/pkg/middlewares/redirect/redirect_regex_test.go index 4239c1ae0..78d4fabd0 100644 --- a/pkg/middlewares/redirect/redirect_regex_test.go +++ b/pkg/middlewares/redirect/redirect_regex_test.go @@ -1,7 +1,6 @@ package redirect import ( - "context" "crypto/tls" "net/http" "net/http/httptest" @@ -158,7 +157,7 @@ func TestRedirectRegexHandler(t *testing.T) { t.Parallel() next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}) - handler, err := NewRedirectRegex(context.Background(), next, test.config, "traefikTest") + handler, err := NewRedirectRegex(t.Context(), next, test.config, "traefikTest") if test.errorExpected { require.Error(t, err) diff --git a/pkg/middlewares/redirect/redirect_scheme_test.go b/pkg/middlewares/redirect/redirect_scheme_test.go index 258bf6c04..aa90bddbc 100644 --- a/pkg/middlewares/redirect/redirect_scheme_test.go +++ b/pkg/middlewares/redirect/redirect_scheme_test.go @@ -1,7 +1,6 @@ package redirect import ( - "context" "crypto/tls" "net/http" "net/http/httptest" @@ -287,7 +286,7 @@ func TestRedirectSchemeHandler(t *testing.T) { t.Parallel() next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}) - handler, err := NewRedirectScheme(context.Background(), next, test.config, "traefikTest") + handler, err := NewRedirectScheme(t.Context(), next, test.config, "traefikTest") if test.errorExpected { require.Error(t, err) diff --git a/pkg/middlewares/replacepath/replace_path.go b/pkg/middlewares/replacepath/replace_path.go index 9c8e404d4..d211b83dd 100644 --- a/pkg/middlewares/replacepath/replace_path.go +++ b/pkg/middlewares/replacepath/replace_path.go @@ -8,7 +8,6 @@ import ( "github.com/traefik/traefik/v3/pkg/config/dynamic" "github.com/traefik/traefik/v3/pkg/middlewares" "github.com/traefik/traefik/v3/pkg/middlewares/observability" - "go.opentelemetry.io/otel/trace" ) const ( @@ -35,8 +34,8 @@ func New(ctx context.Context, next http.Handler, config dynamic.ReplacePath, nam }, nil } -func (r *replacePath) GetTracingInformation() (string, string, trace.SpanKind) { - return r.name, typeName, trace.SpanKindInternal +func (r *replacePath) GetTracingInformation() (string, string) { + return r.name, typeName } func (r *replacePath) ServeHTTP(rw http.ResponseWriter, req *http.Request) { diff --git a/pkg/middlewares/replacepath/replace_path_test.go b/pkg/middlewares/replacepath/replace_path_test.go index 5998579d1..9f4923aea 100644 --- a/pkg/middlewares/replacepath/replace_path_test.go +++ b/pkg/middlewares/replacepath/replace_path_test.go @@ -1,7 +1,6 @@ package replacepath import ( - "context" "net/http" "net/http/httptest" "testing" @@ -82,7 +81,7 @@ func TestReplacePath(t *testing.T) { requestURI = r.RequestURI }) - handler, err := New(context.Background(), next, test.config, "foo-replace-path") + handler, err := New(t.Context(), next, test.config, "foo-replace-path") require.NoError(t, err) server := httptest.NewServer(handler) diff --git a/pkg/middlewares/replacepathregex/replace_path_regex.go b/pkg/middlewares/replacepathregex/replace_path_regex.go index f04e9d9a0..42cd79404 100644 --- a/pkg/middlewares/replacepathregex/replace_path_regex.go +++ b/pkg/middlewares/replacepathregex/replace_path_regex.go @@ -12,7 +12,6 @@ import ( "github.com/traefik/traefik/v3/pkg/middlewares" "github.com/traefik/traefik/v3/pkg/middlewares/observability" "github.com/traefik/traefik/v3/pkg/middlewares/replacepath" - "go.opentelemetry.io/otel/trace" ) const typeName = "ReplacePathRegex" @@ -42,8 +41,8 @@ func New(ctx context.Context, next http.Handler, config dynamic.ReplacePathRegex }, nil } -func (rp *replacePathRegex) GetTracingInformation() (string, string, trace.SpanKind) { - return rp.name, typeName, trace.SpanKindInternal +func (rp *replacePathRegex) GetTracingInformation() (string, string) { + return rp.name, typeName } func (rp *replacePathRegex) ServeHTTP(rw http.ResponseWriter, req *http.Request) { diff --git a/pkg/middlewares/replacepathregex/replace_path_regex_test.go b/pkg/middlewares/replacepathregex/replace_path_regex_test.go index 065b53d80..a1be75fcd 100644 --- a/pkg/middlewares/replacepathregex/replace_path_regex_test.go +++ b/pkg/middlewares/replacepathregex/replace_path_regex_test.go @@ -1,7 +1,6 @@ package replacepathregex import ( - "context" "net/http" "net/http/httptest" "testing" @@ -150,7 +149,7 @@ func TestReplacePathRegex(t *testing.T) { requestURI = r.RequestURI }) - handler, err := New(context.Background(), next, test.config, "foo-replace-path-regexp") + handler, err := New(t.Context(), next, test.config, "foo-replace-path-regexp") if test.expectsError { require.Error(t, err) return diff --git a/pkg/middlewares/requestdecorator/hostresolver_test.go b/pkg/middlewares/requestdecorator/hostresolver_test.go index f1c8c39a2..e00228576 100644 --- a/pkg/middlewares/requestdecorator/hostresolver_test.go +++ b/pkg/middlewares/requestdecorator/hostresolver_test.go @@ -1,7 +1,6 @@ package requestdecorator import ( - "context" "testing" "github.com/stretchr/testify/assert" @@ -43,7 +42,7 @@ func TestCNAMEFlatten(t *testing.T) { ResolvDepth: 5, } - flatH := hostResolver.CNAMEFlatten(context.Background(), test.domain) + flatH := hostResolver.CNAMEFlatten(t.Context(), test.domain) assert.Equal(t, test.expectedDomain, flatH) }) } diff --git a/pkg/middlewares/retry/retry.go b/pkg/middlewares/retry/retry.go index 068030777..de15a21b4 100644 --- a/pkg/middlewares/retry/retry.go +++ b/pkg/middlewares/retry/retry.go @@ -14,9 +14,10 @@ import ( "github.com/cenkalti/backoff/v4" "github.com/traefik/traefik/v3/pkg/config/dynamic" "github.com/traefik/traefik/v3/pkg/middlewares" - "github.com/traefik/traefik/v3/pkg/tracing" + "github.com/traefik/traefik/v3/pkg/middlewares/observability" + "github.com/traefik/traefik/v3/pkg/observability/tracing" "go.opentelemetry.io/otel/attribute" - semconv "go.opentelemetry.io/otel/semconv/v1.26.0" + semconv "go.opentelemetry.io/otel/semconv/v1.37.0" "go.opentelemetry.io/otel/trace" ) @@ -124,7 +125,7 @@ func (r *retry) ServeHTTP(rw http.ResponseWriter, req *http.Request) { var currentSpan trace.Span operation := func() error { - if tracer != nil { + if tracer != nil && observability.DetailedTracingEnabled(req.Context()) { if currentSpan != nil { currentSpan.End() } diff --git a/pkg/middlewares/retry/retry_test.go b/pkg/middlewares/retry/retry_test.go index 6dca141c6..a4575b817 100644 --- a/pkg/middlewares/retry/retry_test.go +++ b/pkg/middlewares/retry/retry_test.go @@ -1,7 +1,6 @@ package retry import ( - "context" "fmt" "io" "net/http" @@ -129,7 +128,7 @@ func TestRetry(t *testing.T) { }) retryListener := &countingRetryListener{} - retry, err := New(context.Background(), next, test.config, retryListener, "traefikTest") + retry, err := New(t.Context(), next, test.config, retryListener, "traefikTest") require.NoError(t, err) recorder := httptest.NewRecorder() @@ -149,7 +148,7 @@ func TestRetryEmptyServerList(t *testing.T) { }) retryListener := &countingRetryListener{} - retry, err := New(context.Background(), next, dynamic.Retry{Attempts: 3}, retryListener, "traefikTest") + retry, err := New(t.Context(), next, dynamic.Retry{Attempts: 3}, retryListener, "traefikTest") require.NoError(t, err) recorder := httptest.NewRecorder() @@ -185,7 +184,7 @@ func TestMultipleRetriesShouldNotLooseHeaders(t *testing.T) { rw.WriteHeader(http.StatusNoContent) }) - retry, err := New(context.Background(), next, dynamic.Retry{Attempts: 3}, &countingRetryListener{}, "traefikTest") + retry, err := New(t.Context(), next, dynamic.Retry{Attempts: 3}, &countingRetryListener{}, "traefikTest") require.NoError(t, err) res := httptest.NewRecorder() @@ -219,7 +218,7 @@ func TestRetryShouldNotLooseHeadersOnWrite(t *testing.T) { require.NoError(t, err) }) - retry, err := New(context.Background(), next, dynamic.Retry{Attempts: 3}, &countingRetryListener{}, "traefikTest") + retry, err := New(t.Context(), next, dynamic.Retry{Attempts: 3}, &countingRetryListener{}, "traefikTest") require.NoError(t, err) res := httptest.NewRecorder() @@ -243,7 +242,7 @@ func TestRetryWithFlush(t *testing.T) { } }) - retry, err := New(context.Background(), next, dynamic.Retry{Attempts: 1}, &countingRetryListener{}, "traefikTest") + retry, err := New(t.Context(), next, dynamic.Retry{Attempts: 1}, &countingRetryListener{}, "traefikTest") require.NoError(t, err) responseRecorder := httptest.NewRecorder() @@ -312,7 +311,7 @@ func TestRetryWebsocket(t *testing.T) { }) retryListener := &countingRetryListener{} - retryH, err := New(context.Background(), next, dynamic.Retry{Attempts: test.maxRequestAttempts}, retryListener, "traefikTest") + retryH, err := New(t.Context(), next, dynamic.Retry{Attempts: test.maxRequestAttempts}, retryListener, "traefikTest") require.NoError(t, err) retryServer := httptest.NewServer(retryH) @@ -345,7 +344,7 @@ func Test1xxResponses(t *testing.T) { }) retryListener := &countingRetryListener{} - retry, err := New(context.Background(), next, dynamic.Retry{Attempts: 1}, retryListener, "traefikTest") + retry, err := New(t.Context(), next, dynamic.Retry{Attempts: 1}, retryListener, "traefikTest") require.NoError(t, err) server := httptest.NewServer(retry) @@ -389,7 +388,7 @@ func Test1xxResponses(t *testing.T) { return nil }, } - req, _ := http.NewRequestWithContext(httptrace.WithClientTrace(context.Background(), trace), http.MethodGet, server.URL, nil) + req, _ := http.NewRequestWithContext(httptrace.WithClientTrace(t.Context(), trace), http.MethodGet, server.URL, nil) res, err := frontendClient.Do(req) assert.NoError(t, err) diff --git a/pkg/middlewares/stripprefix/strip_prefix.go b/pkg/middlewares/stripprefix/strip_prefix.go index 8483f5100..1632814ce 100644 --- a/pkg/middlewares/stripprefix/strip_prefix.go +++ b/pkg/middlewares/stripprefix/strip_prefix.go @@ -7,7 +7,6 @@ import ( "github.com/traefik/traefik/v3/pkg/config/dynamic" "github.com/traefik/traefik/v3/pkg/middlewares" - "go.opentelemetry.io/otel/trace" ) const ( @@ -45,8 +44,8 @@ func New(ctx context.Context, next http.Handler, config dynamic.StripPrefix, nam }, nil } -func (s *stripPrefix) GetTracingInformation() (string, string, trace.SpanKind) { - return s.name, typeName, trace.SpanKindUnspecified +func (s *stripPrefix) GetTracingInformation() (string, string) { + return s.name, typeName } func (s *stripPrefix) ServeHTTP(rw http.ResponseWriter, req *http.Request) { diff --git a/pkg/middlewares/stripprefix/strip_prefix_test.go b/pkg/middlewares/stripprefix/strip_prefix_test.go index 83310ff8b..50cee8e28 100644 --- a/pkg/middlewares/stripprefix/strip_prefix_test.go +++ b/pkg/middlewares/stripprefix/strip_prefix_test.go @@ -1,7 +1,6 @@ package stripprefix import ( - "context" "net/http" "net/http/httptest" "testing" @@ -148,7 +147,7 @@ func TestStripPrefix(t *testing.T) { pointer := func(v bool) *bool { return &v } test.config.ForceSlash = pointer(false) - handler, err := New(context.Background(), next, test.config, "foo-strip-prefix") + handler, err := New(t.Context(), next, test.config, "foo-strip-prefix") require.NoError(t, err) req := testhelpers.MustNewRequest(http.MethodGet, "http://localhost"+test.path, nil) diff --git a/pkg/middlewares/stripprefixregex/strip_prefix_regex.go b/pkg/middlewares/stripprefixregex/strip_prefix_regex.go index 71de1ad28..a38752659 100644 --- a/pkg/middlewares/stripprefixregex/strip_prefix_regex.go +++ b/pkg/middlewares/stripprefixregex/strip_prefix_regex.go @@ -9,7 +9,6 @@ import ( "github.com/traefik/traefik/v3/pkg/config/dynamic" "github.com/traefik/traefik/v3/pkg/middlewares" "github.com/traefik/traefik/v3/pkg/middlewares/stripprefix" - "go.opentelemetry.io/otel/trace" ) const ( @@ -43,8 +42,8 @@ func New(ctx context.Context, next http.Handler, config dynamic.StripPrefixRegex return &stripPrefix, nil } -func (s *stripPrefixRegex) GetTracingInformation() (string, string, trace.SpanKind) { - return s.name, typeName, trace.SpanKindInternal +func (s *stripPrefixRegex) GetTracingInformation() (string, string) { + return s.name, typeName } func (s *stripPrefixRegex) ServeHTTP(rw http.ResponseWriter, req *http.Request) { diff --git a/pkg/middlewares/stripprefixregex/strip_prefix_regex_test.go b/pkg/middlewares/stripprefixregex/strip_prefix_regex_test.go index 9f2c02548..7b7e3092e 100644 --- a/pkg/middlewares/stripprefixregex/strip_prefix_regex_test.go +++ b/pkg/middlewares/stripprefixregex/strip_prefix_regex_test.go @@ -1,7 +1,6 @@ package stripprefixregex import ( - "context" "net/http" "net/http/httptest" "testing" @@ -118,7 +117,7 @@ func TestStripPrefixRegex(t *testing.T) { actualHeader = r.Header.Get(stripprefix.ForwardedPrefixHeader) requestURI = r.RequestURI }) - handler, err := New(context.Background(), handlerPath, testPrefixRegex, "foo-strip-prefix-regex") + handler, err := New(t.Context(), handlerPath, testPrefixRegex, "foo-strip-prefix-regex") require.NoError(t, err) req := testhelpers.MustNewRequest(http.MethodGet, "http://localhost"+test.path, nil) diff --git a/pkg/middlewares/tcp/inflightconn/inflight_conn_test.go b/pkg/middlewares/tcp/inflightconn/inflight_conn_test.go index 28fca80df..d2f9dc141 100644 --- a/pkg/middlewares/tcp/inflightconn/inflight_conn_test.go +++ b/pkg/middlewares/tcp/inflightconn/inflight_conn_test.go @@ -1,7 +1,6 @@ package inflightconn import ( - "context" "net" "testing" "time" @@ -27,7 +26,7 @@ func TestInFlightConn_ServeTCP(t *testing.T) { finishCh <- struct{}{} }) - middleware, err := New(context.Background(), next, dynamic.TCPInFlightConn{Amount: 1}, "foo") + middleware, err := New(t.Context(), next, dynamic.TCPInFlightConn{Amount: 1}, "foo") require.NoError(t, err) // The first connection should succeed and wait. diff --git a/pkg/middlewares/tcp/ipallowlist/ip_allowlist_test.go b/pkg/middlewares/tcp/ipallowlist/ip_allowlist_test.go index 5c918e8cf..9e58e6a3a 100644 --- a/pkg/middlewares/tcp/ipallowlist/ip_allowlist_test.go +++ b/pkg/middlewares/tcp/ipallowlist/ip_allowlist_test.go @@ -43,7 +43,7 @@ func TestNewIPAllowLister(t *testing.T) { t.Parallel() next := tcp.HandlerFunc(func(conn tcp.WriteCloser) {}) - allowLister, err := New(context.Background(), next, test.allowList, "traefikTest") + allowLister, err := New(t.Context(), next, test.allowList, "traefikTest") if test.expectedError { assert.Error(t, err) @@ -92,7 +92,7 @@ func TestIPAllowLister_ServeHTTP(t *testing.T) { require.NoError(t, err) }) - allowLister, err := New(context.Background(), next, test.allowList, "traefikTest") + allowLister, err := New(t.Context(), next, test.allowList, "traefikTest") require.NoError(t, err) server, client := net.Pipe() diff --git a/pkg/middlewares/tcp/ipwhitelist/ip_whitelist_test.go b/pkg/middlewares/tcp/ipwhitelist/ip_whitelist_test.go index f0bf631fb..2fb439714 100644 --- a/pkg/middlewares/tcp/ipwhitelist/ip_whitelist_test.go +++ b/pkg/middlewares/tcp/ipwhitelist/ip_whitelist_test.go @@ -43,7 +43,7 @@ func TestNewIPWhiteLister(t *testing.T) { t.Parallel() next := tcp.HandlerFunc(func(conn tcp.WriteCloser) {}) - whiteLister, err := New(context.Background(), next, test.whiteList, "traefikTest") + whiteLister, err := New(t.Context(), next, test.whiteList, "traefikTest") if test.expectedError { assert.Error(t, err) @@ -92,7 +92,7 @@ func TestIPWhiteLister_ServeHTTP(t *testing.T) { require.NoError(t, err) }) - whiteLister, err := New(context.Background(), next, test.whiteList, "traefikTest") + whiteLister, err := New(t.Context(), next, test.whiteList, "traefikTest") require.NoError(t, err) server, client := net.Pipe() diff --git a/pkg/logs/aws.go b/pkg/observability/logs/aws.go similarity index 100% rename from pkg/logs/aws.go rename to pkg/observability/logs/aws.go diff --git a/pkg/logs/aws_test.go b/pkg/observability/logs/aws_test.go similarity index 100% rename from pkg/logs/aws_test.go rename to pkg/observability/logs/aws_test.go diff --git a/pkg/logs/datadog.go b/pkg/observability/logs/datadog.go similarity index 100% rename from pkg/logs/datadog.go rename to pkg/observability/logs/datadog.go diff --git a/pkg/logs/datadog_test.go b/pkg/observability/logs/datadog_test.go similarity index 100% rename from pkg/logs/datadog_test.go rename to pkg/observability/logs/datadog_test.go diff --git a/pkg/logs/elastic.go b/pkg/observability/logs/elastic.go similarity index 100% rename from pkg/logs/elastic.go rename to pkg/observability/logs/elastic.go diff --git a/pkg/logs/elastic_test.go b/pkg/observability/logs/elastic_test.go similarity index 100% rename from pkg/logs/elastic_test.go rename to pkg/observability/logs/elastic_test.go diff --git a/pkg/logs/fields.go b/pkg/observability/logs/fields.go similarity index 100% rename from pkg/logs/fields.go rename to pkg/observability/logs/fields.go diff --git a/pkg/logs/gokit.go b/pkg/observability/logs/gokit.go similarity index 100% rename from pkg/logs/gokit.go rename to pkg/observability/logs/gokit.go diff --git a/pkg/logs/gokit_test.go b/pkg/observability/logs/gokit_test.go similarity index 100% rename from pkg/logs/gokit_test.go rename to pkg/observability/logs/gokit_test.go diff --git a/pkg/logs/hclog.go b/pkg/observability/logs/hclog.go similarity index 100% rename from pkg/logs/hclog.go rename to pkg/observability/logs/hclog.go diff --git a/pkg/logs/hclog_test.go b/pkg/observability/logs/hclog_test.go similarity index 100% rename from pkg/logs/hclog_test.go rename to pkg/observability/logs/hclog_test.go diff --git a/pkg/logs/instana.go b/pkg/observability/logs/instana.go similarity index 100% rename from pkg/logs/instana.go rename to pkg/observability/logs/instana.go diff --git a/pkg/logs/instana_test.go b/pkg/observability/logs/instana_test.go similarity index 100% rename from pkg/logs/instana_test.go rename to pkg/observability/logs/instana_test.go diff --git a/pkg/logs/log.go b/pkg/observability/logs/log.go similarity index 100% rename from pkg/logs/log.go rename to pkg/observability/logs/log.go diff --git a/pkg/logs/log_test.go b/pkg/observability/logs/log_test.go similarity index 100% rename from pkg/logs/log_test.go rename to pkg/observability/logs/log_test.go diff --git a/pkg/logs/logrus.go b/pkg/observability/logs/logrus.go similarity index 100% rename from pkg/logs/logrus.go rename to pkg/observability/logs/logrus.go diff --git a/pkg/logs/logrus_test.go b/pkg/observability/logs/logrus_test.go similarity index 100% rename from pkg/logs/logrus_test.go rename to pkg/observability/logs/logrus_test.go diff --git a/pkg/logs/otel.go b/pkg/observability/logs/otel.go similarity index 88% rename from pkg/logs/otel.go rename to pkg/observability/logs/otel.go index bc8f95443..f56f102f2 100644 --- a/pkg/logs/otel.go +++ b/pkg/observability/logs/otel.go @@ -1,23 +1,28 @@ package logs import ( + "context" "encoding/json" "fmt" "reflect" "time" "github.com/rs/zerolog" - "github.com/traefik/traefik/v3/pkg/types" + "github.com/traefik/traefik/v3/pkg/observability" + "github.com/traefik/traefik/v3/pkg/observability/types" otellog "go.opentelemetry.io/otel/log" ) // SetupOTelLogger sets up the OpenTelemetry logger. -func SetupOTelLogger(logger zerolog.Logger, config *types.OTelLog) (zerolog.Logger, error) { +func SetupOTelLogger(ctx context.Context, logger zerolog.Logger, config *types.OTelLog) (zerolog.Logger, error) { if config == nil { return logger, nil } - provider, err := config.NewLoggerProvider() + if err := observability.EnsureUserEnvVar(); err != nil { + return zerolog.Logger{}, err + } + provider, err := config.NewLoggerProvider(ctx) if err != nil { return zerolog.Logger{}, fmt.Errorf("setting up OpenTelemetry logger provider: %w", err) } @@ -36,9 +41,6 @@ func (h *otelLoggerHook) Run(e *zerolog.Event, level zerolog.Level, message stri return } - // Discard the event to avoid double logging. - e.Discard() - var record otellog.Record record.SetTimestamp(time.Now().UTC()) record.SetSeverity(otelLogSeverity(level)) diff --git a/pkg/logs/otel_test.go b/pkg/observability/logs/otel_test.go similarity index 95% rename from pkg/logs/otel_test.go rename to pkg/observability/logs/otel_test.go index 60c68cc09..c51322cdd 100644 --- a/pkg/logs/otel_test.go +++ b/pkg/observability/logs/otel_test.go @@ -2,7 +2,6 @@ package logs import ( "compress/gzip" - "context" "encoding/json" "io" "net/http" @@ -14,7 +13,7 @@ import ( "github.com/rs/zerolog" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/traefik/traefik/v3/pkg/types" + otypes "github.com/traefik/traefik/v3/pkg/observability/types" "go.opentelemetry.io/collector/pdata/plog/plogotlp" "go.opentelemetry.io/otel/trace" ) @@ -161,10 +160,10 @@ func TestLog(t *testing.T) { for _, test := range tests { t.Run(test.desc, func(t *testing.T) { - config := &types.OTelLog{ + config := &otypes.OTelLog{ ServiceName: "test", ResourceAttributes: map[string]string{"resource": "attribute"}, - HTTP: &types.OTelHTTP{ + HTTP: &otypes.OTelHTTP{ Endpoint: collector.URL, }, } @@ -172,10 +171,10 @@ func TestLog(t *testing.T) { out := zerolog.MultiLevelWriter(zerolog.ConsoleWriter{Out: os.Stderr, TimeFormat: time.RFC3339}) logger := zerolog.New(out).With().Caller().Logger() - logger, err := SetupOTelLogger(logger, config) + logger, err := SetupOTelLogger(t.Context(), logger, config) require.NoError(t, err) - ctx := trace.ContextWithSpanContext(context.Background(), trace.NewSpanContext(trace.SpanContextConfig{ + ctx := trace.ContextWithSpanContext(t.Context(), trace.NewSpanContext(trace.SpanContextConfig{ TraceID: trace.TraceID{0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8}, SpanID: trace.SpanID{0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8}, })) diff --git a/pkg/logs/oxy.go b/pkg/observability/logs/oxy.go similarity index 100% rename from pkg/logs/oxy.go rename to pkg/observability/logs/oxy.go diff --git a/pkg/logs/oxy_test.go b/pkg/observability/logs/oxy_test.go similarity index 100% rename from pkg/logs/oxy_test.go rename to pkg/observability/logs/oxy_test.go diff --git a/pkg/logs/wasm.go b/pkg/observability/logs/wasm.go similarity index 100% rename from pkg/logs/wasm.go rename to pkg/observability/logs/wasm.go diff --git a/pkg/metrics/datadog.go b/pkg/observability/metrics/datadog.go similarity index 95% rename from pkg/metrics/datadog.go rename to pkg/observability/metrics/datadog.go index 89e7992a1..6b97f650e 100644 --- a/pkg/metrics/datadog.go +++ b/pkg/observability/metrics/datadog.go @@ -10,9 +10,9 @@ import ( "github.com/go-kit/kit/util/conn" gokitlog "github.com/go-kit/log" "github.com/rs/zerolog/log" - "github.com/traefik/traefik/v3/pkg/logs" + "github.com/traefik/traefik/v3/pkg/observability/logs" + otypes "github.com/traefik/traefik/v3/pkg/observability/types" "github.com/traefik/traefik/v3/pkg/safe" - "github.com/traefik/traefik/v3/pkg/types" ) const ( @@ -56,7 +56,7 @@ const ( ) // RegisterDatadog registers the metrics pusher if this didn't happen yet and creates a datadog Registry instance. -func RegisterDatadog(ctx context.Context, config *types.Datadog) Registry { +func RegisterDatadog(ctx context.Context, config *otypes.Datadog) Registry { // Ensures there is only one DataDog client sending metrics at any given time. StopDatadog() @@ -109,7 +109,7 @@ func RegisterDatadog(ctx context.Context, config *types.Datadog) Registry { return registry } -func initDatadogClient(ctx context.Context, config *types.Datadog, logger gokitlog.LoggerFunc) { +func initDatadogClient(ctx context.Context, config *otypes.Datadog, logger gokitlog.LoggerFunc) { network, address := parseDatadogAddress(config.Address) ctx, datadogLoopCancelFunc = context.WithCancel(ctx) diff --git a/pkg/metrics/datadog_test.go b/pkg/observability/metrics/datadog_test.go similarity index 93% rename from pkg/metrics/datadog_test.go rename to pkg/observability/metrics/datadog_test.go index 0fc5bd758..bf4a044e1 100644 --- a/pkg/metrics/datadog_test.go +++ b/pkg/observability/metrics/datadog_test.go @@ -1,7 +1,6 @@ package metrics import ( - "context" "net/http" "strconv" "testing" @@ -10,7 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stvp/go-udp-testing" ptypes "github.com/traefik/paerser/types" - "github.com/traefik/traefik/v3/pkg/types" + otypes "github.com/traefik/traefik/v3/pkg/observability/types" ) func TestDatadog(t *testing.T) { @@ -20,7 +19,7 @@ func TestDatadog(t *testing.T) { // This is needed to make sure that UDP Listener listens for data a bit longer, otherwise it will quit after a millisecond udp.Timeout = 5 * time.Second - datadogRegistry := RegisterDatadog(context.Background(), &types.Datadog{Address: ":18125", PushInterval: ptypes.Duration(time.Second), AddEntryPointsLabels: true, AddRoutersLabels: true, AddServicesLabels: true}) + datadogRegistry := RegisterDatadog(t.Context(), &otypes.Datadog{Address: ":18125", PushInterval: ptypes.Duration(time.Second), AddEntryPointsLabels: true, AddRoutersLabels: true, AddServicesLabels: true}) if !datadogRegistry.IsEpEnabled() || !datadogRegistry.IsRouterEnabled() || !datadogRegistry.IsSvcEnabled() { t.Errorf("DatadogRegistry should return true for IsEnabled(), IsRouterEnabled() and IsSvcEnabled()") @@ -35,7 +34,7 @@ func TestDatadogWithPrefix(t *testing.T) { // This is needed to make sure that UDP Listener listens for data a bit longer, otherwise it will quit after a millisecond udp.Timeout = 5 * time.Second - datadogRegistry := RegisterDatadog(context.Background(), &types.Datadog{Prefix: "testPrefix", Address: ":18125", PushInterval: ptypes.Duration(time.Second), AddEntryPointsLabels: true, AddRoutersLabels: true, AddServicesLabels: true}) + datadogRegistry := RegisterDatadog(t.Context(), &otypes.Datadog{Prefix: "testPrefix", Address: ":18125", PushInterval: ptypes.Duration(time.Second), AddEntryPointsLabels: true, AddRoutersLabels: true, AddServicesLabels: true}) testDatadogRegistry(t, "testPrefix", datadogRegistry) } diff --git a/pkg/metrics/headers.go b/pkg/observability/metrics/headers.go similarity index 100% rename from pkg/metrics/headers.go rename to pkg/observability/metrics/headers.go diff --git a/pkg/metrics/influxdb2.go b/pkg/observability/metrics/influxdb2.go similarity index 95% rename from pkg/metrics/influxdb2.go rename to pkg/observability/metrics/influxdb2.go index 270ac03a9..aaa159391 100644 --- a/pkg/metrics/influxdb2.go +++ b/pkg/observability/metrics/influxdb2.go @@ -12,9 +12,9 @@ import ( influxdb2log "github.com/influxdata/influxdb-client-go/v2/log" influxdb "github.com/influxdata/influxdb1-client/v2" "github.com/rs/zerolog/log" - "github.com/traefik/traefik/v3/pkg/logs" + "github.com/traefik/traefik/v3/pkg/observability/logs" + otypes "github.com/traefik/traefik/v3/pkg/observability/types" "github.com/traefik/traefik/v3/pkg/safe" - "github.com/traefik/traefik/v3/pkg/types" ) var ( @@ -52,7 +52,7 @@ const ( ) // RegisterInfluxDB2 creates metrics exporter for InfluxDB2. -func RegisterInfluxDB2(ctx context.Context, config *types.InfluxDB2) Registry { +func RegisterInfluxDB2(ctx context.Context, config *otypes.InfluxDB2) Registry { logger := log.Ctx(ctx) if influxDB2Client == nil { @@ -133,7 +133,7 @@ func StopInfluxDB2() { } // newInfluxDB2Client creates an influxdb2.Client. -func newInfluxDB2Client(config *types.InfluxDB2) (influxdb2.Client, error) { +func newInfluxDB2Client(config *otypes.InfluxDB2) (influxdb2.Client, error) { if config.Token == "" || config.Org == "" || config.Bucket == "" { return nil, errors.New("token, org or bucket property is missing") } diff --git a/pkg/metrics/influxdb2_test.go b/pkg/observability/metrics/influxdb2_test.go similarity index 98% rename from pkg/metrics/influxdb2_test.go rename to pkg/observability/metrics/influxdb2_test.go index e75141ff4..daf2d0f96 100644 --- a/pkg/metrics/influxdb2_test.go +++ b/pkg/observability/metrics/influxdb2_test.go @@ -1,7 +1,6 @@ package metrics import ( - "context" "fmt" "io" "net/http" @@ -12,7 +11,7 @@ import ( "github.com/stretchr/testify/require" ptypes "github.com/traefik/paerser/types" - "github.com/traefik/traefik/v3/pkg/types" + otypes "github.com/traefik/traefik/v3/pkg/observability/types" ) func TestInfluxDB2(t *testing.T) { @@ -26,8 +25,8 @@ func TestInfluxDB2(t *testing.T) { _, _ = fmt.Fprintln(w, "ok") })) - influxDB2Registry := RegisterInfluxDB2(context.Background(), - &types.InfluxDB2{ + influxDB2Registry := RegisterInfluxDB2(t.Context(), + &otypes.InfluxDB2{ Address: ts.URL, Token: "test-token", PushInterval: ptypes.Duration(10 * time.Millisecond), diff --git a/pkg/metrics/metrics.go b/pkg/observability/metrics/metrics.go similarity index 100% rename from pkg/metrics/metrics.go rename to pkg/observability/metrics/metrics.go diff --git a/pkg/metrics/metrics_test.go b/pkg/observability/metrics/metrics_test.go similarity index 100% rename from pkg/metrics/metrics_test.go rename to pkg/observability/metrics/metrics_test.go diff --git a/pkg/metrics/otel.go b/pkg/observability/metrics/otel.go similarity index 88% rename from pkg/metrics/otel.go rename to pkg/observability/metrics/otel.go index 32e48a5d0..eaf95e736 100644 --- a/pkg/metrics/otel.go +++ b/pkg/observability/metrics/otel.go @@ -11,6 +11,8 @@ import ( "github.com/go-kit/kit/metrics" "github.com/rs/zerolog/log" + "github.com/traefik/traefik/v3/pkg/observability" + otypes "github.com/traefik/traefik/v3/pkg/observability/types" "github.com/traefik/traefik/v3/pkg/types" "github.com/traefik/traefik/v3/pkg/version" "go.opentelemetry.io/otel" @@ -20,7 +22,8 @@ import ( "go.opentelemetry.io/otel/metric" sdkmetric "go.opentelemetry.io/otel/sdk/metric" "go.opentelemetry.io/otel/sdk/resource" - semconv "go.opentelemetry.io/otel/semconv/v1.26.0" + semconv "go.opentelemetry.io/otel/semconv/v1.37.0" + "go.opentelemetry.io/otel/semconv/v1.37.0/httpconv" "google.golang.org/grpc/credentials" "google.golang.org/grpc/encoding/gzip" ) @@ -39,13 +42,17 @@ func SetMeterProvider(meterProvider *sdkmetric.MeterProvider) { // SemConvMetricsRegistry holds stables semantic conventions metric instruments. type SemConvMetricsRegistry struct { // server metrics - httpServerRequestDuration metric.Float64Histogram + httpServerRequestDuration httpconv.ServerRequestDuration // client metrics - httpClientRequestDuration metric.Float64Histogram + httpClientRequestDuration httpconv.ClientRequestDuration } // NewSemConvMetricRegistry registers all stables semantic conventions metrics. -func NewSemConvMetricRegistry(ctx context.Context, config *types.OTLP) (*SemConvMetricsRegistry, error) { +func NewSemConvMetricRegistry(ctx context.Context, config *otypes.OTLP) (*SemConvMetricsRegistry, error) { + if err := observability.EnsureUserEnvVar(); err != nil { + return nil, err + } + if openTelemetryMeterProvider == nil { var err error if openTelemetryMeterProvider, err = newOpenTelemetryMeterProvider(ctx, config); err != nil { @@ -58,17 +65,13 @@ func NewSemConvMetricRegistry(ctx context.Context, config *types.OTLP) (*SemConv meter := otel.Meter("github.com/traefik/traefik", metric.WithInstrumentationVersion(version.Version)) - httpServerRequestDuration, err := meter.Float64Histogram(semconv.HTTPServerRequestDurationName, - metric.WithDescription(semconv.HTTPServerRequestDurationDescription), - metric.WithUnit("s"), + httpServerRequestDuration, err := httpconv.NewServerRequestDuration(meter, metric.WithExplicitBucketBoundaries(config.ExplicitBoundaries...)) if err != nil { return nil, fmt.Errorf("can't build httpServerRequestDuration histogram: %w", err) } - httpClientRequestDuration, err := meter.Float64Histogram(semconv.HTTPClientRequestDurationName, - metric.WithDescription(semconv.HTTPClientRequestDurationDescription), - metric.WithUnit("s"), + httpClientRequestDuration, err := httpconv.NewClientRequestDuration(meter, metric.WithExplicitBucketBoundaries(config.ExplicitBoundaries...)) if err != nil { return nil, fmt.Errorf("can't build httpClientRequestDuration histogram: %w", err) @@ -81,25 +84,25 @@ func NewSemConvMetricRegistry(ctx context.Context, config *types.OTLP) (*SemConv } // HTTPServerRequestDuration returns the HTTP server request duration histogram. -func (s *SemConvMetricsRegistry) HTTPServerRequestDuration() metric.Float64Histogram { +func (s *SemConvMetricsRegistry) HTTPServerRequestDuration() httpconv.ServerRequestDuration { if s == nil { - return nil + return httpconv.ServerRequestDuration{} } return s.httpServerRequestDuration } // HTTPClientRequestDuration returns the HTTP client request duration histogram. -func (s *SemConvMetricsRegistry) HTTPClientRequestDuration() metric.Float64Histogram { +func (s *SemConvMetricsRegistry) HTTPClientRequestDuration() httpconv.ClientRequestDuration { if s == nil { - return nil + return httpconv.ClientRequestDuration{} } return s.httpClientRequestDuration } // RegisterOpenTelemetry registers all OpenTelemetry metrics. -func RegisterOpenTelemetry(ctx context.Context, config *types.OTLP) Registry { +func RegisterOpenTelemetry(ctx context.Context, config *otypes.OTLP) Registry { if openTelemetryMeterProvider == nil { var err error if openTelemetryMeterProvider, err = newOpenTelemetryMeterProvider(ctx, config); err != nil { @@ -122,7 +125,7 @@ func RegisterOpenTelemetry(ctx context.Context, config *types.OTLP) Registry { configReloadsCounter: newOTLPCounterFrom(meter, configReloadsTotalName, "Config reloads"), lastConfigReloadSuccessGauge: newOTLPGaugeFrom(meter, configLastReloadSuccessName, "Last config reload success", "ms"), openConnectionsGauge: newOTLPGaugeFrom(meter, openConnectionsName, "How many open connections exist, by entryPoint and protocol", "1"), - tlsCertsNotAfterTimestampGauge: newOTLPGaugeFrom(meter, tlsCertsNotAfterTimestampName, "Certificate expiration timestamp", "ms"), + tlsCertsNotAfterTimestampGauge: newOTLPGaugeFrom(meter, tlsCertsNotAfterTimestampName, "Certificate expiration timestamp", "s"), } if config.AddEntryPointsLabels { @@ -192,7 +195,7 @@ func StopOpenTelemetry() { } // newOpenTelemetryMeterProvider creates a new controller.Controller. -func newOpenTelemetryMeterProvider(ctx context.Context, config *types.OTLP) (*sdkmetric.MeterProvider, error) { +func newOpenTelemetryMeterProvider(ctx context.Context, config *otypes.OTLP) (*sdkmetric.MeterProvider, error) { var ( exporter sdkmetric.Exporter err error @@ -206,11 +209,27 @@ func newOpenTelemetryMeterProvider(ctx context.Context, config *types.OTLP) (*sd return nil, fmt.Errorf("creating exporter: %w", err) } + var resAttrs []attribute.KeyValue + for k, v := range config.ResourceAttributes { + resAttrs = append(resAttrs, attribute.String(k, v)) + } + res, err := resource.New(ctx, - resource.WithAttributes(semconv.ServiceNameKey.String(config.ServiceName)), - resource.WithAttributes(semconv.ServiceVersionKey.String(version.Version)), - resource.WithFromEnv(), + resource.WithContainer(), + resource.WithHost(), + resource.WithOS(), + resource.WithProcess(), resource.WithTelemetrySDK(), + resource.WithDetectors(types.K8sAttributesDetector{}), + // The following order allows the user to override the service name and version, + // as well as any other attributes set by the above detectors. + resource.WithAttributes( + semconv.ServiceName(config.ServiceName), + semconv.ServiceVersion(version.Version), + ), + resource.WithAttributes(resAttrs...), + // Use the environment variables to allow overriding above resource attributes. + resource.WithFromEnv(), ) if err != nil { return nil, fmt.Errorf("building resource: %w", err) @@ -237,7 +256,7 @@ func newOpenTelemetryMeterProvider(ctx context.Context, config *types.OTLP) (*sd return meterProvider, nil } -func newHTTPExporter(ctx context.Context, config *types.OTelHTTP) (sdkmetric.Exporter, error) { +func newHTTPExporter(ctx context.Context, config *otypes.OTelHTTP) (sdkmetric.Exporter, error) { endpoint, err := url.Parse(config.Endpoint) if err != nil { return nil, fmt.Errorf("invalid collector endpoint %q: %w", config.Endpoint, err) @@ -269,7 +288,7 @@ func newHTTPExporter(ctx context.Context, config *types.OTelHTTP) (sdkmetric.Exp return otlpmetrichttp.New(ctx, opts...) } -func newGRPCExporter(ctx context.Context, config *types.OTelGRPC) (sdkmetric.Exporter, error) { +func newGRPCExporter(ctx context.Context, config *otypes.OTelGRPC) (sdkmetric.Exporter, error) { host, port, err := net.SplitHostPort(config.Endpoint) if err != nil { return nil, fmt.Errorf("invalid collector endpoint %q: %w", config.Endpoint, err) diff --git a/pkg/metrics/otel_test.go b/pkg/observability/metrics/otel_test.go similarity index 98% rename from pkg/metrics/otel_test.go rename to pkg/observability/metrics/otel_test.go index d9af5a090..abcb1856f 100644 --- a/pkg/metrics/otel_test.go +++ b/pkg/observability/metrics/otel_test.go @@ -2,7 +2,6 @@ package metrics import ( "compress/gzip" - "context" "encoding/json" "fmt" "io" @@ -16,7 +15,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ptypes "github.com/traefik/paerser/types" - "github.com/traefik/traefik/v3/pkg/types" + otypes "github.com/traefik/traefik/v3/pkg/observability/types" "github.com/traefik/traefik/v3/pkg/version" "go.opentelemetry.io/collector/pdata/pmetric/pmetricotlp" "go.opentelemetry.io/otel/attribute" @@ -324,10 +323,10 @@ func TestOpenTelemetry(t *testing.T) { ts.Close() }) - var cfg types.OTLP + var cfg otypes.OTLP (&cfg).SetDefaults() cfg.AddRoutersLabels = true - cfg.HTTP = &types.OTelHTTP{ + cfg.HTTP = &otypes.OTelHTTP{ Endpoint: ts.URL, } cfg.PushInterval = ptypes.Duration(10 * time.Millisecond) @@ -338,7 +337,7 @@ func TestOpenTelemetry(t *testing.T) { wantServiceName = test.serviceName } - registry := RegisterOpenTelemetry(context.Background(), &cfg) + registry := RegisterOpenTelemetry(t.Context(), &cfg) require.NotNil(t, registry) if !registry.IsEpEnabled() || !registry.IsRouterEnabled() || !registry.IsSvcEnabled() { @@ -366,7 +365,7 @@ func TestOpenTelemetry(t *testing.T) { tryAssertMessage(t, c, expectedConfig) expectedTLSCerts := []string{ - `({"name":"traefik_tls_certs_not_after","description":"Certificate expiration timestamp","unit":"ms","gauge":{"dataPoints":\[{"attributes":\[{"key":"key","value":{"stringValue":"value"}}\],"startTimeUnixNano":"[\d]{19}","timeUnixNano":"[\d]{19}","asDouble":1}\]}})`, + `({"name":"traefik_tls_certs_not_after","description":"Certificate expiration timestamp","unit":"s","gauge":{"dataPoints":\[{"attributes":\[{"key":"key","value":{"stringValue":"value"}}\],"startTimeUnixNano":"[\d]{19}","timeUnixNano":"[\d]{19}","asDouble":1}\]}})`, } registry.TLSCertsNotAfterTimestampGauge().With("key", "value").Set(1) diff --git a/pkg/metrics/prometheus.go b/pkg/observability/metrics/prometheus.go similarity index 99% rename from pkg/metrics/prometheus.go rename to pkg/observability/metrics/prometheus.go index 608acf16a..f5a0ff735 100644 --- a/pkg/metrics/prometheus.go +++ b/pkg/observability/metrics/prometheus.go @@ -13,7 +13,7 @@ import ( "github.com/prometheus/client_golang/prometheus/promhttp" "github.com/rs/zerolog/log" "github.com/traefik/traefik/v3/pkg/config/dynamic" - "github.com/traefik/traefik/v3/pkg/types" + otypes "github.com/traefik/traefik/v3/pkg/observability/types" ) const ( @@ -80,7 +80,7 @@ func PrometheusHandler() http.Handler { // RegisterPrometheus registers all Prometheus metrics. // It must be called only once and failing to register the metrics will lead to a panic. -func RegisterPrometheus(ctx context.Context, config *types.Prometheus) Registry { +func RegisterPrometheus(ctx context.Context, config *otypes.Prometheus) Registry { standardRegistry := initStandardRegistry(config) if err := promRegistry.Register(collectors.NewProcessCollector(collectors.ProcessCollectorOpts{})); err != nil { @@ -104,7 +104,7 @@ func RegisterPrometheus(ctx context.Context, config *types.Prometheus) Registry return standardRegistry } -func initStandardRegistry(config *types.Prometheus) Registry { +func initStandardRegistry(config *otypes.Prometheus) Registry { buckets := []float64{0.1, 0.3, 1.2, 5.0} if config.Buckets != nil { buckets = config.Buckets diff --git a/pkg/metrics/prometheus_test.go b/pkg/observability/metrics/prometheus_test.go similarity index 96% rename from pkg/metrics/prometheus_test.go rename to pkg/observability/metrics/prometheus_test.go index 91e5a56fe..f3b8c9f49 100644 --- a/pkg/metrics/prometheus_test.go +++ b/pkg/observability/metrics/prometheus_test.go @@ -1,7 +1,6 @@ package metrics import ( - "context" "fmt" "net/http" "strconv" @@ -12,8 +11,8 @@ import ( dto "github.com/prometheus/client_model/go" "github.com/stretchr/testify/assert" "github.com/traefik/traefik/v3/pkg/config/dynamic" + otypes "github.com/traefik/traefik/v3/pkg/observability/types" th "github.com/traefik/traefik/v3/pkg/testhelpers" - "github.com/traefik/traefik/v3/pkg/types" ) func TestRegisterPromState(t *testing.T) { @@ -21,42 +20,42 @@ func TestRegisterPromState(t *testing.T) { testCases := []struct { desc string - prometheusSlice []*types.Prometheus + prometheusSlice []*otypes.Prometheus initPromState bool unregisterPromState bool expectedNbRegistries int }{ { desc: "Register once", - prometheusSlice: []*types.Prometheus{{}}, + prometheusSlice: []*otypes.Prometheus{{}}, initPromState: true, unregisterPromState: false, expectedNbRegistries: 1, }, { desc: "Register once with no promState init", - prometheusSlice: []*types.Prometheus{{}}, + prometheusSlice: []*otypes.Prometheus{{}}, initPromState: false, unregisterPromState: false, expectedNbRegistries: 1, }, { desc: "Register twice", - prometheusSlice: []*types.Prometheus{{}, {}}, + prometheusSlice: []*otypes.Prometheus{{}, {}}, initPromState: true, unregisterPromState: false, expectedNbRegistries: 2, }, { desc: "Register twice with no promstate init", - prometheusSlice: []*types.Prometheus{{}, {}}, + prometheusSlice: []*otypes.Prometheus{{}, {}}, initPromState: false, unregisterPromState: false, expectedNbRegistries: 2, }, { desc: "Register twice with unregister", - prometheusSlice: []*types.Prometheus{{}, {}}, + prometheusSlice: []*otypes.Prometheus{{}, {}}, initPromState: true, unregisterPromState: true, expectedNbRegistries: 2, @@ -70,7 +69,7 @@ func TestRegisterPromState(t *testing.T) { if test.initPromState { initStandardRegistry(prom) } - if registerPromState(context.Background()) { + if registerPromState(t.Context()) { actualNbRegistries++ } if test.unregisterPromState { @@ -91,7 +90,7 @@ func TestPrometheus(t *testing.T) { promRegistry = prometheus.NewRegistry() t.Cleanup(promState.reset) - prometheusRegistry := RegisterPrometheus(context.Background(), &types.Prometheus{ + prometheusRegistry := RegisterPrometheus(t.Context(), &otypes.Prometheus{ AddEntryPointsLabels: true, AddRoutersLabels: true, AddServicesLabels: true, @@ -405,7 +404,7 @@ func TestPrometheusMetricRemoval(t *testing.T) { promRegistry = prometheus.NewRegistry() t.Cleanup(promState.reset) - prometheusRegistry := RegisterPrometheus(context.Background(), &types.Prometheus{AddEntryPointsLabels: true, AddServicesLabels: true, AddRoutersLabels: true}) + prometheusRegistry := RegisterPrometheus(t.Context(), &otypes.Prometheus{AddEntryPointsLabels: true, AddServicesLabels: true, AddRoutersLabels: true}) defer promRegistry.Unregister(promState) conf1 := dynamic.Configuration{ @@ -496,7 +495,7 @@ func TestPrometheusMetricRemoveEndpointForRecoveredService(t *testing.T) { promRegistry = prometheus.NewRegistry() t.Cleanup(promState.reset) - prometheusRegistry := RegisterPrometheus(context.Background(), &types.Prometheus{AddServicesLabels: true}) + prometheusRegistry := RegisterPrometheus(t.Context(), &otypes.Prometheus{AddServicesLabels: true}) defer promRegistry.Unregister(promState) conf1 := dynamic.Configuration{ @@ -535,7 +534,7 @@ func TestPrometheusMetricRemoveEndpointForRecoveredService(t *testing.T) { func TestPrometheusRemovedMetricsReset(t *testing.T) { t.Cleanup(promState.reset) - prometheusRegistry := RegisterPrometheus(context.Background(), &types.Prometheus{AddEntryPointsLabels: true, AddServicesLabels: true}) + prometheusRegistry := RegisterPrometheus(t.Context(), &otypes.Prometheus{AddEntryPointsLabels: true, AddServicesLabels: true}) defer promRegistry.Unregister(promState) conf1 := dynamic.Configuration{ diff --git a/pkg/metrics/statsd.go b/pkg/observability/metrics/statsd.go similarity index 94% rename from pkg/metrics/statsd.go rename to pkg/observability/metrics/statsd.go index 871300ef6..a3a3acbbe 100644 --- a/pkg/metrics/statsd.go +++ b/pkg/observability/metrics/statsd.go @@ -6,9 +6,9 @@ import ( "github.com/go-kit/kit/metrics/statsd" "github.com/rs/zerolog/log" - "github.com/traefik/traefik/v3/pkg/logs" + "github.com/traefik/traefik/v3/pkg/observability/logs" + otypes "github.com/traefik/traefik/v3/pkg/observability/types" "github.com/traefik/traefik/v3/pkg/safe" - "github.com/traefik/traefik/v3/pkg/types" ) var ( @@ -45,7 +45,7 @@ const ( ) // RegisterStatsd registers the metrics pusher if this didn't happen yet and creates a statsd Registry instance. -func RegisterStatsd(ctx context.Context, config *types.Statsd) Registry { +func RegisterStatsd(ctx context.Context, config *otypes.Statsd) Registry { // just to be sure there is a prefix defined if config.Prefix == "" { config.Prefix = defaultMetricsPrefix @@ -97,7 +97,7 @@ func RegisterStatsd(ctx context.Context, config *types.Statsd) Registry { } // initStatsdTicker initializes metrics pusher and creates a statsdClient if not created already. -func initStatsdTicker(ctx context.Context, config *types.Statsd) *time.Ticker { +func initStatsdTicker(ctx context.Context, config *otypes.Statsd) *time.Ticker { address := config.Address if len(address) == 0 { address = "localhost:8125" diff --git a/pkg/metrics/statsd_test.go b/pkg/observability/metrics/statsd_test.go similarity index 91% rename from pkg/metrics/statsd_test.go rename to pkg/observability/metrics/statsd_test.go index 174081db4..ad37314a9 100644 --- a/pkg/metrics/statsd_test.go +++ b/pkg/observability/metrics/statsd_test.go @@ -1,7 +1,6 @@ package metrics import ( - "context" "net/http" "strconv" "testing" @@ -9,7 +8,7 @@ import ( "github.com/stvp/go-udp-testing" ptypes "github.com/traefik/paerser/types" - "github.com/traefik/traefik/v3/pkg/types" + otypes "github.com/traefik/traefik/v3/pkg/observability/types" ) func TestStatsD(t *testing.T) { @@ -21,7 +20,7 @@ func TestStatsD(t *testing.T) { // This is needed to make sure that UDP Listener listens for data a bit longer, otherwise it will quit after a millisecond udp.Timeout = 5 * time.Second - statsdRegistry := RegisterStatsd(context.Background(), &types.Statsd{Address: ":18125", PushInterval: ptypes.Duration(time.Second), AddEntryPointsLabels: true, AddRoutersLabels: true, AddServicesLabels: true}) + statsdRegistry := RegisterStatsd(t.Context(), &otypes.Statsd{Address: ":18125", PushInterval: ptypes.Duration(time.Second), AddEntryPointsLabels: true, AddRoutersLabels: true, AddServicesLabels: true}) testRegistry(t, defaultMetricsPrefix, statsdRegistry) } @@ -35,7 +34,7 @@ func TestStatsDWithPrefix(t *testing.T) { // This is needed to make sure that UDP Listener listens for data a bit longer, otherwise it will quit after a millisecond udp.Timeout = 5 * time.Second - statsdRegistry := RegisterStatsd(context.Background(), &types.Statsd{Address: ":18125", PushInterval: ptypes.Duration(time.Second), AddEntryPointsLabels: true, AddRoutersLabels: true, AddServicesLabels: true, Prefix: "testPrefix"}) + statsdRegistry := RegisterStatsd(t.Context(), &otypes.Statsd{Address: ":18125", PushInterval: ptypes.Duration(time.Second), AddEntryPointsLabels: true, AddRoutersLabels: true, AddServicesLabels: true, Prefix: "testPrefix"}) testRegistry(t, "testPrefix", statsdRegistry) } diff --git a/pkg/observability/observability.go b/pkg/observability/observability.go new file mode 100644 index 000000000..4c05f2cbf --- /dev/null +++ b/pkg/observability/observability.go @@ -0,0 +1,15 @@ +package observability + +import ( + "fmt" + "os" +) + +func EnsureUserEnvVar() error { + if os.Getenv("USER") == "" { + if err := os.Setenv("USER", "traefik"); err != nil { + return fmt.Errorf("could not set USER environment variable: %w", err) + } + } + return nil +} diff --git a/pkg/tracing/tracing.go b/pkg/observability/tracing/tracing.go similarity index 89% rename from pkg/tracing/tracing.go rename to pkg/observability/tracing/tracing.go index 76a3d3302..bb38bc654 100644 --- a/pkg/tracing/tracing.go +++ b/pkg/observability/tracing/tracing.go @@ -13,23 +13,24 @@ import ( "github.com/rs/zerolog/log" "github.com/traefik/traefik/v3/pkg/config/static" - "github.com/traefik/traefik/v3/pkg/types" + "github.com/traefik/traefik/v3/pkg/observability" + otypes "github.com/traefik/traefik/v3/pkg/observability/types" "go.opentelemetry.io/contrib/propagators/autoprop" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" "go.opentelemetry.io/otel/propagation" - semconv "go.opentelemetry.io/otel/semconv/v1.26.0" + semconv "go.opentelemetry.io/otel/semconv/v1.37.0" "go.opentelemetry.io/otel/trace" ) // Backend is an abstraction for tracking backend (OpenTelemetry, ...). type Backend interface { - Setup(serviceName string, sampleRate float64, resourceAttributes map[string]string) (trace.Tracer, io.Closer, error) + Setup(ctx context.Context, serviceName string, sampleRate float64, resourceAttributes map[string]string) (trace.Tracer, io.Closer, error) } // NewTracing Creates a Tracing. -func NewTracing(conf *static.Tracing) (*Tracer, io.Closer, error) { +func NewTracing(ctx context.Context, conf *static.Tracing) (*Tracer, io.Closer, error) { var backend Backend if conf.OTLP != nil { @@ -38,13 +39,17 @@ func NewTracing(conf *static.Tracing) (*Tracer, io.Closer, error) { if backend == nil { log.Debug().Msg("Could not initialize tracing, using OpenTelemetry by default") - defaultBackend := &types.OTelTracing{} + defaultBackend := &otypes.OTelTracing{} backend = defaultBackend } + if err := observability.EnsureUserEnvVar(); err != nil { + return nil, nil, err + } + otel.SetTextMapPropagator(autoprop.NewTextMapPropagator()) - tr, closer, err := backend.Setup(conf.ServiceName, conf.SampleRate, conf.ResourceAttributes) + tr, closer, err := backend.Setup(ctx, conf.ServiceName, conf.SampleRate, conf.ResourceAttributes) if err != nil { return nil, nil, err } @@ -84,13 +89,6 @@ func InjectContextIntoCarrier(req *http.Request) { propagator.Inject(req.Context(), propagation.HeaderCarrier(req.Header)) } -// SetStatusErrorf flags the span as in error and log an event. -func SetStatusErrorf(ctx context.Context, format string, args ...interface{}) { - if span := trace.SpanFromContext(ctx); span != nil { - span.SetStatus(codes.Error, fmt.Sprintf(format, args...)) - } -} - // Span is trace.Span wrapping the Traefik TracerProvider. type Span struct { trace.Span @@ -134,8 +132,8 @@ func NewTracer(tracer trace.Tracer, capturedRequestHeaders, capturedResponseHead return &Tracer{ Tracer: tracer, safeQueryParams: safeQueryParams, - capturedRequestHeaders: capturedRequestHeaders, - capturedResponseHeaders: capturedResponseHeaders, + capturedRequestHeaders: canonicalizeHeaders(capturedRequestHeaders), + capturedResponseHeaders: canonicalizeHeaders(capturedResponseHeaders), } } @@ -353,3 +351,18 @@ func defaultStatus(code int) (codes.Code, string) { } return codes.Unset, "" } + +// canonicalizeHeaders converts a slice of header keys to their canonical form. +// It uses http.CanonicalHeaderKey to ensure that the headers are in a consistent format. +func canonicalizeHeaders(headers []string) []string { + if headers == nil { + return nil + } + + canonicalHeaders := make([]string, len(headers)) + for i, header := range headers { + canonicalHeaders[i] = http.CanonicalHeaderKey(header) + } + + return canonicalHeaders +} diff --git a/pkg/tracing/tracing_test.go b/pkg/observability/tracing/tracing_test.go similarity index 61% rename from pkg/tracing/tracing_test.go rename to pkg/observability/tracing/tracing_test.go index b4d92dca8..91dc19e60 100644 --- a/pkg/tracing/tracing_test.go +++ b/pkg/observability/tracing/tracing_test.go @@ -2,8 +2,6 @@ package tracing import ( "compress/gzip" - "context" - "encoding/json" "io" "net/http" "net/http/httptest" @@ -15,9 +13,12 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/traefik/traefik/v3/pkg/config/static" - "github.com/traefik/traefik/v3/pkg/types" + otypes "github.com/traefik/traefik/v3/pkg/observability/types" + "go.opentelemetry.io/collector/pdata/pcommon" + "go.opentelemetry.io/collector/pdata/ptrace" "go.opentelemetry.io/collector/pdata/ptrace/ptraceotlp" "go.opentelemetry.io/otel/trace" + "go.opentelemetry.io/otel/trace/noop" ) func Test_safeFullURL(t *testing.T) { @@ -76,15 +77,16 @@ func TestTracing(t *testing.T) { headers map[string]string resourceAttributes map[string]string wantServiceHeadersFn func(t *testing.T, headers http.Header) - assertFn func(*testing.T, string) + assertFn func(*testing.T, ptrace.Traces) }{ { desc: "service name and version", - assertFn: func(t *testing.T, trace string) { + assertFn: func(t *testing.T, traces ptrace.Traces) { t.Helper() - assert.Regexp(t, `({"key":"service.name","value":{"stringValue":"traefik"}})`, trace) - assert.Regexp(t, `({"key":"service.version","value":{"stringValue":"dev"}})`, trace) + attributes := resourceAttributes(traces) + assert.Equal(t, "traefik", attributes["service.name"]) + assert.Equal(t, "dev", attributes["service.version"]) }, }, { @@ -92,10 +94,11 @@ func TestTracing(t *testing.T) { resourceAttributes: map[string]string{ "service.environment": "custom", }, - assertFn: func(t *testing.T, trace string) { + assertFn: func(t *testing.T, traces ptrace.Traces) { t.Helper() - assert.Regexp(t, `({"key":"service.environment","value":{"stringValue":"custom"}})`, trace) + attributes := resourceAttributes(traces) + assert.Equal(t, "custom", attributes["service.environment"]) }, }, { @@ -111,12 +114,13 @@ func TestTracing(t *testing.T) { assert.Regexp(t, `(00-00000000000000000000000000000001-\w{16}-01)`, headers["Traceparent"][0]) assert.Equal(t, []string{"foo=bar"}, headers["Tracestate"]) }, - assertFn: func(t *testing.T, trace string) { + assertFn: func(t *testing.T, traces ptrace.Traces) { t.Helper() - assert.Regexp(t, `("traceId":"00000000000000000000000000000001")`, trace) - assert.Regexp(t, `("parentSpanId":"0000000000000001")`, trace) - assert.Regexp(t, `("traceState":"foo=bar")`, trace) + span := mainSpan(traces) + assert.Equal(t, "00000000000000000000000000000001", span.TraceID().String()) + assert.Equal(t, "0000000000000001", span.ParentSpanID().String()) + assert.Equal(t, "foo=bar", span.TraceState().AsRaw()) }, }, { @@ -127,11 +131,12 @@ func TestTracing(t *testing.T) { assert.Regexp(t, `(00-\w{32}-\w{16}-01)`, headers["Traceparent"][0]) }, - assertFn: func(t *testing.T, trace string) { + assertFn: func(t *testing.T, traces ptrace.Traces) { t.Helper() - assert.Regexp(t, `("traceId":"\w{32}")`, trace) - assert.Regexp(t, `("parentSpanId":"\w{16}")`, trace) + span := mainSpan(traces) + assert.Len(t, span.TraceID().String(), 32) + assert.Empty(t, span.ParentSpanID().String()) }, }, { @@ -145,11 +150,12 @@ func TestTracing(t *testing.T) { assert.Regexp(t, `(00000000000000000000000000000001-\w{16}-1)`, headers["B3"][0]) }, - assertFn: func(t *testing.T, trace string) { + assertFn: func(t *testing.T, traces ptrace.Traces) { t.Helper() - assert.Regexp(t, `("traceId":"00000000000000000000000000000001")`, trace) - assert.Regexp(t, `("parentSpanId":"0000000000000002")`, trace) + span := mainSpan(traces) + assert.Equal(t, "00000000000000000000000000000001", span.TraceID().String()) + assert.Equal(t, "0000000000000002", span.ParentSpanID().String()) }, }, { @@ -160,11 +166,12 @@ func TestTracing(t *testing.T) { assert.Regexp(t, `(\w{32}-\w{16}-1)`, headers["B3"][0]) }, - assertFn: func(t *testing.T, trace string) { + assertFn: func(t *testing.T, traces ptrace.Traces) { t.Helper() - assert.Regexp(t, `("traceId":"\w{32}")`, trace) - assert.Regexp(t, `("parentSpanId":"\w{16}")`, trace) + span := mainSpan(traces) + assert.Len(t, span.TraceID().String(), 32) + assert.Empty(t, span.ParentSpanID().String()) }, }, { @@ -184,11 +191,12 @@ func TestTracing(t *testing.T) { assert.Equal(t, "1", headers["X-B3-Sampled"][0]) assert.Len(t, headers["X-B3-Spanid"][0], 16) }, - assertFn: func(t *testing.T, trace string) { + assertFn: func(t *testing.T, traces ptrace.Traces) { t.Helper() - assert.Regexp(t, `("traceId":"00000000000000000000000000000001")`, trace) - assert.Regexp(t, `("parentSpanId":"0000000000000002")`, trace) + span := mainSpan(traces) + assert.Equal(t, "00000000000000000000000000000001", span.TraceID().String()) + assert.Equal(t, "0000000000000002", span.ParentSpanID().String()) }, }, { @@ -201,11 +209,12 @@ func TestTracing(t *testing.T) { assert.Equal(t, "1", headers["X-B3-Sampled"][0]) assert.Regexp(t, `(\w{16})`, headers["X-B3-Spanid"][0]) }, - assertFn: func(t *testing.T, trace string) { + assertFn: func(t *testing.T, traces ptrace.Traces) { t.Helper() - assert.Regexp(t, `("traceId":"\w{32}")`, trace) - assert.Regexp(t, `("parentSpanId":"")`, trace) + span := mainSpan(traces) + assert.Len(t, span.TraceID().String(), 32) + assert.Empty(t, span.ParentSpanID().String()) }, }, { @@ -231,11 +240,12 @@ func TestTracing(t *testing.T) { assert.Regexp(t, `(00000000000000000000000000000001:\w{16}:0:1)`, headers["Uber-Trace-Id"][0]) }, - assertFn: func(t *testing.T, trace string) { + assertFn: func(t *testing.T, traces ptrace.Traces) { t.Helper() - assert.Regexp(t, `("traceId":"00000000000000000000000000000001")`, trace) - assert.Regexp(t, `("parentSpanId":"\w{16}")`, trace) + span := mainSpan(traces) + assert.Equal(t, "00000000000000000000000000000001", span.TraceID().String()) + assert.Len(t, span.ParentSpanID().String(), 16) }, }, { @@ -246,11 +256,12 @@ func TestTracing(t *testing.T) { assert.Regexp(t, `(\w{32}:\w{16}:0:1)`, headers["Uber-Trace-Id"][0]) }, - assertFn: func(t *testing.T, trace string) { + assertFn: func(t *testing.T, traces ptrace.Traces) { t.Helper() - assert.Regexp(t, `("traceId":"\w{32}")`, trace) - assert.Regexp(t, `("parentSpanId":"\w{16}")`, trace) + span := mainSpan(traces) + assert.Len(t, span.TraceID().String(), 32) + assert.Empty(t, span.ParentSpanID().String()) }, }, { @@ -264,11 +275,12 @@ func TestTracing(t *testing.T) { assert.Regexp(t, `(Root=1-5759e988-bd862e3fe1be46a994272793;Parent=\w{16};Sampled=1)`, headers["X-Amzn-Trace-Id"][0]) }, - assertFn: func(t *testing.T, trace string) { + assertFn: func(t *testing.T, traces ptrace.Traces) { t.Helper() - assert.Regexp(t, `("traceId":"5759e988bd862e3fe1be46a994272793")`, trace) - assert.Regexp(t, `("parentSpanId":"\w{16}")`, trace) + span := mainSpan(traces) + assert.Equal(t, "5759e988bd862e3fe1be46a994272793", span.TraceID().String()) + assert.Len(t, span.ParentSpanID().String(), 16) }, }, { @@ -279,11 +291,12 @@ func TestTracing(t *testing.T) { assert.Regexp(t, `(Root=1-\w{8}-\w{24};Parent=\w{16};Sampled=1)`, headers["X-Amzn-Trace-Id"][0]) }, - assertFn: func(t *testing.T, trace string) { + assertFn: func(t *testing.T, traces ptrace.Traces) { t.Helper() - assert.Regexp(t, `("traceId":"\w{32}")`, trace) - assert.Regexp(t, `("parentSpanId":"\w{16}")`, trace) + span := mainSpan(traces) + assert.Len(t, span.TraceID().String(), 32) + assert.Empty(t, span.ParentSpanID().String()) }, }, { @@ -294,16 +307,17 @@ func TestTracing(t *testing.T) { assert.Empty(t, headers) }, - assertFn: func(t *testing.T, trace string) { + assertFn: func(t *testing.T, traces ptrace.Traces) { t.Helper() - assert.Regexp(t, `("traceId":"\w{32}")`, trace) - assert.Regexp(t, `("parentSpanId":"\w{16}")`, trace) + span := mainSpan(traces) + assert.Len(t, span.TraceID().String(), 32) + assert.Empty(t, span.ParentSpanID().String()) }, }, } - traceCh := make(chan string) + traceCh := make(chan ptrace.Traces) collector := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { gzr, err := gzip.NewReader(r.Body) require.NoError(t, err) @@ -315,10 +329,7 @@ func TestTracing(t *testing.T) { err = req.UnmarshalProto(body) require.NoError(t, err) - marshalledReq, err := json.Marshal(req) - require.NoError(t, err) - - traceCh <- string(marshalledReq) + traceCh <- req.Traces() })) t.Cleanup(collector.Close) @@ -344,14 +355,14 @@ func TestTracing(t *testing.T) { ServiceName: "traefik", SampleRate: 1.0, ResourceAttributes: test.resourceAttributes, - OTLP: &types.OTelTracing{ - HTTP: &types.OTelHTTP{ + OTLP: &otypes.OTelTracing{ + HTTP: &otypes.OTelHTTP{ Endpoint: collector.URL, }, }, } - tracer, closer, err := NewTracing(tracingConfig) + tracer, closer, err := NewTracing(t.Context(), tracingConfig) require.NoError(t, err) t.Cleanup(func() { _ = closer.Close() @@ -384,10 +395,10 @@ func TestTracing(t *testing.T) { case <-time.After(10 * time.Second): t.Error("Trace not exported") - case trace := <-traceCh: + case traces := <-traceCh: assert.Equal(t, http.StatusOK, rw.Code) if test.assertFn != nil { - test.assertFn(t, trace) + test.assertFn(t, traces) } } }) @@ -399,19 +410,95 @@ func TestTracing(t *testing.T) { func TestTracerProvider(t *testing.T) { t.Parallel() - otlpConfig := &types.OTelTracing{} + otlpConfig := &otypes.OTelTracing{} otlpConfig.SetDefaults() config := &static.Tracing{OTLP: otlpConfig} - tracer, closer, err := NewTracing(config) + tracer, closer, err := NewTracing(t.Context(), config) if err != nil { t.Fatal(err) } closer.Close() - _, span := tracer.Start(context.Background(), "test") + _, span := tracer.Start(t.Context(), "test") defer span.End() span.TracerProvider().Tracer("github.com/traefik/traefik") span.TracerProvider().Tracer("other") } + +// TestNewTracer_HeadersCanonicalization tests that NewTracer properly canonicalizes headers. +func TestNewTracer_HeadersCanonicalization(t *testing.T) { + testCases := []struct { + desc string + inputHeaders []string + expectedCanonicalHeaders []string + }{ + { + desc: "Empty headers", + inputHeaders: []string{}, + expectedCanonicalHeaders: []string{}, + }, + { + desc: "Already canonical headers", + inputHeaders: []string{"Content-Type", "User-Agent", "Accept-Encoding"}, + expectedCanonicalHeaders: []string{"Content-Type", "User-Agent", "Accept-Encoding"}, + }, + { + desc: "Lowercase headers", + inputHeaders: []string{"content-type", "user-agent", "accept-encoding"}, + expectedCanonicalHeaders: []string{"Content-Type", "User-Agent", "Accept-Encoding"}, + }, + { + desc: "Mixed case headers", + inputHeaders: []string{"CoNtEnT-tYpE", "uSeR-aGeNt", "aCcEpT-eNcOdInG"}, + expectedCanonicalHeaders: []string{"Content-Type", "User-Agent", "Accept-Encoding"}, + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + // Create a mock tracer using a no-op tracer from OpenTelemetry + mockTracer := noop.NewTracerProvider().Tracer("test") + + // Test capturedRequestHeaders + tracer := NewTracer(mockTracer, test.inputHeaders, nil, nil) + assert.Equal(t, test.expectedCanonicalHeaders, tracer.capturedRequestHeaders) + assert.Nil(t, tracer.capturedResponseHeaders) + + // Test capturedResponseHeaders + tracer = NewTracer(mockTracer, nil, test.inputHeaders, nil) + assert.Equal(t, test.expectedCanonicalHeaders, tracer.capturedResponseHeaders) + assert.Nil(t, tracer.capturedRequestHeaders) + }) + } +} + +// resourceAttributes extracts resource attributes as a map. +func resourceAttributes(traces ptrace.Traces) map[string]string { + attributes := make(map[string]string) + if traces.ResourceSpans().Len() > 0 { + resource := traces.ResourceSpans().At(0).Resource() + resource.Attributes().Range(func(k string, v pcommon.Value) bool { + if v.Type() == pcommon.ValueTypeStr { + attributes[k] = v.Str() + } + return true + }) + } + return attributes +} + +// mainSpan gets the main span from traces (assumes single span for testing). +func mainSpan(traces ptrace.Traces) ptrace.Span { + for _, resourceSpans := range traces.ResourceSpans().All() { + for _, scopeSpans := range resourceSpans.ScopeSpans().All() { + if scopeSpans.Spans().Len() > 0 { + return scopeSpans.Spans().At(0) + } + } + } + return ptrace.NewSpan() +} diff --git a/pkg/types/logs.go b/pkg/observability/types/logs.go similarity index 90% rename from pkg/types/logs.go rename to pkg/observability/types/logs.go index 87aef4e40..8bc108ddc 100644 --- a/pkg/types/logs.go +++ b/pkg/observability/types/logs.go @@ -7,13 +7,14 @@ import ( "net/url" "github.com/traefik/paerser/types" + ttypes "github.com/traefik/traefik/v3/pkg/types" "github.com/traefik/traefik/v3/pkg/version" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc" "go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp" otelsdk "go.opentelemetry.io/otel/sdk/log" "go.opentelemetry.io/otel/sdk/resource" - semconv "go.opentelemetry.io/otel/semconv/v1.27.0" + semconv "go.opentelemetry.io/otel/semconv/v1.37.0" "google.golang.org/grpc/credentials" "google.golang.org/grpc/encoding/gzip" ) @@ -58,7 +59,7 @@ func (l *TraefikLog) SetDefaults() { // AccessLog holds the configuration settings for the access logger (middlewares/accesslog). type AccessLog struct { FilePath string `description:"Access log file path. Stdout is used when omitted or empty." json:"filePath,omitempty" toml:"filePath,omitempty" yaml:"filePath,omitempty"` - Format string `description:"Access log format: json | common" json:"format,omitempty" toml:"format,omitempty" yaml:"format,omitempty" export:"true"` + Format string `description:"Access log format: json, common, or genericCLF" json:"format,omitempty" toml:"format,omitempty" yaml:"format,omitempty" export:"true"` Filters *AccessLogFilters `description:"Access log filters, used to keep only specific access logs." json:"filters,omitempty" toml:"filters,omitempty" yaml:"filters,omitempty" export:"true"` Fields *AccessLogFields `description:"AccessLogFields." json:"fields,omitempty" toml:"fields,omitempty" yaml:"fields,omitempty" export:"true"` BufferingSize int64 `description:"Number of access log lines to process in a buffered way." json:"bufferingSize,omitempty" toml:"bufferingSize,omitempty" yaml:"bufferingSize,omitempty" export:"true"` @@ -150,7 +151,7 @@ func checkFieldHeaderValue(value, defaultValue string) string { // OTelLog provides configuration settings for the open-telemetry logger. type OTelLog struct { - ServiceName string `description:"Set the name for this service." json:"serviceName,omitempty" toml:"serviceName,omitempty" yaml:"serviceName,omitempty" export:"true"` + ServiceName string `description:"Defines the service name resource attribute." json:"serviceName,omitempty" toml:"serviceName,omitempty" yaml:"serviceName,omitempty" export:"true"` ResourceAttributes map[string]string `description:"Defines additional resource attributes (key:value)." json:"resourceAttributes,omitempty" toml:"resourceAttributes,omitempty" yaml:"resourceAttributes,omitempty"` GRPC *OTelGRPC `description:"gRPC configuration for the OpenTelemetry collector." json:"grpc,omitempty" toml:"grpc,omitempty" yaml:"grpc,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` HTTP *OTelHTTP `description:"HTTP configuration for the OpenTelemetry collector." json:"http,omitempty" toml:"http,omitempty" yaml:"http,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` @@ -164,7 +165,7 @@ func (o *OTelLog) SetDefaults() { } // NewLoggerProvider creates a new OpenTelemetry logger provider. -func (o *OTelLog) NewLoggerProvider() (*otelsdk.LoggerProvider, error) { +func (o *OTelLog) NewLoggerProvider(ctx context.Context) (*otelsdk.LoggerProvider, error) { var ( err error exporter otelsdk.Exporter @@ -178,21 +179,27 @@ func (o *OTelLog) NewLoggerProvider() (*otelsdk.LoggerProvider, error) { return nil, fmt.Errorf("setting up exporter: %w", err) } - attr := []attribute.KeyValue{ - semconv.ServiceNameKey.String(o.ServiceName), - semconv.ServiceVersionKey.String(version.Version), - } - + var resAttrs []attribute.KeyValue for k, v := range o.ResourceAttributes { - attr = append(attr, attribute.String(k, v)) + resAttrs = append(resAttrs, attribute.String(k, v)) } - res, err := resource.New(context.Background(), - resource.WithAttributes(attr...), - resource.WithFromEnv(), + res, err := resource.New(ctx, + resource.WithContainer(), + resource.WithHost(), + resource.WithOS(), + resource.WithProcess(), resource.WithTelemetrySDK(), - resource.WithOSType(), - resource.WithProcessCommandArgs(), + resource.WithDetectors(ttypes.K8sAttributesDetector{}), + // The following order allows the user to override the service name and version, + // as well as any other attributes set by the above detectors. + resource.WithAttributes( + semconv.ServiceName(o.ServiceName), + semconv.ServiceVersion(version.Version), + ), + resource.WithAttributes(resAttrs...), + // Use the environment variables to allow overriding above resource attributes. + resource.WithFromEnv(), ) if err != nil { return nil, fmt.Errorf("building resource: %w", err) diff --git a/pkg/types/metrics.go b/pkg/observability/types/metrics.go similarity index 86% rename from pkg/types/metrics.go rename to pkg/observability/types/metrics.go index 8ca8dec68..331544513 100644 --- a/pkg/types/metrics.go +++ b/pkg/observability/types/metrics.go @@ -111,12 +111,13 @@ type OTLP struct { GRPC *OTelGRPC `description:"gRPC configuration for the OpenTelemetry collector." json:"grpc,omitempty" toml:"grpc,omitempty" yaml:"grpc,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` HTTP *OTelHTTP `description:"HTTP configuration for the OpenTelemetry collector." json:"http,omitempty" toml:"http,omitempty" yaml:"http,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` - 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"` - PushInterval types.Duration `description:"Period between calls to collect a checkpoint." json:"pushInterval,omitempty" toml:"pushInterval,omitempty" yaml:"pushInterval,omitempty" export:"true"` - ServiceName string `description:"OTEL service name to use." json:"serviceName,omitempty" toml:"serviceName,omitempty" yaml:"serviceName,omitempty" export:"true"` + 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"` + PushInterval types.Duration `description:"Period between calls to collect a checkpoint." json:"pushInterval,omitempty" toml:"pushInterval,omitempty" yaml:"pushInterval,omitempty" export:"true"` + ServiceName string `description:"Defines the service name resource attribute." json:"serviceName,omitempty" toml:"serviceName,omitempty" yaml:"serviceName,omitempty" export:"true"` + ResourceAttributes map[string]string `description:"Defines additional resource attributes (key:value)." json:"resourceAttributes,omitempty" toml:"resourceAttributes,omitempty" yaml:"resourceAttributes,omitempty" export:"true"` } // SetDefaults sets the default values. diff --git a/pkg/types/otel.go b/pkg/observability/types/otel.go similarity index 89% rename from pkg/types/otel.go rename to pkg/observability/types/otel.go index 281d83e1d..7e6f9cf28 100644 --- a/pkg/types/otel.go +++ b/pkg/observability/types/otel.go @@ -1,10 +1,12 @@ package types +import "github.com/traefik/traefik/v3/pkg/types" + // OTelGRPC provides configuration settings for the gRPC open-telemetry. type OTelGRPC struct { Endpoint string `description:"Sets the gRPC endpoint (host:port) of the collector." json:"endpoint,omitempty" toml:"endpoint,omitempty" yaml:"endpoint,omitempty"` Insecure bool `description:"Disables client transport security for the exporter." json:"insecure,omitempty" toml:"insecure,omitempty" yaml:"insecure,omitempty" export:"true"` - TLS *ClientTLS `description:"Defines client transport security parameters." json:"tls,omitempty" toml:"tls,omitempty" yaml:"tls,omitempty" export:"true"` + TLS *types.ClientTLS `description:"Defines client transport security parameters." json:"tls,omitempty" toml:"tls,omitempty" yaml:"tls,omitempty" export:"true"` Headers map[string]string `description:"Headers sent with payload." json:"headers,omitempty" toml:"headers,omitempty" yaml:"headers,omitempty"` } @@ -16,7 +18,7 @@ func (o *OTelGRPC) SetDefaults() { // OTelHTTP provides configuration settings for the HTTP open-telemetry. type OTelHTTP struct { Endpoint string `description:"Sets the HTTP endpoint (scheme://host:port/path) of the collector." json:"endpoint,omitempty" toml:"endpoint,omitempty" yaml:"endpoint,omitempty"` - TLS *ClientTLS `description:"Defines client transport security parameters." json:"tls,omitempty" toml:"tls,omitempty" yaml:"tls,omitempty" export:"true"` + TLS *types.ClientTLS `description:"Defines client transport security parameters." json:"tls,omitempty" toml:"tls,omitempty" yaml:"tls,omitempty" export:"true"` Headers map[string]string `description:"Headers sent with payload." json:"headers,omitempty" toml:"headers,omitempty" yaml:"headers,omitempty"` } diff --git a/pkg/types/tracing.go b/pkg/observability/types/tracing.go similarity index 77% rename from pkg/types/tracing.go rename to pkg/observability/types/tracing.go index c232ad37a..9f525f9c2 100644 --- a/pkg/types/tracing.go +++ b/pkg/observability/types/tracing.go @@ -9,6 +9,7 @@ import ( "time" "github.com/rs/zerolog/log" + ttypes "github.com/traefik/traefik/v3/pkg/types" "github.com/traefik/traefik/v3/pkg/version" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" @@ -17,12 +18,28 @@ import ( "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp" "go.opentelemetry.io/otel/sdk/resource" sdktrace "go.opentelemetry.io/otel/sdk/trace" - semconv "go.opentelemetry.io/otel/semconv/v1.27.0" + semconv "go.opentelemetry.io/otel/semconv/v1.37.0" "go.opentelemetry.io/otel/trace" "google.golang.org/grpc/credentials" "google.golang.org/grpc/encoding/gzip" ) +type TracingVerbosity string + +const ( + MinimalVerbosity TracingVerbosity = "minimal" + DetailedVerbosity TracingVerbosity = "detailed" +) + +func (v TracingVerbosity) Allows(verbosity TracingVerbosity) bool { + switch v { + case DetailedVerbosity: + return verbosity == DetailedVerbosity || verbosity == MinimalVerbosity + default: + return verbosity == MinimalVerbosity + } +} + // OTelTracing provides configuration settings for the open-telemetry tracer. type OTelTracing struct { GRPC *OTelGRPC `description:"gRPC configuration for the OpenTelemetry collector." json:"grpc,omitempty" toml:"grpc,omitempty" yaml:"grpc,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` @@ -36,7 +53,7 @@ func (c *OTelTracing) SetDefaults() { } // Setup sets up the tracer. -func (c *OTelTracing) Setup(serviceName string, sampleRate float64, resourceAttributes map[string]string) (trace.Tracer, io.Closer, error) { +func (c *OTelTracing) Setup(ctx context.Context, serviceName string, sampleRate float64, resourceAttributes map[string]string) (trace.Tracer, io.Closer, error) { var ( err error exporter *otlptrace.Exporter @@ -50,21 +67,27 @@ func (c *OTelTracing) Setup(serviceName string, sampleRate float64, resourceAttr return nil, nil, fmt.Errorf("setting up exporter: %w", err) } - attr := []attribute.KeyValue{ - semconv.ServiceNameKey.String(serviceName), - semconv.ServiceVersionKey.String(version.Version), - } - + var resAttrs []attribute.KeyValue for k, v := range resourceAttributes { - attr = append(attr, attribute.String(k, v)) + resAttrs = append(resAttrs, attribute.String(k, v)) } - res, err := resource.New(context.Background(), - resource.WithAttributes(attr...), - resource.WithFromEnv(), + res, err := resource.New(ctx, + resource.WithContainer(), + resource.WithHost(), + resource.WithOS(), + resource.WithProcess(), resource.WithTelemetrySDK(), - resource.WithOSType(), - resource.WithProcessCommandArgs(), + resource.WithDetectors(ttypes.K8sAttributesDetector{}), + // The following order allows the user to override the service name and version, + // as well as any other attributes set by the above detectors. + resource.WithAttributes( + semconv.ServiceName(serviceName), + semconv.ServiceVersion(version.Version), + ), + resource.WithAttributes(resAttrs...), + // Use the environment variables to allow overriding above resource attributes. + resource.WithFromEnv(), ) if err != nil { return nil, nil, fmt.Errorf("building resource: %w", err) diff --git a/pkg/observability/types/tracing_test.go b/pkg/observability/types/tracing_test.go new file mode 100644 index 000000000..77f764871 --- /dev/null +++ b/pkg/observability/types/tracing_test.go @@ -0,0 +1,72 @@ +package types + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestTracingVerbosity_Allows(t *testing.T) { + tests := []struct { + desc string + from TracingVerbosity + to TracingVerbosity + allows bool + }{ + { + desc: "minimal vs minimal", + from: MinimalVerbosity, + to: MinimalVerbosity, + allows: true, + }, + { + desc: "minimal vs detailed", + from: MinimalVerbosity, + to: DetailedVerbosity, + allows: false, + }, + { + desc: "detailed vs minimal", + from: DetailedVerbosity, + to: MinimalVerbosity, + allows: true, + }, + { + desc: "detailed vs detailed", + from: DetailedVerbosity, + to: DetailedVerbosity, + allows: true, + }, + { + desc: "unknown vs minimal", + from: TracingVerbosity("unknown"), + to: MinimalVerbosity, + allows: true, + }, + { + desc: "unknown vs detailed", + from: TracingVerbosity("unknown"), + to: DetailedVerbosity, + allows: false, + }, + { + desc: "minimal vs unknown", + from: MinimalVerbosity, + to: TracingVerbosity("unknown"), + allows: false, + }, + { + desc: "detailed vs unknown", + from: DetailedVerbosity, + to: TracingVerbosity("unknown"), + allows: false, + }, + } + + for _, test := range tests { + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + require.Equal(t, test.allows, test.from.Allows(test.to)) + }) + } +} diff --git a/pkg/plugins/builder.go b/pkg/plugins/builder.go index 9e47d134b..96a4bf21e 100644 --- a/pkg/plugins/builder.go +++ b/pkg/plugins/builder.go @@ -28,7 +28,7 @@ type Builder struct { } // NewBuilder creates a new Builder. -func NewBuilder(client *Client, plugins map[string]Descriptor, localPlugins map[string]LocalDescriptor) (*Builder, error) { +func NewBuilder(manager *Manager, plugins map[string]Descriptor, localPlugins map[string]LocalDescriptor) (*Builder, error) { ctx := context.Background() pb := &Builder{ @@ -37,9 +37,9 @@ func NewBuilder(client *Client, plugins map[string]Descriptor, localPlugins map[ } for pName, desc := range plugins { - manifest, err := client.ReadManifest(desc.ModuleName) + manifest, err := manager.ReadManifest(desc.ModuleName) if err != nil { - _ = client.ResetAll() + _ = manager.ResetAll() return nil, fmt.Errorf("%s: failed to read manifest: %w", desc.ModuleName, err) } @@ -52,7 +52,7 @@ func NewBuilder(client *Client, plugins map[string]Descriptor, localPlugins map[ switch manifest.Type { case typeMiddleware: - middleware, err := newMiddlewareBuilder(logCtx, client.GoPath(), manifest, desc.ModuleName, desc.Settings) + middleware, err := newMiddlewareBuilder(logCtx, manager.GoPath(), manifest, desc.ModuleName, desc.Settings) if err != nil { return nil, err } @@ -60,7 +60,7 @@ func NewBuilder(client *Client, plugins map[string]Descriptor, localPlugins map[ pb.middlewareBuilders[pName] = middleware case typeProvider: - pBuilder, err := newProviderBuilder(logCtx, manifest, client.GoPath()) + pBuilder, err := newProviderBuilder(logCtx, manifest, manager.GoPath(), desc.Settings) if err != nil { return nil, fmt.Errorf("%s: %w", desc.ModuleName, err) } @@ -95,7 +95,7 @@ func NewBuilder(client *Client, plugins map[string]Descriptor, localPlugins map[ pb.middlewareBuilders[pName] = middleware case typeProvider: - builder, err := newProviderBuilder(logCtx, manifest, localGoPath) + builder, err := newProviderBuilder(logCtx, manifest, localGoPath, desc.Settings) if err != nil { return nil, fmt.Errorf("%s: %w", desc.ModuleName, err) } @@ -139,7 +139,7 @@ func newMiddlewareBuilder(ctx context.Context, goPath string, manifest *Manifest return newWasmMiddlewareBuilder(goPath, moduleName, wasmPath, settings) case runtimeYaegi, "": - i, err := newInterpreter(ctx, goPath, manifest.Import) + i, err := newInterpreter(ctx, goPath, manifest, settings) if err != nil { return nil, fmt.Errorf("failed to create Yaegi interpreter: %w", err) } @@ -151,10 +151,10 @@ func newMiddlewareBuilder(ctx context.Context, goPath string, manifest *Manifest } } -func newProviderBuilder(ctx context.Context, manifest *Manifest, goPath string) (providerBuilder, error) { +func newProviderBuilder(ctx context.Context, manifest *Manifest, goPath string, settings Settings) (providerBuilder, error) { switch manifest.Runtime { case runtimeYaegi, "": - i, err := newInterpreter(ctx, goPath, manifest.Import) + i, err := newInterpreter(ctx, goPath, manifest, settings) if err != nil { return providerBuilder{}, err } diff --git a/pkg/plugins/client.go b/pkg/plugins/client.go deleted file mode 100644 index ac4e71bcf..000000000 --- a/pkg/plugins/client.go +++ /dev/null @@ -1,434 +0,0 @@ -package plugins - -import ( - zipa "archive/zip" - "context" - "crypto/sha256" - "encoding/hex" - "encoding/json" - "errors" - "fmt" - "io" - "net/http" - "net/url" - "os" - "path" - "path/filepath" - "strings" - "time" - - "github.com/hashicorp/go-retryablehttp" - "github.com/rs/zerolog/log" - "github.com/traefik/traefik/v3/pkg/logs" - "golang.org/x/mod/module" - "golang.org/x/mod/zip" - "gopkg.in/yaml.v3" -) - -const ( - sourcesFolder = "sources" - archivesFolder = "archives" - stateFilename = "state.json" - goPathSrc = "src" - pluginManifest = ".traefik.yml" -) - -const pluginsURL = "https://plugins.traefik.io/public/" - -const ( - hashHeader = "X-Plugin-Hash" -) - -// ClientOptions the options of a Traefik plugins client. -type ClientOptions struct { - Output string -} - -// Client a Traefik plugins client. -type Client struct { - HTTPClient *http.Client - baseURL *url.URL - - archives string - stateFile string - goPath string - sources string -} - -// NewClient creates a new Traefik plugins client. -func NewClient(opts ClientOptions) (*Client, error) { - baseURL, err := url.Parse(pluginsURL) - if err != nil { - return nil, err - } - - sourcesRootPath := filepath.Join(filepath.FromSlash(opts.Output), sourcesFolder) - err = resetDirectory(sourcesRootPath) - if err != nil { - return nil, err - } - - goPath, err := os.MkdirTemp(sourcesRootPath, "gop-*") - if err != nil { - return nil, fmt.Errorf("failed to create GoPath: %w", err) - } - - archivesPath := filepath.Join(filepath.FromSlash(opts.Output), archivesFolder) - err = os.MkdirAll(archivesPath, 0o755) - if err != nil { - return nil, fmt.Errorf("failed to create archives directory %s: %w", archivesPath, err) - } - - client := retryablehttp.NewClient() - client.Logger = logs.NewRetryableHTTPLogger(log.Logger) - client.HTTPClient = &http.Client{Timeout: 10 * time.Second} - client.RetryMax = 3 - - return &Client{ - HTTPClient: client.StandardClient(), - baseURL: baseURL, - - archives: archivesPath, - stateFile: filepath.Join(archivesPath, stateFilename), - - goPath: goPath, - sources: filepath.Join(goPath, goPathSrc), - }, nil -} - -// GoPath gets the plugins GoPath. -func (c *Client) GoPath() string { - return c.goPath -} - -// ReadManifest reads a plugin manifest. -func (c *Client) ReadManifest(moduleName string) (*Manifest, error) { - return ReadManifest(c.goPath, moduleName) -} - -// ReadManifest reads a plugin manifest. -func ReadManifest(goPath, moduleName string) (*Manifest, error) { - p := filepath.Join(goPath, goPathSrc, filepath.FromSlash(moduleName), pluginManifest) - - file, err := os.Open(p) - if err != nil { - return nil, fmt.Errorf("failed to open the plugin manifest %s: %w", p, err) - } - - defer func() { _ = file.Close() }() - - m := &Manifest{} - err = yaml.NewDecoder(file).Decode(m) - if err != nil { - return nil, fmt.Errorf("failed to decode the plugin manifest %s: %w", p, err) - } - - return m, nil -} - -// Download downloads a plugin archive. -func (c *Client) Download(ctx context.Context, pName, pVersion string) (string, error) { - filename := c.buildArchivePath(pName, pVersion) - - var hash string - _, err := os.Stat(filename) - if err != nil && !os.IsNotExist(err) { - return "", fmt.Errorf("failed to read archive %s: %w", filename, err) - } - - if err == nil { - hash, err = computeHash(filename) - if err != nil { - return "", fmt.Errorf("failed to compute hash: %w", err) - } - } - - endpoint, err := c.baseURL.Parse(path.Join(c.baseURL.Path, "download", pName, pVersion)) - if err != nil { - return "", fmt.Errorf("failed to parse endpoint URL: %w", err) - } - - req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint.String(), nil) - if err != nil { - return "", fmt.Errorf("failed to create request: %w", err) - } - - if hash != "" { - req.Header.Set(hashHeader, hash) - } - - resp, err := c.HTTPClient.Do(req) - if err != nil { - return "", fmt.Errorf("failed to call service: %w", err) - } - - defer func() { _ = resp.Body.Close() }() - - switch resp.StatusCode { - case http.StatusNotModified: - // noop - return hash, nil - - case http.StatusOK: - err = os.MkdirAll(filepath.Dir(filename), 0o755) - if err != nil { - return "", fmt.Errorf("failed to create directory: %w", err) - } - - var file *os.File - file, err = os.Create(filename) - if err != nil { - return "", fmt.Errorf("failed to create file %q: %w", filename, err) - } - - defer func() { _ = file.Close() }() - - _, err = io.Copy(file, resp.Body) - if err != nil { - return "", fmt.Errorf("failed to write response: %w", err) - } - - hash, err = computeHash(filename) - if err != nil { - return "", fmt.Errorf("failed to compute hash: %w", err) - } - - return hash, nil - - default: - data, _ := io.ReadAll(resp.Body) - return "", fmt.Errorf("error: %d: %s", resp.StatusCode, string(data)) - } -} - -// Check checks the plugin archive integrity. -func (c *Client) Check(ctx context.Context, pName, pVersion, hash string) error { - endpoint, err := c.baseURL.Parse(path.Join(c.baseURL.Path, "validate", pName, pVersion)) - if err != nil { - return fmt.Errorf("failed to parse endpoint URL: %w", err) - } - - req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint.String(), nil) - if err != nil { - return fmt.Errorf("failed to create request: %w", err) - } - - if hash != "" { - req.Header.Set(hashHeader, hash) - } - - resp, err := c.HTTPClient.Do(req) - if err != nil { - return fmt.Errorf("failed to call service: %w", err) - } - - defer func() { _ = resp.Body.Close() }() - - if resp.StatusCode == http.StatusOK { - return nil - } - - return errors.New("plugin integrity check failed") -} - -// Unzip unzip a plugin archive. -func (c *Client) Unzip(pName, pVersion string) error { - err := c.unzipModule(pName, pVersion) - if err == nil { - return nil - } - - return c.unzipArchive(pName, pVersion) -} - -func (c *Client) unzipModule(pName, pVersion string) error { - src := c.buildArchivePath(pName, pVersion) - dest := filepath.Join(c.sources, filepath.FromSlash(pName)) - - return zip.Unzip(dest, module.Version{Path: pName, Version: pVersion}, src) -} - -func (c *Client) unzipArchive(pName, pVersion string) error { - zipPath := c.buildArchivePath(pName, pVersion) - - archive, err := zipa.OpenReader(zipPath) - if err != nil { - return err - } - - defer func() { _ = archive.Close() }() - - dest := filepath.Join(c.sources, filepath.FromSlash(pName)) - - for _, f := range archive.File { - err = unzipFile(f, dest) - if err != nil { - return fmt.Errorf("unable to unzip %s: %w", f.Name, err) - } - } - - return nil -} - -func unzipFile(f *zipa.File, dest string) error { - rc, err := f.Open() - if err != nil { - return err - } - - defer func() { _ = rc.Close() }() - - pathParts := strings.SplitN(f.Name, "/", 2) - - var pp string - if len(pathParts) < 2 { - pp = pathParts[0] - } else { - pp = pathParts[1] - } - - p := filepath.Join(dest, pp) - - if f.FileInfo().IsDir() { - err = os.MkdirAll(p, f.Mode()) - if err != nil { - return fmt.Errorf("unable to create archive directory %s: %w", p, err) - } - - return nil - } - - err = os.MkdirAll(filepath.Dir(p), 0o750) - if err != nil { - return fmt.Errorf("unable to create archive directory %s for file %s: %w", filepath.Dir(p), p, err) - } - - elt, err := os.OpenFile(p, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode()) - if err != nil { - return err - } - - defer func() { _ = elt.Close() }() - - _, err = io.Copy(elt, rc) - if err != nil { - return err - } - - return nil -} - -// CleanArchives cleans plugins archives. -func (c *Client) CleanArchives(plugins map[string]Descriptor) error { - if _, err := os.Stat(c.stateFile); os.IsNotExist(err) { - return nil - } - - stateFile, err := os.Open(c.stateFile) - if err != nil { - return fmt.Errorf("failed to open state file %s: %w", c.stateFile, err) - } - - previous := make(map[string]string) - err = json.NewDecoder(stateFile).Decode(&previous) - if err != nil { - return fmt.Errorf("failed to decode state file %s: %w", c.stateFile, err) - } - - for pName, pVersion := range previous { - for _, desc := range plugins { - if desc.ModuleName == pName && desc.Version != pVersion { - archivePath := c.buildArchivePath(pName, pVersion) - if err = os.RemoveAll(archivePath); err != nil { - return fmt.Errorf("failed to remove archive %s: %w", archivePath, err) - } - } - } - } - - return nil -} - -// WriteState writes the plugins state files. -func (c *Client) WriteState(plugins map[string]Descriptor) error { - m := make(map[string]string) - - for _, descriptor := range plugins { - m[descriptor.ModuleName] = descriptor.Version - } - - mp, err := json.MarshalIndent(m, "", " ") - if err != nil { - return fmt.Errorf("unable to marshal plugin state: %w", err) - } - - return os.WriteFile(c.stateFile, mp, 0o600) -} - -// ResetAll resets all plugins related directories. -func (c *Client) ResetAll() error { - if c.goPath == "" { - return errors.New("goPath is empty") - } - - err := resetDirectory(filepath.Join(c.goPath, "..")) - if err != nil { - return fmt.Errorf("unable to reset plugins GoPath directory %s: %w", c.goPath, err) - } - - err = resetDirectory(c.archives) - if err != nil { - return fmt.Errorf("unable to reset plugins archives directory: %w", err) - } - - return nil -} - -func (c *Client) buildArchivePath(pName, pVersion string) string { - return filepath.Join(c.archives, filepath.FromSlash(pName), pVersion+".zip") -} - -func resetDirectory(dir string) error { - dirPath, err := filepath.Abs(dir) - if err != nil { - return fmt.Errorf("unable to get absolute path of %s: %w", dir, err) - } - - currentPath, err := os.Getwd() - if err != nil { - return fmt.Errorf("unable to get the current directory: %w", err) - } - - if strings.HasPrefix(currentPath, dirPath) { - return fmt.Errorf("cannot be deleted: the directory path %s is the parent of the current path %s", dirPath, currentPath) - } - - err = os.RemoveAll(dir) - if err != nil { - return fmt.Errorf("unable to remove directory %s: %w", dirPath, err) - } - - err = os.MkdirAll(dir, 0o755) - if err != nil { - return fmt.Errorf("unable to create directory %s: %w", dirPath, err) - } - - return nil -} - -func computeHash(filepath string) (string, error) { - file, err := os.Open(filepath) - if err != nil { - return "", err - } - - hash := sha256.New() - - if _, err := io.Copy(hash, file); err != nil { - return "", err - } - - sum := hash.Sum(nil) - - return hex.EncodeToString(sum), nil -} diff --git a/pkg/plugins/downloader.go b/pkg/plugins/downloader.go new file mode 100644 index 000000000..3dce1df4c --- /dev/null +++ b/pkg/plugins/downloader.go @@ -0,0 +1,160 @@ +package plugins + +import ( + "context" + "errors" + "fmt" + "io" + "net/http" + "net/url" + "os" + "path" + "path/filepath" +) + +// PluginDownloader defines the interface for downloading and validating plugins from remote sources. +type PluginDownloader interface { + // Download downloads a plugin archive and returns its hash. + Download(ctx context.Context, pName, pVersion string) (string, error) + // Check checks the plugin archive integrity against a known hash. + Check(ctx context.Context, pName, pVersion, hash string) error +} + +// RegistryDownloaderOptions holds configuration options for creating a RegistryDownloader. +type RegistryDownloaderOptions struct { + HTTPClient *http.Client + ArchivesPath string +} + +// RegistryDownloader implements PluginDownloader for HTTP-based plugin downloads. +type RegistryDownloader struct { + httpClient *http.Client + baseURL *url.URL + archives string +} + +// NewRegistryDownloader creates a new HTTP-based plugin downloader. +func NewRegistryDownloader(opts RegistryDownloaderOptions) (*RegistryDownloader, error) { + baseURL, err := url.Parse(pluginsURL) + if err != nil { + return nil, err + } + + httpClient := opts.HTTPClient + if httpClient == nil { + httpClient = http.DefaultClient + } + + return &RegistryDownloader{ + httpClient: httpClient, + baseURL: baseURL, + archives: opts.ArchivesPath, + }, nil +} + +// Download downloads a plugin archive. +func (d *RegistryDownloader) Download(ctx context.Context, pName, pVersion string) (string, error) { + filename := d.buildArchivePath(pName, pVersion) + + var hash string + _, err := os.Stat(filename) + if err != nil && !os.IsNotExist(err) { + return "", fmt.Errorf("failed to read archive %s: %w", filename, err) + } + + if err == nil { + hash, err = computeHash(filename) + if err != nil { + return "", fmt.Errorf("failed to compute hash: %w", err) + } + } + + endpoint, err := d.baseURL.Parse(path.Join(d.baseURL.Path, "download", pName, pVersion)) + if err != nil { + return "", fmt.Errorf("failed to parse endpoint URL: %w", err) + } + + req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint.String(), nil) + if err != nil { + return "", fmt.Errorf("failed to create request: %w", err) + } + + if hash != "" { + req.Header.Set(hashHeader, hash) + } + + resp, err := d.httpClient.Do(req) + if err != nil { + return "", fmt.Errorf("failed to call service: %w", err) + } + + defer func() { _ = resp.Body.Close() }() + + switch resp.StatusCode { + case http.StatusNotModified: + return hash, nil + case http.StatusOK: + err = os.MkdirAll(filepath.Dir(filename), 0o755) + if err != nil { + return "", fmt.Errorf("failed to create directory: %w", err) + } + + var file *os.File + file, err = os.Create(filename) + if err != nil { + return "", fmt.Errorf("failed to create file %q: %w", filename, err) + } + + defer func() { _ = file.Close() }() + + _, err = io.Copy(file, resp.Body) + if err != nil { + return "", fmt.Errorf("failed to write response: %w", err) + } + + hash, err = computeHash(filename) + if err != nil { + return "", fmt.Errorf("failed to compute hash: %w", err) + } + default: + data, _ := io.ReadAll(resp.Body) + return "", fmt.Errorf("error: %d: %s", resp.StatusCode, string(data)) + } + + return hash, nil +} + +// Check checks the plugin archive integrity. +func (d *RegistryDownloader) Check(ctx context.Context, pName, pVersion, hash string) error { + endpoint, err := d.baseURL.Parse(path.Join(d.baseURL.Path, "validate", pName, pVersion)) + if err != nil { + return fmt.Errorf("failed to parse endpoint URL: %w", err) + } + + req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint.String(), nil) + if err != nil { + return fmt.Errorf("failed to create request: %w", err) + } + + if hash != "" { + req.Header.Set(hashHeader, hash) + } + + resp, err := d.httpClient.Do(req) + if err != nil { + return fmt.Errorf("failed to call service: %w", err) + } + + defer func() { _ = resp.Body.Close() }() + + if resp.StatusCode == http.StatusOK { + return nil + } + + return errors.New("plugin integrity check failed") +} + +// buildArchivePath builds the path to a plugin archive file. +func (d *RegistryDownloader) buildArchivePath(pName, pVersion string) string { + return filepath.Join(d.archives, filepath.FromSlash(pName), pVersion+".zip") +} diff --git a/pkg/plugins/downloader_test.go b/pkg/plugins/downloader_test.go new file mode 100644 index 000000000..bcbd89424 --- /dev/null +++ b/pkg/plugins/downloader_test.go @@ -0,0 +1,159 @@ +package plugins + +import ( + "archive/zip" + "io" + "net/http" + "net/http/httptest" + "net/url" + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestHTTPPluginDownloader_Download(t *testing.T) { + tests := []struct { + name string + serverResponse func(w http.ResponseWriter, r *http.Request) + fileAlreadyExists bool + expectError bool + }{ + { + name: "successful download", + serverResponse: func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/zip") + w.WriteHeader(http.StatusOK) + + require.NoError(t, fillDummyZip(w)) + }, + }, + { + name: "not modified response", + serverResponse: func(w http.ResponseWriter, r *http.Request) { + http.Error(w, "", http.StatusNotModified) + }, + fileAlreadyExists: true, + }, + { + name: "server error", + serverResponse: func(w http.ResponseWriter, r *http.Request) { + http.Error(w, "internal server error", http.StatusInternalServerError) + }, + expectError: true, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(test.serverResponse)) + defer server.Close() + + tempDir := t.TempDir() + archivesPath := filepath.Join(tempDir, "archives") + + if test.fileAlreadyExists { + createDummyZip(t, archivesPath) + } + + baseURL, err := url.Parse(server.URL) + require.NoError(t, err) + + downloader := &RegistryDownloader{ + httpClient: server.Client(), + baseURL: baseURL, + archives: archivesPath, + } + + ctx := t.Context() + hash, err := downloader.Download(ctx, "test/plugin", "v1.0.0") + + if test.expectError { + assert.Error(t, err) + } else { + assert.NoError(t, err) + assert.NotEmpty(t, hash) + + // Check if archive file was created + archivePath := downloader.buildArchivePath("test/plugin", "v1.0.0") + assert.FileExists(t, archivePath) + } + }) + } +} + +func TestHTTPPluginDownloader_Check(t *testing.T) { + tests := []struct { + name string + serverResponse func(w http.ResponseWriter, r *http.Request) + expectError require.ErrorAssertionFunc + }{ + { + name: "successful check", + serverResponse: func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusOK) + }, + expectError: require.NoError, + }, + { + name: "failed check", + serverResponse: func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusBadRequest) + }, + expectError: require.Error, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(test.serverResponse)) + defer server.Close() + + tempDir := t.TempDir() + archivesPath := filepath.Join(tempDir, "archives") + + baseURL, err := url.Parse(server.URL) + require.NoError(t, err) + + downloader := &RegistryDownloader{ + httpClient: server.Client(), + baseURL: baseURL, + archives: archivesPath, + } + + ctx := t.Context() + + err = downloader.Check(ctx, "test/plugin", "v1.0.0", "testhash") + test.expectError(t, err) + }) + } +} + +func createDummyZip(t *testing.T, path string) { + t.Helper() + + err := os.MkdirAll(path+"/test/plugin/", 0o755) + require.NoError(t, err) + + zipfile, err := os.Create(path + "/test/plugin/v1.0.0.zip") + require.NoError(t, err) + defer zipfile.Close() + + err = fillDummyZip(zipfile) + require.NoError(t, err) +} + +func fillDummyZip(w io.Writer) error { + writer := zip.NewWriter(w) + + file, err := writer.Create("test.txt") + if err != nil { + return err + } + + _, _ = file.Write([]byte("test content")) + _ = writer.Close() + return nil +} diff --git a/pkg/plugins/manager.go b/pkg/plugins/manager.go new file mode 100644 index 000000000..2bbb9cbe7 --- /dev/null +++ b/pkg/plugins/manager.go @@ -0,0 +1,356 @@ +package plugins + +import ( + zipa "archive/zip" + "context" + "crypto/sha256" + "encoding/hex" + "encoding/json" + "errors" + "fmt" + "io" + "os" + "path/filepath" + "strings" + + "golang.org/x/mod/module" + "golang.org/x/mod/zip" + "gopkg.in/yaml.v3" +) + +const ( + sourcesFolder = "sources" + archivesFolder = "archives" + stateFilename = "state.json" + goPathSrc = "src" + pluginManifest = ".traefik.yml" +) + +const pluginsURL = "https://plugins.traefik.io/public/" + +const ( + hashHeader = "X-Plugin-Hash" +) + +// ManagerOptions the options of a Traefik plugins manager. +type ManagerOptions struct { + Output string +} + +// Manager manages Traefik plugins lifecycle operations including storage, and manifest reading. +type Manager struct { + downloader PluginDownloader + + stateFile string + + archives string + sources string + goPath string +} + +// NewManager creates a new Traefik plugins manager. +func NewManager(downloader PluginDownloader, opts ManagerOptions) (*Manager, error) { + sourcesRootPath := filepath.Join(filepath.FromSlash(opts.Output), sourcesFolder) + err := resetDirectory(sourcesRootPath) + if err != nil { + return nil, err + } + + goPath, err := os.MkdirTemp(sourcesRootPath, "gop-*") + if err != nil { + return nil, fmt.Errorf("failed to create GoPath: %w", err) + } + + archivesPath := filepath.Join(filepath.FromSlash(opts.Output), archivesFolder) + err = os.MkdirAll(archivesPath, 0o755) + if err != nil { + return nil, fmt.Errorf("failed to create archives directory %s: %w", archivesPath, err) + } + + return &Manager{ + downloader: downloader, + stateFile: filepath.Join(archivesPath, stateFilename), + archives: archivesPath, + sources: filepath.Join(goPath, goPathSrc), + goPath: goPath, + }, nil +} + +// InstallPlugin download and unzip the given plugin. +func (m *Manager) InstallPlugin(ctx context.Context, plugin Descriptor) error { + hash, err := m.downloader.Download(ctx, plugin.ModuleName, plugin.Version) + if err != nil { + return fmt.Errorf("unable to download plugin %s: %w", plugin.ModuleName, err) + } + + if plugin.Hash != "" { + if plugin.Hash != hash { + return fmt.Errorf("invalid hash for plugin %s, expected %s, got %s", plugin.ModuleName, plugin.Hash, hash) + } + } else { + err = m.downloader.Check(ctx, plugin.ModuleName, plugin.Version, hash) + if err != nil { + return fmt.Errorf("unable to check archive integrity of the plugin %s: %w", plugin.ModuleName, err) + } + } + + if err = m.unzip(plugin.ModuleName, plugin.Version); err != nil { + return fmt.Errorf("unable to unzip plugin %s: %w", plugin.ModuleName, err) + } + + return nil +} + +// GoPath gets the plugins GoPath. +func (m *Manager) GoPath() string { + return m.goPath +} + +// ReadManifest reads a plugin manifest. +func (m *Manager) ReadManifest(moduleName string) (*Manifest, error) { + return ReadManifest(m.goPath, moduleName) +} + +// ReadManifest reads a plugin manifest. +func ReadManifest(goPath, moduleName string) (*Manifest, error) { + p := filepath.Join(goPath, goPathSrc, filepath.FromSlash(moduleName), pluginManifest) + + file, err := os.Open(p) + if err != nil { + return nil, fmt.Errorf("failed to open the plugin manifest %s: %w", p, err) + } + + defer func() { _ = file.Close() }() + + m := &Manifest{} + err = yaml.NewDecoder(file).Decode(m) + if err != nil { + return nil, fmt.Errorf("failed to decode the plugin manifest %s: %w", p, err) + } + + return m, nil +} + +// CleanArchives cleans plugins archives. +func (m *Manager) CleanArchives(plugins map[string]Descriptor) error { + if _, err := os.Stat(m.stateFile); os.IsNotExist(err) { + return nil + } + + stateFile, err := os.Open(m.stateFile) + if err != nil { + return fmt.Errorf("failed to open state file %s: %w", m.stateFile, err) + } + + previous := make(map[string]string) + err = json.NewDecoder(stateFile).Decode(&previous) + if err != nil { + return fmt.Errorf("failed to decode state file %s: %w", m.stateFile, err) + } + + for pName, pVersion := range previous { + for _, desc := range plugins { + if desc.ModuleName == pName && desc.Version != pVersion { + archivePath := m.buildArchivePath(pName, pVersion) + if err = os.RemoveAll(archivePath); err != nil { + return fmt.Errorf("failed to remove archive %s: %w", archivePath, err) + } + } + } + } + + return nil +} + +// WriteState writes the plugins state files. +func (m *Manager) WriteState(plugins map[string]Descriptor) error { + state := make(map[string]string) + + for _, descriptor := range plugins { + state[descriptor.ModuleName] = descriptor.Version + } + + mp, err := json.MarshalIndent(state, "", " ") + if err != nil { + return fmt.Errorf("unable to marshal plugin state: %w", err) + } + + return os.WriteFile(m.stateFile, mp, 0o600) +} + +// ResetAll resets all plugins related directories. +func (m *Manager) ResetAll() error { + if m.goPath == "" { + return errors.New("goPath is empty") + } + + err := resetDirectory(filepath.Join(m.goPath, "..")) + if err != nil { + return fmt.Errorf("unable to reset plugins GoPath directory %s: %w", m.goPath, err) + } + + err = resetDirectory(m.archives) + if err != nil { + return fmt.Errorf("unable to reset plugins archives directory: %w", err) + } + + return nil +} + +func (m *Manager) unzip(pName, pVersion string) error { + err := m.unzipModule(pName, pVersion) + if err == nil { + return nil + } + + // Unzip as a generic archive if the module unzip fails. + // This is useful for plugins that have vendor directories or other structures. + // This is also useful for wasm plugins. + return m.unzipArchive(pName, pVersion) +} + +func (m *Manager) unzipModule(pName, pVersion string) error { + src := m.buildArchivePath(pName, pVersion) + dest := filepath.Join(m.sources, filepath.FromSlash(pName)) + + return zip.Unzip(dest, module.Version{Path: pName, Version: pVersion}, src) +} + +func (m *Manager) unzipArchive(pName, pVersion string) error { + zipPath := m.buildArchivePath(pName, pVersion) + + archive, err := zipa.OpenReader(zipPath) + if err != nil { + return err + } + + defer func() { _ = archive.Close() }() + + dest := filepath.Join(m.sources, filepath.FromSlash(pName)) + + for _, f := range archive.File { + err = m.unzipFile(f, dest) + if err != nil { + return fmt.Errorf("unable to unzip %s: %w", f.Name, err) + } + } + + return nil +} + +func (m *Manager) unzipFile(f *zipa.File, dest string) error { + rc, err := f.Open() + if err != nil { + return err + } + + defer func() { _ = rc.Close() }() + + // Split to discard the first part of the path when the archive is a Yaegi go plugin with vendoring. + // In this case the path starts with `[organization]-[project]-[release commit sha1]/`. + pathParts := strings.SplitN(f.Name, "/", 2) + var fileName string + if len(pathParts) < 2 { + fileName = pathParts[0] + } else { + fileName = pathParts[1] + } + + // Validate and sanitize the file path. + cleanName := filepath.Clean(fileName) + if strings.Contains(cleanName, "..") { + return fmt.Errorf("invalid file path in archive: %s", f.Name) + } + + filePath := filepath.Join(dest, cleanName) + absFilePath, err := filepath.Abs(filePath) + if err != nil { + return fmt.Errorf("resolving file path: %w", err) + } + + absDest, err := filepath.Abs(dest) + if err != nil { + return fmt.Errorf("resolving destination directory: %w", err) + } + + if !strings.HasPrefix(absFilePath, absDest) { + return fmt.Errorf("file path escapes destination directory: %s", absFilePath) + } + + if f.FileInfo().IsDir() { + err = os.MkdirAll(filePath, f.Mode()) + if err != nil { + return fmt.Errorf("unable to create archive directory %s: %w", filePath, err) + } + + return nil + } + + err = os.MkdirAll(filepath.Dir(filePath), 0o750) + if err != nil { + return fmt.Errorf("unable to create archive directory %s for file %s: %w", filepath.Dir(filePath), filePath, err) + } + + elt, err := os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode()) + if err != nil { + return err + } + + defer func() { _ = elt.Close() }() + + _, err = io.Copy(elt, rc) + if err != nil { + return err + } + + return nil +} + +func (m *Manager) buildArchivePath(pName, pVersion string) string { + return filepath.Join(m.archives, filepath.FromSlash(pName), pVersion+".zip") +} + +func resetDirectory(dir string) error { + dirPath, err := filepath.Abs(dir) + if err != nil { + return fmt.Errorf("unable to get absolute path of %s: %w", dir, err) + } + + currentPath, err := os.Getwd() + if err != nil { + return fmt.Errorf("unable to get the current directory: %w", err) + } + + if strings.HasPrefix(currentPath, dirPath) { + return fmt.Errorf("cannot be deleted: the directory path %s is the parent of the current path %s", dirPath, currentPath) + } + + err = os.RemoveAll(dir) + if err != nil { + return fmt.Errorf("unable to remove directory %s: %w", dirPath, err) + } + + err = os.MkdirAll(dir, 0o755) + if err != nil { + return fmt.Errorf("unable to create directory %s: %w", dirPath, err) + } + + return nil +} + +func computeHash(filepath string) (string, error) { + file, err := os.Open(filepath) + if err != nil { + return "", err + } + + hash := sha256.New() + + if _, err := io.Copy(hash, file); err != nil { + return "", err + } + + sum := hash.Sum(nil) + + return hex.EncodeToString(sum), nil +} diff --git a/pkg/plugins/manager_test.go b/pkg/plugins/manager_test.go new file mode 100644 index 000000000..5100b2a6b --- /dev/null +++ b/pkg/plugins/manager_test.go @@ -0,0 +1,341 @@ +package plugins + +import ( + zipa "archive/zip" + "context" + "encoding/json" + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "gopkg.in/yaml.v3" +) + +// mockDownloader is a test implementation of PluginDownloader +type mockDownloader struct { + downloadFunc func(ctx context.Context, pName, pVersion string) (string, error) + checkFunc func(ctx context.Context, pName, pVersion, hash string) error +} + +func (m *mockDownloader) Download(ctx context.Context, pName, pVersion string) (string, error) { + if m.downloadFunc != nil { + return m.downloadFunc(ctx, pName, pVersion) + } + return "mockhash", nil +} + +func (m *mockDownloader) Check(ctx context.Context, pName, pVersion, hash string) error { + if m.checkFunc != nil { + return m.checkFunc(ctx, pName, pVersion, hash) + } + return nil +} + +func TestPluginManager_ReadManifest(t *testing.T) { + tempDir := t.TempDir() + opts := ManagerOptions{Output: tempDir} + + downloader := &mockDownloader{} + manager, err := NewManager(downloader, opts) + require.NoError(t, err) + + moduleName := "github.com/test/plugin" + pluginPath := filepath.Join(manager.goPath, "src", moduleName) + err = os.MkdirAll(pluginPath, 0o755) + require.NoError(t, err) + + manifest := &Manifest{ + DisplayName: "Test Plugin", + Type: "middleware", + Import: "github.com/test/plugin", + Summary: "A test plugin", + TestData: map[string]interface{}{ + "test": "data", + }, + } + + manifestPath := filepath.Join(pluginPath, pluginManifest) + manifestData, err := yaml.Marshal(manifest) + require.NoError(t, err) + err = os.WriteFile(manifestPath, manifestData, 0o644) + require.NoError(t, err) + + readManifest, err := manager.ReadManifest(moduleName) + require.NoError(t, err) + assert.Equal(t, manifest.DisplayName, readManifest.DisplayName) + assert.Equal(t, manifest.Type, readManifest.Type) + assert.Equal(t, manifest.Import, readManifest.Import) + assert.Equal(t, manifest.Summary, readManifest.Summary) +} + +func TestPluginManager_ReadManifest_NotFound(t *testing.T) { + tempDir := t.TempDir() + opts := ManagerOptions{Output: tempDir} + + downloader := &mockDownloader{} + manager, err := NewManager(downloader, opts) + require.NoError(t, err) + + _, err = manager.ReadManifest("nonexistent/plugin") + assert.Error(t, err) +} + +func TestPluginManager_CleanArchives(t *testing.T) { + tempDir := t.TempDir() + opts := ManagerOptions{Output: tempDir} + + downloader := &mockDownloader{} + manager, err := NewManager(downloader, opts) + require.NoError(t, err) + + testPlugin1 := "test/plugin1" + testPlugin2 := "test/plugin2" + + archive1Dir := filepath.Join(manager.archives, "test", "plugin1") + archive2Dir := filepath.Join(manager.archives, "test", "plugin2") + err = os.MkdirAll(archive1Dir, 0o755) + require.NoError(t, err) + err = os.MkdirAll(archive2Dir, 0o755) + require.NoError(t, err) + + archive1Old := filepath.Join(archive1Dir, "v1.0.0.zip") + archive1New := filepath.Join(archive1Dir, "v2.0.0.zip") + archive2 := filepath.Join(archive2Dir, "v1.0.0.zip") + + err = os.WriteFile(archive1Old, []byte("old archive"), 0o644) + require.NoError(t, err) + err = os.WriteFile(archive1New, []byte("new archive"), 0o644) + require.NoError(t, err) + err = os.WriteFile(archive2, []byte("archive 2"), 0o644) + require.NoError(t, err) + + state := map[string]string{ + testPlugin1: "v1.0.0", + testPlugin2: "v1.0.0", + } + stateData, err := json.MarshalIndent(state, "", " ") + require.NoError(t, err) + err = os.WriteFile(manager.stateFile, stateData, 0o600) + require.NoError(t, err) + + currentPlugins := map[string]Descriptor{ + "plugin1": { + ModuleName: testPlugin1, + Version: "v2.0.0", + }, + "plugin2": { + ModuleName: testPlugin2, + Version: "v1.0.0", + }, + } + + err = manager.CleanArchives(currentPlugins) + require.NoError(t, err) + + assert.NoFileExists(t, archive1Old) + assert.FileExists(t, archive1New) + assert.FileExists(t, archive2) +} + +func TestPluginManager_WriteState(t *testing.T) { + tempDir := t.TempDir() + opts := ManagerOptions{Output: tempDir} + + downloader := &mockDownloader{} + manager, err := NewManager(downloader, opts) + require.NoError(t, err) + + plugins := map[string]Descriptor{ + "plugin1": { + ModuleName: "test/plugin1", + Version: "v1.0.0", + }, + "plugin2": { + ModuleName: "test/plugin2", + Version: "v2.0.0", + }, + } + + err = manager.WriteState(plugins) + require.NoError(t, err) + + assert.FileExists(t, manager.stateFile) + + data, err := os.ReadFile(manager.stateFile) + require.NoError(t, err) + + var state map[string]string + err = json.Unmarshal(data, &state) + require.NoError(t, err) + + expectedState := map[string]string{ + "test/plugin1": "v1.0.0", + "test/plugin2": "v2.0.0", + } + assert.Equal(t, expectedState, state) +} + +func TestPluginManager_ResetAll(t *testing.T) { + tempDir := t.TempDir() + opts := ManagerOptions{Output: tempDir} + + downloader := &mockDownloader{} + manager, err := NewManager(downloader, opts) + require.NoError(t, err) + + testFile := filepath.Join(manager.GoPath(), "test.txt") + err = os.WriteFile(testFile, []byte("test"), 0o644) + require.NoError(t, err) + + archiveFile := filepath.Join(manager.archives, "test.zip") + err = os.WriteFile(archiveFile, []byte("archive"), 0o644) + require.NoError(t, err) + + err = manager.ResetAll() + require.NoError(t, err) + + assert.DirExists(t, manager.archives) + assert.NoFileExists(t, testFile) + assert.NoFileExists(t, archiveFile) +} + +func TestPluginManager_InstallPlugin(t *testing.T) { + tests := []struct { + name string + plugin Descriptor + downloadFunc func(ctx context.Context, pName, pVersion string) (string, error) + checkFunc func(ctx context.Context, pName, pVersion, hash string) error + setupArchive func(t *testing.T, archivePath string) + expectError bool + errorMsg string + }{ + { + name: "successful installation", + plugin: Descriptor{ + ModuleName: "github.com/test/plugin", + Version: "v1.0.0", + Hash: "expected-hash", + }, + downloadFunc: func(ctx context.Context, pName, pVersion string) (string, error) { + return "expected-hash", nil + }, + checkFunc: func(ctx context.Context, pName, pVersion, hash string) error { + return nil + }, + setupArchive: func(t *testing.T, archivePath string) { + t.Helper() + + // Create a valid zip archive + err := os.MkdirAll(filepath.Dir(archivePath), 0o755) + require.NoError(t, err) + + file, err := os.Create(archivePath) + require.NoError(t, err) + defer file.Close() + + // Write a minimal zip file with a test file + writer := zipa.NewWriter(file) + defer writer.Close() + + fileWriter, err := writer.Create("test-module-v1.0.0/main.go") + require.NoError(t, err) + _, err = fileWriter.Write([]byte("package main\n\nfunc main() {}\n")) + require.NoError(t, err) + }, + expectError: false, + }, + { + name: "download error", + plugin: Descriptor{ + ModuleName: "github.com/test/plugin", + Version: "v1.0.0", + }, + downloadFunc: func(ctx context.Context, pName, pVersion string) (string, error) { + return "", assert.AnError + }, + expectError: true, + errorMsg: "unable to download plugin", + }, + { + name: "check error", + plugin: Descriptor{ + ModuleName: "github.com/test/plugin", + Version: "v1.0.0", + Hash: "expected-hash", + }, + downloadFunc: func(ctx context.Context, pName, pVersion string) (string, error) { + return "actual-hash", nil + }, + checkFunc: func(ctx context.Context, pName, pVersion, hash string) error { + return assert.AnError + }, + expectError: true, + errorMsg: "invalid hash for plugin", + }, + { + name: "unzip error - invalid archive", + plugin: Descriptor{ + ModuleName: "github.com/test/plugin", + Version: "v1.0.0", + }, + downloadFunc: func(ctx context.Context, pName, pVersion string) (string, error) { + return "test-hash", nil + }, + checkFunc: func(ctx context.Context, pName, pVersion, hash string) error { + return nil + }, + setupArchive: func(t *testing.T, archivePath string) { + t.Helper() + + // Create an invalid zip archive + err := os.MkdirAll(filepath.Dir(archivePath), 0o755) + require.NoError(t, err) + err = os.WriteFile(archivePath, []byte("invalid zip content"), 0o644) + require.NoError(t, err) + }, + expectError: true, + errorMsg: "unable to unzip plugin", + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + tempDir := t.TempDir() + opts := ManagerOptions{Output: tempDir} + + downloader := &mockDownloader{ + downloadFunc: test.downloadFunc, + checkFunc: test.checkFunc, + } + + manager, err := NewManager(downloader, opts) + require.NoError(t, err) + + // Setup archive if needed + if test.setupArchive != nil { + archivePath := filepath.Join(manager.archives, + filepath.FromSlash(test.plugin.ModuleName), + test.plugin.Version+".zip") + test.setupArchive(t, archivePath) + } + + ctx := t.Context() + err = manager.InstallPlugin(ctx, test.plugin) + + if test.expectError { + assert.Error(t, err) + if test.errorMsg != "" { + assert.Contains(t, err.Error(), test.errorMsg) + } + } else { + assert.NoError(t, err) + + // Verify that plugin sources were extracted + sourcePath := filepath.Join(manager.sources, filepath.FromSlash(test.plugin.ModuleName)) + assert.DirExists(t, sourcePath) + } + }) + } +} diff --git a/pkg/plugins/middlewarewasm.go b/pkg/plugins/middlewarewasm.go index c33858a9d..48d179426 100644 --- a/pkg/plugins/middlewarewasm.go +++ b/pkg/plugins/middlewarewasm.go @@ -14,8 +14,8 @@ import ( "github.com/http-wasm/http-wasm-host-go/handler" wasm "github.com/http-wasm/http-wasm-host-go/handler/nethttp" "github.com/tetratelabs/wazero" - "github.com/traefik/traefik/v3/pkg/logs" "github.com/traefik/traefik/v3/pkg/middlewares" + "github.com/traefik/traefik/v3/pkg/observability/logs" ) type wasmMiddlewareBuilder struct { diff --git a/pkg/plugins/middlewarewasm_test.go b/pkg/plugins/middlewarewasm_test.go index 38fee4bbe..faa33942f 100644 --- a/pkg/plugins/middlewarewasm_test.go +++ b/pkg/plugins/middlewarewasm_test.go @@ -1,7 +1,6 @@ package plugins import ( - "context" "net/http" "net/http/httptest" "os" @@ -21,7 +20,7 @@ func TestSettingsWithoutSocket(t *testing.T) { zerolog.SetGlobalLevel(zerolog.DebugLevel) - ctx := log.Logger.WithContext(context.Background()) + ctx := log.Logger.WithContext(t.Context()) t.Setenv("PLUGIN_TEST", "MY-TEST") t.Setenv("PLUGIN_TEST_B", "MY-TEST_B") diff --git a/pkg/plugins/middlewareyaegi.go b/pkg/plugins/middlewareyaegi.go index 452f8713b..590938044 100644 --- a/pkg/plugins/middlewareyaegi.go +++ b/pkg/plugins/middlewareyaegi.go @@ -2,6 +2,7 @@ package plugins import ( "context" + "errors" "fmt" "net/http" "os" @@ -12,9 +13,10 @@ import ( "github.com/mitchellh/mapstructure" "github.com/rs/zerolog" "github.com/rs/zerolog/log" - "github.com/traefik/traefik/v3/pkg/logs" + "github.com/traefik/traefik/v3/pkg/observability/logs" "github.com/traefik/yaegi/interp" "github.com/traefik/yaegi/stdlib" + "github.com/traefik/yaegi/stdlib/unsafe" ) type yaegiMiddlewareBuilder struct { @@ -119,7 +121,7 @@ func (m *YaegiMiddleware) NewHandler(ctx context.Context, next http.Handler) (ht return m.builder.newHandler(ctx, next, m.config, m.middlewareName) } -func newInterpreter(ctx context.Context, goPath string, manifestImport string) (*interp.Interpreter, error) { +func newInterpreter(ctx context.Context, goPath string, manifest *Manifest, settings Settings) (*interp.Interpreter, error) { i := interp.New(interp.Options{ GoPath: goPath, Env: os.Environ(), @@ -132,14 +134,25 @@ func newInterpreter(ctx context.Context, goPath string, manifestImport string) ( return nil, fmt.Errorf("failed to load symbols: %w", err) } + if manifest.UseUnsafe && !settings.UseUnsafe { + return nil, errors.New("this plugin uses unsafe import. If you want to use it, you need to allow useUnsafe in the settings") + } + + if settings.UseUnsafe && manifest.UseUnsafe { + err := i.Use(unsafe.Symbols) + if err != nil { + return nil, fmt.Errorf("failed to load unsafe symbols: %w", err) + } + } + err = i.Use(ppSymbols()) if err != nil { return nil, fmt.Errorf("failed to load provider symbols: %w", err) } - _, err = i.Eval(fmt.Sprintf(`import "%s"`, manifestImport)) + _, err = i.Eval(fmt.Sprintf(`import "%s"`, manifest.Import)) if err != nil { - return nil, fmt.Errorf("failed to import plugin code %q: %w", manifestImport, err) + return nil, fmt.Errorf("failed to import plugin code %q: %w", manifest.Import, err) } return i, nil diff --git a/pkg/plugins/plugins.go b/pkg/plugins/plugins.go index 367b6c46c..f7d543154 100644 --- a/pkg/plugins/plugins.go +++ b/pkg/plugins/plugins.go @@ -13,13 +13,13 @@ import ( const localGoPath = "./plugins-local/" // SetupRemotePlugins setup remote plugins environment. -func SetupRemotePlugins(client *Client, plugins map[string]Descriptor) error { +func SetupRemotePlugins(manager *Manager, plugins map[string]Descriptor) error { err := checkRemotePluginsConfiguration(plugins) if err != nil { return fmt.Errorf("invalid configuration: %w", err) } - err = client.CleanArchives(plugins) + err = manager.CleanArchives(plugins) if err != nil { return fmt.Errorf("unable to clean archives: %w", err) } @@ -27,35 +27,20 @@ func SetupRemotePlugins(client *Client, plugins map[string]Descriptor) error { ctx := context.Background() for pAlias, desc := range plugins { - log.Ctx(ctx).Debug().Msgf("Loading of plugin: %s: %s@%s", pAlias, desc.ModuleName, desc.Version) + log.Ctx(ctx).Debug().Msgf("Installing plugin: %s: %s@%s", pAlias, desc.ModuleName, desc.Version) - hash, err := client.Download(ctx, desc.ModuleName, desc.Version) - if err != nil { - _ = client.ResetAll() - return fmt.Errorf("unable to download plugin %s: %w", desc.ModuleName, err) - } - - err = client.Check(ctx, desc.ModuleName, desc.Version, hash) - if err != nil { - _ = client.ResetAll() - return fmt.Errorf("unable to check archive integrity of the plugin %s: %w", desc.ModuleName, err) + if err = manager.InstallPlugin(ctx, desc); err != nil { + _ = manager.ResetAll() + return fmt.Errorf("unable to install plugin %s: %w", pAlias, err) } } - err = client.WriteState(plugins) + err = manager.WriteState(plugins) if err != nil { - _ = client.ResetAll() + _ = manager.ResetAll() return fmt.Errorf("unable to write plugins state: %w", err) } - for _, desc := range plugins { - err = client.Unzip(desc.ModuleName, desc.Version) - if err != nil { - _ = client.ResetAll() - return fmt.Errorf("unable to unzip archive: %w", err) - } - } - return nil } diff --git a/pkg/plugins/providers.go b/pkg/plugins/providers.go index 5fdaf03ac..46c6df8cc 100644 --- a/pkg/plugins/providers.go +++ b/pkg/plugins/providers.go @@ -11,7 +11,7 @@ import ( "github.com/mitchellh/mapstructure" "github.com/rs/zerolog/log" "github.com/traefik/traefik/v3/pkg/config/dynamic" - "github.com/traefik/traefik/v3/pkg/logs" + "github.com/traefik/traefik/v3/pkg/observability/logs" "github.com/traefik/traefik/v3/pkg/provider" "github.com/traefik/traefik/v3/pkg/safe" "github.com/traefik/yaegi/interp" diff --git a/pkg/plugins/types.go b/pkg/plugins/types.go index 23254f7f7..75bb589b3 100644 --- a/pkg/plugins/types.go +++ b/pkg/plugins/types.go @@ -11,8 +11,9 @@ const ( ) type Settings struct { - Envs []string `description:"Environment variables to forward to the wasm guest." json:"envs,omitempty" toml:"envs,omitempty" yaml:"envs,omitempty"` - Mounts []string `description:"Directory to mount to the wasm guest." json:"mounts,omitempty" toml:"mounts,omitempty" yaml:"mounts,omitempty"` + Envs []string `description:"Environment variables to forward to the wasm guest." json:"envs,omitempty" toml:"envs,omitempty" yaml:"envs,omitempty"` + Mounts []string `description:"Directory to mount to the wasm guest." json:"mounts,omitempty" toml:"mounts,omitempty" yaml:"mounts,omitempty"` + UseUnsafe bool `description:"Allow the plugin to use unsafe package." json:"useUnsafe,omitempty" toml:"useUnsafe,omitempty" yaml:"useUnsafe,omitempty"` } // Descriptor The static part of a plugin configuration. @@ -23,6 +24,9 @@ type Descriptor struct { // Version (required) Version string `description:"plugin's version." json:"version,omitempty" toml:"version,omitempty" yaml:"version,omitempty" export:"true"` + // Hash (optional) + Hash string `description:"plugin's hash to validate'" json:"hash,omitempty" toml:"hash,omitempty" yaml:"hash,omitempty" export:"true"` + // Settings (optional) Settings Settings `description:"Plugin's settings (works only for wasm plugins)." json:"settings,omitempty" toml:"settings,omitempty" yaml:"settings,omitempty" export:"true"` } @@ -46,6 +50,7 @@ type Manifest struct { BasePkg string `yaml:"basePkg"` Compatibility string `yaml:"compatibility"` Summary string `yaml:"summary"` + UseUnsafe bool `yaml:"useUnsafe"` TestData map[string]interface{} `yaml:"testData"` } diff --git a/pkg/provider/acme/account.go b/pkg/provider/acme/account.go index c0a7458b3..434f58e3a 100644 --- a/pkg/provider/acme/account.go +++ b/pkg/provider/acme/account.go @@ -10,7 +10,7 @@ import ( "github.com/go-acme/lego/v4/certcrypto" "github.com/go-acme/lego/v4/registration" "github.com/rs/zerolog/log" - "github.com/traefik/traefik/v3/pkg/logs" + "github.com/traefik/traefik/v3/pkg/observability/logs" ) // Account is used to store lets encrypt registration info. diff --git a/pkg/provider/acme/challenge_http.go b/pkg/provider/acme/challenge_http.go index 37dfd0bc8..81830afaf 100644 --- a/pkg/provider/acme/challenge_http.go +++ b/pkg/provider/acme/challenge_http.go @@ -13,7 +13,7 @@ import ( "github.com/go-acme/lego/v4/challenge/http01" "github.com/rs/zerolog/log" - "github.com/traefik/traefik/v3/pkg/logs" + "github.com/traefik/traefik/v3/pkg/observability/logs" ) // ChallengeHTTP HTTP challenge provider implements challenge.Provider. diff --git a/pkg/provider/acme/challenge_tls.go b/pkg/provider/acme/challenge_tls.go index 995f23d18..9201ba8c4 100644 --- a/pkg/provider/acme/challenge_tls.go +++ b/pkg/provider/acme/challenge_tls.go @@ -9,7 +9,7 @@ import ( "github.com/go-acme/lego/v4/challenge/tlsalpn01" "github.com/rs/zerolog/log" "github.com/traefik/traefik/v3/pkg/config/dynamic" - "github.com/traefik/traefik/v3/pkg/logs" + "github.com/traefik/traefik/v3/pkg/observability/logs" "github.com/traefik/traefik/v3/pkg/safe" traefiktls "github.com/traefik/traefik/v3/pkg/tls" "github.com/traefik/traefik/v3/pkg/types" diff --git a/pkg/provider/acme/local_store.go b/pkg/provider/acme/local_store.go index 6e50fb153..dab258822 100644 --- a/pkg/provider/acme/local_store.go +++ b/pkg/provider/acme/local_store.go @@ -8,7 +8,7 @@ import ( "sync" "github.com/rs/zerolog/log" - "github.com/traefik/traefik/v3/pkg/logs" + "github.com/traefik/traefik/v3/pkg/observability/logs" "github.com/traefik/traefik/v3/pkg/safe" ) diff --git a/pkg/provider/acme/local_store_test.go b/pkg/provider/acme/local_store_test.go index a3c05fc58..15d592b9a 100644 --- a/pkg/provider/acme/local_store_test.go +++ b/pkg/provider/acme/local_store_test.go @@ -1,7 +1,6 @@ package acme import ( - "context" "fmt" "os" "path/filepath" @@ -47,7 +46,7 @@ func TestLocalStore_GetAccount(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { - s := NewLocalStore(test.filename, safe.NewPool(context.Background())) + s := NewLocalStore(test.filename, safe.NewPool(t.Context())) account, err := s.GetAccount("test") require.NoError(t, err) @@ -60,7 +59,7 @@ func TestLocalStore_GetAccount(t *testing.T) { func TestLocalStore_SaveAccount(t *testing.T) { acmeFile := filepath.Join(t.TempDir(), "acme.json") - s := NewLocalStore(acmeFile, safe.NewPool(context.Background())) + s := NewLocalStore(acmeFile, safe.NewPool(t.Context())) email := "some@email.com" diff --git a/pkg/provider/acme/provider.go b/pkg/provider/acme/provider.go index 9120ead27..c818ac7d2 100644 --- a/pkg/provider/acme/provider.go +++ b/pkg/provider/acme/provider.go @@ -11,6 +11,7 @@ import ( "net/url" "os" "reflect" + "slices" "sort" "strconv" "strings" @@ -20,15 +21,16 @@ import ( "github.com/go-acme/lego/v4/certificate" "github.com/go-acme/lego/v4/challenge" "github.com/go-acme/lego/v4/challenge/dns01" + "github.com/go-acme/lego/v4/challenge/http01" "github.com/go-acme/lego/v4/lego" "github.com/go-acme/lego/v4/providers/dns" "github.com/go-acme/lego/v4/registration" "github.com/rs/zerolog/log" ptypes "github.com/traefik/paerser/types" "github.com/traefik/traefik/v3/pkg/config/dynamic" - "github.com/traefik/traefik/v3/pkg/logs" httpmuxer "github.com/traefik/traefik/v3/pkg/muxer/http" tcpmuxer "github.com/traefik/traefik/v3/pkg/muxer/tcp" + "github.com/traefik/traefik/v3/pkg/observability/logs" "github.com/traefik/traefik/v3/pkg/safe" traefiktls "github.com/traefik/traefik/v3/pkg/tls" "github.com/traefik/traefik/v3/pkg/types" @@ -49,6 +51,9 @@ type Configuration struct { EAB *EAB `description:"External Account Binding to use." json:"eab,omitempty" toml:"eab,omitempty" yaml:"eab,omitempty"` CertificatesDuration int `description:"Certificates' duration in hours." json:"certificatesDuration,omitempty" toml:"certificatesDuration,omitempty" yaml:"certificatesDuration,omitempty" export:"true"` + ClientTimeout ptypes.Duration `description:"Timeout for a complete HTTP transaction with the ACME server." json:"clientTimeout,omitempty" toml:"clientTimeout,omitempty" yaml:"clientTimeout,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` + ClientResponseHeaderTimeout ptypes.Duration `description:"Timeout for receiving the response headers when communicating with the ACME server." json:"clientResponseHeaderTimeout,omitempty" toml:"clientResponseHeaderTimeout,omitempty" yaml:"clientResponseHeaderTimeout,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` + CACertificates []string `description:"Specify the paths to PEM encoded CA Certificates that can be used to authenticate an ACME server with an HTTPS certificate not issued by a CA in the system-wide trusted root list." json:"caCertificates,omitempty" toml:"caCertificates,omitempty" yaml:"caCertificates,omitempty"` CASystemCertPool bool `description:"Define if the certificates pool must use a copy of the system cert pool." json:"caSystemCertPool,omitempty" toml:"caSystemCertPool,omitempty" yaml:"caSystemCertPool,omitempty" export:"true"` CAServerName string `description:"Specify the CA server name that can be used to authenticate an ACME server with an HTTPS certificate not issued by a CA in the system-wide trusted root list." json:"caServerName,omitempty" toml:"caServerName,omitempty" yaml:"caServerName,omitempty" export:"true"` @@ -64,6 +69,8 @@ func (a *Configuration) SetDefaults() { a.Storage = "acme.json" a.KeyType = "RSA4096" a.CertificatesDuration = 3 * 30 * 24 // 90 Days + a.ClientTimeout = ptypes.Duration(2 * time.Minute) + a.ClientResponseHeaderTimeout = ptypes.Duration(30 * time.Second) } // CertAndStore allows mapping a TLS certificate to a TLS store. @@ -106,7 +113,8 @@ type Propagation struct { // HTTPChallenge contains HTTP challenge configuration. type HTTPChallenge struct { - EntryPoint string `description:"HTTP challenge EntryPoint" json:"entryPoint,omitempty" toml:"entryPoint,omitempty" yaml:"entryPoint,omitempty" export:"true"` + EntryPoint string `description:"HTTP challenge EntryPoint" json:"entryPoint,omitempty" toml:"entryPoint,omitempty" yaml:"entryPoint,omitempty" export:"true"` + Delay ptypes.Duration `description:"Delay between the creation of the challenge and the validation." json:"delay,omitempty" toml:"delay,omitempty" yaml:"delay,omitempty" export:"true"` } // TLSChallenge contains TLS challenge configuration. @@ -162,6 +170,10 @@ func (p *Provider) Init() error { return errors.New("cannot manage certificates with duration lower than 1 hour") } + if p.ClientTimeout < p.ClientResponseHeaderTimeout { + return errors.New("clientTimeout must be at least clientResponseHeaderTimeout") + } + var err error p.account, err = p.Store.GetAccount(p.ResolverName) if err != nil { @@ -351,7 +363,7 @@ func (p *Provider) getClient() (*lego.Client, error) { if p.HTTPChallenge != nil && len(p.HTTPChallenge.EntryPoint) > 0 { logger.Debug().Msg("Using HTTP Challenge provider.") - err = client.Challenge.SetHTTP01Provider(p.HTTPChallengeProvider) + err = client.Challenge.SetHTTP01Provider(p.HTTPChallengeProvider, http01.SetDelay(time.Duration(p.HTTPChallenge.Delay))) if err != nil { return nil, err } @@ -377,7 +389,7 @@ func (p *Provider) createHTTPClient() (*http.Client, error) { } return &http.Client{ - Timeout: 2 * time.Minute, + Timeout: time.Duration(p.ClientTimeout), Transport: &http.Transport{ Proxy: http.ProxyFromEnvironment, DialContext: (&net.Dialer{ @@ -385,7 +397,7 @@ func (p *Provider) createHTTPClient() (*http.Client, error) { KeepAlive: 30 * time.Second, }).DialContext, TLSHandshakeTimeout: 30 * time.Second, - ResponseHeaderTimeout: 30 * time.Second, + ResponseHeaderTimeout: time.Duration(p.ClientResponseHeaderTimeout), TLSClientConfig: tlsConfig, }, }, nil @@ -640,9 +652,8 @@ func (p *Provider) resolveDefaultCertificate(ctx context.Context, domains []stri p.resolvingDomainsMutex.Lock() - sortedDomains := make([]string, len(domains)) - copy(sortedDomains, domains) - sort.Strings(sortedDomains) + sortedDomains := slices.Clone(domains) + slices.Sort(sortedDomains) domainKey := strings.Join(sortedDomains, ",") diff --git a/pkg/provider/acme/provider_test.go b/pkg/provider/acme/provider_test.go index 020411bd2..477164e3a 100644 --- a/pkg/provider/acme/provider_test.go +++ b/pkg/provider/acme/provider_test.go @@ -1,7 +1,6 @@ package acme import ( - "context" "crypto/tls" "testing" "time" @@ -181,7 +180,7 @@ func TestGetUncheckedCertificates(t *testing.T) { resolvingDomains: test.resolvingDomains, } - domains := acmeProvider.getUncheckedDomains(context.Background(), test.domains, "default") + domains := acmeProvider.getUncheckedDomains(t.Context(), test.domains, "default") assert.Len(t, domains, len(test.expectedDomains), "Unexpected domains.") }) } @@ -245,7 +244,7 @@ func TestProvider_sanitizeDomains(t *testing.T) { acmeProvider := Provider{Configuration: &Configuration{DNSChallenge: test.dnsChallenge}} - domains, err := acmeProvider.sanitizeDomains(context.Background(), test.domains) + domains, err := acmeProvider.sanitizeDomains(t.Context(), test.domains) if len(test.expectedErr) > 0 { assert.EqualError(t, err, test.expectedErr, "Unexpected error.") @@ -424,7 +423,7 @@ func TestDeleteUnnecessaryDomains(t *testing.T) { t.Run(test.desc, func(t *testing.T) { t.Parallel() - domains := deleteUnnecessaryDomains(context.Background(), test.domains) + domains := deleteUnnecessaryDomains(t.Context(), test.domains) assert.Equal(t, test.expectedDomains, domains, "unexpected domain") }) } @@ -497,7 +496,7 @@ func TestIsAccountMatchingCaServer(t *testing.T) { t.Run(test.desc, func(t *testing.T) { t.Parallel() - result := isAccountMatchingCaServer(context.Background(), test.accountURI, test.serverURI) + result := isAccountMatchingCaServer(t.Context(), test.accountURI, test.serverURI) assert.Equal(t, test.expected, result) }) @@ -574,7 +573,7 @@ func TestInitAccount(t *testing.T) { acmeProvider := Provider{account: test.account, Configuration: &Configuration{Email: test.email, KeyType: test.keyType}} - actualAccount, err := acmeProvider.initAccount(context.Background()) + actualAccount, err := acmeProvider.initAccount(t.Context()) assert.NoError(t, err, "Init account in error") assert.Equal(t, test.expectedAccount.Email, actualAccount.Email, "unexpected email account") assert.Equal(t, test.expectedAccount.KeyType, actualAccount.KeyType, "unexpected keyType account") diff --git a/pkg/provider/aggregator/aggregator.go b/pkg/provider/aggregator/aggregator.go index 9f8d21308..fb2b1a6dc 100644 --- a/pkg/provider/aggregator/aggregator.go +++ b/pkg/provider/aggregator/aggregator.go @@ -92,6 +92,10 @@ func NewProviderAggregator(conf static.Providers) *ProviderAggregator { p.quietAddProvider(conf.KubernetesIngress) } + if conf.KubernetesIngressNGINX != nil { + p.quietAddProvider(conf.KubernetesIngressNGINX) + } + if conf.KubernetesCRD != nil { p.quietAddProvider(conf.KubernetesCRD) } diff --git a/pkg/provider/aggregator/aggregator_test.go b/pkg/provider/aggregator/aggregator_test.go index 4683a2480..895dc6539 100644 --- a/pkg/provider/aggregator/aggregator_test.go +++ b/pkg/provider/aggregator/aggregator_test.go @@ -1,7 +1,6 @@ package aggregator import ( - "context" "testing" "time" @@ -24,7 +23,7 @@ func TestProviderAggregator_Provide(t *testing.T) { cfgCh := make(chan dynamic.Message) errCh := make(chan error) - pool := safe.NewPool(context.Background()) + pool := safe.NewPool(t.Context()) t.Cleanup(pool.Stop) diff --git a/pkg/provider/configuration.go b/pkg/provider/configuration.go index bbb393425..be18de7ce 100644 --- a/pkg/provider/configuration.go +++ b/pkg/provider/configuration.go @@ -13,7 +13,7 @@ import ( "github.com/Masterminds/sprig/v3" "github.com/rs/zerolog/log" "github.com/traefik/traefik/v3/pkg/config/dynamic" - "github.com/traefik/traefik/v3/pkg/logs" + "github.com/traefik/traefik/v3/pkg/observability/logs" "github.com/traefik/traefik/v3/pkg/tls" ) @@ -396,8 +396,8 @@ func AddStore(configuration *dynamic.TLSConfiguration, storeName string, store t return reflect.DeepEqual(configuration.Stores[storeName], store) } -// MakeDefaultRuleTemplate creates the default rule template. -func MakeDefaultRuleTemplate(defaultRule string, funcMap template.FuncMap) (*template.Template, error) { +// MakeAnyTemplate creates a template with any name +func MakeAnyTemplate(name string, value string, funcMap template.FuncMap) (*template.Template, error) { defaultFuncMap := sprig.TxtFuncMap() defaultFuncMap["normalize"] = Normalize @@ -405,7 +405,12 @@ func MakeDefaultRuleTemplate(defaultRule string, funcMap template.FuncMap) (*tem defaultFuncMap[k] = fn } - return template.New("defaultRule").Funcs(defaultFuncMap).Parse(defaultRule) + return template.New(name).Funcs(defaultFuncMap).Parse(value) +} + +// MakeDefaultRuleTemplate creates the default rule template. +func MakeDefaultRuleTemplate(defaultRule string, funcMap template.FuncMap) (*template.Template, error) { + return MakeAnyTemplate("defaultRule", defaultRule, funcMap) } // BuildTCPRouterConfiguration builds a router configuration. diff --git a/pkg/provider/consulcatalog/config.go b/pkg/provider/consulcatalog/config.go index 513b85298..34c9a90dd 100644 --- a/pkg/provider/consulcatalog/config.go +++ b/pkg/provider/consulcatalog/config.go @@ -13,7 +13,7 @@ import ( "github.com/rs/zerolog/log" "github.com/traefik/traefik/v3/pkg/config/dynamic" "github.com/traefik/traefik/v3/pkg/config/label" - "github.com/traefik/traefik/v3/pkg/logs" + "github.com/traefik/traefik/v3/pkg/observability/logs" "github.com/traefik/traefik/v3/pkg/provider" "github.com/traefik/traefik/v3/pkg/provider/constraints" ) diff --git a/pkg/provider/consulcatalog/config_test.go b/pkg/provider/consulcatalog/config_test.go index 227dd4838..763179d20 100644 --- a/pkg/provider/consulcatalog/config_test.go +++ b/pkg/provider/consulcatalog/config_test.go @@ -1,7 +1,6 @@ package consulcatalog import ( - "context" "fmt" "testing" "time" @@ -323,7 +322,7 @@ func TestDefaultRule(t *testing.T) { require.NoError(t, err) } - configuration := p.buildConfiguration(context.Background(), test.items, nil) + configuration := p.buildConfiguration(t.Context(), test.items, nil) assert.Equal(t, test.expected, configuration) }) @@ -3602,7 +3601,7 @@ func Test_buildConfiguration(t *testing.T) { test.items[i].Tags = tags } - configuration := p.buildConfiguration(context.Background(), test.items, &connectCert{ + configuration := p.buildConfiguration(t.Context(), test.items, &connectCert{ root: []string{"root"}, leaf: keyPair{ cert: "cert", @@ -4120,7 +4119,7 @@ func TestFilterHealthStatuses(t *testing.T) { require.NoError(t, err) } - configuration := p.buildConfiguration(context.Background(), test.items, nil) + configuration := p.buildConfiguration(t.Context(), test.items, nil) assert.Equal(t, test.expected, configuration) }) diff --git a/pkg/provider/consulcatalog/consul_catalog.go b/pkg/provider/consulcatalog/consul_catalog.go index 55c24edb6..abdc8648e 100644 --- a/pkg/provider/consulcatalog/consul_catalog.go +++ b/pkg/provider/consulcatalog/consul_catalog.go @@ -17,7 +17,7 @@ import ( ptypes "github.com/traefik/paerser/types" "github.com/traefik/traefik/v3/pkg/config/dynamic" "github.com/traefik/traefik/v3/pkg/job" - "github.com/traefik/traefik/v3/pkg/logs" + "github.com/traefik/traefik/v3/pkg/observability/logs" "github.com/traefik/traefik/v3/pkg/provider" "github.com/traefik/traefik/v3/pkg/provider/constraints" "github.com/traefik/traefik/v3/pkg/safe" diff --git a/pkg/provider/docker/builder_test.go b/pkg/provider/docker/builder_test.go index 1213b7c01..c0b4f1d7f 100644 --- a/pkg/provider/docker/builder_test.go +++ b/pkg/provider/docker/builder_test.go @@ -1,22 +1,21 @@ package docker import ( - dockertypes "github.com/docker/docker/api/types" - "github.com/docker/docker/api/types/container" - "github.com/docker/docker/api/types/network" - "github.com/docker/docker/api/types/swarm" + containertypes "github.com/docker/docker/api/types/container" + networktypes "github.com/docker/docker/api/types/network" + swarmtypes "github.com/docker/docker/api/types/swarm" "github.com/docker/go-connections/nat" ) -func containerJSON(ops ...func(*dockertypes.ContainerJSON)) dockertypes.ContainerJSON { - c := &dockertypes.ContainerJSON{ - ContainerJSONBase: &dockertypes.ContainerJSONBase{ +func containerJSON(ops ...func(*containertypes.InspectResponse)) containertypes.InspectResponse { + c := &containertypes.InspectResponse{ + ContainerJSONBase: &containertypes.ContainerJSONBase{ Name: "fake", - HostConfig: &container.HostConfig{}, + HostConfig: &containertypes.HostConfig{}, }, - Config: &container.Config{}, - NetworkSettings: &dockertypes.NetworkSettings{ - NetworkSettingsBase: dockertypes.NetworkSettingsBase{}, + Config: &containertypes.Config{}, + NetworkSettings: &containertypes.NetworkSettings{ + NetworkSettingsBase: containertypes.NetworkSettingsBase{}, }, } @@ -27,58 +26,50 @@ func containerJSON(ops ...func(*dockertypes.ContainerJSON)) dockertypes.Containe return *c } -func name(name string) func(*dockertypes.ContainerJSON) { - return func(c *dockertypes.ContainerJSON) { +func name(name string) func(*containertypes.InspectResponse) { + return func(c *containertypes.InspectResponse) { c.ContainerJSONBase.Name = name } } -func networkMode(mode string) func(*dockertypes.ContainerJSON) { - return func(c *dockertypes.ContainerJSON) { - c.ContainerJSONBase.HostConfig.NetworkMode = container.NetworkMode(mode) +func networkMode(mode string) func(*containertypes.InspectResponse) { + return func(c *containertypes.InspectResponse) { + c.ContainerJSONBase.HostConfig.NetworkMode = containertypes.NetworkMode(mode) } } -func nodeIP(ip string) func(*dockertypes.ContainerJSON) { - return func(c *dockertypes.ContainerJSON) { - c.ContainerJSONBase.Node = &dockertypes.ContainerNode{ - IPAddress: ip, - } - } -} - -func ports(portMap nat.PortMap) func(*dockertypes.ContainerJSON) { - return func(c *dockertypes.ContainerJSON) { +func ports(portMap nat.PortMap) func(*containertypes.InspectResponse) { + return func(c *containertypes.InspectResponse) { c.NetworkSettings.NetworkSettingsBase.Ports = portMap } } -func withNetwork(name string, ops ...func(*network.EndpointSettings)) func(*dockertypes.ContainerJSON) { - return func(c *dockertypes.ContainerJSON) { +func withNetwork(name string, ops ...func(*networktypes.EndpointSettings)) func(*containertypes.InspectResponse) { + return func(c *containertypes.InspectResponse) { if c.NetworkSettings.Networks == nil { - c.NetworkSettings.Networks = map[string]*network.EndpointSettings{} + c.NetworkSettings.Networks = map[string]*networktypes.EndpointSettings{} } - c.NetworkSettings.Networks[name] = &network.EndpointSettings{} + c.NetworkSettings.Networks[name] = &networktypes.EndpointSettings{} for _, op := range ops { op(c.NetworkSettings.Networks[name]) } } } -func ipv4(ip string) func(*network.EndpointSettings) { - return func(s *network.EndpointSettings) { +func ipv4(ip string) func(*networktypes.EndpointSettings) { + return func(s *networktypes.EndpointSettings) { s.IPAddress = ip } } -func ipv6(ip string) func(*network.EndpointSettings) { - return func(s *network.EndpointSettings) { +func ipv6(ip string) func(*networktypes.EndpointSettings) { + return func(s *networktypes.EndpointSettings) { s.GlobalIPv6Address = ip } } -func swarmTask(id string, ops ...func(*swarm.Task)) swarm.Task { - task := &swarm.Task{ +func swarmTask(id string, ops ...func(*swarmtypes.Task)) swarmtypes.Task { + task := &swarmtypes.Task{ ID: id, } @@ -89,22 +80,28 @@ func swarmTask(id string, ops ...func(*swarm.Task)) swarm.Task { return *task } -func taskSlot(slot int) func(*swarm.Task) { - return func(task *swarm.Task) { +func taskSlot(slot int) func(*swarmtypes.Task) { + return func(task *swarmtypes.Task) { task.Slot = slot } } -func taskNetworkAttachment(id, name, driver string, addresses []string) func(*swarm.Task) { - return func(task *swarm.Task) { - task.NetworksAttachments = append(task.NetworksAttachments, swarm.NetworkAttachment{ - Network: swarm.Network{ +func taskNodeID(id string) func(*swarmtypes.Task) { + return func(task *swarmtypes.Task) { + task.NodeID = id + } +} + +func taskNetworkAttachment(id, name, driver string, addresses []string) func(*swarmtypes.Task) { + return func(task *swarmtypes.Task) { + task.NetworksAttachments = append(task.NetworksAttachments, swarmtypes.NetworkAttachment{ + Network: swarmtypes.Network{ ID: id, - Spec: swarm.NetworkSpec{ - Annotations: swarm.Annotations{ + Spec: swarmtypes.NetworkSpec{ + Annotations: swarmtypes.Annotations{ Name: name, }, - DriverConfiguration: &swarm.Driver{ + DriverConfiguration: &swarmtypes.Driver{ Name: driver, }, }, @@ -114,9 +111,9 @@ func taskNetworkAttachment(id, name, driver string, addresses []string) func(*sw } } -func taskStatus(ops ...func(*swarm.TaskStatus)) func(*swarm.Task) { - return func(task *swarm.Task) { - status := &swarm.TaskStatus{} +func taskStatus(ops ...func(*swarmtypes.TaskStatus)) func(*swarmtypes.Task) { + return func(task *swarmtypes.Task) { + status := &swarmtypes.TaskStatus{} for _, op := range ops { op(status) @@ -126,25 +123,25 @@ func taskStatus(ops ...func(*swarm.TaskStatus)) func(*swarm.Task) { } } -func taskState(state swarm.TaskState) func(*swarm.TaskStatus) { - return func(status *swarm.TaskStatus) { +func taskState(state swarmtypes.TaskState) func(*swarmtypes.TaskStatus) { + return func(status *swarmtypes.TaskStatus) { status.State = state } } -func taskContainerStatus(id string) func(*swarm.TaskStatus) { - return func(status *swarm.TaskStatus) { - status.ContainerStatus = &swarm.ContainerStatus{ +func taskContainerStatus(id string) func(*swarmtypes.TaskStatus) { + return func(status *swarmtypes.TaskStatus) { + status.ContainerStatus = &swarmtypes.ContainerStatus{ ContainerID: id, } } } -func swarmService(ops ...func(*swarm.Service)) swarm.Service { - service := &swarm.Service{ +func swarmService(ops ...func(*swarmtypes.Service)) swarmtypes.Service { + service := &swarmtypes.Service{ ID: "serviceID", - Spec: swarm.ServiceSpec{ - Annotations: swarm.Annotations{ + Spec: swarmtypes.ServiceSpec{ + Annotations: swarmtypes.Annotations{ Name: "defaultServiceName", }, }, @@ -157,21 +154,21 @@ func swarmService(ops ...func(*swarm.Service)) swarm.Service { return *service } -func serviceName(name string) func(service *swarm.Service) { - return func(service *swarm.Service) { +func serviceName(name string) func(service *swarmtypes.Service) { + return func(service *swarmtypes.Service) { service.Spec.Annotations.Name = name } } -func serviceLabels(labels map[string]string) func(service *swarm.Service) { - return func(service *swarm.Service) { +func serviceLabels(labels map[string]string) func(service *swarmtypes.Service) { + return func(service *swarmtypes.Service) { service.Spec.Annotations.Labels = labels } } -func withEndpoint(ops ...func(*swarm.Endpoint)) func(*swarm.Service) { - return func(service *swarm.Service) { - endpoint := &swarm.Endpoint{} +func withEndpoint(ops ...func(*swarmtypes.Endpoint)) func(*swarmtypes.Service) { + return func(service *swarmtypes.Service) { + endpoint := &swarmtypes.Endpoint{} for _, op := range ops { op(endpoint) @@ -181,21 +178,21 @@ func withEndpoint(ops ...func(*swarm.Endpoint)) func(*swarm.Service) { } } -func virtualIP(networkID, addr string) func(*swarm.Endpoint) { - return func(endpoint *swarm.Endpoint) { +func virtualIP(networkID, addr string) func(*swarmtypes.Endpoint) { + return func(endpoint *swarmtypes.Endpoint) { if endpoint.VirtualIPs == nil { - endpoint.VirtualIPs = []swarm.EndpointVirtualIP{} + endpoint.VirtualIPs = []swarmtypes.EndpointVirtualIP{} } - endpoint.VirtualIPs = append(endpoint.VirtualIPs, swarm.EndpointVirtualIP{ + endpoint.VirtualIPs = append(endpoint.VirtualIPs, swarmtypes.EndpointVirtualIP{ NetworkID: networkID, Addr: addr, }) } } -func withEndpointSpec(ops ...func(*swarm.EndpointSpec)) func(*swarm.Service) { - return func(service *swarm.Service) { - endpointSpec := &swarm.EndpointSpec{} +func withEndpointSpec(ops ...func(*swarmtypes.EndpointSpec)) func(*swarmtypes.Service) { + return func(service *swarmtypes.Service) { + endpointSpec := &swarmtypes.EndpointSpec{} for _, op := range ops { op(endpointSpec) @@ -205,10 +202,10 @@ func withEndpointSpec(ops ...func(*swarm.EndpointSpec)) func(*swarm.Service) { } } -func modeDNSRR(spec *swarm.EndpointSpec) { - spec.Mode = swarm.ResolutionModeDNSRR +func modeDNSRR(spec *swarmtypes.EndpointSpec) { + spec.Mode = swarmtypes.ResolutionModeDNSRR } -func modeVIP(spec *swarm.EndpointSpec) { - spec.Mode = swarm.ResolutionModeVIP +func modeVIP(spec *swarmtypes.EndpointSpec) { + spec.Mode = swarmtypes.ResolutionModeVIP } diff --git a/pkg/provider/docker/config.go b/pkg/provider/docker/config.go index b3bbe2411..0b322715a 100644 --- a/pkg/provider/docker/config.go +++ b/pkg/provider/docker/config.go @@ -1,19 +1,20 @@ package docker import ( + "bytes" "context" "errors" "fmt" "net" "strings" - dockertypes "github.com/docker/docker/api/types" + containertypes "github.com/docker/docker/api/types/container" "github.com/docker/docker/client" "github.com/docker/go-connections/nat" "github.com/rs/zerolog/log" "github.com/traefik/traefik/v3/pkg/config/dynamic" "github.com/traefik/traefik/v3/pkg/config/label" - "github.com/traefik/traefik/v3/pkg/logs" + "github.com/traefik/traefik/v3/pkg/observability/logs" "github.com/traefik/traefik/v3/pkg/provider" "github.com/traefik/traefik/v3/pkg/provider/constraints" ) @@ -28,15 +29,50 @@ func NewDynConfBuilder(configuration Shared, apiClient client.APIClient, swarm b return &DynConfBuilder{Shared: configuration, apiClient: apiClient, swarm: swarm} } +func (p *DynConfBuilder) applyLabels(container *dockerData, model interface{}) { + for _, item := range p.LabelMap { + value, ok := container.Labels[item.From] + if !ok { // label doesn't exist + continue + } + + writer := &bytes.Buffer{} + if err := item.toTpl.Execute(writer, model); err != nil { + continue // should never happen? + } + + to := writer.String() + if item.Value != nil { + container.Labels[to] = *item.Value + } else { + container.Labels[to] = value + } + } +} + func (p *DynConfBuilder) build(ctx context.Context, containersInspected []dockerData) *dynamic.Configuration { configurations := make(map[string]*dynamic.Configuration) for _, container := range containersInspected { + serviceName := getServiceName(container) + + model := struct { + Name string + ContainerName string + Labels *map[string]string + }{ + Name: serviceName, + ContainerName: strings.TrimPrefix(container.Name, "/"), + Labels: &container.Labels, + } + containerName := getServiceName(container) + "-" + container.ID logger := log.Ctx(ctx).With().Str("container", containerName).Logger() ctxContainer := logger.WithContext(ctx) + p.applyLabels(&container, model) + if !p.keepContainer(ctxContainer, container) { continue } @@ -83,18 +119,6 @@ func (p *DynConfBuilder) build(ctx context.Context, containersInspected []docker continue } - serviceName := getServiceName(container) - - model := struct { - Name string - ContainerName string - Labels map[string]string - }{ - Name: serviceName, - ContainerName: strings.TrimPrefix(container.Name, "/"), - Labels: container.Labels, - } - provider.BuildRouterConfiguration(ctx, confFromLabel.HTTP, serviceName, p.defaultRuleTpl, model) configurations[containerName] = confFromLabel @@ -114,7 +138,7 @@ func (p *DynConfBuilder) buildTCPServiceConfiguration(ctx context.Context, conta } } - if container.Health != "" && container.Health != dockertypes.Healthy { + if container.Health != "" && container.Health != containertypes.Healthy { return nil } @@ -138,7 +162,7 @@ func (p *DynConfBuilder) buildUDPServiceConfiguration(ctx context.Context, conta } } - if container.Health != "" && container.Health != dockertypes.Healthy { + if container.Health != "" && container.Health != containertypes.Healthy { return nil } @@ -164,7 +188,7 @@ func (p *DynConfBuilder) buildServiceConfiguration(ctx context.Context, containe } } - if container.Health != "" && container.Health != dockertypes.Healthy { + if container.Health != "" && container.Health != containertypes.Healthy { return nil } @@ -196,7 +220,7 @@ func (p *DynConfBuilder) keepContainer(ctx context.Context, container dockerData return false } - if !p.AllowEmptyServices && container.Health != "" && container.Health != dockertypes.Healthy { + if !p.AllowEmptyServices && container.Health != "" && container.Health != containertypes.Healthy { logger.Debug().Msg("Filtering unhealthy or starting container") return false } @@ -344,8 +368,8 @@ func (p *DynConfBuilder) getIPAddress(ctx context.Context, container dockerData) } if container.NetworkSettings.NetworkMode.IsHost() { - if container.Node != nil && container.Node.IPAddress != "" { - return container.Node.IPAddress + if container.NodeIP != "" { + return container.NodeIP } if host, err := net.LookupHost("host.docker.internal"); err == nil { return host[0] diff --git a/pkg/provider/docker/config_test.go b/pkg/provider/docker/config_test.go index c203fc18a..e54b980c3 100644 --- a/pkg/provider/docker/config_test.go +++ b/pkg/provider/docker/config_test.go @@ -1,14 +1,13 @@ package docker import ( - "context" "strconv" "testing" "time" - docker "github.com/docker/docker/api/types" - "github.com/docker/docker/api/types/network" - "github.com/docker/docker/api/types/swarm" + containertypes "github.com/docker/docker/api/types/container" + networktypes "github.com/docker/docker/api/types/network" + swarmtypes "github.com/docker/docker/api/types/swarm" "github.com/docker/go-connections/nat" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -421,7 +420,7 @@ func TestDynConfBuilder_DefaultRule(t *testing.T) { require.NoError(t, err) } - configuration := builder.build(context.Background(), test.containers) + configuration := builder.build(t.Context(), test.containers) assert.Equal(t, test.expected, configuration) }) @@ -2747,7 +2746,7 @@ func TestDynConfBuilder_build(t *testing.T) { { ServiceName: "Test", Name: "Test", - Health: docker.Unhealthy, + Health: containertypes.Unhealthy, }, }, expected: &dynamic.Configuration{ @@ -2779,7 +2778,7 @@ func TestDynConfBuilder_build(t *testing.T) { { ServiceName: "Test", Name: "Test", - Health: docker.Unhealthy, + Health: containertypes.Unhealthy, }, }, expected: &dynamic.Configuration{ @@ -2826,7 +2825,7 @@ func TestDynConfBuilder_build(t *testing.T) { { ServiceName: "Test", Name: "Test", - Health: docker.Unhealthy, + Health: containertypes.Unhealthy, Labels: map[string]string{ "traefik.tcp.routers.foo.rule": "HostSNI(`foo.bar`)", }, @@ -2861,7 +2860,7 @@ func TestDynConfBuilder_build(t *testing.T) { { ServiceName: "Test", Name: "Test", - Health: docker.Unhealthy, + Health: containertypes.Unhealthy, Labels: map[string]string{ "traefik.tcp.routers.foo.rule": "HostSNI(`foo.bar`)", }, @@ -2904,7 +2903,7 @@ func TestDynConfBuilder_build(t *testing.T) { { ServiceName: "Test", Name: "Test", - Health: docker.Unhealthy, + Health: containertypes.Unhealthy, Labels: map[string]string{ "traefik.udp.routers.foo": "true", }, @@ -2942,7 +2941,7 @@ func TestDynConfBuilder_build(t *testing.T) { Labels: map[string]string{ "traefik.udp.routers.foo": "true", }, - Health: docker.Unhealthy, + Health: containertypes.Unhealthy, }, }, expected: &dynamic.Configuration{ @@ -3929,7 +3928,7 @@ func TestDynConfBuilder_build(t *testing.T) { require.NoError(t, err) } - configuration := builder.build(context.Background(), test.containers) + configuration := builder.build(t.Context(), test.containers) assert.Equal(t, test.expected, configuration) }) @@ -3945,7 +3944,7 @@ func TestDynConfBuilder_getIPPort_docker(t *testing.T) { testCases := []struct { desc string - container docker.ContainerJSON + container containertypes.InspectResponse serverPort string expected expected }{ @@ -4101,7 +4100,7 @@ func TestDynConfBuilder_getIPPort_docker(t *testing.T) { UseBindPortIP: true, }, nil, false) - actualIP, actualPort, actualError := builder.getIPPort(context.Background(), dData, test.serverPort) + actualIP, actualPort, actualError := builder.getIPPort(t.Context(), dData, test.serverPort) if test.expected.error { require.Error(t, actualError) } else { @@ -4116,8 +4115,9 @@ func TestDynConfBuilder_getIPPort_docker(t *testing.T) { func TestDynConfBuilder_getIPAddress_docker(t *testing.T) { testCases := []struct { desc string - container docker.ContainerJSON + container containertypes.InspectResponse network string + nodeIP string expected string }{ { @@ -4193,10 +4193,10 @@ func TestDynConfBuilder_getIPAddress_docker(t *testing.T) { expected: "127.0.0.1", }, { - desc: "no network, no network label, mode host, node IP", + desc: "no network, no network label, mode host, node IP", + nodeIP: "10.0.0.5", container: containerJSON( networkMode("host"), - nodeIP("10.0.0.5"), ), expected: "10.0.0.5", }, @@ -4211,6 +4211,9 @@ func TestDynConfBuilder_getIPAddress_docker(t *testing.T) { } dData := parseContainer(test.container) + if test.nodeIP != "" { + dData.NodeIP = test.nodeIP + } dData.ExtraConf.Network = conf.Network if len(test.network) > 0 { @@ -4219,7 +4222,7 @@ func TestDynConfBuilder_getIPAddress_docker(t *testing.T) { builder := NewDynConfBuilder(conf, nil, false) - actual := builder.getIPAddress(context.Background(), dData) + actual := builder.getIPAddress(t.Context(), dData) assert.Equal(t, test.expected, actual) }) } @@ -4227,14 +4230,14 @@ func TestDynConfBuilder_getIPAddress_docker(t *testing.T) { func TestDynConfBuilder_getIPAddress_swarm(t *testing.T) { testCases := []struct { - service swarm.Service + service swarmtypes.Service expected string - networks map[string]*network.Summary + networks map[string]*networktypes.Summary }{ { service: swarmService(withEndpointSpec(modeDNSRR)), expected: "", - networks: map[string]*network.Summary{}, + networks: map[string]*networktypes.Summary{}, }, { service: swarmService( @@ -4242,7 +4245,7 @@ func TestDynConfBuilder_getIPAddress_swarm(t *testing.T) { withEndpoint(virtualIP("1", "10.11.12.13/24")), ), expected: "10.11.12.13", - networks: map[string]*network.Summary{ + networks: map[string]*networktypes.Summary{ "1": { Name: "foo", }, @@ -4260,7 +4263,7 @@ func TestDynConfBuilder_getIPAddress_swarm(t *testing.T) { ), ), expected: "10.11.12.99", - networks: map[string]*network.Summary{ + networks: map[string]*networktypes.Summary{ "1": { Name: "foonet", }, @@ -4278,11 +4281,11 @@ func TestDynConfBuilder_getIPAddress_swarm(t *testing.T) { var p SwarmProvider require.NoError(t, p.Init()) - dData, err := p.parseService(context.Background(), test.service, test.networks) + dData, err := p.parseService(t.Context(), test.service, test.networks) require.NoError(t, err) builder := NewDynConfBuilder(p.Shared, nil, false) - actual := builder.getIPAddress(context.Background(), dData) + actual := builder.getIPAddress(t.Context(), dData) assert.Equal(t, test.expected, actual) }) } diff --git a/pkg/provider/docker/data.go b/pkg/provider/docker/data.go index 509f2431d..4c42d396e 100644 --- a/pkg/provider/docker/data.go +++ b/pkg/provider/docker/data.go @@ -1,8 +1,7 @@ package docker import ( - dockertypes "github.com/docker/docker/api/types" - dockercontainertypes "github.com/docker/docker/api/types/container" + containertypes "github.com/docker/docker/api/types/container" "github.com/docker/go-connections/nat" ) @@ -14,13 +13,13 @@ type dockerData struct { Labels map[string]string // List of labels set to container or service NetworkSettings networkSettings Health string - Node *dockertypes.ContainerNode + NodeIP string // Only filled in Swarm mode. ExtraConf configuration } // NetworkSettings holds the networks data to the provider. type networkSettings struct { - NetworkMode dockercontainertypes.NetworkMode + NetworkMode containertypes.NetworkMode Ports nat.PortMap Networks map[string]*networkData } diff --git a/pkg/provider/docker/pdocker.go b/pkg/provider/docker/pdocker.go index 44ff3470d..5e88be51b 100644 --- a/pkg/provider/docker/pdocker.go +++ b/pkg/provider/docker/pdocker.go @@ -9,7 +9,6 @@ import ( "time" "github.com/cenkalti/backoff/v4" - dockertypes "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/container" eventtypes "github.com/docker/docker/api/types/events" "github.com/docker/docker/api/types/filters" @@ -17,7 +16,7 @@ import ( "github.com/rs/zerolog/log" "github.com/traefik/traefik/v3/pkg/config/dynamic" "github.com/traefik/traefik/v3/pkg/job" - "github.com/traefik/traefik/v3/pkg/logs" + "github.com/traefik/traefik/v3/pkg/observability/logs" "github.com/traefik/traefik/v3/pkg/provider" "github.com/traefik/traefik/v3/pkg/safe" ) @@ -49,8 +48,15 @@ func (p *Provider) Init() error { if err != nil { return fmt.Errorf("error while parsing default rule: %w", err) } - p.defaultRuleTpl = defaultRuleTpl + + for _, item := range p.LabelMap { + toTpl, err := provider.MakeAnyTemplate(item.From, item.To, nil) + if err != nil { + return fmt.Errorf("error while parsing label %v: %w", item.To, err) + } + item.toTpl = toTpl + } return nil } @@ -104,7 +110,7 @@ func (p *Provider) Provide(configurationChan chan<- dynamic.Message, pool *safe. if p.Watch { f := filters.NewArgs() f.Add("type", "container") - options := dockertypes.EventsOptions{ + options := eventtypes.ListOptions{ Filters: f, } diff --git a/pkg/provider/docker/pswarm.go b/pkg/provider/docker/pswarm.go index 07ad3cabc..bcb7ded48 100644 --- a/pkg/provider/docker/pswarm.go +++ b/pkg/provider/docker/pswarm.go @@ -8,8 +8,8 @@ import ( "time" "github.com/cenkalti/backoff/v4" - dockertypes "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/filters" + networktypes "github.com/docker/docker/api/types/network" swarmtypes "github.com/docker/docker/api/types/swarm" "github.com/docker/docker/api/types/versions" "github.com/docker/docker/client" @@ -17,7 +17,7 @@ import ( ptypes "github.com/traefik/paerser/types" "github.com/traefik/traefik/v3/pkg/config/dynamic" "github.com/traefik/traefik/v3/pkg/job" - "github.com/traefik/traefik/v3/pkg/logs" + "github.com/traefik/traefik/v3/pkg/observability/logs" "github.com/traefik/traefik/v3/pkg/provider" "github.com/traefik/traefik/v3/pkg/safe" ) @@ -161,7 +161,7 @@ func (p *SwarmProvider) Provide(configurationChan chan<- dynamic.Message, pool * func (p *SwarmProvider) listServices(ctx context.Context, dockerClient client.APIClient) ([]dockerData, error) { logger := log.Ctx(ctx) - serviceList, err := dockerClient.ServiceList(ctx, dockertypes.ServiceListOptions{}) + serviceList, err := dockerClient.ServiceList(ctx, swarmtypes.ServiceListOptions{}) if err != nil { return nil, err } @@ -179,13 +179,13 @@ func (p *SwarmProvider) listServices(ctx context.Context, dockerClient client.AP networkListArgs.Add("driver", "overlay") } - networkList, err := dockerClient.NetworkList(ctx, dockertypes.NetworkListOptions{Filters: networkListArgs}) + networkList, err := dockerClient.NetworkList(ctx, networktypes.ListOptions{Filters: networkListArgs}) if err != nil { logger.Debug().Err(err).Msg("Failed to network inspect on client for docker") return nil, err } - networkMap := make(map[string]*dockertypes.NetworkResource) + networkMap := make(map[string]*networktypes.Summary) for _, network := range networkList { networkMap[network.ID] = &network } @@ -218,7 +218,7 @@ func (p *SwarmProvider) listServices(ctx context.Context, dockerClient client.AP return dockerDataList, err } -func (p *SwarmProvider) parseService(ctx context.Context, service swarmtypes.Service, networkMap map[string]*dockertypes.NetworkResource) (dockerData, error) { +func (p *SwarmProvider) parseService(ctx context.Context, service swarmtypes.Service, networkMap map[string]*networktypes.Summary) (dockerData, error) { logger := log.Ctx(ctx) dData := dockerData{ @@ -267,13 +267,13 @@ func (p *SwarmProvider) parseService(ctx context.Context, service swarmtypes.Ser } func listTasks(ctx context.Context, dockerClient client.APIClient, serviceID string, - serviceDockerData dockerData, networkMap map[string]*dockertypes.NetworkResource, isGlobalSvc bool, + serviceDockerData dockerData, networkMap map[string]*networktypes.Summary, isGlobalSvc bool, ) ([]dockerData, error) { serviceIDFilter := filters.NewArgs() serviceIDFilter.Add("service", serviceID) serviceIDFilter.Add("desired-state", "running") - taskList, err := dockerClient.TaskList(ctx, dockertypes.TaskListOptions{Filters: serviceIDFilter}) + taskList, err := dockerClient.TaskList(ctx, swarmtypes.TaskListOptions{Filters: serviceIDFilter}) if err != nil { return nil, err } @@ -283,7 +283,11 @@ func listTasks(ctx context.Context, dockerClient client.APIClient, serviceID str if task.Status.State != swarmtypes.TaskStateRunning { continue } - dData := parseTasks(ctx, task, serviceDockerData, networkMap, isGlobalSvc) + dData, err := parseTasks(ctx, dockerClient, task, serviceDockerData, networkMap, isGlobalSvc) + if err != nil { + log.Ctx(ctx).Warn().Err(err).Msgf("Error while parsing task %s", getServiceName(dData)) + continue + } if len(dData.NetworkSettings.Networks) > 0 { dockerDataList = append(dockerDataList, dData) } @@ -291,9 +295,9 @@ func listTasks(ctx context.Context, dockerClient client.APIClient, serviceID str return dockerDataList, err } -func parseTasks(ctx context.Context, task swarmtypes.Task, serviceDockerData dockerData, - networkMap map[string]*dockertypes.NetworkResource, isGlobalSvc bool, -) dockerData { +func parseTasks(ctx context.Context, dockerClient client.APIClient, task swarmtypes.Task, serviceDockerData dockerData, + networkMap map[string]*networktypes.Summary, isGlobalSvc bool, +) (dockerData, error) { dData := dockerData{ ID: task.ID, ServiceName: serviceDockerData.Name, @@ -307,6 +311,14 @@ func parseTasks(ctx context.Context, task swarmtypes.Task, serviceDockerData doc dData.Name = serviceDockerData.Name + "." + task.ID } + if task.NodeID != "" { + node, _, err := dockerClient.NodeInspectWithRaw(ctx, task.NodeID) + if err != nil { + return dockerData{}, fmt.Errorf("inspecting node %s: %w", task.NodeID, err) + } + dData.NodeIP = node.Status.Addr + } + if task.NetworksAttachments != nil { dData.NetworkSettings.Networks = make(map[string]*networkData) for _, virtualIP := range task.NetworksAttachments { @@ -328,5 +340,5 @@ func parseTasks(ctx context.Context, task swarmtypes.Task, serviceDockerData doc } } } - return dData + return dData, nil } diff --git a/pkg/provider/docker/pswarm_mock_test.go b/pkg/provider/docker/pswarm_mock_test.go index 83eb61b3b..8793982b9 100644 --- a/pkg/provider/docker/pswarm_mock_test.go +++ b/pkg/provider/docker/pswarm_mock_test.go @@ -4,35 +4,47 @@ import ( "context" dockertypes "github.com/docker/docker/api/types" - "github.com/docker/docker/api/types/swarm" + containertypes "github.com/docker/docker/api/types/container" + networktypes "github.com/docker/docker/api/types/network" + swarmtypes "github.com/docker/docker/api/types/swarm" dockerclient "github.com/docker/docker/client" ) type fakeTasksClient struct { dockerclient.APIClient - tasks []swarm.Task - container dockertypes.ContainerJSON + tasks []swarmtypes.Task + container containertypes.InspectResponse err error } -func (c *fakeTasksClient) TaskList(ctx context.Context, options dockertypes.TaskListOptions) ([]swarm.Task, error) { +func (c *fakeTasksClient) TaskList(ctx context.Context, options swarmtypes.TaskListOptions) ([]swarmtypes.Task, error) { return c.tasks, c.err } -func (c *fakeTasksClient) ContainerInspect(ctx context.Context, container string) (dockertypes.ContainerJSON, error) { +func (c *fakeTasksClient) ContainerInspect(ctx context.Context, container string) (containertypes.InspectResponse, error) { return c.container, c.err } type fakeServicesClient struct { dockerclient.APIClient dockerVersion string - networks []dockertypes.NetworkResource - services []swarm.Service - tasks []swarm.Task + networks []networktypes.Summary + nodes []swarmtypes.Node + services []swarmtypes.Service + tasks []swarmtypes.Task err error } -func (c *fakeServicesClient) ServiceList(ctx context.Context, options dockertypes.ServiceListOptions) ([]swarm.Service, error) { +func (c *fakeServicesClient) NodeInspectWithRaw(ctx context.Context, nodeID string) (swarmtypes.Node, []byte, error) { + for _, node := range c.nodes { + if node.ID == nodeID { + return node, nil, nil + } + } + return swarmtypes.Node{}, nil, c.err +} + +func (c *fakeServicesClient) ServiceList(ctx context.Context, options swarmtypes.ServiceListOptions) ([]swarmtypes.Service, error) { return c.services, c.err } @@ -40,10 +52,10 @@ func (c *fakeServicesClient) ServerVersion(ctx context.Context) (dockertypes.Ver return dockertypes.Version{APIVersion: c.dockerVersion}, c.err } -func (c *fakeServicesClient) NetworkList(ctx context.Context, options dockertypes.NetworkListOptions) ([]dockertypes.NetworkResource, error) { +func (c *fakeServicesClient) NetworkList(ctx context.Context, options networktypes.ListOptions) ([]networktypes.Summary, error) { return c.networks, c.err } -func (c *fakeServicesClient) TaskList(ctx context.Context, options dockertypes.TaskListOptions) ([]swarm.Task, error) { +func (c *fakeServicesClient) TaskList(ctx context.Context, options swarmtypes.TaskListOptions) ([]swarmtypes.Task, error) { return c.tasks, c.err } diff --git a/pkg/provider/docker/pswarm_test.go b/pkg/provider/docker/pswarm_test.go index 7d6421037..f762f884d 100644 --- a/pkg/provider/docker/pswarm_test.go +++ b/pkg/provider/docker/pswarm_test.go @@ -1,37 +1,36 @@ package docker import ( - "context" "strconv" "testing" "time" - "github.com/docker/docker/api/types/network" - "github.com/docker/docker/api/types/swarm" + networktypes "github.com/docker/docker/api/types/network" + swarmtypes "github.com/docker/docker/api/types/swarm" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestListTasks(t *testing.T) { testCases := []struct { - service swarm.Service - tasks []swarm.Task + service swarmtypes.Service + tasks []swarmtypes.Task isGlobalSVC bool expectedTasks []string - networks map[string]*network.Summary + networks map[string]*networktypes.Summary }{ { service: swarmService(serviceName("container")), - tasks: []swarm.Task{ + tasks: []swarmtypes.Task{ swarmTask("id1", taskSlot(1), taskNetworkAttachment("1", "network1", "overlay", []string{"127.0.0.1"}), - taskStatus(taskState(swarm.TaskStateRunning)), + taskStatus(taskState(swarmtypes.TaskStateRunning)), ), swarmTask("id2", taskSlot(2), taskNetworkAttachment("1", "network1", "overlay", []string{"127.0.0.2"}), - taskStatus(taskState(swarm.TaskStatePending)), + taskStatus(taskState(swarmtypes.TaskStatePending)), ), swarmTask("id3", taskSlot(3), @@ -40,12 +39,12 @@ func TestListTasks(t *testing.T) { swarmTask("id4", taskSlot(4), taskNetworkAttachment("1", "network1", "overlay", []string{"127.0.0.4"}), - taskStatus(taskState(swarm.TaskStateRunning)), + taskStatus(taskState(swarmtypes.TaskStateRunning)), ), swarmTask("id5", taskSlot(5), taskNetworkAttachment("1", "network1", "overlay", []string{"127.0.0.5"}), - taskStatus(taskState(swarm.TaskStateFailed)), + taskStatus(taskState(swarmtypes.TaskStateFailed)), ), }, isGlobalSVC: false, @@ -53,7 +52,7 @@ func TestListTasks(t *testing.T) { "container.1", "container.4", }, - networks: map[string]*network.Summary{ + networks: map[string]*networktypes.Summary{ "1": { Name: "foo", }, @@ -68,11 +67,11 @@ func TestListTasks(t *testing.T) { var p SwarmProvider require.NoError(t, p.Init()) - dockerData, err := p.parseService(context.Background(), test.service, test.networks) + dockerData, err := p.parseService(t.Context(), test.service, test.networks) require.NoError(t, err) dockerClient := &fakeTasksClient{tasks: test.tasks} - taskDockerData, _ := listTasks(context.Background(), dockerClient, test.service.ID, dockerData, test.networks, test.isGlobalSVC) + taskDockerData, _ := listTasks(t.Context(), dockerClient, test.service.ID, dockerData, test.networks, test.isGlobalSVC) if len(test.expectedTasks) != len(taskDockerData) { t.Errorf("expected tasks %v, got %v", test.expectedTasks, taskDockerData) @@ -90,15 +89,15 @@ func TestListTasks(t *testing.T) { func TestSwarmProvider_listServices(t *testing.T) { testCases := []struct { desc string - services []swarm.Service - tasks []swarm.Task + services []swarmtypes.Service + tasks []swarmtypes.Task dockerVersion string - networks []network.Summary + networks []networktypes.Summary expectedServices []string }{ { desc: "Should return no service due to no networks defined", - services: []swarm.Service{ + services: []swarmtypes.Service{ swarmService( serviceName("service1"), serviceLabels(map[string]string{ @@ -119,12 +118,12 @@ func TestSwarmProvider_listServices(t *testing.T) { withEndpointSpec(modeDNSRR)), }, dockerVersion: "1.30", - networks: []network.Summary{}, + networks: []networktypes.Summary{}, expectedServices: []string{}, }, { desc: "Should return only service1", - services: []swarm.Service{ + services: []swarmtypes.Service{ swarmService( serviceName("service1"), serviceLabels(map[string]string{ @@ -145,7 +144,7 @@ func TestSwarmProvider_listServices(t *testing.T) { withEndpointSpec(modeDNSRR)), }, dockerVersion: "1.30", - networks: []network.Summary{ + networks: []networktypes.Summary{ { Name: "network_name", ID: "yk6l57rfwizjzxxzftn4amaot", @@ -157,8 +156,8 @@ func TestSwarmProvider_listServices(t *testing.T) { Ingress: false, ConfigOnly: false, Options: map[string]string{ - "com.docker.network.driver.overlay.vxlanid_list": "4098", - "com.docker.network.enable_ipv6": "false", + "com.docker.networktypes.driver.overlay.vxlanid_list": "4098", + "com.docker.networktypes.enable_ipv6": "false", }, Labels: map[string]string{ "com.docker.stack.namespace": "test", @@ -171,7 +170,7 @@ func TestSwarmProvider_listServices(t *testing.T) { }, { desc: "Should return service1 and service2", - services: []swarm.Service{ + services: []swarmtypes.Service{ swarmService( serviceName("service1"), serviceLabels(map[string]string{ @@ -189,18 +188,18 @@ func TestSwarmProvider_listServices(t *testing.T) { }), withEndpointSpec(modeDNSRR)), }, - tasks: []swarm.Task{ + tasks: []swarmtypes.Task{ swarmTask("id1", taskNetworkAttachment("yk6l57rfwizjzxxzftn4amaot", "network_name", "overlay", []string{"127.0.0.1"}), - taskStatus(taskState(swarm.TaskStateRunning)), + taskStatus(taskState(swarmtypes.TaskStateRunning)), ), swarmTask("id2", taskNetworkAttachment("yk6l57rfwizjzxxzftn4amaot", "network_name", "overlay", []string{"127.0.0.1"}), - taskStatus(taskState(swarm.TaskStateRunning)), + taskStatus(taskState(swarmtypes.TaskStateRunning)), ), }, dockerVersion: "1.30", - networks: []network.Summary{ + networks: []networktypes.Summary{ { Name: "network_name", ID: "yk6l57rfwizjzxxzftn4amaot", @@ -212,8 +211,8 @@ func TestSwarmProvider_listServices(t *testing.T) { Ingress: false, ConfigOnly: false, Options: map[string]string{ - "com.docker.network.driver.overlay.vxlanid_list": "4098", - "com.docker.network.enable_ipv6": "false", + "com.docker.networktypes.driver.overlay.vxlanid_list": "4098", + "com.docker.networktypes.enable_ipv6": "false", }, Labels: map[string]string{ "com.docker.stack.namespace": "test", @@ -238,7 +237,7 @@ func TestSwarmProvider_listServices(t *testing.T) { var p SwarmProvider require.NoError(t, p.Init()) - serviceDockerData, err := p.listServices(context.Background(), dockerClient) + serviceDockerData, err := p.listServices(t.Context(), dockerClient) assert.NoError(t, err) assert.Len(t, serviceDockerData, len(test.expectedServices)) @@ -254,15 +253,16 @@ func TestSwarmProvider_listServices(t *testing.T) { func TestSwarmProvider_parseService_task(t *testing.T) { testCases := []struct { - service swarm.Service - tasks []swarm.Task + service swarmtypes.Service + tasks []swarmtypes.Task + nodes []swarmtypes.Node isGlobalSVC bool expected map[string]dockerData - networks map[string]*network.Summary + networks map[string]*networktypes.Summary }{ { service: swarmService(serviceName("container")), - tasks: []swarm.Task{ + tasks: []swarmtypes.Task{ swarmTask("id1", taskSlot(1)), swarmTask("id2", taskSlot(2)), swarmTask("id3", taskSlot(3)), @@ -279,7 +279,7 @@ func TestSwarmProvider_parseService_task(t *testing.T) { Name: "container.3", }, }, - networks: map[string]*network.Summary{ + networks: map[string]*networktypes.Summary{ "1": { Name: "foo", }, @@ -287,7 +287,7 @@ func TestSwarmProvider_parseService_task(t *testing.T) { }, { service: swarmService(serviceName("container")), - tasks: []swarm.Task{ + tasks: []swarmtypes.Task{ swarmTask("id1"), swarmTask("id2"), swarmTask("id3"), @@ -304,7 +304,7 @@ func TestSwarmProvider_parseService_task(t *testing.T) { Name: "container.id3", }, }, - networks: map[string]*network.Summary{ + networks: map[string]*networktypes.Summary{ "1": { Name: "foo", }, @@ -318,12 +318,12 @@ func TestSwarmProvider_parseService_task(t *testing.T) { virtualIP("1", ""), ), ), - tasks: []swarm.Task{ + tasks: []swarmtypes.Task{ swarmTask( "id1", taskNetworkAttachment("1", "vlan", "macvlan", []string{"127.0.0.1"}), taskStatus( - taskState(swarm.TaskStateRunning), + taskState(swarmtypes.TaskStateRunning), taskContainerStatus("c1"), ), ), @@ -342,12 +342,40 @@ func TestSwarmProvider_parseService_task(t *testing.T) { }, }, }, - networks: map[string]*network.Summary{ + networks: map[string]*networktypes.Summary{ "1": { Name: "vlan", }, }, }, + { + service: swarmService(serviceName("container")), + tasks: []swarmtypes.Task{ + swarmTask("id1", + taskSlot(1), + taskNodeID("id1"), + ), + }, + nodes: []swarmtypes.Node{ + { + ID: "id1", + Status: swarmtypes.NodeStatus{ + Addr: "10.11.12.13", + }, + }, + }, + expected: map[string]dockerData{ + "id1": { + Name: "container.1", + NodeIP: "10.11.12.13", + }, + }, + networks: map[string]*networktypes.Summary{ + "1": { + Name: "foo", + }, + }, + }, } for caseID, test := range testCases { @@ -357,13 +385,21 @@ func TestSwarmProvider_parseService_task(t *testing.T) { var p SwarmProvider require.NoError(t, p.Init()) - dData, err := p.parseService(context.Background(), test.service, test.networks) + dData, err := p.parseService(t.Context(), test.service, test.networks) require.NoError(t, err) + dockerClient := &fakeServicesClient{ + tasks: test.tasks, + nodes: test.nodes, + } + for _, task := range test.tasks { - taskDockerData := parseTasks(context.Background(), task, dData, test.networks, test.isGlobalSVC) + taskDockerData, err := parseTasks(t.Context(), dockerClient, task, dData, test.networks, test.isGlobalSVC) + require.NoError(t, err) + expected := test.expected[task.ID] assert.Equal(t, expected.Name, taskDockerData.Name) + assert.Equal(t, expected.NodeIP, taskDockerData.NodeIP) } }) } diff --git a/pkg/provider/docker/shared.go b/pkg/provider/docker/shared.go index 97c8d519b..931c61905 100644 --- a/pkg/provider/docker/shared.go +++ b/pkg/provider/docker/shared.go @@ -9,7 +9,7 @@ import ( "time" "github.com/docker/cli/cli/connhelper" - dockertypes "github.com/docker/docker/api/types" + containertypes "github.com/docker/docker/api/types/container" "github.com/docker/docker/client" "github.com/docker/go-connections/nat" "github.com/docker/go-connections/sockets" @@ -33,9 +33,19 @@ type Shared struct { Watch bool `description:"Watch Docker events." json:"watch,omitempty" toml:"watch,omitempty" yaml:"watch,omitempty" export:"true"` DefaultRule string `description:"Default rule." json:"defaultRule,omitempty" toml:"defaultRule,omitempty" yaml:"defaultRule,omitempty"` + LabelMap []*LabelMapItem `description:"Label shorthands." json:"labelMap,omitempty" toml:"labelMap,omitempty" yaml:"labelMap,omitempty"` + defaultRuleTpl *template.Template } +type LabelMapItem struct { + From string `description:"Shorthand label." json:"from,omitempty" toml:"from,omitempty" yaml:"from,omitempty"` + To string `description:"Full label with templates." json:"to,omitempty" toml:"to,omitempty" yaml:"to,omitempty"` + Value *string `description:"Optional override; used instead of user input if set." json:"value,omitempty" toml:"value,omitempty" yaml:"value,omitempty"` + toTpl *template.Template +} + + func inspectContainers(ctx context.Context, dockerClient client.ContainerAPIClient, containerID string) dockerData { containerInspected, err := dockerClient.ContainerInspect(ctx, containerID) if err != nil { @@ -52,7 +62,7 @@ func inspectContainers(ctx context.Context, dockerClient client.ContainerAPIClie return dockerData{} } -func parseContainer(container dockertypes.ContainerJSON) dockerData { +func parseContainer(container containertypes.InspectResponse) dockerData { dData := dockerData{ NetworkSettings: networkSettings{}, } @@ -61,7 +71,6 @@ func parseContainer(container dockertypes.ContainerJSON) dockerData { dData.ID = container.ContainerJSONBase.ID dData.Name = container.ContainerJSONBase.Name dData.ServiceName = dData.Name // Default ServiceName to be the container's Name. - dData.Node = container.ContainerJSONBase.Node if container.ContainerJSONBase.HostConfig != nil { dData.NetworkSettings.NetworkMode = container.ContainerJSONBase.HostConfig.NetworkMode diff --git a/pkg/provider/docker/shared_test.go b/pkg/provider/docker/shared_test.go index 90022c067..a5cf68def 100644 --- a/pkg/provider/docker/shared_test.go +++ b/pkg/provider/docker/shared_test.go @@ -1,12 +1,12 @@ package docker import ( - "context" "strconv" "testing" - docker "github.com/docker/docker/api/types" - "github.com/docker/docker/api/types/swarm" + containertypes "github.com/docker/docker/api/types/container" + networktypes "github.com/docker/docker/api/types/network" + swarmtypes "github.com/docker/docker/api/types/swarm" "github.com/docker/go-connections/nat" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -15,7 +15,7 @@ import ( func Test_getPort_docker(t *testing.T) { testCases := []struct { desc string - container docker.ContainerJSON + container containertypes.InspectResponse serverPort string expected string }{ @@ -79,16 +79,16 @@ func Test_getPort_docker(t *testing.T) { func Test_getPort_swarm(t *testing.T) { testCases := []struct { - service swarm.Service + service swarmtypes.Service serverPort string - networks map[string]*docker.NetworkResource + networks map[string]*networktypes.Summary expected string }{ { service: swarmService( withEndpointSpec(modeDNSRR), ), - networks: map[string]*docker.NetworkResource{}, + networks: map[string]*networktypes.Summary{}, serverPort: "8080", expected: "8080", }, @@ -101,7 +101,7 @@ func Test_getPort_swarm(t *testing.T) { var p SwarmProvider require.NoError(t, p.Init()) - dData, err := p.parseService(context.Background(), test.service, test.networks) + dData, err := p.parseService(t.Context(), test.service, test.networks) require.NoError(t, err) actual := getPort(dData, test.serverPort) diff --git a/pkg/provider/ecs/config_test.go b/pkg/provider/ecs/config_test.go index 8a0b41bab..756168120 100644 --- a/pkg/provider/ecs/config_test.go +++ b/pkg/provider/ecs/config_test.go @@ -1,7 +1,6 @@ package ecs import ( - "context" "testing" "time" @@ -390,7 +389,7 @@ func TestDefaultRule(t *testing.T) { require.NoError(t, err) } - configuration := p.buildConfiguration(context.Background(), test.instances) + configuration := p.buildConfiguration(t.Context(), test.instances) assert.Equal(t, test.expected, configuration) }) @@ -3491,7 +3490,7 @@ func Test_buildConfiguration(t *testing.T) { require.NoError(t, err) } - configuration := p.buildConfiguration(context.Background(), test.containers) + configuration := p.buildConfiguration(t.Context(), test.containers) assert.Equal(t, test.expected, configuration) }) diff --git a/pkg/provider/ecs/ecs.go b/pkg/provider/ecs/ecs.go index b6119e24e..084b9b9ca 100644 --- a/pkg/provider/ecs/ecs.go +++ b/pkg/provider/ecs/ecs.go @@ -24,7 +24,7 @@ import ( "github.com/rs/zerolog/log" "github.com/traefik/traefik/v3/pkg/config/dynamic" "github.com/traefik/traefik/v3/pkg/job" - "github.com/traefik/traefik/v3/pkg/logs" + "github.com/traefik/traefik/v3/pkg/observability/logs" "github.com/traefik/traefik/v3/pkg/provider" "github.com/traefik/traefik/v3/pkg/safe" ) diff --git a/pkg/provider/file/file.go b/pkg/provider/file/file.go index 7b1b79e99..5f0b08223 100644 --- a/pkg/provider/file/file.go +++ b/pkg/provider/file/file.go @@ -18,7 +18,7 @@ import ( "github.com/rs/zerolog/log" "github.com/traefik/paerser/file" "github.com/traefik/traefik/v3/pkg/config/dynamic" - "github.com/traefik/traefik/v3/pkg/logs" + "github.com/traefik/traefik/v3/pkg/observability/logs" "github.com/traefik/traefik/v3/pkg/provider" "github.com/traefik/traefik/v3/pkg/safe" "github.com/traefik/traefik/v3/pkg/tls" diff --git a/pkg/provider/file/file_test.go b/pkg/provider/file/file_test.go index 3cb4a0490..3aebe1908 100644 --- a/pkg/provider/file/file_test.go +++ b/pkg/provider/file/file_test.go @@ -1,7 +1,6 @@ package file import ( - "context" "io" "os" "path/filepath" @@ -67,7 +66,7 @@ func TestTLSCertificateContent(t *testing.T) { require.NoError(t, err) provider := &Provider{} - configuration, err := provider.loadFileConfig(context.Background(), fileConfig.Name(), true) + configuration, err := provider.loadFileConfig(t.Context(), fileConfig.Name(), true) require.NoError(t, err) require.Equal(t, "CONTENT", configuration.TLS.Certificates[0].Certificate.CertFile.String()) @@ -92,7 +91,7 @@ func TestErrorWhenEmptyConfig(t *testing.T) { configChan := make(chan dynamic.Message) errorChan := make(chan struct{}) go func() { - err := provider.Provide(configChan, safe.NewPool(context.Background())) + err := provider.Provide(configChan, safe.NewPool(t.Context())) assert.Error(t, err) close(errorChan) }() @@ -116,7 +115,7 @@ func TestProvideWithoutWatch(t *testing.T) { provider.DebugLogGeneratedTemplate = true go func() { - err := provider.Provide(configChan, safe.NewPool(context.Background())) + err := provider.Provide(configChan, safe.NewPool(t.Context())) assert.NoError(t, err) }() @@ -146,7 +145,7 @@ func TestProvideWithWatch(t *testing.T) { configChan := make(chan dynamic.Message) go func() { - err := provider.Provide(configChan, safe.NewPool(context.Background())) + err := provider.Provide(configChan, safe.NewPool(t.Context())) assert.NoError(t, err) }() diff --git a/pkg/provider/http/http.go b/pkg/provider/http/http.go index 1068429b2..4712e1b0a 100644 --- a/pkg/provider/http/http.go +++ b/pkg/provider/http/http.go @@ -16,7 +16,7 @@ import ( ptypes "github.com/traefik/paerser/types" "github.com/traefik/traefik/v3/pkg/config/dynamic" "github.com/traefik/traefik/v3/pkg/job" - "github.com/traefik/traefik/v3/pkg/logs" + "github.com/traefik/traefik/v3/pkg/observability/logs" "github.com/traefik/traefik/v3/pkg/provider" "github.com/traefik/traefik/v3/pkg/safe" "github.com/traefik/traefik/v3/pkg/tls" diff --git a/pkg/provider/http/http_test.go b/pkg/provider/http/http_test.go index c9f932536..eae98d979 100644 --- a/pkg/provider/http/http_test.go +++ b/pkg/provider/http/http_test.go @@ -1,7 +1,6 @@ package http import ( - "context" "fmt" "net/http" "net/http/httptest" @@ -235,7 +234,7 @@ func TestProvider_Provide(t *testing.T) { }, } - err = provider.Provide(configurationChan, safe.NewPool(context.Background())) + err = provider.Provide(configurationChan, safe.NewPool(t.Context())) require.NoError(t, err) timeout := time.After(time.Second) @@ -269,7 +268,7 @@ func TestProvider_ProvideConfigurationOnlyOnceIfUnchanged(t *testing.T) { configurationChan := make(chan dynamic.Message, 10) - err = provider.Provide(configurationChan, safe.NewPool(context.Background())) + err = provider.Provide(configurationChan, safe.NewPool(t.Context())) require.NoError(t, err) time.Sleep(time.Second) diff --git a/pkg/provider/kubernetes/crd/fixtures/services.yml b/pkg/provider/kubernetes/crd/fixtures/services.yml index 86094ef8a..7c33a2c63 100644 --- a/pkg/provider/kubernetes/crd/fixtures/services.yml +++ b/pkg/provider/kubernetes/crd/fixtures/services.yml @@ -301,6 +301,38 @@ spec: type: ClusterIP clusterIP: 10.10.0.1 +--- +apiVersion: v1 +kind: Service +metadata: + name: native-disabled-svc + namespace: default +spec: + ports: + - name: web + port: 80 + type: ClusterIP + clusterIP: 10.10.0.1 + +--- +kind: EndpointSlice +apiVersion: discovery.k8s.io/v1 +metadata: + name: native-disabled-svc-abc + namespace: default + labels: + kubernetes.io/service-name: native-disabled-svc +addressType: IPv4 +ports: + - name: web + port: 80 +endpoints: + - addresses: + - 10.10.0.20 + - 10.10.0.21 + conditions: + ready: true + --- apiVersion: v1 kind: Service diff --git a/pkg/provider/kubernetes/crd/fixtures/tcp/services.yml b/pkg/provider/kubernetes/crd/fixtures/tcp/services.yml index 8c29006e8..05f49a53f 100644 --- a/pkg/provider/kubernetes/crd/fixtures/tcp/services.yml +++ b/pkg/provider/kubernetes/crd/fixtures/tcp/services.yml @@ -298,11 +298,46 @@ metadata: spec: ports: - - name: myapp - port: 8000 + - name: tcp + protocol: TCP + port: 9000 type: ClusterIP clusterIP: 10.10.0.1 +--- +apiVersion: v1 +kind: Service +metadata: + name: native-disabled-svc-tcp + namespace: default +spec: + ports: + - name: tcp + protocol: TCP + port: 9000 + type: ClusterIP + clusterIP: 10.10.0.1 + +--- +kind: EndpointSlice +apiVersion: discovery.k8s.io/v1 +metadata: + name: native-disabled-tcp-abc + namespace: default + labels: + kubernetes.io/service-name: native-disabled-svc-tcp +addressType: IPv4 +ports: + - name: tcp + protocol: TCP + port: 9000 +endpoints: + - addresses: + - 10.10.0.30 + - 10.10.0.31 + conditions: + ready: true + --- apiVersion: v1 kind: Service diff --git a/pkg/provider/kubernetes/crd/fixtures/tcp/with_global_native_service_lb.yml b/pkg/provider/kubernetes/crd/fixtures/tcp/with_global_native_service_lb.yml index d8832498e..712119c00 100644 --- a/pkg/provider/kubernetes/crd/fixtures/tcp/with_global_native_service_lb.yml +++ b/pkg/provider/kubernetes/crd/fixtures/tcp/with_global_native_service_lb.yml @@ -12,4 +12,4 @@ spec: - match: HostSNI(`foo.com`) services: - name: native-svc-tcp - port: 8000 + port: 9000 diff --git a/pkg/provider/kubernetes/crd/fixtures/tcp/with_native_service_lb.yml b/pkg/provider/kubernetes/crd/fixtures/tcp/with_native_service_lb.yml index c2dbfca31..ad8305ac7 100644 --- a/pkg/provider/kubernetes/crd/fixtures/tcp/with_native_service_lb.yml +++ b/pkg/provider/kubernetes/crd/fixtures/tcp/with_native_service_lb.yml @@ -12,5 +12,5 @@ spec: - match: HostSNI(`foo.com`) services: - name: native-svc-tcp - port: 8000 + port: 9000 nativeLB: true diff --git a/pkg/provider/kubernetes/crd/fixtures/tcp/with_native_service_lb_disabled.yml b/pkg/provider/kubernetes/crd/fixtures/tcp/with_native_service_lb_disabled.yml new file mode 100644 index 000000000..e54b5b3c0 --- /dev/null +++ b/pkg/provider/kubernetes/crd/fixtures/tcp/with_native_service_lb_disabled.yml @@ -0,0 +1,12 @@ +apiVersion: traefik.io/v1alpha1 +kind: IngressRouteTCP +metadata: + name: tcp.route.native-disabled + namespace: default +spec: + routes: + - match: HostSNI(`foo.com`) + services: + - name: native-disabled-svc-tcp + port: 9000 + nativeLB: false diff --git a/pkg/provider/kubernetes/crd/fixtures/udp/services.yml b/pkg/provider/kubernetes/crd/fixtures/udp/services.yml index f77643d36..8993ca084 100644 --- a/pkg/provider/kubernetes/crd/fixtures/udp/services.yml +++ b/pkg/provider/kubernetes/crd/fixtures/udp/services.yml @@ -252,11 +252,46 @@ metadata: spec: ports: - - name: myapp + - name: udp + protocol: UDP port: 8000 type: ClusterIP clusterIP: 10.10.0.1 +--- +apiVersion: v1 +kind: Service +metadata: + name: native-disabled-svc-udp + namespace: default +spec: + ports: + - name: udp + protocol: UDP + port: 8000 + type: ClusterIP + clusterIP: 10.10.0.1 + +--- +kind: EndpointSlice +apiVersion: discovery.k8s.io/v1 +metadata: + name: native-disabled-udp-abc + namespace: default + labels: + kubernetes.io/service-name: native-disabled-svc-udp +addressType: IPv4 +ports: + - name: udp + protocol: UDP + port: 8000 +endpoints: + - addresses: + - 10.10.0.30 + - 10.10.0.31 + conditions: + ready: true + --- apiVersion: v1 kind: Service diff --git a/pkg/provider/kubernetes/crd/fixtures/udp/with_native_service_lb_disabled.yml b/pkg/provider/kubernetes/crd/fixtures/udp/with_native_service_lb_disabled.yml new file mode 100644 index 000000000..f8db343fa --- /dev/null +++ b/pkg/provider/kubernetes/crd/fixtures/udp/with_native_service_lb_disabled.yml @@ -0,0 +1,11 @@ +apiVersion: traefik.io/v1alpha1 +kind: IngressRouteUDP +metadata: + name: udp.route.native-disabled + namespace: default +spec: + routes: + - services: + - name: native-disabled-svc-udp + port: 8000 + nativeLB: false diff --git a/pkg/provider/kubernetes/crd/fixtures/with_native_service_lb_disabled.yml b/pkg/provider/kubernetes/crd/fixtures/with_native_service_lb_disabled.yml new file mode 100644 index 000000000..07a0accf8 --- /dev/null +++ b/pkg/provider/kubernetes/crd/fixtures/with_native_service_lb_disabled.yml @@ -0,0 +1,13 @@ +apiVersion: traefik.io/v1alpha1 +kind: IngressRoute +metadata: + name: test.route.native-disabled + namespace: default +spec: + routes: + - match: Host(`foo.com`) + kind: Rule + services: + - name: native-disabled-svc + port: 80 + nativeLB: false diff --git a/pkg/provider/kubernetes/crd/kubernetes.go b/pkg/provider/kubernetes/crd/kubernetes.go index 2b374eeba..b9f5c0aa5 100644 --- a/pkg/provider/kubernetes/crd/kubernetes.go +++ b/pkg/provider/kubernetes/crd/kubernetes.go @@ -24,7 +24,7 @@ import ( ptypes "github.com/traefik/paerser/types" "github.com/traefik/traefik/v3/pkg/config/dynamic" "github.com/traefik/traefik/v3/pkg/job" - "github.com/traefik/traefik/v3/pkg/logs" + "github.com/traefik/traefik/v3/pkg/observability/logs" "github.com/traefik/traefik/v3/pkg/provider" traefikv1alpha1 "github.com/traefik/traefik/v3/pkg/provider/kubernetes/crd/traefikio/v1alpha1" "github.com/traefik/traefik/v3/pkg/provider/kubernetes/gateway" @@ -490,6 +490,10 @@ func (p *Provider) loadConfigurationFromCRD(ctx context.Context, client Client) } } + if serversTransportTCP.Spec.ProxyProtocol != nil { + tcpServerTransport.ProxyProtocol = serversTransportTCP.Spec.ProxyProtocol + } + if serversTransportTCP.Spec.TLS != nil { if len(serversTransportTCP.Spec.TLS.RootCAsSecrets) > 0 { logger.Warn().Msg("RootCAsSecrets option is deprecated, please use the RootCA option instead.") diff --git a/pkg/provider/kubernetes/crd/kubernetes_http.go b/pkg/provider/kubernetes/crd/kubernetes_http.go index 9fca7c1f2..2e907d6c3 100644 --- a/pkg/provider/kubernetes/crd/kubernetes_http.go +++ b/pkg/provider/kubernetes/crd/kubernetes_http.go @@ -9,8 +9,9 @@ import ( "strings" "github.com/rs/zerolog/log" + ptypes "github.com/traefik/paerser/types" "github.com/traefik/traefik/v3/pkg/config/dynamic" - "github.com/traefik/traefik/v3/pkg/logs" + "github.com/traefik/traefik/v3/pkg/observability/logs" "github.com/traefik/traefik/v3/pkg/provider" traefikv1alpha1 "github.com/traefik/traefik/v3/pkg/provider/kubernetes/crd/traefikio/v1alpha1" "github.com/traefik/traefik/v3/pkg/provider/kubernetes/k8s" @@ -373,6 +374,17 @@ func (c configBuilder) buildServersLB(namespace string, svc traefikv1alpha1.Load return nil, err } } + // If the UnhealthyInterval option is not set, we use the Interval option value, + // to check the unhealthy targets as often as the healthy ones. + if svc.HealthCheck.UnhealthyInterval == nil { + lb.HealthCheck.UnhealthyInterval = &lb.HealthCheck.Interval + } else { + var unhealthyInterval ptypes.Duration + if err := unhealthyInterval.Set(svc.HealthCheck.UnhealthyInterval.String()); err != nil { + return nil, err + } + lb.HealthCheck.UnhealthyInterval = &unhealthyInterval + } if svc.HealthCheck.Timeout != nil { if err := lb.HealthCheck.Timeout.Set(svc.HealthCheck.Timeout.String()); err != nil { return nil, err diff --git a/pkg/provider/kubernetes/crd/kubernetes_tcp.go b/pkg/provider/kubernetes/crd/kubernetes_tcp.go index 1eb3eed84..c4c3fa6d4 100644 --- a/pkg/provider/kubernetes/crd/kubernetes_tcp.go +++ b/pkg/provider/kubernetes/crd/kubernetes_tcp.go @@ -10,7 +10,7 @@ import ( "github.com/rs/zerolog/log" "github.com/traefik/traefik/v3/pkg/config/dynamic" - "github.com/traefik/traefik/v3/pkg/logs" + "github.com/traefik/traefik/v3/pkg/observability/logs" "github.com/traefik/traefik/v3/pkg/provider" traefikv1alpha1 "github.com/traefik/traefik/v3/pkg/provider/kubernetes/crd/traefikio/v1alpha1" "github.com/traefik/traefik/v3/pkg/tls" diff --git a/pkg/provider/kubernetes/crd/kubernetes_test.go b/pkg/provider/kubernetes/crd/kubernetes_test.go index 93b43fdfb..ec6651091 100644 --- a/pkg/provider/kubernetes/crd/kubernetes_test.go +++ b/pkg/provider/kubernetes/crd/kubernetes_test.go @@ -1,7 +1,6 @@ package crd import ( - "context" "os" "path/filepath" "strings" @@ -1677,7 +1676,7 @@ func TestLoadIngressRouteTCPs(t *testing.T) { AllowEmptyServices: test.allowEmptyServices, } - conf := p.loadConfigurationFromCRD(context.Background(), client) + conf := p.loadConfigurationFromCRD(t.Context(), client) assert.Equal(t, test.expected, conf) }) } @@ -2647,10 +2646,11 @@ func TestLoadIngressRoutes(t *testing.T) { FlushInterval: ptypes.Duration(100 * time.Millisecond), }, HealthCheck: &dynamic.ServerHealthCheck{ - Path: "/health", - Timeout: 5000000000, - Interval: 15000000000, - FollowRedirects: pointer(true), + Path: "/health", + Timeout: 5000000000, + Interval: 15000000000, + UnhealthyInterval: pointer(ptypes.Duration(15000000000)), + FollowRedirects: pointer(true), }, }, }, @@ -2712,10 +2712,11 @@ func TestLoadIngressRoutes(t *testing.T) { FlushInterval: ptypes.Duration(100 * time.Millisecond), }, HealthCheck: &dynamic.ServerHealthCheck{ - Path: "/health1", - Timeout: 5000000000, - Interval: 15000000000, - FollowRedirects: pointer(true), + Path: "/health1", + Timeout: 5000000000, + Interval: 15000000000, + UnhealthyInterval: pointer(ptypes.Duration(15000000000)), + FollowRedirects: pointer(true), }, }, }, @@ -2732,10 +2733,11 @@ func TestLoadIngressRoutes(t *testing.T) { FlushInterval: ptypes.Duration(100 * time.Millisecond), }, HealthCheck: &dynamic.ServerHealthCheck{ - Path: "/health2", - Timeout: 5000000000, - Interval: 20000000000, - FollowRedirects: pointer(true), + Path: "/health2", + Timeout: 5000000000, + Interval: 20000000000, + UnhealthyInterval: pointer(ptypes.Duration(20000000000)), + FollowRedirects: pointer(true), }, }, }, @@ -2776,10 +2778,11 @@ func TestLoadIngressRoutes(t *testing.T) { FlushInterval: ptypes.Duration(100 * time.Millisecond), }, HealthCheck: &dynamic.ServerHealthCheck{ - Path: "/health1", - Timeout: 5000000000, - Interval: 15000000000, - FollowRedirects: pointer(true), + Path: "/health1", + Timeout: 5000000000, + Interval: 15000000000, + UnhealthyInterval: pointer(ptypes.Duration(15000000000)), + FollowRedirects: pointer(true), }, }, }, @@ -5309,7 +5312,7 @@ func TestLoadIngressRoutes(t *testing.T) { AllowEmptyServices: test.allowEmptyServices, } - conf := p.loadConfigurationFromCRD(context.Background(), client) + conf := p.loadConfigurationFromCRD(t.Context(), client) assert.Equal(t, test.expected, conf) }) } @@ -5385,7 +5388,7 @@ func TestLoadIngressRoutes_multipleEndpointAddresses(t *testing.T) { } p := Provider{} - conf := p.loadConfigurationFromCRD(context.Background(), client) + conf := p.loadConfigurationFromCRD(t.Context(), client) service, ok := conf.HTTP.Services["default-test-route-6b204d94623b3df4370c"] require.True(t, ok) @@ -5900,7 +5903,7 @@ func TestLoadIngressRouteUDPs(t *testing.T) { AllowEmptyServices: test.allowEmptyServices, } - conf := p.loadConfigurationFromCRD(context.Background(), client) + conf := p.loadConfigurationFromCRD(t.Context(), client) assert.Equal(t, test.expected, conf) }) } @@ -7398,7 +7401,7 @@ func TestCrossNamespace(t *testing.T) { p := Provider{AllowCrossNamespace: test.allowCrossNamespace} - conf := p.loadConfigurationFromCRD(context.Background(), client) + conf := p.loadConfigurationFromCRD(t.Context(), client) assert.Equal(t, test.expected, conf) }) } @@ -7668,7 +7671,7 @@ func TestExternalNameService(t *testing.T) { p := Provider{AllowExternalNameServices: test.allowExternalNameService} - conf := p.loadConfigurationFromCRD(context.Background(), client) + conf := p.loadConfigurationFromCRD(t.Context(), client) assert.Equal(t, test.expected, conf) }) } @@ -7775,7 +7778,7 @@ func TestNativeLB(t *testing.T) { LoadBalancer: &dynamic.TCPServersLoadBalancer{ Servers: []dynamic.TCPServer{ { - Address: "10.10.0.1:8000", + Address: "10.10.0.1:9000", Port: "", }, }, @@ -7850,7 +7853,7 @@ func TestNativeLB(t *testing.T) { p := Provider{} - conf := p.loadConfigurationFromCRD(context.Background(), client) + conf := p.loadConfigurationFromCRD(t.Context(), client) assert.Equal(t, test.expected, conf) }) } @@ -8118,7 +8121,7 @@ func TestNodePortLB(t *testing.T) { DisableClusterScopeResources: test.disableClusterScope, } - conf := p.loadConfigurationFromCRD(context.Background(), client) + conf := p.loadConfigurationFromCRD(t.Context(), client) assert.Equal(t, test.expected, conf) }) } @@ -8372,6 +8375,52 @@ func TestGlobalNativeLB(t *testing.T) { TLS: &dynamic.TLSConfiguration{}, }, }, + { + desc: "HTTP with global native Service LB but service reference has nativeLB disabled", + paths: []string{"services.yml", "with_native_service_lb_disabled.yml"}, + NativeLBByDefault: true, + expected: &dynamic.Configuration{ + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + TCP: &dynamic.TCPConfiguration{ + ServersTransports: map[string]*dynamic.TCPServersTransport{}, + Routers: map[string]*dynamic.TCPRouter{}, + Middlewares: map[string]*dynamic.TCPMiddleware{}, + Services: map[string]*dynamic.TCPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + ServersTransports: map[string]*dynamic.ServersTransport{}, + Routers: map[string]*dynamic.Router{ + "default-test-route-native-disabled-6f97418635c7e18853da": { + Service: "default-test-route-native-disabled-6f97418635c7e18853da", + Rule: "Host(`foo.com`)", + Priority: 0, + }, + }, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{ + "default-test-route-native-disabled-6f97418635c7e18853da": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + Strategy: dynamic.BalancerStrategyWRR, + ResponseForwarding: &dynamic.ResponseForwarding{FlushInterval: dynamic.DefaultFlushInterval}, + Servers: []dynamic.Server{ + { + URL: "http://10.10.0.20:80", + }, + { + URL: "http://10.10.0.21:80", + }, + }, + PassHostHeader: pointer(true), + }, + }, + }, + }, + TLS: &dynamic.TLSConfiguration{}, + }, + }, { desc: "HTTP with native Service LB in ingressroute", paths: []string{"services.yml", "with_native_service_lb.yml"}, @@ -8445,7 +8494,51 @@ func TestGlobalNativeLB(t *testing.T) { LoadBalancer: &dynamic.TCPServersLoadBalancer{ Servers: []dynamic.TCPServer{ { - Address: "10.10.0.1:8000", + Address: "10.10.0.1:9000", + Port: "", + }, + }, + }, + }, + }, + }, + TLS: &dynamic.TLSConfiguration{}, + }, + }, + { + desc: "TCP with global native Service LB but service reference has nativeLB disabled", + paths: []string{"tcp/services.yml", "tcp/with_native_service_lb_disabled.yml"}, + NativeLBByDefault: true, + expected: &dynamic.Configuration{ + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + ServersTransports: map[string]*dynamic.ServersTransport{}, + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, + }, + TCP: &dynamic.TCPConfiguration{ + ServersTransports: map[string]*dynamic.TCPServersTransport{}, + Routers: map[string]*dynamic.TCPRouter{ + "default-tcp.route.native-disabled-fdd3e9338e47a45efefc": { + Service: "default-tcp.route.native-disabled-fdd3e9338e47a45efefc", + Rule: "HostSNI(`foo.com`)", + }, + }, + Middlewares: map[string]*dynamic.TCPMiddleware{}, + Services: map[string]*dynamic.TCPService{ + "default-tcp.route.native-disabled-fdd3e9338e47a45efefc": { + LoadBalancer: &dynamic.TCPServersLoadBalancer{ + Servers: []dynamic.TCPServer{ + { + Address: "10.10.0.30:9000", + Port: "", + }, + { + Address: "10.10.0.31:9000", Port: "", }, }, @@ -8485,7 +8578,7 @@ func TestGlobalNativeLB(t *testing.T) { LoadBalancer: &dynamic.TCPServersLoadBalancer{ Servers: []dynamic.TCPServer{ { - Address: "10.10.0.1:8000", + Address: "10.10.0.1:9000", Port: "", }, }, @@ -8575,6 +8668,49 @@ func TestGlobalNativeLB(t *testing.T) { TLS: &dynamic.TLSConfiguration{}, }, }, + { + desc: "UDP with global native Service LB but service reference has nativeLB disabled", + paths: []string{"udp/services.yml", "udp/with_native_service_lb_disabled.yml"}, + NativeLBByDefault: true, + expected: &dynamic.Configuration{ + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{ + "default-udp.route.native-disabled-0": { + Service: "default-udp.route.native-disabled-0", + }, + }, + Services: map[string]*dynamic.UDPService{ + "default-udp.route.native-disabled-0": { + LoadBalancer: &dynamic.UDPServersLoadBalancer{ + Servers: []dynamic.UDPServer{ + { + Address: "10.10.0.30:8000", + Port: "", + }, + { + Address: "10.10.0.31:8000", + Port: "", + }, + }, + }, + }, + }, + }, + HTTP: &dynamic.HTTPConfiguration{ + ServersTransports: map[string]*dynamic.ServersTransport{}, + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, + }, + TCP: &dynamic.TCPConfiguration{ + ServersTransports: map[string]*dynamic.TCPServersTransport{}, + Routers: map[string]*dynamic.TCPRouter{}, + Middlewares: map[string]*dynamic.TCPMiddleware{}, + Services: map[string]*dynamic.TCPService{}, + }, + TLS: &dynamic.TLSConfiguration{}, + }, + }, } for _, test := range testCases { @@ -8592,8 +8728,6 @@ func TestGlobalNativeLB(t *testing.T) { objects := k8s.MustParseYaml(yamlContent) for _, obj := range objects { switch o := obj.(type) { - case *corev1.Service, *corev1.Endpoints, *corev1.Secret: - k8sObjects = append(k8sObjects, o) case *traefikv1alpha1.IngressRoute: crdObjects = append(crdObjects, o) case *traefikv1alpha1.IngressRouteTCP: @@ -8609,6 +8743,7 @@ func TestGlobalNativeLB(t *testing.T) { case *traefikv1alpha1.TLSStore: crdObjects = append(crdObjects, o) default: + k8sObjects = append(k8sObjects, o) } } } @@ -8630,7 +8765,7 @@ func TestGlobalNativeLB(t *testing.T) { p := Provider{NativeLBByDefault: test.NativeLBByDefault} - conf := p.loadConfigurationFromCRD(context.Background(), client) + conf := p.loadConfigurationFromCRD(t.Context(), client) assert.Equal(t, test.expected, conf) }) } diff --git a/pkg/provider/kubernetes/crd/traefikio/v1alpha1/ingressroute.go b/pkg/provider/kubernetes/crd/traefikio/v1alpha1/ingressroute.go index 007859260..6fda0077d 100644 --- a/pkg/provider/kubernetes/crd/traefikio/v1alpha1/ingressroute.go +++ b/pkg/provider/kubernetes/crd/traefikio/v1alpha1/ingressroute.go @@ -13,18 +13,18 @@ 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/v3.4/routing/entrypoints/ + // More info: https://doc.traefik.io/traefik/v3.5/reference/install-configuration/entrypoints/ // Default: all. EntryPoints []string `json:"entryPoints,omitempty"` // TLS defines the TLS configuration. - // More info: https://doc.traefik.io/traefik/v3.4/routing/routers/#tls + // More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/routing/router/#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/v3.4/routing/routers/#rule + // More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/routing/rules-and-priority/ Match string `json:"match"` // Kind defines the kind of the route. // Rule is the only supported kind. @@ -32,62 +32,62 @@ type Route struct { // +kubebuilder:validation:Enum=Rule Kind string `json:"kind,omitempty"` // Priority defines the router's priority. - // More info: https://doc.traefik.io/traefik/v3.4/routing/routers/#priority + // More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/routing/rules-and-priority/#priority // +kubebuilder:validation:Maximum=9223372036854774807 Priority int `json:"priority,omitempty"` // Syntax defines the router's rule syntax. - // More info: https://doc.traefik.io/traefik/v3.4/routing/routers/#rulesyntax + // More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/routing/rules-and-priority/#rulesyntax // Deprecated: Please do not use this field and rewrite the router rules to use the v3 syntax. Syntax string `json:"syntax,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/v3.4/routing/providers/kubernetes-crd/#kind-middleware + // More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/kubernetes/crd/http/middleware/ Middlewares []MiddlewareRef `json:"middlewares,omitempty"` // Observability defines the observability configuration for a router. - // More info: https://doc.traefik.io/traefik/v3.4/routing/routers/#observability + // More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/routing/observability/ Observability *dynamic.RouterObservabilityConfig `json:"observability,omitempty"` } // TLS holds the TLS configuration. -// More info: https://doc.traefik.io/traefik/v3.4/routing/routers/#tls +// More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/tls/overview/ 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/v3.4/https/tls/#tls-options + // More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/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/v3.4/https/acme/#certificate-resolvers + // More info: https://doc.traefik.io/traefik/v3.5/reference/install-configuration/tls/certificate-resolvers/acme/ 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/v3.4/routing/routers/#domains + // More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/tls/tls-certificates/#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/v3.4/routing/providers/kubernetes-crd/#kind-tlsoption + // More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/kubernetes/crd/http/tlsoption/ Name string `json:"name"` // Namespace defines the namespace of the referenced TLSOption. - // More info: https://doc.traefik.io/traefik/v3.4/routing/providers/kubernetes-crd/#kind-tlsoption + // More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/kubernetes/crd/http/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/v3.4/routing/providers/kubernetes-crd/#kind-tlsstore + // More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/kubernetes/crd/http/tlsstore/ Name string `json:"name"` // Namespace defines the namespace of the referenced TLSStore. - // More info: https://doc.traefik.io/traefik/v3.4/routing/providers/kubernetes-crd/#kind-tlsstore + // More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/kubernetes/crd/http/tlsstore/ Namespace string `json:"namespace,omitempty"` } @@ -104,7 +104,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/v3.4/routing/services/#sticky-sessions + // More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/load-balancing/service/#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. @@ -170,9 +170,13 @@ type ServerHealthCheck struct { Status int `json:"status,omitempty"` // Port defines the server URL port for the health check endpoint. Port int `json:"port,omitempty"` - // Interval defines the frequency of the health check calls. + // Interval defines the frequency of the health check calls for healthy targets. // Default: 30s Interval *intstr.IntOrString `json:"interval,omitempty"` + // UnhealthyInterval defines the frequency of the health check calls for unhealthy targets. + // When UnhealthyInterval is not defined, it defaults to the Interval value. + // Default: 30s + UnhealthyInterval *intstr.IntOrString `json:"unhealthyInterval,omitempty"` // Timeout defines the maximum duration Traefik will wait for a health check request before considering the server unhealthy. // Default: 5s Timeout *intstr.IntOrString `json:"timeout,omitempty"` diff --git a/pkg/provider/kubernetes/crd/traefikio/v1alpha1/ingressroutetcp.go b/pkg/provider/kubernetes/crd/traefikio/v1alpha1/ingressroutetcp.go index 4e04249f0..5ef08dc02 100644 --- a/pkg/provider/kubernetes/crd/traefikio/v1alpha1/ingressroutetcp.go +++ b/pkg/provider/kubernetes/crd/traefikio/v1alpha1/ingressroutetcp.go @@ -13,25 +13,25 @@ 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/v3.4/routing/entrypoints/ + // More info: https://doc.traefik.io/traefik/v3.5/reference/install-configuration/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/v3.4/routing/routers/#tls_1 + // More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/tcp/routing/router/#tls 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/v3.4/routing/routers/#rule_1 + // More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/tcp/routing/rules-and-priority/ Match string `json:"match"` // Priority defines the router's priority. - // More info: https://doc.traefik.io/traefik/v3.4/routing/routers/#priority_1 + // More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/tcp/routing/rules-and-priority/#priority // +kubebuilder:validation:Maximum=9223372036854774807 Priority int `json:"priority,omitempty"` // Syntax defines the router's rule syntax. - // More info: https://doc.traefik.io/traefik/v3.4/routing/routers/#rulesyntax_1 + // More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/tcp/routing/rules-and-priority/#rulesyntax // +kubebuilder:validation:Enum=v3;v2 // Deprecated: Please do not use this field and rewrite the router rules to use the v3 syntax. Syntax string `json:"syntax,omitempty"` @@ -42,7 +42,7 @@ type RouteTCP struct { } // TLSTCP holds the TLS configuration for an IngressRouteTCP. -// More info: https://doc.traefik.io/traefik/v3.4/routing/routers/#tls_1 +// More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/tcp/tls/ type TLSTCP struct { // SecretName is the name of the referenced Kubernetes Secret to specify the certificate details. SecretName string `json:"secretName,omitempty"` @@ -50,17 +50,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/v3.4/https/tls/#tls-options + // More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/tcp/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/v3.4/https/acme/#certificate-resolvers + // More info: https://doc.traefik.io/traefik/v3.5/reference/install-configuration/tls/certificate-resolvers/acme/ 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/v3.4/routing/routers/#domains + // More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/tcp/tls/#domains Domains []types.Domain `json:"domains,omitempty"` } @@ -85,7 +85,8 @@ type ServiceTCP struct { // Deprecated: TerminationDelay will not be supported in future APIVersions, please use ServersTransport to configure the TerminationDelay instead. TerminationDelay *int `json:"terminationDelay,omitempty"` // ProxyProtocol defines the PROXY protocol configuration. - // More info: https://doc.traefik.io/traefik/v3.4/routing/services/#proxy-protocol + // More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/tcp/service/#proxy-protocol + // Deprecated: ProxyProtocol will not be supported in future APIVersions, please use ServersTransport to configure ProxyProtocol instead. ProxyProtocol *dynamic.ProxyProtocol `json:"proxyProtocol,omitempty"` // ServersTransport defines the name of ServersTransportTCP resource to use. // It allows to configure the transport between Traefik and your servers. diff --git a/pkg/provider/kubernetes/crd/traefikio/v1alpha1/ingressrouteudp.go b/pkg/provider/kubernetes/crd/traefikio/v1alpha1/ingressrouteudp.go index 3df06d203..6e0038843 100644 --- a/pkg/provider/kubernetes/crd/traefikio/v1alpha1/ingressrouteudp.go +++ b/pkg/provider/kubernetes/crd/traefikio/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/v3.4/routing/entrypoints/ + // More info: https://doc.traefik.io/traefik/v3.5/reference/install-configuration/entrypoints/ // Default: all. EntryPoints []string `json:"entryPoints,omitempty"` } diff --git a/pkg/provider/kubernetes/crd/traefikio/v1alpha1/middleware.go b/pkg/provider/kubernetes/crd/traefikio/v1alpha1/middleware.go index a64ae9579..32354e2b5 100644 --- a/pkg/provider/kubernetes/crd/traefikio/v1alpha1/middleware.go +++ b/pkg/provider/kubernetes/crd/traefikio/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/v3.4/middlewares/http/overview/ +// More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/middlewares/overview/ type Middleware struct { metav1.TypeMeta `json:",inline"` // Standard object's metadata. @@ -52,7 +52,7 @@ type MiddlewareSpec struct { ContentType *dynamic.ContentType `json:"contentType,omitempty"` GrpcWeb *dynamic.GrpcWeb `json:"grpcWeb,omitempty"` // Plugin defines the middleware plugin configuration. - // More info: https://doc.traefik.io/traefik/plugins/ + // More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/middlewares/overview/#community-middlewares Plugin map[string]apiextensionv1.JSON `json:"plugin,omitempty"` } @@ -60,7 +60,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/v3.4/middlewares/http/errorpages/ +// More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/middlewares/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), @@ -73,7 +73,7 @@ type ErrorPage struct { // For example: "418": 404 or "410-418": 404 StatusRewrites map[string]int `json:"statusRewrites,omitempty"` // Service defines the reference to a Kubernetes Service that will serve the error page. - // More info: https://doc.traefik.io/traefik/v3.4/middlewares/http/errorpages/#service + // More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/middlewares/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. @@ -108,7 +108,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/v3.4/middlewares/http/chain/ +// More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/middlewares/chain/ type Chain struct { // Middlewares is the list of MiddlewareRef which composes the chain. Middlewares []MiddlewareRef `json:"middlewares,omitempty"` @@ -118,7 +118,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/v3.4/middlewares/http/basicauth/ +// More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/middlewares/basicauth/ type BasicAuth struct { // Secret is the name of the referenced Kubernetes Secret containing user credentials. Secret string `json:"secret,omitempty"` @@ -129,7 +129,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/v3.4/middlewares/http/basicauth/#headerfield + // More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/middlewares/basicauth/#headerfield HeaderField string `json:"headerField,omitempty"` } @@ -137,7 +137,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/v3.4/middlewares/http/digestauth/ +// More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/middlewares/digestauth/ type DigestAuth struct { // Secret is the name of the referenced Kubernetes Secret containing user credentials. Secret string `json:"secret,omitempty"` @@ -147,7 +147,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/v3.4/middlewares/http/basicauth/#headerfield + // More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/middlewares/digestauth/#headerfield HeaderField string `json:"headerField,omitempty"` } @@ -155,7 +155,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/v3.4/middlewares/http/forwardauth/ +// More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/middlewares/forwardauth/ type ForwardAuth struct { // Address defines the authentication server address. Address string `json:"address,omitempty"` @@ -164,7 +164,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/v3.4/middlewares/http/forwardauth/#authresponseheadersregex + // More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/middlewares/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. @@ -174,7 +174,7 @@ type ForwardAuth struct { // AddAuthCookiesToResponse defines the list of cookies to copy from the authentication server response to the response. AddAuthCookiesToResponse []string `json:"addAuthCookiesToResponse,omitempty"` // HeaderField defines a header field to store the authenticated user. - // More info: https://doc.traefik.io/traefik/v3.4/middlewares/http/forwardauth/#headerfield + // More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/middlewares/forwardauth/#headerfield HeaderField string `json:"headerField,omitempty"` // ForwardBody defines whether to send the request body to the authentication server. ForwardBody bool `json:"forwardBody,omitempty"` @@ -201,7 +201,7 @@ type ClientTLSWithCAOptional 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/v3.4/middlewares/http/ratelimit/ +// More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/middlewares/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. @@ -286,7 +286,7 @@ type ClientTLS struct { // Compress holds the compress middleware configuration. // This middleware compresses responses before sending them to the client, using gzip, brotli, or zstd compression. -// More info: https://doc.traefik.io/traefik/v3.4/middlewares/http/compress/ +// More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/middlewares/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. @@ -308,7 +308,7 @@ type Compress 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/v3.4/middlewares/http/retry/ +// More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/middlewares/retry/ type Retry struct { // Attempts defines how many times the request should be retried. // +kubebuilder:validation:Minimum=0 diff --git a/pkg/provider/kubernetes/crd/traefikio/v1alpha1/middlewaretcp.go b/pkg/provider/kubernetes/crd/traefikio/v1alpha1/middlewaretcp.go index 544976bca..49309b02c 100644 --- a/pkg/provider/kubernetes/crd/traefikio/v1alpha1/middlewaretcp.go +++ b/pkg/provider/kubernetes/crd/traefikio/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/v3.4/middlewares/overview/ +// More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/tcp/middlewares/overview/ type MiddlewareTCP struct { metav1.TypeMeta `json:",inline"` // Standard object's metadata. @@ -28,11 +28,11 @@ type MiddlewareTCPSpec struct { // IPWhiteList defines the IPWhiteList middleware configuration. // This middleware accepts/refuses connections based on the client IP. // Deprecated: please use IPAllowList instead. - // More info: https://doc.traefik.io/traefik/v3.4/middlewares/tcp/ipwhitelist/ + // More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/tcp/middlewares/ipwhitelist/ IPWhiteList *dynamic.TCPIPWhiteList `json:"ipWhiteList,omitempty"` // IPAllowList defines the IPAllowList middleware configuration. // This middleware accepts/refuses connections based on the client IP. - // More info: https://doc.traefik.io/traefik/v3.4/middlewares/tcp/ipallowlist/ + // More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/tcp/middlewares/ipallowlist/ IPAllowList *dynamic.TCPIPAllowList `json:"ipAllowList,omitempty"` } diff --git a/pkg/provider/kubernetes/crd/traefikio/v1alpha1/serverstransport.go b/pkg/provider/kubernetes/crd/traefikio/v1alpha1/serverstransport.go index 5c7773895..f797671fc 100644 --- a/pkg/provider/kubernetes/crd/traefikio/v1alpha1/serverstransport.go +++ b/pkg/provider/kubernetes/crd/traefikio/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/v3.4/routing/services/#serverstransport_1 +// More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/load-balancing/serverstransport/ type ServersTransport struct { metav1.TypeMeta `json:",inline"` // Standard object's metadata. @@ -39,7 +39,7 @@ type ServersTransportSpec struct { // CertificatesSecrets defines a list of secret storing client certificates for mTLS. CertificatesSecrets []string `json:"certificatesSecrets,omitempty"` // MaxIdleConnsPerHost controls the maximum idle (keep-alive) to keep per-host. - // +kubebuilder:validation:Minimum=0 + // +kubebuilder:validation:Minimum=-1 MaxIdleConnsPerHost int `json:"maxIdleConnsPerHost,omitempty"` // ForwardingTimeouts defines the timeouts for requests forwarded to the backend servers. ForwardingTimeouts *ForwardingTimeouts `json:"forwardingTimeouts,omitempty"` diff --git a/pkg/provider/kubernetes/crd/traefikio/v1alpha1/serverstransporttcp.go b/pkg/provider/kubernetes/crd/traefikio/v1alpha1/serverstransporttcp.go index e4b3d6744..dfb2299be 100644 --- a/pkg/provider/kubernetes/crd/traefikio/v1alpha1/serverstransporttcp.go +++ b/pkg/provider/kubernetes/crd/traefikio/v1alpha1/serverstransporttcp.go @@ -13,7 +13,7 @@ import ( // ServersTransportTCP is the CRD implementation of a TCPServersTransport. // If no tcpServersTransport is specified, a default one named default@internal will be used. // The default@internal tcpServersTransport can be configured in the static configuration. -// More info: https://doc.traefik.io/traefik/v3.4/routing/services/#serverstransport_3 +// More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/tcp/serverstransport/ type ServersTransportTCP struct { metav1.TypeMeta `json:",inline"` // Standard object's metadata. @@ -35,12 +35,14 @@ type ServersTransportTCPSpec struct { // +kubebuilder:validation:Pattern="^([0-9]+(ns|us|µs|ms|s|m|h)?)+$" // +kubebuilder:validation:XIntOrString DialKeepAlive *intstr.IntOrString `json:"dialKeepAlive,omitempty"` + // ProxyProtocol holds the PROXY Protocol configuration. + ProxyProtocol *dynamic.ProxyProtocol `json:"proxyProtocol,omitempty"` // TerminationDelay defines the delay to wait before fully terminating the connection, after one connected peer has closed its writing capability. // +kubebuilder:validation:Pattern="^([0-9]+(ns|us|µs|ms|s|m|h)?)+$" // +kubebuilder:validation:XIntOrString TerminationDelay *intstr.IntOrString `json:"terminationDelay,omitempty"` // TLS defines the TLS configuration - TLS *TLSClientConfig `description:"Defines the TLS configuration." json:"tls,omitempty"` + TLS *TLSClientConfig `json:"tls,omitempty"` } // TLSClientConfig defines the desired state of a TLSClientConfig. diff --git a/pkg/provider/kubernetes/crd/traefikio/v1alpha1/service.go b/pkg/provider/kubernetes/crd/traefikio/v1alpha1/service.go index 009da340e..8adce08ef 100644 --- a/pkg/provider/kubernetes/crd/traefikio/v1alpha1/service.go +++ b/pkg/provider/kubernetes/crd/traefikio/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/v3.4/routing/providers/kubernetes-crd/#kind-traefikservice +// More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/kubernetes/crd/http/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/v3.4/routing/services/#mirroring-service +// More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/load-balancing/service/#mirroring type Mirroring struct { LoadBalancerSpec `json:",inline"` @@ -78,11 +78,11 @@ type MirrorService struct { // +k8s:deepcopy-gen=true // WeightedRoundRobin holds the weighted round-robin configuration. -// More info: https://doc.traefik.io/traefik/v3.4/routing/services/#weighted-round-robin-service +// More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/load-balancing/service/#weighted-round-robin-wrr 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/v3.4/routing/providers/kubernetes-crd/#stickiness-and-load-balancing + // More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/kubernetes/crd/http/traefikservice/#stickiness-and-load-balancing Sticky *dynamic.Sticky `json:"sticky,omitempty"` } diff --git a/pkg/provider/kubernetes/crd/traefikio/v1alpha1/tlsoption.go b/pkg/provider/kubernetes/crd/traefikio/v1alpha1/tlsoption.go index af8aceeb5..07ab9954d 100644 --- a/pkg/provider/kubernetes/crd/traefikio/v1alpha1/tlsoption.go +++ b/pkg/provider/kubernetes/crd/traefikio/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/v3.4/https/tls/#tls-options +// More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/tls/tls-certificates/#certificates-stores#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/v3.4/https/tls/#cipher-suites + // More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/tls/tls-certificates/#certificates-stores#cipher-suites CipherSuites []string `json:"cipherSuites,omitempty"` - // CurvePreferences defines the preferred elliptic curves in a specific order. - // More info: https://doc.traefik.io/traefik/v3.4/https/tls/#curve-preferences + // CurvePreferences defines the preferred elliptic curves. + // More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/tls/tls-certificates/#certificates-stores#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/v3.4/https/tls/#alpn-protocols + // More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/tls/tls-certificates/#certificates-stores#alpn-protocols ALPNProtocols []string `json:"alpnProtocols,omitempty"` // DisableSessionTickets disables TLS session resumption via session tickets. DisableSessionTickets bool `json:"disableSessionTickets,omitempty"` diff --git a/pkg/provider/kubernetes/crd/traefikio/v1alpha1/tlsstore.go b/pkg/provider/kubernetes/crd/traefikio/v1alpha1/tlsstore.go index eac007778..23a5d1a9a 100644 --- a/pkg/provider/kubernetes/crd/traefikio/v1alpha1/tlsstore.go +++ b/pkg/provider/kubernetes/crd/traefikio/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/v3.4/https/tls/#certificates-stores +// More info: https://doc.traefik.io/traefik/v3.5/reference/routing-configuration/http/tls/tls-certificates/#certificates-stores#certificates-stores type TLSStore struct { metav1.TypeMeta `json:",inline"` // Standard object's metadata. diff --git a/pkg/provider/kubernetes/crd/traefikio/v1alpha1/zz_generated.deepcopy.go b/pkg/provider/kubernetes/crd/traefikio/v1alpha1/zz_generated.deepcopy.go index f975a8bfb..c4f898635 100644 --- a/pkg/provider/kubernetes/crd/traefikio/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/provider/kubernetes/crd/traefikio/v1alpha1/zz_generated.deepcopy.go @@ -1280,6 +1280,11 @@ func (in *ServerHealthCheck) DeepCopyInto(out *ServerHealthCheck) { *out = new(intstr.IntOrString) **out = **in } + if in.UnhealthyInterval != nil { + in, out := &in.UnhealthyInterval, &out.UnhealthyInterval + *out = new(intstr.IntOrString) + **out = **in + } if in.Timeout != nil { in, out := &in.Timeout, &out.Timeout *out = new(intstr.IntOrString) @@ -1486,6 +1491,11 @@ func (in *ServersTransportTCPSpec) DeepCopyInto(out *ServersTransportTCPSpec) { *out = new(intstr.IntOrString) **out = **in } + if in.ProxyProtocol != nil { + in, out := &in.ProxyProtocol, &out.ProxyProtocol + *out = new(dynamic.ProxyProtocol) + **out = **in + } if in.TerminationDelay != nil { in, out := &in.TerminationDelay, &out.TerminationDelay *out = new(intstr.IntOrString) diff --git a/pkg/provider/kubernetes/gateway/annotations.go b/pkg/provider/kubernetes/gateway/annotations.go index 8e82ae603..b17cc3cb1 100644 --- a/pkg/provider/kubernetes/gateway/annotations.go +++ b/pkg/provider/kubernetes/gateway/annotations.go @@ -16,7 +16,7 @@ type ServiceConfig struct { // Service is the service's configuration from annotations. type Service struct { - NativeLB bool `json:"nativeLB"` + NativeLB *bool `json:"nativeLB"` } func parseServiceAnnotations(annotations map[string]string) (ServiceConfig, error) { diff --git a/pkg/provider/kubernetes/gateway/annotations_test.go b/pkg/provider/kubernetes/gateway/annotations_test.go index 80537526a..5ca8c994d 100644 --- a/pkg/provider/kubernetes/gateway/annotations_test.go +++ b/pkg/provider/kubernetes/gateway/annotations_test.go @@ -5,6 +5,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "k8s.io/utils/ptr" ) func Test_parseServiceConfig(t *testing.T) { @@ -22,7 +23,7 @@ func Test_parseServiceConfig(t *testing.T) { }, expected: ServiceConfig{ Service: Service{ - NativeLB: true, + NativeLB: ptr.To(true), }, }, }, diff --git a/pkg/provider/kubernetes/gateway/client.go b/pkg/provider/kubernetes/gateway/client.go index 6c704e99e..109a0f9d7 100644 --- a/pkg/provider/kubernetes/gateway/client.go +++ b/pkg/provider/kubernetes/gateway/client.go @@ -10,6 +10,7 @@ import ( "time" "github.com/rs/zerolog/log" + "github.com/traefik/traefik/v3/pkg/provider/kubernetes/k8s" "github.com/traefik/traefik/v3/pkg/types" corev1 "k8s.io/api/core/v1" discoveryv1 "k8s.io/api/discovery/v1" @@ -33,22 +34,6 @@ import ( const resyncPeriod = 10 * time.Minute -type resourceEventHandler struct { - ev chan<- interface{} -} - -func (reh *resourceEventHandler) OnAdd(obj interface{}, _ bool) { - eventHandlerFunc(reh.ev, obj) -} - -func (reh *resourceEventHandler) OnUpdate(_, newObj interface{}) { - eventHandlerFunc(reh.ev, newObj) -} - -func (reh *resourceEventHandler) OnDelete(obj interface{}) { - eventHandlerFunc(reh.ev, obj) -} - type clientWrapper struct { csGateway gateclientset.Interface csKube kclientset.Interface @@ -145,7 +130,7 @@ func newExternalClusterClient(endpoint, caFilePath string, token types.FileOrCon // WatchAll starts namespace-specific controllers for all relevant kinds. func (c *clientWrapper) WatchAll(namespaces []string, stopCh <-chan struct{}) (<-chan interface{}, error) { eventCh := make(chan interface{}, 1) - eventHandler := &resourceEventHandler{ev: eventCh} + eventHandler := &k8s.ResourceEventHandler{Ev: eventCh} if len(namespaces) == 0 { namespaces = []string{metav1.NamespaceAll} @@ -815,16 +800,6 @@ func (c *clientWrapper) isWatchedNamespace(namespace string) bool { return slices.Contains(c.watchedNamespaces, namespace) } -// eventHandlerFunc will pass the obj on to the events channel or drop it. -// This is so passing the events along won't block in the case of high volume. -// The events are only used for signaling anyway so dropping a few is ok. -func eventHandlerFunc(events chan<- interface{}, obj interface{}) { - select { - case events <- obj: - default: - } -} - // translateNotFoundError will translate a "not found" error to a boolean return // value which indicates if the resource exists and a nil error. func translateNotFoundError(err error) (bool, error) { diff --git a/pkg/provider/kubernetes/gateway/fixtures/httproute/simple_nativelb_disabled.yml b/pkg/provider/kubernetes/gateway/fixtures/httproute/simple_nativelb_disabled.yml new file mode 100644 index 000000000..989de4446 --- /dev/null +++ b/pkg/provider/kubernetes/gateway/fixtures/httproute/simple_nativelb_disabled.yml @@ -0,0 +1,51 @@ +--- +kind: GatewayClass +apiVersion: gateway.networking.k8s.io/v1 +metadata: + name: my-gateway-class +spec: + controllerName: traefik.io/gateway-controller + +--- +kind: Gateway +apiVersion: gateway.networking.k8s.io/v1 +metadata: + name: my-gateway + namespace: default +spec: + gatewayClassName: my-gateway-class + listeners: # Use GatewayClass defaults for listener definition. + - name: http + protocol: HTTP + port: 80 + allowedRoutes: + kinds: + - kind: HTTPRoute + group: gateway.networking.k8s.io + namespaces: + from: Same + +--- +kind: HTTPRoute +apiVersion: gateway.networking.k8s.io/v1 +metadata: + name: http-app-1 + namespace: default +spec: + parentRefs: + - name: my-gateway + kind: Gateway + group: gateway.networking.k8s.io + hostnames: + - "foo.com" + rules: + - matches: + - path: + type: Exact + value: /bar + backendRefs: + - name: whoami-native-disabled + port: 80 + weight: 1 + kind: Service + group: "" diff --git a/pkg/provider/kubernetes/gateway/fixtures/httproute/with_app_protocol_service.yml b/pkg/provider/kubernetes/gateway/fixtures/httproute/with_app_protocol_service.yml index c364c8f52..4f51360a0 100644 --- a/pkg/provider/kubernetes/gateway/fixtures/httproute/with_app_protocol_service.yml +++ b/pkg/provider/kubernetes/gateway/fixtures/httproute/with_app_protocol_service.yml @@ -59,3 +59,13 @@ spec: weight: 1 kind: Service group: "" + - name: whoami-HTTP + port: 80 + weight: 1 + kind: Service + group: "" + - name: whoami-HTTPS + port: 443 + weight: 1 + kind: Service + group: "" diff --git a/pkg/provider/kubernetes/gateway/fixtures/services.yml b/pkg/provider/kubernetes/gateway/fixtures/services.yml index 65376431b..143dc8656 100644 --- a/pkg/provider/kubernetes/gateway/fixtures/services.yml +++ b/pkg/provider/kubernetes/gateway/fixtures/services.yml @@ -430,6 +430,80 @@ spec: name: wss appProtocol: kubernetes.io/wss +--- +kind: EndpointSlice +apiVersion: discovery.k8s.io/v1 +metadata: + name: whoami-HTTPS-abc + namespace: default + labels: + kubernetes.io/service-name: whoami-HTTPS + +addressType: IPv4 +ports: + - name: websecure + port: 8443 +endpoints: + - addresses: + - 10.10.0.16 + conditions: + ready: true + +--- +apiVersion: v1 +kind: Service +metadata: + name: whoami-HTTPS + namespace: default + +spec: + ports: + - name: websecure + protocol: TCP + appProtocol: HTTPS + port: 443 + targetPort: websecure + selector: + app: containous + task: whoami-HTTPS + +--- +kind: EndpointSlice +apiVersion: discovery.k8s.io/v1 +metadata: + name: whoami-HTTP-abc + namespace: default + labels: + kubernetes.io/service-name: whoami-HTTP + +addressType: IPv4 +ports: + - name: web + port: 8080 +endpoints: + - addresses: + - 10.10.0.17 + conditions: + ready: true + +--- +apiVersion: v1 +kind: Service +metadata: + name: whoami-HTTP + namespace: default + +spec: + ports: + - name: web + protocol: TCP + port: 80 + appProtocol: HTTP + targetPort: web + selector: + app: containous + task: whoami-HTTP + --- apiVersion: v1 kind: Service @@ -471,3 +545,82 @@ spec: - protocol: TCP port: 10000 name: tcp-2 + +--- +apiVersion: v1 +kind: Service +metadata: + name: whoami-native-disabled + namespace: default + annotations: + traefik.io/service.nativelb: "false" +spec: + clusterIP: 10.10.10.2 + ports: + - name: web + protocol: TCP + port: 80 + targetPort: web + selector: + app: containous + task: whoami + +--- +kind: EndpointSlice +apiVersion: discovery.k8s.io/v1 +metadata: + name: whoami-native-disabled-abc + namespace: default + labels: + kubernetes.io/service-name: whoami-native-disabled +addressType: IPv4 +ports: + - name: web + port: 80 +endpoints: + - addresses: + - 10.10.0.20 + - 10.10.0.21 + conditions: + ready: true + +--- +apiVersion: v1 +kind: Service +metadata: + name: whoamitcp-native-disabled + namespace: default + annotations: + traefik.io/service.nativelb: "false" +spec: + clusterIP: 10.10.10.3 + ports: + - protocol: TCP + port: 9000 + name: tcp-1 + - protocol: TCP + port: 10000 + name: tcp-2 + +--- +kind: EndpointSlice +apiVersion: discovery.k8s.io/v1 +metadata: + name: whoamitcp-native-disabled-abc + namespace: default + labels: + kubernetes.io/service-name: whoamitcp-native-disabled +addressType: IPv4 +ports: + - name: tcp-1 + protocol: TCP + port: 9000 + - name: tcp-2 + protocol: TCP + port: 10000 +endpoints: + - addresses: + - 10.10.0.30 + - 10.10.0.31 + conditions: + ready: true diff --git a/pkg/provider/kubernetes/gateway/fixtures/tcproute/simple_nativelb_disabled.yml b/pkg/provider/kubernetes/gateway/fixtures/tcproute/simple_nativelb_disabled.yml new file mode 100644 index 000000000..15f2518fd --- /dev/null +++ b/pkg/provider/kubernetes/gateway/fixtures/tcproute/simple_nativelb_disabled.yml @@ -0,0 +1,46 @@ +--- +kind: GatewayClass +apiVersion: gateway.networking.k8s.io/v1 +metadata: + name: my-gateway-class + namespace: default +spec: + controllerName: traefik.io/gateway-controller + +--- +kind: Gateway +apiVersion: gateway.networking.k8s.io/v1 +metadata: + name: my-tcp-gateway + namespace: default +spec: + gatewayClassName: my-gateway-class + listeners: # Use GatewayClass defaults for listener definition. + - name: tcp + protocol: TCP + port: 9000 + allowedRoutes: + namespaces: + from: Same + kinds: + - kind: TCPRoute + group: gateway.networking.k8s.io + +--- +kind: TCPRoute +apiVersion: gateway.networking.k8s.io/v1alpha2 +metadata: + name: tcp-app-1 + namespace: default +spec: + parentRefs: + - name: my-tcp-gateway + kind: Gateway + group: gateway.networking.k8s.io + rules: + - backendRefs: + - name: whoamitcp-native-disabled + port: 9000 + weight: 1 + kind: Service + group: "" \ No newline at end of file diff --git a/pkg/provider/kubernetes/gateway/grpcroute.go b/pkg/provider/kubernetes/gateway/grpcroute.go index ecf208568..39b84181a 100644 --- a/pkg/provider/kubernetes/gateway/grpcroute.go +++ b/pkg/provider/kubernetes/gateway/grpcroute.go @@ -430,7 +430,7 @@ func getGRPCServiceProtocol(portSpec corev1.ServicePort) (string, error) { return schemeH2C, nil } - switch ap := *portSpec.AppProtocol; ap { + switch ap := strings.ToLower(*portSpec.AppProtocol); ap { case appProtocolH2C: return schemeH2C, nil case appProtocolHTTPS: diff --git a/pkg/provider/kubernetes/gateway/httproute.go b/pkg/provider/kubernetes/gateway/httproute.go index 8af4cadd4..ebee5fd1f 100644 --- a/pkg/provider/kubernetes/gateway/httproute.go +++ b/pkg/provider/kubernetes/gateway/httproute.go @@ -817,7 +817,7 @@ func getHTTPServiceProtocol(portSpec corev1.ServicePort) (string, error) { return protocol, nil } - switch ap := *portSpec.AppProtocol; ap { + switch ap := strings.ToLower(*portSpec.AppProtocol); ap { case appProtocolH2C: return schemeH2C, nil case appProtocolHTTP, appProtocolWS: diff --git a/pkg/provider/kubernetes/gateway/kubernetes.go b/pkg/provider/kubernetes/gateway/kubernetes.go index 1f31045ca..3010f6bc0 100644 --- a/pkg/provider/kubernetes/gateway/kubernetes.go +++ b/pkg/provider/kubernetes/gateway/kubernetes.go @@ -19,7 +19,7 @@ import ( ptypes "github.com/traefik/paerser/types" "github.com/traefik/traefik/v3/pkg/config/dynamic" "github.com/traefik/traefik/v3/pkg/job" - "github.com/traefik/traefik/v3/pkg/logs" + "github.com/traefik/traefik/v3/pkg/observability/logs" traefikv1alpha1 "github.com/traefik/traefik/v3/pkg/provider/kubernetes/crd/traefikio/v1alpha1" "github.com/traefik/traefik/v3/pkg/provider/kubernetes/k8s" "github.com/traefik/traefik/v3/pkg/safe" @@ -918,7 +918,12 @@ func (p *Provider) getBackendAddresses(namespace string, ref gatev1.BackendRef) return nil, corev1.ServicePort{}, fmt.Errorf("parsing service annotations config: %w", err) } - if p.NativeLBByDefault || annotationsConfig.Service.NativeLB { + nativeLB := p.NativeLBByDefault + if annotationsConfig.Service.NativeLB != nil { + nativeLB = *annotationsConfig.Service.NativeLB + } + + if nativeLB { if service.Spec.ClusterIP == "" || service.Spec.ClusterIP == "None" { return nil, corev1.ServicePort{}, fmt.Errorf("no clusterIP found for service: %s/%s", service.Namespace, service.Name) } diff --git a/pkg/provider/kubernetes/gateway/kubernetes_test.go b/pkg/provider/kubernetes/gateway/kubernetes_test.go index 70000cfd2..90e93f64f 100644 --- a/pkg/provider/kubernetes/gateway/kubernetes_test.go +++ b/pkg/provider/kubernetes/gateway/kubernetes_test.go @@ -1,7 +1,6 @@ package gateway import ( - "context" "errors" "net/http" "os" @@ -75,14 +74,14 @@ func TestGatewayClassLabelSelector(t *testing.T) { client: client, } - _ = p.loadConfigurationFromGateways(context.Background()) + _ = p.loadConfigurationFromGateways(t.Context()) - gw, err := gwClient.GatewayV1().Gateways("default").Get(context.Background(), "traefik-external", metav1.GetOptions{}) + gw, err := gwClient.GatewayV1().Gateways("default").Get(t.Context(), "traefik-external", metav1.GetOptions{}) require.NoError(t, err) assert.Empty(t, gw.Status.Addresses) - gw, err = gwClient.GatewayV1().Gateways("default").Get(context.Background(), "traefik-internal", metav1.GetOptions{}) + gw, err = gwClient.GatewayV1().Gateways("default").Get(t.Context(), "traefik-internal", metav1.GetOptions{}) require.NoError(t, err) require.Len(t, gw.Status.Addresses, 1) require.NotNil(t, gw.Status.Addresses[0].Type) @@ -2523,6 +2522,69 @@ func TestLoadHTTPRoutes(t *testing.T) { TLS: &dynamic.TLSConfiguration{}, }, }, + { + desc: "Simple HTTPRoute with NativeLBByDefault enabled but service has disabled nativelb", + paths: []string{"services.yml", "httproute/simple_nativelb_disabled.yml"}, + nativeLB: true, + entryPoints: map[string]Entrypoint{"web": { + Address: ":80", + }}, + expected: &dynamic.Configuration{ + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Middlewares: map[string]*dynamic.TCPMiddleware{}, + Services: map[string]*dynamic.TCPService{}, + ServersTransports: map[string]*dynamic.TCPServersTransport{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{ + "httproute-default-http-app-1-gw-default-my-gateway-ep-web-0-1c0cf64bde37d9d0df06": { + EntryPoints: []string{"web"}, + Service: "httproute-default-http-app-1-gw-default-my-gateway-ep-web-0-1c0cf64bde37d9d0df06-wrr", + Rule: "Host(`foo.com`) && Path(`/bar`)", + Priority: 100008, + RuleSyntax: "default", + }, + }, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{ + "httproute-default-http-app-1-gw-default-my-gateway-ep-web-0-1c0cf64bde37d9d0df06-wrr": { + Weighted: &dynamic.WeightedRoundRobin{ + Services: []dynamic.WRRService{ + { + Name: "default-whoami-native-disabled-http-80", + Weight: ptr.To(1), + }, + }, + }, + }, + "default-whoami-native-disabled-http-80": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + Strategy: dynamic.BalancerStrategyWRR, + Servers: []dynamic.Server{ + { + URL: "http://10.10.0.20:80", + }, + { + URL: "http://10.10.0.21:80", + }, + }, + PassHostHeader: ptr.To(true), + ResponseForwarding: &dynamic.ResponseForwarding{ + FlushInterval: ptypes.Duration(100 * time.Millisecond), + }, + }, + }, + }, + ServersTransports: map[string]*dynamic.ServersTransport{}, + }, + TLS: &dynamic.TLSConfiguration{}, + }, + }, } for _, test := range testCases { @@ -2556,7 +2618,7 @@ func TestLoadHTTPRoutes(t *testing.T) { client: client, } - conf := p.loadConfigurationFromGateways(context.Background()) + conf := p.loadConfigurationFromGateways(t.Context()) assert.Equal(t, test.expected, conf) }) } @@ -2897,6 +2959,14 @@ func TestLoadHTTPRoutes_backendExtensionRef(t *testing.T) { Name: "default-whoami-wss-http-80", Weight: ptr.To(1), }, + { + Name: "default-whoami-HTTP-http-80", + Weight: ptr.To(1), + }, + { + Name: "default-whoami-HTTPS-http-443", + Weight: ptr.To(1), + }, }, }, }, @@ -2942,6 +3012,34 @@ func TestLoadHTTPRoutes_backendExtensionRef(t *testing.T) { }, }, }, + "default-whoami-HTTPS-http-443": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + Strategy: dynamic.BalancerStrategyWRR, + Servers: []dynamic.Server{ + { + URL: "https://10.10.0.16:8443", + }, + }, + PassHostHeader: ptr.To(true), + ResponseForwarding: &dynamic.ResponseForwarding{ + FlushInterval: ptypes.Duration(100 * time.Millisecond), + }, + }, + }, + "default-whoami-HTTP-http-80": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + Strategy: dynamic.BalancerStrategyWRR, + Servers: []dynamic.Server{ + { + URL: "http://10.10.0.17:8080", + }, + }, + PassHostHeader: ptr.To(true), + ResponseForwarding: &dynamic.ResponseForwarding{ + FlushInterval: ptypes.Duration(100 * time.Millisecond), + }, + }, + }, }, ServersTransports: map[string]*dynamic.ServersTransport{}, }, @@ -2983,7 +3081,7 @@ func TestLoadHTTPRoutes_backendExtensionRef(t *testing.T) { p.RegisterBackendFuncs(group, kind, backendFunc) } } - conf := p.loadConfigurationFromGateways(context.Background()) + conf := p.loadConfigurationFromGateways(t.Context()) assert.Equal(t, test.expected, conf) }) } @@ -3269,7 +3367,7 @@ func TestLoadHTTPRoutes_filterExtensionRef(t *testing.T) { p.RegisterFilterFuncs(group, kind, filterFunc) } } - conf := p.loadConfigurationFromGateways(context.Background()) + conf := p.loadConfigurationFromGateways(t.Context()) assert.Equal(t, test.expected, conf) }) } @@ -3561,7 +3659,7 @@ func TestLoadGRPCRoutes_filterExtensionRef(t *testing.T) { p.RegisterFilterFuncs(group, kind, filterFunc) } } - conf := p.loadConfigurationFromGateways(context.Background()) + conf := p.loadConfigurationFromGateways(t.Context()) assert.Equal(t, test.expected, conf) }) } @@ -4430,6 +4528,63 @@ func TestLoadTCPRoutes(t *testing.T) { TLS: &dynamic.TLSConfiguration{}, }, }, + { + desc: "Simple TCPRoute with NativeLBByDefault enabled but service has disabled nativelb", + paths: []string{"services.yml", "tcproute/simple_nativelb_disabled.yml"}, + nativeLB: true, + entryPoints: map[string]Entrypoint{ + "tcp": {Address: ":9000"}, + }, + expected: &dynamic.Configuration{ + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{ + "tcproute-default-tcp-app-1-gw-default-my-tcp-gateway-ep-tcp-0-e3b0c44298fc1c149afb": { + EntryPoints: []string{"tcp"}, + Service: "tcproute-default-tcp-app-1-gw-default-my-tcp-gateway-ep-tcp-0-e3b0c44298fc1c149afb-wrr", + Rule: "HostSNI(`*`)", + RuleSyntax: "default", + }, + }, + Middlewares: map[string]*dynamic.TCPMiddleware{}, + Services: map[string]*dynamic.TCPService{ + "tcproute-default-tcp-app-1-gw-default-my-tcp-gateway-ep-tcp-0-e3b0c44298fc1c149afb-wrr": { + Weighted: &dynamic.TCPWeightedRoundRobin{ + Services: []dynamic.TCPWRRService{ + { + Name: "default-whoamitcp-native-disabled-9000", + Weight: ptr.To(1), + }, + }, + }, + }, + "default-whoamitcp-native-disabled-9000": { + LoadBalancer: &dynamic.TCPServersLoadBalancer{ + Servers: []dynamic.TCPServer{ + { + Address: "10.10.0.30:9000", + }, + { + Address: "10.10.0.31:9000", + }, + }, + }, + }, + }, + ServersTransports: map[string]*dynamic.TCPServersTransport{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, + ServersTransports: map[string]*dynamic.ServersTransport{}, + }, + TLS: &dynamic.TLSConfiguration{}, + }, + }, } for _, test := range testCases { @@ -4463,7 +4618,7 @@ func TestLoadTCPRoutes(t *testing.T) { client: client, } - conf := p.loadConfigurationFromGateways(context.Background()) + conf := p.loadConfigurationFromGateways(t.Context()) assert.Equal(t, test.expected, conf) }) } @@ -5742,7 +5897,7 @@ func TestLoadTLSRoutes(t *testing.T) { client: client, } - conf := p.loadConfigurationFromGateways(context.Background()) + conf := p.loadConfigurationFromGateways(t.Context()) assert.Equal(t, test.expected, conf) }) } @@ -6798,7 +6953,7 @@ func TestLoadMixedRoutes(t *testing.T) { client: client, } - conf := p.loadConfigurationFromGateways(context.Background()) + conf := p.loadConfigurationFromGateways(t.Context()) assert.Equal(t, test.expected, conf) }) } @@ -7134,7 +7289,7 @@ func TestLoadRoutesWithReferenceGrants(t *testing.T) { client: client, } - conf := p.loadConfigurationFromGateways(context.Background()) + conf := p.loadConfigurationFromGateways(t.Context()) assert.Equal(t, test.expected, conf) }) } @@ -8186,7 +8341,7 @@ func newGatewaySimpleClientSet(t *testing.T, objects ...runtime.Object) *gatefak continue } - _, err := client.GatewayV1().Gateways(gateway.Namespace).Create(context.Background(), gateway, metav1.CreateOptions{}) + _, err := client.GatewayV1().Gateways(gateway.Namespace).Create(t.Context(), gateway, metav1.CreateOptions{}) require.NoError(t, err) } diff --git a/pkg/provider/kubernetes/ingress-nginx/annotations.go b/pkg/provider/kubernetes/ingress-nginx/annotations.go new file mode 100644 index 000000000..6536abed5 --- /dev/null +++ b/pkg/provider/kubernetes/ingress-nginx/annotations.go @@ -0,0 +1,115 @@ +package ingressnginx + +import ( + "errors" + "reflect" + "strconv" + "strings" + + netv1 "k8s.io/api/networking/v1" +) + +type ingressConfig struct { + AuthType *string `annotation:"nginx.ingress.kubernetes.io/auth-type"` + AuthSecret *string `annotation:"nginx.ingress.kubernetes.io/auth-secret"` + AuthRealm *string `annotation:"nginx.ingress.kubernetes.io/auth-realm"` + AuthSecretType *string `annotation:"nginx.ingress.kubernetes.io/auth-secret-type"` + + AuthURL *string `annotation:"nginx.ingress.kubernetes.io/auth-url"` + AuthResponseHeaders *string `annotation:"nginx.ingress.kubernetes.io/auth-response-headers"` + + ForceSSLRedirect *bool `annotation:"nginx.ingress.kubernetes.io/force-ssl-redirect"` + SSLRedirect *bool `annotation:"nginx.ingress.kubernetes.io/ssl-redirect"` + + SSLPassthrough *bool `annotation:"nginx.ingress.kubernetes.io/ssl-passthrough"` + + UseRegex *bool `annotation:"nginx.ingress.kubernetes.io/use-regex"` + + Affinity *string `annotation:"nginx.ingress.kubernetes.io/affinity"` + SessionCookieName *string `annotation:"nginx.ingress.kubernetes.io/session-cookie-name"` + SessionCookieSecure *bool `annotation:"nginx.ingress.kubernetes.io/session-cookie-secure"` + SessionCookiePath *string `annotation:"nginx.ingress.kubernetes.io/session-cookie-path"` + SessionCookieDomain *string `annotation:"nginx.ingress.kubernetes.io/session-cookie-domain"` + SessionCookieSameSite *string `annotation:"nginx.ingress.kubernetes.io/session-cookie-samesite"` + SessionCookieMaxAge *int `annotation:"nginx.ingress.kubernetes.io/session-cookie-max-age"` + + ServiceUpstream *bool `annotation:"nginx.ingress.kubernetes.io/service-upstream"` + + BackendProtocol *string `annotation:"nginx.ingress.kubernetes.io/backend-protocol"` + + ProxySSLSecret *string `annotation:"nginx.ingress.kubernetes.io/proxy-ssl-secret"` + ProxySSLVerify *string `annotation:"nginx.ingress.kubernetes.io/proxy-ssl-verify"` + ProxySSLName *string `annotation:"nginx.ingress.kubernetes.io/proxy-ssl-name"` + ProxySSLServerName *string `annotation:"nginx.ingress.kubernetes.io/proxy-ssl-server-name"` + + EnableCORS *bool `annotation:"nginx.ingress.kubernetes.io/enable-cors"` + EnableCORSAllowCredentials *bool `annotation:"nginx.ingress.kubernetes.io/cors-allow-credentials"` + CORSExposeHeaders *[]string `annotation:"nginx.ingress.kubernetes.io/cors-expose-headers"` + CORSAllowHeaders *[]string `annotation:"nginx.ingress.kubernetes.io/cors-allow-headers"` + CORSAllowMethods *[]string `annotation:"nginx.ingress.kubernetes.io/cors-allow-methods"` + CORSAllowOrigin *[]string `annotation:"nginx.ingress.kubernetes.io/cors-allow-origin"` + CORSMaxAge *int `annotation:"nginx.ingress.kubernetes.io/cors-max-age"` +} + +// parseIngressConfig parses the annotations from an Ingress object into an ingressConfig struct. +func parseIngressConfig(ing *netv1.Ingress) (ingressConfig, error) { + cfg := ingressConfig{} + cfgType := reflect.TypeOf(cfg) + cfgValue := reflect.ValueOf(&cfg).Elem() + + for i := range cfgType.NumField() { + field := cfgType.Field(i) + annotation := field.Tag.Get("annotation") + if annotation == "" { + continue + } + + val, ok := ing.GetAnnotations()[annotation] + if !ok { + continue + } + + switch field.Type.Elem().Kind() { + case reflect.String: + cfgValue.Field(i).Set(reflect.ValueOf(&val)) + case reflect.Bool: + parsed, err := strconv.ParseBool(val) + if err == nil { + cfgValue.Field(i).Set(reflect.ValueOf(&parsed)) + } + case reflect.Int: + parsed, err := strconv.Atoi(val) + if err == nil { + cfgValue.Field(i).Set(reflect.ValueOf(&parsed)) + } + case reflect.Slice: + if field.Type.Elem().Elem().Kind() == reflect.String { + // Handle slice of strings + var slice []string + elements := strings.Split(val, ",") + for _, elt := range elements { + slice = append(slice, strings.TrimSpace(elt)) + } + cfgValue.Field(i).Set(reflect.ValueOf(&slice)) + } else { + return cfg, errors.New("unsupported slice type in annotations") + } + default: + return cfg, errors.New("unsupported kind") + } + } + + return cfg, nil +} + +// parseBackendProtocol parses the backend protocol annotation and returns the corresponding protocol string. +func parseBackendProtocol(bp string) string { + switch strings.ToUpper(bp) { + case "HTTPS", "GRPCS": + return "https" + case "GRPC": + return "h2c" + default: + return "http" + } +} diff --git a/pkg/provider/kubernetes/ingress-nginx/annotations_test.go b/pkg/provider/kubernetes/ingress-nginx/annotations_test.go new file mode 100644 index 000000000..36ce89434 --- /dev/null +++ b/pkg/provider/kubernetes/ingress-nginx/annotations_test.go @@ -0,0 +1,76 @@ +package ingressnginx + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + netv1 "k8s.io/api/networking/v1" + "k8s.io/utils/ptr" +) + +func Test_parseIngressConfig(t *testing.T) { + tests := []struct { + desc string + annotations map[string]string + expected ingressConfig + }{ + { + desc: "all fields set", + annotations: map[string]string{ + "nginx.ingress.kubernetes.io/ssl-passthrough": "true", + "nginx.ingress.kubernetes.io/affinity": "cookie", + "nginx.ingress.kubernetes.io/session-cookie-name": "mycookie", + "nginx.ingress.kubernetes.io/session-cookie-secure": "true", + "nginx.ingress.kubernetes.io/session-cookie-path": "/foo", + "nginx.ingress.kubernetes.io/session-cookie-domain": "example.com", + "nginx.ingress.kubernetes.io/session-cookie-samesite": "Strict", + "nginx.ingress.kubernetes.io/session-cookie-max-age": "3600", + "nginx.ingress.kubernetes.io/backend-protocol": "HTTPS", + "nginx.ingress.kubernetes.io/cors-expose-headers": "foo, bar", + }, + expected: ingressConfig{ + SSLPassthrough: ptr.To(true), + Affinity: ptr.To("cookie"), + SessionCookieName: ptr.To("mycookie"), + SessionCookieSecure: ptr.To(true), + SessionCookiePath: ptr.To("/foo"), + SessionCookieDomain: ptr.To("example.com"), + SessionCookieSameSite: ptr.To("Strict"), + SessionCookieMaxAge: ptr.To(3600), + BackendProtocol: ptr.To("HTTPS"), + CORSExposeHeaders: ptr.To([]string{"foo", "bar"}), + }, + }, + { + desc: "missing fields", + annotations: map[string]string{ + "nginx.ingress.kubernetes.io/ssl-passthrough": "false", + }, + expected: ingressConfig{ + SSLPassthrough: ptr.To(false), + }, + }, + { + desc: "invalid bool and int", + annotations: map[string]string{ + "nginx.ingress.kubernetes.io/ssl-passthrough": "notabool", + "nginx.ingress.kubernetes.io/session-cookie-max-age (in seconds)": "notanint", + }, + }, + } + + for _, test := range tests { + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + var ing netv1.Ingress + ing.SetAnnotations(test.annotations) + + cfg, err := parseIngressConfig(&ing) + require.NoError(t, err) + + assert.Equal(t, test.expected, cfg) + }) + } +} diff --git a/pkg/provider/kubernetes/ingress-nginx/client.go b/pkg/provider/kubernetes/ingress-nginx/client.go new file mode 100644 index 000000000..93eae4a09 --- /dev/null +++ b/pkg/provider/kubernetes/ingress-nginx/client.go @@ -0,0 +1,384 @@ +package ingressnginx + +import ( + "context" + "errors" + "fmt" + "os" + "path/filepath" + "runtime" + "slices" + "time" + + "github.com/rs/zerolog/log" + "github.com/traefik/traefik/v3/pkg/provider/kubernetes/k8s" + "github.com/traefik/traefik/v3/pkg/types" + traefikversion "github.com/traefik/traefik/v3/pkg/version" + corev1 "k8s.io/api/core/v1" + discoveryv1 "k8s.io/api/discovery/v1" + netv1 "k8s.io/api/networking/v1" + kerror "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/selection" + kinformers "k8s.io/client-go/informers" + kclientset "k8s.io/client-go/kubernetes" + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/clientcmd" +) + +const ( + resyncPeriod = 10 * time.Minute + defaultTimeout = 5 * time.Second +) + +type clientWrapper struct { + clientset kclientset.Interface + clusterScopeFactory kinformers.SharedInformerFactory + factoriesKube map[string]kinformers.SharedInformerFactory + factoriesSecret map[string]kinformers.SharedInformerFactory + factoriesIngress map[string]kinformers.SharedInformerFactory + isNamespaceAll bool + watchedNamespaces []string + + ignoreIngressClasses bool +} + +// newInClusterClient returns a new Provider client that is expected to run +// inside the cluster. +func newInClusterClient(endpoint string) (*clientWrapper, error) { + config, err := rest.InClusterConfig() + if err != nil { + return nil, fmt.Errorf("failed to create in-cluster configuration: %w", err) + } + + if endpoint != "" { + config.Host = endpoint + } + + return createClientFromConfig(config) +} + +func newExternalClusterClientFromFile(file string) (*clientWrapper, error) { + configFromFlags, err := clientcmd.BuildConfigFromFlags("", file) + if err != nil { + return nil, err + } + return createClientFromConfig(configFromFlags) +} + +// newExternalClusterClient returns a new Provider client that may run outside +// of the cluster. +// The endpoint parameter must not be empty. +func newExternalClusterClient(endpoint, caFilePath string, token types.FileOrContent) (*clientWrapper, error) { + if endpoint == "" { + return nil, errors.New("endpoint missing for external cluster client") + } + + tokenData, err := token.Read() + if err != nil { + return nil, fmt.Errorf("read token: %w", err) + } + + config := &rest.Config{ + Host: endpoint, + BearerToken: string(tokenData), + } + + if caFilePath != "" { + caData, err := os.ReadFile(caFilePath) + if err != nil { + return nil, fmt.Errorf("failed to read CA file %s: %w", caFilePath, err) + } + + config.TLSClientConfig = rest.TLSClientConfig{CAData: caData} + } + return createClientFromConfig(config) +} + +func createClientFromConfig(c *rest.Config) (*clientWrapper, error) { + c.UserAgent = fmt.Sprintf( + "%s/%s (%s/%s) kubernetes/ingress", + filepath.Base(os.Args[0]), + traefikversion.Version, + runtime.GOOS, + runtime.GOARCH, + ) + + clientset, err := kclientset.NewForConfig(c) + if err != nil { + return nil, err + } + + return newClient(clientset), nil +} + +func newClient(clientSet kclientset.Interface) *clientWrapper { + return &clientWrapper{ + clientset: clientSet, + factoriesSecret: make(map[string]kinformers.SharedInformerFactory), + factoriesIngress: make(map[string]kinformers.SharedInformerFactory), + factoriesKube: make(map[string]kinformers.SharedInformerFactory), + } +} + +// WatchAll starts namespace-specific controllers for all relevant kinds. +func (c *clientWrapper) WatchAll(ctx context.Context, namespace, namespaceSelector string) (<-chan interface{}, error) { + stopCh := ctx.Done() + eventCh := make(chan interface{}, 1) + eventHandler := &k8s.ResourceEventHandler{Ev: eventCh} + + c.ignoreIngressClasses = false + _, err := c.clientset.NetworkingV1().IngressClasses().List(ctx, metav1.ListOptions{Limit: 1}) + if err != nil { + if !kerror.IsNotFound(err) { + if kerror.IsForbidden(err) { + c.ignoreIngressClasses = true + } + } + } + + if namespaceSelector != "" { + ns, err := c.clientset.CoreV1().Namespaces().List(ctx, metav1.ListOptions{LabelSelector: namespaceSelector}) + if err != nil { + return nil, fmt.Errorf("listing namespaces: %w", err) + } + for _, item := range ns.Items { + c.watchedNamespaces = append(c.watchedNamespaces, item.Name) + } + } else { + c.isNamespaceAll = namespace == metav1.NamespaceAll + c.watchedNamespaces = []string{namespace} + } + + notOwnedByHelm := func(opts *metav1.ListOptions) { + opts.LabelSelector = "owner!=helm" + } + + for _, ns := range c.watchedNamespaces { + factoryIngress := kinformers.NewSharedInformerFactoryWithOptions(c.clientset, resyncPeriod, kinformers.WithNamespace(ns)) + + _, err := factoryIngress.Networking().V1().Ingresses().Informer().AddEventHandler(eventHandler) + if err != nil { + return nil, err + } + + c.factoriesIngress[ns] = factoryIngress + + factoryKube := kinformers.NewSharedInformerFactoryWithOptions(c.clientset, resyncPeriod, kinformers.WithNamespace(ns)) + _, err = factoryKube.Core().V1().Services().Informer().AddEventHandler(eventHandler) + if err != nil { + return nil, err + } + _, err = factoryKube.Discovery().V1().EndpointSlices().Informer().AddEventHandler(eventHandler) + if err != nil { + return nil, err + } + c.factoriesKube[ns] = factoryKube + + factorySecret := kinformers.NewSharedInformerFactoryWithOptions(c.clientset, resyncPeriod, kinformers.WithNamespace(ns), kinformers.WithTweakListOptions(notOwnedByHelm)) + _, err = factorySecret.Core().V1().Secrets().Informer().AddEventHandler(eventHandler) + if err != nil { + return nil, err + } + c.factoriesSecret[ns] = factorySecret + } + + for _, ns := range c.watchedNamespaces { + c.factoriesIngress[ns].Start(stopCh) + c.factoriesKube[ns].Start(stopCh) + c.factoriesSecret[ns].Start(stopCh) + } + + for _, ns := range c.watchedNamespaces { + for t, ok := range c.factoriesIngress[ns].WaitForCacheSync(stopCh) { + if !ok { + return nil, fmt.Errorf("timed out waiting for controller caches to sync %s in namespace %q", t.String(), ns) + } + } + + for t, ok := range c.factoriesKube[ns].WaitForCacheSync(stopCh) { + if !ok { + return nil, fmt.Errorf("timed out waiting for controller caches to sync %s in namespace %q", t.String(), ns) + } + } + + for t, ok := range c.factoriesSecret[ns].WaitForCacheSync(stopCh) { + if !ok { + return nil, fmt.Errorf("timed out waiting for controller caches to sync %s in namespace %q", t.String(), ns) + } + } + } + + c.clusterScopeFactory = kinformers.NewSharedInformerFactory(c.clientset, resyncPeriod) + + if !c.ignoreIngressClasses { + _, err = c.clusterScopeFactory.Networking().V1().IngressClasses().Informer().AddEventHandler(eventHandler) + if err != nil { + return nil, err + } + } + + c.clusterScopeFactory.Start(stopCh) + + for t, ok := range c.clusterScopeFactory.WaitForCacheSync(stopCh) { + if !ok { + return nil, fmt.Errorf("timed out waiting for controller caches to sync %s", t.String()) + } + } + + return eventCh, nil +} + +func (c *clientWrapper) ListIngressClasses() ([]*netv1.IngressClass, error) { + if c.ignoreIngressClasses { + return []*netv1.IngressClass{}, nil + } + + return c.clusterScopeFactory.Networking().V1().IngressClasses().Lister().List(labels.Everything()) +} + +// ListIngresses returns all Ingresses for observed namespaces in the cluster. +func (c *clientWrapper) ListIngresses() []*netv1.Ingress { + var results []*netv1.Ingress + + for ns, factory := range c.factoriesIngress { + // networking + listNew, err := factory.Networking().V1().Ingresses().Lister().List(labels.Everything()) + if err != nil { + log.Error().Err(err).Msgf("Failed to list ingresses in namespace %s", ns) + continue + } + + results = append(results, listNew...) + } + + return results +} + +// UpdateIngressStatus updates an Ingress with a provided status. +func (c *clientWrapper) UpdateIngressStatus(src *netv1.Ingress, ingStatus []netv1.IngressLoadBalancerIngress) error { + if !c.isWatchedNamespace(src.Namespace) { + return fmt.Errorf("failed to get ingress %s/%s: namespace is not within watched namespaces", src.Namespace, src.Name) + } + + ing, err := c.factoriesIngress[c.lookupNamespace(src.Namespace)].Networking().V1().Ingresses().Lister().Ingresses(src.Namespace).Get(src.Name) + if err != nil { + return fmt.Errorf("failed to get ingress %s/%s: %w", src.Namespace, src.Name, err) + } + + logger := log.With().Str("namespace", ing.Namespace).Str("ingress", ing.Name).Logger() + + if isLoadBalancerIngressEquals(ing.Status.LoadBalancer.Ingress, ingStatus) { + logger.Debug().Msg("Skipping ingress status update") + return nil + } + + ingCopy := ing.DeepCopy() + ingCopy.Status = netv1.IngressStatus{LoadBalancer: netv1.IngressLoadBalancerStatus{Ingress: ingStatus}} + + ctx, cancel := context.WithTimeout(context.Background(), defaultTimeout) + defer cancel() + + _, err = c.clientset.NetworkingV1().Ingresses(ingCopy.Namespace).UpdateStatus(ctx, ingCopy, metav1.UpdateOptions{}) + if err != nil { + return fmt.Errorf("failed to update ingress status %s/%s: %w", src.Namespace, src.Name, err) + } + + logger.Info().Msg("Updated ingress status") + return nil +} + +// GetService returns the named service from the given namespace. +func (c *clientWrapper) GetService(namespace, name string) (*corev1.Service, error) { + if !c.isWatchedNamespace(namespace) { + return nil, fmt.Errorf("failed to get service %s/%s: namespace is not within watched namespaces", namespace, name) + } + + return c.factoriesKube[c.lookupNamespace(namespace)].Core().V1().Services().Lister().Services(namespace).Get(name) +} + +// GetEndpointSlicesForService returns the EndpointSlices for the given service name in the given namespace. +func (c *clientWrapper) GetEndpointSlicesForService(namespace, serviceName string) ([]*discoveryv1.EndpointSlice, error) { + if !c.isWatchedNamespace(namespace) { + return nil, fmt.Errorf("failed to get endpointslices for service %s/%s: namespace is not within watched namespaces", namespace, serviceName) + } + + serviceLabelRequirement, err := labels.NewRequirement(discoveryv1.LabelServiceName, selection.Equals, []string{serviceName}) + if err != nil { + return nil, fmt.Errorf("failed to create service label selector requirement: %w", err) + } + serviceSelector := labels.NewSelector() + serviceSelector = serviceSelector.Add(*serviceLabelRequirement) + + return c.factoriesKube[c.lookupNamespace(namespace)].Discovery().V1().EndpointSlices().Lister().EndpointSlices(namespace).List(serviceSelector) +} + +// GetSecret returns the named secret from the given namespace. +func (c *clientWrapper) GetSecret(namespace, name string) (*corev1.Secret, error) { + if !c.isWatchedNamespace(namespace) { + return nil, fmt.Errorf("failed to get secret %s/%s: namespace is not within watched namespaces", namespace, name) + } + + return c.factoriesSecret[c.lookupNamespace(namespace)].Core().V1().Secrets().Lister().Secrets(namespace).Get(name) +} + +// lookupNamespace returns the lookup namespace key for the given namespace. +// When listening on all namespaces, it returns the client-go identifier ("") +// for all-namespaces. Otherwise, it returns the given namespace. +// The distinction is necessary because we index all informers on the special +// identifier iff all-namespaces are requested but receive specific namespace +// identifiers from the Kubernetes API, so we have to bridge this gap. +func (c *clientWrapper) lookupNamespace(ns string) string { + if c.isNamespaceAll { + return metav1.NamespaceAll + } + return ns +} + +// isWatchedNamespace checks to ensure that the namespace is being watched before we request +// it to ensure we don't panic by requesting an out-of-watch object. +func (c *clientWrapper) isWatchedNamespace(ns string) bool { + if c.isNamespaceAll { + return true + } + + return slices.Contains(c.watchedNamespaces, ns) +} + +// isLoadBalancerIngressEquals returns true if the given slices are equal, false otherwise. +func isLoadBalancerIngressEquals(aSlice, bSlice []netv1.IngressLoadBalancerIngress) bool { + if len(aSlice) != len(bSlice) { + return false + } + + aMap := make(map[string]struct{}) + for _, aIngress := range aSlice { + aMap[aIngress.Hostname+aIngress.IP] = struct{}{} + } + + for _, bIngress := range bSlice { + if _, exists := aMap[bIngress.Hostname+bIngress.IP]; !exists { + return false + } + } + + return true +} + +// filterIngressClass return a slice containing IngressClass matching either the annotation name or the controller. +func filterIngressClass(ingressClasses []*netv1.IngressClass, ingressClassByName bool, ingressClass, controllerClass string) []*netv1.IngressClass { + var filteredIngressClasses []*netv1.IngressClass + for _, ic := range ingressClasses { + if ingressClassByName && ic.Name == ingressClass { + return append(filteredIngressClasses, ic) + } + + if ic.Spec.Controller == controllerClass { + filteredIngressClasses = append(filteredIngressClasses, ic) + continue + } + } + + return filteredIngressClasses +} diff --git a/pkg/provider/kubernetes/ingress-nginx/client_test.go b/pkg/provider/kubernetes/ingress-nginx/client_test.go new file mode 100644 index 000000000..e6ed09596 --- /dev/null +++ b/pkg/provider/kubernetes/ingress-nginx/client_test.go @@ -0,0 +1,270 @@ +package ingressnginx + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + corev1 "k8s.io/api/core/v1" + discoveryv1 "k8s.io/api/discovery/v1" + netv1 "k8s.io/api/networking/v1" + kerror "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + kversion "k8s.io/apimachinery/pkg/version" + discoveryfake "k8s.io/client-go/discovery/fake" + kubefake "k8s.io/client-go/kubernetes/fake" +) + +func TestIsLoadBalancerIngressEquals(t *testing.T) { + testCases := []struct { + desc string + aSlice []netv1.IngressLoadBalancerIngress + bSlice []netv1.IngressLoadBalancerIngress + expectedEqual bool + }{ + { + desc: "both slices are empty", + expectedEqual: true, + }, + { + desc: "not the same length", + bSlice: []netv1.IngressLoadBalancerIngress{ + {IP: "192.168.1.1", Hostname: "traefik"}, + }, + expectedEqual: false, + }, + { + desc: "same ordered content", + aSlice: []netv1.IngressLoadBalancerIngress{ + {IP: "192.168.1.1", Hostname: "traefik"}, + }, + bSlice: []netv1.IngressLoadBalancerIngress{ + {IP: "192.168.1.1", Hostname: "traefik"}, + }, + expectedEqual: true, + }, + { + desc: "same unordered content", + aSlice: []netv1.IngressLoadBalancerIngress{ + {IP: "192.168.1.1", Hostname: "traefik"}, + {IP: "192.168.1.2", Hostname: "traefik2"}, + }, + bSlice: []netv1.IngressLoadBalancerIngress{ + {IP: "192.168.1.2", Hostname: "traefik2"}, + {IP: "192.168.1.1", Hostname: "traefik"}, + }, + expectedEqual: true, + }, + { + desc: "different ordered content", + aSlice: []netv1.IngressLoadBalancerIngress{ + {IP: "192.168.1.1", Hostname: "traefik"}, + {IP: "192.168.1.2", Hostname: "traefik2"}, + }, + bSlice: []netv1.IngressLoadBalancerIngress{ + {IP: "192.168.1.1", Hostname: "traefik"}, + {IP: "192.168.1.2", Hostname: "traefik"}, + }, + expectedEqual: false, + }, + { + desc: "different unordered content", + aSlice: []netv1.IngressLoadBalancerIngress{ + {IP: "192.168.1.1", Hostname: "traefik"}, + {IP: "192.168.1.2", Hostname: "traefik2"}, + }, + bSlice: []netv1.IngressLoadBalancerIngress{ + {IP: "192.168.1.2", Hostname: "traefik3"}, + {IP: "192.168.1.1", Hostname: "traefik"}, + }, + expectedEqual: false, + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + gotEqual := isLoadBalancerIngressEquals(test.aSlice, test.bSlice) + assert.Equal(t, test.expectedEqual, gotEqual) + }) + } +} + +func TestClientIgnoresHelmOwnedSecrets(t *testing.T) { + secret := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: "secret", + }, + } + helmSecret := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: "helm-secret", + Labels: map[string]string{ + "owner": "helm", + }, + }, + } + + kubeClient := kubefake.NewClientset(helmSecret, secret) + + discovery, _ := kubeClient.Discovery().(*discoveryfake.FakeDiscovery) + discovery.FakedServerVersion = &kversion.Info{ + GitVersion: "v1.19", + } + + client := newClient(kubeClient) + + eventCh, err := client.WatchAll(t.Context(), "", "") + require.NoError(t, err) + + select { + case event := <-eventCh: + secret, ok := event.(*corev1.Secret) + require.True(t, ok) + + assert.NotEqual(t, "helm-secret", secret.Name) + case <-time.After(50 * time.Millisecond): + assert.Fail(t, "expected to receive event for secret") + } + + select { + case <-eventCh: + assert.Fail(t, "received more than one event") + case <-time.After(50 * time.Millisecond): + } + + _, err = client.GetSecret("default", "secret") + require.NoError(t, err) + + _, err = client.GetSecret("default", "helm-secret") + assert.True(t, kerror.IsNotFound(err)) +} + +func TestClientIgnoresEmptyEndpointSliceUpdates(t *testing.T) { + emptyEndpointSlice := &discoveryv1.EndpointSlice{ + ObjectMeta: metav1.ObjectMeta{ + Name: "empty-endpointslice", + Namespace: "test", + ResourceVersion: "1244", + Annotations: map[string]string{ + "test-annotation": "_", + }, + }, + } + + samplePortName := "testing" + samplePortNumber := int32(1337) + samplePortProtocol := corev1.ProtocolTCP + sampleAddressReady := true + filledEndpointSlice := &discoveryv1.EndpointSlice{ + ObjectMeta: metav1.ObjectMeta{ + Name: "filled-endpointslice", + Namespace: "test", + ResourceVersion: "1234", + }, + AddressType: discoveryv1.AddressTypeIPv4, + Endpoints: []discoveryv1.Endpoint{{ + Addresses: []string{"10.13.37.1"}, + Conditions: discoveryv1.EndpointConditions{ + Ready: &sampleAddressReady, + }, + }}, + Ports: []discoveryv1.EndpointPort{{ + Name: &samplePortName, + Port: &samplePortNumber, + Protocol: &samplePortProtocol, + }}, + } + + kubeClient := kubefake.NewClientset(emptyEndpointSlice, filledEndpointSlice) + + discovery, _ := kubeClient.Discovery().(*discoveryfake.FakeDiscovery) + discovery.FakedServerVersion = &kversion.Info{ + GitVersion: "v1.19", + } + + client := newClient(kubeClient) + + eventCh, err := client.WatchAll(t.Context(), "", "") + require.NoError(t, err) + + select { + case event := <-eventCh: + ep, ok := event.(*discoveryv1.EndpointSlice) + require.True(t, ok) + + assert.True(t, ep.Name == "empty-endpointslice" || ep.Name == "filled-endpointslice") + case <-time.After(50 * time.Millisecond): + assert.Fail(t, "expected to receive event for endpointslices") + } + + emptyEndpointSlice, err = kubeClient.DiscoveryV1().EndpointSlices("test").Get(t.Context(), "empty-endpointslice", metav1.GetOptions{}) + assert.NoError(t, err) + + // Update endpoint annotation and resource version (apparently not done by fake client itself) + // to show an update that should not trigger an update event on our eventCh. + // This reflects the behavior of kubernetes controllers which use endpoint annotations for leader election. + emptyEndpointSlice.Annotations["test-annotation"] = "___" + emptyEndpointSlice.ResourceVersion = "1245" + _, err = kubeClient.DiscoveryV1().EndpointSlices("test").Update(t.Context(), emptyEndpointSlice, metav1.UpdateOptions{}) + require.NoError(t, err) + + select { + case event := <-eventCh: + ep, ok := event.(*discoveryv1.EndpointSlice) + require.True(t, ok) + + assert.Fail(t, "didn't expect to receive event for empty endpointslice update", ep.Name) + case <-time.After(50 * time.Millisecond): + } + + filledEndpointSlice, err = kubeClient.DiscoveryV1().EndpointSlices("test").Get(t.Context(), "filled-endpointslice", metav1.GetOptions{}) + assert.NoError(t, err) + + filledEndpointSlice.Endpoints[0].Addresses[0] = "10.13.37.2" + filledEndpointSlice.ResourceVersion = "1235" + _, err = kubeClient.DiscoveryV1().EndpointSlices("test").Update(t.Context(), filledEndpointSlice, metav1.UpdateOptions{}) + require.NoError(t, err) + + select { + case event := <-eventCh: + ep, ok := event.(*discoveryv1.EndpointSlice) + require.True(t, ok) + + assert.Equal(t, "filled-endpointslice", ep.Name) + case <-time.After(50 * time.Millisecond): + assert.Fail(t, "expected to receive event for filled endpointslice") + } + + select { + case <-eventCh: + assert.Fail(t, "received more than one event") + case <-time.After(50 * time.Millisecond): + } + + newPortNumber := int32(42) + filledEndpointSlice.Ports[0].Port = &newPortNumber + filledEndpointSlice.ResourceVersion = "1236" + _, err = kubeClient.DiscoveryV1().EndpointSlices("test").Update(t.Context(), filledEndpointSlice, metav1.UpdateOptions{}) + require.NoError(t, err) + + select { + case event := <-eventCh: + ep, ok := event.(*discoveryv1.EndpointSlice) + require.True(t, ok) + + assert.Equal(t, "filled-endpointslice", ep.Name) + case <-time.After(50 * time.Millisecond): + assert.Fail(t, "expected to receive event for filled endpointslice") + } + + select { + case <-eventCh: + assert.Fail(t, "received more than one event") + case <-time.After(50 * time.Millisecond): + } +} diff --git a/pkg/provider/kubernetes/ingress-nginx/convert.go b/pkg/provider/kubernetes/ingress-nginx/convert.go new file mode 100644 index 000000000..f2a27e034 --- /dev/null +++ b/pkg/provider/kubernetes/ingress-nginx/convert.go @@ -0,0 +1,69 @@ +package ingressnginx + +import ( + "errors" + + corev1 "k8s.io/api/core/v1" + netv1 "k8s.io/api/networking/v1" +) + +type marshaler interface { + Marshal() ([]byte, error) +} + +type unmarshaler interface { + Unmarshal(data []byte) error +} + +type LoadBalancerIngress interface { + corev1.LoadBalancerIngress | netv1.IngressLoadBalancerIngress +} + +// convertSlice converts slice of LoadBalancerIngress to slice of LoadBalancerIngress. +// O (Bar), I (Foo) => []Bar. +func convertSlice[O LoadBalancerIngress, I LoadBalancerIngress](loadBalancerIngresses []I) ([]O, error) { + var results []O + + for _, loadBalancerIngress := range loadBalancerIngresses { + mar, ok := any(&loadBalancerIngress).(marshaler) + if !ok { + // All the pointer of types related to the interface LoadBalancerIngress are compatible with the interface marshaler. + continue + } + + um, err := convert[O](mar) + if err != nil { + return nil, err + } + + v, ok := any(*um).(O) + if !ok { + continue + } + + results = append(results, v) + } + + return results, nil +} + +// convert must only be used with unmarshaler and marshaler compatible types. +func convert[T any](input marshaler) (*T, error) { + data, err := input.Marshal() + if err != nil { + return nil, err + } + + var output T + um, ok := any(&output).(unmarshaler) + if !ok { + return nil, errors.New("the output type doesn't implement unmarshaler interface") + } + + err = um.Unmarshal(data) + if err != nil { + return nil, err + } + + return &output, nil +} diff --git a/pkg/provider/kubernetes/ingress-nginx/convert_test.go b/pkg/provider/kubernetes/ingress-nginx/convert_test.go new file mode 100644 index 000000000..a0bff8834 --- /dev/null +++ b/pkg/provider/kubernetes/ingress-nginx/convert_test.go @@ -0,0 +1,77 @@ +package ingressnginx + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + corev1 "k8s.io/api/core/v1" + netv1 "k8s.io/api/networking/v1" + "k8s.io/utils/ptr" +) + +func Test_convertSlice_corev1_to_networkingv1(t *testing.T) { + g := []corev1.LoadBalancerIngress{ + { + IP: "132456", + Hostname: "foo", + Ports: []corev1.PortStatus{ + { + Port: 123, + Protocol: "https", + Error: ptr.To("test"), + }, + }, + }, + } + + actual, err := convertSlice[netv1.IngressLoadBalancerIngress](g) + require.NoError(t, err) + + expected := []netv1.IngressLoadBalancerIngress{ + { + IP: "132456", + Hostname: "foo", + Ports: []netv1.IngressPortStatus{ + { + Port: 123, + Protocol: "https", + Error: ptr.To("test"), + }, + }, + }, + } + + assert.Equal(t, expected, actual) +} + +func Test_convert(t *testing.T) { + g := &corev1.LoadBalancerIngress{ + IP: "132456", + Hostname: "foo", + Ports: []corev1.PortStatus{ + { + Port: 123, + Protocol: "https", + Error: ptr.To("test"), + }, + }, + } + + actual, err := convert[netv1.IngressLoadBalancerIngress](g) + require.NoError(t, err) + + expected := &netv1.IngressLoadBalancerIngress{ + IP: "132456", + Hostname: "foo", + Ports: []netv1.IngressPortStatus{ + { + Port: 123, + Protocol: "https", + Error: ptr.To("test"), + }, + }, + } + + assert.Equal(t, expected, actual) +} diff --git a/pkg/provider/kubernetes/ingress-nginx/fixtures/ingressclasses.yml b/pkg/provider/kubernetes/ingress-nginx/fixtures/ingressclasses.yml new file mode 100644 index 000000000..33e88a25d --- /dev/null +++ b/pkg/provider/kubernetes/ingress-nginx/fixtures/ingressclasses.yml @@ -0,0 +1,7 @@ +--- +apiVersion: networking.k8s.io/v1 +kind: IngressClass +metadata: + name: nginx +spec: + controller: k8s.io/ingress-nginx \ No newline at end of file diff --git a/pkg/provider/kubernetes/ingress-nginx/fixtures/ingresses/01-ingress-with-basicauth.yml b/pkg/provider/kubernetes/ingress-nginx/fixtures/ingresses/01-ingress-with-basicauth.yml new file mode 100644 index 000000000..cd750a9f1 --- /dev/null +++ b/pkg/provider/kubernetes/ingress-nginx/fixtures/ingresses/01-ingress-with-basicauth.yml @@ -0,0 +1,37 @@ +--- +kind: Ingress +apiVersion: networking.k8s.io/v1 +metadata: + name: ingress-with-basicauth + namespace: default + annotations: + # Configuration basic authentication for the Ingress + nginx.ingress.kubernetes.io/auth-type: "basic" + nginx.ingress.kubernetes.io/auth-secret-type: "auth-file" + nginx.ingress.kubernetes.io/auth-secret: "default/basic-auth" + nginx.ingress.kubernetes.io/auth-realm: "Authentication Required" + +spec: + ingressClassName: nginx + rules: + - host: whoami.localhost + http: + paths: + - path: /basicauth + pathType: Exact + backend: + service: + name: whoami + port: + number: 80 + +--- +kind: Secret +apiVersion: v1 +metadata: + name: basic-auth + namespace: default +type: Opaque +data: + # user:password + auth: dXNlcjp7U0hBfVc2cGg1TW01UHo4R2dpVUxiUGd6RzM3bWo5Zz0= \ No newline at end of file diff --git a/pkg/provider/kubernetes/ingress-nginx/fixtures/ingresses/02-ingress-with-forwardauth.yml b/pkg/provider/kubernetes/ingress-nginx/fixtures/ingresses/02-ingress-with-forwardauth.yml new file mode 100644 index 000000000..220499792 --- /dev/null +++ b/pkg/provider/kubernetes/ingress-nginx/fixtures/ingresses/02-ingress-with-forwardauth.yml @@ -0,0 +1,24 @@ +--- +kind: Ingress +apiVersion: networking.k8s.io/v1 +metadata: + name: ingress-with-forwardauth + namespace: default + annotations: + nginx.ingress.kubernetes.io/auth-url: "http://whoami.default.svc/" + nginx.ingress.kubernetes.io/auth-method: "GET" + nginx.ingress.kubernetes.io/auth-response-headers: "X-Foo" + +spec: + ingressClassName: nginx + rules: + - host: whoami.localhost + http: + paths: + - path: /forwardauth + pathType: Exact + backend: + service: + name: whoami + port: + number: 80 diff --git a/pkg/provider/kubernetes/ingress-nginx/fixtures/ingresses/03-ingress-with-ssl-redirect.yml b/pkg/provider/kubernetes/ingress-nginx/fixtures/ingresses/03-ingress-with-ssl-redirect.yml new file mode 100644 index 000000000..57f8dd420 --- /dev/null +++ b/pkg/provider/kubernetes/ingress-nginx/fixtures/ingresses/03-ingress-with-ssl-redirect.yml @@ -0,0 +1,75 @@ +--- +kind: Ingress +apiVersion: networking.k8s.io/v1 +metadata: + name: ingress-with-ssl-redirect + namespace: default + + +spec: + ingressClassName: nginx + rules: + - host: sslredirect.localhost + http: + paths: + - path: / + pathType: Exact + backend: + service: + name: whoami + port: + number: 80 + tls: + - hosts: + - sslredirect.localhost + secretName: whoami-tls + +--- +kind: Ingress +apiVersion: networking.k8s.io/v1 +metadata: + name: ingress-without-ssl-redirect + namespace: default + annotations: + nginx.ingress.kubernetes.io/ssl-redirect: "false" + +spec: + ingressClassName: nginx + rules: + - host: withoutsslredirect.localhost + http: + paths: + - path: / + pathType: Exact + backend: + service: + name: whoami + port: + number: 80 + tls: + - hosts: + - withoutsslredirect.localhost + secretName: whoami-tls + +--- +kind: Ingress +apiVersion: networking.k8s.io/v1 +metadata: + name: ingress-with-force-ssl-redirect + namespace: default + annotations: + nginx.ingress.kubernetes.io/force-ssl-redirect: "true" + +spec: + ingressClassName: nginx + rules: + - host: forcesslredirect.localhost + http: + paths: + - path: / + pathType: Exact + backend: + service: + name: whoami + port: + number: 80 diff --git a/pkg/provider/kubernetes/ingress-nginx/fixtures/ingresses/04-ingress-with-ssl-passthrough.yml b/pkg/provider/kubernetes/ingress-nginx/fixtures/ingresses/04-ingress-with-ssl-passthrough.yml new file mode 100644 index 000000000..70f531c7c --- /dev/null +++ b/pkg/provider/kubernetes/ingress-nginx/fixtures/ingresses/04-ingress-with-ssl-passthrough.yml @@ -0,0 +1,22 @@ +--- +kind: Ingress +apiVersion: networking.k8s.io/v1 +metadata: + name: ingress-with-ssl-passthrough + namespace: default + annotations: + nginx.ingress.kubernetes.io/ssl-passthrough: "true" + +spec: + ingressClassName: nginx + rules: + - host: passthrough.whoami.localhost + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: whoami-tls + port: + number: 443 \ No newline at end of file diff --git a/pkg/provider/kubernetes/ingress-nginx/fixtures/ingresses/05-ingress-with-default-backend.yml b/pkg/provider/kubernetes/ingress-nginx/fixtures/ingresses/05-ingress-with-default-backend.yml new file mode 100644 index 000000000..8809e7164 --- /dev/null +++ b/pkg/provider/kubernetes/ingress-nginx/fixtures/ingresses/05-ingress-with-default-backend.yml @@ -0,0 +1,24 @@ +--- +kind: Ingress +apiVersion: networking.k8s.io/v1 +metadata: + name: ingress-with-default-backend + namespace: default + +spec: + defaultBackend: + service: + name: whoami-default + port: + number: 80 + + rules: + - http: + paths: + - path: / + pathType: Exact + backend: + service: + name: whoami + port: + number: 80 diff --git a/pkg/provider/kubernetes/ingress-nginx/fixtures/ingresses/05-ingress-with-default-backend2.yml b/pkg/provider/kubernetes/ingress-nginx/fixtures/ingresses/05-ingress-with-default-backend2.yml new file mode 100644 index 000000000..a9c350168 --- /dev/null +++ b/pkg/provider/kubernetes/ingress-nginx/fixtures/ingresses/05-ingress-with-default-backend2.yml @@ -0,0 +1,108 @@ + +--- +kind: Ingress +apiVersion: networking.k8s.io/v1 +metadata: + name: ingress-with-default-backend2 + namespace: default +# annotations: +# nginx.ingress.kubernetes.io/force-ssl-redirect: "true" + +# annotations: +## Configuration basic authentication for the Ingress +# nginx.ingress.kubernetes.io/auth-type: "basic" +# nginx.ingress.kubernetes.io/auth-secret-type: "auth-file" +# nginx.ingress.kubernetes.io/auth-secret: "default/basic-auth" +# nginx.ingress.kubernetes.io/auth-realm: "Authentication Required" + +spec: + defaultBackend: + service: + name: whoami-default2 + port: + number: 80 + + rules: + - host: dd.localhost + http: + paths: + - path: / + pathType: Exact + backend: + service: + name: whoami + port: + number: 443 +# +# tls: +# - hosts: +# - dd.localhost +# secretName: whoami-tls +# +#--- +#kind: Secret +#apiVersion: v1 +#metadata: +# name: whoami-tls +# namespace: default +# +#type: opaque +#stringData: +# tls.crt: | +# -----BEGIN CERTIFICATE----- +# MIIEXjCCAsagAwIBAgIQAJmtU2qHBlD9D2HZFZLMeDANBgkqhkiG9w0BAQsFADCB +# jzEeMBwGA1UEChMVbWtjZXJ0IGRldmVsb3BtZW50IENBMTIwMAYDVQQLDClyb21h +# aW5AY29udGFpbm91cy5ob21lIChSb21haW4gVHJpYm90dMOpKTE5MDcGA1UEAwww +# bWtjZXJ0IHJvbWFpbkBjb250YWlub3VzLmhvbWUgKFJvbWFpbiBUcmlib3R0w6kp +# MB4XDTI1MDYxMDE1NDE0NFoXDTI3MDkxMDE1NDE0NFowXzEnMCUGA1UEChMebWtj +# ZXJ0IGRldmVsb3BtZW50IGNlcnRpZmljYXRlMTQwMgYDVQQLDCtyb21haW5ATWFj +# Qm9vay1Qcm8ubG9jYWwgKFJvbWFpbiBUcmlib3R0w6kpMIIBIjANBgkqhkiG9w0B +# AQEFAAOCAQ8AMIIBCgKCAQEAq3dajz+RgY+VUXvKKtHFFVd+0URcpDRgN+SJOxP/ +# 1uZG2U57DMvTiVy6zfpYo7QPzyEAUwbRTMMgxZV5oy1JPkGzV5kc08GUT3Lh1Azf +# LVPX/K1nA+k7p9+kuMsfkHVABMawRpnWo215T9pjGaTKERA2EaNvrSdq73k6raVn +# DnnmvUgWGPvxTetaLu0AVQscGyrTfQNMB8BwC+JEQJKocenJ0ve5l9/yv9543P2G +# 6UcOv71lDOBNPyltrc4sXfGC2vB1APbp80BVfkZDiF+8Gr8wGJrkd75Esp/xetFV +# yZ6NKO9ZsGZ2E14/qxfvASHGNFNQJafqhnuGbmky8AeaawIDAQABo2UwYzAOBgNV +# HQ8BAf8EBAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUHAwEwHwYDVR0jBBgwFoAUjIHl +# 1gcu+iVVHCicC14yHQiRojgwGwYDVR0RBBQwEoIQd2hvYW1pLmxvY2FsaG9zdDAN +# BgkqhkiG9w0BAQsFAAOCAYEAo/f0ADJwnkOakCHcYCNSqRY/VzRIQSQK3wfDq3bD +# 8EDxGrGPYHOIL+u/Up4RO2/9vLEnFpWb30A8z/qZTKKD+rMuU3qTcCJ2tsB3DAIV +# T+b2GmJYjURf1gqe/NNXnzZqgkoP+bHx6iNvDr1kmc3pZshayz+FxzNmjgpbKl2G +# SgfFLnJDm7hwTC9JFoPyzb586Q0OGQKCJpDMy6pi1MAQl2RWiKyrgo1mhYnSxQmI +# qbJbxYlegRRQQPD6YEJcL5lwILVW3TXcGrK+zuMD+xWznDTBg2BxbF2umG8jmXPH +# 04gRfjlMNLEYSrNEU8EOa/lXebcxnlz6meFOgfYKmSHxL+kwjTUuppDV/qP9U+VS +# /ozJ85VS8iEx1obqZGgqgwcBKMRYzuRnW1XEScGUOK9/cs9mGoXG9uafKb7ekFQc +# wU0j0FoUVzc50WWEjCGFU/dS/2HXUXU/Rcf+uULC10ORplwrB5XdXZYxowh7T//U +# yh86E4+M0LHyZH0vUwDoBk/1 +# -----END CERTIFICATE----- +# +# +# tls.key: | +# -----BEGIN PRIVATE KEY----- +# MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCrd1qPP5GBj5VR +# e8oq0cUVV37RRFykNGA35Ik7E//W5kbZTnsMy9OJXLrN+lijtA/PIQBTBtFMwyDF +# lXmjLUk+QbNXmRzTwZRPcuHUDN8tU9f8rWcD6Tun36S4yx+QdUAExrBGmdajbXlP +# 2mMZpMoREDYRo2+tJ2rveTqtpWcOeea9SBYY+/FN61ou7QBVCxwbKtN9A0wHwHAL +# 4kRAkqhx6cnS97mX3/K/3njc/YbpRw6/vWUM4E0/KW2tzixd8YLa8HUA9unzQFV+ +# RkOIX7wavzAYmuR3vkSyn/F60VXJno0o71mwZnYTXj+rF+8BIcY0U1Alp+qGe4Zu +# aTLwB5prAgMBAAECggEAVNpLxnf+2c7kZd6MvYPxtA4IhCcAcYI5228NOl87TG3I +# weFEo6B6no91IlmxY9HHwQjj0DKfgQ1POnguKcJPbK+2wLLUwTYa3vZLK1TzXMsR +# J8noINda3kiei5R5mlNryvFIaqfWwCl8zzeTsy0JkkgjebcXnOjU0o17rFMeHNsH +# A3iFWWnHtJkn2OaVtOOgsyjJ9oAnGX0AE4cVp7ZZTerpaYTXzkCphbwRi00IEbCk +# 1bn7gPcBQRoxs12GJUUuy/sopQRA51PE//CnV2pkGuDFWBhFBBKYdsHaTUwmTb5P +# l6S5CuCtw44NkTPetTe2sn9DpOIlR7PmojQndmKkgQKBgQDJ6/RDueJCBxSYS6bh +# 7dTPRphJvntoJHs9Q/NNjKdQhxv0vIIdtRk88Q2qhjjlCzHb1RtD5Jsl+D+TxOdG +# wR1/E8+hdbRKv+WACywa38aBPuZSEj89bnyPyQfzs5TtzD5JsdUHT4l5Eudth6Gv +# w14dFKria8WiEd7X2GodnlZX/wKBgQDZY1QBNjAHsi7QJJSvbPKwK8RygvdNJEem +# FYxhjtHzOfUttjyDXDSGheY3/VzKi2rGgVAHLi+qbvwkURn4qT3xtV5Lpi+BLWHP +# Gwepisd9P5TrN0DGQojWjzYatN9MYRzX0JynIB+alabN2bG7kfPPsHikAA7pRxLH +# 7EwMBDGdlQKBgBqd9uoCk9e+VTGqL0py7m2QUbzO1jepL3GpBmZ/lwKffMjrHH/M +# ApKs9+81mERhEGZ5FgoCFY2Qxti0yQPjqv64XtNaz7RWzWrujhbQzrr0zqmc7Cct +# 7E+L4Xd3gbdDCCbwwTMgge+q1UTz7xVbPIm60rfcGwY9MtHjHkHfQGSDAoGBAIA/ +# CAT6+dTgepuSqSDg7j+eYnOH7etVlutVVQ8M2bFbJNiF5Sc900L1ZX7seryHCUP4 +# b8T8q2Qpu5iVO/QlrASXkfyhGu9jXYt4D8omtE+gnfMyEoWkJOQncqzIvd9qf0CW +# soQqAFsLJG/WmPLmRObm3hUqb6GRq3PEZIzGQJsNAoGBAJEN0ZkrIkNK+Jjd1oNB +# AnwgLA0qyAHqJxPig45Nudhb6Jw4ub/hKG9bCrLpcBM57Lue535e2HtQ5Ed22Pim +# 0m7bQkvrIQYjflW99RsfkiH5qJsiTy9O92iKgGtJAJ80vTkIggAbsnzOHlZvR0Fr +# +GhYvMt0TxpugicUqguSSUZp +# -----END PRIVATE KEY----- diff --git a/pkg/provider/kubernetes/ingress-nginx/fixtures/ingresses/06-ingress-with-sticky.yml b/pkg/provider/kubernetes/ingress-nginx/fixtures/ingresses/06-ingress-with-sticky.yml new file mode 100644 index 000000000..ac56745cc --- /dev/null +++ b/pkg/provider/kubernetes/ingress-nginx/fixtures/ingresses/06-ingress-with-sticky.yml @@ -0,0 +1,28 @@ +--- +kind: Ingress +apiVersion: networking.k8s.io/v1 +metadata: + name: ingress-with-sticky + namespace: default + annotations: + nginx.ingress.kubernetes.io/affinity: cookie + nginx.ingress.kubernetes.io/session-cookie-name: foobar + nginx.ingress.kubernetes.io/session-cookie-secure: "true" + nginx.ingress.kubernetes.io/session-cookie-path: "/foobar" + nginx.ingress.kubernetes.io/session-cookie-domain: "foo.localhost" + nginx.ingress.kubernetes.io/session-cookie-samesite: "None" + nginx.ingress.kubernetes.io/session-cookie-max-age: "42" + +spec: + ingressClassName: nginx + rules: + - host: sticky.localhost + http: + paths: + - path: / + pathType: Exact + backend: + service: + name: whoami + port: + number: 80 diff --git a/pkg/provider/kubernetes/ingress-nginx/fixtures/ingresses/07-ingress-with-proxy-ssl.yml b/pkg/provider/kubernetes/ingress-nginx/fixtures/ingresses/07-ingress-with-proxy-ssl.yml new file mode 100644 index 000000000..4d3f4dc63 --- /dev/null +++ b/pkg/provider/kubernetes/ingress-nginx/fixtures/ingresses/07-ingress-with-proxy-ssl.yml @@ -0,0 +1,37 @@ +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: ingress-with-proxy-ssl + namespace: default + annotations: + nginx.ingress.kubernetes.io/backend-protocol: "HTTPS" # HTTP, HTTPS, AUTO_HTTP, GRPC, GRPCS and FCGI + nginx.ingress.kubernetes.io/proxy-ssl-secret: "default/ingress-with-proxy-ssl" + nginx.ingress.kubernetes.io/proxy-ssl-verify: "on" + nginx.ingress.kubernetes.io/proxy-ssl-verify-depth: "1" + nginx.ingress.kubernetes.io/proxy-ssl-server-name: "whoami.localhost" + nginx.ingress.kubernetes.io/proxy-ssl-name: "whoami.localhost" + +spec: + ingressClassName: nginx + rules: + - host: proxy-ssl.localhost + http: + paths: + - path: / + pathType: Exact + backend: + service: + name: whoami-tls + port: + number: 443 + +--- +kind: Secret +apiVersion: v1 +metadata: + namespace: default + name: ingress-with-proxy-ssl + +data: + ca.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0t diff --git a/pkg/provider/kubernetes/ingress-nginx/fixtures/ingresses/08-ingress-with-cors.yml b/pkg/provider/kubernetes/ingress-nginx/fixtures/ingresses/08-ingress-with-cors.yml new file mode 100644 index 000000000..b862b9bf6 --- /dev/null +++ b/pkg/provider/kubernetes/ingress-nginx/fixtures/ingresses/08-ingress-with-cors.yml @@ -0,0 +1,28 @@ +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: ingress-with-cors + namespace: default + annotations: + nginx.ingress.kubernetes.io/enable-cors: "true" + nginx.ingress.kubernetes.io/cors-allow-credentials: "true" + nginx.ingress.kubernetes.io/cors-expose-headers: "X-Forwarded-For, X-Forwarded-Host" + nginx.ingress.kubernetes.io/cors-allow-headers: "X-Foo" + nginx.ingress.kubernetes.io/cors-allow-methods: "PUT, GET, POST, OPTIONS" + nginx.ingress.kubernetes.io/cors-allow-origin: "*" + nginx.ingress.kubernetes.io/cors-max-age: "42" + +spec: + ingressClassName: nginx + rules: + - host: cors.localhost + http: + paths: + - path: / + pathType: Exact + backend: + service: + name: whoami + port: + number: 80 diff --git a/pkg/provider/kubernetes/ingress-nginx/fixtures/ingresses/09-ingress-with-service-upstream.yml b/pkg/provider/kubernetes/ingress-nginx/fixtures/ingresses/09-ingress-with-service-upstream.yml new file mode 100644 index 000000000..2a2adcec2 --- /dev/null +++ b/pkg/provider/kubernetes/ingress-nginx/fixtures/ingresses/09-ingress-with-service-upstream.yml @@ -0,0 +1,22 @@ +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: ingress-with-service-upstream + namespace: default + annotations: + nginx.ingress.kubernetes.io/service-upstream: "true" + +spec: + ingressClassName: nginx + rules: + - host: service-upstream.localhost + http: + paths: + - path: / + pathType: Exact + backend: + service: + name: whoami + port: + number: 80 diff --git a/pkg/provider/kubernetes/ingress-nginx/fixtures/secrets.yml b/pkg/provider/kubernetes/ingress-nginx/fixtures/secrets.yml new file mode 100644 index 000000000..7c53f7fb0 --- /dev/null +++ b/pkg/provider/kubernetes/ingress-nginx/fixtures/secrets.yml @@ -0,0 +1,9 @@ +kind: Secret +apiVersion: v1 +metadata: + namespace: default + name: whoami-tls + +data: + tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0t + tls.key: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0t diff --git a/pkg/provider/kubernetes/ingress-nginx/fixtures/services.yml b/pkg/provider/kubernetes/ingress-nginx/fixtures/services.yml new file mode 100644 index 000000000..b658083aa --- /dev/null +++ b/pkg/provider/kubernetes/ingress-nginx/fixtures/services.yml @@ -0,0 +1,80 @@ +kind: Service +apiVersion: v1 +metadata: + name: whoami + namespace: default + +spec: + clusterIP: 10.10.10.1 + ports: + - name: web2 + protocol: TCP + port: 8000 + targetPort: web2 + - name: web + protocol: TCP + port: 80 + targetPort: web + selector: + app: whoami + task: whoami + +--- +kind: EndpointSlice +apiVersion: discovery.k8s.io/v1 +metadata: + name: whoami + namespace: default + labels: + kubernetes.io/service-name: whoami + +addressType: IPv4 +ports: + - name: web + port: 80 + - name: web2 + port: 8000 +endpoints: + - addresses: + - 10.10.0.1 + - 10.10.0.2 + conditions: + ready: true + +--- +apiVersion: v1 +kind: Service +metadata: + name: whoami-tls + namespace: default + +spec: + ports: + - name: websecure + protocol: TCP + appProtocol: https + port: 443 + targetPort: websecure + selector: + app: whoami-tls + task: whoami + +--- +kind: EndpointSlice +apiVersion: discovery.k8s.io/v1 +metadata: + name: whoami-tls + namespace: default + labels: + kubernetes.io/service-name: whoami-tls + +addressType: IPv4 +ports: + - name: websecure + port: 8443 +endpoints: + - addresses: + - 10.10.0.5 + - 10.10.0.6 + conditions: + ready: true diff --git a/pkg/provider/kubernetes/ingress-nginx/kubernetes.go b/pkg/provider/kubernetes/ingress-nginx/kubernetes.go new file mode 100644 index 000000000..e8f583ba0 --- /dev/null +++ b/pkg/provider/kubernetes/ingress-nginx/kubernetes.go @@ -0,0 +1,1118 @@ +package ingressnginx + +import ( + "context" + "errors" + "fmt" + "maps" + "math" + "net" + "os" + "regexp" + "slices" + "strconv" + "strings" + "time" + + "github.com/cenkalti/backoff/v4" + "github.com/mitchellh/hashstructure" + "github.com/rs/zerolog/log" + ptypes "github.com/traefik/paerser/types" + "github.com/traefik/traefik/v3/pkg/config/dynamic" + "github.com/traefik/traefik/v3/pkg/job" + "github.com/traefik/traefik/v3/pkg/observability/logs" + "github.com/traefik/traefik/v3/pkg/provider" + "github.com/traefik/traefik/v3/pkg/provider/kubernetes/k8s" + "github.com/traefik/traefik/v3/pkg/safe" + "github.com/traefik/traefik/v3/pkg/tls" + "github.com/traefik/traefik/v3/pkg/types" + corev1 "k8s.io/api/core/v1" + netv1 "k8s.io/api/networking/v1" + "k8s.io/utils/ptr" +) + +const ( + providerName = "kubernetesingressnginx" + + annotationIngressClass = "kubernetes.io/ingress.class" + + defaultControllerName = "k8s.io/ingress-nginx" + defaultAnnotationValue = "nginx" + + defaultBackendName = "default-backend" + defaultBackendTLSName = "default-backend-tls" +) + +type backendAddress struct { + Address string + Fenced bool +} + +type namedServersTransport struct { + Name string + ServersTransport *dynamic.ServersTransport +} + +type certBlocks struct { + CA *types.FileOrContent + Certificate *tls.Certificate +} + +// Provider holds configurations of the provider. +type Provider struct { + Endpoint string `description:"Kubernetes server endpoint (required for external cluster client)." json:"endpoint,omitempty" toml:"endpoint,omitempty" yaml:"endpoint,omitempty"` + Token types.FileOrContent `description:"Kubernetes bearer token (not needed for in-cluster client). It accepts either a token value or a file path to the token." json:"token,omitempty" toml:"token,omitempty" yaml:"token,omitempty" loggable:"false"` + CertAuthFilePath string `description:"Kubernetes certificate authority file path (not needed for in-cluster client)." json:"certAuthFilePath,omitempty" toml:"certAuthFilePath,omitempty" yaml:"certAuthFilePath,omitempty"` + ThrottleDuration ptypes.Duration `description:"Ingress refresh throttle duration." json:"throttleDuration,omitempty" toml:"throttleDuration,omitempty" yaml:"throttleDuration,omitempty" export:"true"` + + WatchNamespace string `description:"Namespace the controller watches for updates to Kubernetes objects. All namespaces are watched if this parameter is left empty." json:"watchNamespace,omitempty" toml:"watchNamespace,omitempty" yaml:"watchNamespace,omitempty" export:"true"` + WatchNamespaceSelector string `description:"Selector selects namespaces the controller watches for updates to Kubernetes objects." json:"watchNamespaceSelector,omitempty" toml:"watchNamespaceSelector,omitempty" yaml:"watchNamespaceSelector,omitempty" export:"true"` + + IngressClass string `description:"Name of the ingress class this controller satisfies." json:"ingressClass,omitempty" toml:"ingressClass,omitempty" yaml:"ingressClass,omitempty" export:"true"` + ControllerClass string `description:"Ingress Class Controller value this controller satisfies." json:"controllerClass,omitempty" toml:"controllerClass,omitempty" yaml:"controllerClass,omitempty" export:"true"` + WatchIngressWithoutClass bool `description:"Define if Ingress Controller should also watch for Ingresses without an IngressClass or the annotation specified." json:"watchIngressWithoutClass,omitempty" toml:"watchIngressWithoutClass,omitempty" yaml:"watchIngressWithoutClass,omitempty" export:"true"` + IngressClassByName bool `description:"Define if Ingress Controller should watch for Ingress Class by Name together with Controller Class." json:"ingressClassByName,omitempty" toml:"ingressClassByName,omitempty" yaml:"ingressClassByName,omitempty" export:"true"` + + // TODO: support report-node-internal-ip-address and update-status. + PublishService string `description:"Service fronting the Ingress controller. Takes the form 'namespace/name'." json:"publishService,omitempty" toml:"publishService,omitempty" yaml:"publishService,omitempty" export:"true"` + PublishStatusAddress []string `description:"Customized address (or addresses, separated by comma) to set as the load-balancer status of Ingress objects this controller satisfies." json:"publishStatusAddress,omitempty" toml:"publishStatusAddress,omitempty" yaml:"publishStatusAddress,omitempty"` + + DefaultBackendService string `description:"Service used to serve HTTP requests not matching any known server name (catch-all). Takes the form 'namespace/name'." json:"defaultBackendService,omitempty" toml:"defaultBackendService,omitempty" yaml:"defaultBackendService,omitempty" export:"true"` + DisableSvcExternalName bool `description:"Disable support for Services of type ExternalName." json:"disableSvcExternalName,omitempty" toml:"disableSvcExternalName,omitempty" yaml:"disableSvcExternalName,omitempty" export:"true"` + + defaultBackendServiceNamespace string + defaultBackendServiceName string + + k8sClient *clientWrapper + lastConfiguration safe.Safe +} + +func (p *Provider) SetDefaults() { + p.IngressClass = defaultAnnotationValue + p.ControllerClass = defaultControllerName +} + +// Init the provider. +func (p *Provider) Init() error { + // Validates and parses the default backend configuration. + if p.DefaultBackendService != "" { + parts := strings.Split(p.DefaultBackendService, "/") + if len(parts) != 2 { + return fmt.Errorf("invalid default backend service format: %s, expected 'namespace/name'", p.DefaultBackendService) + } + p.defaultBackendServiceNamespace = parts[0] + p.defaultBackendServiceName = parts[1] + } + + // Initializes Kubernetes client. + var err error + p.k8sClient, err = p.newK8sClient() + if err != nil { + return fmt.Errorf("creating kubernetes client: %w", err) + } + + return nil +} + +// Provide allows the k8s provider to provide configurations to traefik using the given configuration channel. +func (p *Provider) Provide(configurationChan chan<- dynamic.Message, pool *safe.Pool) error { + logger := log.With().Str(logs.ProviderName, providerName).Logger() + ctxLog := logger.WithContext(context.Background()) + + pool.GoCtx(func(ctxPool context.Context) { + operation := func() error { + eventsChan, err := p.k8sClient.WatchAll(ctxPool, p.WatchNamespace, p.WatchNamespaceSelector) + if err != nil { + logger.Error().Err(err).Msg("Error watching kubernetes events") + timer := time.NewTimer(1 * time.Second) + select { + case <-timer.C: + return err + case <-ctxPool.Done(): + return nil + } + } + + throttleDuration := time.Duration(p.ThrottleDuration) + throttledChan := throttleEvents(ctxLog, throttleDuration, pool, eventsChan) + if throttledChan != nil { + eventsChan = throttledChan + } + + for { + select { + case <-ctxPool.Done(): + return nil + case event := <-eventsChan: + // Note that event is the *first* event that came in during this + // throttling interval -- if we're hitting our throttle, we may have + // dropped events. This is fine, because we don't treat different + // event types differently. But if we do in the future, we'll need to + // track more information about the dropped events. + conf := p.loadConfiguration(ctxLog) + + confHash, err := hashstructure.Hash(conf, nil) + switch { + case err != nil: + logger.Error().Msg("Unable to hash the configuration") + case p.lastConfiguration.Get() == confHash: + logger.Debug().Msgf("Skipping Kubernetes event kind %T", event) + default: + p.lastConfiguration.Set(confHash) + configurationChan <- dynamic.Message{ + ProviderName: providerName, + Configuration: conf, + } + } + + // If we're throttling, we sleep here for the throttle duration to + // enforce that we don't refresh faster than our throttle. time.Sleep + // returns immediately if p.ThrottleDuration is 0 (no throttle). + time.Sleep(throttleDuration) + } + } + } + + notify := func(err error, time time.Duration) { + 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 retrieve data") + } + }) + + return nil +} + +func (p *Provider) newK8sClient() (*clientWrapper, error) { + withEndpoint := "" + if p.Endpoint != "" { + withEndpoint = fmt.Sprintf(" with endpoint %v", p.Endpoint) + } + + switch { + case os.Getenv("KUBERNETES_SERVICE_HOST") != "" && os.Getenv("KUBERNETES_SERVICE_PORT") != "": + log.Info().Msgf("Creating in-cluster Provider client%s", withEndpoint) + return newInClusterClient(p.Endpoint) + case os.Getenv("KUBECONFIG") != "": + log.Info().Msgf("Creating cluster-external Provider client from KUBECONFIG %s", os.Getenv("KUBECONFIG")) + return newExternalClusterClientFromFile(os.Getenv("KUBECONFIG")) + default: + log.Info().Msgf("Creating cluster-external Provider client%s", withEndpoint) + return newExternalClusterClient(p.Endpoint, p.CertAuthFilePath, p.Token) + } +} + +func (p *Provider) loadConfiguration(ctx context.Context) *dynamic.Configuration { + conf := &dynamic.Configuration{ + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, + ServersTransports: map[string]*dynamic.ServersTransport{}, + }, + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Services: map[string]*dynamic.TCPService{}, + }, + } + + // We configure the default backend when it is configured at the provider level. + if p.defaultBackendServiceNamespace != "" && p.defaultBackendServiceName != "" { + ib := netv1.IngressBackend{Service: &netv1.IngressServiceBackend{Name: p.defaultBackendServiceName}} + svc, err := p.buildService(p.defaultBackendServiceNamespace, ib, ingressConfig{}) + if err != nil { + log.Ctx(ctx).Error().Err(err).Msg("Cannot build default backend service") + return conf + } + + // Add the default backend service router to the configuration. + conf.HTTP.Routers[defaultBackendName] = &dynamic.Router{ + Rule: "PathPrefix(`/`)", + // "default" stands for the default rule syntax in Traefik v3, i.e. the v3 syntax. + RuleSyntax: "default", + Priority: math.MinInt32, + Service: defaultBackendName, + } + + conf.HTTP.Routers[defaultBackendTLSName] = &dynamic.Router{ + Rule: "PathPrefix(`/`)", + // "default" stands for the default rule syntax in Traefik v3, i.e. the v3 syntax. + RuleSyntax: "default", + Priority: math.MinInt32, + Service: defaultBackendName, + TLS: &dynamic.RouterTLSConfig{}, + } + + conf.HTTP.Services[defaultBackendName] = svc + } + + var ingressClasses []*netv1.IngressClass + ics, err := p.k8sClient.ListIngressClasses() + if err != nil { + log.Ctx(ctx).Warn().Err(err).Msg("Failed to list ingress classes") + } + ingressClasses = filterIngressClass(ics, p.IngressClassByName, p.IngressClass, p.ControllerClass) + + ingresses := p.k8sClient.ListIngresses() + + uniqCerts := make(map[string]*tls.CertAndStores) + for _, ingress := range ingresses { + logger := log.Ctx(ctx).With().Str("ingress", ingress.Name).Str("namespace", ingress.Namespace).Logger() + ctxIngress := logger.WithContext(ctx) + + if !p.shouldProcessIngress(ingress, ingressClasses) { + continue + } + + ingressConfig, err := parseIngressConfig(ingress) + if err != nil { + logger.Error().Err(err).Msg("Error parsing ingress configuration") + continue + } + + if err := p.updateIngressStatus(ingress); err != nil { + logger.Error().Err(err).Msg("Error while updating ingress status") + } + + var hasTLS bool + if len(ingress.Spec.TLS) > 0 { + hasTLS = true + if err := p.loadCertificates(ctxIngress, ingress, uniqCerts); err != nil { + logger.Error().Err(err).Msg("Error configuring TLS") + continue + } + } + + namedServersTransport, err := p.buildServersTransport(ingress.Namespace, ingress.Name, ingressConfig) + if err != nil { + logger.Error().Err(err).Msg("Ignoring Ingress cannot create proxy SSL configuration") + continue + } + + var defaultBackendService *dynamic.Service + if ingress.Spec.DefaultBackend != nil && ingress.Spec.DefaultBackend.Service != nil { + var err error + defaultBackendService, err = p.buildService(ingress.Namespace, *ingress.Spec.DefaultBackend, ingressConfig) + if err != nil { + logger.Error(). + Str("serviceName", ingress.Spec.DefaultBackend.Service.Name). + Str("servicePort", ingress.Spec.DefaultBackend.Service.Port.String()). + Err(err). + Msg("Cannot create default backend service") + } + } + + if defaultBackendService != nil && len(ingress.Spec.Rules) == 0 { + rt := &dynamic.Router{ + Rule: "PathPrefix(`/`)", + // "default" stands for the default rule syntax in Traefik v3, i.e. the v3 syntax. + RuleSyntax: "default", + Priority: math.MinInt32, + Service: defaultBackendName, + } + + if err := p.applyMiddlewares(ingress.Namespace, defaultBackendName, ingressConfig, hasTLS, rt, conf); err != nil { + logger.Error().Err(err).Msg("Error applying middlewares") + } + + conf.HTTP.Routers[defaultBackendName] = rt + + rtTLS := &dynamic.Router{ + Rule: "PathPrefix(`/`)", + // "default" stands for the default rule syntax in Traefik v3, i.e. the v3 syntax. + RuleSyntax: "default", + Priority: math.MinInt32, + Service: defaultBackendName, + TLS: &dynamic.RouterTLSConfig{}, + } + + if err := p.applyMiddlewares(ingress.Namespace, defaultBackendTLSName, ingressConfig, false, rtTLS, conf); err != nil { + logger.Error().Err(err).Msg("Error applying middlewares") + } + + conf.HTTP.Routers[defaultBackendTLSName] = rtTLS + + if namedServersTransport != nil && defaultBackendService.LoadBalancer != nil { + defaultBackendService.LoadBalancer.ServersTransport = namedServersTransport.Name + conf.HTTP.ServersTransports[namedServersTransport.Name] = namedServersTransport.ServersTransport + } + conf.HTTP.Services[defaultBackendName] = defaultBackendService + } + + for ri, rule := range ingress.Spec.Rules { + if ptr.Deref(ingressConfig.SSLPassthrough, false) { + if rule.Host == "" { + logger.Error().Err(err).Msg("Cannot process ssl-passthrough for rule without host") + continue + } + + var backend *netv1.IngressBackend + if rule.HTTP != nil { + for _, path := range rule.HTTP.Paths { + if path.Path == "/" { + backend = &path.Backend + break + } + } + } else if ingress.Spec.DefaultBackend != nil { + // Passthrough with the default backend if no HTTP section. + backend = ingress.Spec.DefaultBackend + } + + if backend == nil { + logger.Error().Msgf("No backend found for ssl-passthrough for rule with host %q", rule.Host) + continue + } + + service, err := p.buildPassthroughService(ingress.Namespace, *backend, ingressConfig) + if err != nil { + logger.Error().Err(err).Msgf("Cannot create passthrough service for %s", backend.Service.Name) + continue + } + + port := backend.Service.Port.Name + if len(backend.Service.Port.Name) == 0 { + port = strconv.Itoa(int(backend.Service.Port.Number)) + } + + serviceName := provider.Normalize(ingress.Namespace + "-" + backend.Service.Name + "-" + port) + conf.TCP.Services[serviceName] = service + + routerKey := strings.TrimPrefix(provider.Normalize(ingress.Namespace+"-"+ingress.Name+"-"+rule.Host), "-") + + conf.TCP.Routers[routerKey] = &dynamic.TCPRouter{ + Rule: fmt.Sprintf("HostSNI(`%s`)", rule.Host), + // "default" stands for the default rule syntax in Traefik v3, i.e. the v3 syntax. + RuleSyntax: "default", + Service: serviceName, + TLS: &dynamic.RouterTCPTLSConfig{ + Passthrough: true, + }, + } + + continue + } + + if defaultBackendService != nil && rule.Host != "" { + key := provider.Normalize(ingress.Namespace + "-" + ingress.Name + "-default-backend") + + rt := &dynamic.Router{ + Rule: buildHostRule(rule.Host), + // "default" stands for the default rule syntax in Traefik v3, i.e. the v3 syntax. + RuleSyntax: "default", + Service: key, + } + + if err := p.applyMiddlewares(ingress.Namespace, key, ingressConfig, hasTLS, rt, conf); err != nil { + logger.Error().Err(err).Msg("Error applying middlewares") + } + + conf.HTTP.Routers[key] = rt + + rtTLS := &dynamic.Router{ + Rule: buildHostRule(rule.Host), + // "default" stands for the default rule syntax in Traefik v3, i.e. the v3 syntax. + RuleSyntax: "default", + Service: key, + TLS: &dynamic.RouterTLSConfig{}, + } + + if err := p.applyMiddlewares(ingress.Namespace, key+"-tls", ingressConfig, false, rtTLS, conf); err != nil { + logger.Error().Err(err).Msg("Error applying middlewares") + } + + conf.HTTP.Routers[key+"-tls"] = rtTLS + + if namedServersTransport != nil && defaultBackendService.LoadBalancer != nil { + defaultBackendService.LoadBalancer.ServersTransport = namedServersTransport.Name + conf.HTTP.ServersTransports[namedServersTransport.Name] = namedServersTransport.ServersTransport + } + + conf.HTTP.Services[key] = defaultBackendService + } + + if rule.HTTP == nil { + continue + } + + for pi, pa := range rule.HTTP.Paths { + // As NGINX we are ignoring resource backend. + // An Ingress backend must have se service or a resource definition. + if pa.Backend.Service == nil { + logger.Error().Str("path", pa.Path). + Err(err).Msg("Ignoring path with no service backend") + continue + } + + portString := pa.Backend.Service.Port.Name + if len(pa.Backend.Service.Port.Name) == 0 { + portString = strconv.Itoa(int(pa.Backend.Service.Port.Number)) + } + + // TODO: if no service, do not add middlewares and 503. + serviceName := provider.Normalize(ingress.Namespace + "-" + pa.Backend.Service.Name + "-" + portString) + + service, err := p.buildService(ingress.Namespace, pa.Backend, ingressConfig) + if err != nil { + logger.Error(). + Str("serviceName", pa.Backend.Service.Name). + Str("servicePort", pa.Backend.Service.Port.String()). + Err(err). + Msg("Cannot create service") + continue + } + + rt := &dynamic.Router{ + Rule: buildRule(rule.Host, pa, ingressConfig), + // "default" stands for the default rule syntax in Traefik v3, i.e. the v3 syntax. + RuleSyntax: "default", + Service: serviceName, + } + if hasTLS { + rt.TLS = &dynamic.RouterTLSConfig{} + } + + routerKey := provider.Normalize(fmt.Sprintf("%s-%s-rule-%d-path-%d", ingress.Namespace, ingress.Name, ri, pi)) + + conf.HTTP.Routers[routerKey] = rt + conf.HTTP.Services[serviceName] = service + + if namedServersTransport != nil && service.LoadBalancer != nil { + service.LoadBalancer.ServersTransport = namedServersTransport.Name + conf.HTTP.ServersTransports[namedServersTransport.Name] = namedServersTransport.ServersTransport + } + + if err := p.applyMiddlewares(ingress.Namespace, routerKey, ingressConfig, hasTLS, rt, conf); err != nil { + logger.Error().Err(err).Msg("Error applying middlewares") + } + } + } + } + + conf.TLS = &dynamic.TLSConfiguration{ + Certificates: slices.Collect(maps.Values(uniqCerts)), + } + + return conf +} + +func (p *Provider) buildServersTransport(namespace, name string, cfg ingressConfig) (*namedServersTransport, error) { + scheme := parseBackendProtocol(ptr.Deref(cfg.BackendProtocol, "HTTP")) + if scheme != "https" { + return nil, nil + } + + nst := &namedServersTransport{ + Name: provider.Normalize(namespace + "-" + name), + ServersTransport: &dynamic.ServersTransport{ + ServerName: ptr.Deref(cfg.ProxySSLName, ptr.Deref(cfg.ProxySSLServerName, "")), + InsecureSkipVerify: strings.ToLower(ptr.Deref(cfg.ProxySSLVerify, "off")) == "on", + }, + } + + if sslSecret := ptr.Deref(cfg.ProxySSLSecret, ""); sslSecret != "" { + parts := strings.Split(sslSecret, "/") + if len(parts) != 2 { + return nil, fmt.Errorf("malformed proxy SSL secret: %s, expected namespace/name", sslSecret) + } + + blocks, err := p.certificateBlocks(parts[0], parts[1]) + if err != nil { + return nil, fmt.Errorf("getting certificate blocks: %w", err) + } + + if blocks.CA != nil { + nst.ServersTransport.RootCAs = []types.FileOrContent{*blocks.CA} + } + + if blocks.Certificate != nil { + nst.ServersTransport.Certificates = []tls.Certificate{*blocks.Certificate} + } + } + + return nst, nil +} + +func (p *Provider) buildService(namespace string, backend netv1.IngressBackend, cfg ingressConfig) (*dynamic.Service, error) { + backendAddresses, err := p.getBackendAddresses(namespace, backend, cfg) + if err != nil { + return nil, fmt.Errorf("getting backend addresses: %w", err) + } + + lb := &dynamic.ServersLoadBalancer{} + lb.SetDefaults() + + if ptr.Deref(cfg.Affinity, "") != "" { + lb.Sticky = &dynamic.Sticky{ + Cookie: &dynamic.Cookie{ + Name: ptr.Deref(cfg.SessionCookieName, "INGRESSCOOKIE"), + Secure: ptr.Deref(cfg.SessionCookieSecure, false), + HTTPOnly: true, // Default value in Nginx. + SameSite: strings.ToLower(ptr.Deref(cfg.SessionCookieSameSite, "")), + MaxAge: ptr.Deref(cfg.SessionCookieMaxAge, 0), + Path: ptr.To(ptr.Deref(cfg.SessionCookiePath, "/")), + Domain: ptr.Deref(cfg.SessionCookieDomain, ""), + }, + } + } + + scheme := parseBackendProtocol(ptr.Deref(cfg.BackendProtocol, "HTTP")) + + svc := &dynamic.Service{LoadBalancer: lb} + for _, addr := range backendAddresses { + svc.LoadBalancer.Servers = append(svc.LoadBalancer.Servers, dynamic.Server{ + URL: fmt.Sprintf("%s://%s", scheme, addr.Address), + }) + } + + return svc, nil +} + +func (p *Provider) buildPassthroughService(namespace string, backend netv1.IngressBackend, cfg ingressConfig) (*dynamic.TCPService, error) { + backendAddresses, err := p.getBackendAddresses(namespace, backend, cfg) + if err != nil { + return nil, fmt.Errorf("getting backend addresses: %w", err) + } + + lb := &dynamic.TCPServersLoadBalancer{} + for _, addr := range backendAddresses { + lb.Servers = append(lb.Servers, dynamic.TCPServer{ + Address: addr.Address, + }) + } + + return &dynamic.TCPService{LoadBalancer: lb}, nil +} + +func (p *Provider) getBackendAddresses(namespace string, backend netv1.IngressBackend, cfg ingressConfig) ([]backendAddress, error) { + service, err := p.k8sClient.GetService(namespace, backend.Service.Name) + if err != nil { + return nil, fmt.Errorf("getting service: %w", err) + } + + if p.DisableSvcExternalName && service.Spec.Type == corev1.ServiceTypeExternalName { + return nil, errors.New("externalName services not allowed") + } + + var portName string + var portSpec corev1.ServicePort + var match bool + for _, p := range service.Spec.Ports { + // A port with number 0 or an empty name is not allowed, this case is there for the default backend service. + if (backend.Service.Port.Number == 0 && backend.Service.Port.Name == "") || + (backend.Service.Port.Number == p.Port || (backend.Service.Port.Name == p.Name && len(p.Name) > 0)) { + portName = p.Name + portSpec = p + match = true + break + } + } + if !match { + return nil, errors.New("service port not found") + } + + if service.Spec.Type == corev1.ServiceTypeExternalName { + return []backendAddress{{Address: net.JoinHostPort(service.Spec.ExternalName, strconv.Itoa(int(portSpec.Port)))}}, nil + } + + // When service upstream is set to true we return the service ClusterIP as the backend address. + if ptr.Deref(cfg.ServiceUpstream, false) { + return []backendAddress{{Address: net.JoinHostPort(service.Spec.ClusterIP, strconv.Itoa(int(portSpec.Port)))}}, nil + } + + endpointSlices, err := p.k8sClient.GetEndpointSlicesForService(namespace, backend.Service.Name) + if err != nil { + return nil, fmt.Errorf("getting endpointslices: %w", err) + } + + var addresses []backendAddress + uniqAddresses := map[string]struct{}{} + for _, endpointSlice := range endpointSlices { + var port int32 + for _, p := range endpointSlice.Ports { + if portName == *p.Name { + port = *p.Port + break + } + } + if port == 0 { + continue + } + + for _, endpoint := range endpointSlice.Endpoints { + if !k8s.EndpointServing(endpoint) { + continue + } + + for _, address := range endpoint.Addresses { + if _, ok := uniqAddresses[address]; ok { + continue + } + + uniqAddresses[address] = struct{}{} + addresses = append(addresses, backendAddress{ + Address: net.JoinHostPort(address, strconv.Itoa(int(port))), + Fenced: ptr.Deref(endpoint.Conditions.Terminating, false) && ptr.Deref(endpoint.Conditions.Serving, false), + }) + } + } + } + + return addresses, nil +} + +func (p *Provider) updateIngressStatus(ing *netv1.Ingress) error { + if p.PublishService == "" && len(p.PublishStatusAddress) == 0 { + // Nothing to do, no PublishService or PublishStatusAddress defined. + return nil + } + + if len(p.PublishStatusAddress) > 0 { + ingStatus := make([]netv1.IngressLoadBalancerIngress, 0, len(p.PublishStatusAddress)) + for _, nameOrIP := range p.PublishStatusAddress { + if net.ParseIP(nameOrIP) != nil { + ingStatus = append(ingStatus, netv1.IngressLoadBalancerIngress{IP: nameOrIP}) + continue + } + + ingStatus = append(ingStatus, netv1.IngressLoadBalancerIngress{Hostname: nameOrIP}) + } + + return p.k8sClient.UpdateIngressStatus(ing, ingStatus) + } + + serviceInfo := strings.Split(p.PublishService, "/") + if len(serviceInfo) != 2 { + return fmt.Errorf("parsing publishService, 'namespace/service' format expected: %s", p.PublishService) + } + + serviceNamespace, serviceName := serviceInfo[0], serviceInfo[1] + + service, err := p.k8sClient.GetService(serviceNamespace, serviceName) + if err != nil { + return fmt.Errorf("getting service: %w", err) + } + + var ingressStatus []netv1.IngressLoadBalancerIngress + + switch service.Spec.Type { + case corev1.ServiceTypeExternalName: + ingressStatus = []netv1.IngressLoadBalancerIngress{{ + Hostname: service.Spec.ExternalName, + }} + + case corev1.ServiceTypeClusterIP: + ingressStatus = []netv1.IngressLoadBalancerIngress{{ + IP: service.Spec.ClusterIP, + }} + + case corev1.ServiceTypeNodePort: + if service.Spec.ExternalIPs == nil { + ingressStatus = []netv1.IngressLoadBalancerIngress{{ + IP: service.Spec.ClusterIP, + }} + } else { + ingressStatus = make([]netv1.IngressLoadBalancerIngress, 0, len(service.Spec.ExternalIPs)) + for _, ip := range service.Spec.ExternalIPs { + ingressStatus = append(ingressStatus, netv1.IngressLoadBalancerIngress{IP: ip}) + } + } + + case corev1.ServiceTypeLoadBalancer: + ingressStatus, err = convertSlice[netv1.IngressLoadBalancerIngress](service.Status.LoadBalancer.Ingress) + if err != nil { + return fmt.Errorf("converting ingress loadbalancer status: %w", err) + } + for _, ip := range service.Spec.ExternalIPs { + // Avoid duplicates in the ingress status. + var found bool + for _, status := range ingressStatus { + if status.IP == ip || status.Hostname == ip { + found = true + continue + } + } + if !found { + ingressStatus = append(ingressStatus, netv1.IngressLoadBalancerIngress{IP: ip}) + } + } + } + + return p.k8sClient.UpdateIngressStatus(ing, ingressStatus) +} + +func (p *Provider) shouldProcessIngress(ingress *netv1.Ingress, ingressClasses []*netv1.IngressClass) bool { + if len(ingressClasses) > 0 && ingress.Spec.IngressClassName != nil { + return slices.ContainsFunc(ingressClasses, func(ic *netv1.IngressClass) bool { + return *ingress.Spec.IngressClassName == ic.ObjectMeta.Name + }) + } + + if class, ok := ingress.Annotations[annotationIngressClass]; ok { + return class == p.IngressClass + } + + return p.WatchIngressWithoutClass +} + +func (p *Provider) loadCertificates(ctx context.Context, ingress *netv1.Ingress, uniqCerts map[string]*tls.CertAndStores) error { + for _, t := range ingress.Spec.TLS { + if t.SecretName == "" { + log.Ctx(ctx).Debug().Msg("Skipping TLS sub-section: No secret name provided") + continue + } + + certKey := ingress.Namespace + "-" + t.SecretName + if _, certExists := uniqCerts[certKey]; !certExists { + blocks, err := p.certificateBlocks(ingress.Namespace, t.SecretName) + if err != nil { + return fmt.Errorf("getting certificate blocks: %w", err) + } + + if blocks.Certificate == nil { + return fmt.Errorf("no keypair found in secret %s/%s", ingress.Namespace, t.SecretName) + } + + uniqCerts[certKey] = &tls.CertAndStores{ + Certificate: *blocks.Certificate, + } + } + } + + return nil +} + +func (p *Provider) applyMiddlewares(namespace, routerKey string, ingressConfig ingressConfig, hasTLS bool, rt *dynamic.Router, conf *dynamic.Configuration) error { + if err := p.applyBasicAuthConfiguration(namespace, routerKey, ingressConfig, rt, conf); err != nil { + return fmt.Errorf("applying basic auth configuration: %w", err) + } + + if err := applyForwardAuthConfiguration(routerKey, ingressConfig, rt, conf); err != nil { + return fmt.Errorf("applying forward auth configuration: %w", err) + } + + applyCORSConfiguration(routerKey, ingressConfig, rt, conf) + + // Apply SSL redirect is mandatory to be applied after all other middlewares. + // TODO: check how to remove this, and create the HTTP router elsewhere. + applySSLRedirectConfiguration(routerKey, ingressConfig, hasTLS, rt, conf) + + return nil +} + +func (p *Provider) applyBasicAuthConfiguration(namespace, routerName string, ingressConfig ingressConfig, rt *dynamic.Router, conf *dynamic.Configuration) error { + if ingressConfig.AuthType == nil { + return nil + } + + authType := ptr.Deref(ingressConfig.AuthType, "") + if authType != "basic" && authType != "digest" { + return fmt.Errorf("invalid auth-type %q, must be 'basic' or 'digest'", authType) + } + + authSecret := ptr.Deref(ingressConfig.AuthSecret, "") + if authSecret == "" { + return fmt.Errorf("invalid auth-secret %q, must not be empty", authSecret) + } + + authSecretParts := strings.Split(authSecret, "/") + if len(authSecretParts) > 2 { + return fmt.Errorf("invalid auth secret %q", authSecret) + } + + secretName := authSecretParts[0] + secretNamespace := namespace + if len(authSecretParts) == 2 { + secretNamespace = authSecretParts[0] + secretName = authSecretParts[1] + } + + secret, err := p.k8sClient.GetSecret(secretNamespace, secretName) + if err != nil { + return fmt.Errorf("getting secret %s: %w", authSecret, err) + } + + authSecretType := ptr.Deref(ingressConfig.AuthSecretType, "auth-file") + if authSecretType != "auth-file" && authSecretType != "auth-map" { + return fmt.Errorf("invalid auth-secret-type %q, must be 'auth-file' or 'auth-map'", authSecretType) + } + + users, err := basicAuthUsers(secret, authSecretType) + if err != nil { + return fmt.Errorf("getting users from secret %s: %w", authSecret, err) + } + + realm := ptr.Deref(ingressConfig.AuthRealm, "") + + switch authType { + case "basic": + basicMiddlewareName := routerName + "-basic-auth" + conf.HTTP.Middlewares[basicMiddlewareName] = &dynamic.Middleware{ + BasicAuth: &dynamic.BasicAuth{ + Users: users, + Realm: realm, + RemoveHeader: false, + }, + } + rt.Middlewares = append(rt.Middlewares, basicMiddlewareName) + + case "digest": + digestMiddlewareName := routerName + "-digest-auth" + conf.HTTP.Middlewares[digestMiddlewareName] = &dynamic.Middleware{ + DigestAuth: &dynamic.DigestAuth{ + Users: users, + Realm: realm, + RemoveHeader: false, + }, + } + rt.Middlewares = append(rt.Middlewares, digestMiddlewareName) + } + + return nil +} + +func (p *Provider) certificateBlocks(namespace, name string) (*certBlocks, error) { + secret, err := p.k8sClient.GetSecret(namespace, name) + if err != nil { + return nil, fmt.Errorf("fetching secret %s/%s: %w", namespace, name, err) + } + + certBytes, hasCert := secret.Data[corev1.TLSCertKey] + keyBytes, hasKey := secret.Data[corev1.TLSPrivateKeyKey] + caBytes, hasCA := secret.Data[corev1.ServiceAccountRootCAKey] + + if !hasCert && !hasKey && !hasCA { + return nil, errors.New("secret does not contain a keypair or CA certificate") + } + + var blocks certBlocks + if hasCA { + if len(caBytes) == 0 { + return nil, errors.New("secret contains an empty CA certificate") + } + + ca := types.FileOrContent(caBytes) + blocks.CA = &ca + } + + if hasKey && hasCert { + if len(certBytes) == 0 { + return nil, errors.New("secret contains an empty certificate") + } + if len(keyBytes) == 0 { + return nil, errors.New("secret contains an empty key") + } + blocks.Certificate = &tls.Certificate{ + CertFile: types.FileOrContent(certBytes), + KeyFile: types.FileOrContent(keyBytes), + } + } + + return &blocks, nil +} + +func applyCORSConfiguration(routerName string, ingressConfig ingressConfig, rt *dynamic.Router, conf *dynamic.Configuration) { + if !ptr.Deref(ingressConfig.EnableCORS, false) { + return + } + + corsMiddlewareName := routerName + "-cors" + conf.HTTP.Middlewares[corsMiddlewareName] = &dynamic.Middleware{ + Headers: &dynamic.Headers{ + AccessControlAllowCredentials: ptr.Deref(ingressConfig.EnableCORSAllowCredentials, true), + AccessControlExposeHeaders: ptr.Deref(ingressConfig.CORSExposeHeaders, []string{}), + AccessControlAllowHeaders: ptr.Deref(ingressConfig.CORSAllowHeaders, []string{"DNT", "Keep-Alive", "User-Agent", "X-Requested-With", "If-Modified-Since", "Cache-Control", "Content-Type", "Range,Authorization"}), + AccessControlAllowMethods: ptr.Deref(ingressConfig.CORSAllowMethods, []string{"GET", "PUT", "POST", "DELETE", "PATCH", "OPTIONS"}), + AccessControlAllowOriginList: ptr.Deref(ingressConfig.CORSAllowOrigin, []string{"*"}), + AccessControlMaxAge: int64(ptr.Deref(ingressConfig.CORSMaxAge, 1728000)), + }, + } + + rt.Middlewares = append(rt.Middlewares, corsMiddlewareName) +} + +func applySSLRedirectConfiguration(routerName string, ingressConfig ingressConfig, hasTLS bool, rt *dynamic.Router, conf *dynamic.Configuration) { + var forceSSLRedirect bool + if ingressConfig.ForceSSLRedirect != nil { + forceSSLRedirect = *ingressConfig.ForceSSLRedirect + } + + sslRedirect := ptr.Deref(ingressConfig.SSLRedirect, hasTLS) + + if !forceSSLRedirect && !sslRedirect { + if hasTLS { + httpRouter := &dynamic.Router{ + Rule: rt.Rule, + // "default" stands for the default rule syntax in Traefik v3, i.e. the v3 syntax. + RuleSyntax: "default", + Middlewares: rt.Middlewares, + Service: rt.Service, + } + + conf.HTTP.Routers[routerName+"-http"] = httpRouter + } + + return + } + + redirectRouter := &dynamic.Router{ + Rule: rt.Rule, + // "default" stands for the default rule syntax in Traefik v3, i.e. the v3 syntax. + RuleSyntax: "default", + Service: "noop@internal", + } + + redirectMiddlewareName := routerName + "-redirect-scheme" + conf.HTTP.Middlewares[redirectMiddlewareName] = &dynamic.Middleware{ + RedirectScheme: &dynamic.RedirectScheme{ + Scheme: "https", + Permanent: true, + }, + } + redirectRouter.Middlewares = append(redirectRouter.Middlewares, redirectMiddlewareName) + + conf.HTTP.Routers[routerName+"-redirect"] = redirectRouter +} + +func applyForwardAuthConfiguration(routerName string, ingressConfig ingressConfig, rt *dynamic.Router, conf *dynamic.Configuration) error { + if ingressConfig.AuthURL == nil { + return nil + } + + if *ingressConfig.AuthURL == "" { + return errors.New("empty auth-url found in ingress annotations") + } + + authResponseHeaders := strings.Split(ptr.Deref(ingressConfig.AuthResponseHeaders, ""), ",") + + forwardMiddlewareName := routerName + "-forward-auth" + conf.HTTP.Middlewares[forwardMiddlewareName] = &dynamic.Middleware{ + ForwardAuth: &dynamic.ForwardAuth{ + Address: *ingressConfig.AuthURL, + AuthResponseHeaders: authResponseHeaders, + }, + } + rt.Middlewares = append(rt.Middlewares, forwardMiddlewareName) + + return nil +} + +func basicAuthUsers(secret *corev1.Secret, authSecretType string) (dynamic.Users, error) { + var users dynamic.Users + if authSecretType == "auth-map" { + if len(secret.Data) == 0 { + return nil, fmt.Errorf("secret %s/%s does not contain any user credentials", secret.Namespace, secret.Name) + } + + for user, pass := range secret.Data { + users = append(users, user+":"+string(pass)) + } + + return users, nil + } + + // Default to auth-file type. + authFileContent, ok := secret.Data["auth"] + if !ok { + return nil, fmt.Errorf("secret %s/%s does not contain auth-file content key `auth`", secret.Namespace, secret.Name) + } + + // Trim lines and filter out blanks + rawLines := strings.Split(string(authFileContent), "\n") + for _, rawLine := range rawLines { + line := strings.TrimSpace(rawLine) + if line != "" && !strings.HasPrefix(line, "#") { + users = append(users, line) + } + } + + return users, nil +} + +func buildRule(host string, pa netv1.HTTPIngressPath, config ingressConfig) string { + var rules []string + if len(host) > 0 { + rules = append(rules, buildHostRule(host)) + } + + if len(pa.Path) > 0 { + pathType := ptr.Deref(pa.PathType, netv1.PathTypePrefix) + if pathType == netv1.PathTypeImplementationSpecific { + pathType = netv1.PathTypePrefix + } + + switch pathType { + case netv1.PathTypeExact: + rules = append(rules, fmt.Sprintf("Path(`%s`)", pa.Path)) + case netv1.PathTypePrefix: + if ptr.Deref(config.UseRegex, false) { + rules = append(rules, fmt.Sprintf("PathRegexp(`^%s`)", regexp.QuoteMeta(pa.Path))) + } else { + rules = append(rules, buildPrefixRule(pa.Path)) + } + } + } + + return strings.Join(rules, " && ") +} + +func buildHostRule(host string) string { + if strings.HasPrefix(host, "*.") { + host = strings.Replace(regexp.QuoteMeta(host), `\*\.`, `[a-zA-Z0-9-]+\.`, 1) + return fmt.Sprintf("HostRegexp(`^%s$`)", host) + } + + return fmt.Sprintf("Host(`%s`)", host) +} + +// buildPrefixRule is a helper function to build a path prefix rule that matches path prefix split by `/`. +// For example, the paths `/abc`, `/abc/`, and `/abc/def` would all match the prefix `/abc`, +// but the path `/abcd` would not. See TestStrictPrefixMatchingRule() for more examples. +// +// "PathPrefix" in Kubernetes Gateway API is semantically equivalent to the "Prefix" path type in the +// Kubernetes Ingress API. +func buildPrefixRule(path string) string { + if path == "/" { + return "PathPrefix(`/`)" + } + + path = strings.TrimSuffix(path, "/") + return fmt.Sprintf("(Path(`%[1]s`) || PathPrefix(`%[1]s/`))", path) +} + +func throttleEvents(ctx context.Context, throttleDuration time.Duration, pool *safe.Pool, eventsChan <-chan interface{}) chan interface{} { + if throttleDuration == 0 { + return nil + } + + // Create a buffered channel to hold the pending event (if we're delaying processing the event due to throttling). + eventsChanBuffered := make(chan interface{}, 1) + + // Run a goroutine that reads events from eventChan and does a + // non-blocking write to pendingEvent. This guarantees that writing to + // eventChan will never block, and that pendingEvent will have + // something in it if there's been an event since we read from that channel. + pool.GoCtx(func(ctxPool context.Context) { + for { + select { + case <-ctxPool.Done(): + return + case nextEvent := <-eventsChan: + select { + case eventsChanBuffered <- nextEvent: + default: + // We already have an event in eventsChanBuffered, so we'll + // do a refresh as soon as our throttle allows us to. It's fine + // to drop the event and keep whatever's in the buffer -- we + // don't do different things for different events. + log.Ctx(ctx).Debug().Msgf("Dropping event kind %T due to throttling", nextEvent) + } + } + } + }) + + return eventsChanBuffered +} diff --git a/pkg/provider/kubernetes/ingress-nginx/kubernetes_test.go b/pkg/provider/kubernetes/ingress-nginx/kubernetes_test.go new file mode 100644 index 000000000..37f819300 --- /dev/null +++ b/pkg/provider/kubernetes/ingress-nginx/kubernetes_test.go @@ -0,0 +1,605 @@ +package ingressnginx + +import ( + "math" + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/traefik/traefik/v3/pkg/config/dynamic" + "github.com/traefik/traefik/v3/pkg/provider/kubernetes/k8s" + "github.com/traefik/traefik/v3/pkg/tls" + "github.com/traefik/traefik/v3/pkg/types" + "k8s.io/apimachinery/pkg/runtime" + kubefake "k8s.io/client-go/kubernetes/fake" + "k8s.io/utils/ptr" +) + +func TestLoadIngresses(t *testing.T) { + testCases := []struct { + desc string + ingressClass string + defaultBackendServiceName string + defaultBackendServiceNamespace string + paths []string + expected *dynamic.Configuration + }{ + { + desc: "Empty, no IngressClass", + paths: []string{ + "services.yml", + "ingresses/01-ingress-with-basicauth.yml", + }, + expected: &dynamic.Configuration{ + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Services: map[string]*dynamic.TCPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, + ServersTransports: map[string]*dynamic.ServersTransport{}, + }, + TLS: &dynamic.TLSConfiguration{}, + }, + }, + { + desc: "Basic Auth", + paths: []string{ + "services.yml", + "ingressclasses.yml", + "ingresses/01-ingress-with-basicauth.yml", + }, + expected: &dynamic.Configuration{ + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Services: map[string]*dynamic.TCPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{ + "default-ingress-with-basicauth-rule-0-path-0": { + Rule: "Host(`whoami.localhost`) && Path(`/basicauth`)", + RuleSyntax: "default", + Middlewares: []string{"default-ingress-with-basicauth-rule-0-path-0-basic-auth"}, + Service: "default-whoami-80", + }, + }, + Middlewares: map[string]*dynamic.Middleware{ + "default-ingress-with-basicauth-rule-0-path-0-basic-auth": { + BasicAuth: &dynamic.BasicAuth{ + Users: dynamic.Users{ + "user:{SHA}W6ph5Mm5Pz8GgiULbPgzG37mj9g=", + }, + Realm: "Authentication Required", + }, + }, + }, + Services: map[string]*dynamic.Service{ + "default-whoami-80": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + Servers: []dynamic.Server{ + { + URL: "http://10.10.0.1:80", + }, + { + URL: "http://10.10.0.2:80", + }, + }, + Strategy: "wrr", + PassHostHeader: ptr.To(true), + ResponseForwarding: &dynamic.ResponseForwarding{ + FlushInterval: dynamic.DefaultFlushInterval, + }, + }, + }, + }, + ServersTransports: map[string]*dynamic.ServersTransport{}, + }, + TLS: &dynamic.TLSConfiguration{}, + }, + }, + { + desc: "Forward Auth", + paths: []string{ + "services.yml", + "ingressclasses.yml", + "ingresses/02-ingress-with-forwardauth.yml", + }, + expected: &dynamic.Configuration{ + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Services: map[string]*dynamic.TCPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{ + "default-ingress-with-forwardauth-rule-0-path-0": { + Rule: "Host(`whoami.localhost`) && Path(`/forwardauth`)", + RuleSyntax: "default", + Middlewares: []string{"default-ingress-with-forwardauth-rule-0-path-0-forward-auth"}, + Service: "default-whoami-80", + }, + }, + Middlewares: map[string]*dynamic.Middleware{ + "default-ingress-with-forwardauth-rule-0-path-0-forward-auth": { + ForwardAuth: &dynamic.ForwardAuth{ + Address: "http://whoami.default.svc/", + AuthResponseHeaders: []string{"X-Foo"}, + }, + }, + }, + Services: map[string]*dynamic.Service{ + "default-whoami-80": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + Servers: []dynamic.Server{ + { + URL: "http://10.10.0.1:80", + }, + { + URL: "http://10.10.0.2:80", + }, + }, + Strategy: "wrr", + PassHostHeader: ptr.To(true), + ResponseForwarding: &dynamic.ResponseForwarding{ + FlushInterval: dynamic.DefaultFlushInterval, + }, + }, + }, + }, + ServersTransports: map[string]*dynamic.ServersTransport{}, + }, + TLS: &dynamic.TLSConfiguration{}, + }, + }, + { + desc: "SSL Redirect", + paths: []string{ + "services.yml", + "secrets.yml", + "ingressclasses.yml", + "ingresses/03-ingress-with-ssl-redirect.yml", + }, + expected: &dynamic.Configuration{ + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Services: map[string]*dynamic.TCPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{ + "default-ingress-with-ssl-redirect-rule-0-path-0": { + Rule: "Host(`sslredirect.localhost`) && Path(`/`)", + RuleSyntax: "default", + TLS: &dynamic.RouterTLSConfig{}, + Service: "default-whoami-80", + }, + "default-ingress-with-ssl-redirect-rule-0-path-0-redirect": { + Rule: "Host(`sslredirect.localhost`) && Path(`/`)", + RuleSyntax: "default", + Middlewares: []string{"default-ingress-with-ssl-redirect-rule-0-path-0-redirect-scheme"}, + Service: "noop@internal", + }, + "default-ingress-without-ssl-redirect-rule-0-path-0-http": { + Rule: "Host(`withoutsslredirect.localhost`) && Path(`/`)", + RuleSyntax: "default", + Service: "default-whoami-80", + }, + "default-ingress-without-ssl-redirect-rule-0-path-0": { + Rule: "Host(`withoutsslredirect.localhost`) && Path(`/`)", + RuleSyntax: "default", + TLS: &dynamic.RouterTLSConfig{}, + Service: "default-whoami-80", + }, + "default-ingress-with-force-ssl-redirect-rule-0-path-0": { + Rule: "Host(`forcesslredirect.localhost`) && Path(`/`)", + RuleSyntax: "default", + Service: "default-whoami-80", + }, + "default-ingress-with-force-ssl-redirect-rule-0-path-0-redirect": { + Rule: "Host(`forcesslredirect.localhost`) && Path(`/`)", + RuleSyntax: "default", + Middlewares: []string{"default-ingress-with-force-ssl-redirect-rule-0-path-0-redirect-scheme"}, + Service: "noop@internal", + }, + }, + Middlewares: map[string]*dynamic.Middleware{ + "default-ingress-with-ssl-redirect-rule-0-path-0-redirect-scheme": { + RedirectScheme: &dynamic.RedirectScheme{ + Scheme: "https", + Permanent: true, + }, + }, + "default-ingress-with-force-ssl-redirect-rule-0-path-0-redirect-scheme": { + RedirectScheme: &dynamic.RedirectScheme{ + Scheme: "https", + Permanent: true, + }, + }, + }, + Services: map[string]*dynamic.Service{ + "default-whoami-80": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + Servers: []dynamic.Server{ + { + URL: "http://10.10.0.1:80", + }, + { + URL: "http://10.10.0.2:80", + }, + }, + Strategy: "wrr", + PassHostHeader: ptr.To(true), + ResponseForwarding: &dynamic.ResponseForwarding{ + FlushInterval: dynamic.DefaultFlushInterval, + }, + }, + }, + }, + ServersTransports: map[string]*dynamic.ServersTransport{}, + }, + TLS: &dynamic.TLSConfiguration{ + Certificates: []*tls.CertAndStores{ + { + Certificate: tls.Certificate{ + CertFile: "-----BEGIN CERTIFICATE-----", + KeyFile: "-----BEGIN CERTIFICATE-----", + }, + }, + }, + }, + }, + }, + { + desc: "SSL Passthrough", + paths: []string{ + "services.yml", + "secrets.yml", + "ingressclasses.yml", + "ingresses/04-ingress-with-ssl-passthrough.yml", + }, + expected: &dynamic.Configuration{ + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{ + "default-ingress-with-ssl-passthrough-passthrough-whoami-localhost": { + Rule: "HostSNI(`passthrough.whoami.localhost`)", + RuleSyntax: "default", + TLS: &dynamic.RouterTCPTLSConfig{ + Passthrough: true, + }, + Service: "default-whoami-tls-443", + }, + }, + Services: map[string]*dynamic.TCPService{ + "default-whoami-tls-443": { + LoadBalancer: &dynamic.TCPServersLoadBalancer{ + Servers: []dynamic.TCPServer{ + { + Address: "10.10.0.5:8443", + }, + { + Address: "10.10.0.6:8443", + }, + }, + }, + }, + }, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{}, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{}, + ServersTransports: map[string]*dynamic.ServersTransport{}, + }, + TLS: &dynamic.TLSConfiguration{}, + }, + }, + { + desc: "Sticky Sessions", + paths: []string{ + "services.yml", + "secrets.yml", + "ingressclasses.yml", + "ingresses/06-ingress-with-sticky.yml", + }, + expected: &dynamic.Configuration{ + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Services: map[string]*dynamic.TCPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{ + "default-ingress-with-sticky-rule-0-path-0": { + Rule: "Host(`sticky.localhost`) && Path(`/`)", + RuleSyntax: "default", + Service: "default-whoami-80", + }, + }, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{ + "default-whoami-80": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + Servers: []dynamic.Server{ + { + URL: "http://10.10.0.1:80", + }, + { + URL: "http://10.10.0.2:80", + }, + }, + Strategy: "wrr", + PassHostHeader: ptr.To(true), + ResponseForwarding: &dynamic.ResponseForwarding{ + FlushInterval: dynamic.DefaultFlushInterval, + }, + Sticky: &dynamic.Sticky{ + Cookie: &dynamic.Cookie{ + Name: "foobar", + Domain: "foo.localhost", + HTTPOnly: true, + MaxAge: 42, + Path: ptr.To("/foobar"), + SameSite: "none", + Secure: true, + }, + }, + }, + }, + }, + ServersTransports: map[string]*dynamic.ServersTransport{}, + }, + TLS: &dynamic.TLSConfiguration{}, + }, + }, + { + desc: "Proxy SSL", + paths: []string{ + "services.yml", + "secrets.yml", + "ingressclasses.yml", + "ingresses/07-ingress-with-proxy-ssl.yml", + }, + expected: &dynamic.Configuration{ + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Services: map[string]*dynamic.TCPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{ + "default-ingress-with-proxy-ssl-rule-0-path-0": { + Rule: "Host(`proxy-ssl.localhost`) && Path(`/`)", + RuleSyntax: "default", + Service: "default-whoami-tls-443", + }, + }, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{ + "default-whoami-tls-443": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + Servers: []dynamic.Server{ + { + URL: "https://10.10.0.5:8443", + }, + { + URL: "https://10.10.0.6:8443", + }, + }, + Strategy: "wrr", + PassHostHeader: ptr.To(true), + ResponseForwarding: &dynamic.ResponseForwarding{ + FlushInterval: dynamic.DefaultFlushInterval, + }, + ServersTransport: "default-ingress-with-proxy-ssl", + }, + }, + }, + ServersTransports: map[string]*dynamic.ServersTransport{ + "default-ingress-with-proxy-ssl": { + ServerName: "whoami.localhost", + InsecureSkipVerify: true, + RootCAs: []types.FileOrContent{"-----BEGIN CERTIFICATE-----"}, + }, + }, + }, + TLS: &dynamic.TLSConfiguration{}, + }, + }, + { + desc: "CORS", + paths: []string{ + "services.yml", + "ingressclasses.yml", + "ingresses/08-ingress-with-cors.yml", + }, + expected: &dynamic.Configuration{ + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Services: map[string]*dynamic.TCPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{ + "default-ingress-with-cors-rule-0-path-0": { + Rule: "Host(`cors.localhost`) && Path(`/`)", + RuleSyntax: "default", + Middlewares: []string{"default-ingress-with-cors-rule-0-path-0-cors"}, + Service: "default-whoami-80", + }, + }, + Middlewares: map[string]*dynamic.Middleware{ + "default-ingress-with-cors-rule-0-path-0-cors": { + Headers: &dynamic.Headers{ + AccessControlAllowCredentials: true, + AccessControlAllowHeaders: []string{"X-Foo"}, + AccessControlAllowMethods: []string{"PUT", "GET", "POST", "OPTIONS"}, + AccessControlAllowOriginList: []string{"*"}, + AccessControlExposeHeaders: []string{"X-Forwarded-For", "X-Forwarded-Host"}, + AccessControlMaxAge: 42, + }, + }, + }, + Services: map[string]*dynamic.Service{ + "default-whoami-80": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + Servers: []dynamic.Server{ + { + URL: "http://10.10.0.1:80", + }, + { + URL: "http://10.10.0.2:80", + }, + }, + Strategy: "wrr", + PassHostHeader: ptr.To(true), + ResponseForwarding: &dynamic.ResponseForwarding{ + FlushInterval: dynamic.DefaultFlushInterval, + }, + }, + }, + }, + ServersTransports: map[string]*dynamic.ServersTransport{}, + }, + TLS: &dynamic.TLSConfiguration{}, + }, + }, + { + desc: "Service Upstream", + paths: []string{ + "services.yml", + "ingressclasses.yml", + "ingresses/09-ingress-with-service-upstream.yml", + }, + expected: &dynamic.Configuration{ + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Services: map[string]*dynamic.TCPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{ + "default-ingress-with-service-upstream-rule-0-path-0": { + Rule: "Host(`service-upstream.localhost`) && Path(`/`)", + RuleSyntax: "default", + Service: "default-whoami-80", + }, + }, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{ + "default-whoami-80": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + Servers: []dynamic.Server{ + { + URL: "http://10.10.10.1:80", + }, + }, + Strategy: "wrr", + PassHostHeader: ptr.To(true), + ResponseForwarding: &dynamic.ResponseForwarding{ + FlushInterval: dynamic.DefaultFlushInterval, + }, + }, + }, + }, + ServersTransports: map[string]*dynamic.ServersTransport{}, + }, + TLS: &dynamic.TLSConfiguration{}, + }, + }, + { + desc: "Default Backend", + defaultBackendServiceName: "whoami", + defaultBackendServiceNamespace: "default", + paths: []string{ + "services.yml", + }, + expected: &dynamic.Configuration{ + TCP: &dynamic.TCPConfiguration{ + Routers: map[string]*dynamic.TCPRouter{}, + Services: map[string]*dynamic.TCPService{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{ + "default-backend": { + Rule: "PathPrefix(`/`)", + RuleSyntax: "default", + Priority: math.MinInt32, + Service: "default-backend", + }, + "default-backend-tls": { + Rule: "PathPrefix(`/`)", + RuleSyntax: "default", + Priority: math.MinInt32, + TLS: &dynamic.RouterTLSConfig{}, + Service: "default-backend", + }, + }, + Middlewares: map[string]*dynamic.Middleware{}, + Services: map[string]*dynamic.Service{ + "default-backend": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + Servers: []dynamic.Server{ + { + URL: "http://10.10.0.1:8000", + }, + { + URL: "http://10.10.0.2:8000", + }, + }, + Strategy: "wrr", + PassHostHeader: ptr.To(true), + ResponseForwarding: &dynamic.ResponseForwarding{ + FlushInterval: dynamic.DefaultFlushInterval, + }, + }, + }, + }, + ServersTransports: map[string]*dynamic.ServersTransport{}, + }, + TLS: &dynamic.TLSConfiguration{}, + }, + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + k8sObjects := readResources(t, test.paths) + kubeClient := kubefake.NewClientset(k8sObjects...) + client := newClient(kubeClient) + + eventCh, err := client.WatchAll(t.Context(), "", "") + require.NoError(t, err) + + if len(k8sObjects) > 0 { + // just wait for the first event + <-eventCh + } + + p := Provider{ + k8sClient: client, + defaultBackendServiceName: test.defaultBackendServiceName, + defaultBackendServiceNamespace: test.defaultBackendServiceNamespace, + } + p.SetDefaults() + + conf := p.loadConfiguration(t.Context()) + assert.Equal(t, test.expected, conf) + }) + } +} + +func readResources(t *testing.T, paths []string) []runtime.Object { + t.Helper() + + var k8sObjects []runtime.Object + for _, path := range paths { + yamlContent, err := os.ReadFile(filepath.FromSlash("./fixtures/" + path)) + if err != nil { + panic(err) + } + + k8sObjects = append(k8sObjects, k8s.MustParseYaml(yamlContent)...) + } + + return k8sObjects +} diff --git a/pkg/provider/kubernetes/ingress/annotations_test.go b/pkg/provider/kubernetes/ingress/annotations_test.go index bda404889..1f86f51c7 100644 --- a/pkg/provider/kubernetes/ingress/annotations_test.go +++ b/pkg/provider/kubernetes/ingress/annotations_test.go @@ -6,6 +6,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/traefik/traefik/v3/pkg/config/dynamic" + otypes "github.com/traefik/traefik/v3/pkg/observability/types" "github.com/traefik/traefik/v3/pkg/types" ) @@ -58,9 +59,10 @@ func Test_parseRouterConfig(t *testing.T) { Options: "foobar", }, Observability: &dynamic.RouterObservabilityConfig{ - AccessLogs: pointer(true), - Tracing: pointer(true), - Metrics: pointer(true), + AccessLogs: pointer(true), + Tracing: pointer(true), + Metrics: pointer(true), + TraceVerbosity: otypes.MinimalVerbosity, }, }, }, diff --git a/pkg/provider/kubernetes/ingress/client_test.go b/pkg/provider/kubernetes/ingress/client_test.go index 24240f277..9014265bd 100644 --- a/pkg/provider/kubernetes/ingress/client_test.go +++ b/pkg/provider/kubernetes/ingress/client_test.go @@ -1,7 +1,6 @@ package ingress import ( - "context" "errors" "testing" "time" @@ -249,7 +248,7 @@ func TestClientIgnoresEmptyEndpointSliceUpdates(t *testing.T) { assert.Fail(t, "expected to receive event for endpointslices") } - emptyEndpointSlice, err = kubeClient.DiscoveryV1().EndpointSlices("test").Get(context.TODO(), "empty-endpointslice", metav1.GetOptions{}) + emptyEndpointSlice, err = kubeClient.DiscoveryV1().EndpointSlices("test").Get(t.Context(), "empty-endpointslice", metav1.GetOptions{}) assert.NoError(t, err) // Update endpoint annotation and resource version (apparently not done by fake client itself) @@ -257,7 +256,7 @@ func TestClientIgnoresEmptyEndpointSliceUpdates(t *testing.T) { // This reflects the behavior of kubernetes controllers which use endpoint annotations for leader election. emptyEndpointSlice.Annotations["test-annotation"] = "___" emptyEndpointSlice.ResourceVersion = "1245" - _, err = kubeClient.DiscoveryV1().EndpointSlices("test").Update(context.TODO(), emptyEndpointSlice, metav1.UpdateOptions{}) + _, err = kubeClient.DiscoveryV1().EndpointSlices("test").Update(t.Context(), emptyEndpointSlice, metav1.UpdateOptions{}) require.NoError(t, err) select { @@ -269,12 +268,12 @@ func TestClientIgnoresEmptyEndpointSliceUpdates(t *testing.T) { case <-time.After(50 * time.Millisecond): } - filledEndpointSlice, err = kubeClient.DiscoveryV1().EndpointSlices("test").Get(context.TODO(), "filled-endpointslice", metav1.GetOptions{}) + filledEndpointSlice, err = kubeClient.DiscoveryV1().EndpointSlices("test").Get(t.Context(), "filled-endpointslice", metav1.GetOptions{}) assert.NoError(t, err) filledEndpointSlice.Endpoints[0].Addresses[0] = "10.13.37.2" filledEndpointSlice.ResourceVersion = "1235" - _, err = kubeClient.DiscoveryV1().EndpointSlices("test").Update(context.TODO(), filledEndpointSlice, metav1.UpdateOptions{}) + _, err = kubeClient.DiscoveryV1().EndpointSlices("test").Update(t.Context(), filledEndpointSlice, metav1.UpdateOptions{}) require.NoError(t, err) select { @@ -296,7 +295,7 @@ func TestClientIgnoresEmptyEndpointSliceUpdates(t *testing.T) { newPortNumber := int32(42) filledEndpointSlice.Ports[0].Port = &newPortNumber filledEndpointSlice.ResourceVersion = "1236" - _, err = kubeClient.DiscoveryV1().EndpointSlices("test").Update(context.TODO(), filledEndpointSlice, metav1.UpdateOptions{}) + _, err = kubeClient.DiscoveryV1().EndpointSlices("test").Update(t.Context(), filledEndpointSlice, metav1.UpdateOptions{}) require.NoError(t, err) select { diff --git a/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-native-lb-by-default-but-service-has-disabled-nativelb.yml b/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-native-lb-by-default-but-service-has-disabled-nativelb.yml new file mode 100644 index 000000000..a076f3c3e --- /dev/null +++ b/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-native-lb-by-default-but-service-has-disabled-nativelb.yml @@ -0,0 +1,53 @@ +kind: Ingress +apiVersion: networking.k8s.io/v1 +metadata: + name: global-native-lb + namespace: default +spec: + rules: + - host: traefik.tchouk + http: + paths: + - path: /bar + backend: + service: + name: native-disabled-svc + port: + name: web + number: 8080 + pathType: Prefix + +--- +kind: Service +apiVersion: v1 +metadata: + name: native-disabled-svc + namespace: default + annotations: + traefik.ingress.kubernetes.io/service.nativelb: "false" +spec: + ports: + - name: web + port: 8080 + clusterIP: 10.0.0.1 + type: ClusterIP + externalName: traefik.wtf + +--- +kind: EndpointSlice +apiVersion: discovery.k8s.io/v1 +metadata: + name: native-disabled-svc-abc + namespace: default + labels: + kubernetes.io/service-name: native-disabled-svc +addressType: IPv4 +ports: + - name: web + port: 8080 +endpoints: + - addresses: + - 10.10.0.20 + - 10.10.0.21 + conditions: + ready: true \ No newline at end of file diff --git a/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-strict-prefix-matching.yml b/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-strict-prefix-matching.yml new file mode 100644 index 000000000..c52467d71 --- /dev/null +++ b/pkg/provider/kubernetes/ingress/fixtures/Ingress-with-strict-prefix-matching.yml @@ -0,0 +1,49 @@ +kind: Ingress +apiVersion: networking.k8s.io/v1 +metadata: + name: "" + namespace: testing + +spec: + rules: + - http: + paths: + - path: /bar + backend: + service: + name: service1 + port: + number: 80 + pathType: Prefix + +--- +kind: Service +apiVersion: v1 +metadata: + name: service1 + namespace: testing + +spec: + ports: + - port: 80 + clusterIP: 10.0.0.1 + +--- +kind: EndpointSlice +apiVersion: discovery.k8s.io/v1 +metadata: + name: service1-abc + namespace: testing + labels: + kubernetes.io/service-name: service1 + +addressType: IPv4 +ports: + - port: 8080 + name: "" +endpoints: + - addresses: + - 10.10.0.1 + - 10.21.0.1 + conditions: + ready: true diff --git a/pkg/provider/kubernetes/ingress/kubernetes.go b/pkg/provider/kubernetes/ingress/kubernetes.go index 591bdcf55..7a1919b3e 100644 --- a/pkg/provider/kubernetes/ingress/kubernetes.go +++ b/pkg/provider/kubernetes/ingress/kubernetes.go @@ -21,7 +21,7 @@ import ( ptypes "github.com/traefik/paerser/types" "github.com/traefik/traefik/v3/pkg/config/dynamic" "github.com/traefik/traefik/v3/pkg/job" - "github.com/traefik/traefik/v3/pkg/logs" + "github.com/traefik/traefik/v3/pkg/observability/logs" "github.com/traefik/traefik/v3/pkg/provider" "github.com/traefik/traefik/v3/pkg/provider/kubernetes/k8s" "github.com/traefik/traefik/v3/pkg/safe" @@ -56,6 +56,7 @@ type Provider struct { DisableIngressClassLookup bool `description:"Disables the lookup of IngressClasses (Deprecated, please use DisableClusterScopeResources)." json:"disableIngressClassLookup,omitempty" toml:"disableIngressClassLookup,omitempty" yaml:"disableIngressClassLookup,omitempty" export:"true"` DisableClusterScopeResources bool `description:"Disables the lookup of cluster scope resources (incompatible with IngressClasses and NodePortLB enabled services)." json:"disableClusterScopeResources,omitempty" toml:"disableClusterScopeResources,omitempty" yaml:"disableClusterScopeResources,omitempty" export:"true"` NativeLBByDefault bool `description:"Defines whether to use Native Kubernetes load-balancing mode by default." json:"nativeLBByDefault,omitempty" toml:"nativeLBByDefault,omitempty" yaml:"nativeLBByDefault,omitempty" export:"true"` + StrictPrefixMatching bool `description:"Make prefix matching strictly comply with the Kubernetes Ingress specification (path-element-wise matching instead of character-by-character string matching)." json:"strictPrefixMatching,omitempty" toml:"strictPrefixMatching,omitempty" yaml:"strictPrefixMatching,omitempty" export:"true"` // The default rule syntax is initialized with the configuration defined by the user with the core.DefaultRuleSyntax option. DefaultRuleSyntax string `json:"-" toml:"-" yaml:"-" label:"-" file:"-"` @@ -700,7 +701,7 @@ func (p *Provider) loadRouter(rule netv1.IngressRule, pa netv1.HTTPIngressPath, matcher = "Path" } - rules = append(rules, fmt.Sprintf("%s(`%s`)", matcher, pa.Path)) + rules = append(rules, buildRule(p.StrictPrefixMatching, matcher, pa.Path)) } rt.Rule = strings.Join(rules, " && ") @@ -846,6 +847,41 @@ func makeRouterKeyWithHash(key, rule string) (string, error) { return dupKey, nil } +func buildRule(strictPrefixMatching bool, matcher string, path string) string { + // When enabled, strictPrefixMatching ensures that prefix matching follows + // the Kubernetes Ingress spec (path-element-wise instead of character-wise). + if strictPrefixMatching && matcher == "PathPrefix" { + // According to + // https://kubernetes.io/docs/concepts/services-networking/ingress/#examples, + // "/v12" should not match "/v1". + // + // Traefik's default PathPrefix matcher performs a character-wise prefix match, + // unlike Kubernetes which matches path elements. To mimic Kubernetes behavior, + // we will use Path and PathPrefix to replicate element-wise behavior. + // + // "PathPrefix" in Kubernetes Gateway API is semantically equivalent to the "Prefix" path type in the + // Kubernetes Ingress API. + return buildStrictPrefixMatchingRule(path) + } + + return fmt.Sprintf("%s(`%s`)", matcher, path) +} + +// buildStrictPrefixMatchingRule is a helper function to build a path prefix rule that matches path prefix split by `/`. +// For example, the paths `/abc`, `/abc/`, and `/abc/def` would all match the prefix `/abc`, +// but the path `/abcd` would not. See TestStrictPrefixMatchingRule() for more examples. +// +// "PathPrefix" in Kubernetes Gateway API is semantically equivalent to the "Prefix" path type in the +// Kubernetes Ingress API. +func buildStrictPrefixMatchingRule(path string) string { + if path == "/" { + return "PathPrefix(`/`)" + } + + path = strings.TrimSuffix(path, "/") + return fmt.Sprintf("(Path(`%[1]s`) || PathPrefix(`%[1]s/`))", path) +} + func throttleEvents(ctx context.Context, throttleDuration time.Duration, pool *safe.Pool, eventsChan <-chan interface{}) chan interface{} { if throttleDuration == 0 { return nil diff --git a/pkg/provider/kubernetes/ingress/kubernetes_test.go b/pkg/provider/kubernetes/ingress/kubernetes_test.go index 548fc4124..d965e68a2 100644 --- a/pkg/provider/kubernetes/ingress/kubernetes_test.go +++ b/pkg/provider/kubernetes/ingress/kubernetes_test.go @@ -1,9 +1,11 @@ package ingress import ( - "context" "errors" + "fmt" "math" + "net/http" + "net/http/httptest" "os" "path/filepath" "strings" @@ -14,6 +16,8 @@ import ( "github.com/stretchr/testify/require" ptypes "github.com/traefik/paerser/types" "github.com/traefik/traefik/v3/pkg/config/dynamic" + traefikhttp "github.com/traefik/traefik/v3/pkg/muxer/http" + otypes "github.com/traefik/traefik/v3/pkg/observability/types" "github.com/traefik/traefik/v3/pkg/provider" "github.com/traefik/traefik/v3/pkg/provider/kubernetes/k8s" "github.com/traefik/traefik/v3/pkg/tls" @@ -38,6 +42,7 @@ func TestLoadConfigurationFromIngresses(t *testing.T) { disableIngressClassLookup bool disableClusterScopeResources bool defaultRuleSyntax string + strictPrefixMatching bool }{ { desc: "Empty ingresses", @@ -120,9 +125,10 @@ func TestLoadConfigurationFromIngresses(t *testing.T) { Options: "foobar", }, Observability: &dynamic.RouterObservabilityConfig{ - AccessLogs: pointer(true), - Tracing: pointer(true), - Metrics: pointer(true), + AccessLogs: pointer(true), + Tracing: pointer(true), + Metrics: pointer(true), + TraceVerbosity: otypes.MinimalVerbosity, }, }, }, @@ -1643,6 +1649,40 @@ func TestLoadConfigurationFromIngresses(t *testing.T) { }, }, }, + { + desc: "Ingress with strict prefix matching", + expected: &dynamic.Configuration{ + HTTP: &dynamic.HTTPConfiguration{ + Middlewares: map[string]*dynamic.Middleware{}, + Routers: map[string]*dynamic.Router{ + "testing-bar": { + Rule: "(Path(`/bar`) || PathPrefix(`/bar/`))", + Service: "testing-service1-80", + }, + }, + Services: map[string]*dynamic.Service{ + "testing-service1-80": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + Strategy: dynamic.BalancerStrategyWRR, + PassHostHeader: pointer(true), + ResponseForwarding: &dynamic.ResponseForwarding{ + FlushInterval: ptypes.Duration(100 * time.Millisecond), + }, + Servers: []dynamic.Server{ + { + URL: "http://10.10.0.1:8080", + }, + { + URL: "http://10.21.0.1:8080", + }, + }, + }, + }, + }, + }, + }, + strictPrefixMatching: true, + }, } for _, test := range testCases { @@ -1656,8 +1696,9 @@ func TestLoadConfigurationFromIngresses(t *testing.T) { DisableIngressClassLookup: test.disableIngressClassLookup, DisableClusterScopeResources: test.disableClusterScopeResources, DefaultRuleSyntax: test.defaultRuleSyntax, + StrictPrefixMatching: test.strictPrefixMatching, } - conf := p.loadConfigurationFromIngresses(context.Background(), clientMock) + conf := p.loadConfigurationFromIngresses(t.Context(), clientMock) assert.Equal(t, test.expected, conf) }) @@ -1783,7 +1824,7 @@ func TestLoadConfigurationFromIngressesWithExternalNameServices(t *testing.T) { p := Provider{IngressClass: test.ingressClass} p.AllowExternalNameServices = test.allowExternalNameServices - conf := p.loadConfigurationFromIngresses(context.Background(), clientMock) + conf := p.loadConfigurationFromIngresses(t.Context(), clientMock) assert.Equal(t, test.expected, conf) }) @@ -1833,7 +1874,7 @@ func TestLoadConfigurationFromIngressesWithNativeLB(t *testing.T) { clientMock := newClientMock(generateTestFilename(test.desc)) p := Provider{IngressClass: test.ingressClass} - conf := p.loadConfigurationFromIngresses(context.Background(), clientMock) + conf := p.loadConfigurationFromIngresses(t.Context(), clientMock) assert.Equal(t, test.expected, conf) }) @@ -1894,7 +1935,7 @@ func TestLoadConfigurationFromIngressesWithNodePortLB(t *testing.T) { clientMock := newClientMock(generateTestFilename(test.desc)) p := Provider{DisableClusterScopeResources: test.clusterScopeDisabled} - conf := p.loadConfigurationFromIngresses(context.Background(), clientMock) + conf := p.loadConfigurationFromIngresses(t.Context(), clientMock) assert.Equal(t, test.expected, conf) }) @@ -2066,7 +2107,7 @@ func TestGetCertificates(t *testing.T) { t.Parallel() tlsConfigs := map[string]*tls.CertAndStores{} - err := getCertificates(context.Background(), test.ingress, test.client, tlsConfigs) + err := getCertificates(t.Context(), test.ingress, test.client, tlsConfigs) if test.errResult != "" { assert.EqualError(t, err, test.errResult) @@ -2140,6 +2181,37 @@ func TestLoadConfigurationFromIngressesWithNativeLBByDefault(t *testing.T) { }, }, }, + { + desc: "Ingress with native lb by default but service has disabled nativelb", + expected: &dynamic.Configuration{ + HTTP: &dynamic.HTTPConfiguration{ + Middlewares: map[string]*dynamic.Middleware{}, + Routers: map[string]*dynamic.Router{ + "default-global-native-lb-traefik-tchouk-bar": { + Rule: "Host(`traefik.tchouk`) && PathPrefix(`/bar`)", + Service: "default-native-disabled-svc-web", + }, + }, + Services: map[string]*dynamic.Service{ + "default-native-disabled-svc-web": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + Strategy: dynamic.BalancerStrategyWRR, + ResponseForwarding: &dynamic.ResponseForwarding{FlushInterval: dynamic.DefaultFlushInterval}, + PassHostHeader: pointer(true), + Servers: []dynamic.Server{ + { + URL: "http://10.10.0.20:8080", + }, + { + URL: "http://10.10.0.21:8080", + }, + }, + }, + }, + }, + }, + }, + }, } for _, test := range testCases { @@ -2152,7 +2224,7 @@ func TestLoadConfigurationFromIngressesWithNativeLBByDefault(t *testing.T) { IngressClass: test.ingressClass, NativeLBByDefault: true, } - conf := p.loadConfigurationFromIngresses(context.Background(), clientMock) + conf := p.loadConfigurationFromIngresses(t.Context(), clientMock) assert.Equal(t, test.expected, conf) }) @@ -2252,9 +2324,9 @@ func TestIngressEndpointPublishedService(t *testing.T) { PublishedService: "default/published-service", }, } - p.loadConfigurationFromIngresses(context.Background(), client) + p.loadConfigurationFromIngresses(t.Context(), client) - ingress, err := kubeClient.NetworkingV1().Ingresses(metav1.NamespaceDefault).Get(context.Background(), "foo", metav1.GetOptions{}) + ingress, err := kubeClient.NetworkingV1().Ingresses(metav1.NamespaceDefault).Get(t.Context(), "foo", metav1.GetOptions{}) require.NoError(t, err) assert.Equal(t, test.expected, ingress.Status.LoadBalancer.Ingress) @@ -2278,3 +2350,108 @@ func readResources(t *testing.T, paths []string) []runtime.Object { return k8sObjects } + +func TestStrictPrefixMatchingRule(t *testing.T) { + tests := []struct { + path string + requestPath string + match bool + }{ // The tests are taken from https://kubernetes.io/docs/concepts/services-networking/ingress/#examples + { + path: "/foo", + requestPath: "/foo", + match: true, + }, + { + path: "/foo", + requestPath: "/foo/", + match: true, + }, + { + path: "/foo/", + requestPath: "/foo", + match: true, + }, + { + path: "/foo/", + requestPath: "/foo/", + match: true, + }, + { + path: "/aaa/bb", + requestPath: "/aaa/bbb", + match: false, + }, + { + path: "/aaa/bbb", + requestPath: "/aaa/bbb", + match: true, + }, + { + path: "/aaa/bbb/", + requestPath: "/aaa/bbb", + match: true, + }, + { + path: "/aaa/bbb", + requestPath: "/aaa/bbb/", + match: true, + }, + { + path: "/aaa/bbb", + requestPath: "/aaa/bbb/ccc", + match: true, + }, + { + path: "/aaa/bbb", + requestPath: "/aaa/bbbxyz", + match: false, + }, + { + path: "/", + requestPath: "/aaa/ccc", + match: true, + }, + { + path: "/aaa", + requestPath: "/aaa/ccc", + match: true, + }, + { + path: "/...", + requestPath: "/aaa", + match: false, + }, + { + path: "/...", + requestPath: "/.../", + match: true, + }, + } + + for _, tt := range tests { + t.Run(fmt.Sprintf("Prefix match case %s", tt.path), func(t *testing.T) { + t.Parallel() + + handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}) + parser, err := traefikhttp.NewSyntaxParser() + require.NoError(t, err) + + muxer := traefikhttp.NewMuxer(parser) + + rule := buildStrictPrefixMatchingRule(tt.path) + err = muxer.AddRoute(rule, "", 0, handler) + require.NoError(t, err) + + w := httptest.NewRecorder() + req := httptest.NewRequest(http.MethodGet, tt.requestPath, http.NoBody) + muxer.ServeHTTP(w, req) + + if tt.match { + assert.Equal(t, http.StatusOK, w.Code) + } else { + assert.Equal(t, http.StatusNotFound, w.Code) + } + }) + } +} diff --git a/pkg/provider/kubernetes/k8s/event_handler.go b/pkg/provider/kubernetes/k8s/event_handler.go index 1de67ec5e..9b3babaa9 100644 --- a/pkg/provider/kubernetes/k8s/event_handler.go +++ b/pkg/provider/kubernetes/k8s/event_handler.go @@ -11,7 +11,7 @@ type ResourceEventHandler struct { } // OnAdd is called on Add Events. -func (reh *ResourceEventHandler) OnAdd(obj interface{}, isInInitialList bool) { +func (reh *ResourceEventHandler) OnAdd(obj interface{}, _ bool) { eventHandlerFunc(reh.Ev, obj) } diff --git a/pkg/provider/kv/kv.go b/pkg/provider/kv/kv.go index 613320617..a03d4e711 100644 --- a/pkg/provider/kv/kv.go +++ b/pkg/provider/kv/kv.go @@ -14,7 +14,7 @@ import ( "github.com/traefik/traefik/v3/pkg/config/dynamic" "github.com/traefik/traefik/v3/pkg/config/kv" "github.com/traefik/traefik/v3/pkg/job" - "github.com/traefik/traefik/v3/pkg/logs" + "github.com/traefik/traefik/v3/pkg/observability/logs" "github.com/traefik/traefik/v3/pkg/safe" ) @@ -55,7 +55,7 @@ func (p *Provider) Provide(configurationChan chan<- dynamic.Message, pool *safe. ctx := logger.WithContext(context.Background()) operation := func() error { - if _, err := p.kvClient.Exists(ctx, path.Join(p.RootKey, "qmslkjdfmqlskdjfmqlksjazçueznbvbwzlkajzebvkwjdcqmlsfj"), nil); err != nil { + if _, err := p.kvClient.Exists(ctx, path.Join(p.RootKey, "qmslkjdfmqlskdjfmqlksjazcueznbvbwzlkajzebvkwjdcqmlsfj"), nil); err != nil { return fmt.Errorf("KV store connection error: %w", err) } return nil diff --git a/pkg/provider/kv/kv_test.go b/pkg/provider/kv/kv_test.go index b439f38ae..c8dc84bbc 100644 --- a/pkg/provider/kv/kv_test.go +++ b/pkg/provider/kv/kv_test.go @@ -1,7 +1,6 @@ package kv import ( - "context" "errors" "testing" "time" @@ -45,6 +44,7 @@ func Test_buildConfiguration(t *testing.T) { "traefik/http/services/Service01/loadBalancer/healthCheck/path": "foobar", "traefik/http/services/Service01/loadBalancer/healthCheck/port": "42", "traefik/http/services/Service01/loadBalancer/healthCheck/interval": "1s", + "traefik/http/services/Service01/loadBalancer/healthCheck/unhealthyinterval": "1s", "traefik/http/services/Service01/loadBalancer/healthCheck/timeout": "1s", "traefik/http/services/Service01/loadBalancer/healthCheck/hostname": "foobar", "traefik/http/services/Service01/loadBalancer/healthCheck/headers/name0": "foobar", @@ -297,7 +297,7 @@ func Test_buildConfiguration(t *testing.T) { "traefik/tls/certificates/1/stores/1": "foobar", })) - cfg, err := provider.buildConfiguration(context.Background()) + cfg, err := provider.buildConfiguration(t.Context()) require.NoError(t, err) expected := &dynamic.Configuration{ @@ -665,14 +665,15 @@ func Test_buildConfiguration(t *testing.T) { }, }, HealthCheck: &dynamic.ServerHealthCheck{ - Scheme: "foobar", - Mode: "foobar", - Path: "foobar", - Port: 42, - Interval: ptypes.Duration(time.Second), - Timeout: ptypes.Duration(time.Second), - Hostname: "foobar", - FollowRedirects: pointer(true), + Scheme: "foobar", + Mode: "foobar", + Path: "foobar", + Port: 42, + Interval: ptypes.Duration(time.Second), + UnhealthyInterval: pointer(ptypes.Duration(time.Second)), + Timeout: ptypes.Duration(time.Second), + Hostname: "foobar", + FollowRedirects: pointer(true), Headers: map[string]string{ "name0": "foobar", "name1": "foobar", @@ -956,7 +957,7 @@ func Test_buildConfiguration_KV_error(t *testing.T) { }, } - cfg, err := provider.buildConfiguration(context.Background()) + cfg, err := provider.buildConfiguration(t.Context()) require.Error(t, err) assert.Nil(t, cfg) } @@ -975,7 +976,7 @@ func TestKvWatchTree(t *testing.T) { configChan := make(chan dynamic.Message) go func() { - err := provider.watchKv(context.Background(), configChan) + err := provider.watchKv(t.Context(), configChan) require.NoError(t, err) }() diff --git a/pkg/provider/kv/redis/redis.go b/pkg/provider/kv/redis/redis.go index 874a6b1a1..c6e357f03 100644 --- a/pkg/provider/kv/redis/redis.go +++ b/pkg/provider/kv/redis/redis.go @@ -21,14 +21,14 @@ type Provider struct { Username string `description:"Username for authentication." json:"username,omitempty" toml:"username,omitempty" yaml:"username,omitempty" loggable:"false"` Password string `description:"Password for authentication." json:"password,omitempty" toml:"password,omitempty" yaml:"password,omitempty" loggable:"false"` DB int `description:"Database to be selected after connecting to the server." json:"db,omitempty" toml:"db,omitempty" yaml:"db,omitempty"` - Sentinel *Sentinel `description:"Enable Sentinel support." json:"sentinel,omitempty" toml:"sentinel,omitempty" yaml:"sentinel,omitempty"` + Sentinel *Sentinel `description:"Enable Sentinel support." json:"sentinel,omitempty" toml:"sentinel,omitempty" yaml:"sentinel,omitempty" export:"true"` } // Sentinel holds the Redis Sentinel configuration. type Sentinel struct { MasterName string `description:"Name of the master." json:"masterName,omitempty" toml:"masterName,omitempty" yaml:"masterName,omitempty" export:"true"` - Username string `description:"Username for Sentinel authentication." json:"username,omitempty" toml:"username,omitempty" yaml:"username,omitempty" export:"true"` - Password string `description:"Password for Sentinel authentication." json:"password,omitempty" toml:"password,omitempty" yaml:"password,omitempty" export:"true"` + Username string `description:"Username for Sentinel authentication." json:"username,omitempty" toml:"username,omitempty" yaml:"username,omitempty" loggable:"false"` + Password string `description:"Password for Sentinel authentication." json:"password,omitempty" toml:"password,omitempty" yaml:"password,omitempty" loggable:"false"` LatencyStrategy bool `description:"Defines whether to route commands to the closest master or replica nodes (mutually exclusive with RandomStrategy and ReplicaStrategy)." json:"latencyStrategy,omitempty" toml:"latencyStrategy,omitempty" yaml:"latencyStrategy,omitempty" export:"true"` RandomStrategy bool `description:"Defines whether to route commands randomly to master or replica nodes (mutually exclusive with LatencyStrategy and ReplicaStrategy)." json:"randomStrategy,omitempty" toml:"randomStrategy,omitempty" yaml:"randomStrategy,omitempty" export:"true"` diff --git a/pkg/provider/nomad/config.go b/pkg/provider/nomad/config.go index 4a2192ef0..a6fb4343b 100644 --- a/pkg/provider/nomad/config.go +++ b/pkg/provider/nomad/config.go @@ -13,7 +13,7 @@ import ( "github.com/rs/zerolog/log" "github.com/traefik/traefik/v3/pkg/config/dynamic" "github.com/traefik/traefik/v3/pkg/config/label" - "github.com/traefik/traefik/v3/pkg/logs" + "github.com/traefik/traefik/v3/pkg/observability/logs" "github.com/traefik/traefik/v3/pkg/provider" "github.com/traefik/traefik/v3/pkg/provider/constraints" ) diff --git a/pkg/provider/nomad/config_test.go b/pkg/provider/nomad/config_test.go index e74ccddb5..efeabe66e 100644 --- a/pkg/provider/nomad/config_test.go +++ b/pkg/provider/nomad/config_test.go @@ -1,7 +1,6 @@ package nomad import ( - "context" "testing" "time" @@ -251,8 +250,7 @@ func Test_defaultRule(t *testing.T) { err := p.Init() require.NoError(t, err) - ctx := context.TODO() - config := p.buildConfig(ctx, test.items) + config := p.buildConfig(t.Context(), test.items) require.Equal(t, test.expected, config) }) } @@ -3077,8 +3075,7 @@ func Test_buildConfig(t *testing.T) { err := p.Init() require.NoError(t, err) - ctx := context.TODO() - c := p.buildConfig(ctx, test.items) + c := p.buildConfig(t.Context(), test.items) require.Equal(t, test.expected, c) }) } @@ -3246,8 +3243,7 @@ func Test_buildConfigAllowEmptyServicesTrue(t *testing.T) { err := p.Init() require.NoError(t, err) - ctx := context.TODO() - c := p.buildConfig(ctx, test.items) + c := p.buildConfig(t.Context(), test.items) require.Equal(t, test.expected, c) }) } @@ -3379,8 +3375,7 @@ func Test_buildConfigAllowEmptyServicesFalseDefault(t *testing.T) { err := p.Init() require.NoError(t, err) - ctx := context.TODO() - c := p.buildConfig(ctx, test.items) + c := p.buildConfig(t.Context(), test.items) require.Equal(t, test.expected, c) }) } @@ -3428,8 +3423,8 @@ func Test_keepItem(t *testing.T) { p := new(Provider) p.SetDefaults() p.Constraints = test.constraints - ctx := context.TODO() - result := p.keepItem(ctx, test.i) + + result := p.keepItem(t.Context(), test.i) require.Equal(t, test.exp, result) }) } diff --git a/pkg/provider/nomad/nomad.go b/pkg/provider/nomad/nomad.go index 6f0b4774e..77876468e 100644 --- a/pkg/provider/nomad/nomad.go +++ b/pkg/provider/nomad/nomad.go @@ -15,7 +15,7 @@ import ( ptypes "github.com/traefik/paerser/types" "github.com/traefik/traefik/v3/pkg/config/dynamic" "github.com/traefik/traefik/v3/pkg/job" - "github.com/traefik/traefik/v3/pkg/logs" + "github.com/traefik/traefik/v3/pkg/observability/logs" "github.com/traefik/traefik/v3/pkg/provider" "github.com/traefik/traefik/v3/pkg/provider/constraints" "github.com/traefik/traefik/v3/pkg/safe" diff --git a/pkg/provider/nomad/nomad_test.go b/pkg/provider/nomad/nomad_test.go index 2794fab0c..d5442d50f 100644 --- a/pkg/provider/nomad/nomad_test.go +++ b/pkg/provider/nomad/nomad_test.go @@ -1,7 +1,6 @@ package nomad import ( - "context" "fmt" "net/http" "net/http/httptest" @@ -170,7 +169,7 @@ func Test_getNomadServiceDataWithEmptyServices_GroupService_Scaling1(t *testing. require.NoError(t, err) // make the query for services - items, err := p.getNomadServiceDataWithEmptyServices(context.TODO()) + items, err := p.getNomadServiceDataWithEmptyServices(t.Context()) require.NoError(t, err) require.Len(t, items, 1) } @@ -200,7 +199,7 @@ func Test_getNomadServiceDataWithEmptyServices_GroupService_Scaling0(t *testing. require.NoError(t, err) // make the query for services - items, err := p.getNomadServiceDataWithEmptyServices(context.TODO()) + items, err := p.getNomadServiceDataWithEmptyServices(t.Context()) require.NoError(t, err) require.Len(t, items, 1) } @@ -230,7 +229,7 @@ func Test_getNomadServiceDataWithEmptyServices_GroupService_ScalingDisabled(t *t require.NoError(t, err) // make the query for services - items, err := p.getNomadServiceDataWithEmptyServices(context.TODO()) + items, err := p.getNomadServiceDataWithEmptyServices(t.Context()) require.NoError(t, err) require.Len(t, items, 1) } @@ -260,7 +259,7 @@ func Test_getNomadServiceDataWithEmptyServices_GroupService_ScalingDisabled_Stop require.NoError(t, err) // make the query for services - items, err := p.getNomadServiceDataWithEmptyServices(context.TODO()) + items, err := p.getNomadServiceDataWithEmptyServices(t.Context()) require.NoError(t, err) // Should not be listed as job is stopped @@ -294,7 +293,7 @@ func Test_getNomadServiceDataWithEmptyServices_GroupTaskService_Scaling1(t *test require.NoError(t, err) // make the query for services - items, err := p.getNomadServiceDataWithEmptyServices(context.TODO()) + items, err := p.getNomadServiceDataWithEmptyServices(t.Context()) require.NoError(t, err) require.Len(t, items, 2) } @@ -326,7 +325,7 @@ func Test_getNomadServiceDataWithEmptyServices_GroupTaskService_Scaling0(t *test require.NoError(t, err) // make the query for services - items, err := p.getNomadServiceDataWithEmptyServices(context.TODO()) + items, err := p.getNomadServiceDataWithEmptyServices(t.Context()) require.NoError(t, err) require.Len(t, items, 2) } @@ -356,7 +355,7 @@ func Test_getNomadServiceDataWithEmptyServices_TCP(t *testing.T) { require.NoError(t, err) // make the query for services - items, err := p.getNomadServiceDataWithEmptyServices(context.TODO()) + items, err := p.getNomadServiceDataWithEmptyServices(t.Context()) require.NoError(t, err) require.Len(t, items, 1) } @@ -386,7 +385,7 @@ func Test_getNomadServiceDataWithEmptyServices_UDP(t *testing.T) { require.NoError(t, err) // make the query for services - items, err := p.getNomadServiceDataWithEmptyServices(context.TODO()) + items, err := p.getNomadServiceDataWithEmptyServices(t.Context()) require.NoError(t, err) require.Len(t, items, 1) } @@ -416,7 +415,7 @@ func Test_getNomadServiceDataWithEmptyServices_ScalingEnabled_Stopped(t *testing require.NoError(t, err) // make the query for services - items, err := p.getNomadServiceDataWithEmptyServices(context.TODO()) + items, err := p.getNomadServiceDataWithEmptyServices(t.Context()) require.NoError(t, err) // Should not be listed as job is stopped @@ -465,7 +464,7 @@ func Test_getNomadServiceData(t *testing.T) { require.NoError(t, err) // make the query for services - items, err := p.getNomadServiceData(context.TODO()) + items, err := p.getNomadServiceData(t.Context()) require.NoError(t, err) require.Len(t, items, 2) } diff --git a/pkg/provider/tailscale/provider.go b/pkg/provider/tailscale/provider.go index 8e795e5be..5e84912e4 100644 --- a/pkg/provider/tailscale/provider.go +++ b/pkg/provider/tailscale/provider.go @@ -12,9 +12,9 @@ import ( "github.com/rs/zerolog/log" "github.com/tailscale/tscert" "github.com/traefik/traefik/v3/pkg/config/dynamic" - "github.com/traefik/traefik/v3/pkg/logs" "github.com/traefik/traefik/v3/pkg/muxer/http" "github.com/traefik/traefik/v3/pkg/muxer/tcp" + "github.com/traefik/traefik/v3/pkg/observability/logs" "github.com/traefik/traefik/v3/pkg/safe" traefiktls "github.com/traefik/traefik/v3/pkg/tls" "github.com/traefik/traefik/v3/pkg/types" diff --git a/pkg/provider/tailscale/provider_test.go b/pkg/provider/tailscale/provider_test.go index 2e6e1b962..279299b3c 100644 --- a/pkg/provider/tailscale/provider_test.go +++ b/pkg/provider/tailscale/provider_test.go @@ -1,7 +1,6 @@ package tailscale import ( - "context" "testing" "github.com/stretchr/testify/assert" @@ -125,7 +124,7 @@ func TestProvider_findDomains(t *testing.T) { p := Provider{ResolverName: "foo"} - got := p.findDomains(context.TODO(), test.config) + got := p.findDomains(t.Context(), test.config) assert.Equal(t, test.want, got) }) } @@ -230,7 +229,7 @@ func Test_sanitizeDomains(t *testing.T) { t.Run(test.desc, func(t *testing.T) { t.Parallel() - got := sanitizeDomains(context.TODO(), test.domains) + got := sanitizeDomains(t.Context(), test.domains) assert.Equal(t, test.want, got) }) } diff --git a/pkg/provider/traefik/internal.go b/pkg/provider/traefik/internal.go index 22efe451d..73d17d63f 100644 --- a/pkg/provider/traefik/internal.go +++ b/pkg/provider/traefik/internal.go @@ -11,7 +11,7 @@ import ( "github.com/rs/zerolog/log" "github.com/traefik/traefik/v3/pkg/config/dynamic" "github.com/traefik/traefik/v3/pkg/config/static" - "github.com/traefik/traefik/v3/pkg/logs" + "github.com/traefik/traefik/v3/pkg/observability/logs" "github.com/traefik/traefik/v3/pkg/provider" "github.com/traefik/traefik/v3/pkg/safe" "github.com/traefik/traefik/v3/pkg/tls" @@ -242,9 +242,10 @@ func (i *Provider) entryPointModels(cfg *dynamic.Configuration) { if ep.Observability != nil { httpModel.Observability = dynamic.RouterObservabilityConfig{ - AccessLogs: ep.Observability.AccessLogs, - Tracing: ep.Observability.Tracing, - Metrics: ep.Observability.Metrics, + AccessLogs: ep.Observability.AccessLogs, + Metrics: ep.Observability.Metrics, + Tracing: ep.Observability.Tracing, + TraceVerbosity: ep.Observability.TraceVerbosity, } } diff --git a/pkg/provider/traefik/internal_test.go b/pkg/provider/traefik/internal_test.go index 3ff89a727..e0697dd8d 100644 --- a/pkg/provider/traefik/internal_test.go +++ b/pkg/provider/traefik/internal_test.go @@ -1,7 +1,6 @@ package traefik import ( - "context" "encoding/json" "flag" "os" @@ -11,6 +10,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/traefik/traefik/v3/pkg/config/static" + otypes "github.com/traefik/traefik/v3/pkg/observability/types" "github.com/traefik/traefik/v3/pkg/ping" "github.com/traefik/traefik/v3/pkg/provider/rest" "github.com/traefik/traefik/v3/pkg/types" @@ -42,8 +42,8 @@ func Test_createConfiguration(t *testing.T) { Insecure: true, }, }, - Metrics: &types.Metrics{ - Prometheus: &types.Prometheus{ + Metrics: &otypes.Metrics{ + Prometheus: &otypes.Prometheus{ EntryPoint: "test", ManualRouting: false, }, @@ -66,8 +66,8 @@ func Test_createConfiguration(t *testing.T) { Insecure: false, }, }, - Metrics: &types.Metrics{ - Prometheus: &types.Prometheus{ + Metrics: &otypes.Metrics{ + Prometheus: &otypes.Prometheus{ EntryPoint: "test", ManualRouting: true, }, @@ -151,8 +151,8 @@ func Test_createConfiguration(t *testing.T) { { desc: "prometheus_simple.json", staticCfg: static.Configuration{ - Metrics: &types.Metrics{ - Prometheus: &types.Prometheus{ + Metrics: &otypes.Metrics{ + Prometheus: &otypes.Prometheus{ EntryPoint: "test", ManualRouting: false, }, @@ -162,8 +162,8 @@ func Test_createConfiguration(t *testing.T) { { desc: "prometheus_custom.json", staticCfg: static.Configuration{ - Metrics: &types.Metrics{ - Prometheus: &types.Prometheus{ + Metrics: &otypes.Metrics{ + Prometheus: &otypes.Prometheus{ EntryPoint: "test", ManualRouting: true, }, @@ -269,7 +269,7 @@ func Test_createConfiguration(t *testing.T) { provider := Provider{staticCfg: test.staticCfg} - cfg := provider.createConfiguration(context.Background()) + cfg := provider.createConfiguration(t.Context()) filename := filepath.Join("fixtures", test.desc) diff --git a/pkg/proxy/fast/proxy_test.go b/pkg/proxy/fast/proxy_test.go index e26c67f63..70b1256aa 100644 --- a/pkg/proxy/fast/proxy_test.go +++ b/pkg/proxy/fast/proxy_test.go @@ -20,7 +20,6 @@ import ( "github.com/traefik/traefik/v3/pkg/config/dynamic" "github.com/traefik/traefik/v3/pkg/config/static" "github.com/traefik/traefik/v3/pkg/testhelpers" - "github.com/traefik/traefik/v3/pkg/tls/generate" ) const ( @@ -125,9 +124,17 @@ func TestProxyFromEnvironment(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { - backendURL, backendCert := newBackendServer(t, test.tls, http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { - _, _ = rw.Write([]byte("backend")) - })) + var backendServer *httptest.Server + if test.tls { + backendServer = httptest.NewTLSServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + _, _ = rw.Write([]byte("backendTLS")) + })) + } else { + backendServer = httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { + _, _ = rw.Write([]byte("backend")) + })) + } + t.Cleanup(backendServer.Close) var proxyCalled bool proxyHandler := http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { @@ -155,8 +162,21 @@ func TestProxyFromEnvironment(t *testing.T) { connHj, _, err := hj.Hijack() require.NoError(t, err) - go func() { _, _ = io.Copy(connHj, conn) }() - _, _ = io.Copy(conn, connHj) + defer func() { + _ = connHj.Close() + _ = conn.Close() + }() + + errCh := make(chan error, 1) + go func() { + _, err = io.Copy(connHj, conn) + errCh <- err + }() + go func() { + _, err = io.Copy(conn, connHj) + errCh <- err + }() + <-errCh // Wait for one of the copy operations to finish }) var proxyURL string @@ -198,7 +218,7 @@ func TestProxyFromEnvironment(t *testing.T) { proxyURL = proxyServer.URL case proxyHTTPS: - proxyServer := httptest.NewServer(proxyHandler) + proxyServer := httptest.NewTLSServer(proxyHandler) t.Cleanup(proxyServer.Close) proxyURL = proxyServer.URL @@ -209,11 +229,8 @@ func TestProxyFromEnvironment(t *testing.T) { if proxyCert != nil { certPool.AddCert(proxyCert) } - if backendCert != nil { - cert, err := x509.ParseCertificate(backendCert.Certificate[0]) - require.NoError(t, err) - - certPool.AddCert(cert) + if backendServer.Certificate() != nil { + certPool.AddCert(backendServer.Certificate()) } builder := NewProxyBuilder(&transportManagerMock{tlsConfig: &tls.Config{RootCAs: certPool}}, static.FastProxyConfig{}) @@ -230,7 +247,7 @@ func TestProxyFromEnvironment(t *testing.T) { return u, nil } - reverseProxy, err := builder.Build("foo", testhelpers.MustParseURL(backendURL), false, false) + reverseProxy, err := builder.Build("foo", testhelpers.MustParseURL(backendServer.URL), false, false) require.NoError(t, err) reverseProxyServer := httptest.NewServer(reverseProxy) @@ -246,7 +263,11 @@ func TestProxyFromEnvironment(t *testing.T) { body, err := io.ReadAll(resp.Body) require.NoError(t, err) - assert.Equal(t, "backend", string(body)) + if test.tls { + assert.Equal(t, "backendTLS", string(body)) + } else { + assert.Equal(t, "backend", string(body)) + } assert.True(t, proxyCalled) }) } @@ -385,52 +406,6 @@ func TestTransferEncodingChunked(t *testing.T) { assert.Equal(t, "chunk 0\nchunk 1\nchunk 2\n", string(body)) } -func newCertificate(t *testing.T, domain string) *tls.Certificate { - t.Helper() - - certPEM, keyPEM, err := generate.KeyPair(domain, time.Time{}) - require.NoError(t, err) - - certificate, err := tls.X509KeyPair(certPEM, keyPEM) - require.NoError(t, err) - - return &certificate -} - -func newBackendServer(t *testing.T, isTLS bool, handler http.Handler) (string, *tls.Certificate) { - t.Helper() - - var ln net.Listener - var err error - var cert *tls.Certificate - - scheme := "http" - domain := "backend.localhost" - if isTLS { - scheme = "https" - - cert = newCertificate(t, domain) - - ln, err = tls.Listen("tcp", ":0", &tls.Config{Certificates: []tls.Certificate{*cert}}) - require.NoError(t, err) - } else { - ln, err = net.Listen("tcp", ":0") - require.NoError(t, err) - } - - srv := &http.Server{Handler: handler} - go func() { _ = srv.Serve(ln) }() - - t.Cleanup(func() { _ = srv.Close() }) - - _, port, err := net.SplitHostPort(ln.Addr().String()) - require.NoError(t, err) - - backendURL := fmt.Sprintf("%s://%s:%s", scheme, domain, port) - - return backendURL, cert -} - type transportManagerMock struct { tlsConfig *tls.Config } diff --git a/pkg/proxy/httputil/builder.go b/pkg/proxy/httputil/builder.go index 64360517a..1a4b04a0f 100644 --- a/pkg/proxy/httputil/builder.go +++ b/pkg/proxy/httputil/builder.go @@ -8,7 +8,7 @@ import ( "time" "github.com/traefik/traefik/v3/pkg/config/dynamic" - "github.com/traefik/traefik/v3/pkg/metrics" + "github.com/traefik/traefik/v3/pkg/observability/metrics" ) // TransportManager manages transport used for backend communications. @@ -38,17 +38,15 @@ func NewProxyBuilder(transportManager TransportManager, semConvMetricsRegistry * func (r *ProxyBuilder) Update(_ map[string]*dynamic.ServersTransport) {} // Build builds a new httputil.ReverseProxy with the given configuration. -func (r *ProxyBuilder) Build(cfgName string, targetURL *url.URL, shouldObserve, passHostHeader, preservePath bool, flushInterval time.Duration) (http.Handler, error) { +func (r *ProxyBuilder) Build(cfgName string, targetURL *url.URL, passHostHeader, preservePath bool, flushInterval time.Duration) (http.Handler, error) { roundTripper, err := r.transportManager.GetRoundTripper(cfgName) if err != nil { return nil, fmt.Errorf("getting RoundTripper: %w", err) } - if shouldObserve { - // Wrapping the roundTripper with the Tracing roundTripper, - // to handle the reverseProxy client span creation. - roundTripper = newObservabilityRoundTripper(r.semConvMetricsRegistry, roundTripper) - } + // Wrapping the roundTripper with the Tracing roundTripper, + // to create, if necessary, the reverseProxy client span and the semConv client metric. + roundTripper = newObservabilityRoundTripper(r.semConvMetricsRegistry, roundTripper) return buildSingleHostProxy(targetURL, passHostHeader, preservePath, flushInterval, roundTripper, r.bufferPool), nil } diff --git a/pkg/proxy/httputil/builder_test.go b/pkg/proxy/httputil/builder_test.go index f7ff93902..e45a871b6 100644 --- a/pkg/proxy/httputil/builder_test.go +++ b/pkg/proxy/httputil/builder_test.go @@ -23,7 +23,7 @@ func TestEscapedPath(t *testing.T) { roundTrippers: map[string]http.RoundTripper{"default": &http.Transport{}}, } - p, err := NewProxyBuilder(transportManager, nil).Build("default", testhelpers.MustParseURL(srv.URL), false, true, false, 0) + p, err := NewProxyBuilder(transportManager, nil).Build("default", testhelpers.MustParseURL(srv.URL), true, false, 0) require.NoError(t, err) proxy := httptest.NewServer(http.HandlerFunc(p.ServeHTTP)) diff --git a/pkg/proxy/httputil/observability.go b/pkg/proxy/httputil/observability.go index 8fa3382e3..23ff5acdf 100644 --- a/pkg/proxy/httputil/observability.go +++ b/pkg/proxy/httputil/observability.go @@ -9,12 +9,12 @@ import ( "strings" "time" - "github.com/traefik/traefik/v3/pkg/metrics" "github.com/traefik/traefik/v3/pkg/middlewares/observability" - "github.com/traefik/traefik/v3/pkg/tracing" + "github.com/traefik/traefik/v3/pkg/observability/metrics" + "github.com/traefik/traefik/v3/pkg/observability/tracing" "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/metric" - semconv "go.opentelemetry.io/otel/semconv/v1.26.0" + semconv "go.opentelemetry.io/otel/semconv/v1.37.0" + "go.opentelemetry.io/otel/semconv/v1.37.0/httpconv" "go.opentelemetry.io/otel/trace" ) @@ -35,7 +35,7 @@ func (t *wrapper) RoundTrip(req *http.Request) (*http.Response, error) { var span trace.Span var tracingCtx context.Context var tracer *tracing.Tracer - if tracer = tracing.TracerFromContext(req.Context()); tracer != nil { + if tracer = tracing.TracerFromContext(req.Context()); tracer != nil && observability.TracingEnabled(req.Context()) { tracingCtx, span = tracer.Start(req.Context(), "ReverseProxy", trace.WithSpanKind(trace.SpanKindClient)) defer span.End() @@ -68,38 +68,43 @@ func (t *wrapper) RoundTrip(req *http.Request) (*http.Response, error) { span.End(trace.WithTimestamp(end)) } - if req.Context().Value(observability.DisableMetricsKey) == nil && t.semConvMetricRegistry != nil && t.semConvMetricRegistry.HTTPClientRequestDuration() != nil { - var attrs []attribute.KeyValue - - if statusCode < 100 || statusCode >= 600 { - attrs = append(attrs, attribute.Key("error.type").String(fmt.Sprintf("Invalid HTTP status code %d", statusCode))) - } else if statusCode >= 400 { - attrs = append(attrs, attribute.Key("error.type").String(strconv.Itoa(statusCode))) - } - - attrs = append(attrs, semconv.HTTPRequestMethodKey.String(req.Method)) - attrs = append(attrs, semconv.HTTPResponseStatusCode(statusCode)) - attrs = append(attrs, semconv.NetworkProtocolName(strings.ToLower(req.Proto))) - attrs = append(attrs, semconv.NetworkProtocolVersion(observability.Proto(req.Proto))) - attrs = append(attrs, semconv.ServerAddress(req.URL.Host)) - - _, port, err := net.SplitHostPort(req.URL.Host) - if err != nil { - switch req.URL.Scheme { - case "http": - attrs = append(attrs, semconv.ServerPort(80)) - case "https": - attrs = append(attrs, semconv.ServerPort(443)) - } - } else { - intPort, _ := strconv.Atoi(port) - attrs = append(attrs, semconv.ServerPort(intPort)) - } - - attrs = append(attrs, semconv.URLScheme(req.Header.Get("X-Forwarded-Proto"))) - - t.semConvMetricRegistry.HTTPClientRequestDuration().Record(req.Context(), end.Sub(start).Seconds(), metric.WithAttributes(attrs...)) + if !observability.SemConvMetricsEnabled(req.Context()) || t.semConvMetricRegistry == nil { + return response, err } + var attrs []attribute.KeyValue + + if statusCode < 100 || statusCode >= 600 { + attrs = append(attrs, attribute.Key("error.type").String(fmt.Sprintf("Invalid HTTP status code %d", statusCode))) + } else if statusCode >= 400 { + attrs = append(attrs, attribute.Key("error.type").String(strconv.Itoa(statusCode))) + } + + attrs = append(attrs, semconv.HTTPRequestMethodKey.String(req.Method)) + attrs = append(attrs, semconv.HTTPResponseStatusCode(statusCode)) + attrs = append(attrs, semconv.NetworkProtocolName(strings.ToLower(req.Proto))) + attrs = append(attrs, semconv.NetworkProtocolVersion(observability.Proto(req.Proto))) + + var serverPort int + _, port, splitErr := net.SplitHostPort(req.URL.Host) + if splitErr != nil { + switch req.URL.Scheme { + case "http": + serverPort = 80 + attrs = append(attrs, semconv.ServerPort(serverPort)) + case "https": + serverPort = 443 + attrs = append(attrs, semconv.ServerPort(serverPort)) + } + } else { + serverPort, _ := strconv.Atoi(port) + attrs = append(attrs, semconv.ServerPort(serverPort)) + } + + attrs = append(attrs, semconv.URLScheme(req.Header.Get("X-Forwarded-Proto"))) + + t.semConvMetricRegistry.HTTPClientRequestDuration().Record(req.Context(), end.Sub(start).Seconds(), + httpconv.RequestMethodAttr(req.Method), req.URL.Host, serverPort, attrs...) + return response, err } diff --git a/pkg/proxy/httputil/observability_test.go b/pkg/proxy/httputil/observability_test.go index ea0721e07..c0ef0e7a3 100644 --- a/pkg/proxy/httputil/observability_test.go +++ b/pkg/proxy/httputil/observability_test.go @@ -1,7 +1,6 @@ package httputil import ( - "context" "net/http" "net/http/httptest" "testing" @@ -9,8 +8,9 @@ import ( "github.com/stretchr/testify/require" ptypes "github.com/traefik/paerser/types" - "github.com/traefik/traefik/v3/pkg/metrics" - "github.com/traefik/traefik/v3/pkg/types" + "github.com/traefik/traefik/v3/pkg/middlewares/observability" + "github.com/traefik/traefik/v3/pkg/observability/metrics" + otypes "github.com/traefik/traefik/v3/pkg/observability/types" "go.opentelemetry.io/otel/attribute" sdkmetric "go.opentelemetry.io/otel/sdk/metric" "go.opentelemetry.io/otel/sdk/metric/metricdata" @@ -59,7 +59,7 @@ func TestObservabilityRoundTripper_metrics(t *testing.T) { t.Run(test.desc, func(t *testing.T) { t.Parallel() - var cfg types.OTLP + var cfg otypes.OTLP (&cfg).SetDefaults() cfg.AddRoutersLabels = true cfg.PushInterval = ptypes.Duration(10 * time.Millisecond) @@ -69,7 +69,7 @@ func TestObservabilityRoundTripper_metrics(t *testing.T) { // force the meter provider with manual reader to collect metrics for the test. metrics.SetMeterProvider(meterProvider) - semConvMetricRegistry, err := metrics.NewSemConvMetricRegistry(context.Background(), &cfg) + semConvMetricRegistry, err := metrics.NewSemConvMetricRegistry(t.Context(), &cfg) require.NoError(t, err) require.NotNil(t, semConvMetricRegistry) @@ -78,12 +78,17 @@ func TestObservabilityRoundTripper_metrics(t *testing.T) { req.Header.Set("User-Agent", "rt-test") req.Header.Set("X-Forwarded-Proto", "http") + // Injection of the observability variables in the request context. + req = req.WithContext(observability.WithObservability(req.Context(), observability.Observability{ + SemConvMetricsEnabled: true, + })) + ort := newObservabilityRoundTripper(semConvMetricRegistry, mockRoundTripper{statusCode: test.statusCode}) _, err = ort.RoundTrip(req) require.NoError(t, err) got := metricdata.ResourceMetrics{} - err = rdr.Collect(context.Background(), &got) + err = rdr.Collect(t.Context(), &got) require.NoError(t, err) require.Len(t, got.ScopeMetrics, 1) diff --git a/pkg/proxy/httputil/proxy.go b/pkg/proxy/httputil/proxy.go index 7d4c52c9d..a6c93cade 100644 --- a/pkg/proxy/httputil/proxy.go +++ b/pkg/proxy/httputil/proxy.go @@ -15,7 +15,7 @@ import ( "github.com/rs/zerolog" "github.com/rs/zerolog/log" - "github.com/traefik/traefik/v3/pkg/logs" + "github.com/traefik/traefik/v3/pkg/observability/logs" "golang.org/x/net/http/httpguts" ) diff --git a/pkg/proxy/httputil/proxy_websocket_test.go b/pkg/proxy/httputil/proxy_websocket_test.go index 48296f955..7abfc39f6 100644 --- a/pkg/proxy/httputil/proxy_websocket_test.go +++ b/pkg/proxy/httputil/proxy_websocket_test.go @@ -301,7 +301,7 @@ func TestWebSocketRequestWithHeadersInResponseWriter(t *testing.T) { }, } - p, err := NewProxyBuilder(transportManager, nil).Build("default@internal", testhelpers.MustParseURL(srv.URL), false, true, false, 0) + p, err := NewProxyBuilder(transportManager, nil).Build("default@internal", testhelpers.MustParseURL(srv.URL), true, false, 0) require.NoError(t, err) proxy := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { req.URL = testhelpers.MustParseURL(srv.URL) @@ -357,7 +357,7 @@ func TestWebSocketUpgradeFailed(t *testing.T) { }, } - p, err := NewProxyBuilder(transportManager, nil).Build("default@internal", testhelpers.MustParseURL(srv.URL), false, true, false, 0) + p, err := NewProxyBuilder(transportManager, nil).Build("default@internal", testhelpers.MustParseURL(srv.URL), true, false, 0) require.NoError(t, err) proxy := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { path := req.URL.Path // keep the original path @@ -618,7 +618,7 @@ func createProxyWithForwarder(t *testing.T, uri string, transport http.RoundTrip roundTrippers: map[string]http.RoundTripper{"fwd": transport}, } - p, err := NewProxyBuilder(transportManager, nil).Build("fwd", u, false, true, false, 0) + p, err := NewProxyBuilder(transportManager, nil).Build("fwd", u, true, false, 0) require.NoError(t, err) srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { diff --git a/pkg/proxy/smart_builder.go b/pkg/proxy/smart_builder.go index 08b247c53..ad9d14d56 100644 --- a/pkg/proxy/smart_builder.go +++ b/pkg/proxy/smart_builder.go @@ -45,7 +45,7 @@ func (b *SmartBuilder) Update(newConfigs map[string]*dynamic.ServersTransport) { } // Build builds an HTTP proxy for the given URL using the ServersTransport with the given name. -func (b *SmartBuilder) Build(configName string, targetURL *url.URL, shouldObserve, passHostHeader, preservePath bool, flushInterval time.Duration) (http.Handler, error) { +func (b *SmartBuilder) Build(configName string, targetURL *url.URL, passHostHeader, preservePath bool, flushInterval time.Duration) (http.Handler, error) { serversTransport, err := b.transportManager.Get(configName) if err != nil { return nil, fmt.Errorf("getting ServersTransport: %w", err) @@ -55,7 +55,7 @@ func (b *SmartBuilder) Build(configName string, targetURL *url.URL, shouldObserv // For the https scheme we cannot guess if the backend communication will use HTTP2, // thus we check if HTTP/2 is disabled to use the fast proxy implementation when this is possible. if targetURL.Scheme == "h2c" || (targetURL.Scheme == "https" && !serversTransport.DisableHTTP2) { - return b.proxyBuilder.Build(configName, targetURL, shouldObserve, passHostHeader, preservePath, flushInterval) + return b.proxyBuilder.Build(configName, targetURL, passHostHeader, preservePath, flushInterval) } return b.fastProxyBuilder.Build(configName, targetURL, passHostHeader, preservePath) } diff --git a/pkg/proxy/smart_builder_test.go b/pkg/proxy/smart_builder_test.go index c03bd19f3..d1c29ddd8 100644 --- a/pkg/proxy/smart_builder_test.go +++ b/pkg/proxy/smart_builder_test.go @@ -101,7 +101,7 @@ func TestSmartBuilder_Build(t *testing.T) { httpProxyBuilder := httputil.NewProxyBuilder(transportManager, nil) proxyBuilder := NewSmartBuilder(transportManager, httpProxyBuilder, test.fastProxyConfig) - proxyHandler, err := proxyBuilder.Build("test", targetURL, false, false, false, time.Second) + proxyHandler, err := proxyBuilder.Build("test", targetURL, false, false, time.Second) require.NoError(t, err) rw := httptest.NewRecorder() diff --git a/pkg/redactor/redactor.go b/pkg/redactor/redactor.go index c28feaa1a..cbe4a6c5c 100644 --- a/pkg/redactor/redactor.go +++ b/pkg/redactor/redactor.go @@ -70,7 +70,7 @@ func doOnJSON(input string) string { } func doOnStruct(field reflect.Value, tag string, redactByDefault bool) error { - if field.Type().AssignableTo(reflect.TypeOf(dynamic.PluginConf{})) { + if field.Type().AssignableTo(reflect.TypeFor[dynamic.PluginConf]()) { resetPlugin(field) return nil } @@ -164,7 +164,7 @@ func reset(field reflect.Value, name string) error { } case reflect.String: if field.String() != "" { - if field.Type().AssignableTo(reflect.TypeOf(types.FileOrContent(""))) { + if field.Type().AssignableTo(reflect.TypeFor[types.FileOrContent]()) { field.Set(reflect.ValueOf(types.FileOrContent(maskShort))) } else { field.Set(reflect.ValueOf(maskShort)) diff --git a/pkg/redactor/redactor_config_test.go b/pkg/redactor/redactor_config_test.go index b086ccd6a..9edf2363f 100644 --- a/pkg/redactor/redactor_config_test.go +++ b/pkg/redactor/redactor_config_test.go @@ -12,6 +12,7 @@ import ( ptypes "github.com/traefik/paerser/types" "github.com/traefik/traefik/v3/pkg/config/dynamic" "github.com/traefik/traefik/v3/pkg/config/static" + otypes "github.com/traefik/traefik/v3/pkg/observability/types" "github.com/traefik/traefik/v3/pkg/ping" "github.com/traefik/traefik/v3/pkg/plugins" "github.com/traefik/traefik/v3/pkg/provider/acme" @@ -798,21 +799,21 @@ func TestDo_staticConfiguration(t *testing.T) { Debug: true, } - config.Metrics = &types.Metrics{ - Prometheus: &types.Prometheus{ + config.Metrics = &otypes.Metrics{ + Prometheus: &otypes.Prometheus{ Buckets: []float64{0.1, 0.3, 1.2, 5}, AddEntryPointsLabels: true, AddServicesLabels: true, EntryPoint: "MyEntryPoint", ManualRouting: true, }, - Datadog: &types.Datadog{ + Datadog: &otypes.Datadog{ Address: "localhost:8181", PushInterval: 42, AddEntryPointsLabels: true, AddServicesLabels: true, }, - StatsD: &types.Statsd{ + StatsD: &otypes.Statsd{ Address: "localhost:8182", PushInterval: 42, AddEntryPointsLabels: true, @@ -827,7 +828,7 @@ func TestDo_staticConfiguration(t *testing.T) { TerminatingStatusCode: 42, } - config.Log = &types.TraefikLog{ + config.Log = &otypes.TraefikLog{ Level: "Level", Format: "json", FilePath: "/foo/path", @@ -835,19 +836,19 @@ func TestDo_staticConfiguration(t *testing.T) { MaxAge: 3, MaxBackups: 4, Compress: true, - OTLP: &types.OTelLog{ + OTLP: &otypes.OTelLog{ ServiceName: "foobar", ResourceAttributes: map[string]string{ "foobar": "foobar", }, - GRPC: &types.OTelGRPC{ + GRPC: &otypes.OTelGRPC{ Endpoint: "foobar", Insecure: true, Headers: map[string]string{ "foobar": "foobar", }, }, - HTTP: &types.OTelHTTP{ + HTTP: &otypes.OTelHTTP{ Endpoint: "foobar", Headers: map[string]string{ "foobar": "foobar", @@ -856,20 +857,20 @@ func TestDo_staticConfiguration(t *testing.T) { }, } - config.AccessLog = &types.AccessLog{ + config.AccessLog = &otypes.AccessLog{ FilePath: "AccessLog FilePath", Format: "AccessLog Format", - Filters: &types.AccessLogFilters{ + Filters: &otypes.AccessLogFilters{ StatusCodes: []string{"200", "500"}, RetryAttempts: true, MinDuration: 42, }, - Fields: &types.AccessLogFields{ + Fields: &otypes.AccessLogFields{ DefaultMode: "drop", Names: map[string]string{ "RequestHost": "keep", }, - Headers: &types.FieldHeaders{ + Headers: &otypes.FieldHeaders{ DefaultMode: "drop", Names: map[string]string{ "Referer": "keep", @@ -877,19 +878,19 @@ func TestDo_staticConfiguration(t *testing.T) { }, }, BufferingSize: 42, - OTLP: &types.OTelLog{ + OTLP: &otypes.OTelLog{ ServiceName: "foobar", ResourceAttributes: map[string]string{ "foobar": "foobar", }, - GRPC: &types.OTelGRPC{ + GRPC: &otypes.OTelGRPC{ Endpoint: "foobar", Insecure: true, Headers: map[string]string{ "foobar": "foobar", }, }, - HTTP: &types.OTelHTTP{ + HTTP: &otypes.OTelHTTP{ Endpoint: "foobar", Headers: map[string]string{ "foobar": "foobar", @@ -907,14 +908,14 @@ func TestDo_staticConfiguration(t *testing.T) { "foobar": "foobar", }, SampleRate: 42, - OTLP: &types.OTelTracing{ - HTTP: &types.OTelHTTP{ + OTLP: &otypes.OTelTracing{ + HTTP: &otypes.OTelHTTP{ Endpoint: "foobar", Headers: map[string]string{ "foobar": "foobar", }, }, - GRPC: &types.OTelGRPC{ + GRPC: &otypes.OTelGRPC{ Endpoint: "foobar", Insecure: true, Headers: map[string]string{ diff --git a/pkg/safe/routine_test.go b/pkg/safe/routine_test.go index 12875972c..3fa403e78 100644 --- a/pkg/safe/routine_test.go +++ b/pkg/safe/routine_test.go @@ -15,7 +15,7 @@ func TestNewPoolContext(t *testing.T) { testKey := testKeyType("test") - ctx := context.WithValue(context.Background(), testKey, "test") + ctx := context.WithValue(t.Context(), testKey, "test") p := NewPool(ctx) p.GoCtx(func(ctx context.Context) { @@ -66,7 +66,7 @@ func TestPoolWithCtx(t *testing.T) { t.Run(test.desc, func(t *testing.T) { // These subtests cannot be run in parallel, since the testRoutine // is shared across the subtests. - p := NewPool(context.Background()) + p := NewPool(t.Context()) timer := time.NewTimer(500 * time.Millisecond) defer timer.Stop() @@ -93,7 +93,7 @@ func TestPoolWithCtx(t *testing.T) { } func TestPoolCleanupWithGoPanicking(t *testing.T) { - p := NewPool(context.Background()) + p := NewPool(t.Context()) timer := time.NewTimer(500 * time.Millisecond) defer timer.Stop() diff --git a/pkg/server/aggregator.go b/pkg/server/aggregator.go index 2a0c1bd4f..8f22474fe 100644 --- a/pkg/server/aggregator.go +++ b/pkg/server/aggregator.go @@ -7,7 +7,8 @@ import ( "github.com/go-acme/lego/v4/challenge/tlsalpn01" "github.com/rs/zerolog/log" "github.com/traefik/traefik/v3/pkg/config/dynamic" - "github.com/traefik/traefik/v3/pkg/logs" + "github.com/traefik/traefik/v3/pkg/observability/logs" + otypes "github.com/traefik/traefik/v3/pkg/observability/types" "github.com/traefik/traefik/v3/pkg/server/provider" "github.com/traefik/traefik/v3/pkg/tls" ) @@ -208,6 +209,10 @@ func applyModel(cfg dynamic.Configuration) dynamic.Configuration { cp.Observability.Tracing = m.Observability.Tracing } + if cp.Observability.TraceVerbosity == "" { + cp.Observability.TraceVerbosity = m.Observability.TraceVerbosity + } + rtName := name if len(eps) > 1 { rtName = epName + "-" + name @@ -224,7 +229,7 @@ func applyModel(cfg dynamic.Configuration) dynamic.Configuration { cfg.HTTP.Routers = rts } - // Apply default observability model to HTTP routers. + // Apply the default observability model to HTTP routers. applyDefaultObservabilityModel(cfg) if cfg.TCP == nil || len(cfg.TCP.Models) == 0 { @@ -256,14 +261,16 @@ func applyModel(cfg dynamic.Configuration) dynamic.Configuration { // and make sure it is serialized and available in the API. // We could have introduced a "default" model, but it would have been more complex to manage for now. // This could be generalized in the future. +// TODO: check if we can remove this and rely on the SetDefaults instead. func applyDefaultObservabilityModel(cfg dynamic.Configuration) { if cfg.HTTP != nil { for _, router := range cfg.HTTP.Routers { if router.Observability == nil { router.Observability = &dynamic.RouterObservabilityConfig{ - AccessLogs: pointer(true), - Metrics: pointer(true), - Tracing: pointer(true), + AccessLogs: pointer(true), + Metrics: pointer(true), + Tracing: pointer(true), + TraceVerbosity: otypes.MinimalVerbosity, } continue @@ -273,12 +280,16 @@ func applyDefaultObservabilityModel(cfg dynamic.Configuration) { router.Observability.AccessLogs = pointer(true) } + if router.Observability.Metrics == nil { + router.Observability.Metrics = pointer(true) + } + if router.Observability.Tracing == nil { router.Observability.Tracing = pointer(true) } - if router.Observability.Metrics == nil { - router.Observability.Metrics = pointer(true) + if router.Observability.TraceVerbosity == "" { + router.Observability.TraceVerbosity = otypes.MinimalVerbosity } } } diff --git a/pkg/server/aggregator_test.go b/pkg/server/aggregator_test.go index 182815a39..e33ef7ae1 100644 --- a/pkg/server/aggregator_test.go +++ b/pkg/server/aggregator_test.go @@ -6,6 +6,7 @@ import ( "github.com/go-acme/lego/v4/challenge/tlsalpn01" "github.com/stretchr/testify/assert" "github.com/traefik/traefik/v3/pkg/config/dynamic" + otypes "github.com/traefik/traefik/v3/pkg/observability/types" "github.com/traefik/traefik/v3/pkg/tls" ) @@ -521,9 +522,10 @@ func Test_applyModel(t *testing.T) { Routers: map[string]*dynamic.Router{ "test": { Observability: &dynamic.RouterObservabilityConfig{ - AccessLogs: pointer(true), - Metrics: pointer(true), - Tracing: pointer(true), + AccessLogs: pointer(true), + Metrics: pointer(true), + Tracing: pointer(true), + TraceVerbosity: otypes.MinimalVerbosity, }, }, }, @@ -589,9 +591,10 @@ func Test_applyModel(t *testing.T) { Middlewares: []string{"test"}, TLS: &dynamic.RouterTLSConfig{}, Observability: &dynamic.RouterObservabilityConfig{ - AccessLogs: pointer(true), - Metrics: pointer(true), - Tracing: pointer(true), + AccessLogs: pointer(true), + Metrics: pointer(true), + Tracing: pointer(true), + TraceVerbosity: otypes.MinimalVerbosity, }, }, }, @@ -622,9 +625,10 @@ func Test_applyModel(t *testing.T) { Middlewares: []string{"test"}, TLS: &dynamic.RouterTLSConfig{}, Observability: dynamic.RouterObservabilityConfig{ - AccessLogs: pointer(true), - Tracing: pointer(true), - Metrics: pointer(true), + AccessLogs: pointer(true), + Tracing: pointer(true), + Metrics: pointer(true), + TraceVerbosity: otypes.MinimalVerbosity, }, }, }, @@ -638,9 +642,10 @@ func Test_applyModel(t *testing.T) { Middlewares: []string{"test"}, TLS: &dynamic.RouterTLSConfig{}, Observability: &dynamic.RouterObservabilityConfig{ - AccessLogs: pointer(true), - Tracing: pointer(true), - Metrics: pointer(true), + AccessLogs: pointer(true), + Tracing: pointer(true), + Metrics: pointer(true), + TraceVerbosity: otypes.MinimalVerbosity, }, }, }, @@ -651,9 +656,10 @@ func Test_applyModel(t *testing.T) { Middlewares: []string{"test"}, TLS: &dynamic.RouterTLSConfig{}, Observability: dynamic.RouterObservabilityConfig{ - AccessLogs: pointer(true), - Tracing: pointer(true), - Metrics: pointer(true), + AccessLogs: pointer(true), + Tracing: pointer(true), + Metrics: pointer(true), + TraceVerbosity: otypes.MinimalVerbosity, }, }, }, @@ -688,9 +694,10 @@ func Test_applyModel(t *testing.T) { Middlewares: []string{"test"}, TLS: &dynamic.RouterTLSConfig{CertResolver: "router"}, Observability: &dynamic.RouterObservabilityConfig{ - AccessLogs: pointer(true), - Metrics: pointer(true), - Tracing: pointer(true), + AccessLogs: pointer(true), + Metrics: pointer(true), + Tracing: pointer(true), + TraceVerbosity: otypes.MinimalVerbosity, }, }, }, @@ -730,9 +737,10 @@ func Test_applyModel(t *testing.T) { "test": { EntryPoints: []string{"web"}, Observability: &dynamic.RouterObservabilityConfig{ - AccessLogs: pointer(true), - Metrics: pointer(true), - Tracing: pointer(true), + AccessLogs: pointer(true), + Metrics: pointer(true), + Tracing: pointer(true), + TraceVerbosity: otypes.MinimalVerbosity, }, }, "websecure-test": { @@ -740,9 +748,10 @@ func Test_applyModel(t *testing.T) { Middlewares: []string{"test"}, TLS: &dynamic.RouterTLSConfig{}, Observability: &dynamic.RouterObservabilityConfig{ - AccessLogs: pointer(true), - Metrics: pointer(true), - Tracing: pointer(true), + AccessLogs: pointer(true), + Metrics: pointer(true), + Tracing: pointer(true), + TraceVerbosity: otypes.MinimalVerbosity, }, }, }, diff --git a/pkg/server/configurationwatcher.go b/pkg/server/configurationwatcher.go index 5071f2498..8142f8359 100644 --- a/pkg/server/configurationwatcher.go +++ b/pkg/server/configurationwatcher.go @@ -8,7 +8,7 @@ import ( "github.com/rs/zerolog" "github.com/rs/zerolog/log" "github.com/traefik/traefik/v3/pkg/config/dynamic" - "github.com/traefik/traefik/v3/pkg/logs" + "github.com/traefik/traefik/v3/pkg/observability/logs" "github.com/traefik/traefik/v3/pkg/provider" "github.com/traefik/traefik/v3/pkg/safe" "github.com/traefik/traefik/v3/pkg/tls" diff --git a/pkg/server/configurationwatcher_test.go b/pkg/server/configurationwatcher_test.go index 7baf233d0..99a196008 100644 --- a/pkg/server/configurationwatcher_test.go +++ b/pkg/server/configurationwatcher_test.go @@ -57,7 +57,7 @@ func (p *mockProvider) Init() error { } func TestNewConfigurationWatcher(t *testing.T) { - routinesPool := safe.NewPool(context.Background()) + routinesPool := safe.NewPool(t.Context()) t.Cleanup(routinesPool.Stop) pvd := &mockProvider{ @@ -117,7 +117,7 @@ func TestNewConfigurationWatcher(t *testing.T) { } func TestWaitForRequiredProvider(t *testing.T) { - routinesPool := safe.NewPool(context.Background()) + routinesPool := safe.NewPool(t.Context()) pvdAggregator := &mockProvider{ wait: 5 * time.Millisecond, @@ -165,7 +165,7 @@ func TestWaitForRequiredProvider(t *testing.T) { } func TestIgnoreTransientConfiguration(t *testing.T) { - routinesPool := safe.NewPool(context.Background()) + routinesPool := safe.NewPool(t.Context()) config := &dynamic.Configuration{ HTTP: th.BuildConfiguration( @@ -305,7 +305,7 @@ func TestIgnoreTransientConfiguration(t *testing.T) { } func TestListenProvidersThrottleProviderConfigReload(t *testing.T) { - routinesPool := safe.NewPool(context.Background()) + routinesPool := safe.NewPool(t.Context()) pvd := &mockProvider{ wait: 10 * time.Millisecond, @@ -350,7 +350,7 @@ func TestListenProvidersThrottleProviderConfigReload(t *testing.T) { } func TestListenProvidersSkipsEmptyConfigs(t *testing.T) { - routinesPool := safe.NewPool(context.Background()) + routinesPool := safe.NewPool(t.Context()) pvd := &mockProvider{ messages: []dynamic.Message{{ProviderName: "mock"}}, @@ -371,7 +371,7 @@ func TestListenProvidersSkipsEmptyConfigs(t *testing.T) { } func TestListenProvidersSkipsSameConfigurationForProvider(t *testing.T) { - routinesPool := safe.NewPool(context.Background()) + routinesPool := safe.NewPool(t.Context()) message := dynamic.Message{ ProviderName: "mock", @@ -405,7 +405,7 @@ func TestListenProvidersSkipsSameConfigurationForProvider(t *testing.T) { } func TestListenProvidersDoesNotSkipFlappingConfiguration(t *testing.T) { - routinesPool := safe.NewPool(context.Background()) + routinesPool := safe.NewPool(t.Context()) configuration := &dynamic.Configuration{ HTTP: th.BuildConfiguration( @@ -475,7 +475,7 @@ func TestListenProvidersDoesNotSkipFlappingConfiguration(t *testing.T) { } func TestListenProvidersIgnoreSameConfig(t *testing.T) { - routinesPool := safe.NewPool(context.Background()) + routinesPool := safe.NewPool(t.Context()) configuration := &dynamic.Configuration{ HTTP: th.BuildConfiguration( @@ -568,7 +568,7 @@ func TestListenProvidersIgnoreSameConfig(t *testing.T) { } func TestApplyConfigUnderStress(t *testing.T) { - routinesPool := safe.NewPool(context.Background()) + routinesPool := safe.NewPool(t.Context()) watcher := NewConfigurationWatcher(routinesPool, &mockProvider{}, []string{}, "") @@ -611,7 +611,7 @@ func TestApplyConfigUnderStress(t *testing.T) { } func TestListenProvidersIgnoreIntermediateConfigs(t *testing.T) { - routinesPool := safe.NewPool(context.Background()) + routinesPool := safe.NewPool(t.Context()) configuration := &dynamic.Configuration{ HTTP: th.BuildConfiguration( @@ -704,7 +704,7 @@ func TestListenProvidersIgnoreIntermediateConfigs(t *testing.T) { } func TestListenProvidersPublishesConfigForEachProvider(t *testing.T) { - routinesPool := safe.NewPool(context.Background()) + routinesPool := safe.NewPool(t.Context()) configuration := &dynamic.Configuration{ HTTP: th.BuildConfiguration( @@ -771,7 +771,7 @@ func TestListenProvidersPublishesConfigForEachProvider(t *testing.T) { } func TestPublishConfigUpdatedByProvider(t *testing.T) { - routinesPool := safe.NewPool(context.Background()) + routinesPool := safe.NewPool(t.Context()) pvdConfiguration := dynamic.Configuration{ TCP: &dynamic.TCPConfiguration{ @@ -817,7 +817,7 @@ func TestPublishConfigUpdatedByProvider(t *testing.T) { } func TestPublishConfigUpdatedByConfigWatcherListener(t *testing.T) { - routinesPool := safe.NewPool(context.Background()) + routinesPool := safe.NewPool(t.Context()) pvd := &mockProvider{ wait: 10 * time.Millisecond, diff --git a/pkg/server/middleware/middlewares.go b/pkg/server/middleware/middlewares.go index 9eebfd31c..624a3c9c9 100644 --- a/pkg/server/middleware/middlewares.go +++ b/pkg/server/middleware/middlewares.go @@ -428,8 +428,5 @@ func (b *Builder) buildConstructor(ctx context.Context, middlewareName string) ( return nil, fmt.Errorf("invalid middleware %q configuration: invalid middleware type or middleware does not exist", middlewareName) } - // The tracing middleware is a NOOP if tracing is not setup on the middleware chain. - // Hence, regarding internal resources' observability deactivation, - // this would not enable tracing. return observability.WrapMiddleware(ctx, middleware), nil } diff --git a/pkg/server/middleware/middlewares_test.go b/pkg/server/middleware/middlewares_test.go index 89ea08963..92cebc050 100644 --- a/pkg/server/middleware/middlewares_test.go +++ b/pkg/server/middleware/middlewares_test.go @@ -1,7 +1,6 @@ package middleware import ( - "context" "errors" "net/http" "net/http/httptest" @@ -20,7 +19,7 @@ func TestBuilder_BuildChainNilConfig(t *testing.T) { } middlewaresBuilder := NewBuilder(testConfig, nil, nil) - chain := middlewaresBuilder.BuildChain(context.Background(), []string{"empty"}) + chain := middlewaresBuilder.BuildChain(t.Context(), []string{"empty"}) _, err := chain.Then(nil) require.Error(t, err) } @@ -31,7 +30,7 @@ func TestBuilder_BuildChainNonExistentChain(t *testing.T) { } middlewaresBuilder := NewBuilder(testConfig, nil, nil) - chain := middlewaresBuilder.BuildChain(context.Background(), []string{"empty"}) + chain := middlewaresBuilder.BuildChain(t.Context(), []string{"empty"}) _, err := chain.Then(nil) require.Error(t, err) } @@ -259,7 +258,7 @@ func TestBuilder_BuildChainWithContext(t *testing.T) { t.Run(test.desc, func(t *testing.T) { t.Parallel() - ctx := context.Background() + ctx := t.Context() if len(test.contextProvider) > 0 { ctx = provider.AddInContext(ctx, "foobar@"+test.contextProvider) } @@ -366,7 +365,7 @@ func TestBuilder_buildConstructor(t *testing.T) { t.Run(test.desc, func(t *testing.T) { t.Parallel() - constructor, err := middlewaresBuilder.buildConstructor(context.Background(), test.middlewareID) + constructor, err := middlewaresBuilder.buildConstructor(t.Context(), test.middlewareID) require.NoError(t, err) middleware, err2 := constructor(http.HandlerFunc(func(_ http.ResponseWriter, _ *http.Request) {})) diff --git a/pkg/server/middleware/observability.go b/pkg/server/middleware/observability.go index d279be902..0ffe34165 100644 --- a/pkg/server/middleware/observability.go +++ b/pkg/server/middleware/observability.go @@ -4,19 +4,19 @@ import ( "context" "io" "net/http" - "strings" "github.com/containous/alice" "github.com/rs/zerolog/log" "github.com/traefik/traefik/v3/pkg/config/dynamic" "github.com/traefik/traefik/v3/pkg/config/static" - "github.com/traefik/traefik/v3/pkg/logs" - "github.com/traefik/traefik/v3/pkg/metrics" "github.com/traefik/traefik/v3/pkg/middlewares/accesslog" "github.com/traefik/traefik/v3/pkg/middlewares/capture" mmetrics "github.com/traefik/traefik/v3/pkg/middlewares/metrics" "github.com/traefik/traefik/v3/pkg/middlewares/observability" - "github.com/traefik/traefik/v3/pkg/tracing" + "github.com/traefik/traefik/v3/pkg/observability/logs" + "github.com/traefik/traefik/v3/pkg/observability/metrics" + "github.com/traefik/traefik/v3/pkg/observability/tracing" + otypes "github.com/traefik/traefik/v3/pkg/observability/types" ) // ObservabilityMgr is a manager for observability (AccessLogs, Metrics and Tracing) enablement. @@ -42,111 +42,44 @@ func NewObservabilityMgr(config static.Configuration, metricsRegistry metrics.Re } // BuildEPChain an observability middleware chain by entry point. -func (o *ObservabilityMgr) BuildEPChain(ctx context.Context, entryPointName string, resourceName string, observabilityConfig *dynamic.RouterObservabilityConfig) alice.Chain { +func (o *ObservabilityMgr) BuildEPChain(ctx context.Context, entryPointName string, internal bool, config dynamic.RouterObservabilityConfig) alice.Chain { chain := alice.New() if o == nil { return chain } - if o.accessLoggerMiddleware != nil || o.metricsRegistry != nil && (o.metricsRegistry.IsEpEnabled() || o.metricsRegistry.IsRouterEnabled() || o.metricsRegistry.IsSvcEnabled()) { - if o.ShouldAddAccessLogs(resourceName, observabilityConfig) || o.ShouldAddMetrics(resourceName, observabilityConfig) { - chain = chain.Append(capture.Wrap) - } + // Injection of the observability variables in the request context. + // This injection must be the first step in order for other observability middlewares to rely on it. + chain = chain.Append(func(next http.Handler) (http.Handler, error) { + return o.observabilityContextHandler(next, internal, config), nil + }) + + // Capture middleware for accessLogs or metrics. + if o.shouldAccessLog(internal, config) || o.shouldMeter(internal, config) || o.shouldMeterSemConv(internal, config) { + chain = chain.Append(capture.Wrap) } // As the Entry point observability middleware ensures that the tracing is added to the request and logger context, // it needs to be added before the access log middleware to ensure that the trace ID is logged. - if o.tracer != nil && o.ShouldAddTracing(resourceName, observabilityConfig) { - chain = chain.Append(observability.EntryPointHandler(ctx, o.tracer, entryPointName)) - } + chain = chain.Append(observability.EntryPointHandler(ctx, o.tracer, entryPointName)) - if o.accessLoggerMiddleware != nil && o.ShouldAddAccessLogs(resourceName, observabilityConfig) { - chain = chain.Append(accesslog.WrapHandler(o.accessLoggerMiddleware)) - chain = chain.Append(func(next http.Handler) (http.Handler, error) { - return accesslog.NewFieldHandler(next, logs.EntryPointName, entryPointName, accesslog.InitServiceFields), nil - }) - } + // Access log handlers. + chain = chain.Append(o.accessLoggerMiddleware.AliceConstructor()) + chain = chain.Append(func(next http.Handler) (http.Handler, error) { + return accesslog.NewFieldHandler(next, logs.EntryPointName, entryPointName, accesslog.InitServiceFields), nil + }) + + // Entrypoint metrics handler. + metricsHandler := mmetrics.EntryPointMetricsHandler(ctx, o.metricsRegistry, entryPointName) + chain = chain.Append(observability.WrapMiddleware(ctx, metricsHandler)) // Semantic convention server metrics handler. - if o.semConvMetricRegistry != nil && o.ShouldAddMetrics(resourceName, observabilityConfig) { - chain = chain.Append(observability.SemConvServerMetricsHandler(ctx, o.semConvMetricRegistry)) - } - - if o.metricsRegistry != nil && o.metricsRegistry.IsEpEnabled() && o.ShouldAddMetrics(resourceName, observabilityConfig) { - metricsHandler := mmetrics.WrapEntryPointHandler(ctx, o.metricsRegistry, entryPointName) - - if o.tracer != nil && o.ShouldAddTracing(resourceName, observabilityConfig) { - chain = chain.Append(observability.WrapMiddleware(ctx, metricsHandler)) - } else { - chain = chain.Append(metricsHandler) - } - } - - // Inject context keys to control whether to produce metrics further downstream (services, round-tripper), - // because the router configuration cannot be evaluated during build time for services. - if observabilityConfig != nil && observabilityConfig.Metrics != nil && !*observabilityConfig.Metrics { - chain = chain.Append(func(next http.Handler) (http.Handler, error) { - return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { - next.ServeHTTP(rw, req.WithContext(context.WithValue(req.Context(), observability.DisableMetricsKey, true))) - }), nil - }) - } + chain = chain.Append(observability.SemConvServerMetricsHandler(ctx, o.semConvMetricRegistry)) return chain } -// ShouldAddAccessLogs returns whether the access logs should be enabled for the given serviceName and the observability config. -func (o *ObservabilityMgr) ShouldAddAccessLogs(serviceName string, observabilityConfig *dynamic.RouterObservabilityConfig) bool { - if o == nil { - return false - } - - if o.config.AccessLog == nil { - return false - } - - if strings.HasSuffix(serviceName, "@internal") && !o.config.AccessLog.AddInternals { - return false - } - - return observabilityConfig == nil || observabilityConfig.AccessLogs == nil || *observabilityConfig.AccessLogs -} - -// ShouldAddMetrics returns whether the metrics should be enabled for the given resource and the observability config. -func (o *ObservabilityMgr) ShouldAddMetrics(serviceName string, observabilityConfig *dynamic.RouterObservabilityConfig) bool { - if o == nil { - return false - } - - if o.config.Metrics == nil { - return false - } - - if strings.HasSuffix(serviceName, "@internal") && !o.config.Metrics.AddInternals { - return false - } - - return observabilityConfig == nil || observabilityConfig.Metrics == nil || *observabilityConfig.Metrics -} - -// ShouldAddTracing returns whether the tracing should be enabled for the given serviceName and the observability config. -func (o *ObservabilityMgr) ShouldAddTracing(serviceName string, observabilityConfig *dynamic.RouterObservabilityConfig) bool { - if o == nil { - return false - } - - if o.config.Tracing == nil { - return false - } - - if strings.HasSuffix(serviceName, "@internal") && !o.config.Tracing.AddInternals { - return false - } - - return observabilityConfig == nil || observabilityConfig.Tracing == nil || *observabilityConfig.Tracing -} - // MetricsRegistry is an accessor to the metrics registry. func (o *ObservabilityMgr) MetricsRegistry() metrics.Registry { if o == nil { @@ -191,3 +124,89 @@ func (o *ObservabilityMgr) RotateAccessLogs() error { return o.accessLoggerMiddleware.Rotate() } + +func (o *ObservabilityMgr) observabilityContextHandler(next http.Handler, internal bool, config dynamic.RouterObservabilityConfig) http.Handler { + return observability.WithObservabilityHandler(next, observability.Observability{ + AccessLogsEnabled: o.shouldAccessLog(internal, config), + MetricsEnabled: o.shouldMeter(internal, config), + SemConvMetricsEnabled: o.shouldMeterSemConv(internal, config), + TracingEnabled: o.shouldTrace(internal, config, otypes.MinimalVerbosity), + DetailedTracingEnabled: o.shouldTrace(internal, config, otypes.DetailedVerbosity), + }) +} + +// shouldAccessLog returns whether the access logs should be enabled for the given serviceName and the observability config. +func (o *ObservabilityMgr) shouldAccessLog(internal bool, observabilityConfig dynamic.RouterObservabilityConfig) bool { + if o == nil { + return false + } + + if o.config.AccessLog == nil { + return false + } + + if internal && !o.config.AccessLog.AddInternals { + return false + } + + return observabilityConfig.AccessLogs == nil || *observabilityConfig.AccessLogs +} + +// shouldMeter returns whether the metrics should be enabled for the given serviceName and the observability config. +func (o *ObservabilityMgr) shouldMeter(internal bool, observabilityConfig dynamic.RouterObservabilityConfig) bool { + if o == nil || o.metricsRegistry == nil { + return false + } + + if !o.metricsRegistry.IsEpEnabled() && !o.metricsRegistry.IsRouterEnabled() && !o.metricsRegistry.IsSvcEnabled() { + return false + } + + if o.config.Metrics == nil { + return false + } + + if internal && !o.config.Metrics.AddInternals { + return false + } + + return observabilityConfig.Metrics == nil || *observabilityConfig.Metrics +} + +// shouldMeterSemConv returns whether the OTel semantic convention metrics should be enabled for the given serviceName and the observability config. +func (o *ObservabilityMgr) shouldMeterSemConv(internal bool, observabilityConfig dynamic.RouterObservabilityConfig) bool { + if o == nil || o.semConvMetricRegistry == nil { + return false + } + + if o.config.Metrics == nil { + return false + } + + if internal && !o.config.Metrics.AddInternals { + return false + } + + return observabilityConfig.Metrics == nil || *observabilityConfig.Metrics +} + +// shouldTrace returns whether the tracing should be enabled for the given serviceName and the observability config. +func (o *ObservabilityMgr) shouldTrace(internal bool, observabilityConfig dynamic.RouterObservabilityConfig, verbosity otypes.TracingVerbosity) bool { + if o == nil { + return false + } + + if o.config.Tracing == nil { + return false + } + + if internal && !o.config.Tracing.AddInternals { + return false + } + + if !observabilityConfig.TraceVerbosity.Allows(verbosity) { + return false + } + + return observabilityConfig.Tracing == nil || *observabilityConfig.Tracing +} diff --git a/pkg/server/middleware/plugins.go b/pkg/server/middleware/plugins.go index eacf8fba1..0529ca190 100644 --- a/pkg/server/middleware/plugins.go +++ b/pkg/server/middleware/plugins.go @@ -7,7 +7,6 @@ import ( "github.com/traefik/traefik/v3/pkg/config/dynamic" "github.com/traefik/traefik/v3/pkg/plugins" - "go.opentelemetry.io/otel/trace" ) const typeName = "Plugin" @@ -55,6 +54,6 @@ func (s *traceablePlugin) ServeHTTP(rw http.ResponseWriter, req *http.Request) { s.h.ServeHTTP(rw, req) } -func (s *traceablePlugin) GetTracingInformation() (string, string, trace.SpanKind) { - return s.name, typeName, trace.SpanKindInternal +func (s *traceablePlugin) GetTracingInformation() (string, string) { + return s.name, typeName } diff --git a/pkg/server/provider/provider_test.go b/pkg/server/provider/provider_test.go index 7686d1bb5..f4a0b27e3 100644 --- a/pkg/server/provider/provider_test.go +++ b/pkg/server/provider/provider_test.go @@ -16,31 +16,31 @@ func TestAddInContext(t *testing.T) { }{ { desc: "without provider information", - ctx: context.Background(), + ctx: t.Context(), name: "test", expected: "", }, { desc: "provider name embedded in element name", - ctx: context.Background(), + ctx: t.Context(), name: "test@foo", expected: "foo", }, { desc: "provider name in context", - ctx: context.WithValue(context.Background(), key, "foo"), + ctx: context.WithValue(t.Context(), key, "foo"), name: "test", expected: "foo", }, { desc: "provider name in context and different provider name embedded in element name", - ctx: context.WithValue(context.Background(), key, "foo"), + ctx: context.WithValue(t.Context(), key, "foo"), name: "test@fii", expected: "fii", }, { desc: "provider name in context and same provider name embedded in element name", - ctx: context.WithValue(context.Background(), key, "foo"), + ctx: context.WithValue(t.Context(), key, "foo"), name: "test@foo", expected: "foo", }, @@ -71,31 +71,31 @@ func TestGetQualifiedName(t *testing.T) { }{ { desc: "empty name", - ctx: context.Background(), + ctx: t.Context(), name: "", expected: "", }, { desc: "without provider", - ctx: context.Background(), + ctx: t.Context(), name: "test", expected: "test", }, { desc: "with explicit provider", - ctx: context.Background(), + ctx: t.Context(), name: "test@foo", expected: "test@foo", }, { desc: "with provider in context", - ctx: context.WithValue(context.Background(), key, "foo"), + ctx: context.WithValue(t.Context(), key, "foo"), name: "test", expected: "test@foo", }, { desc: "with provider in context and explicit name", - ctx: context.WithValue(context.Background(), key, "foo"), + ctx: context.WithValue(t.Context(), key, "foo"), name: "test@fii", expected: "test@fii", }, diff --git a/pkg/server/router/router.go b/pkg/server/router/router.go index 3abd239e5..bac2d5c48 100644 --- a/pkg/server/router/router.go +++ b/pkg/server/router/router.go @@ -10,14 +10,15 @@ import ( "github.com/containous/alice" "github.com/rs/zerolog/log" + "github.com/traefik/traefik/v3/pkg/config/dynamic" "github.com/traefik/traefik/v3/pkg/config/runtime" - "github.com/traefik/traefik/v3/pkg/logs" "github.com/traefik/traefik/v3/pkg/middlewares/accesslog" "github.com/traefik/traefik/v3/pkg/middlewares/denyrouterrecursion" metricsMiddle "github.com/traefik/traefik/v3/pkg/middlewares/metrics" "github.com/traefik/traefik/v3/pkg/middlewares/observability" "github.com/traefik/traefik/v3/pkg/middlewares/recovery" httpmuxer "github.com/traefik/traefik/v3/pkg/muxer/http" + "github.com/traefik/traefik/v3/pkg/observability/logs" "github.com/traefik/traefik/v3/pkg/server/middleware" "github.com/traefik/traefik/v3/pkg/server/provider" "github.com/traefik/traefik/v3/pkg/tls" @@ -70,11 +71,22 @@ func (m *Manager) getHTTPRouters(ctx context.Context, entryPoints []string, tls func (m *Manager) BuildHandlers(rootCtx context.Context, entryPoints []string, tls bool) map[string]http.Handler { entryPointHandlers := make(map[string]http.Handler) + defaultObsConfig := dynamic.RouterObservabilityConfig{} + defaultObsConfig.SetDefaults() + for entryPointName, routers := range m.getHTTPRouters(rootCtx, entryPoints, tls) { logger := log.Ctx(rootCtx).With().Str(logs.EntryPointName, entryPointName).Logger() ctx := logger.WithContext(rootCtx) - handler, err := m.buildEntryPointHandler(ctx, entryPointName, routers) + // TODO: Improve this part. Relying on models is a shortcut to get the entrypoint observability configuration. Maybe we should pass down the static configuration. + // When the entry point has no observability configuration no model is produced, + // and we need to create the default configuration is this case. + epObsConfig := defaultObsConfig + if model, ok := m.conf.Models[entryPointName+"@internal"]; ok && model != nil { + epObsConfig = model.Observability + } + + handler, err := m.buildEntryPointHandler(ctx, entryPointName, routers, epObsConfig) if err != nil { logger.Error().Err(err).Send() continue @@ -93,7 +105,15 @@ func (m *Manager) BuildHandlers(rootCtx context.Context, entryPoints []string, t continue } - defaultHandler, err := m.observabilityMgr.BuildEPChain(ctx, entryPointName, "", nil).Then(BuildDefaultHTTPRouter()) + // TODO: Improve this part. Relying on models is a shortcut to get the entrypoint observability configuration. Maybe we should pass down the static configuration. + // When the entry point has no observability configuration no model is produced, + // and we need to create the default configuration is this case. + epObsConfig := defaultObsConfig + if model, ok := m.conf.Models[entryPointName+"@internal"]; ok && model != nil { + epObsConfig = model.Observability + } + + defaultHandler, err := m.observabilityMgr.BuildEPChain(ctx, entryPointName, false, epObsConfig).Then(http.NotFoundHandler()) if err != nil { logger.Error().Err(err).Send() continue @@ -104,10 +124,10 @@ func (m *Manager) BuildHandlers(rootCtx context.Context, entryPoints []string, t return entryPointHandlers } -func (m *Manager) buildEntryPointHandler(ctx context.Context, entryPointName string, configs map[string]*runtime.RouterInfo) (http.Handler, error) { +func (m *Manager) buildEntryPointHandler(ctx context.Context, entryPointName string, configs map[string]*runtime.RouterInfo, config dynamic.RouterObservabilityConfig) (http.Handler, error) { muxer := httpmuxer.NewMuxer(m.parser) - defaultHandler, err := m.observabilityMgr.BuildEPChain(ctx, entryPointName, "", nil).Then(http.NotFoundHandler()) + defaultHandler, err := m.observabilityMgr.BuildEPChain(ctx, entryPointName, false, config).Then(http.NotFoundHandler()) if err != nil { return nil, err } @@ -136,7 +156,11 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, entryPointName str continue } - observabilityChain := m.observabilityMgr.BuildEPChain(ctx, entryPointName, routerConfig.Service, routerConfig.Observability) + if routerConfig.Observability != nil { + config = *routerConfig.Observability + } + + observabilityChain := m.observabilityMgr.BuildEPChain(ctxRouter, entryPointName, strings.HasSuffix(routerConfig.Service, "@internal"), config) handler, err = observabilityChain.Then(handler) if err != nil { routerConfig.AddError(err, true) @@ -180,22 +204,7 @@ func (m *Manager) buildRouterHandler(ctx context.Context, routerName string, rou return nil, err } - // Prevents from enabling observability for internal resources. - if !m.observabilityMgr.ShouldAddAccessLogs(provider.GetQualifiedName(ctx, routerConfig.Service), routerConfig.Observability) { - m.routerHandlers[routerName] = handler - return m.routerHandlers[routerName], nil - } - - handlerWithAccessLog, err := alice.New(func(next http.Handler) (http.Handler, error) { - return accesslog.NewFieldHandler(next, accesslog.RouterName, routerName, nil), nil - }).Then(handler) - if err != nil { - log.Ctx(ctx).Error().Err(err).Send() - m.routerHandlers[routerName] = handler - } else { - m.routerHandlers[routerName] = handlerWithAccessLog - } - + m.routerHandlers[routerName] = handler return m.routerHandlers[routerName], nil } @@ -210,40 +219,29 @@ func (m *Manager) buildHTTPHandler(ctx context.Context, router *runtime.RouterIn return nil, errors.New("the service is missing on the router") } - sHandler, err := m.serviceManager.BuildHTTP(ctx, router.Service) - if err != nil { - return nil, err - } - - mHandler := m.middlewaresBuilder.BuildChain(ctx, router.Middlewares) + qualifiedService := provider.GetQualifiedName(ctx, router.Service) chain := alice.New() - if m.observabilityMgr.MetricsRegistry() != nil && m.observabilityMgr.MetricsRegistry().IsRouterEnabled() && - m.observabilityMgr.ShouldAddMetrics(provider.GetQualifiedName(ctx, router.Service), router.Observability) { - chain = chain.Append(metricsMiddle.WrapRouterHandler(ctx, m.observabilityMgr.MetricsRegistry(), routerName, provider.GetQualifiedName(ctx, router.Service))) - } - - // Prevents from enabling tracing for internal resources. - if !m.observabilityMgr.ShouldAddTracing(provider.GetQualifiedName(ctx, router.Service), router.Observability) { - return chain.Extend(*mHandler).Then(sHandler) - } - - chain = chain.Append(observability.WrapRouterHandler(ctx, routerName, router.Rule, provider.GetQualifiedName(ctx, router.Service))) - - if m.observabilityMgr.MetricsRegistry() != nil && m.observabilityMgr.MetricsRegistry().IsRouterEnabled() { - metricsHandler := metricsMiddle.WrapRouterHandler(ctx, m.observabilityMgr.MetricsRegistry(), routerName, provider.GetQualifiedName(ctx, router.Service)) - chain = chain.Append(observability.WrapMiddleware(ctx, metricsHandler)) - } - if router.DefaultRule { chain = chain.Append(denyrouterrecursion.WrapHandler(routerName)) } + // Access logs, metrics, and tracing middlewares are idempotent if the associated signal is disabled. + chain = chain.Append(observability.WrapRouterHandler(ctx, routerName, router.Rule, qualifiedService)) + metricsHandler := metricsMiddle.RouterMetricsHandler(ctx, m.observabilityMgr.MetricsRegistry(), routerName, qualifiedService) + + chain = chain.Append(observability.WrapMiddleware(ctx, metricsHandler)) + chain = chain.Append(func(next http.Handler) (http.Handler, error) { + return accesslog.NewFieldHandler(next, accesslog.RouterName, routerName, nil), nil + }) + + mHandler := m.middlewaresBuilder.BuildChain(ctx, router.Middlewares) + + sHandler, err := m.serviceManager.BuildHTTP(ctx, qualifiedService) + if err != nil { + return nil, err + } + return chain.Extend(*mHandler).Then(sHandler) } - -// BuildDefaultHTTPRouter creates a default HTTP router. -func BuildDefaultHTTPRouter() http.Handler { - return http.NotFoundHandler() -} diff --git a/pkg/server/router/router_test.go b/pkg/server/router/router_test.go index b73f3df5a..1458b2e99 100644 --- a/pkg/server/router/router_test.go +++ b/pkg/server/router/router_test.go @@ -1,7 +1,6 @@ package router import ( - "context" "crypto/tls" "io" "math" @@ -326,14 +325,14 @@ func TestRouterManager_Get(t *testing.T) { serviceManager := service.NewManager(rtConf.Services, nil, nil, transportManager, proxyBuilderMock{}) middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager, nil) - tlsManager := traefiktls.NewManager() + tlsManager := traefiktls.NewManager(nil) parser, err := httpmuxer.NewSyntaxParser() require.NoError(t, err) routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, nil, tlsManager, parser) - handlers := routerManager.BuildHandlers(context.Background(), test.entryPoints, false) + handlers := routerManager.BuildHandlers(t.Context(), test.entryPoints, false) w := httptest.NewRecorder() req := testhelpers.MustNewRequest(http.MethodGet, "http://foo.bar/", nil) @@ -713,16 +712,16 @@ func TestRuntimeConfiguration(t *testing.T) { serviceManager := service.NewManager(rtConf.Services, nil, nil, transportManager, proxyBuilderMock{}) middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager, nil) - tlsManager := traefiktls.NewManager() - tlsManager.UpdateConfigs(context.Background(), nil, test.tlsOptions, nil) + tlsManager := traefiktls.NewManager(nil) + tlsManager.UpdateConfigs(t.Context(), nil, test.tlsOptions, nil) parser, err := httpmuxer.NewSyntaxParser() require.NoError(t, err) routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, nil, tlsManager, parser) - _ = routerManager.BuildHandlers(context.Background(), entryPoints, false) - _ = routerManager.BuildHandlers(context.Background(), entryPoints, true) + _ = routerManager.BuildHandlers(t.Context(), entryPoints, false) + _ = routerManager.BuildHandlers(t.Context(), 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 @@ -795,14 +794,14 @@ func TestProviderOnMiddlewares(t *testing.T) { serviceManager := service.NewManager(rtConf.Services, nil, nil, transportManager, nil) middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager, nil) - tlsManager := traefiktls.NewManager() + tlsManager := traefiktls.NewManager(nil) parser, err := httpmuxer.NewSyntaxParser() require.NoError(t, err) routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, nil, tlsManager, parser) - _ = routerManager.BuildHandlers(context.Background(), entryPoints, false) + _ = routerManager.BuildHandlers(t.Context(), entryPoints, false) assert.Equal(t, []string{"chain@file", "m1@file"}, rtConf.Routers["router@file"].Middlewares) assert.Equal(t, []string{"m1@file", "m2@file", "m1@file"}, rtConf.Middlewares["chain@file"].Chain.Middlewares) @@ -874,14 +873,14 @@ func BenchmarkRouterServe(b *testing.B) { serviceManager := service.NewManager(rtConf.Services, nil, nil, staticTransportManager{res}, nil) middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager, nil) - tlsManager := traefiktls.NewManager() + tlsManager := traefiktls.NewManager(nil) parser, err := httpmuxer.NewSyntaxParser() require.NoError(b, err) routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, nil, tlsManager, parser) - handlers := routerManager.BuildHandlers(context.Background(), entryPoints, false) + handlers := routerManager.BuildHandlers(b.Context(), entryPoints, false) w := httptest.NewRecorder() req := testhelpers.MustNewRequest(http.MethodGet, "http://foo.bar/", nil) @@ -921,7 +920,7 @@ func BenchmarkService(b *testing.B) { w := httptest.NewRecorder() req := testhelpers.MustNewRequest(http.MethodGet, "http://foo.bar/", nil) - handler, _ := serviceManager.BuildHTTP(context.Background(), "foo-service") + handler, _ := serviceManager.BuildHTTP(b.Context(), "foo-service") b.ReportAllocs() for range b.N { handler.ServeHTTP(w, req) @@ -930,7 +929,7 @@ func BenchmarkService(b *testing.B) { type proxyBuilderMock struct{} -func (p proxyBuilderMock) Build(_ string, _ *url.URL, _, _, _ bool, _ time.Duration) (http.Handler, error) { +func (p proxyBuilderMock) Build(_ string, _ *url.URL, _, _ bool, _ time.Duration) (http.Handler, error) { return http.HandlerFunc(func(responseWriter http.ResponseWriter, req *http.Request) {}), nil } diff --git a/pkg/server/router/tcp/manager.go b/pkg/server/router/tcp/manager.go index 5966462cc..788bf7cb0 100644 --- a/pkg/server/router/tcp/manager.go +++ b/pkg/server/router/tcp/manager.go @@ -11,10 +11,10 @@ import ( "github.com/rs/zerolog/log" "github.com/traefik/traefik/v3/pkg/config/runtime" - "github.com/traefik/traefik/v3/pkg/logs" "github.com/traefik/traefik/v3/pkg/middlewares/snicheck" httpmuxer "github.com/traefik/traefik/v3/pkg/muxer/http" tcpmuxer "github.com/traefik/traefik/v3/pkg/muxer/tcp" + "github.com/traefik/traefik/v3/pkg/observability/logs" "github.com/traefik/traefik/v3/pkg/server/provider" tcpservice "github.com/traefik/traefik/v3/pkg/server/service/tcp" "github.com/traefik/traefik/v3/pkg/tcp" diff --git a/pkg/server/router/tcp/manager_test.go b/pkg/server/router/tcp/manager_test.go index 97cec0444..32b3218e0 100644 --- a/pkg/server/router/tcp/manager_test.go +++ b/pkg/server/router/tcp/manager_test.go @@ -1,7 +1,6 @@ package tcp import ( - "context" "crypto/tls" "math" "net/http" @@ -348,9 +347,9 @@ func TestRuntimeConfiguration(t *testing.T) { dialerManager := tcp2.NewDialerManager(nil) dialerManager.Update(map[string]*dynamic.TCPServersTransport{"default@internal": {}}) serviceManager := tcp.NewManager(conf, dialerManager) - tlsManager := traefiktls.NewManager() + tlsManager := traefiktls.NewManager(nil) tlsManager.UpdateConfigs( - context.Background(), + t.Context(), map[string]traefiktls.Store{}, map[string]traefiktls.Options{ "default": { @@ -370,7 +369,7 @@ func TestRuntimeConfiguration(t *testing.T) { routerManager := NewManager(conf, serviceManager, middlewaresBuilder, nil, nil, tlsManager) - _ = routerManager.BuildHandlers(context.Background(), entryPoints) + _ = routerManager.BuildHandlers(t.Context(), entryPoints) // even though conf was passed by argument to the manager builders above, // it's ok to use it as the result we check, because everything worth checking @@ -660,8 +659,8 @@ func TestDomainFronting(t *testing.T) { serviceManager := tcp.NewManager(conf, tcp2.NewDialerManager(nil)) - tlsManager := traefiktls.NewManager() - tlsManager.UpdateConfigs(context.Background(), map[string]traefiktls.Store{}, test.tlsOptions, []*traefiktls.CertAndStores{}) + tlsManager := traefiktls.NewManager(nil) + tlsManager.UpdateConfigs(t.Context(), map[string]traefiktls.Store{}, test.tlsOptions, []*traefiktls.CertAndStores{}) httpsHandler := map[string]http.Handler{ "web": http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {}), @@ -671,7 +670,7 @@ func TestDomainFronting(t *testing.T) { routerManager := NewManager(conf, serviceManager, middlewaresBuilder, nil, httpsHandler, tlsManager) - routers := routerManager.BuildHandlers(context.Background(), entryPoints) + routers := routerManager.BuildHandlers(t.Context(), entryPoints) router, ok := routers["web"] require.True(t, ok) diff --git a/pkg/server/router/tcp/router_test.go b/pkg/server/router/tcp/router_test.go index 16f633443..1c2875776 100644 --- a/pkg/server/router/tcp/router_test.go +++ b/pkg/server/router/tcp/router_test.go @@ -2,7 +2,6 @@ package tcp import ( "bytes" - "context" "crypto/tls" "errors" "fmt" @@ -173,9 +172,9 @@ func Test_Routing(t *testing.T) { require.NoError(t, err) // Creates the tlsManager and defines the TLS 1.0 and 1.2 TLSOptions. - tlsManager := traefiktls.NewManager() + tlsManager := traefiktls.NewManager(nil) tlsManager.UpdateConfigs( - context.Background(), + t.Context(), map[string]traefiktls.Store{ tlsalpn01.ACMETLS1Protocol: {}, }, @@ -606,7 +605,7 @@ func Test_Routing(t *testing.T) { router(dynConf) } - router, err := manager.buildEntryPointHandler(context.Background(), dynConf.TCPRouters, dynConf.Routers, nil, nil) + router, err := manager.buildEntryPointHandler(t.Context(), dynConf.TCPRouters, dynConf.Routers, nil, nil) require.NoError(t, err) if test.allowACMETLSPassthrough { diff --git a/pkg/server/router/udp/router.go b/pkg/server/router/udp/router.go index 8fb0f70d6..910efa499 100644 --- a/pkg/server/router/udp/router.go +++ b/pkg/server/router/udp/router.go @@ -7,7 +7,7 @@ import ( "github.com/rs/zerolog/log" "github.com/traefik/traefik/v3/pkg/config/runtime" - "github.com/traefik/traefik/v3/pkg/logs" + "github.com/traefik/traefik/v3/pkg/observability/logs" "github.com/traefik/traefik/v3/pkg/server/provider" udpservice "github.com/traefik/traefik/v3/pkg/server/service/udp" "github.com/traefik/traefik/v3/pkg/udp" diff --git a/pkg/server/router/udp/router_test.go b/pkg/server/router/udp/router_test.go index eaa99e174..9dfcaacab 100644 --- a/pkg/server/router/udp/router_test.go +++ b/pkg/server/router/udp/router_test.go @@ -1,7 +1,6 @@ package udp import ( - "context" "testing" "github.com/stretchr/testify/assert" @@ -118,7 +117,7 @@ func TestRuntimeConfiguration(t *testing.T) { serviceManager := udp.NewManager(conf) routerManager := NewManager(conf, serviceManager) - _ = routerManager.BuildHandlers(context.Background(), entryPoints) + _ = routerManager.BuildHandlers(t.Context(), entryPoints) // even though conf was passed by argument to the manager builders above, // it's ok to use it as the result we check, because everything worth checking diff --git a/pkg/server/routerfactory_test.go b/pkg/server/routerfactory_test.go index 648b24e8f..ba77f5e31 100644 --- a/pkg/server/routerfactory_test.go +++ b/pkg/server/routerfactory_test.go @@ -55,7 +55,7 @@ func TestReuseService(t *testing.T) { transportManager.Update(map[string]*dynamic.ServersTransport{"default@internal": {}}) managerFactory := service.NewManagerFactory(staticConfig, nil, nil, transportManager, proxyBuilderMock{}, nil) - tlsManager := tls.NewManager() + tlsManager := tls.NewManager(nil) dialerManager := tcp.NewDialerManager(nil) dialerManager.Update(map[string]*dynamic.TCPServersTransport{"default@internal": {}}) @@ -193,7 +193,7 @@ func TestServerResponseEmptyBackend(t *testing.T) { transportManager.Update(map[string]*dynamic.ServersTransport{"default@internal": {}}) managerFactory := service.NewManagerFactory(staticConfig, nil, nil, transportManager, proxyBuilderMock{}, nil) - tlsManager := tls.NewManager() + tlsManager := tls.NewManager(nil) dialerManager := tcp.NewDialerManager(nil) dialerManager.Update(map[string]*dynamic.TCPServersTransport{"default@internal": {}}) @@ -239,7 +239,7 @@ func TestInternalServices(t *testing.T) { transportManager.Update(map[string]*dynamic.ServersTransport{"default@internal": {}}) managerFactory := service.NewManagerFactory(staticConfig, nil, nil, transportManager, nil, nil) - tlsManager := tls.NewManager() + tlsManager := tls.NewManager(nil) dialerManager := tcp.NewDialerManager(nil) dialerManager.Update(map[string]*dynamic.TCPServersTransport{"default@internal": {}}) @@ -258,7 +258,7 @@ func TestInternalServices(t *testing.T) { type proxyBuilderMock struct{} -func (p proxyBuilderMock) Build(_ string, _ *url.URL, _, _, _ bool, _ time.Duration) (http.Handler, error) { +func (p proxyBuilderMock) Build(_ string, _ *url.URL, _, _ bool, _ time.Duration) (http.Handler, error) { return http.HandlerFunc(func(responseWriter http.ResponseWriter, req *http.Request) {}), nil } diff --git a/pkg/server/server.go b/pkg/server/server.go index 9c96bdcc6..72af729e6 100644 --- a/pkg/server/server.go +++ b/pkg/server/server.go @@ -8,7 +8,7 @@ import ( "time" "github.com/rs/zerolog/log" - "github.com/traefik/traefik/v3/pkg/metrics" + "github.com/traefik/traefik/v3/pkg/observability/metrics" "github.com/traefik/traefik/v3/pkg/safe" "github.com/traefik/traefik/v3/pkg/server/middleware" ) diff --git a/pkg/server/server_entrypoint_listenconfig_other_test.go b/pkg/server/server_entrypoint_listenconfig_other_test.go index b143db1c6..45fa5ec47 100644 --- a/pkg/server/server_entrypoint_listenconfig_other_test.go +++ b/pkg/server/server_entrypoint_listenconfig_other_test.go @@ -16,12 +16,12 @@ func TestNewListenConfig(t *testing.T) { require.Nil(t, listenConfig.Control) require.Zero(t, listenConfig.KeepAlive) - l1, err := listenConfig.Listen(context.Background(), "tcp", ep.Address) + l1, err := listenConfig.Listen(t.Context(), "tcp", ep.Address) require.NoError(t, err) require.NotNil(t, l1) defer l1.Close() - l2, err := listenConfig.Listen(context.Background(), "tcp", l1.Addr().String()) + l2, err := listenConfig.Listen(t.Context(), "tcp", l1.Addr().String()) require.Error(t, err) require.ErrorContains(t, err, "address already in use") require.Nil(t, l2) @@ -31,12 +31,12 @@ func TestNewListenConfig(t *testing.T) { require.Nil(t, listenConfig.Control) require.Zero(t, listenConfig.KeepAlive) - l3, err := listenConfig.Listen(context.Background(), "tcp", ep.Address) + l3, err := listenConfig.Listen(t.Context(), "tcp", ep.Address) require.NoError(t, err) require.NotNil(t, l3) defer l3.Close() - l4, err := listenConfig.Listen(context.Background(), "tcp", l3.Addr().String()) + l4, err := listenConfig.Listen(t.Context(), "tcp", l3.Addr().String()) require.Error(t, err) require.ErrorContains(t, err, "address already in use") require.Nil(t, l4) diff --git a/pkg/server/server_entrypoint_listenconfig_unix_test.go b/pkg/server/server_entrypoint_listenconfig_unix_test.go index a5f7dda0a..98aab2fea 100644 --- a/pkg/server/server_entrypoint_listenconfig_unix_test.go +++ b/pkg/server/server_entrypoint_listenconfig_unix_test.go @@ -3,7 +3,6 @@ package server import ( - "context" "net" "testing" @@ -17,12 +16,12 @@ func TestNewListenConfig(t *testing.T) { require.Nil(t, listenConfig.Control) require.Zero(t, listenConfig.KeepAlive) - l1, err := listenConfig.Listen(context.Background(), "tcp", ep.Address) + l1, err := listenConfig.Listen(t.Context(), "tcp", ep.Address) require.NoError(t, err) require.NotNil(t, l1) defer l1.Close() - l2, err := listenConfig.Listen(context.Background(), "tcp", l1.Addr().String()) + l2, err := listenConfig.Listen(t.Context(), "tcp", l1.Addr().String()) require.Error(t, err) require.ErrorContains(t, err, "address already in use") require.Nil(t, l2) @@ -32,24 +31,24 @@ func TestNewListenConfig(t *testing.T) { require.NotNil(t, listenConfig.Control) require.Zero(t, listenConfig.KeepAlive) - l3, err := listenConfig.Listen(context.Background(), "tcp", ep.Address) + l3, err := listenConfig.Listen(t.Context(), "tcp", ep.Address) require.NoError(t, err) require.NotNil(t, l3) defer l3.Close() - l4, err := listenConfig.Listen(context.Background(), "tcp", l3.Addr().String()) + l4, err := listenConfig.Listen(t.Context(), "tcp", l3.Addr().String()) require.NoError(t, err) require.NotNil(t, l4) defer l4.Close() _, l3Port, err := net.SplitHostPort(l3.Addr().String()) require.NoError(t, err) - l5, err := listenConfig.Listen(context.Background(), "tcp", "127.0.0.1:"+l3Port) + l5, err := listenConfig.Listen(t.Context(), "tcp", "127.0.0.1:"+l3Port) require.NoError(t, err) require.NotNil(t, l5) defer l5.Close() - l6, err := listenConfig.Listen(context.Background(), "tcp", l1.Addr().String()) + l6, err := listenConfig.Listen(t.Context(), "tcp", l1.Addr().String()) require.Error(t, err) require.ErrorContains(t, err, "address already in use") require.Nil(t, l6) diff --git a/pkg/server/server_entrypoint_tcp.go b/pkg/server/server_entrypoint_tcp.go index 039d06659..aef3954bf 100644 --- a/pkg/server/server_entrypoint_tcp.go +++ b/pkg/server/server_entrypoint_tcp.go @@ -12,6 +12,7 @@ import ( "os" "strings" "sync" + "sync/atomic" "syscall" "time" @@ -22,20 +23,17 @@ import ( "github.com/rs/zerolog/log" "github.com/traefik/traefik/v3/pkg/config/static" "github.com/traefik/traefik/v3/pkg/ip" - "github.com/traefik/traefik/v3/pkg/logs" - "github.com/traefik/traefik/v3/pkg/metrics" "github.com/traefik/traefik/v3/pkg/middlewares" "github.com/traefik/traefik/v3/pkg/middlewares/contenttype" "github.com/traefik/traefik/v3/pkg/middlewares/forwardedheaders" "github.com/traefik/traefik/v3/pkg/middlewares/requestdecorator" + "github.com/traefik/traefik/v3/pkg/observability/logs" + "github.com/traefik/traefik/v3/pkg/observability/metrics" "github.com/traefik/traefik/v3/pkg/safe" - "github.com/traefik/traefik/v3/pkg/server/router" tcprouter "github.com/traefik/traefik/v3/pkg/server/router/tcp" "github.com/traefik/traefik/v3/pkg/server/service" "github.com/traefik/traefik/v3/pkg/tcp" "github.com/traefik/traefik/v3/pkg/types" - "golang.org/x/net/http2" - "golang.org/x/net/http2/h2c" ) type key string @@ -59,15 +57,19 @@ type connState struct { type httpForwarder struct { net.Listener - connChan chan net.Conn - errChan chan error + + connChan chan net.Conn + errChan chan error + closeChan chan struct{} + closeOnce sync.Once } func newHTTPForwarder(ln net.Listener) *httpForwarder { return &httpForwarder{ - Listener: ln, - connChan: make(chan net.Conn), - errChan: make(chan error), + Listener: ln, + connChan: make(chan net.Conn), + errChan: make(chan error), + closeChan: make(chan struct{}), } } @@ -79,6 +81,8 @@ func (h *httpForwarder) ServeTCP(conn tcp.WriteCloser) { // Accept retrieves a served connection in ServeTCP. func (h *httpForwarder) Accept() (net.Conn, error) { select { + case <-h.closeChan: + return nil, errors.New("listener closed") case conn := <-h.connChan: return conn, nil case err := <-h.errChan: @@ -86,6 +90,14 @@ func (h *httpForwarder) Accept() (net.Conn, error) { } } +// Close closes the wrapped listener and unblocks Accept. +func (h *httpForwarder) Close() error { + h.closeOnce.Do(func() { + close(h.closeChan) + }) + return h.Listener.Close() +} + // TCPEntryPoints holds a map of TCPEntryPoint (the entrypoint names being the keys). type TCPEntryPoints map[string]*TCPEntryPoint @@ -165,8 +177,9 @@ type TCPEntryPoint struct { tracker *connectionTracker httpServer *httpServer httpsServer *httpServer - - http3Server *http3server + http3Server *http3server + // inShutdown reports whether the Shutdown method has been called. + inShutdown atomic.Bool } // NewTCPEntryPoint creates a new TCPEntryPoint. @@ -175,31 +188,31 @@ func NewTCPEntryPoint(ctx context.Context, name string, config *static.EntryPoin listener, err := buildListener(ctx, name, config) if err != nil { - return nil, fmt.Errorf("error preparing server: %w", err) + return nil, fmt.Errorf("building listener: %w", err) } rt, err := tcprouter.NewRouter() if err != nil { - return nil, fmt.Errorf("error preparing tcp router: %w", err) + return nil, fmt.Errorf("creating TCP router: %w", err) } reqDecorator := requestdecorator.New(hostResolverConfig) - httpServer, err := createHTTPServer(ctx, listener, config, true, reqDecorator) + httpServer, err := newHTTPServer(ctx, listener, config, true, reqDecorator) if err != nil { - return nil, fmt.Errorf("error preparing http server: %w", err) + return nil, fmt.Errorf("creating HTTP server: %w", err) } rt.SetHTTPForwarder(httpServer.Forwarder) - httpsServer, err := createHTTPServer(ctx, listener, config, false, reqDecorator) + httpsServer, err := newHTTPServer(ctx, listener, config, false, reqDecorator) if err != nil { - return nil, fmt.Errorf("error preparing https server: %w", err) + return nil, fmt.Errorf("creating HTTPS server: %w", err) } h3Server, err := newHTTP3Server(ctx, name, config, httpsServer) if err != nil { - return nil, fmt.Errorf("error preparing http3 server: %w", err) + return nil, fmt.Errorf("creating HTTP3 server: %w", err) } rt.SetHTTPSForwarder(httpsServer.Forwarder) @@ -229,6 +242,11 @@ func (e *TCPEntryPoint) Start(ctx context.Context) { for { conn, err := e.listener.Accept() + // As the Shutdown method has been called, an error is expected. + // Thus, it is not necessary to log it. + if err != nil && e.inShutdown.Load() { + return + } if err != nil { logger.Error().Err(err).Send() @@ -280,6 +298,8 @@ func (e *TCPEntryPoint) Start(ctx context.Context) { func (e *TCPEntryPoint) Shutdown(ctx context.Context) { logger := log.Ctx(ctx) + e.inShutdown.Store(true) + reqAcceptGraceTimeOut := time.Duration(e.transportConfiguration.LifeCycle.RequestAcceptGraceTimeout) if reqAcceptGraceTimeOut > 0 { logger.Info().Msgf("Waiting %s for incoming requests to cease", reqAcceptGraceTimeOut) @@ -353,7 +373,7 @@ func (e *TCPEntryPoint) SwitchRouter(rt *tcprouter.Router) { httpHandler := rt.GetHTTPHandler() if httpHandler == nil { - httpHandler = router.BuildDefaultHTTPRouter() + httpHandler = http.NotFoundHandler() } e.httpServer.Switcher.UpdateHandler(httpHandler) @@ -362,7 +382,7 @@ func (e *TCPEntryPoint) SwitchRouter(rt *tcprouter.Router) { httpsHandler := rt.GetHTTPSHandler() if httpsHandler == nil { - httpsHandler = router.BuildDefaultHTTPRouter() + httpsHandler = http.NotFoundHandler() } e.httpsServer.Switcher.UpdateHandler(httpsHandler) @@ -464,6 +484,18 @@ func buildProxyProtocolListener(ctx context.Context, entryPoint *static.EntryPoi return proxyListener, nil } +type onceCloseListener struct { + net.Listener + + once sync.Once + closeErr error +} + +func (oc *onceCloseListener) Close() error { + oc.once.Do(func() { oc.closeErr = oc.Listener.Close() }) + return oc.closeErr +} + func buildListener(ctx context.Context, name string, config *static.EntryPoint) (net.Listener, error) { var listener net.Listener var err error @@ -478,6 +510,13 @@ func buildListener(ctx context.Context, name string, config *static.EntryPoint) if listener == nil { listenConfig := newListenConfig(config) + + // TODO: Look into configuring keepAlive period through listenConfig instead of our custom tcpKeepAliveListener, to reactivate MultipathTCP? + // MultipathTCP is not supported on all platforms, and is notably unsupported in combination with TCP keep-alive. + if !strings.Contains(os.Getenv("GODEBUG"), "multipathtcp") { + listenConfig.SetMultipathTCP(false) + } + listener, err = listenConfig.Listen(ctx, "tcp", config.GetAddress()) if err != nil { return nil, fmt.Errorf("error opening listener: %w", err) @@ -492,7 +531,7 @@ func buildListener(ctx context.Context, name string, config *static.EntryPoint) return nil, fmt.Errorf("error creating proxy protocol listener: %w", err) } } - return listener, nil + return &onceCloseListener{Listener: listener}, nil } func newConnectionTracker(openConnectionsGauge gokitmetrics.Gauge) *connectionTracker { @@ -588,12 +627,12 @@ type httpServer struct { Switcher *middlewares.HTTPHandlerSwitcher } -func createHTTPServer(ctx context.Context, ln net.Listener, configuration *static.EntryPoint, withH2c bool, reqDecorator *requestdecorator.RequestDecorator) (*httpServer, error) { +func newHTTPServer(ctx context.Context, ln net.Listener, configuration *static.EntryPoint, withH2c bool, reqDecorator *requestdecorator.RequestDecorator) (*httpServer, error) { if configuration.HTTP2.MaxConcurrentStreams < 0 { return nil, errors.New("max concurrent streams value must be greater than or equal to zero") } - httpSwitcher := middlewares.NewHandlerSwitcher(router.BuildDefaultHTTPRouter()) + httpSwitcher := middlewares.NewHandlerSwitcher(http.NotFoundHandler()) next, err := alice.New(requestdecorator.WrapHandler(reqDecorator)).Then(httpSwitcher) if err != nil { @@ -615,11 +654,12 @@ func createHTTPServer(ctx context.Context, ln net.Listener, configuration *stati handler = newKeepAliveMiddleware(handler, configuration.Transport.KeepAliveMaxRequests, configuration.Transport.KeepAliveMaxTime) } - if withH2c { - handler = h2c.NewHandler(handler, &http2.Server{ - MaxConcurrentStreams: uint32(configuration.HTTP2.MaxConcurrentStreams), - }) - } + var protocols http.Protocols + protocols.SetHTTP1(true) + protocols.SetHTTP2(true) + + // With the addition of UnencryptedHTTP2 in http.Server#Protocols in go1.24 setting the h2c handler is not necessary anymore. + protocols.SetUnencryptedHTTP2(withH2c) handler = contenttype.DisableAutoDetection(handler) @@ -640,12 +680,16 @@ func createHTTPServer(ctx context.Context, ln net.Listener, configuration *stati handler = denyFragment(handler) serverHTTP := &http.Server{ + Protocols: &protocols, Handler: handler, ErrorLog: stdlog.New(logs.NoLevel(log.Logger, zerolog.DebugLevel), "", 0), ReadTimeout: time.Duration(configuration.Transport.RespondingTimeouts.ReadTimeout), WriteTimeout: time.Duration(configuration.Transport.RespondingTimeouts.WriteTimeout), IdleTimeout: time.Duration(configuration.Transport.RespondingTimeouts.IdleTimeout), MaxHeaderBytes: configuration.HTTP.MaxHeaderBytes, + HTTP2: &http.HTTP2Config{ + MaxConcurrentStreams: int(configuration.HTTP2.MaxConcurrentStreams), + }, } if debugConnection || (configuration.Transport != nil && (configuration.Transport.KeepAliveMaxTime > 0 || configuration.Transport.KeepAliveMaxRequests > 0)) { serverHTTP.ConnContext = func(ctx context.Context, c net.Conn) context.Context { @@ -679,24 +723,11 @@ func createHTTPServer(ctx context.Context, ln net.Listener, configuration *stati return ctx } - // ConfigureServer configures HTTP/2 with the MaxConcurrentStreams option for the given server. - // Also keeping behavior the same as - // https://cs.opensource.google/go/go/+/refs/tags/go1.17.7:src/net/http/server.go;l=3262 - if !strings.Contains(os.Getenv("GODEBUG"), "http2server=0") { - err = http2.ConfigureServer(serverHTTP, &http2.Server{ - MaxConcurrentStreams: uint32(configuration.HTTP2.MaxConcurrentStreams), - NewWriteScheduler: func() http2.WriteScheduler { return http2.NewPriorityWriteScheduler(nil) }, - }) - if err != nil { - return nil, fmt.Errorf("configure HTTP/2 server: %w", err) - } - } - listener := newHTTPForwarder(ln) go func() { err := serverHTTP.Serve(listener) if err != nil && !errors.Is(err, http.ErrServerClosed) { - log.Ctx(ctx).Error().Err(err).Msg("Error while starting server") + log.Ctx(ctx).Error().Err(err).Msg("Error while running HTTP server") } }() return &httpServer{ diff --git a/pkg/server/server_entrypoint_tcp_http3_test.go b/pkg/server/server_entrypoint_tcp_http3_test.go index cc6b18082..746821958 100644 --- a/pkg/server/server_entrypoint_tcp_http3_test.go +++ b/pkg/server/server_entrypoint_tcp_http3_test.go @@ -2,7 +2,6 @@ package server import ( "bufio" - "context" "crypto/tls" "crypto/x509" "net/http" @@ -87,7 +86,7 @@ func TestHTTP3AdvertisedPort(t *testing.T) { epConfig := &static.EntryPointsTransport{} epConfig.SetDefaults() - entryPoint, err := NewTCPEntryPoint(context.Background(), "foo", &static.EntryPoint{ + entryPoint, err := NewTCPEntryPoint(t.Context(), "foo", &static.EntryPoint{ Address: "127.0.0.1:0", Transport: epConfig, ForwardedHeaders: &static.ForwardedHeaders{}, @@ -108,7 +107,7 @@ func TestHTTP3AdvertisedPort(t *testing.T) { rw.WriteHeader(http.StatusOK) }), nil) - ctx := context.Background() + ctx := t.Context() go entryPoint.Start(ctx) entryPoint.SwitchRouter(router) @@ -151,7 +150,7 @@ func TestHTTP30RTT(t *testing.T) { epConfig := &static.EntryPointsTransport{} epConfig.SetDefaults() - entryPoint, err := NewTCPEntryPoint(context.Background(), "foo", &static.EntryPoint{ + entryPoint, err := NewTCPEntryPoint(t.Context(), "foo", &static.EntryPoint{ Address: "127.0.0.1:8090", Transport: epConfig, ForwardedHeaders: &static.ForwardedHeaders{}, @@ -170,7 +169,7 @@ func TestHTTP30RTT(t *testing.T) { rw.WriteHeader(http.StatusOK) }), nil) - ctx := context.Background() + ctx := t.Context() go entryPoint.Start(ctx) entryPoint.SwitchRouter(router) @@ -193,7 +192,7 @@ func TestHTTP30RTT(t *testing.T) { tlsConf.ClientSessionCache = cache // This first DialAddrEarly connection is here to populate the cache. - earlyConnection, err := quic.DialAddrEarly(context.Background(), "127.0.0.1:8090", tlsConf, &quic.Config{}) + earlyConnection, err := quic.DialAddrEarly(t.Context(), "127.0.0.1:8090", tlsConf, &quic.Config{}) require.NoError(t, err) t.Cleanup(func() { @@ -207,7 +206,7 @@ func TestHTTP30RTT(t *testing.T) { // 0RTT is always false on the first connection. require.False(t, earlyConnection.ConnectionState().Used0RTT) - earlyConnection, err = quic.DialAddrEarly(context.Background(), "127.0.0.1:8090", tlsConf, &quic.Config{}) + earlyConnection, err = quic.DialAddrEarly(t.Context(), "127.0.0.1:8090", tlsConf, &quic.Config{}) require.NoError(t, err) <-earlyConnection.HandshakeComplete() diff --git a/pkg/server/server_entrypoint_tcp_test.go b/pkg/server/server_entrypoint_tcp_test.go index ecb0c06ae..8170234a7 100644 --- a/pkg/server/server_entrypoint_tcp_test.go +++ b/pkg/server/server_entrypoint_tcp_test.go @@ -80,7 +80,7 @@ func testShutdown(t *testing.T, router *tcprouter.Router) { epConfig.RespondingTimeouts.ReadTimeout = ptypes.Duration(5 * time.Second) epConfig.RespondingTimeouts.WriteTimeout = ptypes.Duration(5 * time.Second) - entryPoint, err := NewTCPEntryPoint(context.Background(), "", &static.EntryPoint{ + entryPoint, err := NewTCPEntryPoint(t.Context(), "", &static.EntryPoint{ // We explicitly use an IPV4 address because on Alpine, with an IPV6 address // there seems to be shenanigans related to properly cleaning up file descriptors Address: "127.0.0.1:0", @@ -90,7 +90,7 @@ func testShutdown(t *testing.T, router *tcprouter.Router) { }, nil, nil) require.NoError(t, err) - conn, err := startEntrypoint(entryPoint, router) + conn, err := startEntrypoint(t, entryPoint, router) require.NoError(t, err) t.Cleanup(func() { _ = conn.Close() }) @@ -113,7 +113,7 @@ func testShutdown(t *testing.T, router *tcprouter.Router) { _, err = reader.Peek(1) require.NoError(t, err) - go entryPoint.Shutdown(context.Background()) + go entryPoint.Shutdown(t.Context()) // Make sure that new connections are not permitted anymore. // Note that this should be true not only after Shutdown has returned, @@ -144,8 +144,10 @@ func testShutdown(t *testing.T, router *tcprouter.Router) { assert.Equal(t, http.StatusOK, resp.StatusCode) } -func startEntrypoint(entryPoint *TCPEntryPoint, router *tcprouter.Router) (net.Conn, error) { - go entryPoint.Start(context.Background()) +func startEntrypoint(t *testing.T, entryPoint *TCPEntryPoint, router *tcprouter.Router) (net.Conn, error) { + t.Helper() + + go entryPoint.Start(t.Context()) entryPoint.SwitchRouter(router) @@ -167,7 +169,7 @@ func TestReadTimeoutWithoutFirstByte(t *testing.T) { epConfig.SetDefaults() epConfig.RespondingTimeouts.ReadTimeout = ptypes.Duration(2 * time.Second) - entryPoint, err := NewTCPEntryPoint(context.Background(), "", &static.EntryPoint{ + entryPoint, err := NewTCPEntryPoint(t.Context(), "", &static.EntryPoint{ Address: ":0", Transport: epConfig, ForwardedHeaders: &static.ForwardedHeaders{}, @@ -182,7 +184,7 @@ func TestReadTimeoutWithoutFirstByte(t *testing.T) { rw.WriteHeader(http.StatusOK) })) - conn, err := startEntrypoint(entryPoint, router) + conn, err := startEntrypoint(t, entryPoint, router) require.NoError(t, err) errChan := make(chan error) @@ -206,7 +208,7 @@ func TestReadTimeoutWithFirstByte(t *testing.T) { epConfig.SetDefaults() epConfig.RespondingTimeouts.ReadTimeout = ptypes.Duration(2 * time.Second) - entryPoint, err := NewTCPEntryPoint(context.Background(), "", &static.EntryPoint{ + entryPoint, err := NewTCPEntryPoint(t.Context(), "", &static.EntryPoint{ Address: ":0", Transport: epConfig, ForwardedHeaders: &static.ForwardedHeaders{}, @@ -221,7 +223,7 @@ func TestReadTimeoutWithFirstByte(t *testing.T) { rw.WriteHeader(http.StatusOK) })) - conn, err := startEntrypoint(entryPoint, router) + conn, err := startEntrypoint(t, entryPoint, router) require.NoError(t, err) _, err = conn.Write([]byte("GET /some HTTP/1.1\r\n")) @@ -248,7 +250,7 @@ func TestKeepAliveMaxRequests(t *testing.T) { epConfig.SetDefaults() epConfig.KeepAliveMaxRequests = 3 - entryPoint, err := NewTCPEntryPoint(context.Background(), "", &static.EntryPoint{ + entryPoint, err := NewTCPEntryPoint(t.Context(), "", &static.EntryPoint{ Address: ":0", Transport: epConfig, ForwardedHeaders: &static.ForwardedHeaders{}, @@ -263,7 +265,7 @@ func TestKeepAliveMaxRequests(t *testing.T) { rw.WriteHeader(http.StatusOK) })) - conn, err := startEntrypoint(entryPoint, router) + conn, err := startEntrypoint(t, entryPoint, router) require.NoError(t, err) http.DefaultClient.Transport = &http.Transport{ @@ -296,7 +298,7 @@ func TestKeepAliveMaxTime(t *testing.T) { epConfig.SetDefaults() epConfig.KeepAliveMaxTime = ptypes.Duration(time.Millisecond) - entryPoint, err := NewTCPEntryPoint(context.Background(), "", &static.EntryPoint{ + entryPoint, err := NewTCPEntryPoint(t.Context(), "", &static.EntryPoint{ Address: ":0", Transport: epConfig, ForwardedHeaders: &static.ForwardedHeaders{}, @@ -311,7 +313,7 @@ func TestKeepAliveMaxTime(t *testing.T) { rw.WriteHeader(http.StatusOK) })) - conn, err := startEntrypoint(entryPoint, router) + conn, err := startEntrypoint(t, entryPoint, router) require.NoError(t, err) http.DefaultClient.Transport = &http.Transport{ @@ -340,7 +342,7 @@ func TestKeepAliveH2c(t *testing.T) { epConfig.SetDefaults() epConfig.KeepAliveMaxRequests = 1 - entryPoint, err := NewTCPEntryPoint(context.Background(), "", &static.EntryPoint{ + entryPoint, err := NewTCPEntryPoint(t.Context(), "", &static.EntryPoint{ Address: ":0", Transport: epConfig, ForwardedHeaders: &static.ForwardedHeaders{}, @@ -355,7 +357,7 @@ func TestKeepAliveH2c(t *testing.T) { rw.WriteHeader(http.StatusOK) })) - conn, err := startEntrypoint(entryPoint, router) + conn, err := startEntrypoint(t, entryPoint, router) require.NoError(t, err) http2Transport := &http2.Transport{ @@ -509,7 +511,7 @@ func TestNormalizePath_malformedPercentEncoding(t *testing.T) { } } -// TestPathOperations tests the whole behavior of normalizePath, and sanitizePath combined through the use of the createHTTPServer func. +// TestPathOperations tests the whole behavior of normalizePath, and sanitizePath combined through the use of the newHTTPServer func. // It aims to guarantee the server entrypoint handler is secure regarding a large variety of cases that could lead to path traversal attacks. func TestPathOperations(t *testing.T) { // Create a listener for the server. @@ -523,8 +525,8 @@ func TestPathOperations(t *testing.T) { configuration := &static.EntryPoint{} configuration.SetDefaults() - // Create the HTTP server using createHTTPServer. - server, err := createHTTPServer(context.Background(), ln, configuration, false, requestdecorator.New(nil)) + // Create the HTTP server using newHTTPServer. + server, err := newHTTPServer(t.Context(), ln, configuration, false, requestdecorator.New(nil)) require.NoError(t, err) server.Switcher.UpdateHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { diff --git a/pkg/server/server_entrypoint_udp.go b/pkg/server/server_entrypoint_udp.go index 00a798f22..2d7250496 100644 --- a/pkg/server/server_entrypoint_udp.go +++ b/pkg/server/server_entrypoint_udp.go @@ -8,7 +8,7 @@ import ( "github.com/rs/zerolog/log" "github.com/traefik/traefik/v3/pkg/config/static" - "github.com/traefik/traefik/v3/pkg/logs" + "github.com/traefik/traefik/v3/pkg/observability/logs" "github.com/traefik/traefik/v3/pkg/udp" ) diff --git a/pkg/server/server_entrypoint_udp_test.go b/pkg/server/server_entrypoint_udp_test.go index dea497938..c66a7d9dc 100644 --- a/pkg/server/server_entrypoint_udp_test.go +++ b/pkg/server/server_entrypoint_udp_test.go @@ -1,7 +1,6 @@ package server import ( - "context" "io" "net" "testing" @@ -27,7 +26,7 @@ func TestShutdownUDPConn(t *testing.T) { entryPoint, err := NewUDPEntryPoint(&ep, "") require.NoError(t, err) - go entryPoint.Start(context.Background()) + go entryPoint.Start(t.Context()) entryPoint.Switch(udp.HandlerFunc(func(conn *udp.Conn) { for { b := make([]byte, 1024*1024) @@ -54,12 +53,18 @@ func TestShutdownUDPConn(t *testing.T) { // Start sending packets, to create a "session" with the server. requireEcho(t, "TEST", conn, time.Second) + shutdownStartedChan := make(chan struct{}) doneChan := make(chan struct{}) go func() { - entryPoint.Shutdown(context.Background()) + close(shutdownStartedChan) + entryPoint.Shutdown(t.Context()) close(doneChan) }() + // Wait until shutdown has started, and hopefully after 100 ms the listener has stopped accepting new sessions. + <-shutdownStartedChan + time.Sleep(100 * time.Millisecond) + // Make sure that our session is still live even after the shutdown. requireEcho(t, "TEST2", conn, time.Second) diff --git a/pkg/server/service/loadbalancer/failover/failover_test.go b/pkg/server/service/loadbalancer/failover/failover_test.go index a2f7f89f0..3d357017e 100644 --- a/pkg/server/service/loadbalancer/failover/failover_test.go +++ b/pkg/server/service/loadbalancer/failover/failover_test.go @@ -1,7 +1,6 @@ package failover import ( - "context" "net/http" "net/http/httptest" "testing" @@ -51,7 +50,7 @@ func TestFailover(t *testing.T) { assert.Equal(t, []int{200}, recorder.status) assert.True(t, status) - failover.SetHandlerStatus(context.Background(), false) + failover.SetHandlerStatus(t.Context(), false) recorder = &responseRecorder{ResponseRecorder: httptest.NewRecorder(), save: map[string]int{}} failover.ServeHTTP(recorder, httptest.NewRequest(http.MethodGet, "/", nil)) @@ -61,7 +60,7 @@ func TestFailover(t *testing.T) { assert.Equal(t, []int{200}, recorder.status) assert.True(t, status) - failover.SetFallbackHandlerStatus(context.Background(), false) + failover.SetFallbackHandlerStatus(t.Context(), false) recorder = &responseRecorder{ResponseRecorder: httptest.NewRecorder(), save: map[string]int{}} failover.ServeHTTP(recorder, httptest.NewRequest(http.MethodGet, "/", nil)) @@ -92,7 +91,7 @@ func TestFailoverDownThenUp(t *testing.T) { assert.Equal(t, 0, recorder.save["fallback"]) assert.Equal(t, []int{200}, recorder.status) - failover.SetHandlerStatus(context.Background(), false) + failover.SetHandlerStatus(t.Context(), false) recorder = &responseRecorder{ResponseRecorder: httptest.NewRecorder(), save: map[string]int{}} failover.ServeHTTP(recorder, httptest.NewRequest(http.MethodGet, "/", nil)) @@ -101,7 +100,7 @@ func TestFailoverDownThenUp(t *testing.T) { assert.Equal(t, 1, recorder.save["fallback"]) assert.Equal(t, []int{200}, recorder.status) - failover.SetHandlerStatus(context.Background(), true) + failover.SetHandlerStatus(t.Context(), true) recorder = &responseRecorder{ResponseRecorder: httptest.NewRecorder(), save: map[string]int{}} failover.ServeHTTP(recorder, httptest.NewRequest(http.MethodGet, "/", nil)) @@ -129,7 +128,7 @@ func TestFailoverPropagate(t *testing.T) { rw.WriteHeader(http.StatusOK) })) err := failover.RegisterStatusUpdater(func(up bool) { - topFailover.SetHandlerStatus(context.Background(), up) + topFailover.SetHandlerStatus(t.Context(), up) }) require.NoError(t, err) @@ -141,7 +140,7 @@ func TestFailoverPropagate(t *testing.T) { assert.Equal(t, 0, recorder.save["topFailover"]) assert.Equal(t, []int{200}, recorder.status) - failover.SetHandlerStatus(context.Background(), false) + failover.SetHandlerStatus(t.Context(), false) recorder = &responseRecorder{ResponseRecorder: httptest.NewRecorder(), save: map[string]int{}} topFailover.ServeHTTP(recorder, httptest.NewRequest(http.MethodGet, "/", nil)) @@ -151,7 +150,7 @@ func TestFailoverPropagate(t *testing.T) { assert.Equal(t, 0, recorder.save["topFailover"]) assert.Equal(t, []int{200}, recorder.status) - failover.SetFallbackHandlerStatus(context.Background(), false) + failover.SetFallbackHandlerStatus(t.Context(), false) recorder = &responseRecorder{ResponseRecorder: httptest.NewRecorder(), save: map[string]int{}} topFailover.ServeHTTP(recorder, httptest.NewRequest(http.MethodGet, "/", nil)) diff --git a/pkg/server/service/loadbalancer/mirror/mirror_test.go b/pkg/server/service/loadbalancer/mirror/mirror_test.go index 94a4c62a0..515bafdb7 100644 --- a/pkg/server/service/loadbalancer/mirror/mirror_test.go +++ b/pkg/server/service/loadbalancer/mirror/mirror_test.go @@ -2,7 +2,6 @@ package mirror import ( "bytes" - "context" "io" "net/http" "net/http/httptest" @@ -20,7 +19,7 @@ func TestMirroringOn100(t *testing.T) { handler := http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { rw.WriteHeader(http.StatusOK) }) - pool := safe.NewPool(context.Background()) + pool := safe.NewPool(t.Context()) mirror := New(handler, pool, true, defaultMaxBodySize, nil) err := mirror.AddMirror(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { atomic.AddInt32(&countMirror1, 1) @@ -49,7 +48,7 @@ func TestMirroringOn10(t *testing.T) { handler := http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { rw.WriteHeader(http.StatusOK) }) - pool := safe.NewPool(context.Background()) + pool := safe.NewPool(t.Context()) mirror := New(handler, pool, true, defaultMaxBodySize, nil) err := mirror.AddMirror(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { atomic.AddInt32(&countMirror1, 1) @@ -74,7 +73,7 @@ func TestMirroringOn10(t *testing.T) { } func TestInvalidPercent(t *testing.T) { - mirror := New(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {}), safe.NewPool(context.Background()), true, defaultMaxBodySize, nil) + mirror := New(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {}), safe.NewPool(t.Context()), true, defaultMaxBodySize, nil) err := mirror.AddMirror(nil, -1) assert.Error(t, err) @@ -92,7 +91,7 @@ func TestHijack(t *testing.T) { handler := http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { rw.WriteHeader(http.StatusOK) }) - pool := safe.NewPool(context.Background()) + pool := safe.NewPool(t.Context()) mirror := New(handler, pool, true, defaultMaxBodySize, nil) var mirrorRequest bool @@ -116,7 +115,7 @@ func TestFlush(t *testing.T) { handler := http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { rw.WriteHeader(http.StatusOK) }) - pool := safe.NewPool(context.Background()) + pool := safe.NewPool(t.Context()) mirror := New(handler, pool, true, defaultMaxBodySize, nil) var mirrorRequest bool @@ -144,7 +143,7 @@ func TestMirroringWithBody(t *testing.T) { body = []byte(`body`) ) - pool := safe.NewPool(context.Background()) + pool := safe.NewPool(t.Context()) handler := http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { assert.NotNil(t, r.Body) @@ -186,7 +185,7 @@ func TestMirroringWithIgnoredBody(t *testing.T) { emptyBody = []byte(``) ) - pool := safe.NewPool(context.Background()) + pool := safe.NewPool(t.Context()) handler := http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { assert.NotNil(t, r.Body) diff --git a/pkg/server/service/loadbalancer/p2c/p2c.go b/pkg/server/service/loadbalancer/p2c/p2c.go index d6d31413c..3bf06a512 100644 --- a/pkg/server/service/loadbalancer/p2c/p2c.go +++ b/pkg/server/service/loadbalancer/p2c/p2c.go @@ -43,6 +43,7 @@ type rnd interface { type Balancer struct { wantsHealthCheck bool + // handlersMu is a mutex to protect the handlers slice, the status and the fenced maps. handlersMu sync.RWMutex handlers []*namedHandler // status is a record of which child services of the Balancer are healthy, keyed @@ -50,11 +51,12 @@ type Balancer struct { // created via Add, and it is later removed or added to the map as needed, // through the SetStatus method. status map[string]struct{} + // fenced is the list of terminating yet still serving child services. + fenced map[string]struct{} + // updaters is the list of hooks that are run (to update the Balancer // parent(s)), whenever the Balancer status changes. updaters []func(bool) - // fenced is the list of terminating yet still serving child services. - fenced map[string]struct{} sticky *loadbalancer.Sticky @@ -181,7 +183,10 @@ func (b *Balancer) ServeHTTP(rw http.ResponseWriter, req *http.Request) { if err != nil { log.Error().Err(err).Msg("Error while getting sticky handler") } else if h != nil { - if _, ok := b.status[h.Name]; ok { + b.handlersMu.RLock() + _, ok := b.status[h.Name] + b.handlersMu.RUnlock() + if ok { if rewrite { if err := b.sticky.WriteStickyCookie(rw, h.Name); err != nil { log.Error().Err(err).Msg("Writing sticky cookie") diff --git a/pkg/server/service/loadbalancer/p2c/p2c_test.go b/pkg/server/service/loadbalancer/p2c/p2c_test.go index b89420de2..bce6cc456 100644 --- a/pkg/server/service/loadbalancer/p2c/p2c_test.go +++ b/pkg/server/service/loadbalancer/p2c/p2c_test.go @@ -1,7 +1,6 @@ package p2c import ( - "context" "net/http" "net/http/httptest" "strconv" @@ -207,7 +206,7 @@ func TestBalancerPropagate(t *testing.T) { assert.Equal(t, http.StatusOK, recorder.Code) // two gets downed, but balancer still up since first is still up. - balancer.SetStatus(context.Background(), "second", false) + balancer.SetStatus(t.Context(), "second", false) assert.Equal(t, 0, calls) recorder = httptest.NewRecorder() @@ -216,7 +215,7 @@ func TestBalancerPropagate(t *testing.T) { assert.Equal(t, "first", recorder.Header().Get("server")) // first gets downed, balancer is down. - balancer.SetStatus(context.Background(), "first", false) + balancer.SetStatus(t.Context(), "first", false) assert.Equal(t, 1, calls) recorder = httptest.NewRecorder() @@ -224,7 +223,7 @@ func TestBalancerPropagate(t *testing.T) { assert.Equal(t, http.StatusServiceUnavailable, recorder.Code) // two gets up, balancer up. - balancer.SetStatus(context.Background(), "second", true) + balancer.SetStatus(t.Context(), "second", true) assert.Equal(t, 2, calls) recorder = httptest.NewRecorder() diff --git a/pkg/server/service/loadbalancer/wrr/wrr.go b/pkg/server/service/loadbalancer/wrr/wrr.go index 8206b97aa..2c9bff0ee 100644 --- a/pkg/server/service/loadbalancer/wrr/wrr.go +++ b/pkg/server/service/loadbalancer/wrr/wrr.go @@ -27,6 +27,7 @@ type namedHandler struct { type Balancer struct { wantsHealthCheck bool + // handlersMu is a mutex to protect the handlers slice, the status and the fenced maps. handlersMu sync.RWMutex handlers []*namedHandler // status is a record of which child services of the Balancer are healthy, keyed @@ -34,11 +35,12 @@ type Balancer struct { // created via Add, and it is later removed or added to the map as needed, // through the SetStatus method. status map[string]struct{} + // fenced is the list of terminating yet still serving child services. + fenced map[string]struct{} + // updaters is the list of hooks that are run (to update the Balancer // parent(s)), whenever the Balancer status changes. updaters []func(bool) - // fenced is the list of terminating yet still serving child services. - fenced map[string]struct{} sticky *loadbalancer.Sticky @@ -180,7 +182,10 @@ func (b *Balancer) ServeHTTP(rw http.ResponseWriter, req *http.Request) { if err != nil { log.Error().Err(err).Msg("Error while getting sticky handler") } else if h != nil { - if _, ok := b.status[h.Name]; ok { + b.handlersMu.RLock() + _, ok := b.status[h.Name] + b.handlersMu.RUnlock() + if ok { if rewrite { if err := b.sticky.WriteStickyCookie(rw, h.Name); err != nil { log.Error().Err(err).Msg("Writing sticky cookie") diff --git a/pkg/server/service/loadbalancer/wrr/wrr_test.go b/pkg/server/service/loadbalancer/wrr/wrr_test.go index 7e9c3bc04..5260623be 100644 --- a/pkg/server/service/loadbalancer/wrr/wrr_test.go +++ b/pkg/server/service/loadbalancer/wrr/wrr_test.go @@ -76,8 +76,8 @@ func TestBalancerNoServiceUp(t *testing.T) { rw.WriteHeader(http.StatusInternalServerError) }), pointer(1), false) - balancer.SetStatus(context.WithValue(context.Background(), serviceName, "parent"), "first", false) - balancer.SetStatus(context.WithValue(context.Background(), serviceName, "parent"), "second", false) + balancer.SetStatus(context.WithValue(t.Context(), serviceName, "parent"), "first", false) + balancer.SetStatus(context.WithValue(t.Context(), serviceName, "parent"), "second", false) recorder := httptest.NewRecorder() balancer.ServeHTTP(recorder, httptest.NewRequest(http.MethodGet, "/", nil)) @@ -96,7 +96,7 @@ func TestBalancerOneServerDown(t *testing.T) { balancer.Add("second", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { rw.WriteHeader(http.StatusInternalServerError) }), pointer(1), false) - balancer.SetStatus(context.WithValue(context.Background(), serviceName, "parent"), "second", false) + balancer.SetStatus(context.WithValue(t.Context(), serviceName, "parent"), "second", false) recorder := &responseRecorder{ResponseRecorder: httptest.NewRecorder(), save: map[string]int{}} for range 3 { @@ -118,7 +118,7 @@ func TestBalancerDownThenUp(t *testing.T) { rw.Header().Set("server", "second") rw.WriteHeader(http.StatusOK) }), pointer(1), false) - balancer.SetStatus(context.WithValue(context.Background(), serviceName, "parent"), "second", false) + balancer.SetStatus(context.WithValue(t.Context(), serviceName, "parent"), "second", false) recorder := &responseRecorder{ResponseRecorder: httptest.NewRecorder(), save: map[string]int{}} for range 3 { @@ -126,7 +126,7 @@ func TestBalancerDownThenUp(t *testing.T) { } assert.Equal(t, 3, recorder.save["first"]) - balancer.SetStatus(context.WithValue(context.Background(), serviceName, "parent"), "second", true) + balancer.SetStatus(context.WithValue(t.Context(), serviceName, "parent"), "second", true) recorder = &responseRecorder{ResponseRecorder: httptest.NewRecorder(), save: map[string]int{}} for range 2 { balancer.ServeHTTP(recorder, httptest.NewRequest(http.MethodGet, "/", nil)) @@ -160,13 +160,13 @@ func TestBalancerPropagate(t *testing.T) { topBalancer := New(nil, true) topBalancer.Add("balancer1", balancer1, pointer(1), false) _ = balancer1.RegisterStatusUpdater(func(up bool) { - topBalancer.SetStatus(context.WithValue(context.Background(), serviceName, "top"), "balancer1", up) + topBalancer.SetStatus(context.WithValue(t.Context(), serviceName, "top"), "balancer1", up) // TODO(mpl): if test gets flaky, add channel or something here to signal that // propagation is done, and wait on it before sending request. }) topBalancer.Add("balancer2", balancer2, pointer(1), false) _ = balancer2.RegisterStatusUpdater(func(up bool) { - topBalancer.SetStatus(context.WithValue(context.Background(), serviceName, "top"), "balancer2", up) + topBalancer.SetStatus(context.WithValue(t.Context(), serviceName, "top"), "balancer2", up) }) recorder := &responseRecorder{ResponseRecorder: httptest.NewRecorder(), save: map[string]int{}} @@ -181,7 +181,7 @@ func TestBalancerPropagate(t *testing.T) { assert.Equal(t, wantStatus, recorder.status) // fourth gets downed, but balancer2 still up since third is still up. - balancer2.SetStatus(context.WithValue(context.Background(), serviceName, "top"), "fourth", false) + balancer2.SetStatus(context.WithValue(t.Context(), serviceName, "top"), "fourth", false) recorder = &responseRecorder{ResponseRecorder: httptest.NewRecorder(), save: map[string]int{}} for range 8 { topBalancer.ServeHTTP(recorder, httptest.NewRequest(http.MethodGet, "/", nil)) @@ -195,7 +195,7 @@ func TestBalancerPropagate(t *testing.T) { // third gets downed, and the propagation triggers balancer2 to be marked as // down as well for topBalancer. - balancer2.SetStatus(context.WithValue(context.Background(), serviceName, "top"), "third", false) + balancer2.SetStatus(context.WithValue(t.Context(), serviceName, "top"), "third", false) recorder = &responseRecorder{ResponseRecorder: httptest.NewRecorder(), save: map[string]int{}} for range 8 { topBalancer.ServeHTTP(recorder, httptest.NewRequest(http.MethodGet, "/", nil)) diff --git a/pkg/server/service/managerfactory.go b/pkg/server/service/managerfactory.go index 19da05d7b..a850ec745 100644 --- a/pkg/server/service/managerfactory.go +++ b/pkg/server/service/managerfactory.go @@ -9,7 +9,7 @@ import ( "github.com/traefik/traefik/v3/pkg/api/dashboard" "github.com/traefik/traefik/v3/pkg/config/runtime" "github.com/traefik/traefik/v3/pkg/config/static" - "github.com/traefik/traefik/v3/pkg/metrics" + "github.com/traefik/traefik/v3/pkg/observability/metrics" "github.com/traefik/traefik/v3/pkg/safe" "github.com/traefik/traefik/v3/pkg/server/middleware" ) diff --git a/pkg/server/service/service.go b/pkg/server/service/service.go index 82cc96e36..a8e00cd1d 100644 --- a/pkg/server/service/service.go +++ b/pkg/server/service/service.go @@ -17,12 +17,11 @@ import ( "github.com/traefik/traefik/v3/pkg/config/dynamic" "github.com/traefik/traefik/v3/pkg/config/runtime" "github.com/traefik/traefik/v3/pkg/healthcheck" - "github.com/traefik/traefik/v3/pkg/logs" "github.com/traefik/traefik/v3/pkg/middlewares/accesslog" - "github.com/traefik/traefik/v3/pkg/middlewares/capture" metricsMiddle "github.com/traefik/traefik/v3/pkg/middlewares/metrics" "github.com/traefik/traefik/v3/pkg/middlewares/observability" "github.com/traefik/traefik/v3/pkg/middlewares/retry" + "github.com/traefik/traefik/v3/pkg/observability/logs" "github.com/traefik/traefik/v3/pkg/proxy/httputil" "github.com/traefik/traefik/v3/pkg/safe" "github.com/traefik/traefik/v3/pkg/server/cookie" @@ -37,7 +36,7 @@ import ( // ProxyBuilder builds reverse proxy handlers. type ProxyBuilder interface { - Build(cfgName string, targetURL *url.URL, shouldObserve, passHostHeader, preservePath bool, flushInterval time.Duration) (http.Handler, error) + Build(cfgName string, targetURL *url.URL, passHostHeader, preservePath bool, flushInterval time.Duration) (http.Handler, error) Update(configs map[string]*dynamic.ServersTransport) } @@ -364,50 +363,32 @@ func (m *Manager) getLoadBalancerServiceHandler(ctx context.Context, serviceName qualifiedSvcName := provider.GetQualifiedName(ctx, serviceName) - shouldObserve := m.observabilityMgr.ShouldAddTracing(qualifiedSvcName, nil) || m.observabilityMgr.ShouldAddMetrics(qualifiedSvcName, nil) - proxy, err := m.proxyBuilder.Build(service.ServersTransport, target, shouldObserve, passHostHeader, server.PreservePath, flushInterval) + proxy, err := m.proxyBuilder.Build(service.ServersTransport, target, passHostHeader, server.PreservePath, flushInterval) if err != nil { return nil, fmt.Errorf("error building proxy for server URL %s: %w", server.URL, err) } + // The retry wrapping must be done just before the proxy handler, // to make sure that the retry will not be triggered/disabled by // middlewares in the chain. proxy = retry.WrapHandler(proxy) - // Prevents from enabling observability for internal resources. + // Access logs, metrics, and tracing middlewares are idempotent if the associated signal is disabled. + proxy = accesslog.NewFieldHandler(proxy, accesslog.ServiceURL, target.String(), nil) + proxy = accesslog.NewFieldHandler(proxy, accesslog.ServiceAddr, target.Host, nil) + proxy = accesslog.NewFieldHandler(proxy, accesslog.ServiceName, qualifiedSvcName, accesslog.AddServiceFields) - if m.observabilityMgr.ShouldAddAccessLogs(qualifiedSvcName, nil) { - proxy = accesslog.NewFieldHandler(proxy, accesslog.ServiceURL, target.String(), nil) - proxy = accesslog.NewFieldHandler(proxy, accesslog.ServiceAddr, target.Host, nil) - proxy = accesslog.NewFieldHandler(proxy, accesslog.ServiceName, serviceName, accesslog.AddServiceFields) + metricsHandler := metricsMiddle.ServiceMetricsHandler(ctx, m.observabilityMgr.MetricsRegistry(), qualifiedSvcName) + metricsHandler = observability.WrapMiddleware(ctx, metricsHandler) + + proxy, err = alice.New(). + Append(metricsHandler). + Then(proxy) + if err != nil { + return nil, fmt.Errorf("error wrapping metrics handler: %w", err) } - if m.observabilityMgr.MetricsRegistry() != nil && m.observabilityMgr.MetricsRegistry().IsSvcEnabled() && - m.observabilityMgr.ShouldAddMetrics(qualifiedSvcName, nil) { - metricsHandler := metricsMiddle.WrapServiceHandler(ctx, m.observabilityMgr.MetricsRegistry(), serviceName) - - proxy, err = alice.New(). - Append(observability.WrapMiddleware(ctx, metricsHandler)). - Then(proxy) - if err != nil { - return nil, fmt.Errorf("error wrapping metrics handler: %w", err) - } - } - - if m.observabilityMgr.ShouldAddTracing(qualifiedSvcName, nil) { - proxy = observability.NewService(ctx, serviceName, proxy) - } - - if m.observabilityMgr.ShouldAddAccessLogs(qualifiedSvcName, nil) || m.observabilityMgr.ShouldAddMetrics(qualifiedSvcName, nil) { - // Some piece of middleware, like the ErrorPage, are relying on this serviceBuilder to get the handler for a given service, - // to re-target the request to it. - // Those pieces of middleware can be configured on routes that expose a Traefik internal service. - // In such a case, observability for internals being optional, the capture probe could be absent from context (no wrap via the entrypoint). - // But if the service targeted by this piece of middleware is not an internal one, - // and requires observability, we still want the capture probe to be present in the request context. - // Makes sure a capture probe is in the request context. - proxy, _ = capture.Wrap(proxy) - } + proxy = observability.NewService(ctx, qualifiedSvcName, proxy) lb.AddServer(server.URL, proxy, server) diff --git a/pkg/server/service/service_test.go b/pkg/server/service/service_test.go index 1a31a9e75..7d00a69fb 100644 --- a/pkg/server/service/service_test.go +++ b/pkg/server/service/service_test.go @@ -74,7 +74,7 @@ func TestGetLoadBalancer(t *testing.T) { t.Parallel() serviceInfo := &runtime.ServiceInfo{Service: &dynamic.Service{LoadBalancer: test.service}} - handler, err := sm.getLoadBalancerServiceHandler(context.Background(), test.serviceName, serviceInfo) + handler, err := sm.getLoadBalancerServiceHandler(t.Context(), test.serviceName, serviceInfo) if test.expectError { require.Error(t, err) assert.Nil(t, handler) @@ -321,7 +321,7 @@ func TestGetLoadBalancerServiceHandler(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { serviceInfo := &runtime.ServiceInfo{Service: &dynamic.Service{LoadBalancer: test.service}} - handler, err := sm.getLoadBalancerServiceHandler(context.Background(), test.serviceName, serviceInfo) + handler, err := sm.getLoadBalancerServiceHandler(t.Context(), test.serviceName, serviceInfo) assert.NoError(t, err) assert.NotNil(t, handler) @@ -402,7 +402,7 @@ func Test1xxResponses(t *testing.T) { }, } - handler, err := sm.getLoadBalancerServiceHandler(context.Background(), "foobar", info) + handler, err := sm.getLoadBalancerServiceHandler(t.Context(), "foobar", info) assert.NoError(t, err) frontend := httptest.NewServer(handler) @@ -446,7 +446,7 @@ func Test1xxResponses(t *testing.T) { return nil }, } - req, _ := http.NewRequestWithContext(httptrace.WithClientTrace(context.Background(), trace), http.MethodGet, frontend.URL, nil) + req, _ := http.NewRequestWithContext(httptrace.WithClientTrace(t.Context(), trace), http.MethodGet, frontend.URL, nil) res, err := frontendClient.Do(req) assert.NoError(t, err) @@ -496,15 +496,15 @@ func TestManager_ServiceBuilders(t *testing.T) { return nil, nil })) - h, err := manager.BuildHTTP(context.Background(), "test@internal") + h, err := manager.BuildHTTP(t.Context(), "test@internal") require.NoError(t, err) assert.Equal(t, internalHandler, h) - h, err = manager.BuildHTTP(context.Background(), "test@test") + h, err = manager.BuildHTTP(t.Context(), "test@test") require.NoError(t, err) assert.NotNil(t, h) - _, err = manager.BuildHTTP(context.Background(), "wrong@test") + _, err = manager.BuildHTTP(t.Context(), "wrong@test") assert.Error(t, err) } @@ -563,7 +563,7 @@ func TestManager_Build(t *testing.T) { manager := NewManager(test.configs, nil, nil, &transportManagerMock{}, nil) - ctx := context.Background() + ctx := t.Context() if len(test.providerName) > 0 { ctx = provider.AddInContext(ctx, "foobar@"+test.providerName) } @@ -586,7 +586,7 @@ func TestMultipleTypeOnBuildHTTP(t *testing.T) { manager := NewManager(services, nil, nil, &transportManagerMock{}, nil) - _, err := manager.BuildHTTP(context.Background(), "test@file") + _, err := manager.BuildHTTP(t.Context(), "test@file") assert.Error(t, err, "cannot create service: multi-types service not supported, consider declaring two different pieces of service instead") } diff --git a/pkg/server/service/tcp/service.go b/pkg/server/service/tcp/service.go index 64c173a51..9ff454fad 100644 --- a/pkg/server/service/tcp/service.go +++ b/pkg/server/service/tcp/service.go @@ -10,10 +10,9 @@ import ( "github.com/rs/zerolog/log" "github.com/traefik/traefik/v3/pkg/config/runtime" - "github.com/traefik/traefik/v3/pkg/logs" + "github.com/traefik/traefik/v3/pkg/observability/logs" "github.com/traefik/traefik/v3/pkg/server/provider" "github.com/traefik/traefik/v3/pkg/tcp" - "golang.org/x/net/proxy" ) // Manager is the TCPHandlers factory. @@ -58,6 +57,10 @@ func (m *Manager) BuildTCP(rootCtx context.Context, serviceName string) (tcp.Han log.Ctx(ctx).Warn().Msgf("Service %q load balancer uses `TerminationDelay`, but this option is deprecated, please use ServersTransport configuration instead.", serviceName) } + if conf.LoadBalancer.ProxyProtocol != nil { + log.Ctx(ctx).Warn().Msgf("Service %q load balancer uses `ProxyProtocol`, but this option is deprecated, please use ServersTransport configuration instead.", serviceName) + } + if len(conf.LoadBalancer.ServersTransport) > 0 { conf.LoadBalancer.ServersTransport = provider.GetQualifiedName(ctx, conf.LoadBalancer.ServersTransport) } @@ -72,20 +75,12 @@ func (m *Manager) BuildTCP(rootCtx context.Context, serviceName string) (tcp.Han continue } - dialer, err := m.dialerManager.Get(conf.LoadBalancer.ServersTransport, server.TLS) + dialer, err := m.dialerManager.Build(conf.LoadBalancer, server.TLS) if err != nil { return nil, err } - // Handle TerminationDelay deprecated option. - if conf.LoadBalancer.ServersTransport == "" && conf.LoadBalancer.TerminationDelay != nil { - dialer = &dialerWrapper{ - Dialer: dialer, - terminationDelay: time.Duration(*conf.LoadBalancer.TerminationDelay), - } - } - - handler, err := tcp.NewProxy(server.Address, conf.LoadBalancer.ProxyProtocol, dialer) + handler, err := tcp.NewProxy(server.Address, dialer) if err != nil { srvLogger.Error().Err(err).Msg("Failed to create server") continue @@ -126,13 +121,3 @@ func shuffle[T any](values []T, r *rand.Rand) []T { return shuffled } - -// dialerWrapper is only used to handle TerminationDelay deprecated option on TCPServersLoadBalancer. -type dialerWrapper struct { - proxy.Dialer - terminationDelay time.Duration -} - -func (d dialerWrapper) TerminationDelay() time.Duration { - return d.terminationDelay -} diff --git a/pkg/server/service/tcp/service_test.go b/pkg/server/service/tcp/service_test.go index 6f06d296c..8dd0b7393 100644 --- a/pkg/server/service/tcp/service_test.go +++ b/pkg/server/service/tcp/service_test.go @@ -1,7 +1,6 @@ package tcp import ( - "context" "testing" "github.com/stretchr/testify/assert" @@ -232,7 +231,7 @@ func TestManager_BuildTCP(t *testing.T) { }, }, providerName: "provider-1", - expectedError: "TCP dialer not found myServersTransport@provider-1", + expectedError: "no transport configuration found for \"myServersTransport@provider-1\"", }, } @@ -249,7 +248,7 @@ func TestManager_BuildTCP(t *testing.T) { TCPServices: test.configs, }, dialerManager) - ctx := context.Background() + ctx := t.Context() if len(test.providerName) > 0 { ctx = provider.AddInContext(ctx, "foobar@"+test.providerName) } diff --git a/pkg/server/service/transport_test.go b/pkg/server/service/transport_test.go index d27f293f2..0fd3a8ab8 100644 --- a/pkg/server/service/transport_test.go +++ b/pkg/server/service/transport_test.go @@ -1,7 +1,6 @@ package service import ( - "context" "crypto/rand" "crypto/rsa" "crypto/tls" @@ -607,7 +606,7 @@ func TestKerberosRoundTripper(t *testing.T) { }), } - ctx := AddTransportOnContext(context.Background()) + ctx := AddTransportOnContext(t.Context()) for _, expected := range test.expectedStatusCode { req, err := http.NewRequestWithContext(ctx, http.MethodGet, "http://127.0.0.1", http.NoBody) require.NoError(t, err) diff --git a/pkg/server/service/udp/service.go b/pkg/server/service/udp/service.go index 1772c1954..d0d3028b4 100644 --- a/pkg/server/service/udp/service.go +++ b/pkg/server/service/udp/service.go @@ -10,7 +10,7 @@ import ( "github.com/rs/zerolog/log" "github.com/traefik/traefik/v3/pkg/config/runtime" - "github.com/traefik/traefik/v3/pkg/logs" + "github.com/traefik/traefik/v3/pkg/observability/logs" "github.com/traefik/traefik/v3/pkg/server/provider" "github.com/traefik/traefik/v3/pkg/udp" ) diff --git a/pkg/server/service/udp/service_test.go b/pkg/server/service/udp/service_test.go index ec1794bc1..3a744c6e6 100644 --- a/pkg/server/service/udp/service_test.go +++ b/pkg/server/service/udp/service_test.go @@ -1,7 +1,6 @@ package udp import ( - "context" "testing" "github.com/stretchr/testify/assert" @@ -181,7 +180,7 @@ func TestManager_BuildUDP(t *testing.T) { UDPServices: test.configs, }) - ctx := context.Background() + ctx := t.Context() if len(test.providerName) > 0 { ctx = provider.AddInContext(ctx, "foobar@"+test.providerName) } diff --git a/pkg/tcp/dialer.go b/pkg/tcp/dialer.go index ca08f7d2e..bc4855b5b 100644 --- a/pkg/tcp/dialer.go +++ b/pkg/tcp/dialer.go @@ -9,33 +9,85 @@ import ( "sync" "time" + "github.com/pires/go-proxyproto" "github.com/rs/zerolog/log" "github.com/spiffe/go-spiffe/v2/bundle/x509bundle" "github.com/spiffe/go-spiffe/v2/spiffeid" "github.com/spiffe/go-spiffe/v2/spiffetls/tlsconfig" "github.com/spiffe/go-spiffe/v2/svid/x509svid" + ptypes "github.com/traefik/paerser/types" "github.com/traefik/traefik/v3/pkg/config/dynamic" traefiktls "github.com/traefik/traefik/v3/pkg/tls" "github.com/traefik/traefik/v3/pkg/types" - "golang.org/x/net/proxy" ) -type Dialer interface { - proxy.Dialer +// ClientConn is the interface that provides information about the client connection. +type ClientConn interface { + // LocalAddr returns the local network address, if known. + LocalAddr() net.Addr + // RemoteAddr returns the remote network address, if known. + RemoteAddr() net.Addr +} + +// Dialer is an interface to dial a network connection, with support for PROXY protocol and termination delay. +type Dialer interface { + Dial(network, addr string, clientConn ClientConn) (c net.Conn, err error) TerminationDelay() time.Duration } type tcpDialer struct { - proxy.Dialer + dialer *net.Dialer terminationDelay time.Duration + proxyProtocol *dynamic.ProxyProtocol } +// TerminationDelay returns the termination delay duration. func (d tcpDialer) TerminationDelay() time.Duration { return d.terminationDelay } -// SpiffeX509Source allows to retrieve a x509 SVID and bundle. +// Dial dials a network connection and optionally sends a PROXY protocol header. +func (d tcpDialer) Dial(network, addr string, clientConn ClientConn) (net.Conn, error) { + conn, err := d.dialer.Dial(network, addr) + if err != nil { + return nil, err + } + + if d.proxyProtocol != nil && clientConn != nil && d.proxyProtocol.Version > 0 && d.proxyProtocol.Version < 3 { + header := proxyproto.HeaderProxyFromAddrs(byte(d.proxyProtocol.Version), clientConn.RemoteAddr(), clientConn.LocalAddr()) + if _, err := header.WriteTo(conn); err != nil { + _ = conn.Close() + return nil, fmt.Errorf("writing PROXY Protocol header: %w", err) + } + } + + return conn, nil +} + +type tcpTLSDialer struct { + tcpDialer + tlsConfig *tls.Config +} + +// Dial dials a network connection with the wrapped tcpDialer and performs a TLS handshake. +func (d tcpTLSDialer) Dial(network, addr string, clientConn ClientConn) (net.Conn, error) { + conn, err := d.tcpDialer.Dial(network, addr, clientConn) + if err != nil { + return nil, err + } + + // Now perform TLS handshake on the connection + tlsConn := tls.Client(conn, d.tlsConfig) + if err := tlsConn.Handshake(); err != nil { + _ = conn.Close() + return nil, fmt.Errorf("TLS handshake failed: %w", err) + } + + return tlsConn, nil +} + +// SpiffeX509Source allows retrieving a x509 SVID and bundle. type SpiffeX509Source interface { x509svid.Source x509bundle.Source @@ -43,118 +95,106 @@ type SpiffeX509Source interface { // DialerManager handles dialer for the reverse proxy. type DialerManager struct { - rtLock sync.RWMutex - dialers map[string]Dialer - dialersTLS map[string]Dialer - spiffeX509Source SpiffeX509Source + serversTransportsMu sync.RWMutex + serversTransports map[string]*dynamic.TCPServersTransport + spiffeX509Source SpiffeX509Source } // NewDialerManager creates a new DialerManager. func NewDialerManager(spiffeX509Source SpiffeX509Source) *DialerManager { return &DialerManager{ - dialers: make(map[string]Dialer), - dialersTLS: make(map[string]Dialer), - spiffeX509Source: spiffeX509Source, + serversTransports: make(map[string]*dynamic.TCPServersTransport), + spiffeX509Source: spiffeX509Source, } } -// Update updates the dialers configurations. +// Update updates the TCP serversTransport configurations. func (d *DialerManager) Update(configs map[string]*dynamic.TCPServersTransport) { - d.rtLock.Lock() - defer d.rtLock.Unlock() + d.serversTransportsMu.Lock() + defer d.serversTransportsMu.Unlock() - d.dialers = make(map[string]Dialer) - d.dialersTLS = make(map[string]Dialer) - for configName, config := range configs { - if err := d.createDialers(configName, config); err != nil { - log.Debug(). - Str("dialer", configName). - Err(err). - Msg("Create TCP Dialer") - } - } + d.serversTransports = configs } -// Get gets a dialer by name. -func (d *DialerManager) Get(name string, tls bool) (Dialer, error) { - if len(name) == 0 { - name = "default@internal" +// Build builds a dialer by name. +func (d *DialerManager) Build(config *dynamic.TCPServersLoadBalancer, isTLS bool) (Dialer, error) { + name := "default@internal" + if config.ServersTransport != "" { + name = config.ServersTransport } - d.rtLock.RLock() - defer d.rtLock.RUnlock() - - if tls { - if rt, ok := d.dialersTLS[name]; ok { - return rt, nil - } - - return nil, fmt.Errorf("TCP dialer not found %s", name) + var st *dynamic.TCPServersTransport + d.serversTransportsMu.RLock() + st, ok := d.serversTransports[name] + d.serversTransportsMu.RUnlock() + if !ok || st == nil { + return nil, fmt.Errorf("no transport configuration found for %q", name) } - if rt, ok := d.dialers[name]; ok { - return rt, nil + // Handle TerminationDelay and ProxyProtocol deprecated options. + var terminationDelay ptypes.Duration + if config.TerminationDelay != nil { + terminationDelay = ptypes.Duration(*config.TerminationDelay) + } + proxyProtocol := config.ProxyProtocol + + if config.ServersTransport != "" { + terminationDelay = st.TerminationDelay + proxyProtocol = st.ProxyProtocol } - return nil, fmt.Errorf("TCP dialer not found %s", name) -} - -// createDialers creates the dialers according to the TCPServersTransport configuration. -func (d *DialerManager) createDialers(name string, cfg *dynamic.TCPServersTransport) error { - if cfg == nil { - return errors.New("no transport configuration given") - } - - dialer := &net.Dialer{ - Timeout: time.Duration(cfg.DialTimeout), - KeepAlive: time.Duration(cfg.DialKeepAlive), + if proxyProtocol != nil && (proxyProtocol.Version < 1 || proxyProtocol.Version > 2) { + return nil, fmt.Errorf("unknown proxyProtocol version: %d", proxyProtocol.Version) } var tlsConfig *tls.Config - - if cfg.TLS != nil { - if cfg.TLS.Spiffe != nil { + if st.TLS != nil { + if st.TLS.Spiffe != nil { if d.spiffeX509Source == nil { - return errors.New("SPIFFE is enabled for this transport, but not configured") + return nil, errors.New("SPIFFE is enabled for this transport, but not configured") } - authorizer, err := buildSpiffeAuthorizer(cfg.TLS.Spiffe) + authorizer, err := buildSpiffeAuthorizer(st.TLS.Spiffe) if err != nil { - return fmt.Errorf("unable to build SPIFFE authorizer: %w", err) + return nil, fmt.Errorf("unable to build SPIFFE authorizer: %w", err) } tlsConfig = tlsconfig.MTLSClientConfig(d.spiffeX509Source, d.spiffeX509Source, authorizer) } - if cfg.TLS.InsecureSkipVerify || len(cfg.TLS.RootCAs) > 0 || len(cfg.TLS.ServerName) > 0 || len(cfg.TLS.Certificates) > 0 || cfg.TLS.PeerCertURI != "" { + if st.TLS.InsecureSkipVerify || len(st.TLS.RootCAs) > 0 || len(st.TLS.ServerName) > 0 || len(st.TLS.Certificates) > 0 || st.TLS.PeerCertURI != "" { if tlsConfig != nil { - return errors.New("TLS and SPIFFE configuration cannot be defined at the same time") + return nil, errors.New("TLS and SPIFFE configuration cannot be defined at the same time") } tlsConfig = &tls.Config{ - ServerName: cfg.TLS.ServerName, - InsecureSkipVerify: cfg.TLS.InsecureSkipVerify, - RootCAs: createRootCACertPool(cfg.TLS.RootCAs), - Certificates: cfg.TLS.Certificates.GetCertificates(), + ServerName: st.TLS.ServerName, + InsecureSkipVerify: st.TLS.InsecureSkipVerify, + RootCAs: createRootCACertPool(st.TLS.RootCAs), + Certificates: st.TLS.Certificates.GetCertificates(), } - if cfg.TLS.PeerCertURI != "" { + if st.TLS.PeerCertURI != "" { tlsConfig.VerifyPeerCertificate = func(rawCerts [][]byte, _ [][]*x509.Certificate) error { - return traefiktls.VerifyPeerCertificate(cfg.TLS.PeerCertURI, tlsConfig, rawCerts) + return traefiktls.VerifyPeerCertificate(st.TLS.PeerCertURI, tlsConfig, rawCerts) } } } } - tlsDialer := &tls.Dialer{ - NetDialer: dialer, - Config: tlsConfig, + dialer := tcpDialer{ + dialer: &net.Dialer{ + Timeout: time.Duration(st.DialTimeout), + KeepAlive: time.Duration(st.DialKeepAlive), + }, + terminationDelay: time.Duration(terminationDelay), + proxyProtocol: proxyProtocol, } - d.dialers[name] = tcpDialer{dialer, time.Duration(cfg.TerminationDelay)} - d.dialersTLS[name] = tcpDialer{tlsDialer, time.Duration(cfg.TerminationDelay)} - - return nil + if !isTLS { + return dialer, nil + } + return tcpTLSDialer{dialer, tlsConfig}, nil } func createRootCACertPool(rootCAs []types.FileOrContent) *x509.CertPool { diff --git a/pkg/tcp/dialer_test.go b/pkg/tcp/dialer_test.go index 97ae5d8df..07d8a7b12 100644 --- a/pkg/tcp/dialer_test.go +++ b/pkg/tcp/dialer_test.go @@ -7,6 +7,7 @@ import ( "crypto/tls" "crypto/x509" "crypto/x509/pkix" + "errors" "io" "math/big" "net" @@ -14,6 +15,7 @@ import ( "testing" "time" + "github.com/pires/go-proxyproto" "github.com/spiffe/go-spiffe/v2/bundle/x509bundle" "github.com/spiffe/go-spiffe/v2/spiffeid" "github.com/spiffe/go-spiffe/v2/spiffetls/tlsconfig" @@ -131,7 +133,7 @@ func TestConflictingConfig(t *testing.T) { dialerManager.Update(dynamicConf) - _, err := dialerManager.Get("test", false) + _, err := dialerManager.Build(&dynamic.TCPServersLoadBalancer{ServersTransport: "test"}, false) require.Error(t, err) } @@ -140,7 +142,7 @@ func TestNoTLS(t *testing.T) { require.NoError(t, err) defer backendListener.Close() - go fakeRedis(t, backendListener) + go fakeServer(t, backendListener) _, port, err := net.SplitHostPort(backendListener.Addr().String()) require.NoError(t, err) @@ -155,10 +157,10 @@ func TestNoTLS(t *testing.T) { dialerManager.Update(dynamicConf) - dialer, err := dialerManager.Get("test", false) + dialer, err := dialerManager.Build(&dynamic.TCPServersLoadBalancer{ServersTransport: "test"}, false) require.NoError(t, err) - conn, err := dialer.Dial("tcp", ":"+port) + conn, err := dialer.Dial("tcp", ":"+port, nil) require.NoError(t, err) _, err = conn.Write([]byte("ping\n")) @@ -186,7 +188,7 @@ func TestTLS(t *testing.T) { tlsListener := tls.NewListener(backendListener, &tls.Config{Certificates: []tls.Certificate{cert}}) defer tlsListener.Close() - go fakeRedis(t, tlsListener) + go fakeServer(t, tlsListener) _, port, err := net.SplitHostPort(tlsListener.Addr().String()) require.NoError(t, err) @@ -204,10 +206,10 @@ func TestTLS(t *testing.T) { dialerManager.Update(dynamicConf) - dialer, err := dialerManager.Get("test", true) + dialer, err := dialerManager.Build(&dynamic.TCPServersLoadBalancer{ServersTransport: "test"}, true) require.NoError(t, err) - conn, err := dialer.Dial("tcp", ":"+port) + conn, err := dialer.Dial("tcp", ":"+port, nil) require.NoError(t, err) _, err = conn.Write([]byte("ping\n")) @@ -236,7 +238,7 @@ func TestTLSWithInsecureSkipVerify(t *testing.T) { tlsListener := tls.NewListener(backendListener, &tls.Config{Certificates: []tls.Certificate{cert}}) defer tlsListener.Close() - go fakeRedis(t, tlsListener) + go fakeServer(t, tlsListener) _, port, err := net.SplitHostPort(tlsListener.Addr().String()) require.NoError(t, err) @@ -255,10 +257,10 @@ func TestTLSWithInsecureSkipVerify(t *testing.T) { dialerManager.Update(dynamicConf) - dialer, err := dialerManager.Get("test", true) + dialer, err := dialerManager.Build(&dynamic.TCPServersLoadBalancer{ServersTransport: "test"}, true) require.NoError(t, err) - conn, err := dialer.Dial("tcp", ":"+port) + conn, err := dialer.Dial("tcp", ":"+port, nil) require.NoError(t, err) _, err = conn.Write([]byte("ping\n")) @@ -297,7 +299,7 @@ func TestMTLS(t *testing.T) { }) defer tlsListener.Close() - go fakeRedis(t, tlsListener) + go fakeServer(t, tlsListener) _, port, err := net.SplitHostPort(tlsListener.Addr().String()) require.NoError(t, err) @@ -324,10 +326,10 @@ func TestMTLS(t *testing.T) { dialerManager.Update(dynamicConf) - dialer, err := dialerManager.Get("test", true) + dialer, err := dialerManager.Build(&dynamic.TCPServersLoadBalancer{ServersTransport: "test"}, true) require.NoError(t, err) - conn, err := dialer.Dial("tcp", ":"+port) + conn, err := dialer.Dial("tcp", ":"+port, nil) require.NoError(t, err) _, err = conn.Write([]byte("ping\n")) @@ -444,7 +446,7 @@ func TestSpiffeMTLS(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { - go fakeRedis(t, tlsListener) + go fakeServer(t, tlsListener) dialerManager := NewDialerManager(test.clientSource) @@ -458,10 +460,10 @@ func TestSpiffeMTLS(t *testing.T) { dialerManager.Update(dynamicConf) - dialer, err := dialerManager.Get("test", true) + dialer, err := dialerManager.Build(&dynamic.TCPServersLoadBalancer{ServersTransport: "test"}, true) require.NoError(t, err) - conn, err := dialer.Dial("tcp", ":"+port) + conn, err := dialer.Dial("tcp", ":"+port, nil) if test.wantError { require.Error(t, err) @@ -487,6 +489,271 @@ func TestSpiffeMTLS(t *testing.T) { } } +func TestProxyProtocol(t *testing.T) { + testCases := []struct { + desc string + version int + }{ + { + desc: "proxy protocol v1", + version: 1, + }, + { + desc: "proxy protocol v2", + version: 2, + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + backendListener, err := net.Listen("tcp", ":0") + require.NoError(t, err) + + var version int + var localAddr, remoteAddr string + proxyBackendListener := proxyproto.Listener{ + Listener: backendListener, + ValidateHeader: func(h *proxyproto.Header) error { + version = int(h.Version) + localAddr = h.DestinationAddr.String() + remoteAddr = h.SourceAddr.String() + return nil + }, + Policy: func(upstream net.Addr) (proxyproto.Policy, error) { + switch test.version { + case 1, 2: + return proxyproto.USE, nil + default: + return proxyproto.REQUIRE, errors.New("unsupported version") + } + }, + } + defer proxyBackendListener.Close() + + go fakeServer(t, &proxyBackendListener) + + _, port, err := net.SplitHostPort(backendListener.Addr().String()) + require.NoError(t, err) + + dialerManager := NewDialerManager(nil) + dialerManager.Update(map[string]*dynamic.TCPServersTransport{ + "test": { + ProxyProtocol: &dynamic.ProxyProtocol{ + Version: test.version, + }, + }, + }) + + dialer, err := dialerManager.Build(&dynamic.TCPServersLoadBalancer{ServersTransport: "test"}, false) + require.NoError(t, err) + + clientConn := &fakeClientConn{ + localAddr: &net.TCPAddr{ + IP: net.ParseIP("2.2.2.2"), + Port: 12345, + }, + remoteAddr: &net.TCPAddr{ + IP: net.ParseIP("1.1.1.1"), + Port: 12345, + }, + } + + conn, err := dialer.Dial("tcp", ":"+port, clientConn) + require.NoError(t, err) + defer conn.Close() + + _, err = conn.Write([]byte("ping")) + require.NoError(t, err) + + buf := make([]byte, 64) + n, err := conn.Read(buf) + require.NoError(t, err) + + assert.Equal(t, 4, n) + assert.Equal(t, "PONG", string(buf[:4])) + assert.Equal(t, test.version, version) + assert.Equal(t, "2.2.2.2:12345", localAddr) + assert.Equal(t, "1.1.1.1:12345", remoteAddr) + }) + } +} + +func TestProxyProtocolWithTLS(t *testing.T) { + testCases := []struct { + desc string + version int + }{ + { + desc: "proxy protocol v1", + version: 1, + }, + { + desc: "proxy protocol v2", + version: 2, + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + cert, err := tls.X509KeyPair(LocalhostCert, LocalhostKey) + require.NoError(t, err) + + backendListener, err := net.Listen("tcp", ":0") + require.NoError(t, err) + + var version int + var localAddr, remoteAddr string + proxyBackendListener := proxyproto.Listener{ + Listener: backendListener, + ValidateHeader: func(h *proxyproto.Header) error { + version = int(h.Version) + localAddr = h.DestinationAddr.String() + remoteAddr = h.SourceAddr.String() + return nil + }, + Policy: func(upstream net.Addr) (proxyproto.Policy, error) { + switch test.version { + case 1, 2: + return proxyproto.USE, nil + default: + return proxyproto.REQUIRE, errors.New("unsupported version") + } + }, + } + defer proxyBackendListener.Close() + + go func() { + conn, err := proxyBackendListener.Accept() + require.NoError(t, err) + defer conn.Close() + + // Now wrap with TLS and perform handshake + tlsConn := tls.Server(conn, &tls.Config{Certificates: []tls.Certificate{cert}}) + defer tlsConn.Close() + + err = tlsConn.Handshake() + require.NoError(t, err) + + buf := make([]byte, 64) + n, err := tlsConn.Read(buf) + require.NoError(t, err) + + if bytes.Equal(buf[:n], []byte("ping")) { + _, _ = tlsConn.Write([]byte("PONG")) + } + }() + + _, port, err := net.SplitHostPort(backendListener.Addr().String()) + require.NoError(t, err) + + dialerManager := NewDialerManager(nil) + dialerManager.Update(map[string]*dynamic.TCPServersTransport{ + "test": { + TLS: &dynamic.TLSClientConfig{ + ServerName: "example.com", + RootCAs: []types.FileOrContent{types.FileOrContent(LocalhostCert)}, + InsecureSkipVerify: true, + }, + ProxyProtocol: &dynamic.ProxyProtocol{ + Version: test.version, + }, + }, + }) + + dialer, err := dialerManager.Build(&dynamic.TCPServersLoadBalancer{ + ServersTransport: "test", + }, true) + require.NoError(t, err) + + clientConn := &fakeClientConn{ + localAddr: &net.TCPAddr{ + IP: net.ParseIP("2.2.2.2"), + Port: 12345, + }, + remoteAddr: &net.TCPAddr{ + IP: net.ParseIP("1.1.1.1"), + Port: 12345, + }, + } + + conn, err := dialer.Dial("tcp", ":"+port, clientConn) + require.NoError(t, err) + defer conn.Close() + + _, err = conn.Write([]byte("ping")) + require.NoError(t, err) + + buf := make([]byte, 64) + n, err := conn.Read(buf) + require.NoError(t, err) + + assert.Equal(t, 4, n) + assert.Equal(t, "PONG", string(buf[:4])) + assert.Equal(t, test.version, version) + assert.Equal(t, "2.2.2.2:12345", localAddr) + assert.Equal(t, "1.1.1.1:12345", remoteAddr) + }) + } +} + +func TestProxyProtocolDisabled(t *testing.T) { + backendListener, err := net.Listen("tcp", ":0") + require.NoError(t, err) + defer backendListener.Close() + + go func() { + conn, err := backendListener.Accept() + require.NoError(t, err) + defer conn.Close() + + buf := make([]byte, 64) + n, err := conn.Read(buf) + require.NoError(t, err) + + if bytes.Equal(buf[:n], []byte("ping")) { + _, _ = conn.Write([]byte("PONG")) + } + }() + + _, port, err := net.SplitHostPort(backendListener.Addr().String()) + require.NoError(t, err) + + // No proxy protocol configuration. + dialerManager := NewDialerManager(nil) + dialerManager.Update(map[string]*dynamic.TCPServersTransport{ + "test": {}, + }) + + dialer, err := dialerManager.Build(&dynamic.TCPServersLoadBalancer{ServersTransport: "test"}, false) + require.NoError(t, err) + + conn, err := dialer.Dial("tcp", ":"+port, nil) + require.NoError(t, err) + + _, err = conn.Write([]byte("ping")) + require.NoError(t, err) + + buf := make([]byte, 64) + n, err := conn.Read(buf) + require.NoError(t, err) + + assert.Equal(t, 4, n) + assert.Equal(t, "PONG", string(buf[:4])) +} + +type fakeClientConn struct { + remoteAddr *net.TCPAddr + localAddr *net.TCPAddr +} + +func (f fakeClientConn) LocalAddr() net.Addr { + return f.localAddr +} + +func (f fakeClientConn) RemoteAddr() net.Addr { + return f.remoteAddr +} + // fakeSpiffePKI simulates a SPIFFE aware PKI and allows generating multiple valid SVIDs. type fakeSpiffePKI struct { caPrivateKey *rsa.PrivateKey diff --git a/pkg/tcp/proxy.go b/pkg/tcp/proxy.go index 33b9acd8b..aa979d303 100644 --- a/pkg/tcp/proxy.go +++ b/pkg/tcp/proxy.go @@ -2,34 +2,25 @@ package tcp import ( "errors" - "fmt" "io" "net" "syscall" "time" - "github.com/pires/go-proxyproto" "github.com/rs/zerolog/log" - "github.com/traefik/traefik/v3/pkg/config/dynamic" ) // Proxy forwards a TCP request to a TCP service. type Proxy struct { - address string - proxyProtocol *dynamic.ProxyProtocol - dialer Dialer + address string + dialer Dialer } // NewProxy creates a new Proxy. -func NewProxy(address string, proxyProtocol *dynamic.ProxyProtocol, dialer Dialer) (*Proxy, error) { - if proxyProtocol != nil && (proxyProtocol.Version < 1 || proxyProtocol.Version > 2) { - return nil, fmt.Errorf("unknown proxyProtocol version: %d", proxyProtocol.Version) - } - +func NewProxy(address string, dialer Dialer) (*Proxy, error) { return &Proxy{ - address: address, - proxyProtocol: proxyProtocol, - dialer: dialer, + address: address, + dialer: dialer, }, nil } @@ -43,7 +34,7 @@ func (p *Proxy) ServeTCP(conn WriteCloser) { // needed because of e.g. server.trackedConnection defer conn.Close() - connBackend, err := p.dialBackend() + connBackend, err := p.dialBackend(conn) if err != nil { log.Error().Err(err).Msg("Error while dialing backend") return @@ -53,14 +44,6 @@ func (p *Proxy) ServeTCP(conn WriteCloser) { defer connBackend.Close() errChan := make(chan error) - if p.proxyProtocol != nil && p.proxyProtocol.Version > 0 && p.proxyProtocol.Version < 3 { - header := proxyproto.HeaderProxyFromAddrs(byte(p.proxyProtocol.Version), conn.RemoteAddr(), conn.LocalAddr()) - if _, err := header.WriteTo(connBackend); err != nil { - log.Error().Err(err).Msg("Error while writing TCP proxy protocol headers to backend connection") - return - } - } - go p.connCopy(conn, connBackend, errChan) go p.connCopy(connBackend, conn, errChan) @@ -79,8 +62,10 @@ func (p *Proxy) ServeTCP(conn WriteCloser) { <-errChan } -func (p *Proxy) dialBackend() (WriteCloser, error) { - conn, err := p.dialer.Dial("tcp", p.address) +func (p *Proxy) dialBackend(clientConn net.Conn) (WriteCloser, error) { + // The clientConn is passed to the dialer so that it can use information from it if needed, + // to build a PROXY protocol header. + conn, err := p.dialer.Dial("tcp", p.address, clientConn) if err != nil { return nil, err } diff --git a/pkg/tcp/proxy_test.go b/pkg/tcp/proxy_test.go index e2874e8aa..34bd6cdfe 100644 --- a/pkg/tcp/proxy_test.go +++ b/pkg/tcp/proxy_test.go @@ -2,59 +2,25 @@ package tcp import ( "bytes" - "errors" "io" "net" "testing" "time" - "github.com/pires/go-proxyproto" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/traefik/traefik/v3/pkg/config/dynamic" ) -func fakeRedis(t *testing.T, listener net.Listener) { - t.Helper() - - for { - conn, err := listener.Accept() - require.NoError(t, err) - - for { - withErr := false - buf := make([]byte, 64) - if _, err := conn.Read(buf); err != nil { - withErr = true - } - - if string(buf[:4]) == "ping" { - time.Sleep(1 * time.Millisecond) - if _, err := conn.Write([]byte("PONG")); err != nil { - _ = conn.Close() - return - } - } - - if withErr { - _ = conn.Close() - return - } - } - } -} - func TestCloseWrite(t *testing.T) { backendListener, err := net.Listen("tcp", ":0") require.NoError(t, err) - go fakeRedis(t, backendListener) + go fakeServer(t, backendListener) _, port, err := net.SplitHostPort(backendListener.Addr().String()) require.NoError(t, err) - dialer := tcpDialer{&net.Dialer{}, 10 * time.Millisecond} + dialer := tcpDialer{&net.Dialer{}, 10 * time.Millisecond, nil} - proxy, err := NewProxy(":"+port, nil, dialer) + proxy, err := NewProxy(":"+port, dialer) require.NoError(t, err) proxyListener, err := net.Listen("tcp", ":0") @@ -84,90 +50,37 @@ func TestCloseWrite(t *testing.T) { buffer := bytes.NewBuffer(buf) n, err := io.Copy(buffer, conn) require.NoError(t, err) + require.Equal(t, int64(4), n) require.Equal(t, "PONG", buffer.String()) } -func TestProxyProtocol(t *testing.T) { - testCases := []struct { - desc string - version int - }{ - { - desc: "PROXY protocol v1", - version: 1, - }, - { - desc: "PROXY protocol v2", - version: 2, - }, - } +func fakeServer(t *testing.T, listener net.Listener) { + t.Helper() - for _, test := range testCases { - t.Run(test.desc, func(t *testing.T) { - backendListener, err := net.Listen("tcp", ":0") - require.NoError(t, err) + for { + conn, err := listener.Accept() + require.NoError(t, err) - var version int - proxyBackendListener := proxyproto.Listener{ - Listener: backendListener, - ValidateHeader: func(h *proxyproto.Header) error { - version = int(h.Version) - return nil - }, - Policy: func(upstream net.Addr) (proxyproto.Policy, error) { - switch test.version { - case 1, 2: - return proxyproto.USE, nil - default: - return proxyproto.REQUIRE, errors.New("unsupported version") - } - }, + for { + withErr := false + buf := make([]byte, 64) + if _, err := conn.Read(buf); err != nil { + withErr = true } - defer proxyBackendListener.Close() - go fakeRedis(t, &proxyBackendListener) - - _, port, err := net.SplitHostPort(proxyBackendListener.Addr().String()) - require.NoError(t, err) - - dialer := tcpDialer{&net.Dialer{}, 10 * time.Millisecond} - - proxy, err := NewProxy(":"+port, &dynamic.ProxyProtocol{Version: test.version}, dialer) - require.NoError(t, err) - - proxyListener, err := net.Listen("tcp", ":0") - require.NoError(t, err) - - go func() { - for { - conn, err := proxyListener.Accept() - require.NoError(t, err) - proxy.ServeTCP(conn.(*net.TCPConn)) + if string(buf[:4]) == "ping" { + time.Sleep(1 * time.Millisecond) + if _, err := conn.Write([]byte("PONG")); err != nil { + _ = conn.Close() + return } - }() + } - _, port, err = net.SplitHostPort(proxyListener.Addr().String()) - require.NoError(t, err) - - conn, err := net.Dial("tcp", ":"+port) - require.NoError(t, err) - - _, err = conn.Write([]byte("ping\n")) - require.NoError(t, err) - - err = conn.(*net.TCPConn).CloseWrite() - require.NoError(t, err) - - var buf []byte - buffer := bytes.NewBuffer(buf) - n, err := io.Copy(buffer, conn) - require.NoError(t, err) - - assert.Equal(t, int64(4), n) - assert.Equal(t, "PONG", buffer.String()) - - assert.Equal(t, test.version, version) - }) + if withErr { + _ = conn.Close() + return + } + } } } diff --git a/pkg/testhelpers/config.go b/pkg/testhelpers/config.go index ee3dbc975..afd298599 100644 --- a/pkg/testhelpers/config.go +++ b/pkg/testhelpers/config.go @@ -2,6 +2,7 @@ package testhelpers import ( "github.com/traefik/traefik/v3/pkg/config/dynamic" + otypes "github.com/traefik/traefik/v3/pkg/observability/types" ) // BuildConfiguration is a helper to create a configuration. @@ -57,9 +58,10 @@ func WithServiceName(serviceName string) func(*dynamic.Router) { func WithObservability() func(*dynamic.Router) { return func(r *dynamic.Router) { r.Observability = &dynamic.RouterObservabilityConfig{ - AccessLogs: pointer(true), - Metrics: pointer(true), - Tracing: pointer(true), + AccessLogs: pointer(true), + Metrics: pointer(true), + Tracing: pointer(true), + TraceVerbosity: otypes.MinimalVerbosity, } } } diff --git a/pkg/tls/certificate.go b/pkg/tls/certificate.go index e1e11191c..f99796783 100644 --- a/pkg/tls/certificate.go +++ b/pkg/tls/certificate.go @@ -7,7 +7,6 @@ import ( "fmt" "net/url" "os" - "sort" "strings" "github.com/rs/zerolog/log" @@ -35,14 +34,16 @@ var ( // Available CurveIDs defined at https://godoc.org/crypto/tls#CurveID, // also allowing rfc names defined at https://tools.ietf.org/html/rfc8446#section-4.2.7 CurveIDs = map[string]tls.CurveID{ - `secp256r1`: tls.CurveP256, - `CurveP256`: tls.CurveP256, - `secp384r1`: tls.CurveP384, - `CurveP384`: tls.CurveP384, - `secp521r1`: tls.CurveP521, - `CurveP521`: tls.CurveP521, - `x25519`: tls.X25519, - `X25519`: tls.X25519, + `secp256r1`: tls.CurveP256, + `CurveP256`: tls.CurveP256, + `secp384r1`: tls.CurveP384, + `CurveP384`: tls.CurveP384, + `secp521r1`: tls.CurveP521, + `CurveP521`: tls.CurveP521, + `x25519`: tls.X25519, + `X25519`: tls.X25519, + `x25519mlkem768`: tls.X25519MLKEM768, + `X25519MLKEM768`: tls.X25519MLKEM768, } ) @@ -74,68 +75,6 @@ type Certificate struct { KeyFile types.FileOrContent `json:"keyFile,omitempty" toml:"keyFile,omitempty" yaml:"keyFile,omitempty" loggable:"false"` } -// AppendCertificate appends a Certificate to a certificates map keyed by store name. -func (c *Certificate) AppendCertificate(certs map[string]map[string]*tls.Certificate, storeName string) error { - certContent, err := c.CertFile.Read() - if err != nil { - return fmt.Errorf("unable to read CertFile : %w", err) - } - - keyContent, err := c.KeyFile.Read() - if err != nil { - return fmt.Errorf("unable to read KeyFile : %w", err) - } - tlsCert, err := tls.X509KeyPair(certContent, keyContent) - if err != nil { - return fmt.Errorf("unable to generate TLS certificate : %w", err) - } - - parsedCert, _ := x509.ParseCertificate(tlsCert.Certificate[0]) - - var SANs []string - if parsedCert.Subject.CommonName != "" { - SANs = append(SANs, strings.ToLower(parsedCert.Subject.CommonName)) - } - if parsedCert.DNSNames != nil { - for _, dnsName := range parsedCert.DNSNames { - if dnsName != parsedCert.Subject.CommonName { - SANs = append(SANs, strings.ToLower(dnsName)) - } - } - } - if parsedCert.IPAddresses != nil { - for _, ip := range parsedCert.IPAddresses { - if ip.String() != parsedCert.Subject.CommonName { - SANs = append(SANs, strings.ToLower(ip.String())) - } - } - } - - // Guarantees the order to produce a unique cert key. - sort.Strings(SANs) - certKey := strings.Join(SANs, ",") - - certExists := false - if certs[storeName] == nil { - certs[storeName] = make(map[string]*tls.Certificate) - } else { - for domains := range certs[storeName] { - if domains == certKey { - certExists = true - break - } - } - } - if certExists { - log.Debug().Msgf("Skipping addition of certificate for domain(s) %q, to TLS Store %s, as it already exists for this store.", certKey, storeName) - } else { - log.Debug().Msgf("Adding certificate for domain(s) %s", certKey) - certs[storeName][certKey] = &tlsCert - } - - return err -} - // GetCertificate returns a tls.Certificate matching the configured CertFile and KeyFile. func (c *Certificate) GetCertificate() (tls.Certificate, error) { certContent, err := c.CertFile.Read() @@ -167,24 +106,6 @@ func (c *Certificate) GetCertificateFromBytes() (tls.Certificate, error) { return cert, nil } -// Set is the method to set the flag value, part of the flag.Value interface. -// Set's argument is a string to be parsed to set the flag. -// It's a comma-separated list, so we split it. -func (c *Certificates) Set(value string) error { - certificates := strings.Split(value, ";") - for _, certificate := range certificates { - files := strings.Split(certificate, ",") - if len(files) != 2 { - return fmt.Errorf("bad certificates format: %s", value) - } - *c = append(*c, Certificate{ - CertFile: types.FileOrContent(files[0]), - KeyFile: types.FileOrContent(files[1]), - }) - } - return nil -} - // GetTruncatedCertificateName truncates the certificate name. func (c *Certificate) GetTruncatedCertificateName() string { certName := c.CertFile.String() diff --git a/pkg/tls/certificate_store.go b/pkg/tls/certificate_store.go index 2ead96ccf..57979f45d 100644 --- a/pkg/tls/certificate_store.go +++ b/pkg/tls/certificate_store.go @@ -2,7 +2,7 @@ package tls import ( "crypto/tls" - "crypto/x509" + "fmt" "net" "sort" "strings" @@ -13,57 +13,40 @@ import ( "github.com/traefik/traefik/v3/pkg/safe" ) +// CertificateData holds runtime data for runtime TLS certificate handling. +type CertificateData struct { + Hash string + Certificate *tls.Certificate +} + // CertificateStore store for dynamic certificates. type CertificateStore struct { DynamicCerts *safe.Safe - DefaultCertificate *tls.Certificate + DefaultCertificate *CertificateData CertCache *cache.Cache + + ocspStapler *ocspStapler } // NewCertificateStore create a store for dynamic certificates. -func NewCertificateStore() *CertificateStore { - s := &safe.Safe{} - s.Set(make(map[string]*tls.Certificate)) +func NewCertificateStore(ocspStapler *ocspStapler) *CertificateStore { + var dynamicCerts safe.Safe + dynamicCerts.Set(make(map[string]*CertificateData)) return &CertificateStore{ - DynamicCerts: s, + DynamicCerts: &dynamicCerts, CertCache: cache.New(1*time.Hour, 10*time.Minute), + ocspStapler: ocspStapler, } } -func (c *CertificateStore) getDefaultCertificateDomains() []string { - var allCerts []string - - if c.DefaultCertificate == nil { - return allCerts - } - - x509Cert, err := x509.ParseCertificate(c.DefaultCertificate.Certificate[0]) - if err != nil { - log.Error().Err(err).Msg("Could not parse default certificate") - return allCerts - } - - if len(x509Cert.Subject.CommonName) > 0 { - allCerts = append(allCerts, x509Cert.Subject.CommonName) - } - - allCerts = append(allCerts, x509Cert.DNSNames...) - - for _, ipSan := range x509Cert.IPAddresses { - allCerts = append(allCerts, ipSan.String()) - } - - return allCerts -} - // GetAllDomains return a slice with all the certificate domain. func (c *CertificateStore) GetAllDomains() []string { allDomains := c.getDefaultCertificateDomains() // Get dynamic certificates if c.DynamicCerts != nil && c.DynamicCerts.Get() != nil { - for domain := range c.DynamicCerts.Get().(map[string]*tls.Certificate) { + for domain := range c.DynamicCerts.Get().(map[string]*CertificateData) { allDomains = append(allDomains, domain) } } @@ -71,6 +54,23 @@ func (c *CertificateStore) GetAllDomains() []string { return allDomains } +// GetDefaultCertificate returns the default certificate. +func (c *CertificateStore) GetDefaultCertificate() *tls.Certificate { + if c == nil { + return nil + } + + if c.ocspStapler != nil && c.DefaultCertificate.Hash != "" { + if staple, ok := c.ocspStapler.GetStaple(c.DefaultCertificate.Hash); ok { + // We are updating the OCSPStaple of the certificate without any synchronization + // as this should not cause any issue. + c.DefaultCertificate.Certificate.OCSPStaple = staple + } + } + + return c.DefaultCertificate.Certificate +} + // GetBestCertificate returns the best match certificate, and caches the response. func (c *CertificateStore) GetBestCertificate(clientHello *tls.ClientHelloInfo) *tls.Certificate { if c == nil { @@ -87,12 +87,21 @@ func (c *CertificateStore) GetBestCertificate(clientHello *tls.ClientHelloInfo) } if cert, ok := c.CertCache.Get(serverName); ok { - return cert.(*tls.Certificate) + certificateData := cert.(*CertificateData) + if c.ocspStapler != nil && certificateData.Hash != "" { + if staple, ok := c.ocspStapler.GetStaple(certificateData.Hash); ok { + // We are updating the OCSPStaple of the certificate without any synchronization + // as this should not cause any issue. + certificateData.Certificate.OCSPStaple = staple + } + } + + return certificateData.Certificate } - matchedCerts := map[string]*tls.Certificate{} + matchedCerts := map[string]*CertificateData{} if c.DynamicCerts != nil && c.DynamicCerts.Get() != nil { - for domains, cert := range c.DynamicCerts.Get().(map[string]*tls.Certificate) { + for domains, cert := range c.DynamicCerts.Get().(map[string]*CertificateData) { for _, certDomain := range strings.Split(domains, ",") { if matchDomain(serverName, certDomain) { matchedCerts[certDomain] = cert @@ -110,15 +119,25 @@ func (c *CertificateStore) GetBestCertificate(clientHello *tls.ClientHelloInfo) sort.Strings(keys) // cache best match - c.CertCache.SetDefault(serverName, matchedCerts[keys[len(keys)-1]]) - return matchedCerts[keys[len(keys)-1]] + certificateData := matchedCerts[keys[len(keys)-1]] + c.CertCache.SetDefault(serverName, certificateData) + + if c.ocspStapler != nil && certificateData.Hash != "" { + if staple, ok := c.ocspStapler.GetStaple(certificateData.Hash); ok { + // We are updating the OCSPStaple of the certificate without any synchronization + // as this should not cause any issue. + certificateData.Certificate.OCSPStaple = staple + } + } + + return certificateData.Certificate } return nil } // GetCertificate returns the first certificate matching all the given domains. -func (c *CertificateStore) GetCertificate(domains []string) *tls.Certificate { +func (c *CertificateStore) GetCertificate(domains []string) *CertificateData { if c == nil { return nil } @@ -127,11 +146,11 @@ func (c *CertificateStore) GetCertificate(domains []string) *tls.Certificate { domainsKey := strings.Join(domains, ",") if cert, ok := c.CertCache.Get(domainsKey); ok { - return cert.(*tls.Certificate) + return cert.(*CertificateData) } if c.DynamicCerts != nil && c.DynamicCerts.Get() != nil { - for certDomains, cert := range c.DynamicCerts.Get().(map[string]*tls.Certificate) { + for certDomains, cert := range c.DynamicCerts.Get().(map[string]*CertificateData) { if domainsKey == certDomains { c.CertCache.SetDefault(domainsKey, cert) return cert @@ -163,6 +182,91 @@ func (c *CertificateStore) ResetCache() { } } +func (c *CertificateStore) getDefaultCertificateDomains() []string { + if c.DefaultCertificate == nil { + return nil + } + + defaultCert := c.DefaultCertificate.Certificate.Leaf + + var allCerts []string + if len(defaultCert.Subject.CommonName) > 0 { + allCerts = append(allCerts, defaultCert.Subject.CommonName) + } + + allCerts = append(allCerts, defaultCert.DNSNames...) + + for _, ipSan := range defaultCert.IPAddresses { + allCerts = append(allCerts, ipSan.String()) + } + + return allCerts +} + +// appendCertificate appends a Certificate to a certificates map keyed by store name. +func appendCertificate(certs map[string]map[string]*CertificateData, subjectAltNames []string, storeName string, cert *CertificateData) { + // Guarantees the order to produce a unique cert key. + sort.Strings(subjectAltNames) + certKey := strings.Join(subjectAltNames, ",") + + certExists := false + if certs[storeName] == nil { + certs[storeName] = make(map[string]*CertificateData) + } else { + for domains := range certs[storeName] { + if domains == certKey { + certExists = true + break + } + } + } + if certExists { + log.Debug().Msgf("Skipping addition of certificate for domain(s) %q, to TLS Store %s, as it already exists for this store.", certKey, storeName) + } else { + log.Debug().Msgf("Adding certificate for domain(s) %s", certKey) + + certs[storeName][certKey] = cert + } +} + +func parseCertificate(cert *Certificate) (tls.Certificate, []string, error) { + certContent, err := cert.CertFile.Read() + if err != nil { + return tls.Certificate{}, nil, fmt.Errorf("unable to read CertFile: %w", err) + } + + keyContent, err := cert.KeyFile.Read() + if err != nil { + return tls.Certificate{}, nil, fmt.Errorf("unable to read KeyFile: %w", err) + } + + tlsCert, err := tls.X509KeyPair(certContent, keyContent) + if err != nil { + return tls.Certificate{}, nil, fmt.Errorf("unable to generate TLS certificate: %w", err) + } + + var SANs []string + if tlsCert.Leaf.Subject.CommonName != "" { + SANs = append(SANs, strings.ToLower(tlsCert.Leaf.Subject.CommonName)) + } + if tlsCert.Leaf.DNSNames != nil { + for _, dnsName := range tlsCert.Leaf.DNSNames { + if dnsName != tlsCert.Leaf.Subject.CommonName { + SANs = append(SANs, strings.ToLower(dnsName)) + } + } + } + if tlsCert.Leaf.IPAddresses != nil { + for _, ip := range tlsCert.Leaf.IPAddresses { + if ip.String() != tlsCert.Leaf.Subject.CommonName { + SANs = append(SANs, strings.ToLower(ip.String())) + } + } + } + + return tlsCert, SANs, err +} + // matchDomain returns whether the server name matches the cert domain. // The server name, from TLS SNI, must not have trailing dots (https://datatracker.ietf.org/doc/html/rfc6066#section-3). // This is enforced by https://github.com/golang/go/blob/d3d7998756c33f69706488cade1cd2b9b10a4c7f/src/crypto/tls/handshake_messages.go#L423-L427. diff --git a/pkg/tls/certificate_store_test.go b/pkg/tls/certificate_store_test.go index cbc668bd6..ef4fd3885 100644 --- a/pkg/tls/certificate_store_test.go +++ b/pkg/tls/certificate_store_test.go @@ -58,12 +58,12 @@ func TestGetBestCertificate(t *testing.T) { for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { t.Parallel() - dynamicMap := map[string]*tls.Certificate{} + dynamicMap := map[string]*CertificateData{} if test.dynamicCert != "" { cert, err := loadTestCert(test.dynamicCert, test.uppercase) require.NoError(t, err) - dynamicMap[strings.ToLower(test.dynamicCert)] = cert + dynamicMap[strings.ToLower(test.dynamicCert)] = &CertificateData{Certificate: cert} } store := &CertificateStore{ diff --git a/pkg/tls/ocsp.go b/pkg/tls/ocsp.go new file mode 100644 index 000000000..2df703fb0 --- /dev/null +++ b/pkg/tls/ocsp.go @@ -0,0 +1,208 @@ +package tls + +import ( + "bytes" + "context" + "crypto/x509" + "errors" + "fmt" + "io" + "net/http" + "time" + + "github.com/patrickmn/go-cache" + "github.com/rs/zerolog/log" + "golang.org/x/crypto/ocsp" +) + +const defaultCacheDuration = 24 * time.Hour + +type ocspEntry struct { + leaf *x509.Certificate + issuer *x509.Certificate + responders []string + nextUpdate time.Time + staple []byte +} + +// ocspStapler retrieves staples from OCSP responders and store them in an in-memory cache. +// It also updates the staples on a regular basis and before they expire. +type ocspStapler struct { + client *http.Client + cache cache.Cache + forceStapleUpdates chan struct{} + responderOverrides map[string]string +} + +// newOCSPStapler creates a new ocspStapler cache. +func newOCSPStapler(responderOverrides map[string]string) *ocspStapler { + return &ocspStapler{ + client: &http.Client{Timeout: 10 * time.Second}, + cache: *cache.New(defaultCacheDuration, 5*time.Minute), + forceStapleUpdates: make(chan struct{}, 1), + responderOverrides: responderOverrides, + } +} + +// Run updates the OCSP staples every hours. +func (o *ocspStapler) Run(ctx context.Context) { + ticker := time.NewTicker(time.Hour) + defer ticker.Stop() + + for { + select { + case <-ctx.Done(): + return + + case <-o.forceStapleUpdates: + o.updateStaples(ctx) + + case <-ticker.C: + o.updateStaples(ctx) + } + } +} + +// ForceStapleUpdates triggers staple updates in the background instead of waiting for the Run routine to update them. +func (o *ocspStapler) ForceStapleUpdates() { + select { + case o.forceStapleUpdates <- struct{}{}: + default: + } +} + +// GetStaple retrieves the OCSP staple for the corresponding to the given key (public certificate hash). +func (o *ocspStapler) GetStaple(key string) ([]byte, bool) { + if item, ok := o.cache.Get(key); ok && item != nil { + if entry, ok := item.(*ocspEntry); ok { + return entry.staple, true + } + } + return nil, false +} + +// Upsert creates a new entry for the given certificate. +// The ocspStapler will then be responsible from retrieving and updating the corresponding OCSP obtainStaple. +func (o *ocspStapler) Upsert(key string, leaf, issuer *x509.Certificate) error { + if len(leaf.OCSPServer) == 0 { + return errors.New("leaf certificate does not contain an OCSP server") + } + + if item, ok := o.cache.Get(key); ok { + o.cache.Set(key, item, cache.NoExpiration) + return nil + } + + var responders []string + for _, url := range leaf.OCSPServer { + if len(o.responderOverrides) > 0 { + if newURL, ok := o.responderOverrides[url]; ok { + url = newURL + } + } + responders = append(responders, url) + } + + o.cache.Set(key, &ocspEntry{ + leaf: leaf, + issuer: issuer, + responders: responders, + }, cache.NoExpiration) + + return nil +} + +// ResetTTL resets the expiration time for all items having no expiration. +// This allows setting a TTL for certificates that do not exist anymore in the dynamic configuration. +// For certificates that are still provided by the dynamic configuration, +// their expiration time will be unset when calling the Upsert method. +func (o *ocspStapler) ResetTTL() { + for key, item := range o.cache.Items() { + if item.Expiration > 0 { + continue + } + + o.cache.Set(key, item.Object, defaultCacheDuration) + } +} + +func (o *ocspStapler) updateStaples(ctx context.Context) { + for _, item := range o.cache.Items() { + select { + case <-ctx.Done(): + return + default: + } + + entry := item.Object.(*ocspEntry) + + if entry.staple != nil && time.Now().Before(entry.nextUpdate) { + continue + } + + if err := o.updateStaple(ctx, entry); err != nil { + log.Error().Err(err).Msgf("Unable to retieve OCSP staple for: %s", entry.leaf.Subject.CommonName) + continue + } + } +} + +// obtainStaple obtains the OCSP stable for the given leaf certificate. +func (o *ocspStapler) updateStaple(ctx context.Context, entry *ocspEntry) error { + ocspReq, err := ocsp.CreateRequest(entry.leaf, entry.issuer, nil) + if err != nil { + return fmt.Errorf("creating OCSP request: %w", err) + } + + for _, responder := range entry.responders { + logger := log.With().Str("responder", responder).Logger() + + req, err := http.NewRequestWithContext(ctx, http.MethodPost, responder, bytes.NewReader(ocspReq)) + if err != nil { + return fmt.Errorf("creating OCSP request: %w", err) + } + + req.Header.Set("Content-Type", "application/ocsp-request") + + res, err := o.client.Do(req) + if err != nil && ctx.Err() != nil { + return ctx.Err() + } + if err != nil { + logger.Debug().Err(err).Msg("Unable to obtain OCSP response") + continue + } + defer res.Body.Close() + + if res.StatusCode/100 != 2 { + logger.Debug().Msgf("Unable to obtain OCSP response due to status code: %d", res.StatusCode) + continue + } + + ocspResBytes, err := io.ReadAll(res.Body) + if err != nil { + logger.Debug().Err(err).Msg("Unable to read OCSP response bytes") + continue + } + + ocspRes, err := ocsp.ParseResponseForCert(ocspResBytes, entry.leaf, entry.issuer) + if err != nil { + logger.Debug().Err(err).Msg("Unable to parse OCSP response") + continue + } + + entry.staple = ocspResBytes + + // As per RFC 6960, the nextUpdate field is optional. + if ocspRes.NextUpdate.IsZero() { + // NextUpdate is not set, the staple should be updated on the next update. + entry.nextUpdate = time.Now() + } else { + entry.nextUpdate = ocspRes.ThisUpdate.Add(ocspRes.NextUpdate.Sub(ocspRes.ThisUpdate) / 2) + } + + return nil + } + + return errors.New("no OCSP staple obtained from any responders") +} diff --git a/pkg/tls/ocsp_test.go b/pkg/tls/ocsp_test.go new file mode 100644 index 000000000..0cbb69a89 --- /dev/null +++ b/pkg/tls/ocsp_test.go @@ -0,0 +1,485 @@ +package tls + +import ( + "crypto" + "crypto/tls" + "io" + "net/http" + "net/http/httptest" + "testing" + "time" + + "github.com/patrickmn/go-cache" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "golang.org/x/crypto/ocsp" +) + +const certWithOCSPServer = `-----BEGIN CERTIFICATE----- +MIIBgjCCASegAwIBAgICIAAwCgYIKoZIzj0EAwIwEjEQMA4GA1UEAxMHVGVzdCBD +QTAeFw0yMzAxMDExMjAwMDBaFw0yMzAyMDExMjAwMDBaMCAxHjAcBgNVBAMTFU9D +U1AgVGVzdCBDZXJ0aWZpY2F0ZTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABIoe +I/bjo34qony8LdRJD+Jhuk8/S8YHXRHl6rH9t5VFCFtX8lIPN/Ll1zCrQ2KB3Wlb +fxSgiQyLrCpZyrdhVPSjXzBdMAwGA1UdEwEB/wQCMAAwHwYDVR0jBBgwFoAU+Eo3 +5sST4LRrwS4dueIdGBZ5d7IwLAYIKwYBBQUHAQEEIDAeMBwGCCsGAQUFBzABhhBv +Y3NwLmV4YW1wbGUuY29tMAoGCCqGSM49BAMCA0kAMEYCIQDg94xY/+/VepESdvTT +ykCwiWOS2aCpjyryrKpwMKkR0AIhAPc/+ZEz4W10OENxC1t+NUTvS8JbEGOwulkZ +z9yfaLuD +-----END CERTIFICATE-----` + +const certWithoutOCSPServer = `-----BEGIN CERTIFICATE----- +MIIBUzCB+aADAgECAgIgADAKBggqhkjOPQQDAjASMRAwDgYDVQQDEwdUZXN0IENB +MB4XDTIzMDEwMTEyMDAwMFoXDTIzMDIwMTEyMDAwMFowIDEeMBwGA1UEAxMVT0NT +UCBUZXN0IENlcnRpZmljYXRlMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEih4j +9uOjfiqifLwt1EkP4mG6Tz9LxgddEeXqsf23lUUIW1fyUg838uXXMKtDYoHdaVt/ +FKCJDIusKlnKt2FU9KMxMC8wDAYDVR0TAQH/BAIwADAfBgNVHSMEGDAWgBT4Sjfm +xJPgtGvBLh254h0YFnl3sjAKBggqhkjOPQQDAgNJADBGAiEA3rWetLGblfSuNZKf +5CpZxhj3A0BjEocEh+2P+nAgIdUCIQDIgptabR1qTLQaF2u0hJsEX2IKuIUvYWH3 +6Lb92+zIHg== +-----END CERTIFICATE-----` + +// certKey is the private key for both certWithOCSPServer and certWithoutOCSPServer. +const certKey = `-----BEGIN EC PRIVATE KEY----- +MHcCAQEEINnVcgrSNh4HlThWlZpegq14M8G/p9NVDtdVjZrseUGLoAoGCCqGSM49 +AwEHoUQDQgAEih4j9uOjfiqifLwt1EkP4mG6Tz9LxgddEeXqsf23lUUIW1fyUg83 +8uXXMKtDYoHdaVt/FKCJDIusKlnKt2FU9A== +-----END EC PRIVATE KEY-----` + +// caCert is the issuing certificate for certWithOCSPServer and certWithoutOCSPServer. +const caCert = `-----BEGIN CERTIFICATE----- +MIIBazCCARGgAwIBAgICEAAwCgYIKoZIzj0EAwIwEjEQMA4GA1UEAxMHVGVzdCBD +QTAeFw0yMzAxMDExMjAwMDBaFw0yMzAyMDExMjAwMDBaMBIxEDAOBgNVBAMTB1Rl +c3QgQ0EwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAASdKexSor/aeazDM57UHhAX +rCkJxUeF2BWf0lZYCRxc3f0GdrEsVvjJW8+/E06eAzDCGSdM/08Nvun1nb6AmAlt +o1cwVTAOBgNVHQ8BAf8EBAMCAQYwEwYDVR0lBAwwCgYIKwYBBQUHAwkwDwYDVR0T +AQH/BAUwAwEB/zAdBgNVHQ4EFgQU+Eo35sST4LRrwS4dueIdGBZ5d7IwCgYIKoZI +zj0EAwIDSAAwRQIgGbA39+kETTB/YMLBFoC2fpZe1cDWfFB7TUdfINUqdH4CIQCR +ByUFC8A+hRNkK5YNH78bgjnKk/88zUQF5ONy4oPGdQ== +-----END CERTIFICATE-----` + +const caKey = `-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIDJ59ptjq3MzILH4zn5IKoH1sYn+zrUeq2kD8+DD2x+OoAoGCCqGSM49 +AwEHoUQDQgAEnSnsUqK/2nmswzOe1B4QF6wpCcVHhdgVn9JWWAkcXN39BnaxLFb4 +yVvPvxNOngMwwhknTP9PDb7p9Z2+gJgJbQ== +-----END EC PRIVATE KEY-----` + +func TestOCSPStapler_Upsert(t *testing.T) { + ocspStapler := newOCSPStapler(nil) + + issuerCert, err := tls.X509KeyPair([]byte(caCert), []byte(caKey)) + require.NoError(t, err) + + leafCert, err := tls.X509KeyPair([]byte(certWithOCSPServer), []byte(certKey)) + require.NoError(t, err) + + // Upsert a certificate without an OCSP server should raise an error. + leafCertWithoutOCSPServer, err := tls.X509KeyPair([]byte(certWithoutOCSPServer), []byte(certKey)) + require.NoError(t, err) + + err = ocspStapler.Upsert("foo", leafCertWithoutOCSPServer.Leaf, issuerCert.Leaf) + require.Error(t, err) + + // Upsert a certificate with an OCSP server. + err = ocspStapler.Upsert("foo", leafCert.Leaf, issuerCert.Leaf) + require.NoError(t, err) + + i, ok := ocspStapler.cache.Get("foo") + require.True(t, ok) + + e, ok := i.(*ocspEntry) + require.True(t, ok) + + assert.Equal(t, leafCert.Leaf, e.leaf) + assert.Equal(t, issuerCert.Leaf, e.issuer) + assert.Nil(t, e.staple) + assert.Equal(t, []string{"ocsp.example.com"}, e.responders) + assert.Equal(t, int64(0), ocspStapler.cache.Items()["foo"].Expiration) + + // Upsert an existing entry to make sure that the existing staple is preserved. + e.staple = []byte("foo") + e.nextUpdate = time.Now() + e.responders = []string{"foo.com"} + + err = ocspStapler.Upsert("foo", leafCert.Leaf, issuerCert.Leaf) + require.NoError(t, err) + + i, ok = ocspStapler.cache.Get("foo") + require.True(t, ok) + + e, ok = i.(*ocspEntry) + require.True(t, ok) + + assert.Equal(t, leafCert.Leaf, e.leaf) + assert.Equal(t, issuerCert.Leaf, e.issuer) + assert.Equal(t, []byte("foo"), e.staple) + assert.NotZero(t, e.nextUpdate) + assert.Equal(t, []string{"foo.com"}, e.responders) + assert.Equal(t, int64(0), ocspStapler.cache.Items()["foo"].Expiration) +} + +func TestOCSPStapler_Upsert_withResponderOverrides(t *testing.T) { + ocspStapler := newOCSPStapler(map[string]string{ + "ocsp.example.com": "foo.com", + }) + + issuerCert, err := tls.X509KeyPair([]byte(caCert), []byte(caKey)) + require.NoError(t, err) + + leafCert, err := tls.X509KeyPair([]byte(certWithOCSPServer), []byte(certKey)) + require.NoError(t, err) + + err = ocspStapler.Upsert("foo", leafCert.Leaf, issuerCert.Leaf) + require.NoError(t, err) + + i, ok := ocspStapler.cache.Get("foo") + require.True(t, ok) + + e, ok := i.(*ocspEntry) + require.True(t, ok) + + assert.Equal(t, leafCert.Leaf, e.leaf) + assert.Equal(t, issuerCert.Leaf, e.issuer) + assert.Nil(t, e.staple) + assert.Equal(t, []string{"foo.com"}, e.responders) +} + +func TestOCSPStapler_ResetTTL(t *testing.T) { + ocspStapler := newOCSPStapler(nil) + + issuerCert, err := tls.X509KeyPair([]byte(caCert), []byte(caKey)) + require.NoError(t, err) + + leafCert, err := tls.X509KeyPair([]byte(certWithOCSPServer), []byte(certKey)) + require.NoError(t, err) + + ocspStapler.cache.Set("foo", &ocspEntry{ + leaf: leafCert.Leaf, + issuer: issuerCert.Leaf, + responders: []string{"foo.com"}, + nextUpdate: time.Now(), + staple: []byte("foo"), + }, cache.NoExpiration) + + ocspStapler.cache.Set("bar", &ocspEntry{ + leaf: leafCert.Leaf, + issuer: issuerCert.Leaf, + responders: []string{"bar.com"}, + nextUpdate: time.Now(), + staple: []byte("bar"), + }, time.Hour) + + wantBarExpiration := ocspStapler.cache.Items()["bar"].Expiration + + ocspStapler.ResetTTL() + + item, ok := ocspStapler.cache.Items()["foo"] + require.True(t, ok) + + e, ok := item.Object.(*ocspEntry) + require.True(t, ok) + + assert.Positive(t, item.Expiration) + assert.Equal(t, leafCert.Leaf, e.leaf) + assert.Equal(t, issuerCert.Leaf, e.issuer) + assert.Equal(t, []byte("foo"), e.staple) + assert.NotZero(t, e.nextUpdate) + assert.Equal(t, []string{"foo.com"}, e.responders) + + item, ok = ocspStapler.cache.Items()["bar"] + require.True(t, ok) + + e, ok = item.Object.(*ocspEntry) + require.True(t, ok) + + assert.Equal(t, wantBarExpiration, item.Expiration) + assert.Equal(t, leafCert.Leaf, e.leaf) + assert.Equal(t, issuerCert.Leaf, e.issuer) + assert.Equal(t, []byte("bar"), e.staple) + assert.NotZero(t, e.nextUpdate) + assert.Equal(t, []string{"bar.com"}, e.responders) +} + +func TestOCSPStapler_GetStaple(t *testing.T) { + ocspStapler := newOCSPStapler(nil) + + // Get an un-existing staple. + staple, exists := ocspStapler.GetStaple("foo") + + assert.False(t, exists) + assert.Nil(t, staple) + + // Get an existing staple. + ocspStapler.cache.Set("foo", &ocspEntry{staple: []byte("foo")}, cache.NoExpiration) + + staple, exists = ocspStapler.GetStaple("foo") + + assert.True(t, exists) + assert.Equal(t, []byte("foo"), staple) +} + +func TestOCSPStapler_updateStaple(t *testing.T) { + leafCert, err := tls.X509KeyPair([]byte(certWithOCSPServer), []byte(certKey)) + require.NoError(t, err) + + issuerCert, err := tls.X509KeyPair([]byte(caCert), []byte(caKey)) + require.NoError(t, err) + + thisUpdate, err := time.Parse("2006-01-02", "2025-01-01") + require.NoError(t, err) + nextUpdate, err := time.Parse("2006-01-02", "2025-01-02") + require.NoError(t, err) + stapleUpdate := thisUpdate.Add(nextUpdate.Sub(thisUpdate) / 2) + + ocspResponseTmpl := ocsp.Response{ + SerialNumber: leafCert.Leaf.SerialNumber, + TBSResponseData: []byte("foo"), + ThisUpdate: thisUpdate, + NextUpdate: nextUpdate, + } + + ocspResponse, err := ocsp.CreateResponse(leafCert.Leaf, leafCert.Leaf, ocspResponseTmpl, issuerCert.PrivateKey.(crypto.Signer)) + require.NoError(t, err) + + handler := func(rw http.ResponseWriter, req *http.Request) { + ct := req.Header.Get("Content-Type") + assert.Equal(t, "application/ocsp-request", ct) + + reqBytes, err := io.ReadAll(req.Body) + require.NoError(t, err) + + _, err = ocsp.ParseRequest(reqBytes) + require.NoError(t, err) + + rw.Header().Set("Content-Type", "application/ocsp-response") + + _, err = rw.Write(ocspResponse) + require.NoError(t, err) + } + + responder := httptest.NewServer(http.HandlerFunc(handler)) + t.Cleanup(responder.Close) + + responderStatusNotOK := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusNotFound) + })) + t.Cleanup(responderStatusNotOK.Close) + + testCases := []struct { + desc string + entry *ocspEntry + expectError bool + }{ + { + desc: "no responder", + entry: &ocspEntry{ + leaf: leafCert.Leaf, + issuer: issuerCert.Leaf, + }, + expectError: true, + }, + { + desc: "wrong responder", + entry: &ocspEntry{ + leaf: leafCert.Leaf, + issuer: issuerCert.Leaf, + responders: []string{"http://foo.bar"}, + }, + expectError: true, + }, + { + desc: "not ok status responder", + entry: &ocspEntry{ + leaf: leafCert.Leaf, + issuer: issuerCert.Leaf, + responders: []string{responderStatusNotOK.URL}, + }, + expectError: true, + }, + { + desc: "one wrong responder, one ok", + entry: &ocspEntry{ + leaf: leafCert.Leaf, + issuer: issuerCert.Leaf, + responders: []string{"http://foo.bar", responder.URL}, + }, + }, + { + desc: "ok responder", + entry: &ocspEntry{ + leaf: leafCert.Leaf, + issuer: issuerCert.Leaf, + responders: []string{responder.URL}, + }, + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + t.Parallel() + + ocspStapler := newOCSPStapler(nil) + ocspStapler.client = &http.Client{Timeout: time.Second} + + err = ocspStapler.updateStaple(t.Context(), test.entry) + if test.expectError { + require.Error(t, err) + return + } + + require.NoError(t, err) + + assert.Equal(t, ocspResponse, test.entry.staple) + assert.Equal(t, stapleUpdate.UTC(), test.entry.nextUpdate) + }) + } +} + +func TestOCSPStapler_updateStaple_withoutNextUpdate(t *testing.T) { + leafCert, err := tls.X509KeyPair([]byte(certWithOCSPServer), []byte(certKey)) + require.NoError(t, err) + + issuerCert, err := tls.X509KeyPair([]byte(caCert), []byte(caKey)) + require.NoError(t, err) + + thisUpdate, err := time.Parse("2006-01-02", "2025-01-01") + require.NoError(t, err) + + ocspResponseTmpl := ocsp.Response{ + SerialNumber: leafCert.Leaf.SerialNumber, + TBSResponseData: []byte("foo"), + ThisUpdate: thisUpdate, + } + + ocspResponse, err := ocsp.CreateResponse(leafCert.Leaf, leafCert.Leaf, ocspResponseTmpl, issuerCert.PrivateKey.(crypto.Signer)) + require.NoError(t, err) + + handler := func(rw http.ResponseWriter, req *http.Request) { + ct := req.Header.Get("Content-Type") + assert.Equal(t, "application/ocsp-request", ct) + + reqBytes, err := io.ReadAll(req.Body) + require.NoError(t, err) + + _, err = ocsp.ParseRequest(reqBytes) + require.NoError(t, err) + + rw.Header().Set("Content-Type", "application/ocsp-response") + + _, err = rw.Write(ocspResponse) + require.NoError(t, err) + } + + responder := httptest.NewServer(http.HandlerFunc(handler)) + t.Cleanup(responder.Close) + + responderStatusNotOK := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusNotFound) + })) + t.Cleanup(responderStatusNotOK.Close) + + ocspStapler := newOCSPStapler(nil) + ocspStapler.client = &http.Client{Timeout: time.Second} + + entry := &ocspEntry{ + leaf: leafCert.Leaf, + issuer: issuerCert.Leaf, + responders: []string{responder.URL}, + } + err = ocspStapler.updateStaple(t.Context(), entry) + require.NoError(t, err) + + assert.Equal(t, ocspResponse, entry.staple) + assert.NotZero(t, entry.nextUpdate) + assert.Greater(t, time.Now(), entry.nextUpdate) +} + +func TestOCSPStapler_updateStaples(t *testing.T) { + leafCert, err := tls.X509KeyPair([]byte(certWithOCSPServer), []byte(certKey)) + require.NoError(t, err) + + issuerCert, err := tls.X509KeyPair([]byte(caCert), []byte(caKey)) + require.NoError(t, err) + + thisUpdate, err := time.Parse("2006-01-02", "2025-01-01") + require.NoError(t, err) + nextUpdate, err := time.Parse("2006-01-02", "2025-01-02") + require.NoError(t, err) + stapleUpdate := thisUpdate.Add(nextUpdate.Sub(thisUpdate) / 2) + + ocspResponseTmpl := ocsp.Response{ + SerialNumber: leafCert.Leaf.SerialNumber, + TBSResponseData: []byte("foo"), + ThisUpdate: thisUpdate, + NextUpdate: nextUpdate, + } + + ocspResponse, err := ocsp.CreateResponse(leafCert.Leaf, leafCert.Leaf, ocspResponseTmpl, issuerCert.PrivateKey.(crypto.Signer)) + require.NoError(t, err) + + handler := func(rw http.ResponseWriter, req *http.Request) { + ct := req.Header.Get("Content-Type") + assert.Equal(t, "application/ocsp-request", ct) + + reqBytes, err := io.ReadAll(req.Body) + require.NoError(t, err) + + _, err = ocsp.ParseRequest(reqBytes) + require.NoError(t, err) + + rw.Header().Set("Content-Type", "application/ocsp-response") + + _, err = rw.Write(ocspResponse) + require.NoError(t, err) + } + + responder := httptest.NewServer(http.HandlerFunc(handler)) + t.Cleanup(responder.Close) + + ocspStapler := newOCSPStapler(nil) + ocspStapler.client = &http.Client{Timeout: time.Second} + + // nil staple entry + ocspStapler.cache.Set("nilStaple", &ocspEntry{ + leaf: leafCert.Leaf, + issuer: issuerCert.Leaf, + responders: []string{responder.URL}, + nextUpdate: time.Now().Add(-time.Hour), + }, cache.NoExpiration) + // staple entry with nextUpdate in the past + ocspStapler.cache.Set("toUpdate", &ocspEntry{ + leaf: leafCert.Leaf, + issuer: issuerCert.Leaf, + responders: []string{responder.URL}, + staple: []byte("foo"), + nextUpdate: time.Now().Add(-time.Hour), + }, cache.NoExpiration) + // staple entry with nextUpdate in the future + inOneHour := time.Now().Add(time.Hour) + ocspStapler.cache.Set("noUpdate", &ocspEntry{ + leaf: leafCert.Leaf, + issuer: issuerCert.Leaf, + responders: []string{responder.URL}, + staple: []byte("foo"), + nextUpdate: inOneHour, + }, cache.NoExpiration) + + ocspStapler.updateStaples(t.Context()) + + nilStaple, ok := ocspStapler.cache.Get("nilStaple") + require.True(t, ok) + + assert.Equal(t, ocspResponse, nilStaple.(*ocspEntry).staple) + assert.Equal(t, stapleUpdate.UTC(), nilStaple.(*ocspEntry).nextUpdate) + + toUpdate, ok := ocspStapler.cache.Get("toUpdate") + require.True(t, ok) + + assert.Equal(t, ocspResponse, toUpdate.(*ocspEntry).staple) + assert.Equal(t, stapleUpdate.UTC(), nilStaple.(*ocspEntry).nextUpdate) + + noUpdate, ok := ocspStapler.cache.Get("noUpdate") + require.True(t, ok) + + assert.Equal(t, []byte("foo"), noUpdate.(*ocspEntry).staple) + assert.Equal(t, inOneHour, noUpdate.(*ocspEntry).nextUpdate) +} diff --git a/pkg/tls/tlsmanager.go b/pkg/tls/tlsmanager.go index 7fcbeab76..54d2c4cbf 100644 --- a/pkg/tls/tlsmanager.go +++ b/pkg/tls/tlsmanager.go @@ -6,14 +6,16 @@ import ( "crypto/x509" "errors" "fmt" + "hash/fnv" "slices" + "strconv" "strings" "sync" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/challenge/tlsalpn01" "github.com/rs/zerolog/log" - "github.com/traefik/traefik/v3/pkg/logs" + "github.com/traefik/traefik/v3/pkg/observability/logs" "github.com/traefik/traefik/v3/pkg/tls/generate" "github.com/traefik/traefik/v3/pkg/types" ) @@ -43,6 +45,11 @@ func getCipherSuites() []string { return ciphers } +// OCSPConfig contains the OCSP configuration. +type OCSPConfig struct { + ResponderOverrides map[string]string `description:"Defines a map of OCSP responders to replace for querying OCSP servers." json:"responderOverrides,omitempty" toml:"responderOverrides,omitempty" yaml:"responderOverrides,omitempty"` +} + // Manager is the TLS option/store/configuration factory. type Manager struct { lock sync.RWMutex @@ -50,16 +57,33 @@ type Manager struct { stores map[string]*CertificateStore configs map[string]Options certs []*CertAndStores + + // As of today, the TLS manager contains and is responsible for creating/starting the OCSP ocspStapler. + // It would likely have been a Configuration listener but this implies that certs are re-parsed. + // But this would probably have impact on resource consumption. + ocspStapler *ocspStapler } // NewManager creates a new Manager. -func NewManager() *Manager { - return &Manager{ +func NewManager(ocspConfig *OCSPConfig) *Manager { + manager := &Manager{ stores: map[string]*CertificateStore{}, configs: map[string]Options{ "default": DefaultTLSOptions, }, } + + if ocspConfig != nil { + manager.ocspStapler = newOCSPStapler(ocspConfig.ResponderOverrides) + } + + return manager +} + +func (m *Manager) Run(ctx context.Context) { + if m.ocspStapler != nil { + m.ocspStapler.Run(ctx) + } } // UpdateConfigs updates the TLS* configuration options. @@ -91,7 +115,14 @@ func (m *Manager) UpdateConfigs(ctx context.Context, stores map[string]Store, co m.storesConfig[tlsalpn01.ACMETLS1Protocol] = Store{} } - storesCertificates := make(map[string]map[string]*tls.Certificate) + storesCertificates := make(map[string]map[string]*CertificateData) + + // Define the TTL for all the cache entries with no TTL. + // This will discard entries that are not used anymore. + if m.ocspStapler != nil { + m.ocspStapler.ResetTTL() + } + for _, conf := range certs { if len(conf.Stores) == 0 { log.Ctx(ctx).Debug().MsgFunc(func() string { @@ -101,24 +132,49 @@ func (m *Manager) UpdateConfigs(ctx context.Context, stores map[string]Store, co conf.Stores = []string{DefaultTLSStoreName} } - for _, store := range conf.Stores { - logger := log.Ctx(ctx).With().Str(logs.TLSStoreName, store).Logger() + cert, SANs, err := parseCertificate(&conf.Certificate) + if err != nil { + log.Ctx(ctx).Error().Err(err).Msgf("Unable to parse certificate %s", conf.Certificate.GetTruncatedCertificateName()) + continue + } + var certHash string + if m.ocspStapler != nil && len(cert.Leaf.OCSPServer) > 0 { + certHash = hashRawCert(cert.Leaf.Raw) + + issuer := cert.Leaf + if len(cert.Certificate) > 1 { + issuer, err = x509.ParseCertificate(cert.Certificate[1]) + if err != nil { + log.Ctx(ctx).Error().Err(err).Msgf("Unable to parse issuer certificate %s", conf.Certificate.GetTruncatedCertificateName()) + continue + } + } + + if err := m.ocspStapler.Upsert(certHash, cert.Leaf, issuer); err != nil { + log.Ctx(ctx).Error().Err(err).Msgf("Unable to upsert OCSP certificate %s", conf.Certificate.GetTruncatedCertificateName()) + continue + } + } + + certData := &CertificateData{ + Certificate: &cert, + Hash: certHash, + } + + for _, store := range conf.Stores { if _, ok := m.storesConfig[store]; !ok { m.storesConfig[store] = Store{} } - err := conf.Certificate.AppendCertificate(storesCertificates, store) - if err != nil { - logger.Error().Err(err).Msgf("Unable to append certificate %s to store", conf.Certificate.GetTruncatedCertificateName()) - } + appendCertificate(storesCertificates, SANs, store, certData) } } m.stores = make(map[string]*CertificateStore) for storeName, storeConfig := range m.storesConfig { - st := NewCertificateStore() + st := NewCertificateStore(m.ocspStapler) m.stores[storeName] = st if certs, ok := storesCertificates[storeName]; ok { @@ -133,13 +189,17 @@ func (m *Manager) UpdateConfigs(ctx context.Context, stores map[string]Store, co logger := log.Ctx(ctx).With().Str(logs.TLSStoreName, storeName).Logger() ctxStore := logger.WithContext(ctx) - certificate, err := getDefaultCertificate(ctxStore, storeConfig, st) + certificate, err := m.getDefaultCertificate(ctxStore, storeConfig, st) if err != nil { logger.Error().Err(err).Msg("Error while creating certificate store") } st.DefaultCertificate = certificate } + + if m.ocspStapler != nil { + m.ocspStapler.ForceStapleUpdates() + } } // sanitizeDomains sanitizes the domain definition Main and SANS, @@ -226,7 +286,8 @@ func (m *Manager) Get(storeName, configName string) (*tls.Config, error) { } log.Debug().Msgf("Serving default certificate for request: %q", domainToCheck) - return store.DefaultCertificate, nil + + return store.GetDefaultCertificate(), nil } return tlsConfig, err @@ -245,8 +306,8 @@ func (m *Manager) GetServerCertificates() []*x509.Certificate { // We iterate over all the certificates. if defaultStore.DynamicCerts != nil && defaultStore.DynamicCerts.Get() != nil { - for _, cert := range defaultStore.DynamicCerts.Get().(map[string]*tls.Certificate) { - x509Cert, err := x509.ParseCertificate(cert.Certificate[0]) + for _, cert := range defaultStore.DynamicCerts.Get().(map[string]*CertificateData) { + x509Cert, err := x509.ParseCertificate(cert.Certificate.Certificate[0]) if err != nil { continue } @@ -256,7 +317,7 @@ func (m *Manager) GetServerCertificates() []*x509.Certificate { } if defaultStore.DefaultCertificate != nil { - x509Cert, err := x509.ParseCertificate(defaultStore.DefaultCertificate.Certificate[0]) + x509Cert, err := x509.ParseCertificate(defaultStore.DefaultCertificate.Certificate.Certificate[0]) if err != nil { return certificates } @@ -289,9 +350,9 @@ func (m *Manager) GetStore(storeName string) *CertificateStore { return m.getStore(storeName) } -func getDefaultCertificate(ctx context.Context, tlsStore Store, st *CertificateStore) (*tls.Certificate, error) { +func (m *Manager) getDefaultCertificate(ctx context.Context, tlsStore Store, st *CertificateStore) (*CertificateData, error) { if tlsStore.DefaultCertificate != nil { - cert, err := buildDefaultCertificate(tlsStore.DefaultCertificate) + cert, err := m.buildDefaultCertificate(tlsStore.DefaultCertificate) if err != nil { return nil, err } @@ -304,22 +365,65 @@ func getDefaultCertificate(ctx context.Context, tlsStore Store, st *CertificateS return nil, err } + defaultCertificate := &CertificateData{ + Certificate: defaultCert, + } + if tlsStore.DefaultGeneratedCert != nil && tlsStore.DefaultGeneratedCert.Domain != nil && tlsStore.DefaultGeneratedCert.Resolver != "" { domains, err := sanitizeDomains(*tlsStore.DefaultGeneratedCert.Domain) if err != nil { - return defaultCert, fmt.Errorf("falling back to the internal generated certificate because invalid domains: %w", err) + return defaultCertificate, fmt.Errorf("falling back to the internal generated certificate because invalid domains: %w", err) } defaultACMECert := st.GetCertificate(domains) if defaultACMECert == nil { - return defaultCert, fmt.Errorf("unable to find certificate for domains %q: falling back to the internal generated certificate", strings.Join(domains, ",")) + return defaultCertificate, fmt.Errorf("unable to find certificate for domains %q: falling back to the internal generated certificate", strings.Join(domains, ",")) } return defaultACMECert, nil } log.Ctx(ctx).Debug().Msg("No default certificate, fallback to the internal generated certificate") - return defaultCert, nil + return defaultCertificate, nil +} + +func (m *Manager) buildDefaultCertificate(defaultCertificate *Certificate) (*CertificateData, error) { + certFile, err := defaultCertificate.CertFile.Read() + if err != nil { + return nil, fmt.Errorf("failed to get cert file content: %w", err) + } + + keyFile, err := defaultCertificate.KeyFile.Read() + if err != nil { + return nil, fmt.Errorf("failed to get key file content: %w", err) + } + + cert, err := tls.X509KeyPair(certFile, keyFile) + if err != nil { + return nil, fmt.Errorf("failed to load X509 key pair: %w", err) + } + + var certHash string + if m.ocspStapler != nil && len(cert.Leaf.OCSPServer) > 0 { + certHash = hashRawCert(cert.Leaf.Raw) + + issuer := cert.Leaf + if len(cert.Certificate) > 1 { + issuer, err = x509.ParseCertificate(cert.Certificate[1]) + if err != nil { + return nil, fmt.Errorf("parsing issuer certificate %s: %w", defaultCertificate.GetTruncatedCertificateName(), err) + } + } + + if err := m.ocspStapler.Upsert(certHash, cert.Leaf, issuer); err != nil { + return nil, fmt.Errorf("upserting OCSP certificate %s: %w", defaultCertificate.GetTruncatedCertificateName(), err) + } + } + + return &CertificateData{ + Certificate: &cert, + Hash: certHash, + }, nil } // creates a TLS config that allows terminating HTTPS for multiple domains using SNI. @@ -412,20 +516,10 @@ func buildTLSConfig(tlsOption Options) (*tls.Config, error) { return conf, nil } -func buildDefaultCertificate(defaultCertificate *Certificate) (*tls.Certificate, error) { - certFile, err := defaultCertificate.CertFile.Read() - if err != nil { - return nil, fmt.Errorf("failed to get cert file content: %w", err) - } +func hashRawCert(rawCert []byte) string { + hasher := fnv.New64() - keyFile, err := defaultCertificate.KeyFile.Read() - if err != nil { - return nil, fmt.Errorf("failed to get key file content: %w", err) - } - - cert, err := tls.X509KeyPair(certFile, keyFile) - if err != nil { - return nil, fmt.Errorf("failed to load X509 key pair: %w", err) - } - return &cert, nil + // purposely ignoring the error, as no error can be returned from the implementation. + _, _ = hasher.Write(rawCert) + return strconv.FormatUint(hasher.Sum64(), 16) } diff --git a/pkg/tls/tlsmanager_test.go b/pkg/tls/tlsmanager_test.go index f6df9c3c8..1cee20276 100644 --- a/pkg/tls/tlsmanager_test.go +++ b/pkg/tls/tlsmanager_test.go @@ -2,14 +2,21 @@ package tls import ( "context" + "crypto" "crypto/tls" "crypto/x509" "encoding/pem" + "io" + "net/http" + "net/http/httptest" "testing" + "time" + "github.com/patrickmn/go-cache" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/traefik/traefik/v3/pkg/types" + "golang.org/x/crypto/ocsp" ) // LocalhostCert is a PEM-encoded TLS cert with SAN IPs @@ -77,10 +84,10 @@ func TestTLSInStore(t *testing.T) { }, }} - tlsManager := NewManager() - tlsManager.UpdateConfigs(context.Background(), nil, nil, dynamicConfigs) + tlsManager := NewManager(nil) + tlsManager.UpdateConfigs(t.Context(), nil, nil, dynamicConfigs) - certs := tlsManager.GetStore("default").DynamicCerts.Get().(map[string]*tls.Certificate) + certs := tlsManager.GetStore("default").DynamicCerts.Get().(map[string]*CertificateData) if len(certs) == 0 { t.Fatal("got error: default store must have TLS certificates.") } @@ -94,8 +101,8 @@ func TestTLSInvalidStore(t *testing.T) { }, }} - tlsManager := NewManager() - tlsManager.UpdateConfigs(context.Background(), + tlsManager := NewManager(nil) + tlsManager.UpdateConfigs(t.Context(), map[string]Store{ "default": { DefaultCertificate: &Certificate{ @@ -105,7 +112,7 @@ func TestTLSInvalidStore(t *testing.T) { }, }, nil, dynamicConfigs) - certs := tlsManager.GetStore("default").DynamicCerts.Get().(map[string]*tls.Certificate) + certs := tlsManager.GetStore("default").DynamicCerts.Get().(map[string]*CertificateData) if len(certs) == 0 { t.Fatal("got error: default store must have TLS certificates.") } @@ -158,8 +165,8 @@ func TestManager_Get(t *testing.T) { }, } - tlsManager := NewManager() - tlsManager.UpdateConfigs(context.Background(), nil, tlsConfigs, dynamicConfigs) + tlsManager := NewManager(nil) + tlsManager.UpdateConfigs(t.Context(), nil, tlsConfigs, dynamicConfigs) for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { @@ -297,8 +304,8 @@ func TestClientAuth(t *testing.T) { }, } - tlsManager := NewManager() - tlsManager.UpdateConfigs(context.Background(), nil, tlsConfigs, nil) + tlsManager := NewManager(nil) + tlsManager.UpdateConfigs(t.Context(), nil, tlsConfigs, nil) for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { @@ -324,8 +331,108 @@ func TestClientAuth(t *testing.T) { } } +func TestManager_UpdateConfigs_OCSPConfig(t *testing.T) { + leafCert, err := tls.X509KeyPair([]byte(certWithOCSPServer), []byte(certKey)) + require.NoError(t, err) + + issuerCert, err := tls.X509KeyPair([]byte(caCert), []byte(caKey)) + require.NoError(t, err) + + thisUpdate, err := time.Parse("2006-01-02", "2025-01-01") + require.NoError(t, err) + nextUpdate, err := time.Parse("2006-01-02", "2025-01-02") + require.NoError(t, err) + + ocspResponseTmpl := ocsp.Response{ + SerialNumber: leafCert.Leaf.SerialNumber, + TBSResponseData: []byte("foo"), + ThisUpdate: thisUpdate, + NextUpdate: nextUpdate, + } + + ocspResponse, err := ocsp.CreateResponse(leafCert.Leaf, leafCert.Leaf, ocspResponseTmpl, issuerCert.PrivateKey.(crypto.Signer)) + require.NoError(t, err) + + responderCall := make(chan struct{}) + + handler := func(rw http.ResponseWriter, req *http.Request) { + ct := req.Header.Get("Content-Type") + assert.Equal(t, "application/ocsp-request", ct) + + reqBytes, err := io.ReadAll(req.Body) + require.NoError(t, err) + + _, err = ocsp.ParseRequest(reqBytes) + require.NoError(t, err) + + rw.Header().Set("Content-Type", "application/ocsp-response") + + _, err = rw.Write(ocspResponse) + require.NoError(t, err) + + responderCall <- struct{}{} + } + + responder := httptest.NewServer(http.HandlerFunc(handler)) + t.Cleanup(responder.Close) + + testContext, cancel := context.WithCancel(t.Context()) + t.Cleanup(cancel) + + tlsManager := NewManager(&OCSPConfig{ + ResponderOverrides: map[string]string{ + "ocsp.example.com": responder.URL, + }, + }) + + go tlsManager.Run(testContext) + + tlsManager.ocspStapler.cache.Set("existing", &ocspEntry{ + leaf: leafCert.Leaf, + issuer: issuerCert.Leaf, + staple: []byte("foo"), + nextUpdate: time.Now().Add(time.Hour), + }, cache.NoExpiration) + tlsManager.ocspStapler.cache.Set("existingWithTTL", &ocspEntry{ + leaf: leafCert.Leaf, + issuer: issuerCert.Leaf, + staple: []byte("foo"), + nextUpdate: time.Now().Add(time.Hour), + }, 2*defaultCacheDuration) + + tlsManager.UpdateConfigs(testContext, nil, nil, []*CertAndStores{ + { + Certificate: Certificate{ + CertFile: certWithOCSPServer, + KeyFile: certKey, + }, + }, + }) + + // Asserting that UpdateConfigs resets the expiration for existing entries. + _, expiration, ok := tlsManager.ocspStapler.cache.GetWithExpiration("existing") + require.True(t, ok) + assert.Greater(t, expiration, time.Now()) + // But not for entries with TTL already set. + _, expiration, ok = tlsManager.ocspStapler.cache.GetWithExpiration("existingWithTTL") + require.True(t, ok) + assert.Greater(t, expiration, time.Now().Add(defaultCacheDuration)) + + select { + case <-responderCall: + case <-time.After(3 * time.Second): + t.Fatal("Timeout waiting for OCSP responder call") + } + + assert.Len(t, tlsManager.ocspStapler.cache.Items(), 3) + + certHash := hashRawCert(leafCert.Leaf.Raw) + _, ok = tlsManager.ocspStapler.cache.Get(certHash) + require.True(t, ok) +} + func TestManager_Get_DefaultValues(t *testing.T) { - tlsManager := NewManager() + tlsManager := NewManager(nil) // Ensures we won't break things for Traefik users when updating Go config, _ := tlsManager.Get("default", "default") diff --git a/pkg/types/k8sdetector.go b/pkg/types/k8sdetector.go new file mode 100644 index 000000000..3031783ee --- /dev/null +++ b/pkg/types/k8sdetector.go @@ -0,0 +1,70 @@ +package types + +import ( + "context" + "errors" + "fmt" + "os" + "strings" + + "github.com/rs/zerolog/log" + "go.opentelemetry.io/otel/sdk/resource" + semconv "go.opentelemetry.io/otel/semconv/v1.37.0" + kerror "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + kclientset "k8s.io/client-go/kubernetes" + "k8s.io/client-go/rest" +) + +// K8sAttributesDetector detects the metadata of the Traefik pod running in a Kubernetes cluster. +// It reads the pod name from the hostname file and the namespace from the service account namespace file and queries the Kubernetes API to get the pod's UID. +type K8sAttributesDetector struct{} + +func (K8sAttributesDetector) Detect(ctx context.Context) (*resource.Resource, error) { + attrs := os.Getenv("OTEL_RESOURCE_ATTRIBUTES") + if strings.Contains(attrs, string(semconv.K8SPodNameKey)) || strings.Contains(attrs, string(semconv.K8SPodUIDKey)) { + return resource.Empty(), nil + } + + // The InClusterConfig function returns a config for the Kubernetes API server + // when it is running inside a Kubernetes cluster. + config, err := rest.InClusterConfig() + if err != nil && errors.Is(err, rest.ErrNotInCluster) { + return resource.Empty(), nil + } + if err != nil { + return nil, fmt.Errorf("creating in cluster config: %w", err) + } + + client, err := kclientset.NewForConfig(config) + if err != nil { + return nil, fmt.Errorf("creating Kubernetes client: %w", err) + } + + podName, err := os.Hostname() + if err != nil { + return nil, fmt.Errorf("getting pod name: %w", err) + } + + podNamespaceBytes, err := os.ReadFile("/var/run/secrets/kubernetes.io/serviceaccount/namespace") + if err != nil { + return nil, fmt.Errorf("getting pod namespace: %w", err) + } + podNamespace := string(podNamespaceBytes) + + pod, err := client.CoreV1().Pods(podNamespace).Get(ctx, podName, metav1.GetOptions{}) + if err != nil && (kerror.IsForbidden(err) || kerror.IsNotFound(err)) { + log.Error().Err(err).Msg("Unable to build K8s resource attributes for Traefik pod") + return resource.Empty(), nil + } + if err != nil { + return nil, fmt.Errorf("getting pod metadata: %w", err) + } + + // To avoid version conflicts with other detectors, we use a Schemaless resource. + return resource.NewSchemaless( + semconv.K8SPodUID(string(pod.UID)), + semconv.K8SPodName(pod.Name), + semconv.K8SNamespaceName(podNamespace), + ), nil +} diff --git a/pkg/types/tls_test.go b/pkg/types/tls_test.go index b123aba53..615267644 100644 --- a/pkg/types/tls_test.go +++ b/pkg/types/tls_test.go @@ -1,7 +1,6 @@ package types import ( - "context" "testing" "github.com/stretchr/testify/assert" @@ -105,7 +104,7 @@ func TestClientTLS_CreateTLSConfig(t *testing.T) { for _, test := range tests { t.Run(test.desc, func(t *testing.T) { - tlsConfig, err := test.clientTLS.CreateTLSConfig(context.Background()) + tlsConfig, err := test.clientTLS.CreateTLSConfig(t.Context()) if test.wantErr { require.Error(t, err) return diff --git a/pkg/udp/conn.go b/pkg/udp/conn.go index 69ba8ebe2..36778f648 100644 --- a/pkg/udp/conn.go +++ b/pkg/udp/conn.go @@ -34,7 +34,7 @@ type Listener struct { timeout time.Duration } -// Creates a new listener from PacketConn. +// ListenPacketConn creates a new listener from PacketConn. func ListenPacketConn(packetConn net.PacketConn, timeout time.Duration) (*Listener, error) { if timeout <= 0 { return nil, errors.New("timeout should be greater than zero") diff --git a/pkg/udp/conn_test.go b/pkg/udp/conn_test.go index fbcd080b3..3e9459102 100644 --- a/pkg/udp/conn_test.go +++ b/pkg/udp/conn_test.go @@ -1,11 +1,9 @@ package udp import ( - "crypto/rand" "errors" "io" "net" - "runtime" "testing" "time" @@ -252,13 +250,19 @@ func TestShutdown(t *testing.T) { // Start sending packets, to create a "session" with the server. requireEcho(t, "TEST", conn, time.Second) + shutdownStartedChan := make(chan struct{}) doneChan := make(chan struct{}) go func() { + close(shutdownStartedChan) err := l.Shutdown(5 * time.Second) require.NoError(t, err) close(doneChan) }() + // Wait until shutdown has started, and hopefully after 100 ms the listener has stopped accepting new sessions. + <-shutdownStartedChan + time.Sleep(100 * time.Millisecond) + // Make sure that our session is still live even after the shutdown. requireEcho(t, "TEST2", conn, time.Second) @@ -303,58 +307,6 @@ func TestShutdown(t *testing.T) { } } -func TestReadLoopMaxDataSize(t *testing.T) { - if runtime.GOOS == "darwin" { - // sudo sysctl -w net.inet.udp.maxdgram=65507 - t.Skip("Skip test on darwin as the maximum dgram size is set to 9216 bytes by default") - } - - // Theoretical maximum size of data in a UDP datagram. - // 65535 − 8 (UDP header) − 20 (IP header). - dataSize := 65507 - - doneCh := make(chan struct{}) - - l, err := Listen(net.ListenConfig{}, "udp", ":0", 3*time.Second) - require.NoError(t, err) - - defer func() { - err := l.Close() - require.NoError(t, err) - }() - - go func() { - defer close(doneCh) - - conn, err := l.Accept() - require.NoError(t, err) - - buffer := make([]byte, dataSize) - - n, err := conn.Read(buffer) - require.NoError(t, err) - - assert.Equal(t, dataSize, n) - }() - - c, err := net.Dial("udp", l.Addr().String()) - require.NoError(t, err) - - data := make([]byte, dataSize) - - _, err = rand.Read(data) - require.NoError(t, err) - - _, err = c.Write(data) - require.NoError(t, err) - - select { - case <-doneCh: - case <-time.Tick(5 * time.Second): - t.Fatal("Timeout waiting for datagram read") - } -} - // 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, diff --git a/pkg/updater/provider.go b/pkg/updater/provider.go new file mode 100644 index 000000000..26d1fc469 --- /dev/null +++ b/pkg/updater/provider.go @@ -0,0 +1,49 @@ +package updater + +import ( + "bytes" + "encoding/json" + "net/http" + + "github.com/rs/zerolog/log" + "github.com/traefik/traefik/v3/pkg/config/dynamic" + "github.com/traefik/traefik/v3/pkg/config/static" + "github.com/traefik/traefik/v3/pkg/safe" +) + +type Updater struct { + callbackUrls []string +} + +func New(config *static.Configuration) *Updater { + updater := &Updater{ + callbackUrls: config.Global.UpdaterCallbacks, + } + + return updater +} + +func (u *Updater) HandleConfigUpdate(cfg dynamic.Configuration) { + body, err := json.Marshal(cfg) + + if err != nil { + // should never happen? + log.Error().Err(err).Msg("Error while marshalling dynamic configuration data to json") + return + } + + requestBody := bytes.NewBuffer(body) + + for _, url := range u.callbackUrls { + safe.Go(func() { + resp, err := http.Post(url, "application/json", requestBody) + + if err != nil { + log.Error().Err(err).Str("url", url).Msg("Error while sending configuration data to callback") + } else { + log.Debug().Str("url", url).Msg("Configuration data sent") + resp.Body.Close() + } + }) + } +} diff --git a/schema.json b/schema.json new file mode 100644 index 000000000..0f8823ad9 --- /dev/null +++ b/schema.json @@ -0,0 +1,1851 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://json.schemastore.org/traefik-v3.json", + "$defs": { + "CertificateResolverTailscaleStruct": { + "additionalProperties": false, + "type": "object" + }, + "acmeConfiguration": { + "additionalProperties": false, + "properties": { + "caCertificates": { + "items": { + "type": "string" + }, + "type": ["array", "null"] + }, + "caServer": { + "type": "string" + }, + "caServerName": { + "type": "string" + }, + "caSystemCertPool": { + "type": "boolean" + }, + "certificatesDuration": { + "type": "integer" + }, + "dnsChallenge": { + "$ref": "#/$defs/acmeDNSChallenge" + }, + "eab": { + "$ref": "#/$defs/acmeEAB" + }, + "email": { + "type": "string" + }, + "httpChallenge": { + "$ref": "#/$defs/acmeHTTPChallenge" + }, + "keyType": { + "type": "string" + }, + "preferredChain": { + "type": "string" + }, + "storage": { + "type": "string" + }, + "tlsChallenge": { + "$ref": "#/$defs/acmeTLSChallenge" + } + }, + "type": "object" + }, + "acmeDNSChallenge": { + "additionalProperties": false, + "properties": { + "delayBeforeCheck": { + "type": "string" + }, + "disablePropagationCheck": { + "type": "boolean" + }, + "propagation": { + "$ref": "#/$defs/acmePropagation" + }, + "provider": { + "type": "string" + }, + "resolvers": { + "items": { + "type": "string" + }, + "type": ["array", "null"] + } + }, + "type": "object" + }, + "acmeEAB": { + "additionalProperties": false, + "properties": { + "hmacEncoded": { + "type": "string" + }, + "kid": { + "type": "string" + } + }, + "type": "object" + }, + "acmeHTTPChallenge": { + "additionalProperties": false, + "properties": { + "entryPoint": { + "type": "string" + } + }, + "type": "object" + }, + "acmePropagation": { + "additionalProperties": false, + "properties": { + "delayBeforeChecks": { + "type": "string" + }, + "disableANSChecks": { + "type": "boolean" + }, + "disableChecks": { + "type": "boolean" + }, + "requireAllRNS": { + "type": "boolean" + } + }, + "type": "object" + }, + "acmeTLSChallenge": { + "additionalProperties": false, + "type": "object" + }, + "consulProviderBuilder": { + "additionalProperties": false, + "properties": { + "endpoints": { + "items": { + "type": "string" + }, + "type": ["array", "null"] + }, + "namespaces": { + "items": { + "type": "string" + }, + "type": ["array", "null"] + }, + "rootKey": { + "type": "string" + }, + "tls": { + "$ref": "#/$defs/typesClientTLS" + }, + "token": { + "type": "string" + } + }, + "type": "object" + }, + "consulcatalogEndpointConfig": { + "additionalProperties": false, + "properties": { + "address": { + "type": "string" + }, + "datacenter": { + "type": "string" + }, + "endpointWaitTime": { + "type": "string" + }, + "httpAuth": { + "$ref": "#/$defs/consulcatalogEndpointHTTPAuthConfig" + }, + "scheme": { + "type": "string" + }, + "tls": { + "$ref": "#/$defs/typesClientTLS" + }, + "token": { + "type": "string" + } + }, + "type": "object" + }, + "consulcatalogEndpointHTTPAuthConfig": { + "additionalProperties": false, + "properties": { + "password": { + "type": "string" + }, + "username": { + "type": "string" + } + }, + "type": "object" + }, + "consulcatalogProviderBuilder": { + "additionalProperties": false, + "properties": { + "cache": { + "type": "boolean" + }, + "connectAware": { + "type": "boolean" + }, + "connectByDefault": { + "type": "boolean" + }, + "constraints": { + "type": "string" + }, + "defaultRule": { + "type": "string" + }, + "endpoint": { + "$ref": "#/$defs/consulcatalogEndpointConfig" + }, + "exposedByDefault": { + "type": "boolean" + }, + "namespaces": { + "items": { + "type": "string" + }, + "type": ["array", "null"] + }, + "prefix": { + "type": "string" + }, + "refreshInterval": { + "type": "string" + }, + "requireConsistent": { + "type": "boolean" + }, + "serviceName": { + "type": "string" + }, + "stale": { + "type": "boolean" + }, + "strictChecks": { + "items": { + "type": "string" + }, + "type": ["array", "null"] + }, + "watch": { + "type": "boolean" + } + }, + "type": "object" + }, + "crdProvider": { + "additionalProperties": false, + "properties": { + "allowCrossNamespace": { + "type": "boolean" + }, + "allowEmptyServices": { + "type": "boolean" + }, + "allowExternalNameServices": { + "type": "boolean" + }, + "certAuthFilePath": { + "type": "string" + }, + "disableClusterScopeResources": { + "type": "boolean" + }, + "endpoint": { + "type": "string" + }, + "ingressClass": { + "type": "string" + }, + "labelSelector": { + "type": "string" + }, + "namespaces": { + "items": { + "type": "string" + }, + "type": ["array", "null"] + }, + "nativeLBByDefault": { + "type": "boolean" + }, + "throttleDuration": { + "type": "string" + }, + "token": { + "type": "string" + } + }, + "type": "object" + }, + "dockerProvider": { + "additionalProperties": false, + "properties": { + "allowEmptyServices": { + "type": "boolean" + }, + "constraints": { + "type": "string" + }, + "defaultRule": { + "type": "string" + }, + "endpoint": { + "type": "string" + }, + "exposedByDefault": { + "type": "boolean" + }, + "httpClientTimeout": { + "type": "string" + }, + "network": { + "type": "string" + }, + "password": { + "type": "string" + }, + "tls": { + "$ref": "#/$defs/typesClientTLS" + }, + "useBindPortIP": { + "type": "boolean" + }, + "username": { + "type": "string" + }, + "watch": { + "type": "boolean" + }, + "labelMap": { + "type": "array", + "items": { + "type": "object", + "properties": { + "from": { + "type": "string", + "description": "Shorthand label." + }, + "to": { + "type": "string", + "description": "Full label with templates." + }, + "value": { + "type": "string", + "description": "Optional override; used instead of user input if set." + } + }, + "required": ["from", "to"], + "additionalProperties": false + } + } + }, + "type": "object" + }, + "dockerSwarmProvider": { + "additionalProperties": false, + "properties": { + "allowEmptyServices": { + "type": "boolean" + }, + "constraints": { + "type": "string" + }, + "defaultRule": { + "type": "string" + }, + "endpoint": { + "type": "string" + }, + "exposedByDefault": { + "type": "boolean" + }, + "httpClientTimeout": { + "type": "string" + }, + "network": { + "type": "string" + }, + "password": { + "type": "string" + }, + "refreshSeconds": { + "type": "string" + }, + "tls": { + "$ref": "#/$defs/typesClientTLS" + }, + "useBindPortIP": { + "type": "boolean" + }, + "username": { + "type": "string" + }, + "watch": { + "type": "boolean" + } + }, + "type": "object" + }, + "ecsProvider": { + "additionalProperties": false, + "properties": { + "accessKeyID": { + "type": "string" + }, + "autoDiscoverClusters": { + "type": "boolean" + }, + "clusters": { + "items": { + "type": "string" + }, + "type": ["array", "null"] + }, + "constraints": { + "type": "string" + }, + "defaultRule": { + "type": "string" + }, + "ecsAnywhere": { + "type": "boolean" + }, + "exposedByDefault": { + "type": "boolean" + }, + "healthyTasksOnly": { + "type": "boolean" + }, + "refreshSeconds": { + "type": "integer" + }, + "region": { + "type": "string" + }, + "secretAccessKey": { + "type": "string" + } + }, + "type": "object" + }, + "etcdProvider": { + "additionalProperties": false, + "properties": { + "endpoints": { + "items": { + "type": "string" + }, + "type": ["array", "null"] + }, + "password": { + "type": "string" + }, + "rootKey": { + "type": "string" + }, + "tls": { + "$ref": "#/$defs/typesClientTLS" + }, + "username": { + "type": "string" + } + }, + "type": "object" + }, + "fileProvider": { + "additionalProperties": false, + "properties": { + "debugLogGeneratedTemplate": { + "type": "boolean" + }, + "directory": { + "type": "string" + }, + "filename": { + "type": "string" + }, + "watch": { + "type": "boolean" + } + }, + "type": "object" + }, + "gatewayProvider": { + "additionalProperties": false, + "properties": { + "certAuthFilePath": { + "type": "string" + }, + "endpoint": { + "type": "string" + }, + "experimentalChannel": { + "type": "boolean" + }, + "labelSelector": { + "type": "string" + }, + "namespaces": { + "items": { + "type": "string" + }, + "type": ["array", "null"] + }, + "nativeLBByDefault": { + "type": "boolean" + }, + "statusAddress": { + "$ref": "#/$defs/gatewayStatusAddress" + }, + "throttleDuration": { + "type": "string" + }, + "token": { + "type": "string" + } + }, + "type": "object" + }, + "gatewayServiceRef": { + "additionalProperties": false, + "properties": { + "name": { + "type": "string" + }, + "namespace": { + "type": "string" + } + }, + "type": "object" + }, + "gatewayStatusAddress": { + "additionalProperties": false, + "properties": { + "hostname": { + "type": "string" + }, + "ip": { + "type": "string" + }, + "service": { + "$ref": "#/$defs/gatewayServiceRef" + } + }, + "type": "object" + }, + "httpProvider": { + "additionalProperties": false, + "properties": { + "endpoint": { + "type": "string" + }, + "headers": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "pollInterval": { + "type": "string" + }, + "pollTimeout": { + "type": "string" + }, + "tls": { + "$ref": "#/$defs/typesClientTLS" + } + }, + "required": ["endpoint"], + "type": "object" + }, + "ingressEndpointIngress": { + "additionalProperties": false, + "properties": { + "hostname": { + "type": "string" + }, + "ip": { + "type": "string" + }, + "publishedService": { + "type": "string" + } + }, + "type": "object" + }, + "ingressProvider": { + "additionalProperties": false, + "properties": { + "allowEmptyServices": { + "type": "boolean" + }, + "allowExternalNameServices": { + "type": "boolean" + }, + "certAuthFilePath": { + "type": "string" + }, + "disableClusterScopeResources": { + "type": "boolean" + }, + "disableIngressClassLookup": { + "type": "boolean" + }, + "endpoint": { + "type": "string" + }, + "ingressClass": { + "type": "string" + }, + "ingressEndpoint": { + "$ref": "#/$defs/ingressEndpointIngress" + }, + "labelSelector": { + "type": "string" + }, + "namespaces": { + "items": { + "type": "string" + }, + "type": ["array", "null"] + }, + "nativeLBByDefault": { + "type": "boolean" + }, + "throttleDuration": { + "type": "string" + }, + "token": { + "type": "string" + } + }, + "type": "object" + }, + "nomadEndpointConfig": { + "additionalProperties": false, + "properties": { + "address": { + "type": "string" + }, + "endpointWaitTime": { + "type": "string" + }, + "region": { + "type": "string" + }, + "tls": { + "$ref": "#/$defs/typesClientTLS" + }, + "token": { + "type": "string" + } + }, + "type": "object" + }, + "nomadProviderBuilder": { + "additionalProperties": false, + "properties": { + "allowEmptyServices": { + "type": "boolean" + }, + "constraints": { + "type": "string" + }, + "defaultRule": { + "type": "string" + }, + "endpoint": { + "$ref": "#/$defs/nomadEndpointConfig" + }, + "exposedByDefault": { + "type": "boolean" + }, + "namespaces": { + "items": { + "type": "string" + }, + "type": ["array", "null"] + }, + "prefix": { + "type": "string" + }, + "refreshInterval": { + "type": "string" + }, + "stale": { + "type": "boolean" + }, + "throttleDuration": { + "type": "string" + }, + "watch": { + "type": "boolean" + } + }, + "type": "object" + }, + "pingHandler": { + "additionalProperties": false, + "properties": { + "entryPoint": { + "type": "string" + }, + "manualRouting": { + "type": "boolean" + }, + "terminatingStatusCode": { + "type": "integer" + } + }, + "type": "object" + }, + "pluginsDescriptor": { + "additionalProperties": false, + "properties": { + "moduleName": { + "type": "string" + }, + "settings": { + "$ref": "#/$defs/pluginsSettings" + }, + "version": { + "type": "string" + } + }, + "type": "object" + }, + "pluginsLocalDescriptor": { + "additionalProperties": false, + "properties": { + "moduleName": { + "type": "string" + }, + "settings": { + "$ref": "#/$defs/pluginsSettings" + } + }, + "type": "object" + }, + "pluginsSettings": { + "additionalProperties": false, + "properties": { + "envs": { + "items": { + "type": "string" + }, + "type": ["array", "null"] + }, + "mounts": { + "items": { + "type": "string" + }, + "type": ["array", "null"] + } + }, + "type": "object" + }, + "redisProvider": { + "additionalProperties": false, + "properties": { + "db": { + "type": "integer" + }, + "endpoints": { + "items": { + "type": "string" + }, + "type": ["array", "null"] + }, + "password": { + "type": "string" + }, + "rootKey": { + "type": "string" + }, + "sentinel": { + "$ref": "#/$defs/redisSentinel" + }, + "tls": { + "$ref": "#/$defs/typesClientTLS" + }, + "username": { + "type": "string" + } + }, + "type": "object" + }, + "redisSentinel": { + "additionalProperties": false, + "properties": { + "latencyStrategy": { + "type": "boolean" + }, + "masterName": { + "type": "string" + }, + "password": { + "type": "string" + }, + "randomStrategy": { + "type": "boolean" + }, + "replicaStrategy": { + "type": "boolean" + }, + "useDisconnectedReplicas": { + "type": "boolean" + }, + "username": { + "type": "string" + } + }, + "type": "object" + }, + "restProvider": { + "additionalProperties": false, + "properties": { + "insecure": { + "type": "boolean" + } + }, + "type": "object" + }, + "staticAPI": { + "additionalProperties": false, + "properties": { + "basePath": { + "type": "string" + }, + "dashboard": { + "type": "boolean" + }, + "debug": { + "type": "boolean" + }, + "disableDashboardAd": { + "type": "boolean" + }, + "insecure": { + "type": "boolean" + } + }, + "type": "object" + }, + "staticCertificateResolver": { + "additionalProperties": false, + "properties": { + "acme": { + "$ref": "#/$defs/acmeConfiguration" + }, + "tailscale": { + "$ref": "#/$defs/CertificateResolverTailscaleStruct" + } + }, + "type": "object" + }, + "staticCore": { + "additionalProperties": false, + "properties": { + "defaultRuleSyntax": { + "type": "string" + } + }, + "type": "object" + }, + "staticEntryPoint": { + "additionalProperties": false, + "properties": { + "address": { + "type": "string" + }, + "allowACMEByPass": { + "type": "boolean" + }, + "asDefault": { + "type": "boolean" + }, + "forwardedHeaders": { + "$ref": "#/$defs/staticForwardedHeaders" + }, + "http": { + "$ref": "#/$defs/staticHTTPConfig" + }, + "http2": { + "$ref": "#/$defs/staticHTTP2Config" + }, + "http3": { + "$ref": "#/$defs/staticHTTP3Config" + }, + "observability": { + "$ref": "#/$defs/staticObservabilityConfig" + }, + "proxyProtocol": { + "$ref": "#/$defs/staticProxyProtocol" + }, + "reusePort": { + "type": "boolean" + }, + "transport": { + "$ref": "#/$defs/staticEntryPointsTransport" + }, + "udp": { + "$ref": "#/$defs/staticUDPConfig" + } + }, + "type": "object" + }, + "staticEntryPointsTransport": { + "additionalProperties": false, + "properties": { + "keepAliveMaxRequests": { + "type": "integer" + }, + "keepAliveMaxTime": { + "type": "string" + }, + "lifeCycle": { + "$ref": "#/$defs/staticLifeCycle" + }, + "respondingTimeouts": { + "$ref": "#/$defs/staticRespondingTimeouts" + } + }, + "type": "object" + }, + "staticExperimental": { + "additionalProperties": false, + "properties": { + "abortOnPluginFailure": { + "type": "boolean" + }, + "fastProxy": { + "$ref": "#/$defs/staticFastProxyConfig" + }, + "kubernetesGateway": { + "type": "boolean" + }, + "localPlugins": { + "additionalProperties": { + "$ref": "#/$defs/pluginsLocalDescriptor" + }, + "type": "object" + }, + "otlplogs": { + "type": "boolean" + }, + "plugins": { + "additionalProperties": { + "$ref": "#/$defs/pluginsDescriptor" + }, + "type": "object" + } + }, + "type": "object" + }, + "staticFastProxyConfig": { + "additionalProperties": false, + "properties": { + "debug": { + "type": "boolean" + } + }, + "type": "object" + }, + "staticForwardedHeaders": { + "additionalProperties": false, + "properties": { + "connection": { + "items": { + "type": "string" + }, + "type": ["array", "null"] + }, + "insecure": { + "type": "boolean" + }, + "trustedIPs": { + "items": { + "type": "string" + }, + "type": ["array", "null"] + } + }, + "type": "object" + }, + "staticForwardingTimeouts": { + "additionalProperties": false, + "properties": { + "dialTimeout": { + "type": "string" + }, + "idleConnTimeout": { + "type": "string" + }, + "responseHeaderTimeout": { + "type": "string" + } + }, + "type": "object" + }, + "staticGlobal": { + "additionalProperties": false, + "properties": { + "checkNewVersion": { + "type": "boolean" + }, + "sendAnonymousUsage": { + "type": "boolean" + }, + "updaterCallbacks": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "type": "object" + }, + "staticHTTP2Config": { + "additionalProperties": false, + "properties": { + "maxConcurrentStreams": { + "type": "integer" + } + }, + "type": "object" + }, + "staticHTTP3Config": { + "additionalProperties": false, + "properties": { + "advertisedPort": { + "type": "integer" + } + }, + "type": "object" + }, + "staticHTTPConfig": { + "additionalProperties": false, + "properties": { + "encodeQuerySemicolons": { + "type": "boolean" + }, + "maxHeaderBytes": { + "type": "integer" + }, + "middlewares": { + "items": { + "type": "string" + }, + "type": ["array", "null"] + }, + "redirections": { + "$ref": "#/$defs/staticRedirections" + }, + "tls": { + "$ref": "#/$defs/staticTLSConfig" + } + }, + "type": "object" + }, + "staticLifeCycle": { + "additionalProperties": false, + "properties": { + "graceTimeOut": { + "type": "string" + }, + "requestAcceptGraceTimeout": { + "type": "string" + } + }, + "type": "object" + }, + "staticObservabilityConfig": { + "additionalProperties": false, + "properties": { + "accessLogs": { + "type": "boolean" + }, + "metrics": { + "type": "boolean" + }, + "tracing": { + "type": "boolean" + } + }, + "type": "object" + }, + "staticProviders": { + "additionalProperties": false, + "properties": { + "consul": { + "$ref": "#/$defs/consulProviderBuilder" + }, + "consulCatalog": { + "$ref": "#/$defs/consulcatalogProviderBuilder" + }, + "docker": { + "$ref": "#/$defs/dockerProvider" + }, + "ecs": { + "$ref": "#/$defs/ecsProvider" + }, + "etcd": { + "$ref": "#/$defs/etcdProvider" + }, + "file": { + "$ref": "#/$defs/fileProvider" + }, + "http": { + "$ref": "#/$defs/httpProvider" + }, + "kubernetesCRD": { + "$ref": "#/$defs/crdProvider" + }, + "kubernetesGateway": { + "$ref": "#/$defs/gatewayProvider" + }, + "kubernetesIngress": { + "$ref": "#/$defs/ingressProvider" + }, + "nomad": { + "$ref": "#/$defs/nomadProviderBuilder" + }, + "plugin": { + "additionalProperties": { + "additionalProperties": {}, + "type": "object" + }, + "type": "object" + }, + "providersThrottleDuration": { + "type": "string" + }, + "redis": { + "$ref": "#/$defs/redisProvider" + }, + "rest": { + "$ref": "#/$defs/restProvider" + }, + "swarm": { + "$ref": "#/$defs/dockerSwarmProvider" + }, + "zooKeeper": { + "$ref": "#/$defs/zkProvider" + } + }, + "type": "object" + }, + "staticProxyProtocol": { + "additionalProperties": false, + "properties": { + "insecure": { + "type": "boolean" + }, + "trustedIPs": { + "items": { + "type": "string" + }, + "type": ["array", "null"] + } + }, + "type": "object" + }, + "staticRedirectEntryPoint": { + "additionalProperties": false, + "properties": { + "permanent": { + "type": "boolean" + }, + "priority": { + "type": "integer" + }, + "scheme": { + "type": "string" + }, + "to": { + "type": "string" + } + }, + "type": "object" + }, + "staticRedirections": { + "additionalProperties": false, + "properties": { + "entryPoint": { + "$ref": "#/$defs/staticRedirectEntryPoint" + } + }, + "type": "object" + }, + "staticRespondingTimeouts": { + "additionalProperties": false, + "properties": { + "idleTimeout": { + "type": "string" + }, + "readTimeout": { + "type": "string" + }, + "writeTimeout": { + "type": "string" + } + }, + "type": "object" + }, + "staticServersTransport": { + "additionalProperties": false, + "properties": { + "forwardingTimeouts": { + "$ref": "#/$defs/staticForwardingTimeouts" + }, + "insecureSkipVerify": { + "type": "boolean" + }, + "maxIdleConnsPerHost": { + "type": "integer" + }, + "rootCAs": { + "items": { + "type": "string" + }, + "type": ["array", "null"] + }, + "spiffe": { + "$ref": "#/$defs/staticSpiffe" + } + }, + "type": "object" + }, + "staticSpiffe": { + "additionalProperties": false, + "properties": { + "ids": { + "items": { + "type": "string" + }, + "type": ["array", "null"] + }, + "trustDomain": { + "type": "string" + } + }, + "type": "object" + }, + "staticSpiffeClientConfig": { + "additionalProperties": false, + "properties": { + "workloadAPIAddr": { + "type": "string" + } + }, + "type": "object" + }, + "staticTCPServersTransport": { + "additionalProperties": false, + "properties": { + "dialKeepAlive": { + "type": "string" + }, + "dialTimeout": { + "type": "string" + }, + "terminationDelay": { + "type": "string" + }, + "tls": { + "$ref": "#/$defs/staticTLSClientConfig" + } + }, + "type": "object" + }, + "staticTLSClientConfig": { + "additionalProperties": false, + "properties": { + "insecureSkipVerify": { + "type": "boolean" + }, + "rootCAs": { + "items": { + "type": "string" + }, + "type": ["array", "null"] + }, + "spiffe": { + "$ref": "#/$defs/staticSpiffe" + } + }, + "type": "object" + }, + "staticTLSConfig": { + "additionalProperties": false, + "properties": { + "certResolver": { + "type": "string" + }, + "domains": { + "items": { + "$ref": "#/$defs/typesDomain" + }, + "type": ["array", "null"] + }, + "options": { + "type": "string" + } + }, + "type": "object" + }, + "staticTracing": { + "additionalProperties": false, + "properties": { + "addInternals": { + "type": "boolean" + }, + "capturedRequestHeaders": { + "items": { + "type": "string" + }, + "type": ["array", "null"] + }, + "capturedResponseHeaders": { + "items": { + "type": "string" + }, + "type": ["array", "null"] + }, + "globalAttributes": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "otlp": { + "$ref": "#/$defs/typesOTelTracing" + }, + "resourceAttributes": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "safeQueryParams": { + "items": { + "type": "string" + }, + "type": ["array", "null"] + }, + "sampleRate": { + "type": "number" + }, + "serviceName": { + "type": "string" + } + }, + "type": "object" + }, + "staticUDPConfig": { + "additionalProperties": false, + "properties": { + "timeout": { + "type": "string" + } + }, + "type": "object" + }, + "typesAccessLog": { + "additionalProperties": false, + "properties": { + "addInternals": { + "type": "boolean" + }, + "bufferingSize": { + "type": "integer" + }, + "fields": { + "$ref": "#/$defs/typesAccessLogFields" + }, + "filePath": { + "type": "string" + }, + "filters": { + "$ref": "#/$defs/typesAccessLogFilters" + }, + "format": { + "type": "string" + }, + "otlp": { + "$ref": "#/$defs/typesOTelLog" + } + }, + "type": "object" + }, + "typesAccessLogFields": { + "additionalProperties": false, + "properties": { + "defaultMode": { + "type": "string" + }, + "headers": { + "$ref": "#/$defs/typesFieldHeaders" + }, + "names": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + } + }, + "type": "object" + }, + "typesAccessLogFilters": { + "additionalProperties": false, + "properties": { + "minDuration": { + "type": "string" + }, + "retryAttempts": { + "type": "boolean" + }, + "statusCodes": { + "items": { + "type": "string" + }, + "type": ["array", "null"] + } + }, + "type": "object" + }, + "typesClientTLS": { + "additionalProperties": false, + "properties": { + "ca": { + "type": "string" + }, + "cert": { + "type": "string" + }, + "insecureSkipVerify": { + "type": "boolean" + }, + "key": { + "type": "string" + } + }, + "type": "object" + }, + "typesDatadog": { + "additionalProperties": false, + "properties": { + "addEntryPointsLabels": { + "type": "boolean" + }, + "addRoutersLabels": { + "type": "boolean" + }, + "addServicesLabels": { + "type": "boolean" + }, + "address": { + "type": "string" + }, + "prefix": { + "type": "string" + }, + "pushInterval": { + "type": "string" + } + }, + "type": "object" + }, + "typesDomain": { + "additionalProperties": false, + "properties": { + "main": { + "type": "string" + }, + "sans": { + "items": { + "type": "string" + }, + "type": ["array", "null"] + } + }, + "type": "object" + }, + "typesFieldHeaders": { + "additionalProperties": false, + "properties": { + "defaultMode": { + "type": "string" + }, + "names": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + } + }, + "type": "object" + }, + "typesHostResolverConfig": { + "additionalProperties": false, + "properties": { + "cnameFlattening": { + "type": "boolean" + }, + "resolvConfig": { + "type": "string" + }, + "resolvDepth": { + "type": "integer" + } + }, + "type": "object" + }, + "typesInfluxDB2": { + "additionalProperties": false, + "properties": { + "addEntryPointsLabels": { + "type": "boolean" + }, + "addRoutersLabels": { + "type": "boolean" + }, + "addServicesLabels": { + "type": "boolean" + }, + "additionalLabels": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "address": { + "type": "string" + }, + "bucket": { + "type": "string" + }, + "org": { + "type": "string" + }, + "pushInterval": { + "type": "string" + }, + "token": { + "type": "string" + } + }, + "type": "object" + }, + "typesMetrics": { + "additionalProperties": false, + "properties": { + "addInternals": { + "type": "boolean" + }, + "datadog": { + "$ref": "#/$defs/typesDatadog" + }, + "influxDB2": { + "$ref": "#/$defs/typesInfluxDB2" + }, + "otlp": { + "$ref": "#/$defs/typesOTLP" + }, + "prometheus": { + "$ref": "#/$defs/typesPrometheus" + }, + "statsD": { + "$ref": "#/$defs/typesStatsd" + } + }, + "type": "object" + }, + "typesOTLP": { + "additionalProperties": false, + "properties": { + "addEntryPointsLabels": { + "type": "boolean" + }, + "addRoutersLabels": { + "type": "boolean" + }, + "addServicesLabels": { + "type": "boolean" + }, + "explicitBoundaries": { + "items": { + "type": "number" + }, + "type": ["array", "null"] + }, + "grpc": { + "$ref": "#/$defs/typesOTelGRPC" + }, + "http": { + "$ref": "#/$defs/typesOTelHTTP" + }, + "pushInterval": { + "type": "string" + }, + "serviceName": { + "type": "string" + } + }, + "type": "object" + }, + "typesOTelGRPC": { + "additionalProperties": false, + "properties": { + "endpoint": { + "type": "string" + }, + "headers": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "insecure": { + "type": "boolean" + }, + "tls": { + "$ref": "#/$defs/typesClientTLS" + } + }, + "type": "object" + }, + "typesOTelHTTP": { + "additionalProperties": false, + "properties": { + "endpoint": { + "type": "string" + }, + "headers": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "tls": { + "$ref": "#/$defs/typesClientTLS" + } + }, + "type": "object" + }, + "typesOTelLog": { + "additionalProperties": false, + "properties": { + "grpc": { + "$ref": "#/$defs/typesOTelGRPC" + }, + "http": { + "$ref": "#/$defs/typesOTelHTTP" + }, + "resourceAttributes": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "serviceName": { + "type": "string" + } + }, + "type": "object" + }, + "typesOTelTracing": { + "additionalProperties": false, + "properties": { + "grpc": { + "$ref": "#/$defs/typesOTelGRPC" + }, + "http": { + "$ref": "#/$defs/typesOTelHTTP" + } + }, + "type": "object" + }, + "typesPrometheus": { + "additionalProperties": false, + "properties": { + "addEntryPointsLabels": { + "type": "boolean" + }, + "addRoutersLabels": { + "type": "boolean" + }, + "addServicesLabels": { + "type": "boolean" + }, + "buckets": { + "items": { + "type": "number" + }, + "type": ["array", "null"] + }, + "entryPoint": { + "type": "string" + }, + "headerLabels": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "manualRouting": { + "type": "boolean" + } + }, + "type": "object" + }, + "typesStatsd": { + "additionalProperties": false, + "properties": { + "addEntryPointsLabels": { + "type": "boolean" + }, + "addRoutersLabels": { + "type": "boolean" + }, + "addServicesLabels": { + "type": "boolean" + }, + "address": { + "type": "string" + }, + "prefix": { + "type": "string" + }, + "pushInterval": { + "type": "string" + } + }, + "type": "object" + }, + "typesTraefikLog": { + "additionalProperties": false, + "properties": { + "compress": { + "type": "boolean" + }, + "filePath": { + "type": "string" + }, + "format": { + "type": "string" + }, + "level": { + "type": "string" + }, + "maxAge": { + "type": "integer" + }, + "maxBackups": { + "type": "integer" + }, + "maxSize": { + "type": "integer" + }, + "noColor": { + "type": "boolean" + }, + "otlp": { + "$ref": "#/$defs/typesOTelLog" + } + }, + "type": "object" + }, + "zkProvider": { + "additionalProperties": false, + "properties": { + "endpoints": { + "items": { + "type": "string" + }, + "type": ["array", "null"] + }, + "password": { + "type": "string" + }, + "rootKey": { + "type": "string" + }, + "username": { + "type": "string" + } + }, + "type": "object" + } + }, + "title": "Traefik v3 Static Configuration", + "properties": { + "accessLog": { + "$ref": "#/$defs/typesAccessLog" + }, + "api": { + "$ref": "#/$defs/staticAPI" + }, + "certificatesResolvers": { + "additionalProperties": { + "$ref": "#/$defs/staticCertificateResolver" + }, + "type": "object" + }, + "core": { + "$ref": "#/$defs/staticCore" + }, + "entryPoints": { + "additionalProperties": { + "$ref": "#/$defs/staticEntryPoint" + }, + "type": "object" + }, + "experimental": { + "$ref": "#/$defs/staticExperimental" + }, + "global": { + "$ref": "#/$defs/staticGlobal" + }, + "hostResolver": { + "$ref": "#/$defs/typesHostResolverConfig" + }, + "log": { + "$ref": "#/$defs/typesTraefikLog" + }, + "metrics": { + "$ref": "#/$defs/typesMetrics" + }, + "ping": { + "$ref": "#/$defs/pingHandler" + }, + "providers": { + "$ref": "#/$defs/staticProviders" + }, + "serversTransport": { + "$ref": "#/$defs/staticServersTransport" + }, + "spiffe": { + "$ref": "#/$defs/staticSpiffeClientConfig" + }, + "tcpServersTransport": { + "$ref": "#/$defs/staticTCPServersTransport" + }, + "tracing": { + "$ref": "#/$defs/staticTracing" + } + }, + "type": "object" +} diff --git a/script/gcg/traefik-bugfix.toml b/script/gcg/traefik-bugfix.toml index 5e4a617a5..36d6242a3 100644 --- a/script/gcg/traefik-bugfix.toml +++ b/script/gcg/traefik-bugfix.toml @@ -4,11 +4,11 @@ RepositoryName = "traefik" OutputType = "file" FileName = "traefik_changelog.md" -# example new bugfix v3.4.1 -CurrentRef = "v3.4" -PreviousRef = "v3.4.0" -BaseBranch = "v3.4" -FutureCurrentRefName = "v3.4.1" +# example new bugfix v3.5.4 +CurrentRef = "v3.5" +PreviousRef = "v3.5.3" +BaseBranch = "v3.5" +FutureCurrentRefName = "v3.5.4" ThresholdPreviousRef = 10 ThresholdCurrentRef = 10 diff --git a/script/gcg/traefik-final-release-part1.toml b/script/gcg/traefik-final-release-part1.toml index edba19179..95291aea5 100644 --- a/script/gcg/traefik-final-release-part1.toml +++ b/script/gcg/traefik-final-release-part1.toml @@ -4,11 +4,11 @@ RepositoryName = "traefik" OutputType = "file" FileName = "traefik_changelog.md" -# example final release of v3.4.0 -CurrentRef = "v3.4" -PreviousRef = "v3.4.0-rc1" -BaseBranch = "v3.4" -FutureCurrentRefName = "v3.4.0" +# example final release of v3.5.0 +CurrentRef = "v3.5" +PreviousRef = "v3.5.0-rc1" +BaseBranch = "v3.5" +FutureCurrentRefName = "v3.5.0" ThresholdPreviousRef = 10 ThresholdCurrentRef = 10 diff --git a/script/gcg/traefik-final-release-part2.toml b/script/gcg/traefik-final-release-part2.toml index 453ff650f..ceaa118de 100644 --- a/script/gcg/traefik-final-release-part2.toml +++ b/script/gcg/traefik-final-release-part2.toml @@ -4,11 +4,11 @@ RepositoryName = "traefik" OutputType = "file" FileName = "traefik_changelog.md" -# example final release of v3.4.0 -CurrentRef = "v3.4.0-rc1" -PreviousRef = "v3.3.0-rc1" +# example final release of v3.5.0 +CurrentRef = "v3.5.0-rc1" +PreviousRef = "v3.4.0-rc1" BaseBranch = "master" -FutureCurrentRefName = "v3.4.0" +FutureCurrentRefName = "v3.5.0" ThresholdPreviousRef = 10 ThresholdCurrentRef = 10 diff --git a/script/gcg/traefik-rc-first.toml b/script/gcg/traefik-rc-first.toml index 9d3afbc12..a946c438d 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 v3.4.0-rc1 +# example RC1 of v3.5.0-rc1 CurrentRef = "master" -PreviousRef = "v3.3.0-rc1" +PreviousRef = "v3.4.0-rc1" BaseBranch = "master" -FutureCurrentRefName = "v3.4.0-rc1" +FutureCurrentRefName = "v3.5.0-rc1" ThresholdPreviousRef = 10 ThresholdCurrentRef = 10 diff --git a/script/gcg/traefik-rc-new.toml b/script/gcg/traefik-rc-new.toml index 252218964..70a89e7cd 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 rc2 of v3.4.0 -CurrentRef = "v3.4" -PreviousRef = "v3.4.0-rc1" -BaseBranch = "v3.4" -FutureCurrentRefName = "v3.4.0-rc2" +# example rc2 of v3.5.0 +CurrentRef = "v3.5" +PreviousRef = "v3.5.0-rc1" +BaseBranch = "v3.5" +FutureCurrentRefName = "v3.5.0-rc2" ThresholdPreviousRef = 10 ThresholdCurrentRef = 10 diff --git a/script/release-packages.sh b/script/release-packages.sh deleted file mode 100755 index 18ed92d3b..000000000 --- a/script/release-packages.sh +++ /dev/null @@ -1,28 +0,0 @@ -#!/usr/bin/env bash -set -e - -if [ -n "${SEMAPHORE_GIT_TAG_NAME}" ]; then - echo "Releasing packages..." -else - echo "Skipping release" - exit 0 -fi - -rm -rf dist - -for os in linux darwin windows freebsd openbsd; do - goreleaser release --skip=publish -p 2 --timeout="90m" --config "$(go run ./internal/release "$os")" - go clean -cache -done - -cat dist/**/*_checksums.txt >> "dist/traefik_${VERSION}_checksums.txt" -rm dist/**/*_checksums.txt -tar cfz "dist/traefik-${VERSION}.src.tar.gz" \ - --exclude-vcs \ - --exclude .idea \ - --exclude .travis \ - --exclude .semaphoreci \ - --exclude .github \ - --exclude dist . - -chown -R "$(id -u)":"$(id -g)" dist/ diff --git a/traefik.sample.toml b/traefik.sample.toml index 7cf8ed2bf..a88793976 100644 --- a/traefik.sample.toml +++ b/traefik.sample.toml @@ -81,12 +81,16 @@ # # filePath = "/path/to/log/log.txt" - # Format is either "json" or "common". + # Format is either "json", "common", or "genericCLF". + # - "common": Traefik's extended CLF format (default) + # - "genericCLF": Standard CLF format compatible with standard log analyzers + # - "json": JSON format for structured logging # # Optional # Default: "common" # # format = "json" + # format = "genericCLF" ################################################################ # API and dashboard configuration diff --git a/traefik.sample.yml b/traefik.sample.yml index c13ebcd42..bcfa53441 100644 --- a/traefik.sample.yml +++ b/traefik.sample.yml @@ -79,12 +79,16 @@ entryPoints: # # filePath: /path/to/log/log.txt - # Format is either "json" or "common". + # Format is either "json", "common", or "genericCLF". + # - "common": Traefik's extended CLF format (default) + # - "genericCLF": Standard CLF format compatible with standard log analyzers + # - "json": JSON format for structured logging # # Optional # Default: "common" # # format: json +# format: genericCLF ################################################################ # API and dashboard configuration diff --git a/webui/.dockerignore b/webui/.dockerignore deleted file mode 100644 index 8e8fdce7b..000000000 --- a/webui/.dockerignore +++ /dev/null @@ -1,5 +0,0 @@ -# compiled output -/dist - -# dependencies -/node_modules diff --git a/webui/.editorconfig b/webui/.editorconfig index 9d08a1a82..cb5b5bbaa 100644 --- a/webui/.editorconfig +++ b/webui/.editorconfig @@ -1,9 +1,22 @@ +# Editor configuration, see http://editorconfig.org root = true [*] charset = utf-8 + +[*.{js,ts,tsx}] indent_style = space indent_size = 2 -end_of_line = lf insert_final_newline = true trim_trailing_whitespace = true + +[*.md] +max_line_length = off +trim_trailing_whitespace = false + +[Makefile] +indent_style = tab + +[{package.json}] +indent_style = space +indent_size = 2 \ No newline at end of file diff --git a/webui/.env.sample b/webui/.env.sample new file mode 100644 index 000000000..0fe8436ee --- /dev/null +++ b/webui/.env.sample @@ -0,0 +1,2 @@ +VITE_APP_BASE_API_URL=/api +VITE_APP_BASE_URL= diff --git a/webui/.eslintignore b/webui/.eslintignore deleted file mode 100644 index 9f81cf845..000000000 --- a/webui/.eslintignore +++ /dev/null @@ -1,7 +0,0 @@ -/dist -/src-capacitor -/src-cordova -/.quasar -/node_modules -.eslintrc.cjs -/quasar.config.*.temporary.compiled* diff --git a/webui/.eslintrc.cjs b/webui/.eslintrc.cjs deleted file mode 100644 index 331093689..000000000 --- a/webui/.eslintrc.cjs +++ /dev/null @@ -1,68 +0,0 @@ -module.exports = { - root: true, - - parserOptions: { - parser: '@babel/eslint-parser', - ecmaVersion: 2021, // Allows for the parsing of modern ECMAScript features - }, - - env: { - node: true, - browser: true, - 'vue/setup-compiler-macros': true - }, - - extends: [ - // https://github.com/vuejs/eslint-plugin-vue#priority-a-essential-error-prevention - // consider switching to `plugin:vue/strongly-recommended` or `plugin:vue/recommended` for stricter rules. - 'plugin:vue/vue3-essential', - 'plugin:vue/vue3-recommended', - 'standard' - ], - - // required to lint *.vue files - plugins: [ - 'vue', - ], - - globals: { - ga: 'readonly', // Google Analytics - cordova: 'readonly', - __statics: 'readonly', - __QUASAR_SSR__: 'readonly', - __QUASAR_SSR_SERVER__: 'readonly', - __QUASAR_SSR_CLIENT__: 'readonly', - __QUASAR_SSR_PWA__: 'readonly', - process: 'readonly', - Capacitor: 'readonly', - chrome: 'readonly' - }, - - // add your custom rules here - rules: { - // allow async-await - 'generator-star-spacing': 'off', - // allow paren-less arrow functions - 'arrow-parens': 'off', - 'one-var': 'off', - 'no-void': 'off', - 'multiline-ternary': 'off', - - 'import/first': 'off', - 'import/named': 'error', - 'import/namespace': 'error', - 'import/default': 'error', - 'import/export': 'error', - 'import/extensions': 'off', - 'import/no-unresolved': 'off', - 'import/no-extraneous-dependencies': 'off', - 'prefer-promise-reject-errors': 'off', - 'vue/multi-word-component-names': 'off', - - // allow console.log during development only - //'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off', - // allow debugger during development only - //'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off' - } -} - diff --git a/webui/.gitignore b/webui/.gitignore index db32ba46c..541d4f563 100644 --- a/webui/.gitignore +++ b/webui/.gitignore @@ -1,32 +1,62 @@ -.quasar -.DS_Store -.thumbs.db -node_modules +# See http://help.github.com/ignore-files/ for more about ignoring files. + +# compiled output /dist -/dev_local -/src-cordova/node_modules -/src-cordova/platforms -/src-cordova/plugins -/src-cordova/www +/build +/dist-server +/tmp +/out-tsc -# Log files -npm-debug.log* -yarn-debug.log* -yarn-error.log* +# dependencies +/node_modules +.yalc -# Editor directories and files -.idea -.vscode -*.suo -*.ntvs* -*.njsproj -*.sln -*.sw? +# IDEs and editors +/.idea +.project +.classpath +.c9/ +*.launch +.settings/ +*.sublime-workspace -# local env files -.env.local -.env.*.local +# IDE - VSCode +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json -# static assets (ignore all except the DO NOT EDIT file) +# misc +/.sass-cache +/connect.lock +/coverage +/libpeerconnection.log +npm-debug.log +yarn-error.log +testem.log +/typings + +# e2e +/e2e/*.js +/e2e/*.map + +# System Files +.DS_Store +Thumbs.db + +# env +.env + +# yarn berry with no zero-installs +.pnp.* +.yarn/* +!.yarn/patches +!.yarn/plugins +!.yarn/releases +!.yarn/sdks +!.yarn/versions + +# static assets static/* !static/DONT-EDIT-FILES-IN-THIS-DIRECTORY.md diff --git a/webui/.nvmrc b/webui/.nvmrc index 8b0beab16..26600046d 100644 --- a/webui/.nvmrc +++ b/webui/.nvmrc @@ -1 +1 @@ -20.11.0 +v22.15.1 diff --git a/webui/.postcssrc.cjs b/webui/.postcssrc.cjs deleted file mode 100644 index 1174fe52b..000000000 --- a/webui/.postcssrc.cjs +++ /dev/null @@ -1,8 +0,0 @@ -// https://github.com/michael-ciniawsky/postcss-load-config - -module.exports = { - plugins: [ - // to edit target browsers: use "browserslist" field in package.json - require('autoprefixer') - ] -} diff --git a/webui/.prettierrc.json b/webui/.prettierrc.json new file mode 100644 index 000000000..7933dbd7b --- /dev/null +++ b/webui/.prettierrc.json @@ -0,0 +1,6 @@ +{ + "semi": false, + "trailingComma": "all", + "singleQuote": true, + "printWidth": 120 +} \ No newline at end of file diff --git a/webui/.stylintrc b/webui/.stylintrc deleted file mode 100644 index ce38d777e..000000000 --- a/webui/.stylintrc +++ /dev/null @@ -1,35 +0,0 @@ -{ - "blocks": "never", - "brackets": "never", - "colons": "never", - "colors": "always", - "commaSpace": "always", - "commentSpace": "always", - "cssLiteral": "never", - "depthLimit": false, - "duplicates": true, - "efficient": "always", - "extendPref": false, - "globalDupe": true, - "indentPref": 2, - "leadingZero": "never", - "maxErrors": false, - "maxWarnings": false, - "mixed": false, - "namingConvention": false, - "namingConventionStrict": false, - "none": "never", - "noImportant": false, - "parenSpace": "never", - "placeholder": false, - "prefixVarsWithDollar": "always", - "quotePref": "single", - "semicolons": "never", - "sortOrder": false, - "stackedProperties": "never", - "trailingWhitespace": "never", - "universal": "never", - "valid": true, - "zeroUnits": "never", - "zIndexNormalize": false -} diff --git a/webui/.yarnrc.yml b/webui/.yarnrc.yml new file mode 100644 index 000000000..3186f3f07 --- /dev/null +++ b/webui/.yarnrc.yml @@ -0,0 +1 @@ +nodeLinker: node-modules diff --git a/webui/Dockerfile b/webui/Dockerfile deleted file mode 100644 index 283101452..000000000 --- a/webui/Dockerfile +++ /dev/null @@ -1,17 +0,0 @@ -FROM node:22.9-alpine3.20 -# Current Active LTS release according to (https://nodejs.org/en/about/releases/) - -ENV WEBUI_DIR=/src/webui -RUN mkdir -p $WEBUI_DIR - -COPY package.json $WEBUI_DIR/ -COPY yarn.lock $WEBUI_DIR/ - -WORKDIR $WEBUI_DIR -RUN yarn install - -COPY . $WEBUI_DIR/ - -EXPOSE 8080 - -RUN yarn lint diff --git a/webui/babel.config.cjs b/webui/babel.config.cjs deleted file mode 100644 index cd63d3933..000000000 --- a/webui/babel.config.cjs +++ /dev/null @@ -1,16 +0,0 @@ -/* eslint-disable */ - -module.exports = api => { - return { - presets: [ - [ - '@quasar/babel-preset-app', - api.caller(caller => caller && caller.target === 'node') - ? { targets: { node: 'current' } } - : {} - ] - ] - } -} - - diff --git a/webui/buildx.Dockerfile b/webui/buildx.Dockerfile new file mode 100644 index 000000000..09b65ab81 --- /dev/null +++ b/webui/buildx.Dockerfile @@ -0,0 +1,18 @@ +FROM node:22.15.1-alpine3.20 + +ENV WEBUI_DIR=/src/webui +RUN mkdir -p $WEBUI_DIR + +COPY package.json yarn.lock .yarnrc.yml $WEBUI_DIR/ + +ENV VITE_APP_BASE_URL="" +ENV VITE_APP_BASE_API_URL="/api" + +WORKDIR $WEBUI_DIR + +RUN corepack enable +RUN yarn workspaces focus --all --production + +COPY . $WEBUI_DIR/ + +EXPOSE 8080 diff --git a/webui/dev/scripts/transfer.js b/webui/dev/scripts/transfer.js deleted file mode 100644 index eaa02bceb..000000000 --- a/webui/dev/scripts/transfer.js +++ /dev/null @@ -1,17 +0,0 @@ -const fs = require('fs-extra') - -const folder = process.argv[2] - -async function execute () { - try { - await fs.emptyDir('./static') - await fs.outputFile('./static/DONT-EDIT-FILES-IN-THIS-DIRECTORY.md', 'For more information see `webui/readme.md`') - console.log('Deleted static folder contents!') - await fs.copy(`./dist/${folder}`, './static', { overwrite: true }) - console.log('Installed new files in static folder!') - } catch (err) { - console.error(err) - } -} - -execute() diff --git a/webui/eslint.config.mjs b/webui/eslint.config.mjs new file mode 100644 index 000000000..51edbb887 --- /dev/null +++ b/webui/eslint.config.mjs @@ -0,0 +1,57 @@ +import js from '@eslint/js' +import eslintConfigPrettier from 'eslint-config-prettier' +import importPlugin from 'eslint-plugin-import' +import jsxA11y from 'eslint-plugin-jsx-a11y' +import react from 'eslint-plugin-react' +import reactHooks from 'eslint-plugin-react-hooks' +import globals from 'globals' +import tseslint from 'typescript-eslint' + +export default tseslint.config( + { ignores: ['dist'] }, + { + extends: [js.configs.recommended, ...tseslint.configs.recommended], + files: ['**/*.{ts,tsx}'], + languageOptions: { + ecmaVersion: 2020, + globals: globals.browser, + parserOptions: { + ecmaFeatures: { + jsx: true, + }, + }, + }, + plugins: { + react: react, + 'react-hooks': reactHooks, + }, + rules: { + ...reactHooks.configs.recommended.rules, + "@typescript-eslint/no-explicit-any": "warn", + }, + }, + eslintConfigPrettier, + { + files: ['**/*.{ts,tsx}'], + extends: [importPlugin.flatConfigs.recommended, importPlugin.flatConfigs.typescript], + rules: { + 'import/order': [ + 'error', + { + alphabetize: { + order: 'asc', + caseInsensitive: true, + }, + 'newlines-between': 'always', + }, + ], + }, + settings: { + 'import/resolver': { + typescript: true, + node: true, + }, + }, + }, + jsxA11y.flatConfigs.recommended, +) diff --git a/webui/index.dev.html b/webui/index.dev.html new file mode 100644 index 000000000..215a1a4a8 --- /dev/null +++ b/webui/index.dev.html @@ -0,0 +1,25 @@ + + + + + + + + + + + + Traefik Proxy + + + +
+ + + diff --git a/webui/index.html b/webui/index.html index 654437a27..4b5b2e2a0 100644 --- a/webui/index.html +++ b/webui/index.html @@ -1,32 +1,31 @@ - + {{if .APIUrl}} {{end}} - <%= productName %> - - - - - - - - - - - - - - - - + + + + + + + + Traefik Proxy - + +
+ diff --git a/webui/jsconfig.json b/webui/jsconfig.json deleted file mode 100644 index 456944a5e..000000000 --- a/webui/jsconfig.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "compilerOptions": { - "baseUrl": ".", - "paths": { - "src/*": [ - "src/*" - ], - "app/*": [ - "*" - ], - "components/*": [ - "src/components/*" - ], - "layouts/*": [ - "src/layouts/*" - ], - "pages/*": [ - "src/pages/*" - ], - "assets/*": [ - "src/assets/*" - ], - "boot/*": [ - "src/boot/*" - ], - "stores/*": [ - "src/stores/*" - ], - "vue$": [ - "node_modules/vue/dist/vue.runtime.esm-bundler.js" - ] - } - }, - "exclude": [ - "dist", - ".quasar", - "node_modules" - ] -} \ No newline at end of file diff --git a/webui/package.json b/webui/package.json index 7cd74012b..3721a8ae1 100644 --- a/webui/package.json +++ b/webui/package.json @@ -1,62 +1,102 @@ { - "name": "traefik-ui", - "version": "2.0.0", - "description": "Traefik UI", - "productName": "Traefik", - "cordovaId": "io.traefik.traefik", + "name": "traefik-proxy-dashboard", + "version": "0.1.0", "private": true, + "homepage": ".", "scripts": { - "transfer": "node dev/scripts/transfer.js", - "lint": "eslint src/**/*.{js,vue}", - "dev": "APP_ENV=development quasar dev", - "build-quasar": "quasar build", - "build-staging": "NODE_ENV=production APP_ENV=development yarn build-quasar", - "build": "NODE_ENV=production APP_ENV=production yarn build-quasar && yarn transfer spa", - "build:nc": "yarn build", - "test": "echo \"See package.json => scripts for available tests.\" && exit 0", - "test:unit": "vitest", + "build": "vite build", + "build:prod": "yarn test && yarn tsc && yarn lint && yarn build", + "dev": "vite", + "format": "prettier './src/**/*.{ts,tsx}' --config .prettierrc.json --write", + "lint": "eslint './src/**/*.{ts,tsx}'", + "lint:fix": "eslint --fix './src/**/*.{ts,tsx}'", + "preview": "vite preview", + "test": "vitest run", + "test:coverage": "vitest run --coverage", + "test:watch": "vitest", "test:unit:ci": "vitest run" }, + "lint-staged": { + "**/*.{ts,tsx}": [ + "yarn format", + "eslint --fix", + "git add" + ] + }, + "husky": { + "hooks": { + "pre-commit": "lint-staged" + } + }, + "browserslist": { + "production": [ + ">0.2%", + "not dead", + "not op_mini all" + ], + "development": [ + "last 1 chrome version", + "last 1 firefox version", + "last 1 safari version" + ] + }, + "type": "module", "dependencies": { - "@quasar/extras": "^1.16.12", - "axios": "^1.7.4", + "@eslint/js": "^9.32.0", + "@testing-library/dom": "^10.4.1", + "@testing-library/jest-dom": "^6.4.2", + "@testing-library/react": "^14.2.1", + "@testing-library/user-event": "^14.5.2", + "@traefiklabs/faency": "11.1.4", + "@types/lodash": "^4.17.16", + "@types/node": "^22.15.18", + "@types/react": "^18.2.0", + "@types/react-dom": "^18.2.19", + "@types/react-router-dom": "^5.1.3", + "@typescript-eslint/parser": "^8.38.0", + "@vitejs/plugin-react": "^4.7.0", + "@vitest/coverage-v8": "^3.2.4", "chart.js": "^4.4.1", - "core-js": "^3.35.1", - "dot-prop": "^8.0.2", - "lodash.isequal": "4.5.0", - "moment": "^2.30.1", - "quasar": "^2.16.6", - "query-string": "^8.1.0", - "vue": "^3.0.0", - "vue-chartjs": "^5.3.0", - "vue-router": "^4.0.12", - "vuex": "^4.1.0", - "vuex-map-fields": "^1.4.1" + "eslint": "^9.32.0", + "eslint-config-prettier": "^10.1.8", + "eslint-import-resolver-typescript": "^4.4.4", + "eslint-plugin-import": "^2.32.0", + "eslint-plugin-jsx-a11y": "^6.10.2", + "eslint-plugin-react": "^7.37.5", + "eslint-plugin-react-hooks": "^5.2.0", + "framer-motion": "^11.18.2", + "globals": "^16.0.0", + "jest-extended": "^4.0.2", + "jsdom": "^24.0.0", + "lodash": "^4.17.21", + "msw": "^2.1.7", + "query-string": "^6.9.0", + "react": "^18.2.0", + "react-chartjs-2": "^5.2.0", + "react-dom": "^18.2.0", + "react-error-boundary": "^4.0.12", + "react-helmet-async": "^2.0.4", + "react-icons": "^5.0.1", + "react-infinite-scroll-hook": "^4.1.1", + "react-router-dom": "6.22.1", + "swr": "^2.2.4", + "typescript": "^5.2.2", + "typescript-eslint": "^8.38.0", + "usehooks-ts": "^2.14.0", + "vite": "^5.4.19", + "vite-tsconfig-paths": "^5.1.4", + "vitest": "^3.2.4", + "vitest-canvas-mock": "^0.3.3" }, "devDependencies": { - "@babel/core": "^7.23.9", - "@babel/eslint-parser": "^7.23.10", - "@quasar/app-vite": "^2.0.0-beta.15", - "@quasar/babel-preset-app": "^2.0.3", - "@quasar/quasar-app-extension-testing-unit-vitest": "^1.0.0", - "@vue/test-utils": "^2.4.4", - "autoprefixer": "^10.4.2", - "eslint": "^8.11.0", - "eslint-config-standard": "^17.0.0", - "eslint-plugin-import": "^2.19.1", - "eslint-plugin-n": "^16.6.2", - "eslint-plugin-promise": "^6.0.0", - "eslint-plugin-vue": "^9.0.0", - "postcss": "^8.4.14", - "vitest": "^1.6.0" + "husky": "^3.1.0", + "lint-staged": "^9.5.0", + "prettier": "^3.5.3" }, - "resolutions": { - "cookie": "^0.7.0" + "msw": { + "workerDirectory": [ + "public" + ] }, - "engines": { - "node": "^22 || ^20 || ^18 || ^16", - "npm": ">= 6.13.4", - "yarn": ">= 1.22.22" - }, - "packageManager": "yarn@1.22.22" + "packageManager": "yarn@4.9.1" } diff --git a/webui/postcss.config.cjs b/webui/postcss.config.cjs deleted file mode 100644 index 94b7b1c85..000000000 --- a/webui/postcss.config.cjs +++ /dev/null @@ -1,27 +0,0 @@ -/* eslint-disable */ -// https://github.com/michael-ciniawsky/postcss-load-config - -module.exports = { - plugins: [ - // https://github.com/postcss/autoprefixer - require('autoprefixer')({ - overrideBrowserslist: [ - 'last 4 Chrome versions', - 'last 4 Firefox versions', - 'last 4 Edge versions', - 'last 4 Safari versions', - 'last 4 Android versions', - 'last 4 ChromeAndroid versions', - 'last 4 FirefoxAndroid versions', - 'last 4 iOS versions' - ] - }) - - // https://github.com/elchininet/postcss-rtlcss - // If you want to support RTL css, then - // 1. yarn/npm install postcss-rtlcss - // 2. optionally set quasar.config.js > framework > lang to an RTL language - // 3. uncomment the following line: - // require('postcss-rtlcss') - ] -} diff --git a/webui/public/app-logo-128x128.png b/webui/public/app-logo-128x128.png deleted file mode 100755 index af9348b10..000000000 Binary files a/webui/public/app-logo-128x128.png and /dev/null differ diff --git a/webui/public/browserconfig.xml b/webui/public/browserconfig.xml new file mode 100644 index 000000000..4c3e081e5 --- /dev/null +++ b/webui/public/browserconfig.xml @@ -0,0 +1,9 @@ + + + + + + #ddee6d + + + diff --git a/webui/public/favicon-16x16.png b/webui/public/favicon-16x16.png new file mode 100644 index 000000000..3d57b6754 Binary files /dev/null and b/webui/public/favicon-16x16.png differ diff --git a/webui/public/favicon-32x32.png b/webui/public/favicon-32x32.png new file mode 100644 index 000000000..ae98b9bf0 Binary files /dev/null and b/webui/public/favicon-32x32.png differ diff --git a/webui/public/favicon.ico b/webui/public/favicon.ico new file mode 100644 index 000000000..95ed61ba4 Binary files /dev/null and b/webui/public/favicon.ico differ diff --git a/webui/public/manifest.json b/webui/public/manifest.json new file mode 100644 index 000000000..73d155b68 --- /dev/null +++ b/webui/public/manifest.json @@ -0,0 +1,25 @@ +{ + "short_name": "Traefik Proxy", + "name": "Traefik Proxy", + "icons": [ + { + "src": "favicon-16x16.png", + "sizes": "16x16", + "type": "image/png" + }, + { + "src": "favicon-32x32.png", + "sizes": "32x32", + "type": "image/png" + }, + { + "src": "favicon-96x96.png", + "sizes": "96x96", + "type": "image/png" + } + ], + "start_url": ".", + "display": "standalone", + "theme_color": "#ddee6d", + "background_color": "#091827" +} diff --git a/webui/public/mockServiceWorker.js b/webui/public/mockServiceWorker.js new file mode 100644 index 000000000..34057e898 --- /dev/null +++ b/webui/public/mockServiceWorker.js @@ -0,0 +1,307 @@ +/* eslint-disable */ +/* tslint:disable */ + +/** + * Mock Service Worker. + * @see https://github.com/mswjs/msw + * - Please do NOT modify this file. + * - Please do NOT serve this file on production. + */ + +const PACKAGE_VERSION = '2.7.3' +const INTEGRITY_CHECKSUM = '00729d72e3b82faf54ca8b9621dbb96f' +const IS_MOCKED_RESPONSE = Symbol('isMockedResponse') +const activeClientIds = new Set() + +self.addEventListener('install', function () { + self.skipWaiting() +}) + +self.addEventListener('activate', function (event) { + event.waitUntil(self.clients.claim()) +}) + +self.addEventListener('message', async function (event) { + const clientId = event.source.id + + if (!clientId || !self.clients) { + return + } + + const client = await self.clients.get(clientId) + + if (!client) { + return + } + + const allClients = await self.clients.matchAll({ + type: 'window', + }) + + switch (event.data) { + case 'KEEPALIVE_REQUEST': { + sendToClient(client, { + type: 'KEEPALIVE_RESPONSE', + }) + break + } + + case 'INTEGRITY_CHECK_REQUEST': { + sendToClient(client, { + type: 'INTEGRITY_CHECK_RESPONSE', + payload: { + packageVersion: PACKAGE_VERSION, + checksum: INTEGRITY_CHECKSUM, + }, + }) + break + } + + case 'MOCK_ACTIVATE': { + activeClientIds.add(clientId) + + sendToClient(client, { + type: 'MOCKING_ENABLED', + payload: { + client: { + id: client.id, + frameType: client.frameType, + }, + }, + }) + break + } + + case 'MOCK_DEACTIVATE': { + activeClientIds.delete(clientId) + break + } + + case 'CLIENT_CLOSED': { + activeClientIds.delete(clientId) + + const remainingClients = allClients.filter((client) => { + return client.id !== clientId + }) + + // Unregister itself when there are no more clients + if (remainingClients.length === 0) { + self.registration.unregister() + } + + break + } + } +}) + +self.addEventListener('fetch', function (event) { + const { request } = event + + // Bypass navigation requests. + if (request.mode === 'navigate') { + return + } + + // Opening the DevTools triggers the "only-if-cached" request + // that cannot be handled by the worker. Bypass such requests. + if (request.cache === 'only-if-cached' && request.mode !== 'same-origin') { + return + } + + // Bypass all requests when there are no active clients. + // Prevents the self-unregistered worked from handling requests + // after it's been deleted (still remains active until the next reload). + if (activeClientIds.size === 0) { + return + } + + // Generate unique request ID. + const requestId = crypto.randomUUID() + event.respondWith(handleRequest(event, requestId)) +}) + +async function handleRequest(event, requestId) { + const client = await resolveMainClient(event) + const response = await getResponse(event, client, requestId) + + // Send back the response clone for the "response:*" life-cycle events. + // Ensure MSW is active and ready to handle the message, otherwise + // this message will pend indefinitely. + if (client && activeClientIds.has(client.id)) { + ;(async function () { + const responseClone = response.clone() + + sendToClient( + client, + { + type: 'RESPONSE', + payload: { + requestId, + isMockedResponse: IS_MOCKED_RESPONSE in response, + type: responseClone.type, + status: responseClone.status, + statusText: responseClone.statusText, + body: responseClone.body, + headers: Object.fromEntries(responseClone.headers.entries()), + }, + }, + [responseClone.body], + ) + })() + } + + return response +} + +// Resolve the main client for the given event. +// Client that issues a request doesn't necessarily equal the client +// that registered the worker. It's with the latter the worker should +// communicate with during the response resolving phase. +async function resolveMainClient(event) { + const client = await self.clients.get(event.clientId) + + if (activeClientIds.has(event.clientId)) { + return client + } + + if (client?.frameType === 'top-level') { + return client + } + + const allClients = await self.clients.matchAll({ + type: 'window', + }) + + return allClients + .filter((client) => { + // Get only those clients that are currently visible. + return client.visibilityState === 'visible' + }) + .find((client) => { + // Find the client ID that's recorded in the + // set of clients that have registered the worker. + return activeClientIds.has(client.id) + }) +} + +async function getResponse(event, client, requestId) { + const { request } = event + + // Clone the request because it might've been already used + // (i.e. its body has been read and sent to the client). + const requestClone = request.clone() + + function passthrough() { + // Cast the request headers to a new Headers instance + // so the headers can be manipulated with. + const headers = new Headers(requestClone.headers) + + // Remove the "accept" header value that marked this request as passthrough. + // This prevents request alteration and also keeps it compliant with the + // user-defined CORS policies. + const acceptHeader = headers.get('accept') + if (acceptHeader) { + const values = acceptHeader.split(',').map((value) => value.trim()) + const filteredValues = values.filter( + (value) => value !== 'msw/passthrough', + ) + + if (filteredValues.length > 0) { + headers.set('accept', filteredValues.join(', ')) + } else { + headers.delete('accept') + } + } + + return fetch(requestClone, { headers }) + } + + // Bypass mocking when the client is not active. + if (!client) { + return passthrough() + } + + // Bypass initial page load requests (i.e. static assets). + // The absence of the immediate/parent client in the map of the active clients + // means that MSW hasn't dispatched the "MOCK_ACTIVATE" event yet + // and is not ready to handle requests. + if (!activeClientIds.has(client.id)) { + return passthrough() + } + + // Notify the client that a request has been intercepted. + const requestBuffer = await request.arrayBuffer() + const clientMessage = await sendToClient( + client, + { + type: 'REQUEST', + payload: { + id: requestId, + url: request.url, + mode: request.mode, + method: request.method, + headers: Object.fromEntries(request.headers.entries()), + cache: request.cache, + credentials: request.credentials, + destination: request.destination, + integrity: request.integrity, + redirect: request.redirect, + referrer: request.referrer, + referrerPolicy: request.referrerPolicy, + body: requestBuffer, + keepalive: request.keepalive, + }, + }, + [requestBuffer], + ) + + switch (clientMessage.type) { + case 'MOCK_RESPONSE': { + return respondWithMock(clientMessage.data) + } + + case 'PASSTHROUGH': { + return passthrough() + } + } + + return passthrough() +} + +function sendToClient(client, message, transferrables = []) { + return new Promise((resolve, reject) => { + const channel = new MessageChannel() + + channel.port1.onmessage = (event) => { + if (event.data && event.data.error) { + return reject(event.data.error) + } + + resolve(event.data) + } + + client.postMessage( + message, + [channel.port2].concat(transferrables.filter(Boolean)), + ) + }) +} + +async function respondWithMock(response) { + // Setting response status code to 0 is a no-op. + // However, when responding with a "Response.error()", the produced Response + // instance will have status code set to 0. Since it's not possible to create + // a Response instance with status code 0, handle that use-case separately. + if (response.status === 0) { + return Response.error() + } + + const mockedResponse = new Response(response.body, response) + + Reflect.defineProperty(mockedResponse, IS_MOCKED_RESPONSE, { + value: true, + enumerable: true, + }) + + return mockedResponse +} diff --git a/webui/public/providers/consul.svg b/webui/public/providers/consul.svg deleted file mode 100644 index b9b33d374..000000000 --- a/webui/public/providers/consul.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/webui/public/providers/consulcatalog.svg b/webui/public/providers/consulcatalog.svg deleted file mode 100644 index a692dede2..000000000 --- a/webui/public/providers/consulcatalog.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - diff --git a/webui/public/providers/docker.svg b/webui/public/providers/docker.svg deleted file mode 100644 index db4a729e6..000000000 --- a/webui/public/providers/docker.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/webui/public/providers/ecs.svg b/webui/public/providers/ecs.svg deleted file mode 100644 index aad9305b5..000000000 --- a/webui/public/providers/ecs.svg +++ /dev/null @@ -1,36 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/webui/public/providers/etcd.svg b/webui/public/providers/etcd.svg deleted file mode 100644 index 3c270f632..000000000 --- a/webui/public/providers/etcd.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/webui/public/providers/file.svg b/webui/public/providers/file.svg deleted file mode 100644 index bf4d1cae1..000000000 --- a/webui/public/providers/file.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/webui/public/providers/http.svg b/webui/public/providers/http.svg deleted file mode 100644 index 338e1afca..000000000 --- a/webui/public/providers/http.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/webui/public/providers/hub.svg b/webui/public/providers/hub.svg deleted file mode 100644 index 1df28d7a8..000000000 --- a/webui/public/providers/hub.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/webui/public/providers/internal.svg b/webui/public/providers/internal.svg deleted file mode 100644 index ce0fc3496..000000000 --- a/webui/public/providers/internal.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/webui/public/providers/kubernetes.svg b/webui/public/providers/kubernetes.svg deleted file mode 100644 index b6670fe5d..000000000 --- a/webui/public/providers/kubernetes.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/webui/public/providers/kubernetescrd.svg b/webui/public/providers/kubernetescrd.svg deleted file mode 100644 index b6670fe5d..000000000 --- a/webui/public/providers/kubernetescrd.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/webui/public/providers/kubernetesgateway.svg b/webui/public/providers/kubernetesgateway.svg deleted file mode 100644 index b6670fe5d..000000000 --- a/webui/public/providers/kubernetesgateway.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/webui/public/providers/kubernetesingress.svg b/webui/public/providers/kubernetesingress.svg deleted file mode 100644 index b6670fe5d..000000000 --- a/webui/public/providers/kubernetesingress.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/webui/public/providers/marathon.svg b/webui/public/providers/marathon.svg deleted file mode 100644 index 0d65d89b6..000000000 --- a/webui/public/providers/marathon.svg +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - - - diff --git a/webui/public/providers/nomad.svg b/webui/public/providers/nomad.svg deleted file mode 100755 index e71d75007..000000000 --- a/webui/public/providers/nomad.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - diff --git a/webui/public/providers/plugin.svg b/webui/public/providers/plugin.svg deleted file mode 100644 index 5a6a63769..000000000 --- a/webui/public/providers/plugin.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - plugin - - - - - - - \ No newline at end of file diff --git a/webui/public/providers/rancher.svg b/webui/public/providers/rancher.svg deleted file mode 100644 index 7d9a4e776..000000000 --- a/webui/public/providers/rancher.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/webui/public/providers/redis.svg b/webui/public/providers/redis.svg deleted file mode 100644 index e0944a282..000000000 --- a/webui/public/providers/redis.svg +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/webui/public/providers/rest.svg b/webui/public/providers/rest.svg deleted file mode 100644 index 9a877e0d4..000000000 --- a/webui/public/providers/rest.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/webui/public/providers/swarm.svg b/webui/public/providers/swarm.svg deleted file mode 100644 index db4a729e6..000000000 --- a/webui/public/providers/swarm.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/webui/public/providers/zookeeper.svg b/webui/public/providers/zookeeper.svg deleted file mode 100644 index 0b9f1be63..000000000 --- a/webui/public/providers/zookeeper.svg +++ /dev/null @@ -1,128 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/webui/public/robots.txt b/webui/public/robots.txt new file mode 100644 index 000000000..1f53798bb --- /dev/null +++ b/webui/public/robots.txt @@ -0,0 +1,2 @@ +User-agent: * +Disallow: / diff --git a/webui/public/traefiklabs-hub-button-app/main-v1.js b/webui/public/traefiklabs-hub-button-app/main-v1.js index 2c912be53..e140dab34 100644 --- a/webui/public/traefiklabs-hub-button-app/main-v1.js +++ b/webui/public/traefiklabs-hub-button-app/main-v1.js @@ -1,3 +1,23 @@ /* eslint-disable */ -!function(){var e={110:function(e,t,n){"use strict";var r=n(441),a={childContextTypes:!0,contextType:!0,contextTypes:!0,defaultProps:!0,displayName:!0,getDefaultProps:!0,getDerivedStateFromError:!0,getDerivedStateFromProps:!0,mixins:!0,propTypes:!0,type:!0},l={name:!0,length:!0,prototype:!0,caller:!0,callee:!0,arguments:!0,arity:!0},o={$$typeof:!0,compare:!0,defaultProps:!0,displayName:!0,propTypes:!0,type:!0},i={};function u(e){return r.isMemo(e)?o:i[e.$$typeof]||a}i[r.ForwardRef]={$$typeof:!0,render:!0,defaultProps:!0,displayName:!0,propTypes:!0},i[r.Memo]=o;var s=Object.defineProperty,c=Object.getOwnPropertyNames,f=Object.getOwnPropertySymbols,d=Object.getOwnPropertyDescriptor,p=Object.getPrototypeOf,h=Object.prototype;e.exports=function e(t,n,r){if("string"!==typeof n){if(h){var a=p(n);a&&a!==h&&e(t,a,r)}var o=c(n);f&&(o=o.concat(f(n)));for(var i=u(t),m=u(n),g=0;g