diff --git a/.github/workflows/validate.yaml b/.github/workflows/validate.yaml index 904bb9fef..e24e1a40e 100644 --- a/.github/workflows/validate.yaml +++ b/.github/workflows/validate.yaml @@ -7,7 +7,7 @@ on: env: GO_VERSION: 1.17 - GOLANGCI_LINT_VERSION: v1.42.1 + GOLANGCI_LINT_VERSION: v1.43.0 MISSSPELL_VERSION: v0.3.4 PRE_TARGET: "" diff --git a/.golangci.toml b/.golangci.toml index 9caed7360..904ed4446 100644 --- a/.golangci.toml +++ b/.golangci.toml @@ -96,6 +96,10 @@ "godox", # Too strict "forcetypeassert", # Too strict "tagliatelle", # Not compatible with current tags. + "varnamelen", # not relevant + "nilnil", # not relevant + "ireturn", # not relevant + "contextcheck", # too many false-positive ] [issues] diff --git a/.semaphore/semaphore.yml b/.semaphore/semaphore.yml index 7f24bcefe..bd03faefc 100644 --- a/.semaphore/semaphore.yml +++ b/.semaphore/semaphore.yml @@ -25,7 +25,7 @@ global_job_config: - export "PATH=${GOPATH}/bin:${PATH}" - mkdir -vp "${SEMAPHORE_GIT_DIR}" "${GOPATH}/bin" - export GOPROXY=https://proxy.golang.org,direct - - curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b "${GOPATH}/bin" v1.42.1 + - curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b "${GOPATH}/bin" v1.43.0 - curl -sfL https://install.goreleaser.com/github.com/goreleaser/goreleaser.sh | bash -s -- -b "${GOPATH}/bin" - checkout - cache restore traefik-$(checksum go.sum) diff --git a/CHANGELOG.md b/CHANGELOG.md index e5a696986..26dd81c2c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,33 @@ +## [v2.5.4](https://github.com/traefik/traefik/tree/v2.5.4) (2021-11-08) +[All Commits](https://github.com/traefik/traefik/compare/v2.5.3...v2.5.4) + +**Bug fixes:** +- **[acme]** Update go-acme/lego to v4.5.0 ([#8481](https://github.com/traefik/traefik/pull/8481) by [ldez](https://github.com/ldez)) +- **[k8s/crd,k8s]** fix: add missing RequireAnyClientCert value to TLSOption CRD ([#8464](https://github.com/traefik/traefik/pull/8464) by [kevinpollet](https://github.com/kevinpollet)) +- **[k8s/crd,k8s]** fix: normalize middleware names in ingress route config ([#8484](https://github.com/traefik/traefik/pull/8484) by [aaronraff](https://github.com/aaronraff)) +- **[middleware,provider,tls]** fix: do not require a TLS client cert when InsecureSkipVerify is false ([#8525](https://github.com/traefik/traefik/pull/8525) by [kevinpollet](https://github.com/kevinpollet)) +- **[middleware,tls]** fix: use host's root CA set if ClientTLS ca is not defined ([#8545](https://github.com/traefik/traefik/pull/8545) by [kevinpollet](https://github.com/kevinpollet)) +- **[middleware]** fix: forward request Host to errors middleware service ([#8460](https://github.com/traefik/traefik/pull/8460) by [kevinpollet](https://github.com/kevinpollet)) +- **[middleware]** fix: use EscapedPath as header value when RawPath is empty ([#8251](https://github.com/traefik/traefik/pull/8251) by [dtomcej](https://github.com/dtomcej)) +- **[tcp,udp]** fix: TCP/UDP wrr when all servers have a weight set to 0 ([#8553](https://github.com/traefik/traefik/pull/8553) by [tomMoulard](https://github.com/tomMoulard)) +- **[webui]** fix: bug parsing weighted service provider name ([#8522](https://github.com/traefik/traefik/pull/8522) by [cocoanton](https://github.com/cocoanton)) + +**Documentation:** +- **[acme]** docs: remove quotes in certificatesresolvers CLI examples ([#8544](https://github.com/traefik/traefik/pull/8544) by [rdxmb](https://github.com/rdxmb)) +- **[k8s/ingress,k8s]** docs: clarify usage for cross provider references in Kubernetes ingress annotations ([#8536](https://github.com/traefik/traefik/pull/8536) by [rtribotte](https://github.com/rtribotte)) +- **[k8s/ingress]** docs: networking.k8s.io/v1beta1 to networking.k8s.io/v1 ([#8523](https://github.com/traefik/traefik/pull/8523) by [pmareke](https://github.com/pmareke)) +- **[k8s]** docs: replace links to French translation of k8s docs with English ones ([#8457](https://github.com/traefik/traefik/pull/8457) by [FoseFx](https://github.com/FoseFx)) +- **[k8s]** docs: remove non-working kind config in IngressRouteTCP/UDP examples ([#8538](https://github.com/traefik/traefik/pull/8538) by [kevinpollet](https://github.com/kevinpollet)) +- **[kv]** docs: fix typo in KV providers documentation ([#8477](https://github.com/traefik/traefik/pull/8477) by [rondoe](https://github.com/rondoe)) +- **[metrics]** docs: fix typo in addRoutersLabels option title ([#8561](https://github.com/traefik/traefik/pull/8561) by [kevinpollet](https://github.com/kevinpollet)) +- **[middleware]** fix: sourceCriterion documentation for InFlightReq and RateLimit middlewares ([#8524](https://github.com/traefik/traefik/pull/8524) by [pmareke](https://github.com/pmareke)) +- **[middleware]** Mention escaping escape characters in YAML for regex usage ([#8496](https://github.com/traefik/traefik/pull/8496) by [JackMorganNZ](https://github.com/JackMorganNZ)) +- **[rules]** docs: add named groups details to Regexp Syntax section ([#8559](https://github.com/traefik/traefik/pull/8559) by [kerrsmith](https://github.com/kerrsmith)) +- **[tracing]** docs: reword tracing config descriptions to be consistent ([#8473](https://github.com/traefik/traefik/pull/8473) by [kevinpollet](https://github.com/kevinpollet)) +- docs: remove link to microbadger.com ([#8555](https://github.com/traefik/traefik/pull/8555) by [CrispyBaguette](https://github.com/CrispyBaguette)) +- docs: remove http scheme urls in documentation ([#8507](https://github.com/traefik/traefik/pull/8507) by [tomMoulard](https://github.com/tomMoulard)) +- docs: update traefik image version ([#8533](https://github.com/traefik/traefik/pull/8533) by [tomMoulard](https://github.com/tomMoulard)) + ## [v2.5.3](https://github.com/traefik/traefik/tree/v2.5.3) (2021-09-20) [All Commits](https://github.com/traefik/traefik/compare/v2.5.2...v2.5.3) diff --git a/README.md b/README.md index 120806bc8..c51d744ee 100644 --- a/README.md +++ b/README.md @@ -63,7 +63,7 @@ _(But if you'd rather configure some of your routes manually, Traefik supports t - Keeps access logs (JSON, CLF) - Fast - Exposes a Rest API -- Packaged as a single binary file (made with :heart: with go) and available as a [tiny](https://microbadger.com/images/traefik) [official](https://hub.docker.com/r/_/traefik/) docker image +- Packaged as a single binary file (made with :heart: with go) and available as an [official](https://hub.docker.com/r/_/traefik/) docker image ## Supported Backends diff --git a/build.Dockerfile b/build.Dockerfile index 1e6254406..fd72177f9 100644 --- a/build.Dockerfile +++ b/build.Dockerfile @@ -14,7 +14,7 @@ RUN mkdir -p /usr/local/bin \ | tar -xzC /usr/local/bin --transform 's#^.+/##x' # Download golangci-lint binary to bin folder in $GOPATH -RUN curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | bash -s -- -b $GOPATH/bin v1.42.1 +RUN curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | bash -s -- -b $GOPATH/bin v1.43.0 # Download misspell binary to bin folder in $GOPATH RUN curl -sfL https://raw.githubusercontent.com/client9/misspell/master/install-misspell.sh | bash -s -- -b $GOPATH/bin v0.3.4 diff --git a/docs/content/getting-started/install-traefik.md b/docs/content/getting-started/install-traefik.md index e82168f8e..02a1aa243 100644 --- a/docs/content/getting-started/install-traefik.md +++ b/docs/content/getting-started/install-traefik.md @@ -24,7 +24,7 @@ For more details, go to the [Docker provider documentation](../providers/docker. !!! tip * Prefer a fixed version than the latest that could be an unexpected version. - ex: `traefik:v2.1.4` + ex: `traefik:v2.5` * Docker images are based from the [Alpine Linux Official image](https://hub.docker.com/_/alpine). * Any orchestrator using docker images can fetch the official Traefik docker image. @@ -101,13 +101,13 @@ helm install traefik traefik/traefik This HelmChart does not expose the Traefik dashboard by default, for security concerns. Thus, there are multiple ways to expose the dashboard. -For instance, the dashboard access could be achieved through a port-forward : +For instance, the dashboard access could be achieved through a port-forward: ```shell kubectl port-forward $(kubectl get pods --selector "app.kubernetes.io/name=traefik" --output=name) 9000:9000 ``` -Accessible with the url: http://127.0.0.1:9000/dashboard/ +It can then be reached at: `http://127.0.0.1:9000/dashboard/` Another way would be to apply your own configuration, for instance, by defining and applying an IngressRoute CRD (`kubectl apply -f dashboard.yaml`): diff --git a/docs/content/getting-started/quick-start.md b/docs/content/getting-started/quick-start.md index ee0ee5923..4cce47022 100644 --- a/docs/content/getting-started/quick-start.md +++ b/docs/content/getting-started/quick-start.md @@ -36,7 +36,7 @@ Start your `reverse-proxy` with the following command: docker-compose up -d reverse-proxy ``` -You can open a browser and go to to see Traefik's API rawdata (we'll go back there once we have launched a service in step 2). +You can open a browser and go to `http://localhost:8080/api/rawdata` to see Traefik's API rawdata (we'll go back there once we have launched a service in step 2). ## Traefik Detects New Services and Creates the Route for You @@ -61,7 +61,7 @@ Start the `whoami` service with the following command: docker-compose up -d whoami ``` -Go back to your browser () and see that Traefik has automatically detected the new container and updated its own configuration. +Go back to your browser (`http://localhost:8080/api/rawdata`) and see that Traefik has automatically detected the new container and updated its own configuration. When Traefik detects new services, it creates the corresponding routes so you can call them ... _let's see!_ (Here, we're using curl) @@ -85,7 +85,7 @@ Run more instances of your `whoami` service with the following command: docker-compose up -d --scale whoami=2 ``` -Go back to your browser () and see that Traefik has automatically detected the new instance of the container. +Go back to your browser (`http://localhost:8080/api/rawdata`) and see that Traefik has automatically detected the new instance of the container. Finally, see that Traefik load-balances between the two instances of your service by running the following command twice: diff --git a/docs/content/https/acme.md b/docs/content/https/acme.md index 0f4bf8472..38b25746f 100644 --- a/docs/content/https/acme.md +++ b/docs/content/https/acme.md @@ -560,7 +560,7 @@ certificatesResolvers: ```bash tab="CLI" # ... ---certificatesresolvers.myresolver.acme.preferredChain="ISRG Root X1" +--certificatesresolvers.myresolver.acme.preferredChain=ISRG Root X1 # ... ``` @@ -588,7 +588,7 @@ certificatesResolvers: ```bash tab="CLI" # ... ---certificatesresolvers.myresolver.acme.keyType="RSA4096" +--certificatesresolvers.myresolver.acme.keyType=RSA4096 # ... ``` diff --git a/docs/content/middlewares/http/forwardauth.md b/docs/content/middlewares/http/forwardauth.md index f56fd320f..9b21e0279 100644 --- a/docs/content/middlewares/http/forwardauth.md +++ b/docs/content/middlewares/http/forwardauth.md @@ -353,7 +353,8 @@ The `tls` option is the TLS configuration from Traefik to the authentication ser #### `tls.ca` -Certificate Authority used for the secured connection to the authentication server. +Certificate Authority used for the secured connection to the authentication server, +defaults to the system bundle. ```yaml tab="Docker" labels: diff --git a/docs/content/middlewares/http/inflightreq.md b/docs/content/middlewares/http/inflightreq.md index 8095ccd15..f2f76a3cb 100644 --- a/docs/content/middlewares/http/inflightreq.md +++ b/docs/content/middlewares/http/inflightreq.md @@ -115,7 +115,7 @@ http: ### `sourceCriterion` The `sourceCriterion` option defines what criterion is used to group requests as originating from a common source. -The precedence order is `ipStrategy`, then `requestHeaderName`, then `requestHost`. +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`. #### `sourceCriterion.ipStrategy` diff --git a/docs/content/middlewares/http/ratelimit.md b/docs/content/middlewares/http/ratelimit.md index 8c382de10..7c8a45906 100644 --- a/docs/content/middlewares/http/ratelimit.md +++ b/docs/content/middlewares/http/ratelimit.md @@ -250,7 +250,7 @@ http: ### `sourceCriterion` The `sourceCriterion` option defines what criterion is used to group requests as originating from a common source. -The precedence order is `ipStrategy`, then `requestHeaderName`, then `requestHost`. +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`). #### `sourceCriterion.ipStrategy` diff --git a/docs/content/migration/v2.md b/docs/content/migration/v2.md index fa907387e..ec772d7da 100644 --- a/docs/content/migration/v2.md +++ b/docs/content/migration/v2.md @@ -179,7 +179,7 @@ To enable HTTPS, it is not sufficient anymore to only rely on a TLS section in t #### Expose an Ingress on 80 and 443 -Define the default TLS configuration on the HTTPS entry point. +Define the default TLS configuration on the HTTPS entry point. ```yaml tab="Ingress" kind: Ingress @@ -335,7 +335,7 @@ The file parser has been changed, since v2.3 the unknown options/fields in a dyn ### IngressClass In `v2.3`, the support of `IngressClass`, which is available since Kubernetes version `1.18`, has been introduced. -In order to be able to use this new resource the [Kubernetes RBAC](../reference/dynamic-configuration/kubernetes-crd.md#rbac) must be updated. +In order to be able to use this new resource the [Kubernetes RBAC](../reference/dynamic-configuration/kubernetes-crd.md#rbac) must be updated. ## v2.3 to v2.4 @@ -350,7 +350,7 @@ It is therefore necessary to update [RBAC](../reference/dynamic-configuration/ku In `v2.4.8`, we introduced a new check on domain names used in HTTP router rule `Host` and `HostRegexp` expressions, and in TCP router rule `HostSNI` expression. -This check ensures that provided domain names don't contain non-ASCII characters. +This check ensures that provided domain names don't contain non-ASCII characters. If not, an error is raised, and the associated router will be shown as invalid in the dashboard. This new behavior is intended to show what was failing silently previously and to help troubleshooting configuration issues. @@ -380,8 +380,8 @@ To allow it, the `allowExternalNameServices` option should be set to `true`. ### Kubernetes CRD -In `v2.5`, the [Traefik CRDs](../reference/dynamic-configuration/kubernetes-crd.md#definitions) have been updated to support the new API version `apiextensions.k8s.io/v1`. -As required by `apiextensions.k8s.io/v1`, we have included the OpenAPI validation schema. +In `v2.5`, the [Traefik CRDs](../reference/dynamic-configuration/kubernetes-crd.md#definitions) have been updated to support the new API version `apiextensions.k8s.io/v1`. +As required by `apiextensions.k8s.io/v1`, we have included the OpenAPI validation schema. After deploying the new [Traefik CRDs](../reference/dynamic-configuration/kubernetes-crd.md#definitions), the resources will be validated only on creation or update. @@ -420,7 +420,7 @@ the legacy behavior related to the CommonName field can not be enabled at all an ### Errors middleware -In `v2.5.4`, when the errors service is configured with the [`PassHostHeader`](../routing/services/index.md#pass-host-header) option to `true` (default), +In `v2.5.4`, when the errors service is configured with the [`PassHostHeader`](../routing/services/index.md#pass-host-header) option to `true` (default), the forwarded Host header value is now set to the client request Host value and not `0.0.0.0`. Check out the [Errors middleware](../middlewares/http/errorpages.md#service) documentation for more details. diff --git a/docs/content/observability/access-logs.md b/docs/content/observability/access-logs.md index c1ad21d94..1f9ba9903 100644 --- a/docs/content/observability/access-logs.md +++ b/docs/content/observability/access-logs.md @@ -247,7 +247,7 @@ version: "3.7" services: traefik: - image: traefik:v2.2 + image: traefik:v2.5 environment: - TZ=US/Alaska command: diff --git a/docs/content/observability/metrics/datadog.md b/docs/content/observability/metrics/datadog.md index dd10105b1..b52b35b65 100644 --- a/docs/content/observability/metrics/datadog.md +++ b/docs/content/observability/metrics/datadog.md @@ -59,7 +59,7 @@ metrics: ```bash tab="CLI" --metrics.datadog.addEntryPointsLabels=true ``` -#### `AddRoutersLabels` +#### `addRoutersLabels` _Optional, Default=false_ diff --git a/docs/content/observability/metrics/influxdb.md b/docs/content/observability/metrics/influxdb.md index 447379821..5aa1c9ebb 100644 --- a/docs/content/observability/metrics/influxdb.md +++ b/docs/content/observability/metrics/influxdb.md @@ -170,7 +170,7 @@ metrics: --metrics.influxdb.addEntryPointsLabels=true ``` -#### `AddRoutersLabels` +#### `addRoutersLabels` _Optional, Default=false_ diff --git a/docs/content/observability/metrics/prometheus.md b/docs/content/observability/metrics/prometheus.md index d751ebb56..d4ac5e54a 100644 --- a/docs/content/observability/metrics/prometheus.md +++ b/docs/content/observability/metrics/prometheus.md @@ -64,7 +64,7 @@ metrics: --metrics.prometheus.addEntryPointsLabels=true ``` -#### `AddRoutersLabels` +#### `addRoutersLabels` _Optional, Default=false_ diff --git a/docs/content/observability/metrics/statsd.md b/docs/content/observability/metrics/statsd.md index 7a57ea91f..4d310969a 100644 --- a/docs/content/observability/metrics/statsd.md +++ b/docs/content/observability/metrics/statsd.md @@ -60,7 +60,7 @@ metrics: --metrics.statsd.addEntryPointsLabels=true ``` -#### `AddRoutersLabels` +#### `addRoutersLabels` _Optional, Default=false_ diff --git a/docs/content/providers/consul-catalog.md b/docs/content/providers/consul-catalog.md index bfadd47a5..1e4a1bedb 100644 --- a/docs/content/providers/consul-catalog.md +++ b/docs/content/providers/consul-catalog.md @@ -368,7 +368,8 @@ Defines TLS options for Consul server endpoint. _Optional_ -`ca` is the path to the CA certificate used for Consul communication, defaults to the system bundle if not specified. +Certificate Authority used for the secure connection to Consul, +defaults to the system bundle. ```yaml tab="File (YAML)" providers: diff --git a/docs/content/providers/consul.md b/docs/content/providers/consul.md index ab488ef3f..d101c7e69 100644 --- a/docs/content/providers/consul.md +++ b/docs/content/providers/consul.md @@ -106,7 +106,8 @@ _Optional_ #### `tls.ca` -Certificate Authority used for the secure connection to Consul. +Certificate Authority used for the secure connection to Consul, +defaults to the system bundle. ```yaml tab="File (YAML)" providers: diff --git a/docs/content/providers/docker.md b/docs/content/providers/docker.md index e354599e1..f69401e4d 100644 --- a/docs/content/providers/docker.md +++ b/docs/content/providers/docker.md @@ -615,7 +615,8 @@ _Optional_ #### `tls.ca` -Certificate Authority used for the secure connection to Docker. +Certificate Authority used for the secure connection to Docker, +defaults to the system bundle. ```yaml tab="File (YAML)" providers: diff --git a/docs/content/providers/etcd.md b/docs/content/providers/etcd.md index 46e61f4fc..8df549ca3 100644 --- a/docs/content/providers/etcd.md +++ b/docs/content/providers/etcd.md @@ -106,7 +106,8 @@ _Optional_ #### `tls.ca` -Certificate Authority used for the secure connection to etcd. +Certificate Authority used for the secure connection to etcd, +defaults to the system bundle. ```yaml tab="File (YAML)" providers: diff --git a/docs/content/providers/http.md b/docs/content/providers/http.md index 5c611cef9..f70c2d059 100644 --- a/docs/content/providers/http.md +++ b/docs/content/providers/http.md @@ -78,7 +78,8 @@ _Optional_ #### `tls.ca` -Certificate Authority used for the secure connection to the configured endpoint. +Certificate Authority used for the secure connection to the configured endpoint, +defaults to the system bundle. ```yaml tab="File (YAML)" providers: diff --git a/docs/content/providers/kubernetes-crd.md b/docs/content/providers/kubernetes-crd.md index 7276e391d..89dd02e6b 100644 --- a/docs/content/providers/kubernetes-crd.md +++ b/docs/content/providers/kubernetes-crd.md @@ -62,7 +62,7 @@ Previous versions of Traefik used a [KV store](https://doc.traefik.io/traefik/v1 If you need Let's Encrypt with HA in a Kubernetes environment, we recommend using [Traefik Enterprise](https://traefik.io/traefik-enterprise/), which includes distributed Let's Encrypt as a supported feature. -If you want to keep using Traefik Proxy, high availability for Let's Encrypt can be achieved by using a Certificate Controller such as [Cert-Manager](https://docs.cert-manager.io/en/latest/index.html). +If you want to keep using Traefik Proxy, high availability for Let's Encrypt can be achieved by using a Certificate Controller such as [Cert-Manager](https://cert-manager.io/docs/). When using Cert-Manager to manage certificates, it creates secrets in your namespaces that can be referenced as TLS secrets in your [ingress objects](https://kubernetes.io/docs/concepts/services-networking/ingress/#tls). When using the Traefik Kubernetes CRD Provider, unfortunately Cert-Manager cannot yet interface directly with the CRDs. A workaround is to enable the [Kubernetes Ingress provider](./kubernetes-ingress.md) to allow Cert-Manager to create ingress objects to complete the challenges. diff --git a/docs/content/providers/kubernetes-ingress.md b/docs/content/providers/kubernetes-ingress.md index 63e4ef06a..659f6a3d8 100644 --- a/docs/content/providers/kubernetes-ingress.md +++ b/docs/content/providers/kubernetes-ingress.md @@ -36,10 +36,10 @@ and derives the corresponding dynamic configuration from it, which in turn creates the resulting routers, services, handlers, etc. ```yaml tab="Ingress" +apiVersion: networking.k8s.io/v1 kind: Ingress -apiVersion: networking.k8s.io/v1beta1 metadata: - name: "foo" + name: foo namespace: production spec: @@ -48,20 +48,26 @@ spec: http: paths: - path: /bar + pathType: Exact backend: - serviceName: service1 - servicePort: 80 + service: + name: service1 + port: + number: 80 - path: /foo + pathType: Exact backend: - serviceName: service1 - servicePort: 80 + service: + name: service1 + port: + number: 80 ``` -```yaml tab="Ingress Kubernetes v1.19+" +```yaml tab="Ingress v1beta1 (deprecated)" +apiVersion: networking.k8s.io/v1beta1 kind: Ingress -apiVersion: networking.k8s.io/v1 metadata: - name: "foo" + name: foo namespace: production spec: @@ -70,19 +76,13 @@ spec: http: paths: - path: /bar - pathType: Exact backend: - service: - name: service1 - port: - number: 80 + serviceName: service1 + servicePort: 80 - path: /foo - pathType: Exact backend: - service: - name: service1 - port: - number: 80 + serviceName: service1 + servicePort: 80 ``` ## LetsEncrypt Support with the Ingress Provider @@ -104,7 +104,7 @@ If you need Let's Encrypt with high availability in a Kubernetes environment, we recommend using [Traefik Enterprise](https://traefik.io/traefik-enterprise/) which includes distributed Let's Encrypt as a supported feature. If you want to keep using Traefik Proxy, -LetsEncrypt HA can be achieved by using a Certificate Controller such as [Cert-Manager](https://docs.cert-manager.io/en/latest/index.html). +LetsEncrypt HA can be achieved by using a Certificate Controller such as [Cert-Manager](https://cert-manager.io/docs/). When using Cert-Manager to manage certificates, it creates secrets in your namespaces that can be referenced as TLS secrets in your [ingress objects](https://kubernetes.io/docs/concepts/services-networking/ingress/#tls). @@ -272,19 +272,19 @@ Otherwise, Ingresses missing the annotation, having an empty value, or the value ``` ```yaml tab="Ingress" - apiVersion: "networking.k8s.io/v1beta1" - kind: "Ingress" + apiVersion: networking.k8s.io/v1beta1 + kind: Ingress metadata: - name: "example-ingress" + name: example-ingress spec: - ingressClassName: "traefik-lb" + ingressClassName: traefik-lb rules: - host: "*.example.com" http: paths: - - path: "/example" + - path: /example backend: - serviceName: "example-service" + serviceName: example-service servicePort: 80 ``` @@ -303,21 +303,21 @@ Otherwise, Ingresses missing the annotation, having an empty value, or the value ``` ```yaml tab="Ingress" - apiVersion: "networking.k8s.io/v1" - kind: "Ingress" + apiVersion: networking.k8s.io/v1 + kind: Ingress metadata: - name: "example-ingress" + name: example-ingress spec: - ingressClassName: "traefik-lb" + ingressClassName: traefik-lb rules: - host: "*.example.com" http: paths: - - path: "/example" + - path: /example pathType: Exact backend: service: - name: "example-service" + name: example-service port: number: 80 ``` diff --git a/docs/content/providers/marathon.md b/docs/content/providers/marathon.md index 769d2ee3c..290aa500c 100644 --- a/docs/content/providers/marathon.md +++ b/docs/content/providers/marathon.md @@ -406,7 +406,8 @@ _Optional_ #### `tls.ca` -Certificate Authority used for the secure connection to Marathon. +Certificate Authority used for the secure connection to Marathon, +defaults to the system bundle. ```yaml tab="File (YAML)" providers: diff --git a/docs/content/providers/redis.md b/docs/content/providers/redis.md index 61fa801d0..70607d2ae 100644 --- a/docs/content/providers/redis.md +++ b/docs/content/providers/redis.md @@ -106,7 +106,8 @@ _Optional_ #### `tls.ca` -Certificate Authority used for the secure connection to Redis. +Certificate Authority used for the secure connection to Redis, +defaults to the system bundle. ```yaml tab="File (YAML)" providers: diff --git a/docs/content/providers/zookeeper.md b/docs/content/providers/zookeeper.md index ffb343b81..b89daa6bf 100644 --- a/docs/content/providers/zookeeper.md +++ b/docs/content/providers/zookeeper.md @@ -106,7 +106,8 @@ _Optional_ #### `tls.ca` -Certificate Authority used for the secure connection to ZooKeeper. +Certificate Authority used for the secure connection to ZooKeeper, +defaults to the system bundle. ```yaml tab="File (YAML)" providers: diff --git a/docs/content/reference/dynamic-configuration/kubernetes-crd-rbac.yml b/docs/content/reference/dynamic-configuration/kubernetes-crd-rbac.yml index 58548c561..4a5b80e6b 100644 --- a/docs/content/reference/dynamic-configuration/kubernetes-crd-rbac.yml +++ b/docs/content/reference/dynamic-configuration/kubernetes-crd-rbac.yml @@ -1,5 +1,5 @@ +apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole -apiVersion: rbac.authorization.k8s.io/v1beta1 metadata: name: traefik-ingress-controller @@ -48,8 +48,8 @@ rules: - watch --- +apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding -apiVersion: rbac.authorization.k8s.io/v1beta1 metadata: name: traefik-ingress-controller diff --git a/docs/content/reference/dynamic-configuration/kubernetes-gateway-rbac.yml b/docs/content/reference/dynamic-configuration/kubernetes-gateway-rbac.yml index 7542b170b..7d04a81cf 100644 --- a/docs/content/reference/dynamic-configuration/kubernetes-gateway-rbac.yml +++ b/docs/content/reference/dynamic-configuration/kubernetes-gateway-rbac.yml @@ -45,8 +45,8 @@ rules: - update --- +apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding -apiVersion: rbac.authorization.k8s.io/v1beta1 metadata: name: gateway-controller diff --git a/docs/content/reference/dynamic-configuration/traefik.containo.us_middlewares.yaml b/docs/content/reference/dynamic-configuration/traefik.containo.us_middlewares.yaml index 63e57686a..932e54010 100644 --- a/docs/content/reference/dynamic-configuration/traefik.containo.us_middlewares.yaml +++ b/docs/content/reference/dynamic-configuration/traefik.containo.us_middlewares.yaml @@ -400,7 +400,7 @@ spec: info configuration. properties: issuer: - description: TLSCLientCertificateIssuerDNInfo holds the client + description: TLSClientCertificateIssuerDNInfo holds the client TLS certificate distinguished name info configuration. cf https://tools.ietf.org/html/rfc3739 properties: @@ -428,7 +428,7 @@ spec: serialNumber: type: boolean subject: - description: TLSCLientCertificateSubjectDNInfo holds the client + description: TLSClientCertificateSubjectDNInfo holds the client TLS certificate distinguished name info configuration. cf https://tools.ietf.org/html/rfc3739 properties: diff --git a/docs/content/routing/providers/kubernetes-crd.md b/docs/content/routing/providers/kubernetes-crd.md index 56b93dfc4..239aec169 100644 --- a/docs/content/routing/providers/kubernetes-crd.md +++ b/docs/content/routing/providers/kubernetes-crd.md @@ -131,7 +131,6 @@ The Kubernetes Ingress Controller, The Custom Resource Way. - tcpep routes: - match: HostSNI(`bar`) - kind: Rule services: - name: whoamitcp port: 8080 @@ -147,8 +146,7 @@ The Kubernetes Ingress Controller, The Custom Resource Way. entryPoints: - udpep routes: - - kind: Rule - services: + - services: - name: whoamiudp port: 8080 ``` @@ -1224,7 +1222,6 @@ Register the `IngressRouteTCP` [kind](../../reference/dynamic-configuration/kube routes: - match: HostSNI(`*`) - kind: Rule services: - name: external-svc port: 80 @@ -1254,7 +1251,6 @@ Register the `IngressRouteTCP` [kind](../../reference/dynamic-configuration/kube routes: - match: HostSNI(`*`) - kind: Rule services: - name: external-svc port: 80 diff --git a/docs/content/routing/providers/kubernetes-ingress.md b/docs/content/routing/providers/kubernetes-ingress.md index e4a2fb585..0f24b0317 100644 --- a/docs/content/routing/providers/kubernetes-ingress.md +++ b/docs/content/routing/providers/kubernetes-ingress.md @@ -15,8 +15,8 @@ which in turn will create the resulting routers, services, handlers, etc. ```yaml tab="RBAC" --- + apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole - apiVersion: rbac.authorization.k8s.io/v1beta1 metadata: name: traefik-ingress-controller rules: @@ -48,8 +48,8 @@ which in turn will create the resulting routers, services, handlers, etc. - update --- + apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding - apiVersion: rbac.authorization.k8s.io/v1beta1 metadata: name: traefik-ingress-controller roleRef: @@ -63,8 +63,37 @@ which in turn will create the resulting routers, services, handlers, etc. ``` ```yaml tab="Ingress" + apiVersion: networking.k8s.io/v1 kind: Ingress + metadata: + name: myingress + annotations: + traefik.ingress.kubernetes.io/router.entrypoints: web + + spec: + rules: + - host: example.com + http: + paths: + - path: /bar + pathType: Exact + backend: + service: + name: whoami + port: + number: 80 + - path: /foo + pathType: Exact + backend: + service: + name: whoami + port: + number: 80 + ``` + + ```yaml tab="Ingress v1beta1 (deprecated)" apiVersion: networking.k8s.io/v1beta1 + kind: Ingress metadata: name: myingress annotations: @@ -84,36 +113,7 @@ which in turn will create the resulting routers, services, handlers, etc. serviceName: whoami servicePort: 80 ``` - - ```yaml tab="Ingress Kubernetes v1.19+" - kind: Ingress - apiVersion: networking.k8s.io/v1 - metadata: - name: myingress - annotations: - traefik.ingress.kubernetes.io/router.entrypoints: web - - spec: - rules: - - host: example.com - http: - paths: - - path: /bar - pathType: Exact - backend: - service: - name: whoami - port: - number: 80 - - path: /foo - pathType: Exact - backend: - service: - name: whoami - port: - number: 80 - ``` - + ```yaml tab="Traefik" apiVersion: v1 kind: ServiceAccount @@ -121,8 +121,8 @@ which in turn will create the resulting routers, services, handlers, etc. name: traefik-ingress-controller --- - kind: Deployment apiVersion: apps/v1 + kind: Deployment metadata: name: traefik labels: @@ -166,8 +166,8 @@ which in turn will create the resulting routers, services, handlers, etc. ``` ```yaml tab="Whoami" - kind: Deployment apiVersion: apps/v1 + kind: Deployment metadata: name: whoami labels: @@ -209,6 +209,11 @@ which in turn will create the resulting routers, services, handlers, etc. ## Annotations +!!! warning "Referencing resources in annotations" + + In an annotation, when referencing a resource defined by another provider, + the [provider namespace syntax](../../providers/overview.md#provider-namespace) must be used. + #### On Ingress ??? info "`traefik.ingress.kubernetes.io/router.entrypoints`" @@ -224,7 +229,7 @@ which in turn will create the resulting routers, services, handlers, etc. See [middlewares](../routers/index.md#middlewares) and [middlewares overview](../../middlewares/overview.md) for more information. ```yaml - traefik.ingress.kubernetes.io/router.middlewares: auth@file,prefix@kubernetescrd,cb@file + traefik.ingress.kubernetes.io/router.middlewares: auth@file,default-prefix@kubernetescrd ``` ??? info "`traefik.ingress.kubernetes.io/router.priority`" @@ -237,7 +242,7 @@ which in turn will create the resulting routers, services, handlers, etc. ??? info "`traefik.ingress.kubernetes.io/router.pathmatcher`" - Overrides the default router rule type used for a path. + Overrides the default router rule type used for a path. Only path-related matcher name can be specified: `Path`, `PathPrefix`. Default `PathPrefix` @@ -283,7 +288,7 @@ which in turn will create the resulting routers, services, handlers, etc. See [options](../routers/index.md#options) for more information. ```yaml - traefik.ingress.kubernetes.io/router.tls.options: foobar + traefik.ingress.kubernetes.io/router.tls.options: foobar@file ``` #### On Service @@ -401,8 +406,8 @@ This way, any Ingress attached to this Entrypoint will have TLS termination by d ```yaml tab="RBAC" --- + apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole - apiVersion: rbac.authorization.k8s.io/v1beta1 metadata: name: traefik-ingress-controller rules: @@ -434,8 +439,8 @@ This way, any Ingress attached to this Entrypoint will have TLS termination by d - update --- + apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding - apiVersion: rbac.authorization.k8s.io/v1beta1 metadata: name: traefik-ingress-controller roleRef: @@ -449,8 +454,37 @@ This way, any Ingress attached to this Entrypoint will have TLS termination by d ``` ```yaml tab="Ingress" + apiVersion: networking.k8s.io/v1 kind: Ingress + metadata: + name: myingress + annotations: + traefik.ingress.kubernetes.io/router.entrypoints: websecure + + spec: + rules: + - host: example.com + http: + paths: + - path: /bar + pathType: Exact + backend: + service: + name: whoami + port: + number: 80 + - path: /foo + pathType: Exact + backend: + service: + name: whoami + port: + number: 80 + ``` + + ```yaml tab="Ingress v1beta1 (deprecated)" apiVersion: networking.k8s.io/v1beta1 + kind: Ingress metadata: name: myingress annotations: @@ -470,36 +504,7 @@ This way, any Ingress attached to this Entrypoint will have TLS termination by d serviceName: whoami servicePort: 80 ``` - - ```yaml tab="Ingress Kubernetes v1.19+" - kind: Ingress - apiVersion: networking.k8s.io/v1 - metadata: - name: myingress - annotations: - traefik.ingress.kubernetes.io/router.entrypoints: websecure - - spec: - rules: - - host: example.com - http: - paths: - - path: /bar - pathType: Exact - backend: - service: - name: whoami - port: - number: 80 - - path: /foo - pathType: Exact - backend: - service: - name: whoami - port: - number: 80 - ``` - + ```yaml tab="Traefik" apiVersion: v1 kind: ServiceAccount @@ -507,8 +512,8 @@ This way, any Ingress attached to this Entrypoint will have TLS termination by d name: traefik-ingress-controller --- - kind: Deployment apiVersion: apps/v1 + kind: Deployment metadata: name: traefik labels: @@ -553,8 +558,8 @@ This way, any Ingress attached to this Entrypoint will have TLS termination by d ``` ```yaml tab="Whoami" - kind: Deployment apiVersion: apps/v1 + kind: Deployment metadata: name: whoami labels: @@ -608,8 +613,8 @@ For more options, please refer to the available [annotations](#on-ingress). ```yaml tab="RBAC" --- + apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole - apiVersion: rbac.authorization.k8s.io/v1beta1 metadata: name: traefik-ingress-controller rules: @@ -641,8 +646,8 @@ For more options, please refer to the available [annotations](#on-ingress). - update --- + apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding - apiVersion: rbac.authorization.k8s.io/v1beta1 metadata: name: traefik-ingress-controller roleRef: @@ -656,8 +661,38 @@ For more options, please refer to the available [annotations](#on-ingress). ``` ```yaml tab="Ingress" + apiVersion: networking.k8s.io/v1 kind: Ingress + metadata: + name: myingress + annotations: + traefik.ingress.kubernetes.io/router.entrypoints: websecure + traefik.ingress.kubernetes.io/router.tls: true + + spec: + rules: + - host: example.com + http: + paths: + - path: /bar + pathType: Exact + backend: + service: + name: whoami + port: + number: 80 + - path: /foo + pathType: Exact + backend: + service: + name: whoami + port: + number: 80 + ``` + + ```yaml tab="Ingress v1beta1 (deprecated)" apiVersion: networking.k8s.io/v1beta1 + kind: Ingress metadata: name: myingress annotations: @@ -678,37 +713,7 @@ For more options, please refer to the available [annotations](#on-ingress). serviceName: whoami servicePort: 80 ``` - - ```yaml tab="Ingress Kubernetes v1.19+" - kind: Ingress - apiVersion: networking.k8s.io/v1 - metadata: - name: myingress - annotations: - traefik.ingress.kubernetes.io/router.entrypoints: websecure - traefik.ingress.kubernetes.io/router.tls: true - - spec: - rules: - - host: example.com - http: - paths: - - path: /bar - pathType: Exact - backend: - service: - name: whoami - port: - number: 80 - - path: /foo - pathType: Exact - backend: - service: - name: whoami - port: - number: 80 - ``` - + ```yaml tab="Traefik" apiVersion: v1 kind: ServiceAccount @@ -716,8 +721,8 @@ For more options, please refer to the available [annotations](#on-ingress). name: traefik-ingress-controller --- - kind: Deployment apiVersion: apps/v1 + kind: Deployment metadata: name: traefik labels: @@ -761,8 +766,8 @@ For more options, please refer to the available [annotations](#on-ingress). ``` ```yaml tab="Whoami" - kind: Deployment apiVersion: apps/v1 + kind: Deployment metadata: name: whoami labels: @@ -807,8 +812,34 @@ For more options, please refer to the available [annotations](#on-ingress). ??? example "Using a secret" ```yaml tab="Ingress" + apiVersion: networking.k8s.io/v1 kind: Ingress + metadata: + name: foo + namespace: production + + spec: + rules: + - host: example.net + http: + paths: + - path: /bar + pathType: Exact + backend: + service: + name: service1 + port: + number: 80 + # Only selects which certificate(s) should be loaded from the secret, in order to terminate TLS. + # Doesn't enable TLS for that ingress (hence for the underlying router). + # Please see the TLS annotations on ingress made for that purpose. + tls: + - secretName: supersecret + ``` + + ```yaml tab="Ingress v1beta1 (deprecated)" apiVersion: networking.k8s.io/v1beta1 + kind: Ingress metadata: name: foo namespace: production @@ -829,32 +860,6 @@ For more options, please refer to the available [annotations](#on-ingress). - secretName: supersecret ``` - ```yaml tab="Ingress Kubernetes v1.19+" - kind: Ingress - apiVersion: networking.k8s.io/v1 - metadata: - name: foo - namespace: production - - spec: - rules: - - host: example.net - http: - paths: - - path: /bar - pathType: Exact - backend: - service: - name: service1 - port: - number: 80 - # Only selects which certificate(s) should be loaded from the secret, in order to terminate TLS. - # Doesn't enable TLS for that ingress (hence for the underlying router). - # Please see the TLS annotations on ingress made for that purpose. - tls: - - secretName: supersecret - ``` - ```yaml tab="Secret" apiVersion: v1 kind: Secret @@ -900,18 +905,6 @@ and will connect via TLS automatically. Ingresses can be created that look like the following: ```yaml tab="Ingress" -apiVersion: networking.k8s.io/v1beta1 -kind: Ingress -metadata: - name: cheese - -spec: - defaultBackend: - serviceName: stilton - serverPort: 80 -``` - -```yaml tab="Ingress Kubernetes v1.19+" apiVersion: networking.k8s.io/v1 kind: Ingress metadata: @@ -925,6 +918,18 @@ spec: number: 80 ``` +```yaml tab="Ingress v1beta1 (deprecated)" +apiVersion: networking.k8s.io/v1beta1 +kind: Ingress +metadata: + name: cheese + +spec: + defaultBackend: + serviceName: stilton + serverPort: 80 +``` + This ingress follows the Global Default Backend property of ingresses. This will allow users to create a "default router" that will match all unmatched requests. diff --git a/docs/content/routing/routers/index.md b/docs/content/routing/routers/index.md index ef221ad0a..c0508c16c 100644 --- a/docs/content/routing/routers/index.md +++ b/docs/content/routing/routers/index.md @@ -251,6 +251,7 @@ The table below lists all the available matchers: `HostRegexp` and `Path` accept an expression with zero or more groups enclosed by curly braces. Named groups can be like `{name:pattern}` that matches the given regexp pattern or like `{name}` that matches anything until the next dot. + The group name (`name` is the above examples) is an arbitrary value. Any pattern supported by [Go's regexp package](https://golang.org/pkg/regexp/) may be used (example: `{subdomain:[a-z]+}.{domain}.com`). !!! info "Combining Matchers Using Operators and Parenthesis" diff --git a/integration/fixtures/k8s/01-traefik-crd.yml b/integration/fixtures/k8s/01-traefik-crd.yml index 3227cb571..889f0c40c 100644 --- a/integration/fixtures/k8s/01-traefik-crd.yml +++ b/integration/fixtures/k8s/01-traefik-crd.yml @@ -842,7 +842,7 @@ spec: info configuration. properties: issuer: - description: TLSCLientCertificateIssuerDNInfo holds the client + description: TLSClientCertificateIssuerDNInfo holds the client TLS certificate distinguished name info configuration. cf https://tools.ietf.org/html/rfc3739 properties: @@ -870,7 +870,7 @@ spec: serialNumber: type: boolean subject: - description: TLSCLientCertificateSubjectDNInfo holds the client + description: TLSClientCertificateSubjectDNInfo holds the client TLS certificate distinguished name info configuration. cf https://tools.ietf.org/html/rfc3739 properties: diff --git a/pkg/anonymize/anonymize_config_test.go b/pkg/anonymize/anonymize_config_test.go index 5077cd252..8f8176ece 100644 --- a/pkg/anonymize/anonymize_config_test.go +++ b/pkg/anonymize/anonymize_config_test.go @@ -272,7 +272,7 @@ func TestDo_dynamicConfiguration(t *testing.T) { }, ForwardAuth: &dynamic.ForwardAuth{ Address: "127.0.0.1", - TLS: &dynamic.ClientTLS{ + TLS: &types.ClientTLS{ CA: "ca.pem", CAOptional: true, Cert: "cert.pem", @@ -314,7 +314,7 @@ func TestDo_dynamicConfiguration(t *testing.T) { NotAfter: true, NotBefore: true, Sans: true, - Subject: &dynamic.TLSCLientCertificateSubjectDNInfo{ + Subject: &dynamic.TLSClientCertificateSubjectDNInfo{ Country: true, Province: true, Locality: true, @@ -324,7 +324,7 @@ func TestDo_dynamicConfiguration(t *testing.T) { SerialNumber: true, DomainComponent: true, }, - Issuer: &dynamic.TLSCLientCertificateIssuerDNInfo{ + Issuer: &dynamic.TLSClientCertificateIssuerDNInfo{ Country: true, Province: true, Locality: true, diff --git a/pkg/config/dynamic/middlewares.go b/pkg/config/dynamic/middlewares.go index 9c45fbfef..6f82df3a8 100644 --- a/pkg/config/dynamic/middlewares.go +++ b/pkg/config/dynamic/middlewares.go @@ -1,14 +1,11 @@ package dynamic import ( - "crypto/tls" - "crypto/x509" - "fmt" - "os" "time" ptypes "github.com/traefik/paerser/types" "github.com/traefik/traefik/v2/pkg/ip" + "github.com/traefik/traefik/v2/pkg/types" ) // +k8s:deepcopy-gen=true @@ -131,12 +128,12 @@ type ErrorPage struct { // ForwardAuth holds the http forward authentication configuration. type ForwardAuth struct { - Address string `json:"address,omitempty" toml:"address,omitempty" yaml:"address,omitempty"` - TLS *ClientTLS `json:"tls,omitempty" toml:"tls,omitempty" yaml:"tls,omitempty" export:"true"` - TrustForwardHeader bool `json:"trustForwardHeader,omitempty" toml:"trustForwardHeader,omitempty" yaml:"trustForwardHeader,omitempty" export:"true"` - AuthResponseHeaders []string `json:"authResponseHeaders,omitempty" toml:"authResponseHeaders,omitempty" yaml:"authResponseHeaders,omitempty" export:"true"` - AuthResponseHeadersRegex string `json:"authResponseHeadersRegex,omitempty" toml:"authResponseHeadersRegex,omitempty" yaml:"authResponseHeadersRegex,omitempty" export:"true"` - AuthRequestHeaders []string `json:"authRequestHeaders,omitempty" toml:"authRequestHeaders,omitempty" yaml:"authRequestHeaders,omitempty" export:"true"` + Address string `json:"address,omitempty" toml:"address,omitempty" yaml:"address,omitempty"` + TLS *types.ClientTLS `json:"tls,omitempty" toml:"tls,omitempty" yaml:"tls,omitempty" export:"true"` + TrustForwardHeader bool `json:"trustForwardHeader,omitempty" toml:"trustForwardHeader,omitempty" yaml:"trustForwardHeader,omitempty" export:"true"` + AuthResponseHeaders []string `json:"authResponseHeaders,omitempty" toml:"authResponseHeaders,omitempty" yaml:"authResponseHeaders,omitempty" export:"true"` + AuthResponseHeadersRegex string `json:"authResponseHeadersRegex,omitempty" toml:"authResponseHeadersRegex,omitempty" yaml:"authResponseHeadersRegex,omitempty" export:"true"` + AuthRequestHeaders []string `json:"authRequestHeaders,omitempty" toml:"authRequestHeaders,omitempty" yaml:"authRequestHeaders,omitempty" export:"true"` } // +k8s:deepcopy-gen=true @@ -403,16 +400,16 @@ type TLSClientCertificateInfo struct { NotAfter bool `json:"notAfter,omitempty" toml:"notAfter,omitempty" yaml:"notAfter,omitempty" export:"true"` NotBefore bool `json:"notBefore,omitempty" toml:"notBefore,omitempty" yaml:"notBefore,omitempty" export:"true"` Sans bool `json:"sans,omitempty" toml:"sans,omitempty" yaml:"sans,omitempty" export:"true"` - Subject *TLSCLientCertificateSubjectDNInfo `json:"subject,omitempty" toml:"subject,omitempty" yaml:"subject,omitempty" export:"true"` - Issuer *TLSCLientCertificateIssuerDNInfo `json:"issuer,omitempty" toml:"issuer,omitempty" yaml:"issuer,omitempty" export:"true"` + Subject *TLSClientCertificateSubjectDNInfo `json:"subject,omitempty" toml:"subject,omitempty" yaml:"subject,omitempty" export:"true"` + Issuer *TLSClientCertificateIssuerDNInfo `json:"issuer,omitempty" toml:"issuer,omitempty" yaml:"issuer,omitempty" export:"true"` SerialNumber bool `json:"serialNumber,omitempty" toml:"serialNumber,omitempty" yaml:"serialNumber,omitempty" export:"true"` } // +k8s:deepcopy-gen=true -// TLSCLientCertificateIssuerDNInfo holds the client TLS certificate distinguished name info configuration. +// TLSClientCertificateIssuerDNInfo holds the client TLS certificate distinguished name info configuration. // cf https://tools.ietf.org/html/rfc3739 -type TLSCLientCertificateIssuerDNInfo struct { +type TLSClientCertificateIssuerDNInfo struct { Country bool `json:"country,omitempty" toml:"country,omitempty" yaml:"country,omitempty" export:"true"` Province bool `json:"province,omitempty" toml:"province,omitempty" yaml:"province,omitempty" export:"true"` Locality bool `json:"locality,omitempty" toml:"locality,omitempty" yaml:"locality,omitempty" export:"true"` @@ -424,9 +421,9 @@ type TLSCLientCertificateIssuerDNInfo struct { // +k8s:deepcopy-gen=true -// TLSCLientCertificateSubjectDNInfo holds the client TLS certificate distinguished name info configuration. +// TLSClientCertificateSubjectDNInfo holds the client TLS certificate distinguished name info configuration. // cf https://tools.ietf.org/html/rfc3739 -type TLSCLientCertificateSubjectDNInfo struct { +type TLSClientCertificateSubjectDNInfo struct { Country bool `json:"country,omitempty" toml:"country,omitempty" yaml:"country,omitempty" export:"true"` Province bool `json:"province,omitempty" toml:"province,omitempty" yaml:"province,omitempty" export:"true"` Locality bool `json:"locality,omitempty" toml:"locality,omitempty" yaml:"locality,omitempty" export:"true"` @@ -441,83 +438,3 @@ type TLSCLientCertificateSubjectDNInfo struct { // Users holds a list of users. type Users []string - -// +k8s:deepcopy-gen=true - -// ClientTLS holds the TLS specific configurations as client -// CA, Cert and Key can be either path or file contents. -type ClientTLS struct { - CA string `json:"ca,omitempty" toml:"ca,omitempty" yaml:"ca,omitempty"` - CAOptional bool `json:"caOptional,omitempty" toml:"caOptional,omitempty" yaml:"caOptional,omitempty" export:"true"` - Cert string `json:"cert,omitempty" toml:"cert,omitempty" yaml:"cert,omitempty"` - Key string `json:"key,omitempty" toml:"key,omitempty" yaml:"key,omitempty"` - InsecureSkipVerify bool `json:"insecureSkipVerify,omitempty" toml:"insecureSkipVerify,omitempty" yaml:"insecureSkipVerify,omitempty" export:"true"` -} - -// CreateTLSConfig creates a TLS config from ClientTLS structures. -func (c *ClientTLS) CreateTLSConfig() (*tls.Config, error) { - if c == nil { - return nil, nil - } - - var err error - caPool := x509.NewCertPool() - clientAuth := tls.NoClientCert - if c.CA != "" { - var ca []byte - if _, errCA := os.Stat(c.CA); errCA == nil { - ca, err = os.ReadFile(c.CA) - if err != nil { - return nil, fmt.Errorf("failed to read CA. %w", err) - } - } else { - ca = []byte(c.CA) - } - - if !caPool.AppendCertsFromPEM(ca) { - return nil, fmt.Errorf("failed to parse CA") - } - - if c.CAOptional { - clientAuth = tls.VerifyClientCertIfGiven - } else { - clientAuth = tls.RequireAndVerifyClientCert - } - } - - cert := tls.Certificate{} - _, errKeyIsFile := os.Stat(c.Key) - - if !c.InsecureSkipVerify && (len(c.Cert) == 0 || len(c.Key) == 0) { - return nil, fmt.Errorf("TLS Certificate or Key file must be set when TLS configuration is created") - } - - if len(c.Cert) > 0 && len(c.Key) > 0 { - if _, errCertIsFile := os.Stat(c.Cert); errCertIsFile == nil { - if errKeyIsFile == nil { - cert, err = tls.LoadX509KeyPair(c.Cert, c.Key) - if err != nil { - return nil, fmt.Errorf("failed to load TLS keypair: %w", err) - } - } else { - return nil, fmt.Errorf("tls cert is a file, but tls key is not") - } - } else { - if errKeyIsFile != nil { - cert, err = tls.X509KeyPair([]byte(c.Cert), []byte(c.Key)) - if err != nil { - return nil, fmt.Errorf("failed to load TLS keypair: %w", err) - } - } else { - return nil, fmt.Errorf("TLS key is a file, but tls cert is not") - } - } - } - - return &tls.Config{ - Certificates: []tls.Certificate{cert}, - RootCAs: caPool, - InsecureSkipVerify: c.InsecureSkipVerify, - ClientAuth: clientAuth, - }, nil -} diff --git a/pkg/config/dynamic/zz_generated.deepcopy.go b/pkg/config/dynamic/zz_generated.deepcopy.go index 74dafc03b..da72eb9de 100644 --- a/pkg/config/dynamic/zz_generated.deepcopy.go +++ b/pkg/config/dynamic/zz_generated.deepcopy.go @@ -124,22 +124,6 @@ func (in *CircuitBreaker) DeepCopy() *CircuitBreaker { return out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ClientTLS) DeepCopyInto(out *ClientTLS) { - *out = *in - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClientTLS. -func (in *ClientTLS) DeepCopy() *ClientTLS { - if in == nil { - return nil - } - out := new(ClientTLS) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Compress) DeepCopyInto(out *Compress) { *out = *in @@ -306,7 +290,7 @@ func (in *ForwardAuth) DeepCopyInto(out *ForwardAuth) { *out = *in if in.TLS != nil { in, out := &in.TLS, &out.TLS - *out = new(ClientTLS) + *out = new(types.ClientTLS) **out = **in } if in.AuthResponseHeaders != nil { @@ -1535,49 +1519,17 @@ func (in *TCPWeightedRoundRobin) DeepCopy() *TCPWeightedRoundRobin { return out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *TLSCLientCertificateIssuerDNInfo) DeepCopyInto(out *TLSCLientCertificateIssuerDNInfo) { - *out = *in - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TLSCLientCertificateIssuerDNInfo. -func (in *TLSCLientCertificateIssuerDNInfo) DeepCopy() *TLSCLientCertificateIssuerDNInfo { - if in == nil { - return nil - } - out := new(TLSCLientCertificateIssuerDNInfo) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *TLSCLientCertificateSubjectDNInfo) DeepCopyInto(out *TLSCLientCertificateSubjectDNInfo) { - *out = *in - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TLSCLientCertificateSubjectDNInfo. -func (in *TLSCLientCertificateSubjectDNInfo) DeepCopy() *TLSCLientCertificateSubjectDNInfo { - if in == nil { - return nil - } - out := new(TLSCLientCertificateSubjectDNInfo) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *TLSClientCertificateInfo) DeepCopyInto(out *TLSClientCertificateInfo) { *out = *in if in.Subject != nil { in, out := &in.Subject, &out.Subject - *out = new(TLSCLientCertificateSubjectDNInfo) + *out = new(TLSClientCertificateSubjectDNInfo) **out = **in } if in.Issuer != nil { in, out := &in.Issuer, &out.Issuer - *out = new(TLSCLientCertificateIssuerDNInfo) + *out = new(TLSClientCertificateIssuerDNInfo) **out = **in } return @@ -1593,6 +1545,38 @@ func (in *TLSClientCertificateInfo) DeepCopy() *TLSClientCertificateInfo { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TLSClientCertificateIssuerDNInfo) DeepCopyInto(out *TLSClientCertificateIssuerDNInfo) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TLSClientCertificateIssuerDNInfo. +func (in *TLSClientCertificateIssuerDNInfo) DeepCopy() *TLSClientCertificateIssuerDNInfo { + if in == nil { + return nil + } + out := new(TLSClientCertificateIssuerDNInfo) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TLSClientCertificateSubjectDNInfo) DeepCopyInto(out *TLSClientCertificateSubjectDNInfo) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TLSClientCertificateSubjectDNInfo. +func (in *TLSClientCertificateSubjectDNInfo) DeepCopy() *TLSClientCertificateSubjectDNInfo { + if in == nil { + return nil + } + out := new(TLSClientCertificateSubjectDNInfo) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *TLSConfiguration) DeepCopyInto(out *TLSConfiguration) { *out = *in diff --git a/pkg/config/label/label_test.go b/pkg/config/label/label_test.go index c3827bda2..19e6dbd74 100644 --- a/pkg/config/label/label_test.go +++ b/pkg/config/label/label_test.go @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/require" ptypes "github.com/traefik/paerser/types" "github.com/traefik/traefik/v2/pkg/config/dynamic" + "github.com/traefik/traefik/v2/pkg/types" ) func TestDecodeConfiguration(t *testing.T) { @@ -367,7 +368,7 @@ func TestDecodeConfiguration(t *testing.T) { NotAfter: true, NotBefore: true, SerialNumber: true, - Subject: &dynamic.TLSCLientCertificateSubjectDNInfo{ + Subject: &dynamic.TLSClientCertificateSubjectDNInfo{ Country: true, Province: true, Locality: true, @@ -377,7 +378,7 @@ func TestDecodeConfiguration(t *testing.T) { SerialNumber: true, DomainComponent: true, }, - Issuer: &dynamic.TLSCLientCertificateIssuerDNInfo{ + Issuer: &dynamic.TLSClientCertificateIssuerDNInfo{ Country: true, Province: true, Locality: true, @@ -505,7 +506,7 @@ func TestDecodeConfiguration(t *testing.T) { "Middleware7": { ForwardAuth: &dynamic.ForwardAuth{ Address: "foobar", - TLS: &dynamic.ClientTLS{ + TLS: &types.ClientTLS{ CA: "foobar", CAOptional: true, Cert: "foobar", @@ -848,7 +849,7 @@ func TestEncodeConfiguration(t *testing.T) { NotAfter: true, NotBefore: true, SerialNumber: true, - Subject: &dynamic.TLSCLientCertificateSubjectDNInfo{ + Subject: &dynamic.TLSClientCertificateSubjectDNInfo{ Country: true, Province: true, Locality: true, @@ -858,7 +859,7 @@ func TestEncodeConfiguration(t *testing.T) { SerialNumber: true, DomainComponent: true, }, - Issuer: &dynamic.TLSCLientCertificateIssuerDNInfo{ + Issuer: &dynamic.TLSClientCertificateIssuerDNInfo{ Country: true, Province: true, Locality: true, @@ -993,7 +994,7 @@ func TestEncodeConfiguration(t *testing.T) { "Middleware7": { ForwardAuth: &dynamic.ForwardAuth{ Address: "foobar", - TLS: &dynamic.ClientTLS{ + TLS: &types.ClientTLS{ CA: "foobar", CAOptional: true, Cert: "foobar", diff --git a/pkg/middlewares/accesslog/logger_formatters_test.go b/pkg/middlewares/accesslog/logger_formatters_test.go index 1a7f69b5a..74a86b2a4 100644 --- a/pkg/middlewares/accesslog/logger_formatters_test.go +++ b/pkg/middlewares/accesslog/logger_formatters_test.go @@ -2,7 +2,6 @@ package accesslog import ( "net/http" - "os" "testing" "time" @@ -84,7 +83,7 @@ func TestCommonLogFormatter_Format(t *testing.T) { } // Set timezone to Etc/GMT+9 to have a constant behavior - os.Setenv("TZ", "Etc/GMT+9") + t.Setenv("TZ", "Etc/GMT+9") for _, test := range testCases { test := test diff --git a/pkg/middlewares/auth/forward.go b/pkg/middlewares/auth/forward.go index df1895f09..a0cee86ce 100644 --- a/pkg/middlewares/auth/forward.go +++ b/pkg/middlewares/auth/forward.go @@ -72,9 +72,9 @@ func NewForward(ctx context.Context, next http.Handler, config dynamic.ForwardAu } if config.TLS != nil { - tlsConfig, err := config.TLS.CreateTLSConfig() + tlsConfig, err := config.TLS.CreateTLSConfig(ctx) if err != nil { - return nil, err + return nil, fmt.Errorf("unable to create client TLS configuration: %w", err) } tr := http.DefaultTransport.(*http.Transport).Clone() diff --git a/pkg/middlewares/passtlsclientcert/pass_tls_client_cert.go b/pkg/middlewares/passtlsclientcert/pass_tls_client_cert.go index 3089f02d5..099446745 100644 --- a/pkg/middlewares/passtlsclientcert/pass_tls_client_cert.go +++ b/pkg/middlewares/passtlsclientcert/pass_tls_client_cert.go @@ -48,7 +48,7 @@ type IssuerDistinguishedNameOptions struct { StateOrProvinceName bool } -func newIssuerDistinguishedNameOptions(info *dynamic.TLSCLientCertificateIssuerDNInfo) *IssuerDistinguishedNameOptions { +func newIssuerDistinguishedNameOptions(info *dynamic.TLSClientCertificateIssuerDNInfo) *IssuerDistinguishedNameOptions { if info == nil { return nil } @@ -78,7 +78,7 @@ type SubjectDistinguishedNameOptions struct { StateOrProvinceName bool } -func newSubjectDistinguishedNameOptions(info *dynamic.TLSCLientCertificateSubjectDNInfo) *SubjectDistinguishedNameOptions { +func newSubjectDistinguishedNameOptions(info *dynamic.TLSClientCertificateSubjectDNInfo) *SubjectDistinguishedNameOptions { if info == nil { return nil } diff --git a/pkg/middlewares/passtlsclientcert/pass_tls_client_cert_test.go b/pkg/middlewares/passtlsclientcert/pass_tls_client_cert_test.go index b4a56d208..6aca1c189 100644 --- a/pkg/middlewares/passtlsclientcert/pass_tls_client_cert_test.go +++ b/pkg/middlewares/passtlsclientcert/pass_tls_client_cert_test.go @@ -376,7 +376,7 @@ func TestPassTLSClientCert_certInfo(t *testing.T) { desc: "No TLS, with subject info", config: dynamic.PassTLSClientCert{ Info: &dynamic.TLSClientCertificateInfo{ - Subject: &dynamic.TLSCLientCertificateSubjectDNInfo{ + Subject: &dynamic.TLSClientCertificateSubjectDNInfo{ CommonName: true, Organization: true, OrganizationalUnit: true, @@ -393,7 +393,7 @@ func TestPassTLSClientCert_certInfo(t *testing.T) { config: dynamic.PassTLSClientCert{ PEM: false, Info: &dynamic.TLSClientCertificateInfo{ - Subject: &dynamic.TLSCLientCertificateSubjectDNInfo{}, + Subject: &dynamic.TLSClientCertificateSubjectDNInfo{}, }, }, }, @@ -406,7 +406,7 @@ func TestPassTLSClientCert_certInfo(t *testing.T) { NotBefore: true, Sans: true, SerialNumber: true, - Subject: &dynamic.TLSCLientCertificateSubjectDNInfo{ + Subject: &dynamic.TLSClientCertificateSubjectDNInfo{ CommonName: true, Country: true, DomainComponent: true, @@ -416,7 +416,7 @@ func TestPassTLSClientCert_certInfo(t *testing.T) { Province: true, SerialNumber: true, }, - Issuer: &dynamic.TLSCLientCertificateIssuerDNInfo{ + Issuer: &dynamic.TLSClientCertificateIssuerDNInfo{ CommonName: true, Country: true, DomainComponent: true, @@ -436,10 +436,10 @@ func TestPassTLSClientCert_certInfo(t *testing.T) { Info: &dynamic.TLSClientCertificateInfo{ NotAfter: true, Sans: true, - Subject: &dynamic.TLSCLientCertificateSubjectDNInfo{ + Subject: &dynamic.TLSClientCertificateSubjectDNInfo{ Organization: true, }, - Issuer: &dynamic.TLSCLientCertificateIssuerDNInfo{ + Issuer: &dynamic.TLSClientCertificateIssuerDNInfo{ Country: true, }, }, @@ -453,13 +453,13 @@ func TestPassTLSClientCert_certInfo(t *testing.T) { Info: &dynamic.TLSClientCertificateInfo{ NotAfter: true, Sans: true, - Subject: &dynamic.TLSCLientCertificateSubjectDNInfo{ + Subject: &dynamic.TLSClientCertificateSubjectDNInfo{ Organization: true, // OrganizationalUnit is not set on this example certificate, // so even though it's requested, it will be absent. OrganizationalUnit: true, }, - Issuer: &dynamic.TLSCLientCertificateIssuerDNInfo{ + Issuer: &dynamic.TLSClientCertificateIssuerDNInfo{ Country: true, }, }, @@ -475,7 +475,7 @@ func TestPassTLSClientCert_certInfo(t *testing.T) { NotBefore: true, Sans: true, SerialNumber: true, - Subject: &dynamic.TLSCLientCertificateSubjectDNInfo{ + Subject: &dynamic.TLSClientCertificateSubjectDNInfo{ Country: true, Province: true, Locality: true, @@ -485,7 +485,7 @@ func TestPassTLSClientCert_certInfo(t *testing.T) { SerialNumber: true, DomainComponent: true, }, - Issuer: &dynamic.TLSCLientCertificateIssuerDNInfo{ + Issuer: &dynamic.TLSClientCertificateIssuerDNInfo{ Country: true, Province: true, Locality: true, @@ -507,7 +507,7 @@ func TestPassTLSClientCert_certInfo(t *testing.T) { NotBefore: true, Sans: true, SerialNumber: true, - Subject: &dynamic.TLSCLientCertificateSubjectDNInfo{ + Subject: &dynamic.TLSClientCertificateSubjectDNInfo{ Country: true, Province: true, Locality: true, @@ -517,7 +517,7 @@ func TestPassTLSClientCert_certInfo(t *testing.T) { SerialNumber: true, DomainComponent: true, }, - Issuer: &dynamic.TLSCLientCertificateIssuerDNInfo{ + Issuer: &dynamic.TLSClientCertificateIssuerDNInfo{ Country: true, Province: true, Locality: true, diff --git a/pkg/middlewares/replacepath/replace_path.go b/pkg/middlewares/replacepath/replace_path.go index a7e82826a..d730a931c 100644 --- a/pkg/middlewares/replacepath/replace_path.go +++ b/pkg/middlewares/replacepath/replace_path.go @@ -41,12 +41,12 @@ func (r *replacePath) GetTracingInformation() (string, ext.SpanKindEnum) { } func (r *replacePath) ServeHTTP(rw http.ResponseWriter, req *http.Request) { - if req.URL.RawPath == "" { - req.Header.Add(ReplacedPathHeader, req.URL.Path) - } else { - req.Header.Add(ReplacedPathHeader, req.URL.RawPath) + currentPath := req.URL.RawPath + if currentPath == "" { + currentPath = req.URL.EscapedPath() } + req.Header.Add(ReplacedPathHeader, currentPath) req.URL.RawPath = r.path var err error diff --git a/pkg/middlewares/replacepath/replace_path_test.go b/pkg/middlewares/replacepath/replace_path_test.go index 657b1c3b8..782733bc0 100644 --- a/pkg/middlewares/replacepath/replace_path_test.go +++ b/pkg/middlewares/replacepath/replace_path_test.go @@ -60,6 +60,16 @@ func TestReplacePath(t *testing.T) { expectedRawPath: "/foo%2Fbar", expectedHeader: "/path", }, + { + desc: "replacement with percent encoded backspace char", + path: "/path/%08bar", + config: dynamic.ReplacePath{ + Path: "/path/%08bar", + }, + expectedPath: "/path/\bbar", + expectedRawPath: "/path/%08bar", + expectedHeader: "/path/%08bar", + }, } for _, test := range testCases { diff --git a/pkg/middlewares/replacepathregex/replace_path_regex.go b/pkg/middlewares/replacepathregex/replace_path_regex.go index 4a10fc1c9..fea6fa2de 100644 --- a/pkg/middlewares/replacepathregex/replace_path_regex.go +++ b/pkg/middlewares/replacepathregex/replace_path_regex.go @@ -16,9 +16,7 @@ import ( "github.com/traefik/traefik/v2/pkg/tracing" ) -const ( - typeName = "ReplacePathRegex" -) +const typeName = "ReplacePathRegex" // ReplacePathRegex is a middleware used to replace the path of a URL request with a regular expression. type replacePathRegex struct { @@ -50,16 +48,13 @@ func (rp *replacePathRegex) GetTracingInformation() (string, ext.SpanKindEnum) { } func (rp *replacePathRegex) ServeHTTP(rw http.ResponseWriter, req *http.Request) { - var currentPath string - if req.URL.RawPath == "" { - currentPath = req.URL.Path - } else { - currentPath = req.URL.RawPath + currentPath := req.URL.RawPath + if currentPath == "" { + currentPath = req.URL.EscapedPath() } if rp.regexp != nil && len(rp.replacement) > 0 && rp.regexp.MatchString(currentPath) { req.Header.Add(replacepath.ReplacedPathHeader, currentPath) - req.URL.RawPath = rp.regexp.ReplaceAllString(currentPath, rp.replacement) // as replacement can introduce escaped characters diff --git a/pkg/middlewares/replacepathregex/replace_path_regex_test.go b/pkg/middlewares/replacepathregex/replace_path_regex_test.go index c22083ea5..59018d784 100644 --- a/pkg/middlewares/replacepathregex/replace_path_regex_test.go +++ b/pkg/middlewares/replacepathregex/replace_path_regex_test.go @@ -106,6 +106,16 @@ func TestReplacePathRegex(t *testing.T) { expectedPath: "/aaa/bbb", expectedRawPath: "/aaa%2Fbbb", }, + { + desc: "path with percent encoded backspace char", + path: "/foo/%08bar", + config: dynamic.ReplacePathRegex{ + Replacement: "/$1", + Regex: `^/foo/(.*)`, + }, + expectedPath: "/\bbar", + expectedRawPath: "/%08bar", + }, } for _, test := range testCases { diff --git a/pkg/provider/docker/docker.go b/pkg/provider/docker/docker.go index 10ba1709c..6f87cfcd8 100644 --- a/pkg/provider/docker/docker.go +++ b/pkg/provider/docker/docker.go @@ -165,7 +165,7 @@ func (p *Provider) getClientOpts() ([]client.Opt, error) { conf, err := p.TLS.CreateTLSConfig(ctx) if err != nil { - return nil, err + return nil, fmt.Errorf("unable to create client TLS configuration: %w", err) } hostURL, err := client.ParseHostURL(p.Endpoint) diff --git a/pkg/provider/http/http.go b/pkg/provider/http/http.go index 8d1ce329e..c7caf7fdb 100644 --- a/pkg/provider/http/http.go +++ b/pkg/provider/http/http.go @@ -55,7 +55,7 @@ func (p *Provider) Init() error { if p.TLS != nil { tlsConfig, err := p.TLS.CreateTLSConfig(context.Background()) if err != nil { - return fmt.Errorf("unable to create TLS configuration: %w", err) + return fmt.Errorf("unable to create client TLS configuration: %w", err) } p.httpClient.Transport = &http.Transport{ diff --git a/pkg/provider/kubernetes/crd/fixtures/tcp/with_middleware_multiple_hyphens.yml b/pkg/provider/kubernetes/crd/fixtures/tcp/with_middleware_multiple_hyphens.yml new file mode 100644 index 000000000..dc66091c2 --- /dev/null +++ b/pkg/provider/kubernetes/crd/fixtures/tcp/with_middleware_multiple_hyphens.yml @@ -0,0 +1,29 @@ +apiVersion: traefik.containo.us/v1alpha1 +kind: MiddlewareTCP +metadata: + name: multiple---hyphens + namespace: default +spec: + ipWhiteList: + sourceRange: + - 127.0.0.1/32 + +--- +apiVersion: traefik.containo.us/v1alpha1 +kind: IngressRouteTCP +metadata: + name: test.route + namespace: default + +spec: + entryPoints: + - foo + + routes: + - match: HostSNI(`foo.com`) + services: + - name: whoamitcp + port: 8000 + + middlewares: + - name: multiple---hyphens diff --git a/pkg/provider/kubernetes/crd/fixtures/with_middleware_multiple_hyphens.yml b/pkg/provider/kubernetes/crd/fixtures/with_middleware_multiple_hyphens.yml new file mode 100644 index 000000000..cee835136 --- /dev/null +++ b/pkg/provider/kubernetes/crd/fixtures/with_middleware_multiple_hyphens.yml @@ -0,0 +1,31 @@ +apiVersion: traefik.containo.us/v1alpha1 +kind: Middleware +metadata: + name: multiple---hyphens + namespace: default + +spec: + stripPrefix: + prefixes: + - /tobestripped + +--- +apiVersion: traefik.containo.us/v1alpha1 +kind: IngressRoute +metadata: + name: test2.route + namespace: default + +spec: + entryPoints: + - web + + routes: + - match: Host(`foo.com`) && PathPrefix(`/tobestripped`) + priority: 12 + kind: Rule + services: + - name: whoami + port: 80 + middlewares: + - name: multiple---hyphens diff --git a/pkg/provider/kubernetes/crd/kubernetes.go b/pkg/provider/kubernetes/crd/kubernetes.go index d987492c1..7c2b2d051 100644 --- a/pkg/provider/kubernetes/crd/kubernetes.go +++ b/pkg/provider/kubernetes/crd/kubernetes.go @@ -25,6 +25,7 @@ import ( "github.com/traefik/traefik/v2/pkg/provider/kubernetes/crd/traefik/v1alpha1" "github.com/traefik/traefik/v2/pkg/safe" "github.com/traefik/traefik/v2/pkg/tls" + "github.com/traefik/traefik/v2/pkg/types" corev1 "k8s.io/api/core/v1" apiextensionv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" "k8s.io/apimachinery/pkg/labels" @@ -483,7 +484,7 @@ func createForwardAuthMiddleware(k8sClient Client, namespace string, auth *v1alp return forwardAuth, nil } - forwardAuth.TLS = &dynamic.ClientTLS{ + forwardAuth.TLS = &types.ClientTLS{ CAOptional: auth.TLS.CAOptional, InsecureSkipVerify: auth.TLS.InsecureSkipVerify, } diff --git a/pkg/provider/kubernetes/crd/kubernetes_http.go b/pkg/provider/kubernetes/crd/kubernetes_http.go index b78d14c31..b95a6679a 100644 --- a/pkg/provider/kubernetes/crd/kubernetes_http.go +++ b/pkg/provider/kubernetes/crd/kubernetes_http.go @@ -183,7 +183,7 @@ func (p *Provider) makeMiddlewareKeys(ctx context.Context, ingRouteNamespace str ns = mi.Namespace } - mds = append(mds, makeID(ns, name)) + mds = append(mds, provider.Normalize(makeID(ns, name))) } return mds, nil diff --git a/pkg/provider/kubernetes/crd/kubernetes_tcp.go b/pkg/provider/kubernetes/crd/kubernetes_tcp.go index 187020a2d..37c584e17 100644 --- a/pkg/provider/kubernetes/crd/kubernetes_tcp.go +++ b/pkg/provider/kubernetes/crd/kubernetes_tcp.go @@ -10,6 +10,7 @@ import ( "github.com/traefik/traefik/v2/pkg/config/dynamic" "github.com/traefik/traefik/v2/pkg/log" + "github.com/traefik/traefik/v2/pkg/provider" "github.com/traefik/traefik/v2/pkg/provider/kubernetes/crd/traefik/v1alpha1" "github.com/traefik/traefik/v2/pkg/tls" corev1 "k8s.io/api/core/v1" @@ -162,7 +163,7 @@ func (p *Provider) makeMiddlewareTCPKeys(ctx context.Context, ingRouteTCPNamespa ns = mi.Namespace } - mds = append(mds, makeID(ns, mi.Name)) + mds = append(mds, provider.Normalize(makeID(ns, mi.Name))) } return mds, nil diff --git a/pkg/provider/kubernetes/crd/kubernetes_test.go b/pkg/provider/kubernetes/crd/kubernetes_test.go index be8cee687..5ab7b5023 100644 --- a/pkg/provider/kubernetes/crd/kubernetes_test.go +++ b/pkg/provider/kubernetes/crd/kubernetes_test.go @@ -11,13 +11,14 @@ import ( auth "github.com/abbot/go-http-auth" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/traefik/paerser/types" + ptypes "github.com/traefik/paerser/types" "github.com/traefik/traefik/v2/pkg/config/dynamic" "github.com/traefik/traefik/v2/pkg/provider" crdfake "github.com/traefik/traefik/v2/pkg/provider/kubernetes/crd/generated/clientset/versioned/fake" "github.com/traefik/traefik/v2/pkg/provider/kubernetes/crd/traefik/v1alpha1" "github.com/traefik/traefik/v2/pkg/provider/kubernetes/k8s" "github.com/traefik/traefik/v2/pkg/tls" + "github.com/traefik/traefik/v2/pkg/types" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/util/intstr" @@ -151,6 +152,54 @@ func TestLoadIngressRouteTCPs(t *testing.T) { TLS: &dynamic.TLSConfiguration{}, }, }, + { + desc: "Middlewares in ingress route config are normalized", + paths: []string{"tcp/services.yml", "tcp/with_middleware_multiple_hyphens.yml"}, + expected: &dynamic.Configuration{ + UDP: &dynamic.UDPConfiguration{ + Routers: map[string]*dynamic.UDPRouter{}, + Services: map[string]*dynamic.UDPService{}, + }, + 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{ + "default-test.route-fdd3e9338e47a45efefc": { + EntryPoints: []string{"foo"}, + Service: "default-test.route-fdd3e9338e47a45efefc", + Middlewares: []string{"default-multiple-hyphens"}, + Rule: "HostSNI(`foo.com`)", + }, + }, + Middlewares: map[string]*dynamic.TCPMiddleware{ + "default-multiple-hyphens": { + IPWhiteList: &dynamic.TCPIPWhiteList{ + SourceRange: []string{"127.0.0.1/32"}, + }, + }, + }, + Services: map[string]*dynamic.TCPService{ + "default-test.route-fdd3e9338e47a45efefc": { + LoadBalancer: &dynamic.TCPServersLoadBalancer{ + Servers: []dynamic.TCPServer{ + { + Address: "10.10.0.1:8000", + }, + { + Address: "10.10.0.2:8000", + }, + }, + }, + }, + }, + }, + TLS: &dynamic.TLSConfiguration{}, + }, + }, { desc: "Simple Ingress Route, with foo entrypoint and crossprovider middleware", paths: []string{"tcp/services.yml", "tcp/with_middleware_crossprovider.yml"}, @@ -1458,6 +1507,57 @@ func TestLoadIngressRoutes(t *testing.T) { TLS: &dynamic.TLSConfiguration{}, }, }, + { + desc: "Middlewares in ingress route config are normalized", + AllowCrossNamespace: true, + paths: []string{"services.yml", "with_middleware_multiple_hyphens.yml"}, + 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{}, + }, + HTTP: &dynamic.HTTPConfiguration{ + Routers: map[string]*dynamic.Router{ + "default-test2-route-23c7f4c450289ee29016": { + EntryPoints: []string{"web"}, + Service: "default-test2-route-23c7f4c450289ee29016", + Rule: "Host(`foo.com`) && PathPrefix(`/tobestripped`)", + Priority: 12, + Middlewares: []string{"default-multiple-hyphens"}, + }, + }, + Middlewares: map[string]*dynamic.Middleware{ + "default-multiple-hyphens": { + StripPrefix: &dynamic.StripPrefix{ + Prefixes: []string{"/tobestripped"}, + }, + }, + }, + Services: map[string]*dynamic.Service{ + "default-test2-route-23c7f4c450289ee29016": { + LoadBalancer: &dynamic.ServersLoadBalancer{ + Servers: []dynamic.Server{ + { + URL: "http://10.10.0.1:80", + }, + { + URL: "http://10.10.0.2:80", + }, + }, + PassHostHeader: Bool(true), + }, + }, + }, + ServersTransports: map[string]*dynamic.ServersTransport{}, + }, + TLS: &dynamic.TLSConfiguration{}, + }, + }, { desc: "Simple Ingress Route with middleware crossprovider", AllowCrossNamespace: true, @@ -3146,7 +3246,7 @@ func TestLoadIngressRoutes(t *testing.T) { "default-forwardauth": { ForwardAuth: &dynamic.ForwardAuth{ Address: "test.com", - TLS: &dynamic.ClientTLS{ + TLS: &types.ClientTLS{ CA: "-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----", Cert: "-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----", Key: "-----BEGIN PRIVATE KEY-----\n-----END PRIVATE KEY-----", @@ -3515,17 +3615,17 @@ func TestLoadIngressRoutes(t *testing.T) { MaxIdleConnsPerHost: 42, DisableHTTP2: true, ForwardingTimeouts: &dynamic.ForwardingTimeouts{ - DialTimeout: types.Duration(42 * time.Second), - ResponseHeaderTimeout: types.Duration(42 * time.Second), - IdleConnTimeout: types.Duration(42 * time.Millisecond), + DialTimeout: ptypes.Duration(42 * time.Second), + ResponseHeaderTimeout: ptypes.Duration(42 * time.Second), + IdleConnTimeout: ptypes.Duration(42 * time.Millisecond), }, PeerCertURI: "foo://bar", }, "default-test": { ServerName: "test", ForwardingTimeouts: &dynamic.ForwardingTimeouts{ - DialTimeout: types.Duration(30 * time.Second), - IdleConnTimeout: types.Duration(90 * time.Second), + DialTimeout: ptypes.Duration(30 * time.Second), + IdleConnTimeout: ptypes.Duration(90 * time.Second), }, }, }, diff --git a/pkg/provider/kubernetes/k8s/parser.go b/pkg/provider/kubernetes/k8s/parser.go index 306f862fe..6a3e94f7b 100644 --- a/pkg/provider/kubernetes/k8s/parser.go +++ b/pkg/provider/kubernetes/k8s/parser.go @@ -14,7 +14,7 @@ import ( func MustParseYaml(content []byte) []runtime.Object { acceptedK8sTypes := regexp.MustCompile(`^(Namespace|Deployment|Endpoints|Service|Ingress|IngressRoute|IngressRouteTCP|IngressRouteUDP|Middleware|MiddlewareTCP|Secret|TLSOption|TLSStore|TraefikService|IngressClass|ServersTransport|GatewayClass|Gateway|HTTPRoute|TCPRoute|TLSRoute)$`) - files := strings.Split(string(content), "---") + files := strings.Split(string(content), "---\n") retVal := make([]runtime.Object, 0, len(files)) for _, file := range files { if file == "\n" || file == "" { diff --git a/pkg/provider/kv/kv.go b/pkg/provider/kv/kv.go index d62b142c5..4eee4ab63 100644 --- a/pkg/provider/kv/kv.go +++ b/pkg/provider/kv/kv.go @@ -170,7 +170,7 @@ func (p *Provider) createKVClient(ctx context.Context) (store.Store, error) { var err error storeConfig.TLS, err = p.TLS.CreateTLSConfig(ctx) if err != nil { - return nil, err + return nil, fmt.Errorf("unable to create client TLS configuration: %w", err) } } diff --git a/pkg/provider/kv/kv_test.go b/pkg/provider/kv/kv_test.go index d13874609..76f1c8e95 100644 --- a/pkg/provider/kv/kv_test.go +++ b/pkg/provider/kv/kv_test.go @@ -402,7 +402,7 @@ func Test_buildConfiguration(t *testing.T) { "Middleware08": { ForwardAuth: &dynamic.ForwardAuth{ Address: "foobar", - TLS: &dynamic.ClientTLS{ + TLS: &types.ClientTLS{ CA: "foobar", CAOptional: true, Cert: "foobar", @@ -481,7 +481,7 @@ func Test_buildConfiguration(t *testing.T) { NotAfter: true, NotBefore: true, Sans: true, - Subject: &dynamic.TLSCLientCertificateSubjectDNInfo{ + Subject: &dynamic.TLSClientCertificateSubjectDNInfo{ Country: true, Province: true, Locality: true, @@ -491,7 +491,7 @@ func Test_buildConfiguration(t *testing.T) { SerialNumber: true, DomainComponent: true, }, - Issuer: &dynamic.TLSCLientCertificateIssuerDNInfo{ + Issuer: &dynamic.TLSClientCertificateIssuerDNInfo{ Country: true, Province: true, Locality: true, diff --git a/pkg/provider/marathon/marathon.go b/pkg/provider/marathon/marathon.go index d0405b6db..c78413ad7 100644 --- a/pkg/provider/marathon/marathon.go +++ b/pkg/provider/marathon/marathon.go @@ -134,7 +134,7 @@ func (p *Provider) Provide(configurationChan chan<- dynamic.Message, pool *safe. } TLSConfig, err := p.TLS.CreateTLSConfig(ctx) if err != nil { - return err + return fmt.Errorf("unable to create client TLS configuration: %w", err) } confg.HTTPClient = &http.Client{ Transport: &http.Transport{ diff --git a/pkg/tcp/wrr_load_balancer.go b/pkg/tcp/wrr_load_balancer.go index 8f26251ef..236fd8007 100644 --- a/pkg/tcp/wrr_load_balancer.go +++ b/pkg/tcp/wrr_load_balancer.go @@ -15,7 +15,7 @@ type server struct { // WRRLoadBalancer is a naive RoundRobin load balancer for TCP services. type WRRLoadBalancer struct { servers []server - lock sync.RWMutex + lock sync.Mutex currentWeight int index int } @@ -29,16 +29,16 @@ func NewWRRLoadBalancer() *WRRLoadBalancer { // ServeTCP forwards the connection to the right service. func (b *WRRLoadBalancer) ServeTCP(conn WriteCloser) { - if len(b.servers) == 0 { - log.WithoutContext().Error("no available server") - return - } - + b.lock.Lock() next, err := b.next() + b.lock.Unlock() + if err != nil { log.WithoutContext().Errorf("Error during load balancing: %v", err) conn.Close() + return } + next.ServeTCP(conn) } @@ -50,6 +50,9 @@ func (b *WRRLoadBalancer) AddServer(serverHandler Handler) { // AddWeightServer appends a server to the existing list with a weight. func (b *WRRLoadBalancer) AddWeightServer(serverHandler Handler, weight *int) { + b.lock.Lock() + defer b.lock.Unlock() + w := 1 if weight != nil { w = *weight @@ -87,9 +90,6 @@ func gcd(a, b int) int { } func (b *WRRLoadBalancer) next() (Handler, error) { - b.lock.Lock() - defer b.lock.Unlock() - if len(b.servers) == 0 { return nil, fmt.Errorf("no servers in the pool") } @@ -98,10 +98,14 @@ func (b *WRRLoadBalancer) next() (Handler, error) { // it calculates the GCD and subtracts it on every iteration, what interleaves servers // and allows us not to build an iterator every time we readjust weights - // GCD across all enabled servers - gcd := b.weightGcd() // Maximum weight across all enabled servers max := b.maxWeight() + if max == 0 { + return nil, fmt.Errorf("all servers have 0 weight") + } + + // GCD across all enabled servers + gcd := b.weightGcd() for { b.index = (b.index + 1) % len(b.servers) @@ -109,9 +113,6 @@ func (b *WRRLoadBalancer) next() (Handler, error) { b.currentWeight -= gcd if b.currentWeight <= 0 { b.currentWeight = max - if b.currentWeight == 0 { - return nil, fmt.Errorf("all servers have 0 weight") - } } } srv := b.servers[b.index] diff --git a/pkg/tcp/wrr_load_balancer_test.go b/pkg/tcp/wrr_load_balancer_test.go index 933f48976..a07f51762 100644 --- a/pkg/tcp/wrr_load_balancer_test.go +++ b/pkg/tcp/wrr_load_balancer_test.go @@ -10,7 +10,8 @@ import ( ) type fakeConn struct { - call map[string]int + writeCall map[string]int + closeCall int } func (f *fakeConn) Read(b []byte) (n int, err error) { @@ -18,12 +19,13 @@ func (f *fakeConn) Read(b []byte) (n int, err error) { } func (f *fakeConn) Write(b []byte) (n int, err error) { - f.call[string(b)]++ + f.writeCall[string(b)]++ return len(b), nil } func (f *fakeConn) Close() error { - panic("implement me") + f.closeCall++ + return nil } func (f *fakeConn) LocalAddr() net.Addr { @@ -55,7 +57,8 @@ func TestLoadBalancing(t *testing.T) { desc string serversWeight map[string]int totalCall int - expected map[string]int + expectedWrite map[string]int + expectedClose int }{ { desc: "RoundRobin", @@ -64,7 +67,7 @@ func TestLoadBalancing(t *testing.T) { "h2": 1, }, totalCall: 4, - expected: map[string]int{ + expectedWrite: map[string]int{ "h1": 2, "h2": 2, }, @@ -76,7 +79,7 @@ func TestLoadBalancing(t *testing.T) { "h2": 1, }, totalCall: 4, - expected: map[string]int{ + expectedWrite: map[string]int{ "h1": 3, "h2": 1, }, @@ -88,22 +91,33 @@ func TestLoadBalancing(t *testing.T) { "h2": 1, }, totalCall: 16, - expected: map[string]int{ + expectedWrite: map[string]int{ "h1": 12, "h2": 4, }, }, { - desc: "WeighedRoundRobin with 0 weight server", + desc: "WeighedRoundRobin with one 0 weight server", serversWeight: map[string]int{ "h1": 3, "h2": 0, }, totalCall: 16, - expected: map[string]int{ + expectedWrite: map[string]int{ "h1": 16, }, }, + { + desc: "WeighedRoundRobin with all servers with 0 weight", + serversWeight: map[string]int{ + "h1": 0, + "h2": 0, + "h3": 0, + }, + totalCall: 10, + expectedWrite: map[string]int{}, + expectedClose: 10, + }, } for _, test := range testCases { @@ -120,12 +134,13 @@ func TestLoadBalancing(t *testing.T) { }), &weight) } - conn := &fakeConn{call: make(map[string]int)} + conn := &fakeConn{writeCall: make(map[string]int)} for i := 0; i < test.totalCall; i++ { balancer.ServeTCP(conn) } - assert.Equal(t, test.expected, conn.call) + assert.Equal(t, test.expectedWrite, conn.writeCall) + assert.Equal(t, test.expectedClose, conn.closeCall) }) } } diff --git a/pkg/types/fixtures/cert.pem b/pkg/types/fixtures/cert.pem new file mode 100644 index 000000000..b0261ccd6 --- /dev/null +++ b/pkg/types/fixtures/cert.pem @@ -0,0 +1,13 @@ +-----BEGIN CERTIFICATE----- +MIIB9jCCAV+gAwIBAgIQI3edJckNbicw4WIHs5Ws9TANBgkqhkiG9w0BAQsFADAS +MRAwDgYDVQQKEwdBY21lIENvMCAXDTcwMDEwMTAwMDAwMFoYDzIwODQwMTI5MTYw +MDAwWjASMRAwDgYDVQQKEwdBY21lIENvMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCB +iQKBgQCb8oWyME1QRBoMLFei3M8TVKwfZfW74cVjtcugCBMTTOTCouEIgjjmiMv6 +FdMio2uBcgeD9R3dOtjjnA7N+xjwZ4vIPqDlJRE3YbfpV9igVX3sXU7ssHTSH0vs +R0TuYJwGReIFUnu5QIjGwVorodF+CQ8dTnyXVLeQVU9kvjohHwIDAQABo0swSTAO +BgNVHQ8BAf8EBAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIw +ADAUBgNVHREEDTALgglsb2NhbGhvc3QwDQYJKoZIhvcNAQELBQADgYEADqylUQ/4 +lrxh4h8UUQ2wKATQ2kG2YvMGlaIhr2vPZo2QDBlmL2xzai7YXX3+JZyM15TNCamn +WtFR7WQIOHzKA1GkR9WkaXKmFbJjhGMSZVCG6ghhTjzB+stBYZXhBsdjCJbkZWBu +OeI73oivo0MdI+4iCYCo7TnoY4PZGObwcgI= +-----END CERTIFICATE----- diff --git a/pkg/types/fixtures/key.pem b/pkg/types/fixtures/key.pem new file mode 100644 index 000000000..fd1d7e728 --- /dev/null +++ b/pkg/types/fixtures/key.pem @@ -0,0 +1,16 @@ +-----BEGIN PRIVATE KEY----- +MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAJvyhbIwTVBEGgws +V6LczxNUrB9l9bvhxWO1y6AIExNM5MKi4QiCOOaIy/oV0yKja4FyB4P1Hd062OOc +Ds37GPBni8g+oOUlETdht+lX2KBVfexdTuywdNIfS+xHRO5gnAZF4gVSe7lAiMbB +Wiuh0X4JDx1OfJdUt5BVT2S+OiEfAgMBAAECgYA9+PbghQl0aFvhko2RDybLi86K ++73X2DTVFx3AjvTlqp0OLCQ5eWabVqmYzKuHDGJgoqwR6Irhq80dRpsriCm0YNui +mMV35bbimOKz9FoCTKx0ZB6xsqrVoFhjVmX3DOD9Txe41H42ZxmccOKZndR/QaXz +VV+1W/Wbz2VawnkyYQJBAMvF6w2eOJRRoN8e7GM7b7uqkupJPp9axgFREoJZb16W +mqXUZnH4Cydzc5keG4yknQRHdgz6RrQxnvR7GyKHLfUCQQDD6qG9D5BX0+mNW6TG +PRwW/L2qWgnmg9lxtSSQat9ZOnBhw2OLPi0zTu4p70oSmU67/YJr50HEoJpRccZJ +mnJDAkBdBTtY2xpe8qhqUjZ80hweYi5wzwDMQ+bRoQ2+/U6usjdkbgJaEm4dE0H4 +6tqOqHKZCnokUHfIOEKkvjHT4DulAkBAgiJNSTGi6aDOLa28pGR6YS/mRo1Z/HH9 +kcJ/VuFB1Q8p8Zb2QzvI2CVtY2AFbbtSBPALrXKnVqZZSNgcZiFXAkEAvcLKaEXE +haGMGwq2BLADPHqAR3hdCJL3ikMJwWUsTkTjm973iEIEZfF5j57EzRI4bASm4Zq5 +Zt3BcblLODQ//w== +-----END PRIVATE KEY----- diff --git a/pkg/types/tls.go b/pkg/types/tls.go index 8760a7217..c701b6b7e 100644 --- a/pkg/types/tls.go +++ b/pkg/types/tls.go @@ -4,12 +4,15 @@ import ( "context" "crypto/tls" "crypto/x509" + "errors" "fmt" "os" "github.com/traefik/traefik/v2/pkg/log" ) +// +k8s:deepcopy-gen=true + // ClientTLS holds TLS specific configurations as client // CA, Cert and Key can be either path or file contents. type ClientTLS struct { @@ -27,7 +30,9 @@ func (clientTLS *ClientTLS) CreateTLSConfig(ctx context.Context) (*tls.Config, e return nil, nil } - caPool := x509.NewCertPool() + // Not initialized, to rely on system bundle. + var caPool *x509.CertPool + clientAuth := tls.NoClientCert if clientTLS.CA != "" { var ca []byte @@ -41,8 +46,9 @@ func (clientTLS *ClientTLS) CreateTLSConfig(ctx context.Context) (*tls.Config, e ca = []byte(clientTLS.CA) } + caPool = x509.NewCertPool() if !caPool.AppendCertsFromPEM(ca) { - return nil, fmt.Errorf("failed to parse CA") + return nil, errors.New("failed to parse CA") } if clientTLS.CAOptional { @@ -52,34 +58,24 @@ func (clientTLS *ClientTLS) CreateTLSConfig(ctx context.Context) (*tls.Config, e } } - if !clientTLS.InsecureSkipVerify && (len(clientTLS.Cert) == 0 || len(clientTLS.Key) == 0) { - return nil, fmt.Errorf("TLS Certificate or Key file must be set when TLS configuration is created") + hasCert := len(clientTLS.Cert) > 0 + hasKey := len(clientTLS.Key) > 0 + + if hasCert != hasKey { + return nil, errors.New("both TLS cert and key must be defined") } - cert := tls.Certificate{} - _, errKeyIsFile := os.Stat(clientTLS.Key) + if !hasCert || !hasKey { + return &tls.Config{ + RootCAs: caPool, + InsecureSkipVerify: clientTLS.InsecureSkipVerify, + ClientAuth: clientAuth, + }, nil + } - if len(clientTLS.Cert) > 0 && len(clientTLS.Key) > 0 { - var err error - if _, errCertIsFile := os.Stat(clientTLS.Cert); errCertIsFile == nil { - if errKeyIsFile == nil { - cert, err = tls.LoadX509KeyPair(clientTLS.Cert, clientTLS.Key) - if err != nil { - return nil, fmt.Errorf("failed to load TLS keypair: %w", err) - } - } else { - return nil, fmt.Errorf("TLS cert is a file, but tls key is not") - } - } else { - if errKeyIsFile != nil { - cert, err = tls.X509KeyPair([]byte(clientTLS.Cert), []byte(clientTLS.Key)) - if err != nil { - return nil, fmt.Errorf("failed to load TLS keypair: %w", err) - } - } else { - return nil, fmt.Errorf("TLS key is a file, but tls cert is not") - } - } + cert, err := loadKeyPair(clientTLS.Cert, clientTLS.Key) + if err != nil { + return nil, err } return &tls.Config{ @@ -89,3 +85,27 @@ func (clientTLS *ClientTLS) CreateTLSConfig(ctx context.Context) (*tls.Config, e ClientAuth: clientAuth, }, nil } + +func loadKeyPair(cert, key string) (tls.Certificate, error) { + keyPair, err := tls.X509KeyPair([]byte(cert), []byte(key)) + if err == nil { + return keyPair, nil + } + + _, err = os.Stat(cert) + if err != nil { + return tls.Certificate{}, errors.New("cert file does not exist") + } + + _, err = os.Stat(key) + if err != nil { + return tls.Certificate{}, errors.New("key file does not exist") + } + + keyPair, err = tls.LoadX509KeyPair(cert, key) + if err != nil { + return tls.Certificate{}, err + } + + return keyPair, nil +} diff --git a/pkg/types/tls_test.go b/pkg/types/tls_test.go new file mode 100644 index 000000000..d3e93f408 --- /dev/null +++ b/pkg/types/tls_test.go @@ -0,0 +1,129 @@ +package types + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// go run $GOROOT/src/crypto/tls/generate_cert.go --rsa-bits 1024 --host localhost --start-date "Jan 1 00:00:00 1970" --duration=1000000h +var cert = `-----BEGIN CERTIFICATE----- +MIIB9jCCAV+gAwIBAgIQI3edJckNbicw4WIHs5Ws9TANBgkqhkiG9w0BAQsFADAS +MRAwDgYDVQQKEwdBY21lIENvMCAXDTcwMDEwMTAwMDAwMFoYDzIwODQwMTI5MTYw +MDAwWjASMRAwDgYDVQQKEwdBY21lIENvMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCB +iQKBgQCb8oWyME1QRBoMLFei3M8TVKwfZfW74cVjtcugCBMTTOTCouEIgjjmiMv6 +FdMio2uBcgeD9R3dOtjjnA7N+xjwZ4vIPqDlJRE3YbfpV9igVX3sXU7ssHTSH0vs +R0TuYJwGReIFUnu5QIjGwVorodF+CQ8dTnyXVLeQVU9kvjohHwIDAQABo0swSTAO +BgNVHQ8BAf8EBAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIw +ADAUBgNVHREEDTALgglsb2NhbGhvc3QwDQYJKoZIhvcNAQELBQADgYEADqylUQ/4 +lrxh4h8UUQ2wKATQ2kG2YvMGlaIhr2vPZo2QDBlmL2xzai7YXX3+JZyM15TNCamn +WtFR7WQIOHzKA1GkR9WkaXKmFbJjhGMSZVCG6ghhTjzB+stBYZXhBsdjCJbkZWBu +OeI73oivo0MdI+4iCYCo7TnoY4PZGObwcgI= +-----END CERTIFICATE-----` + +var key = `-----BEGIN PRIVATE KEY----- +MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAJvyhbIwTVBEGgws +V6LczxNUrB9l9bvhxWO1y6AIExNM5MKi4QiCOOaIy/oV0yKja4FyB4P1Hd062OOc +Ds37GPBni8g+oOUlETdht+lX2KBVfexdTuywdNIfS+xHRO5gnAZF4gVSe7lAiMbB +Wiuh0X4JDx1OfJdUt5BVT2S+OiEfAgMBAAECgYA9+PbghQl0aFvhko2RDybLi86K ++73X2DTVFx3AjvTlqp0OLCQ5eWabVqmYzKuHDGJgoqwR6Irhq80dRpsriCm0YNui +mMV35bbimOKz9FoCTKx0ZB6xsqrVoFhjVmX3DOD9Txe41H42ZxmccOKZndR/QaXz +VV+1W/Wbz2VawnkyYQJBAMvF6w2eOJRRoN8e7GM7b7uqkupJPp9axgFREoJZb16W +mqXUZnH4Cydzc5keG4yknQRHdgz6RrQxnvR7GyKHLfUCQQDD6qG9D5BX0+mNW6TG +PRwW/L2qWgnmg9lxtSSQat9ZOnBhw2OLPi0zTu4p70oSmU67/YJr50HEoJpRccZJ +mnJDAkBdBTtY2xpe8qhqUjZ80hweYi5wzwDMQ+bRoQ2+/U6usjdkbgJaEm4dE0H4 +6tqOqHKZCnokUHfIOEKkvjHT4DulAkBAgiJNSTGi6aDOLa28pGR6YS/mRo1Z/HH9 +kcJ/VuFB1Q8p8Zb2QzvI2CVtY2AFbbtSBPALrXKnVqZZSNgcZiFXAkEAvcLKaEXE +haGMGwq2BLADPHqAR3hdCJL3ikMJwWUsTkTjm973iEIEZfF5j57EzRI4bASm4Zq5 +Zt3BcblLODQ//w== +-----END PRIVATE KEY-----` + +func TestClientTLS_CreateTLSConfig(t *testing.T) { + tests := []struct { + desc string + clientTLS ClientTLS + wantCertLen int + wantCALen int + wantErr bool + }{ + { + desc: "Configure CA", + clientTLS: ClientTLS{CA: cert}, + wantCALen: 1, + wantErr: false, + }, + { + desc: "Configure the client keyPair from strings", + clientTLS: ClientTLS{Cert: cert, Key: key}, + wantCertLen: 1, + wantErr: false, + }, + { + desc: "Configure the client keyPair from files", + clientTLS: ClientTLS{Cert: "fixtures/cert.pem", Key: "fixtures/key.pem"}, + wantCertLen: 1, + wantErr: false, + }, + { + desc: "Configure InsecureSkipVerify", + clientTLS: ClientTLS{InsecureSkipVerify: true}, + wantErr: false, + }, + { + desc: "Return an error if only the client cert is provided", + clientTLS: ClientTLS{Cert: cert}, + wantErr: true, + }, + { + desc: "Return an error if only the client key is provided", + clientTLS: ClientTLS{Key: key}, + wantErr: true, + }, + { + desc: "Return an error if only the client cert is of type file", + clientTLS: ClientTLS{Cert: "fixtures/cert.pem", Key: key}, + wantErr: true, + }, + { + desc: "Return an error if only the client key is of type file", + clientTLS: ClientTLS{Cert: cert, Key: "fixtures/key.pem"}, + wantErr: true, + }, + { + desc: "Return an error if the client cert does not exist", + clientTLS: ClientTLS{Cert: "fixtures/cert2.pem", Key: "fixtures/key.pem"}, + wantErr: true, + }, + { + desc: "Return an error if the client key does not exist", + clientTLS: ClientTLS{Cert: "fixtures/cert.pem", Key: "fixtures/key2.pem"}, + wantErr: true, + }, + } + + for _, test := range tests { + test := test + + t.Run(test.desc, func(t *testing.T) { + tlsConfig, err := test.clientTLS.CreateTLSConfig(context.Background()) + if test.wantErr { + require.Error(t, err) + return + } + + require.NoError(t, err) + + assert.Len(t, tlsConfig.Certificates, test.wantCertLen) + assert.Equal(t, test.clientTLS.InsecureSkipVerify, tlsConfig.InsecureSkipVerify) + + if test.wantCALen > 0 { + assert.Len(t, tlsConfig.RootCAs.Subjects(), test.wantCALen) + return + } + + assert.Nil(t, tlsConfig.RootCAs) + }) + } +} diff --git a/pkg/types/zz_generated.deepcopy.go b/pkg/types/zz_generated.deepcopy.go index 06daf6fa7..91c7716b3 100644 --- a/pkg/types/zz_generated.deepcopy.go +++ b/pkg/types/zz_generated.deepcopy.go @@ -29,6 +29,22 @@ THE SOFTWARE. package types +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ClientTLS) DeepCopyInto(out *ClientTLS) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClientTLS. +func (in *ClientTLS) DeepCopy() *ClientTLS { + if in == nil { + return nil + } + out := new(ClientTLS) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Domain) DeepCopyInto(out *Domain) { *out = *in diff --git a/pkg/udp/wrr_load_balancer.go b/pkg/udp/wrr_load_balancer.go index 384b699fc..7dd2a9c07 100644 --- a/pkg/udp/wrr_load_balancer.go +++ b/pkg/udp/wrr_load_balancer.go @@ -15,7 +15,7 @@ type server struct { // WRRLoadBalancer is a naive RoundRobin load balancer for UDP services. type WRRLoadBalancer struct { servers []server - lock sync.RWMutex + lock sync.Mutex currentWeight int index int } @@ -29,16 +29,16 @@ func NewWRRLoadBalancer() *WRRLoadBalancer { // ServeUDP forwards the connection to the right service. func (b *WRRLoadBalancer) ServeUDP(conn *Conn) { - if len(b.servers) == 0 { - log.WithoutContext().Error("no available server") - return - } - + b.lock.Lock() next, err := b.next() + b.lock.Unlock() + if err != nil { log.WithoutContext().Errorf("Error during load balancing: %v", err) conn.Close() + return } + next.ServeUDP(conn) } @@ -50,6 +50,9 @@ func (b *WRRLoadBalancer) AddServer(serverHandler Handler) { // AddWeightedServer appends a handler to the existing list with a weight. func (b *WRRLoadBalancer) AddWeightedServer(serverHandler Handler, weight *int) { + b.lock.Lock() + defer b.lock.Unlock() + w := 1 if weight != nil { w = *weight @@ -87,9 +90,6 @@ func gcd(a, b int) int { } func (b *WRRLoadBalancer) next() (Handler, error) { - b.lock.Lock() - defer b.lock.Unlock() - if len(b.servers) == 0 { return nil, fmt.Errorf("no servers in the pool") } @@ -98,10 +98,14 @@ func (b *WRRLoadBalancer) next() (Handler, error) { // but is actually very simple it calculates the GCD and subtracts it on every iteration, // what interleaves servers and allows us not to build an iterator every time we readjust weights. - // GCD across all enabled servers - gcd := b.weightGcd() // Maximum weight across all enabled servers max := b.maxWeight() + if max == 0 { + return nil, fmt.Errorf("all servers have 0 weight") + } + + // GCD across all enabled servers + gcd := b.weightGcd() for { b.index = (b.index + 1) % len(b.servers) @@ -109,9 +113,6 @@ func (b *WRRLoadBalancer) next() (Handler, error) { b.currentWeight -= gcd if b.currentWeight <= 0 { b.currentWeight = max - if b.currentWeight == 0 { - return nil, fmt.Errorf("all servers have 0 weight") - } } } srv := b.servers[b.index] diff --git a/script/gcg/traefik-bugfix.toml b/script/gcg/traefik-bugfix.toml index d9678b286..78db43815 100644 --- a/script/gcg/traefik-bugfix.toml +++ b/script/gcg/traefik-bugfix.toml @@ -4,11 +4,11 @@ RepositoryName = "traefik" OutputType = "file" FileName = "traefik_changelog.md" -# example new bugfix v2.5.3 +# example new bugfix v2.5.4 CurrentRef = "v2.5" -PreviousRef = "v2.5.2" +PreviousRef = "v2.5.3" BaseBranch = "v2.5" -FutureCurrentRefName = "v2.5.3" +FutureCurrentRefName = "v2.5.4" ThresholdPreviousRef = 10 ThresholdCurrentRef = 10 diff --git a/script/validate-shell-script.sh b/script/validate-shell-script.sh index befaebe59..5f785128d 100755 --- a/script/validate-shell-script.sh +++ b/script/validate-shell-script.sh @@ -12,10 +12,10 @@ then # The shellcheck command are run in background, to have an overview of the linter (instead of a fail at first issue) shellcheck "${script_to_check}" & done < <( # Search all the repository for sh and bash shebangs, excluding .js and .md files - # the folders ".git" and "vendor" are also ignored + # the folders ".git", "vendor" and "node_modules" are also ignored grep -rI '#!/' "${script_dir}"/.. \ | grep 'sh' | grep -v '.js' | grep -v '.md' \ - | grep -v '.git/' | grep -v 'vendor/' \ + | grep -v '.git/' | grep -v 'vendor/' | grep -v 'node_modules/' \ | cut -d':' -f1 ) wait # Wait for all background command to be completed diff --git a/webui/src/components/_commons/PanelWeightedServices.vue b/webui/src/components/_commons/PanelWeightedServices.vue index f5c9d3880..3f23ed697 100644 --- a/webui/src/components/_commons/PanelWeightedServices.vue +++ b/webui/src/components/_commons/PanelWeightedServices.vue @@ -64,7 +64,7 @@ export default { }, getProviderLogoPath (service) { const provider = this.getProvider(service) - const name = provider.name.toLowerCase() + const name = provider.toLowerCase() if (name.includes('plugin-')) { return 'statics/providers/plugin.svg'