forked from Ivasoft/traefik
Compare commits
23 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d97d3a6726 | ||
|
|
a8df674dcf | ||
|
|
abd569701f | ||
|
|
7e3fe48b80 | ||
|
|
778188ed34 | ||
|
|
88603810a8 | ||
|
|
c7647b4938 | ||
|
|
af71443b61 | ||
|
|
18d66d7432 | ||
|
|
7c72780820 | ||
|
|
68e8eb2435 | ||
|
|
81a5b1b4c8 | ||
|
|
52e6ce95cf | ||
|
|
d547718fdd | ||
|
|
af4e74c39d | ||
|
|
f6b7940b76 | ||
|
|
f1b91a119d | ||
|
|
35d8281f4d | ||
|
|
00de5c711a | ||
|
|
b935c80dbd | ||
|
|
22c6630412 | ||
|
|
b2c4221429 | ||
|
|
97de552e06 |
31
CHANGELOG.md
31
CHANGELOG.md
@@ -1,3 +1,34 @@
|
||||
## [v2.9.6](https://github.com/traefik/traefik/tree/v2.9.6) (2022-12-07)
|
||||
[All Commits](https://github.com/traefik/traefik/compare/v2.9.5...v2.9.6)
|
||||
|
||||
**Bug fixes:**
|
||||
- **[acme]** Update go-acme/lego to v4.9.1 ([#9550](https://github.com/traefik/traefik/pull/9550) by [ldez](https://github.com/ldez))
|
||||
- **[k8s/crd]** Support of allowEmptyServices in TraefikService ([#9424](https://github.com/traefik/traefik/pull/9424) by [jeromeguiard](https://github.com/jeromeguiard))
|
||||
- **[logs]** Remove logs of the request ([#9574](https://github.com/traefik/traefik/pull/9574) by [ldez](https://github.com/ldez))
|
||||
- **[plugins]** Increase the timeout on plugin download ([#9529](https://github.com/traefik/traefik/pull/9529) by [ldez](https://github.com/ldez))
|
||||
- **[server]** Update golang.org/x/net ([#9582](https://github.com/traefik/traefik/pull/9582) by [ldez](https://github.com/ldez))
|
||||
- **[tls]** Handle broken TLS conf better ([#9572](https://github.com/traefik/traefik/pull/9572) by [mpl](https://github.com/mpl))
|
||||
- **[tracing]** Update DataDog tracing dependency to v1.43.1 ([#9526](https://github.com/traefik/traefik/pull/9526) by [rtribotte](https://github.com/rtribotte))
|
||||
- **[webui]** Add missing serialNumber passTLSClientCert option to middleware panel ([#9539](https://github.com/traefik/traefik/pull/9539) by [rtribotte](https://github.com/rtribotte))
|
||||
|
||||
**Documentation:**
|
||||
- **[docker]** Add networking example ([#9542](https://github.com/traefik/traefik/pull/9542) by [Janik-Haag](https://github.com/Janik-Haag))
|
||||
- **[hub]** Add information about the Hub Agent ([#9560](https://github.com/traefik/traefik/pull/9560) by [nmengin](https://github.com/nmengin))
|
||||
- **[k8s/helm]** Update Helm installation section ([#9564](https://github.com/traefik/traefik/pull/9564) by [mloiseleur](https://github.com/mloiseleur))
|
||||
- **[middleware]** Clarify PathPrefix matcher greediness ([#9519](https://github.com/traefik/traefik/pull/9519) by [mpl](https://github.com/mpl))
|
||||
|
||||
## [v2.9.5](https://github.com/traefik/traefik/tree/v2.9.5) (2022-11-17)
|
||||
[All Commits](https://github.com/traefik/traefik/compare/v2.9.4...v2.9.5)
|
||||
|
||||
**Bug fixes:**
|
||||
- **[logs,middleware]** Create a new capture instance for each incoming request ([#9510](https://github.com/traefik/traefik/pull/9510) by [sdelicata](https://github.com/sdelicata))
|
||||
|
||||
**Documentation:**
|
||||
- **[k8s/helm]** Update helm repository ([#9506](https://github.com/traefik/traefik/pull/9506) by [charlie-haley](https://github.com/charlie-haley))
|
||||
- Enhance wording of building-testing page ([#9509](https://github.com/traefik/traefik/pull/9509) by [svx](https://github.com/svx))
|
||||
- Add link descriptions and update wording ([#9507](https://github.com/traefik/traefik/pull/9507) by [svx](https://github.com/svx))
|
||||
- Removes the experimental tag on the Traefik Hub header ([#9498](https://github.com/traefik/traefik/pull/9498) by [tfny](https://github.com/tfny))
|
||||
|
||||
## [v2.9.4](https://github.com/traefik/traefik/tree/v2.9.4) (2022-10-27)
|
||||
[All Commits](https://github.com/traefik/traefik/compare/v2.9.1...v2.9.4)
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ import (
|
||||
"github.com/traefik/traefik/v2/pkg/tracing/jaeger"
|
||||
"github.com/traefik/traefik/v2/pkg/types"
|
||||
"github.com/traefik/traefik/v2/pkg/version"
|
||||
"github.com/vulcand/oxy/roundrobin"
|
||||
"github.com/vulcand/oxy/v2/roundrobin"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
@@ -8,17 +8,22 @@ description: "Compile and test your own Traefik Proxy! Learn how to build your o
|
||||
Compile and Test Your Own Traefik!
|
||||
{: .subtitle }
|
||||
|
||||
So you want to build your own Traefik binary from the sources?
|
||||
You want to build your own Traefik binary from the sources?
|
||||
Let's see how.
|
||||
|
||||
## Building
|
||||
|
||||
You need either [Docker](https://github.com/docker/docker) and `make` (Method 1), or `go` (Method 2) in order to build Traefik.
|
||||
You need either [Docker](https://github.com/docker/docker "Link to website of Docker") and `make` (Method 1), or [Go](https://go.dev/ "Link to website of Go") (Method 2) in order to build Traefik.
|
||||
For changes to its dependencies, the `dep` dependency management tool is required.
|
||||
|
||||
### Method 1: Using `Docker` and `Makefile`
|
||||
|
||||
Run make with the `binary` target.
|
||||
|
||||
```bash
|
||||
make binary
|
||||
```
|
||||
|
||||
This will create binaries for the Linux platform in the `dist` folder.
|
||||
|
||||
In case when you run build on CI, you may probably want to run docker in non-interactive mode. To achieve that define `DOCKER_NON_INTERACTIVE=true` environment variable.
|
||||
@@ -160,7 +165,7 @@ TESTFLAGS="-check.f MyTestSuite.My" make test-integration
|
||||
TESTFLAGS="-check.f MyTestSuite.*Test" make test-integration
|
||||
```
|
||||
|
||||
More: https://labix.org/gocheck
|
||||
Check [gocheck](https://labix.org/gocheck "Link to website of gocheck") for more information.
|
||||
|
||||
### Method 2: `go`
|
||||
|
||||
|
||||
@@ -15,10 +15,14 @@ Let's see how.
|
||||
|
||||
### General
|
||||
|
||||
This [documentation](https://doc.traefik.io/traefik/) is built with [mkdocs](https://mkdocs.org/).
|
||||
This [documentation](https://doc.traefik.io/traefik/ "Link to the official Traefik documentation") is built with [MkDocs](https://mkdocs.org/ "Link to website of MkDocs").
|
||||
|
||||
### Method 1: `Docker` and `make`
|
||||
|
||||
Please make sure you have the following requirements installed:
|
||||
|
||||
- [Docker](https://www.docker.com/ "Link to website of Docker")
|
||||
|
||||
You can build the documentation and test it locally (with live reloading), using the `docs-serve` target:
|
||||
|
||||
```bash
|
||||
@@ -43,9 +47,12 @@ $ make docs-build
|
||||
...
|
||||
```
|
||||
|
||||
### Method 2: `mkdocs`
|
||||
### Method 2: `MkDocs`
|
||||
|
||||
First, make sure you have `python` and `pip` installed.
|
||||
Please make sure you have the following requirements installed:
|
||||
|
||||
- [Python](https://www.python.org/ "Link to website of Python")
|
||||
- [pip](https://pypi.org/project/pip/ "Link to the website of pip on PyPI")
|
||||
|
||||
```bash
|
||||
$ python --version
|
||||
@@ -54,7 +61,7 @@ $ pip --version
|
||||
pip 1.5.2
|
||||
```
|
||||
|
||||
Then, install mkdocs with `pip`.
|
||||
Then, install MkDocs with `pip`.
|
||||
|
||||
```bash
|
||||
pip install --user -r requirements.txt
|
||||
@@ -87,7 +94,7 @@ Running ["HtmlCheck", "ImageCheck", "ScriptCheck", "LinkCheck"] on /app/site/bas
|
||||
|
||||
!!! note "Clean & Verify"
|
||||
|
||||
If you've made changes to the documentation, it's safter to clean it before verifying it.
|
||||
If you've made changes to the documentation, it's safer to clean it before verifying it.
|
||||
|
||||
```bash
|
||||
$ make docs
|
||||
|
||||
@@ -44,13 +44,13 @@ Traefik can be installed in Kubernetes using the Helm chart from <https://github
|
||||
|
||||
Ensure that the following requirements are met:
|
||||
|
||||
* Kubernetes 1.14+
|
||||
* Helm version 3.x is [installed](https://helm.sh/docs/intro/install/)
|
||||
* Kubernetes 1.16+
|
||||
* Helm version 3.9+ is [installed](https://helm.sh/docs/intro/install/)
|
||||
|
||||
Add Traefik's chart repository to Helm:
|
||||
Add Traefik Labs chart repository to Helm:
|
||||
|
||||
```bash
|
||||
helm repo add traefik https://helm.traefik.io/traefik
|
||||
helm repo add traefik https://traefik.github.io/charts
|
||||
```
|
||||
|
||||
You can update the chart repository by running:
|
||||
@@ -68,6 +68,9 @@ helm install traefik traefik/traefik
|
||||
!!! tip "Helm Features"
|
||||
|
||||
All [Helm features](https://helm.sh/docs/intro/using_helm/) are supported.
|
||||
|
||||
Examples are provided [here](https://github.com/traefik/traefik-helm-chart/blob/master/EXAMPLES.md).
|
||||
|
||||
For instance, installing the chart in a dedicated namespace:
|
||||
|
||||
```bash tab="Install in a Dedicated Namespace"
|
||||
@@ -83,8 +86,7 @@ helm install traefik traefik/traefik
|
||||
as with [any helm chart](https://helm.sh/docs/intro/using_helm/#customizing-the-chart-before-installing).
|
||||
{: #helm-custom-values }
|
||||
|
||||
The values are not (yet) documented, but are self-explanatory:
|
||||
you can look at the [default `values.yaml`](https://github.com/traefik/traefik-helm-chart/blob/master/traefik/values.yaml) file to explore possibilities.
|
||||
All parameters are documented in the default [`values.yaml`](https://github.com/traefik/traefik-helm-chart/blob/master/traefik/values.yaml).
|
||||
|
||||
You can also set Traefik command line flags using `additionalArguments`.
|
||||
Example of installation with logging set to `DEBUG`:
|
||||
|
||||
@@ -50,7 +50,12 @@ Now that we have a Traefik instance up and running, we will deploy new services.
|
||||
Edit your `docker-compose.yml` file and add the following at the end of your file.
|
||||
|
||||
```yaml
|
||||
# ...
|
||||
version: '3'
|
||||
|
||||
services:
|
||||
|
||||
...
|
||||
|
||||
whoami:
|
||||
# A container that exposes an API to show its IP address
|
||||
image: traefik/whoami
|
||||
|
||||
@@ -276,10 +276,11 @@ The table below lists all the available matchers:
|
||||
|
||||
!!! info "Path Vs PathPrefix"
|
||||
|
||||
Use `Path` if your service listens on the exact path only. For instance, `Path: /products` would match `/products` but not `/products/shoes`.
|
||||
Use `Path` if your service listens on the exact path only. For instance, ```Path(`/products`)``` would match `/products` but not `/products/shoes`.
|
||||
|
||||
Use a `*Prefix*` matcher if your service listens on a particular base path but also serves requests on sub-paths.
|
||||
For instance, `PathPrefix: /products` would match `/products` but also `/products/shoes` and `/products/shirts`.
|
||||
For instance, ```PathPrefix(`/products`)``` would match `/products` and `/products/shoes`,
|
||||
as well as `/productsforsale`, and `/productsforsale/shoes`.
|
||||
Since the path is forwarded as-is, your service is expected to listen on `/products`.
|
||||
|
||||
!!! info "ClientIP matcher"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Traefik Hub (Experimental)
|
||||
# Traefik Hub
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -29,6 +29,12 @@ This agent can:
|
||||
* The Traefik Hub Agent must be installed to connect to the Traefik Hub platform.
|
||||
* Activate this feature in the experimental section of the static configuration.
|
||||
|
||||
!!! information "Configuration Discovery"
|
||||
|
||||
According to installation options, the Traefik Hub Agent listens to the Docker or Kubernetes API to discover containers/services.
|
||||
|
||||
It doesn't support the routers discovered by Traefik Proxy using other providers, e.g., using the File provider.
|
||||
|
||||
!!! example "Minimal Static Configuration to Activate Traefik Hub for Docker"
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
|
||||
@@ -16,6 +16,35 @@ This will also be used as a starting point for the other docker-compose guides.
|
||||
--8<-- "content/user-guides/docker-compose/basic-example/docker-compose.yml"
|
||||
```
|
||||
|
||||
??? Networking
|
||||
|
||||
The Traefik container has to be attached to the same network as the containers to be exposed.
|
||||
If no networks are specified in the docker-compose file, Docker creates a default one that allows Traefik to reach the containers defined in the same file.
|
||||
You can [customize the network](https://docs.docker.com/compose/networking/#specify-custom-networks) as described in the example below.
|
||||
You can use a [pre-existing network](https://docs.docker.com/compose/networking/#use-a-pre-existing-network) too.
|
||||
|
||||
```yaml
|
||||
version: "3.3"
|
||||
|
||||
networks:
|
||||
traefiknet: {}
|
||||
|
||||
services:
|
||||
|
||||
traefik:
|
||||
image: "traefik:v2.9"
|
||||
...
|
||||
networks:
|
||||
- traefiknet
|
||||
|
||||
whoami:
|
||||
image: "traefik/whoami"
|
||||
...
|
||||
networks:
|
||||
- traefiknet
|
||||
|
||||
```
|
||||
|
||||
- Replace `whoami.localhost` by your **own domain** within the `traefik.http.routers.whoami.rule` label of the `whoami` service.
|
||||
- Run `docker-compose up -d` within the folder where you created the previous file.
|
||||
- Wait a bit and visit `http://your_own_domain` to confirm everything went fine.
|
||||
|
||||
35
go.mod
35
go.mod
@@ -19,7 +19,7 @@ require (
|
||||
github.com/docker/go-connections v0.4.0
|
||||
github.com/fatih/structs v1.1.0
|
||||
github.com/gambol99/go-marathon v0.0.0-20180614232016-99a156b96fb2
|
||||
github.com/go-acme/lego/v4 v4.9.0
|
||||
github.com/go-acme/lego/v4 v4.9.1
|
||||
github.com/go-check/check v0.0.0-00010101000000-000000000000
|
||||
github.com/go-kit/kit v0.10.1-0.20200915143503-439c4d2ed3ea
|
||||
github.com/golang/protobuf v1.5.2
|
||||
@@ -35,7 +35,7 @@ require (
|
||||
github.com/influxdata/influxdb-client-go/v2 v2.7.0
|
||||
github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d
|
||||
github.com/instana/go-sensor v1.38.3
|
||||
github.com/klauspost/compress v1.14.2
|
||||
github.com/klauspost/compress v1.15.0
|
||||
github.com/kvtools/consul v1.0.2
|
||||
github.com/kvtools/etcdv3 v1.0.2
|
||||
github.com/kvtools/redis v1.0.2
|
||||
@@ -56,8 +56,8 @@ require (
|
||||
github.com/prometheus/client_golang v1.12.2-0.20220704083116-e8f91604d835
|
||||
github.com/prometheus/client_model v0.2.0
|
||||
github.com/rancher/go-rancher-metadata v0.0.0-20200311180630-7f4c936a06ac
|
||||
github.com/sirupsen/logrus v1.8.1
|
||||
github.com/stretchr/testify v1.8.0
|
||||
github.com/sirupsen/logrus v1.9.0
|
||||
github.com/stretchr/testify v1.8.1
|
||||
github.com/stvp/go-udp-testing v0.0.0-20191102171040-06b61409b154
|
||||
github.com/traefik/paerser v0.1.9
|
||||
github.com/traefik/yaegi v0.14.3
|
||||
@@ -66,17 +66,17 @@ require (
|
||||
github.com/unrolled/render v1.0.2
|
||||
github.com/unrolled/secure v1.0.9
|
||||
github.com/vdemeester/shakers v0.1.0
|
||||
github.com/vulcand/oxy v1.4.1
|
||||
github.com/vulcand/oxy/v2 v2.0.0-20221121151423-d5cb734e4467
|
||||
github.com/vulcand/predicate v1.2.0
|
||||
go.elastic.co/apm v1.13.1
|
||||
go.elastic.co/apm/module/apmot v1.13.1
|
||||
golang.org/x/mod v0.4.2
|
||||
golang.org/x/net v0.0.0-20220927171203-f486391704dc
|
||||
golang.org/x/text v0.3.7
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4
|
||||
golang.org/x/net v0.3.1-0.20221206200815-1e63c2f08a10
|
||||
golang.org/x/text v0.5.0
|
||||
golang.org/x/time v0.0.0-20220224211638-0e9765cccd65
|
||||
golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2
|
||||
golang.org/x/tools v0.1.12
|
||||
google.golang.org/grpc v1.41.0
|
||||
gopkg.in/DataDog/dd-trace-go.v1 v1.38.1
|
||||
gopkg.in/DataDog/dd-trace-go.v1 v1.43.1
|
||||
gopkg.in/fsnotify.v1 v1.4.7
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
k8s.io/api v0.22.1
|
||||
@@ -106,7 +106,7 @@ require (
|
||||
github.com/DataDog/datadog-agent/pkg/obfuscate v0.0.0-20211129110424-6491aa3bf583 // indirect
|
||||
github.com/DataDog/datadog-go v4.8.2+incompatible // indirect
|
||||
github.com/DataDog/datadog-go/v5 v5.0.2 // indirect
|
||||
github.com/DataDog/sketches-go v1.0.0 // indirect
|
||||
github.com/DataDog/sketches-go v1.2.1 // indirect
|
||||
github.com/HdrHistogram/hdrhistogram-go v1.1.2 // indirect
|
||||
github.com/Masterminds/goutils v1.1.1 // indirect
|
||||
github.com/Masterminds/semver/v3 v3.1.1 // indirect
|
||||
@@ -268,7 +268,7 @@ require (
|
||||
github.com/onsi/ginkgo v1.16.5 // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/opencontainers/image-spec v1.0.2 // indirect
|
||||
github.com/opencontainers/runc v1.0.2 // indirect
|
||||
github.com/opencontainers/runc v1.0.3 // indirect
|
||||
github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492 // indirect
|
||||
github.com/oracle/oci-go-sdk v24.3.0+incompatible // indirect
|
||||
github.com/ovh/go-ovh v1.1.0 // indirect
|
||||
@@ -293,7 +293,7 @@ require (
|
||||
github.com/spf13/cast v1.3.1 // indirect
|
||||
github.com/spf13/cobra v1.2.1 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/stretchr/objx v0.4.0 // indirect
|
||||
github.com/stretchr/objx v0.5.0 // indirect
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.490 // indirect
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.490 // indirect
|
||||
github.com/theupdateframework/notary v0.6.1 // indirect
|
||||
@@ -319,12 +319,14 @@ require (
|
||||
go.uber.org/multierr v1.6.0 // indirect
|
||||
go.uber.org/ratelimit v0.2.0 // indirect
|
||||
go.uber.org/zap v1.18.1 // indirect
|
||||
go4.org/intern v0.0.0-20211027215823-ae77deb06f29 // indirect
|
||||
go4.org/unsafe/assume-no-moving-gc v0.0.0-20220617031537-928513b29760 // indirect
|
||||
golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f // indirect
|
||||
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 // indirect
|
||||
golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1 // indirect
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab // indirect
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 // indirect
|
||||
golang.org/x/sys v0.3.0 // indirect
|
||||
golang.org/x/term v0.3.0 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
|
||||
google.golang.org/api v0.44.0 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
@@ -337,6 +339,7 @@ require (
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
howett.net/plist v0.0.0-20181124034731-591f970eefbb // indirect
|
||||
inet.af/netaddr v0.0.0-20220617031823-097006376321 // indirect
|
||||
k8s.io/klog/v2 v2.10.0 // indirect
|
||||
k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e // indirect
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.1.2 // indirect
|
||||
|
||||
60
integration/fixtures/https/https_invalid_tls_options.toml
Normal file
60
integration/fixtures/https/https_invalid_tls_options.toml
Normal file
@@ -0,0 +1,60 @@
|
||||
[global]
|
||||
checkNewVersion = false
|
||||
sendAnonymousUsage = false
|
||||
|
||||
[log]
|
||||
level = "DEBUG"
|
||||
|
||||
[entryPoints.websecure]
|
||||
address = ":4443"
|
||||
|
||||
[api]
|
||||
insecure = true
|
||||
|
||||
[providers.file]
|
||||
filename = "{{ .SelfFilename }}"
|
||||
|
||||
## dynamic configuration ##
|
||||
|
||||
[http.routers]
|
||||
|
||||
[http.routers.router1]
|
||||
entryPoints = ["websecure"]
|
||||
service = "service1"
|
||||
rule = "Host(`snitest.com`)"
|
||||
[http.routers.router1.tls]
|
||||
options = "invalidTLSOptions"
|
||||
|
||||
[http.routers.router2]
|
||||
entryPoints = ["websecure"]
|
||||
service = "service1"
|
||||
rule = "Host(`snitest.org`)"
|
||||
[http.routers.router2.tls]
|
||||
|
||||
# fallback router
|
||||
[http.routers.router3]
|
||||
entryPoints = ["websecure"]
|
||||
service = "service1"
|
||||
rule = "Path(`/`)"
|
||||
[http.routers.router3.tls]
|
||||
|
||||
[[http.services.service1.loadBalancer.servers]]
|
||||
url = "http://127.0.0.1:9010"
|
||||
|
||||
[[tls.certificates]]
|
||||
certFile = "fixtures/https/snitest.com.cert"
|
||||
keyFile = "fixtures/https/snitest.com.key"
|
||||
|
||||
[[tls.certificates]]
|
||||
certFile = "fixtures/https/snitest.org.cert"
|
||||
keyFile = "fixtures/https/snitest.org.key"
|
||||
|
||||
[tls.options]
|
||||
|
||||
[tls.options.default.clientAuth]
|
||||
# Missing caFile to have an invalid mTLS configuration.
|
||||
clientAuthType = "RequireAndVerifyClientCert"
|
||||
|
||||
[tls.options.invalidTLSOptions.clientAuth]
|
||||
# Missing caFile to have an invalid mTLS configuration.
|
||||
clientAuthType = "RequireAndVerifyClientCert"
|
||||
15
integration/fixtures/simple_debug_log.toml
Normal file
15
integration/fixtures/simple_debug_log.toml
Normal file
@@ -0,0 +1,15 @@
|
||||
[global]
|
||||
checkNewVersion = false
|
||||
sendAnonymousUsage = false
|
||||
|
||||
[log]
|
||||
level = "DEBUG"
|
||||
|
||||
[api]
|
||||
insecure = true
|
||||
|
||||
[providers.docker]
|
||||
|
||||
[entryPoints]
|
||||
[entryPoints.webHost]
|
||||
address = ":8000"
|
||||
@@ -33,6 +33,13 @@
|
||||
[tcp.routers.to-whoami-sni-strict.tls]
|
||||
options = "bar"
|
||||
|
||||
[tcp.routers.to-whoami-invalid-tls]
|
||||
rule = "HostSNI(`whoami-i.test`)"
|
||||
service = "whoami-no-cert"
|
||||
entryPoints = [ "tcp" ]
|
||||
[tcp.routers.to-whoami-invalid-tls.tls]
|
||||
options = "invalid"
|
||||
|
||||
[tcp.services.whoami-no-cert]
|
||||
[tcp.services.whoami-no-cert.loadBalancer]
|
||||
[[tcp.services.whoami-no-cert.loadBalancer.servers]]
|
||||
@@ -45,3 +52,7 @@
|
||||
|
||||
[tls.options.bar]
|
||||
minVersion = "VersionTLS13"
|
||||
|
||||
[tls.options.invalid.clientAuth]
|
||||
# Missing CA files to have an invalid mTLS configuration.
|
||||
clientAuthType = "RequireAndVerifyClientCert"
|
||||
|
||||
@@ -1226,3 +1226,53 @@ func (s *HTTPSSuite) TestWithDomainFronting(c *check.C) {
|
||||
c.Assert(err, checker.IsNil)
|
||||
}
|
||||
}
|
||||
|
||||
// TestWithInvalidTLSOption verifies the behavior when using an invalid tlsOption configuration.
|
||||
func (s *HTTPSSuite) TestWithInvalidTLSOption(c *check.C) {
|
||||
backend := startTestServer("9010", http.StatusOK, "server1")
|
||||
defer backend.Close()
|
||||
|
||||
file := s.adaptFile(c, "fixtures/https/https_invalid_tls_options.toml", struct{}{})
|
||||
defer os.Remove(file)
|
||||
cmd, display := s.traefikCmd(withConfigFile(file))
|
||||
defer display(c)
|
||||
err := cmd.Start()
|
||||
c.Assert(err, checker.IsNil)
|
||||
defer s.killCmd(cmd)
|
||||
|
||||
// wait for Traefik
|
||||
err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 500*time.Millisecond, try.BodyContains("Host(`snitest.com`)"))
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
testCases := []struct {
|
||||
desc string
|
||||
serverName string
|
||||
}{
|
||||
{
|
||||
desc: "With invalid TLS Options specified",
|
||||
serverName: "snitest.com",
|
||||
},
|
||||
{
|
||||
desc: "With invalid Default TLS Options",
|
||||
serverName: "snitest.org",
|
||||
},
|
||||
{
|
||||
desc: "With TLS Options without servername (fallback to default)",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
|
||||
tlsConfig := &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
}
|
||||
if test.serverName != "" {
|
||||
tlsConfig.ServerName = test.serverName
|
||||
}
|
||||
|
||||
conn, err := tls.Dial("tcp", "127.0.0.1:4443", tlsConfig)
|
||||
c.Assert(err, checker.NotNil, check.Commentf("connected to server successfully"))
|
||||
c.Assert(conn, checker.IsNil)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
version: "3.8"
|
||||
services:
|
||||
server:
|
||||
image: rancher/k3s:v1.18.20-k3s1
|
||||
image: rancher/k3s:v1.20.15-k3s1
|
||||
command: server --disable-agent --no-deploy coredns --no-deploy servicelb --no-deploy traefik --no-deploy local-storage --no-deploy metrics-server --log /output/k3s.log --bind-address=server --tls-san=server
|
||||
environment:
|
||||
K3S_CLUSTER_SECRET: somethingtotallyrandom
|
||||
@@ -12,7 +12,7 @@ services:
|
||||
- ./fixtures/k8s:/var/lib/rancher/k3s/server/manifests
|
||||
|
||||
node:
|
||||
image: rancher/k3s:v1.18.20-k3s1
|
||||
image: rancher/k3s:v1.20.15-k3s1
|
||||
privileged: true
|
||||
environment:
|
||||
K3S_URL: https://server:6443
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"syscall"
|
||||
@@ -1378,3 +1379,36 @@ func (s *SimpleSuite) TestMuxer(c *check.C) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *SimpleSuite) TestDebugLog(c *check.C) {
|
||||
s.createComposeProject(c, "base")
|
||||
|
||||
s.composeUp(c)
|
||||
defer s.composeDown(c)
|
||||
|
||||
file := s.adaptFile(c, "fixtures/simple_debug_log.toml", struct{}{})
|
||||
defer os.Remove(file)
|
||||
|
||||
cmd, output := s.cmdTraefik(withConfigFile(file))
|
||||
|
||||
err := cmd.Start()
|
||||
c.Assert(err, checker.IsNil)
|
||||
defer s.killCmd(cmd)
|
||||
|
||||
err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("PathPrefix(`/whoami`)"))
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, "http://localhost:8000/whoami", http.NoBody)
|
||||
c.Assert(err, checker.IsNil)
|
||||
req.Header.Set("Autorization", "Bearer ThisIsABearerToken")
|
||||
|
||||
response, err := http.DefaultClient.Do(req)
|
||||
c.Assert(err, checker.IsNil)
|
||||
c.Assert(response.StatusCode, checker.Equals, http.StatusOK)
|
||||
|
||||
if regexp.MustCompile("ThisIsABearerToken").MatchReader(output) {
|
||||
c.Logf("Traefik Logs: %s", output.String())
|
||||
c.Log("Found Authorization Header in Traefik DEBUG logs")
|
||||
c.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,6 +116,14 @@ func (s *TCPSuite) TestTLSOptions(c *check.C) {
|
||||
_, err = guessWhoTLSMaxVersion("127.0.0.1:8093", "whoami-d.test", true, tls.VersionTLS12)
|
||||
c.Assert(err, checker.NotNil)
|
||||
c.Assert(err.Error(), checker.Contains, "protocol version not supported")
|
||||
|
||||
// Check that we can't reach a route with an invalid mTLS configuration.
|
||||
conn, err := tls.Dial("tcp", "127.0.0.1:8093", &tls.Config{
|
||||
ServerName: "whoami-i.test",
|
||||
InsecureSkipVerify: true,
|
||||
})
|
||||
c.Assert(conn, checker.IsNil)
|
||||
c.Assert(err, checker.NotNil)
|
||||
}
|
||||
|
||||
func (s *TCPSuite) TestNonTLSFallback(c *check.C) {
|
||||
|
||||
@@ -18,7 +18,7 @@ import (
|
||||
"github.com/traefik/traefik/v2/pkg/log"
|
||||
"github.com/traefik/traefik/v2/pkg/metrics"
|
||||
"github.com/traefik/traefik/v2/pkg/safe"
|
||||
"github.com/vulcand/oxy/roundrobin"
|
||||
"github.com/vulcand/oxy/v2/roundrobin"
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/traefik/traefik/v2/pkg/config/runtime"
|
||||
"github.com/traefik/traefik/v2/pkg/testhelpers"
|
||||
"github.com/vulcand/oxy/roundrobin"
|
||||
"github.com/vulcand/oxy/v2/roundrobin"
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
|
||||
"github.com/traefik/traefik/v2/pkg/log"
|
||||
"github.com/traefik/traefik/v2/pkg/middlewares/capture"
|
||||
"github.com/vulcand/oxy/utils"
|
||||
"github.com/vulcand/oxy/v2/utils"
|
||||
)
|
||||
|
||||
// FieldApply function hook to add data in accesslog.
|
||||
|
||||
@@ -17,8 +17,8 @@ import (
|
||||
"github.com/traefik/traefik/v2/pkg/middlewares"
|
||||
"github.com/traefik/traefik/v2/pkg/middlewares/connectionheader"
|
||||
"github.com/traefik/traefik/v2/pkg/tracing"
|
||||
"github.com/vulcand/oxy/forward"
|
||||
"github.com/vulcand/oxy/utils"
|
||||
"github.com/vulcand/oxy/v2/forward"
|
||||
"github.com/vulcand/oxy/v2/utils"
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
@@ -16,7 +16,7 @@ import (
|
||||
tracingMiddleware "github.com/traefik/traefik/v2/pkg/middlewares/tracing"
|
||||
"github.com/traefik/traefik/v2/pkg/testhelpers"
|
||||
"github.com/traefik/traefik/v2/pkg/tracing"
|
||||
"github.com/vulcand/oxy/forward"
|
||||
"github.com/vulcand/oxy/v2/forward"
|
||||
)
|
||||
|
||||
func TestForwardAuthFail(t *testing.T) {
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
"github.com/traefik/traefik/v2/pkg/log"
|
||||
"github.com/traefik/traefik/v2/pkg/middlewares"
|
||||
"github.com/traefik/traefik/v2/pkg/tracing"
|
||||
oxybuffer "github.com/vulcand/oxy/buffer"
|
||||
oxybuffer "github.com/vulcand/oxy/v2/buffer"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -34,7 +34,7 @@ func New(ctx context.Context, next http.Handler, config dynamic.Buffering, name
|
||||
oxybuffer.MaxRequestBodyBytes(config.MaxRequestBodyBytes),
|
||||
oxybuffer.MemResponseBodyBytes(config.MemResponseBodyBytes),
|
||||
oxybuffer.MaxResponseBodyBytes(config.MaxResponseBodyBytes),
|
||||
oxybuffer.CondSetter(len(config.RetryExpression) > 0, oxybuffer.Retry(config.RetryExpression)),
|
||||
oxybuffer.Cond(len(config.RetryExpression) > 0, oxybuffer.Retry(config.RetryExpression)),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -39,11 +39,14 @@ type key string
|
||||
|
||||
const capturedData key = "capturedData"
|
||||
|
||||
// Wrap returns a new handler that inserts a Capture into the given handler.
|
||||
// Wrap returns a new handler that inserts a Capture into the given handler for each incoming request.
|
||||
// It satisfies the alice.Constructor type.
|
||||
func Wrap(handler http.Handler) (http.Handler, error) {
|
||||
c := Capture{}
|
||||
return c.Reset(handler), nil
|
||||
func Wrap(next http.Handler) (http.Handler, error) {
|
||||
return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
c := &Capture{}
|
||||
newRW, newReq := c.renew(rw, req)
|
||||
next.ServeHTTP(newRW, newReq)
|
||||
}), nil
|
||||
}
|
||||
|
||||
// FromContext returns the Capture value found in ctx, or an empty Capture otherwise.
|
||||
@@ -68,6 +71,7 @@ type Capture struct {
|
||||
|
||||
// NeedsReset returns whether the given http.ResponseWriter is the capture's probe.
|
||||
func (c *Capture) NeedsReset(rw http.ResponseWriter) bool {
|
||||
// This comparison is naive.
|
||||
return c.rw != rw
|
||||
}
|
||||
|
||||
@@ -75,20 +79,25 @@ func (c *Capture) NeedsReset(rw http.ResponseWriter) bool {
|
||||
// them when deferring to next.
|
||||
func (c *Capture) Reset(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
ctx := context.WithValue(req.Context(), capturedData, c)
|
||||
newReq := req.WithContext(ctx)
|
||||
|
||||
if newReq.Body != nil {
|
||||
readCounter := &readCounter{source: newReq.Body}
|
||||
c.rr = readCounter
|
||||
newReq.Body = readCounter
|
||||
}
|
||||
c.rw = newResponseWriter(rw)
|
||||
|
||||
next.ServeHTTP(c.rw, newReq)
|
||||
newRW, newReq := c.renew(rw, req)
|
||||
next.ServeHTTP(newRW, newReq)
|
||||
})
|
||||
}
|
||||
|
||||
func (c *Capture) renew(rw http.ResponseWriter, req *http.Request) (http.ResponseWriter, *http.Request) {
|
||||
ctx := context.WithValue(req.Context(), capturedData, c)
|
||||
newReq := req.WithContext(ctx)
|
||||
|
||||
if newReq.Body != nil {
|
||||
readCounter := &readCounter{source: newReq.Body}
|
||||
c.rr = readCounter
|
||||
newReq.Body = readCounter
|
||||
}
|
||||
c.rw = newResponseWriter(rw)
|
||||
|
||||
return c.rw, newReq
|
||||
}
|
||||
|
||||
func (c *Capture) ResponseSize() int64 {
|
||||
return c.rw.Size()
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
"github.com/traefik/traefik/v2/pkg/log"
|
||||
"github.com/traefik/traefik/v2/pkg/middlewares"
|
||||
"github.com/traefik/traefik/v2/pkg/tracing"
|
||||
"github.com/vulcand/oxy/cbreaker"
|
||||
"github.com/vulcand/oxy/v2/cbreaker"
|
||||
)
|
||||
|
||||
const typeName = "CircuitBreaker"
|
||||
@@ -28,7 +28,7 @@ func New(ctx context.Context, next http.Handler, confCircuitBreaker dynamic.Circ
|
||||
logger.Debug("Creating middleware")
|
||||
logger.Debugf("Setting up with expression: %s", expression)
|
||||
|
||||
cbOpts := []cbreaker.CircuitBreakerOption{
|
||||
cbOpts := []cbreaker.Option{
|
||||
cbreaker.Fallback(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
tracing.SetErrorWithEvent(req, "blocked by circuit-breaker (%q)", expression)
|
||||
rw.WriteHeader(http.StatusServiceUnavailable)
|
||||
|
||||
@@ -16,7 +16,7 @@ import (
|
||||
"github.com/traefik/traefik/v2/pkg/middlewares"
|
||||
"github.com/traefik/traefik/v2/pkg/tracing"
|
||||
"github.com/traefik/traefik/v2/pkg/types"
|
||||
"github.com/vulcand/oxy/utils"
|
||||
"github.com/vulcand/oxy/v2/utils"
|
||||
)
|
||||
|
||||
// Compile time validation that the response recorder implements http interfaces correctly.
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/traefik/traefik/v2/pkg/testhelpers"
|
||||
"github.com/vulcand/oxy/roundrobin"
|
||||
"github.com/vulcand/oxy/v2/roundrobin"
|
||||
)
|
||||
|
||||
func TestEmptyBackendHandler(t *testing.T) {
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
|
||||
"github.com/traefik/traefik/v2/pkg/config/dynamic"
|
||||
"github.com/traefik/traefik/v2/pkg/log"
|
||||
"github.com/vulcand/oxy/utils"
|
||||
"github.com/vulcand/oxy/v2/utils"
|
||||
)
|
||||
|
||||
// GetSourceExtractor returns the SourceExtractor function corresponding to the given sourceMatcher.
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
"github.com/traefik/traefik/v2/pkg/log"
|
||||
"github.com/traefik/traefik/v2/pkg/middlewares"
|
||||
"github.com/traefik/traefik/v2/pkg/tracing"
|
||||
"github.com/vulcand/oxy/connlimit"
|
||||
"github.com/vulcand/oxy/v2/connlimit"
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
@@ -14,7 +14,7 @@ import (
|
||||
"github.com/traefik/traefik/v2/pkg/log"
|
||||
"github.com/traefik/traefik/v2/pkg/middlewares"
|
||||
"github.com/traefik/traefik/v2/pkg/tracing"
|
||||
"github.com/vulcand/oxy/utils"
|
||||
"github.com/vulcand/oxy/v2/utils"
|
||||
"golang.org/x/time/rate"
|
||||
)
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ import (
|
||||
ptypes "github.com/traefik/paerser/types"
|
||||
"github.com/traefik/traefik/v2/pkg/config/dynamic"
|
||||
"github.com/traefik/traefik/v2/pkg/testhelpers"
|
||||
"github.com/vulcand/oxy/utils"
|
||||
"github.com/vulcand/oxy/v2/utils"
|
||||
)
|
||||
|
||||
func TestNewRateLimiter(t *testing.T) {
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
|
||||
"github.com/opentracing/opentracing-go/ext"
|
||||
"github.com/traefik/traefik/v2/pkg/tracing"
|
||||
"github.com/vulcand/oxy/utils"
|
||||
"github.com/vulcand/oxy/v2/utils"
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
@@ -78,7 +78,7 @@ func NewClient(opts ClientOptions) (*Client, error) {
|
||||
}
|
||||
|
||||
return &Client{
|
||||
HTTPClient: &http.Client{Timeout: 5 * time.Second},
|
||||
HTTPClient: &http.Client{Timeout: 10 * time.Second},
|
||||
baseURL: baseURL,
|
||||
|
||||
archives: archivesPath,
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
apiVersion: traefik.containo.us/v1alpha1
|
||||
kind: IngressRoute
|
||||
metadata:
|
||||
name: test.route
|
||||
namespace: default
|
||||
|
||||
spec:
|
||||
entryPoints:
|
||||
- foo
|
||||
|
||||
routes:
|
||||
- match: Host(`foo.com`) && PathPrefix(`/bar`)
|
||||
kind: Rule
|
||||
priority: 12
|
||||
services:
|
||||
- name: test-weighted
|
||||
kind: TraefikService
|
||||
- name: test-mirror
|
||||
kind: TraefikService
|
||||
middlewares:
|
||||
- name: test-errorpage
|
||||
|
||||
---
|
||||
apiVersion: traefik.containo.us/v1alpha1
|
||||
kind: TraefikService
|
||||
metadata:
|
||||
name: test-weighted
|
||||
namespace: default
|
||||
|
||||
spec:
|
||||
weighted:
|
||||
services:
|
||||
- name: whoami-without-endpoints-subsets
|
||||
weight: 1
|
||||
port: 80
|
||||
|
||||
---
|
||||
apiVersion: traefik.containo.us/v1alpha1
|
||||
kind: TraefikService
|
||||
metadata:
|
||||
name: test-mirror
|
||||
namespace: default
|
||||
|
||||
spec:
|
||||
mirroring:
|
||||
name: whoami-without-endpoints-subsets
|
||||
port: 80
|
||||
mirrors:
|
||||
- name: whoami-without-endpoints-subsets
|
||||
port: 80
|
||||
- name: test-weighted
|
||||
kind: TraefikService
|
||||
|
||||
---
|
||||
apiVersion: traefik.containo.us/v1alpha1
|
||||
kind: Middleware
|
||||
metadata:
|
||||
name: test-errorpage
|
||||
namespace: default
|
||||
|
||||
spec:
|
||||
errors:
|
||||
service:
|
||||
name: whoami-without-endpoints-subsets
|
||||
port: 80
|
||||
@@ -292,7 +292,12 @@ func (p *Provider) loadConfigurationFromCRD(ctx context.Context, client Client)
|
||||
}
|
||||
}
|
||||
|
||||
cb := configBuilder{client: client, allowCrossNamespace: p.AllowCrossNamespace, allowExternalNameServices: p.AllowExternalNameServices}
|
||||
cb := configBuilder{
|
||||
client: client,
|
||||
allowCrossNamespace: p.AllowCrossNamespace,
|
||||
allowExternalNameServices: p.AllowExternalNameServices,
|
||||
allowEmptyServices: p.AllowEmptyServices,
|
||||
}
|
||||
|
||||
for _, service := range client.GetTraefikServices() {
|
||||
err := cb.buildTraefikService(ctx, service, conf.HTTP.Services)
|
||||
@@ -578,7 +583,14 @@ func (p *Provider) createErrorPageMiddleware(client Client, namespace string, er
|
||||
Query: errorPage.Query,
|
||||
}
|
||||
|
||||
balancerServerHTTP, err := configBuilder{client: client, allowCrossNamespace: p.AllowCrossNamespace, allowExternalNameServices: p.AllowExternalNameServices}.buildServersLB(namespace, errorPage.Service.LoadBalancerSpec)
|
||||
cb := configBuilder{
|
||||
client: client,
|
||||
allowCrossNamespace: p.AllowCrossNamespace,
|
||||
allowExternalNameServices: p.AllowExternalNameServices,
|
||||
allowEmptyServices: p.AllowEmptyServices,
|
||||
}
|
||||
|
||||
balancerServerHTTP, err := cb.buildServersLB(namespace, errorPage.Service.LoadBalancerSpec)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
@@ -4037,6 +4037,91 @@ func TestLoadIngressRoutes(t *testing.T) {
|
||||
TLS: &dynamic.TLSConfiguration{},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "TraefikService, empty service allowed",
|
||||
allowEmptyServices: true,
|
||||
paths: []string{"services.yml", "with_empty_services_ts.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-test-route-6b204d94623b3df4370c": {
|
||||
EntryPoints: []string{"foo"},
|
||||
Middlewares: []string{"default-test-errorpage"},
|
||||
Service: "default-test-route-6b204d94623b3df4370c",
|
||||
Rule: "Host(`foo.com`) && PathPrefix(`/bar`)",
|
||||
Priority: 12,
|
||||
},
|
||||
},
|
||||
Middlewares: map[string]*dynamic.Middleware{
|
||||
"default-test-errorpage": {
|
||||
Errors: &dynamic.ErrorPage{
|
||||
Service: "default-test-errorpage-errorpage-service",
|
||||
},
|
||||
},
|
||||
},
|
||||
Services: map[string]*dynamic.Service{
|
||||
"default-test-route-6b204d94623b3df4370c": {
|
||||
Weighted: &dynamic.WeightedRoundRobin{
|
||||
Services: []dynamic.WRRService{
|
||||
{
|
||||
Name: "default-test-weighted",
|
||||
Weight: func(i int) *int { return &i }(1),
|
||||
},
|
||||
{
|
||||
Name: "default-test-mirror",
|
||||
Weight: func(i int) *int { return &i }(1),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"default-test-errorpage-errorpage-service": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
PassHostHeader: Bool(true),
|
||||
},
|
||||
},
|
||||
"default-test-weighted": {
|
||||
Weighted: &dynamic.WeightedRoundRobin{
|
||||
Services: []dynamic.WRRService{
|
||||
{
|
||||
Name: "default-whoami-without-endpoints-subsets-80",
|
||||
Weight: func(i int) *int { return &i }(1),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"default-test-mirror": {
|
||||
Mirroring: &dynamic.Mirroring{
|
||||
Service: "default-whoami-without-endpoints-subsets-80",
|
||||
Mirrors: []dynamic.MirrorService{
|
||||
{
|
||||
Name: "default-whoami-without-endpoints-subsets-80",
|
||||
},
|
||||
{
|
||||
Name: "default-test-weighted",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"default-whoami-without-endpoints-subsets-80": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
PassHostHeader: Bool(true),
|
||||
},
|
||||
},
|
||||
},
|
||||
ServersTransports: map[string]*dynamic.ServersTransport{},
|
||||
},
|
||||
TLS: &dynamic.TLSConfiguration{},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
|
||||
@@ -58,7 +58,7 @@ func (p *mockProvider) Init() error {
|
||||
|
||||
func TestNewConfigurationWatcher(t *testing.T) {
|
||||
routinesPool := safe.NewPool(context.Background())
|
||||
defer routinesPool.Stop()
|
||||
t.Cleanup(routinesPool.Stop)
|
||||
|
||||
pvd := &mockProvider{
|
||||
messages: []dynamic.Message{{
|
||||
@@ -115,7 +115,6 @@ func TestNewConfigurationWatcher(t *testing.T) {
|
||||
|
||||
func TestWaitForRequiredProvider(t *testing.T) {
|
||||
routinesPool := safe.NewPool(context.Background())
|
||||
defer routinesPool.Stop()
|
||||
|
||||
pvdAggregator := &mockProvider{
|
||||
wait: 5 * time.Millisecond,
|
||||
@@ -151,7 +150,9 @@ func TestWaitForRequiredProvider(t *testing.T) {
|
||||
})
|
||||
|
||||
watcher.Start()
|
||||
defer watcher.Stop()
|
||||
|
||||
t.Cleanup(watcher.Stop)
|
||||
t.Cleanup(routinesPool.Stop)
|
||||
|
||||
// give some time so that the configuration can be processed
|
||||
time.Sleep(20 * time.Millisecond)
|
||||
@@ -162,7 +163,6 @@ func TestWaitForRequiredProvider(t *testing.T) {
|
||||
|
||||
func TestIgnoreTransientConfiguration(t *testing.T) {
|
||||
routinesPool := safe.NewPool(context.Background())
|
||||
defer routinesPool.Stop()
|
||||
|
||||
config := &dynamic.Configuration{
|
||||
HTTP: th.BuildConfiguration(
|
||||
@@ -190,7 +190,9 @@ func TestIgnoreTransientConfiguration(t *testing.T) {
|
||||
})
|
||||
|
||||
watcher.Start()
|
||||
defer watcher.Stop()
|
||||
|
||||
t.Cleanup(watcher.Stop)
|
||||
t.Cleanup(routinesPool.Stop)
|
||||
|
||||
watcher.allProvidersConfigs <- dynamic.Message{
|
||||
ProviderName: "mock",
|
||||
@@ -243,7 +245,6 @@ func TestIgnoreTransientConfiguration(t *testing.T) {
|
||||
|
||||
func TestListenProvidersThrottleProviderConfigReload(t *testing.T) {
|
||||
routinesPool := safe.NewPool(context.Background())
|
||||
defer routinesPool.Stop()
|
||||
|
||||
pvd := &mockProvider{
|
||||
wait: 10 * time.Millisecond,
|
||||
@@ -274,7 +275,9 @@ func TestListenProvidersThrottleProviderConfigReload(t *testing.T) {
|
||||
})
|
||||
|
||||
watcher.Start()
|
||||
defer watcher.Stop()
|
||||
|
||||
t.Cleanup(watcher.Stop)
|
||||
t.Cleanup(routinesPool.Stop)
|
||||
|
||||
// Give some time so that the configuration can be processed.
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
@@ -287,7 +290,6 @@ func TestListenProvidersThrottleProviderConfigReload(t *testing.T) {
|
||||
|
||||
func TestListenProvidersSkipsEmptyConfigs(t *testing.T) {
|
||||
routinesPool := safe.NewPool(context.Background())
|
||||
defer routinesPool.Stop()
|
||||
|
||||
pvd := &mockProvider{
|
||||
messages: []dynamic.Message{{ProviderName: "mock"}},
|
||||
@@ -299,7 +301,9 @@ func TestListenProvidersSkipsEmptyConfigs(t *testing.T) {
|
||||
})
|
||||
|
||||
watcher.Start()
|
||||
defer watcher.Stop()
|
||||
|
||||
t.Cleanup(watcher.Stop)
|
||||
t.Cleanup(routinesPool.Stop)
|
||||
|
||||
// give some time so that the configuration can be processed
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
@@ -307,7 +311,6 @@ func TestListenProvidersSkipsEmptyConfigs(t *testing.T) {
|
||||
|
||||
func TestListenProvidersSkipsSameConfigurationForProvider(t *testing.T) {
|
||||
routinesPool := safe.NewPool(context.Background())
|
||||
defer routinesPool.Stop()
|
||||
|
||||
message := dynamic.Message{
|
||||
ProviderName: "mock",
|
||||
@@ -331,7 +334,9 @@ func TestListenProvidersSkipsSameConfigurationForProvider(t *testing.T) {
|
||||
})
|
||||
|
||||
watcher.Start()
|
||||
defer watcher.Stop()
|
||||
|
||||
t.Cleanup(watcher.Stop)
|
||||
t.Cleanup(routinesPool.Stop)
|
||||
|
||||
// give some time so that the configuration can be processed
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
@@ -340,7 +345,6 @@ func TestListenProvidersSkipsSameConfigurationForProvider(t *testing.T) {
|
||||
|
||||
func TestListenProvidersDoesNotSkipFlappingConfiguration(t *testing.T) {
|
||||
routinesPool := safe.NewPool(context.Background())
|
||||
defer routinesPool.Stop()
|
||||
|
||||
configuration := &dynamic.Configuration{
|
||||
HTTP: th.BuildConfiguration(
|
||||
@@ -374,7 +378,9 @@ func TestListenProvidersDoesNotSkipFlappingConfiguration(t *testing.T) {
|
||||
})
|
||||
|
||||
watcher.Start()
|
||||
defer watcher.Stop()
|
||||
|
||||
t.Cleanup(watcher.Stop)
|
||||
t.Cleanup(routinesPool.Stop)
|
||||
|
||||
// give some time so that the configuration can be processed
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
@@ -407,7 +413,6 @@ func TestListenProvidersDoesNotSkipFlappingConfiguration(t *testing.T) {
|
||||
|
||||
func TestListenProvidersIgnoreSameConfig(t *testing.T) {
|
||||
routinesPool := safe.NewPool(context.Background())
|
||||
defer routinesPool.Stop()
|
||||
|
||||
configuration := &dynamic.Configuration{
|
||||
HTTP: th.BuildConfiguration(
|
||||
@@ -453,8 +458,7 @@ func TestListenProvidersIgnoreSameConfig(t *testing.T) {
|
||||
configurationReloads++
|
||||
lastConfig = conf
|
||||
|
||||
// Allows next configurations to be sent by the mock provider
|
||||
// as soon as the first configuration message is applied.
|
||||
// Allows next configurations to be sent by the mock provider as soon as the first configuration message is applied.
|
||||
once.Do(func() {
|
||||
pvd.first <- struct{}{}
|
||||
// Wait for all configuration messages to pile in
|
||||
@@ -463,7 +467,9 @@ func TestListenProvidersIgnoreSameConfig(t *testing.T) {
|
||||
})
|
||||
|
||||
watcher.Start()
|
||||
defer watcher.Stop()
|
||||
|
||||
t.Cleanup(watcher.Stop)
|
||||
t.Cleanup(routinesPool.Stop)
|
||||
|
||||
// Wait long enough
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
@@ -498,7 +504,6 @@ func TestListenProvidersIgnoreSameConfig(t *testing.T) {
|
||||
|
||||
func TestApplyConfigUnderStress(t *testing.T) {
|
||||
routinesPool := safe.NewPool(context.Background())
|
||||
defer routinesPool.Stop()
|
||||
|
||||
watcher := NewConfigurationWatcher(routinesPool, &mockProvider{}, []string{"defaultEP"}, "")
|
||||
|
||||
@@ -525,15 +530,16 @@ func TestApplyConfigUnderStress(t *testing.T) {
|
||||
})
|
||||
|
||||
watcher.Start()
|
||||
defer watcher.Stop()
|
||||
|
||||
t.Cleanup(watcher.Stop)
|
||||
t.Cleanup(routinesPool.Stop)
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
// Ensure that at least two configurations have been applied
|
||||
// if we simulate being spammed configuration changes by the
|
||||
// provider(s).
|
||||
// In theory, checking at least one would be sufficient, but
|
||||
// checking for two also ensures that we're looping properly,
|
||||
// if we simulate being spammed configuration changes by the provider(s).
|
||||
// In theory, checking at least one would be sufficient,
|
||||
// but checking for two also ensures that we're looping properly,
|
||||
// and that the whole algo holds, etc.
|
||||
t.Log(configurationReloads)
|
||||
assert.GreaterOrEqual(t, configurationReloads, 2)
|
||||
@@ -541,7 +547,6 @@ func TestApplyConfigUnderStress(t *testing.T) {
|
||||
|
||||
func TestListenProvidersIgnoreIntermediateConfigs(t *testing.T) {
|
||||
routinesPool := safe.NewPool(context.Background())
|
||||
defer routinesPool.Stop()
|
||||
|
||||
configuration := &dynamic.Configuration{
|
||||
HTTP: th.BuildConfiguration(
|
||||
@@ -596,7 +601,9 @@ func TestListenProvidersIgnoreIntermediateConfigs(t *testing.T) {
|
||||
})
|
||||
|
||||
watcher.Start()
|
||||
defer watcher.Stop()
|
||||
|
||||
t.Cleanup(watcher.Stop)
|
||||
t.Cleanup(routinesPool.Stop)
|
||||
|
||||
// Wait long enough
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
@@ -631,7 +638,6 @@ func TestListenProvidersIgnoreIntermediateConfigs(t *testing.T) {
|
||||
|
||||
func TestListenProvidersPublishesConfigForEachProvider(t *testing.T) {
|
||||
routinesPool := safe.NewPool(context.Background())
|
||||
defer routinesPool.Stop()
|
||||
|
||||
configuration := &dynamic.Configuration{
|
||||
HTTP: th.BuildConfiguration(
|
||||
@@ -656,7 +662,9 @@ func TestListenProvidersPublishesConfigForEachProvider(t *testing.T) {
|
||||
})
|
||||
|
||||
watcher.Start()
|
||||
defer watcher.Stop()
|
||||
|
||||
t.Cleanup(watcher.Stop)
|
||||
t.Cleanup(routinesPool.Stop)
|
||||
|
||||
// give some time so that the configuration can be processed
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
@@ -695,7 +703,6 @@ func TestListenProvidersPublishesConfigForEachProvider(t *testing.T) {
|
||||
|
||||
func TestPublishConfigUpdatedByProvider(t *testing.T) {
|
||||
routinesPool := safe.NewPool(context.Background())
|
||||
defer routinesPool.Stop()
|
||||
|
||||
pvdConfiguration := dynamic.Configuration{
|
||||
TCP: &dynamic.TCPConfiguration{
|
||||
@@ -725,12 +732,14 @@ func TestPublishConfigUpdatedByProvider(t *testing.T) {
|
||||
watcher.AddListener(func(configuration dynamic.Configuration) {
|
||||
publishedConfigCount++
|
||||
|
||||
// Update the provider configuration published in next dynamic Message which should trigger a new publish.
|
||||
// Update the provider configuration published in next dynamic Message which should trigger a new publishing.
|
||||
pvdConfiguration.TCP.Routers["bar"] = &dynamic.TCPRouter{}
|
||||
})
|
||||
|
||||
watcher.Start()
|
||||
defer watcher.Stop()
|
||||
|
||||
t.Cleanup(watcher.Stop)
|
||||
t.Cleanup(routinesPool.Stop)
|
||||
|
||||
// give some time so that the configuration can be processed.
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
@@ -740,7 +749,6 @@ func TestPublishConfigUpdatedByProvider(t *testing.T) {
|
||||
|
||||
func TestPublishConfigUpdatedByConfigWatcherListener(t *testing.T) {
|
||||
routinesPool := safe.NewPool(context.Background())
|
||||
defer routinesPool.Stop()
|
||||
|
||||
pvd := &mockProvider{
|
||||
wait: 10 * time.Millisecond,
|
||||
@@ -774,13 +782,15 @@ func TestPublishConfigUpdatedByConfigWatcherListener(t *testing.T) {
|
||||
watcher.AddListener(func(configuration dynamic.Configuration) {
|
||||
publishedConfigCount++
|
||||
|
||||
// Modify the provided configuration. This should not modify the configuration stored in the configuration
|
||||
// watcher and cause a new publish.
|
||||
// Modify the provided configuration.
|
||||
// This should not modify the configuration stored in the configuration watcher and therefore there will be no new publishing.
|
||||
configuration.TCP.Routers["foo@mock"].Rule = "bar"
|
||||
})
|
||||
|
||||
watcher.Start()
|
||||
defer watcher.Stop()
|
||||
|
||||
t.Cleanup(watcher.Stop)
|
||||
t.Cleanup(routinesPool.Stop)
|
||||
|
||||
// give some time so that the configuration can be processed.
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
@@ -3,6 +3,7 @@ package router
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/containous/alice"
|
||||
@@ -16,6 +17,7 @@ import (
|
||||
httpmuxer "github.com/traefik/traefik/v2/pkg/muxer/http"
|
||||
"github.com/traefik/traefik/v2/pkg/server/middleware"
|
||||
"github.com/traefik/traefik/v2/pkg/server/provider"
|
||||
"github.com/traefik/traefik/v2/pkg/tls"
|
||||
)
|
||||
|
||||
type middlewareBuilder interface {
|
||||
@@ -35,10 +37,11 @@ type Manager struct {
|
||||
middlewaresBuilder middlewareBuilder
|
||||
chainBuilder *middleware.ChainBuilder
|
||||
conf *runtime.Configuration
|
||||
tlsManager *tls.Manager
|
||||
}
|
||||
|
||||
// NewManager Creates a new Manager.
|
||||
func NewManager(conf *runtime.Configuration, serviceManager serviceManager, middlewaresBuilder middlewareBuilder, chainBuilder *middleware.ChainBuilder, metricsRegistry metrics.Registry) *Manager {
|
||||
// NewManager creates a new Manager.
|
||||
func NewManager(conf *runtime.Configuration, serviceManager serviceManager, middlewaresBuilder middlewareBuilder, chainBuilder *middleware.ChainBuilder, metricsRegistry metrics.Registry, tlsManager *tls.Manager) *Manager {
|
||||
return &Manager{
|
||||
routerHandlers: make(map[string]http.Handler),
|
||||
serviceManager: serviceManager,
|
||||
@@ -46,6 +49,7 @@ func NewManager(conf *runtime.Configuration, serviceManager serviceManager, midd
|
||||
middlewaresBuilder: middlewaresBuilder,
|
||||
chainBuilder: chainBuilder,
|
||||
conf: conf,
|
||||
tlsManager: tlsManager,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,6 +145,17 @@ func (m *Manager) buildRouterHandler(ctx context.Context, routerName string, rou
|
||||
return handler, nil
|
||||
}
|
||||
|
||||
if routerConfig.TLS != nil {
|
||||
// Don't build the router if the TLSOptions configuration is invalid.
|
||||
tlsOptionsName := tls.DefaultTLSConfigName
|
||||
if len(routerConfig.TLS.Options) > 0 && routerConfig.TLS.Options != tls.DefaultTLSConfigName {
|
||||
tlsOptionsName = provider.GetQualifiedName(ctx, routerConfig.TLS.Options)
|
||||
}
|
||||
if _, err := m.tlsManager.Get(tls.DefaultTLSStoreName, tlsOptionsName); err != nil {
|
||||
return nil, fmt.Errorf("building router handler: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
handler, err := m.buildHTTPHandler(ctx, routerConfig, routerName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
"github.com/traefik/traefik/v2/pkg/server/middleware"
|
||||
"github.com/traefik/traefik/v2/pkg/server/service"
|
||||
"github.com/traefik/traefik/v2/pkg/testhelpers"
|
||||
"github.com/traefik/traefik/v2/pkg/tls"
|
||||
"github.com/traefik/traefik/v2/pkg/types"
|
||||
)
|
||||
|
||||
@@ -317,8 +318,9 @@ func TestRouterManager_Get(t *testing.T) {
|
||||
serviceManager := service.NewManager(rtConf.Services, nil, nil, roundTripperManager)
|
||||
middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager, nil)
|
||||
chainBuilder := middleware.NewChainBuilder(nil, nil, nil)
|
||||
tlsManager := tls.NewManager()
|
||||
|
||||
routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, chainBuilder, metrics.NewVoidRegistry())
|
||||
routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, chainBuilder, metrics.NewVoidRegistry(), tlsManager)
|
||||
|
||||
handlers := routerManager.BuildHandlers(context.Background(), test.entryPoints, false)
|
||||
|
||||
@@ -423,8 +425,9 @@ func TestAccessLog(t *testing.T) {
|
||||
serviceManager := service.NewManager(rtConf.Services, nil, nil, roundTripperManager)
|
||||
middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager, nil)
|
||||
chainBuilder := middleware.NewChainBuilder(nil, nil, nil)
|
||||
tlsManager := tls.NewManager()
|
||||
|
||||
routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, chainBuilder, metrics.NewVoidRegistry())
|
||||
routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, chainBuilder, metrics.NewVoidRegistry(), tlsManager)
|
||||
|
||||
handlers := routerManager.BuildHandlers(context.Background(), test.entryPoints, false)
|
||||
|
||||
@@ -462,6 +465,7 @@ func TestRuntimeConfiguration(t *testing.T) {
|
||||
serviceConfig map[string]*dynamic.Service
|
||||
routerConfig map[string]*dynamic.Router
|
||||
middlewareConfig map[string]*dynamic.Middleware
|
||||
tlsOptions map[string]tls.Options
|
||||
expectedError int
|
||||
}{
|
||||
{
|
||||
@@ -665,7 +669,6 @@ func TestRuntimeConfiguration(t *testing.T) {
|
||||
},
|
||||
expectedError: 1,
|
||||
},
|
||||
|
||||
{
|
||||
desc: "Router with broken middleware",
|
||||
serviceConfig: map[string]*dynamic.Service{
|
||||
@@ -696,8 +699,71 @@ func TestRuntimeConfiguration(t *testing.T) {
|
||||
},
|
||||
expectedError: 2,
|
||||
},
|
||||
{
|
||||
desc: "Router with broken tlsOption",
|
||||
serviceConfig: map[string]*dynamic.Service{
|
||||
"foo-service": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://127.0.0.1",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
middlewareConfig: map[string]*dynamic.Middleware{},
|
||||
routerConfig: map[string]*dynamic.Router{
|
||||
"bar": {
|
||||
EntryPoints: []string{"web"},
|
||||
Service: "foo-service",
|
||||
Rule: "Host(`foo.bar`)",
|
||||
TLS: &dynamic.RouterTLSConfig{
|
||||
Options: "broken-tlsOption",
|
||||
},
|
||||
},
|
||||
},
|
||||
tlsOptions: map[string]tls.Options{
|
||||
"broken-tlsOption": {
|
||||
ClientAuth: tls.ClientAuth{
|
||||
ClientAuthType: "foobar",
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedError: 1,
|
||||
},
|
||||
{
|
||||
desc: "Router with broken default tlsOption",
|
||||
serviceConfig: map[string]*dynamic.Service{
|
||||
"foo-service": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://127.0.0.1",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
middlewareConfig: map[string]*dynamic.Middleware{},
|
||||
routerConfig: map[string]*dynamic.Router{
|
||||
"bar": {
|
||||
EntryPoints: []string{"web"},
|
||||
Service: "foo-service",
|
||||
Rule: "Host(`foo.bar`)",
|
||||
TLS: &dynamic.RouterTLSConfig{},
|
||||
},
|
||||
},
|
||||
tlsOptions: map[string]tls.Options{
|
||||
"default": {
|
||||
ClientAuth: tls.ClientAuth{
|
||||
ClientAuthType: "foobar",
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedError: 1,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
@@ -711,6 +777,9 @@ func TestRuntimeConfiguration(t *testing.T) {
|
||||
Routers: test.routerConfig,
|
||||
Middlewares: test.middlewareConfig,
|
||||
},
|
||||
TLS: &dynamic.TLSConfiguration{
|
||||
Options: test.tlsOptions,
|
||||
},
|
||||
})
|
||||
|
||||
roundTripperManager := service.NewRoundTripperManager()
|
||||
@@ -718,10 +787,13 @@ func TestRuntimeConfiguration(t *testing.T) {
|
||||
serviceManager := service.NewManager(rtConf.Services, nil, nil, roundTripperManager)
|
||||
middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager, nil)
|
||||
chainBuilder := middleware.NewChainBuilder(nil, nil, nil)
|
||||
tlsManager := tls.NewManager()
|
||||
tlsManager.UpdateConfigs(context.Background(), nil, test.tlsOptions, nil)
|
||||
|
||||
routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, chainBuilder, metrics.NewVoidRegistry())
|
||||
routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, chainBuilder, metrics.NewVoidRegistry(), tlsManager)
|
||||
|
||||
_ = routerManager.BuildHandlers(context.Background(), entryPoints, false)
|
||||
_ = routerManager.BuildHandlers(context.Background(), entryPoints, true)
|
||||
|
||||
// even though rtConf was passed by argument to the manager builders above,
|
||||
// it's ok to use it as the result we check, because everything worth checking
|
||||
@@ -793,8 +865,9 @@ func TestProviderOnMiddlewares(t *testing.T) {
|
||||
serviceManager := service.NewManager(rtConf.Services, nil, nil, roundTripperManager)
|
||||
middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager, nil)
|
||||
chainBuilder := middleware.NewChainBuilder(nil, nil, nil)
|
||||
tlsManager := tls.NewManager()
|
||||
|
||||
routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, chainBuilder, metrics.NewVoidRegistry())
|
||||
routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, chainBuilder, metrics.NewVoidRegistry(), tlsManager)
|
||||
|
||||
_ = routerManager.BuildHandlers(context.Background(), entryPoints, false)
|
||||
|
||||
@@ -861,8 +934,9 @@ func BenchmarkRouterServe(b *testing.B) {
|
||||
serviceManager := service.NewManager(rtConf.Services, nil, nil, staticRoundTripperGetter{res})
|
||||
middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager, nil)
|
||||
chainBuilder := middleware.NewChainBuilder(nil, nil, nil)
|
||||
tlsManager := tls.NewManager()
|
||||
|
||||
routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, chainBuilder, metrics.NewVoidRegistry())
|
||||
routerManager := NewManager(rtConf, serviceManager, middlewaresBuilder, chainBuilder, metrics.NewVoidRegistry(), tlsManager)
|
||||
|
||||
handlers := routerManager.BuildHandlers(context.Background(), entryPoints, false)
|
||||
|
||||
|
||||
@@ -103,18 +103,21 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string
|
||||
|
||||
router.SetHTTPHandler(handlerHTTP)
|
||||
|
||||
// Even though the error is seemingly ignored (aside from logging it),
|
||||
// we actually rely later on the fact that a tls config is nil (which happens when an error is returned) to take special steps
|
||||
// when assigning a handler to a route.
|
||||
defaultTLSConf, err := m.tlsManager.Get(traefiktls.DefaultTLSStoreName, traefiktls.DefaultTLSConfigName)
|
||||
if err != nil {
|
||||
log.FromContext(ctx).Errorf("Error during the build of the default TLS configuration: %v", err)
|
||||
}
|
||||
|
||||
// Keyed by domain. The source of truth for doing SNI checking, and for what TLS
|
||||
// options will actually be used for the connection.
|
||||
// Keyed by domain. The source of truth for doing SNI checking (domain fronting).
|
||||
// As soon as there's (at least) two different tlsOptions found for the same domain,
|
||||
// we set the value to the default TLS conf.
|
||||
tlsOptionsForHost := map[string]string{}
|
||||
|
||||
// Keyed by domain, then by options reference.
|
||||
// The actual source of truth for what TLS options will actually be used for the connection.
|
||||
// As opposed to tlsOptionsForHost, it keeps track of all the (different) TLS
|
||||
// options that occur for a given host name, so that later on we can set relevant
|
||||
// errors and logging for all the routers concerned (i.e. wrongly configured).
|
||||
@@ -142,21 +145,20 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string
|
||||
}
|
||||
|
||||
if len(domains) == 0 {
|
||||
// Extra Host(*) rule, for HTTPS routers with no Host rule, and for requests for
|
||||
// which the SNI does not match _any_ of the other existing routers Host. This is
|
||||
// only about choosing the TLS configuration. The actual routing will be done
|
||||
// further on by the HTTPS handler. See examples below.
|
||||
// Extra Host(*) rule, for HTTPS routers with no Host rule,
|
||||
// and for requests for which the SNI does not match _any_ of the other existing routers Host.
|
||||
// This is only about choosing the TLS configuration.
|
||||
// The actual routing will be done further on by the HTTPS handler.
|
||||
// See examples below.
|
||||
router.AddHTTPTLSConfig("*", defaultTLSConf)
|
||||
|
||||
// The server name (from a Host(SNI) rule) is the only parameter (available in
|
||||
// HTTP routing rules) on which we can map a TLS config, because it is the only one
|
||||
// accessible before decryption (we obtain it during the ClientHello). Therefore,
|
||||
// when a router has no Host rule, it does not make any sense to specify some TLS
|
||||
// options. Consequently, when it comes to deciding what TLS config will be used,
|
||||
// for a request that will match an HTTPS router with no Host rule, the result will
|
||||
// depend on the _others_ existing routers (their Host rule, to be precise), and
|
||||
// the TLS options associated with them, even though they don't match the incoming
|
||||
// request. Consider the following examples:
|
||||
// The server name (from a Host(SNI) rule) is the only parameter (available in HTTP routing rules) on which we can map a TLS config,
|
||||
// because it is the only one accessible before decryption (we obtain it during the ClientHello).
|
||||
// Therefore, when a router has no Host rule, it does not make any sense to specify some TLS options.
|
||||
// Consequently, when it comes to deciding what TLS config will be used,
|
||||
// for a request that will match an HTTPS router with no Host rule,
|
||||
// the result will depend on the _others_ existing routers (their Host rule, to be precise), and the TLS options associated with them,
|
||||
// even though they don't match the incoming request. Consider the following examples:
|
||||
|
||||
// # conf1
|
||||
// httpRouter1:
|
||||
@@ -170,17 +172,19 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string
|
||||
// httpRouter2:
|
||||
// rule: Host("foo.com") && PathPrefix("/bar")
|
||||
// tlsoptions: myTLSOptions
|
||||
// # When a request for "/foo" comes, even though it won't be routed by
|
||||
// httpRouter2, if its SNI is set to foo.com, myTLSOptions will be used for the TLS
|
||||
// connection. Otherwise, it will fallback to the default TLS config.
|
||||
// # When a request for "/foo" comes, even though it won't be routed by httpRouter2,
|
||||
// # if its SNI is set to foo.com, myTLSOptions will be used for the TLS connection.
|
||||
// # Otherwise, it will fallback to the default TLS config.
|
||||
logger.Warnf("No domain found in rule %v, the TLS options applied for this router will depend on the SNI of each request", routerHTTPConfig.Rule)
|
||||
}
|
||||
|
||||
tlsConf, err := m.tlsManager.Get(traefiktls.DefaultTLSStoreName, tlsOptionsName)
|
||||
if err != nil {
|
||||
routerHTTPConfig.AddError(err, true)
|
||||
logger.Error(err)
|
||||
continue
|
||||
// Even though the error is seemingly ignored (aside from logging it),
|
||||
// we actually rely later on the fact that a tls config is nil (which happens when an error is returned) to take special steps
|
||||
// when assigning a handler to a route.
|
||||
tlsConf, tlsConfErr := m.tlsManager.Get(traefiktls.DefaultTLSStoreName, tlsOptionsName)
|
||||
if tlsConfErr != nil {
|
||||
// Note: we do not call AddError here because we already did so when buildRouterHandler errored for the same reason.
|
||||
logger.Error(tlsConfErr)
|
||||
}
|
||||
|
||||
for _, domain := range domains {
|
||||
@@ -204,6 +208,7 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string
|
||||
|
||||
sniCheck := snicheck.New(tlsOptionsForHost, handlerHTTPS)
|
||||
|
||||
// Keep in mind that defaultTLSConf might be nil here.
|
||||
router.SetHTTPSHandler(sniCheck, defaultTLSConf)
|
||||
|
||||
logger := log.FromContext(ctx)
|
||||
@@ -217,22 +222,42 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string
|
||||
break
|
||||
}
|
||||
|
||||
logger.Debugf("Adding route for %s with TLS options %s", hostSNI, optionsName)
|
||||
|
||||
router.AddHTTPTLSConfig(hostSNI, config)
|
||||
} else {
|
||||
routers := make([]string, 0, len(tlsConfigs))
|
||||
for _, v := range tlsConfigs {
|
||||
configsHTTP[v.routerName].AddError(fmt.Errorf("found different TLS options for routers on the same host %v, so using the default TLS options instead", hostSNI), false)
|
||||
routers = append(routers, v.routerName)
|
||||
if config == nil {
|
||||
// we use nil config as a signal to insert a handler
|
||||
// that enforces that TLS connection attempts to the corresponding (broken) router should fail.
|
||||
logger.Debugf("Adding special closing route for %s because broken TLS options %s", hostSNI, optionsName)
|
||||
router.AddHTTPTLSConfig(hostSNI, nil)
|
||||
continue
|
||||
}
|
||||
|
||||
logger.Warnf("Found different TLS options for routers on the same host %v, so using the default TLS options instead for these routers: %#v", hostSNI, routers)
|
||||
|
||||
router.AddHTTPTLSConfig(hostSNI, defaultTLSConf)
|
||||
logger.Debugf("Adding route for %s with TLS options %s", hostSNI, optionsName)
|
||||
router.AddHTTPTLSConfig(hostSNI, config)
|
||||
continue
|
||||
}
|
||||
|
||||
// multiple tlsConfigs
|
||||
|
||||
routers := make([]string, 0, len(tlsConfigs))
|
||||
for _, v := range tlsConfigs {
|
||||
configsHTTP[v.routerName].AddError(fmt.Errorf("found different TLS options for routers on the same host %v, so using the default TLS options instead", hostSNI), false)
|
||||
routers = append(routers, v.routerName)
|
||||
}
|
||||
|
||||
logger.Warnf("Found different TLS options for routers on the same host %v, so using the default TLS options instead for these routers: %#v", hostSNI, routers)
|
||||
if defaultTLSConf == nil {
|
||||
logger.Debugf("Adding special closing route for %s because broken default TLS options", hostSNI)
|
||||
}
|
||||
|
||||
router.AddHTTPTLSConfig(hostSNI, defaultTLSConf)
|
||||
}
|
||||
|
||||
m.addTCPHandlers(ctx, configs, router)
|
||||
|
||||
return router, nil
|
||||
}
|
||||
|
||||
// addTCPHandlers creates the TCP handlers defined in configs, and adds them to router.
|
||||
func (m *Manager) addTCPHandlers(ctx context.Context, configs map[string]*runtime.TCPRouterInfo, router *Router) {
|
||||
for routerName, routerConfig := range configs {
|
||||
ctxRouter := log.With(provider.AddInContext(ctx, routerName), log.Str(log.RouterName, routerName))
|
||||
logger := log.FromContext(ctxRouter)
|
||||
@@ -251,13 +276,6 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string
|
||||
continue
|
||||
}
|
||||
|
||||
handler, err := m.buildTCPHandler(ctxRouter, routerConfig)
|
||||
if err != nil {
|
||||
routerConfig.AddError(err, true)
|
||||
logger.Error(err)
|
||||
continue
|
||||
}
|
||||
|
||||
domains, err := tcpmuxer.ParseHostSNI(routerConfig.Rule)
|
||||
if err != nil {
|
||||
routerErr := fmt.Errorf("invalid rule: %q , %w", routerConfig.Rule, err)
|
||||
@@ -274,6 +292,16 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string
|
||||
logger.Error(routerErr)
|
||||
}
|
||||
|
||||
var handler tcp.Handler
|
||||
if routerConfig.TLS == nil || routerConfig.TLS.Passthrough {
|
||||
handler, err = m.buildTCPHandler(ctxRouter, routerConfig)
|
||||
if err != nil {
|
||||
routerConfig.AddError(err, true)
|
||||
logger.Error(err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if routerConfig.TLS == nil {
|
||||
logger.Debugf("Adding route for %q", routerConfig.Rule)
|
||||
if err := router.AddRoute(routerConfig.Rule, routerConfig.Priority, handler); err != nil {
|
||||
@@ -285,7 +313,7 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string
|
||||
|
||||
if routerConfig.TLS.Passthrough {
|
||||
logger.Debugf("Adding Passthrough route for %q", routerConfig.Rule)
|
||||
if err := router.AddRouteTLS(routerConfig.Rule, routerConfig.Priority, handler, nil); err != nil {
|
||||
if err := router.muxerTCPTLS.AddRoute(routerConfig.Rule, routerConfig.Priority, handler); err != nil {
|
||||
routerConfig.AddError(err, true)
|
||||
logger.Error(err)
|
||||
}
|
||||
@@ -315,7 +343,15 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string
|
||||
tlsConf, err := m.tlsManager.Get(traefiktls.DefaultTLSStoreName, tlsOptionsName)
|
||||
if err != nil {
|
||||
routerConfig.AddError(err, true)
|
||||
|
||||
logger.Error(err)
|
||||
logger.Debugf("Adding special TLS closing route for %q because broken TLS options %s", routerConfig.Rule, tlsOptionsName)
|
||||
|
||||
err = router.muxerTCPTLS.AddRoute(routerConfig.Rule, routerConfig.Priority, &brokenTLSRouter{})
|
||||
if err != nil {
|
||||
routerConfig.AddError(err, true)
|
||||
logger.Error(err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -327,20 +363,30 @@ func (m *Manager) buildEntryPointHandler(ctx context.Context, configs map[string
|
||||
// rule: HostSNI(foo.com) && ClientIP(IP2)
|
||||
// tlsOption: tlsTwo
|
||||
// i.e. same HostSNI but different tlsOptions
|
||||
// This is only applicable if the muxer can decide about the routing _before_
|
||||
// telling the client about the tlsConf (i.e. before the TLS HandShake). This seems
|
||||
// to be the case so far with the existing matchers (HostSNI, and ClientIP), so
|
||||
// it's all good. Otherwise, we would have to do as for HTTPS, i.e. disallow
|
||||
// different TLS configs for the same HostSNIs.
|
||||
// This is only applicable if the muxer can decide about the routing _before_ telling the client about the tlsConf (i.e. before the TLS HandShake).
|
||||
// This seems to be the case so far with the existing matchers (HostSNI, and ClientIP), so it's all good.
|
||||
// Otherwise, we would have to do as for HTTPS, i.e. disallow different TLS configs for the same HostSNIs.
|
||||
|
||||
handler, err = m.buildTCPHandler(ctxRouter, routerConfig)
|
||||
if err != nil {
|
||||
routerConfig.AddError(err, true)
|
||||
logger.Error(err)
|
||||
continue
|
||||
}
|
||||
|
||||
handler = &tcp.TLSHandler{
|
||||
Next: handler,
|
||||
Config: tlsConf,
|
||||
}
|
||||
|
||||
logger.Debugf("Adding TLS route for %q", routerConfig.Rule)
|
||||
if err := router.AddRouteTLS(routerConfig.Rule, routerConfig.Priority, handler, tlsConf); err != nil {
|
||||
|
||||
err = router.muxerTCPTLS.AddRoute(routerConfig.Rule, routerConfig.Priority, handler)
|
||||
if err != nil {
|
||||
routerConfig.AddError(err, true)
|
||||
logger.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
return router, nil
|
||||
}
|
||||
|
||||
func (m *Manager) buildTCPHandler(ctx context.Context, router *runtime.TCPRouterInfo) (tcp.Handler, error) {
|
||||
|
||||
@@ -27,19 +27,20 @@ type Router struct {
|
||||
muxerHTTPS tcpmuxer.Muxer
|
||||
|
||||
// Forwarder handlers.
|
||||
// Handles all HTTP requests.
|
||||
// httpForwarder handles all HTTP requests.
|
||||
httpForwarder tcp.Handler
|
||||
// Handles (indirectly through muxerHTTPS, or directly) all HTTPS requests.
|
||||
// httpsForwarder handles (indirectly through muxerHTTPS, or directly) all HTTPS requests.
|
||||
httpsForwarder tcp.Handler
|
||||
|
||||
// Neither is used directly, but they are held here, and recreated on config
|
||||
// reload, so that they can be passed to the Switcher at the end of the config
|
||||
// reload phase.
|
||||
// Neither is used directly, but they are held here, and recreated on config reload,
|
||||
// so that they can be passed to the Switcher at the end of the config reload phase.
|
||||
httpHandler http.Handler
|
||||
httpsHandler http.Handler
|
||||
|
||||
// TLS configs.
|
||||
httpsTLSConfig *tls.Config // default TLS config
|
||||
httpsTLSConfig *tls.Config // default TLS config
|
||||
// hostHTTPTLSConfig contains TLS configs keyed by SNI.
|
||||
// A nil config is the hint to set up a brokenTLSRouter.
|
||||
hostHTTPTLSConfig map[string]*tls.Config // TLS configs keyed by SNI
|
||||
}
|
||||
|
||||
@@ -80,11 +81,11 @@ func (r *Router) GetTLSGetClientInfo() func(info *tls.ClientHelloInfo) (*tls.Con
|
||||
|
||||
// ServeTCP forwards the connection to the right TCP/HTTP handler.
|
||||
func (r *Router) ServeTCP(conn tcp.WriteCloser) {
|
||||
// Handling Non-TLS TCP connection early if there is neither HTTP(S) nor TLS
|
||||
// routers on the entryPoint, and if there is at least one non-TLS TCP router.
|
||||
// In the case of a non-TLS TCP client (that does not "send" first), we would
|
||||
// block forever on clientHelloInfo, which is why we want to detect and
|
||||
// handle that case first and foremost.
|
||||
// Handling Non-TLS TCP connection early if there is neither HTTP(S) nor TLS routers on the entryPoint,
|
||||
// and if there is at least one non-TLS TCP router.
|
||||
// In the case of a non-TLS TCP client (that does not "send" first),
|
||||
// we would block forever on clientHelloInfo,
|
||||
// which is why we want to detect and handle that case first and foremost.
|
||||
if r.muxerTCP.HasRoutes() && !r.muxerTCPTLS.HasRoutes() && !r.muxerHTTPS.HasRoutes() {
|
||||
connData, err := tcpmuxer.NewConnData("", conn, nil)
|
||||
if err != nil {
|
||||
@@ -152,9 +153,9 @@ func (r *Router) ServeTCP(conn tcp.WriteCloser) {
|
||||
// (wrapped inside the returned handler) requested for the given HostSNI.
|
||||
handlerHTTPS, catchAllHTTPS := r.muxerHTTPS.Match(connData)
|
||||
if handlerHTTPS != nil && !catchAllHTTPS {
|
||||
// In order not to depart from the behavior in 2.6, we only allow an HTTPS router
|
||||
// to take precedence over a TCP-TLS router if it is _not_ an HostSNI(*) router (so
|
||||
// basically any router that has a specific HostSNI based rule).
|
||||
// In order not to depart from the behavior in 2.6,
|
||||
// we only allow an HTTPS router to take precedence over a TCP-TLS router if it is _not_ an HostSNI(*) router
|
||||
// (so basically any router that has a specific HostSNI based rule).
|
||||
handlerHTTPS.ServeTCP(r.GetConn(conn, hello.peeked))
|
||||
return
|
||||
}
|
||||
@@ -180,7 +181,7 @@ func (r *Router) ServeTCP(conn tcp.WriteCloser) {
|
||||
return
|
||||
}
|
||||
|
||||
// needed to handle 404s for HTTPS, as well as all non-Host (e.g. PathPrefix) matches.
|
||||
// To handle 404s for HTTPS.
|
||||
if r.httpsForwarder != nil {
|
||||
r.httpsForwarder.ServeTCP(r.GetConn(conn, hello.peeked))
|
||||
return
|
||||
@@ -194,19 +195,6 @@ func (r *Router) AddRoute(rule string, priority int, target tcp.Handler) error {
|
||||
return r.muxerTCP.AddRoute(rule, priority, target)
|
||||
}
|
||||
|
||||
// AddRouteTLS defines a handler for a given rule and sets the matching tlsConfig.
|
||||
func (r *Router) AddRouteTLS(rule string, priority int, target tcp.Handler, config *tls.Config) error {
|
||||
// TLS PassThrough
|
||||
if config == nil {
|
||||
return r.muxerTCPTLS.AddRoute(rule, priority, target)
|
||||
}
|
||||
|
||||
return r.muxerTCPTLS.AddRoute(rule, priority, &tcp.TLSHandler{
|
||||
Next: target,
|
||||
Config: config,
|
||||
})
|
||||
}
|
||||
|
||||
// AddHTTPTLSConfig defines a handler for a given sniHost and sets the matching tlsConfig.
|
||||
func (r *Router) AddHTTPTLSConfig(sniHost string, config *tls.Config) {
|
||||
if r.hostHTTPTLSConfig == nil {
|
||||
@@ -242,20 +230,44 @@ func (r *Router) SetHTTPForwarder(handler tcp.Handler) {
|
||||
r.httpForwarder = handler
|
||||
}
|
||||
|
||||
// SetHTTPSForwarder sets the tcp handler that will forward the TLS connections to an http handler.
|
||||
// brokenTLSRouter is associated to a Host(SNI) rule for which we know the TLS conf is broken.
|
||||
// It is used to make sure any attempt to connect to that hostname is closed,
|
||||
// since we cannot proceed with the intended TLS conf.
|
||||
type brokenTLSRouter struct{}
|
||||
|
||||
// ServeTCP instantly closes the connection.
|
||||
func (t *brokenTLSRouter) ServeTCP(conn tcp.WriteCloser) {
|
||||
_ = conn.Close()
|
||||
}
|
||||
|
||||
// SetHTTPSForwarder sets the tcp handler that will forward the TLS connections to an HTTP handler.
|
||||
// It also sets up each TLS handler (with its TLS config) for each Host(SNI) rule we previously kept track of.
|
||||
// It sets up a special handler that closes the connection if a TLS config is nil.
|
||||
func (r *Router) SetHTTPSForwarder(handler tcp.Handler) {
|
||||
for sniHost, tlsConf := range r.hostHTTPTLSConfig {
|
||||
var tcpHandler tcp.Handler
|
||||
if tlsConf == nil {
|
||||
tcpHandler = &brokenTLSRouter{}
|
||||
} else {
|
||||
tcpHandler = &tcp.TLSHandler{
|
||||
Next: handler,
|
||||
Config: tlsConf,
|
||||
}
|
||||
}
|
||||
|
||||
// muxerHTTPS only contains single HostSNI rules (and no other kind of rules),
|
||||
// so there's no need for specifying a priority for them.
|
||||
err := r.muxerHTTPS.AddRoute("HostSNI(`"+sniHost+"`)", 0, &tcp.TLSHandler{
|
||||
Next: handler,
|
||||
Config: tlsConf,
|
||||
})
|
||||
err := r.muxerHTTPS.AddRoute("HostSNI(`"+sniHost+"`)", 0, tcpHandler)
|
||||
if err != nil {
|
||||
log.WithoutContext().Errorf("Error while adding route for host: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
if r.httpsTLSConfig == nil {
|
||||
r.httpsForwarder = &brokenTLSRouter{}
|
||||
return
|
||||
}
|
||||
|
||||
r.httpsForwarder = &tcp.TLSHandler{
|
||||
Next: handler,
|
||||
Config: r.httpsTLSConfig,
|
||||
@@ -275,15 +287,14 @@ func (r *Router) SetHTTPSHandler(handler http.Handler, config *tls.Config) {
|
||||
|
||||
// Conn is a connection proxy that handles Peeked bytes.
|
||||
type Conn struct {
|
||||
// Peeked are the bytes that have been read from Conn for the
|
||||
// purposes of route matching, but have not yet been consumed
|
||||
// by Read calls. It set to nil by Read when fully consumed.
|
||||
// Peeked are the bytes that have been read from Conn for the purposes of route matching,
|
||||
// but have not yet been consumed by Read calls.
|
||||
// It set to nil by Read when fully consumed.
|
||||
Peeked []byte
|
||||
|
||||
// Conn is the underlying connection.
|
||||
// It can be type asserted against *net.TCPConn or other types
|
||||
// as needed. It should not be read from directly unless
|
||||
// Peeked is nil.
|
||||
// It can be type asserted against *net.TCPConn or other types as needed.
|
||||
// It should not be read from directly unless Peeked is nil.
|
||||
tcp.WriteCloser
|
||||
}
|
||||
|
||||
@@ -320,15 +331,14 @@ func clientHelloInfo(br *bufio.Reader) (*clientHello, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// No valid TLS record has a type of 0x80, however SSLv2 handshakes
|
||||
// start with a uint16 length where the MSB is set and the first record
|
||||
// is always < 256 bytes long. Therefore typ == 0x80 strongly suggests
|
||||
// an SSLv2 client.
|
||||
// No valid TLS record has a type of 0x80, however SSLv2 handshakes start with an uint16 length
|
||||
// where the MSB is set and the first record is always < 256 bytes long.
|
||||
// Therefore, typ == 0x80 strongly suggests an SSLv2 client.
|
||||
const recordTypeSSLv2 = 0x80
|
||||
const recordTypeHandshake = 0x16
|
||||
if hdr[0] != recordTypeHandshake {
|
||||
if hdr[0] == recordTypeSSLv2 {
|
||||
// we consider SSLv2 as TLS and it will be refused by real TLS handshake.
|
||||
// we consider SSLv2 as TLS, and it will be refused by real TLS handshake.
|
||||
return &clientHello{
|
||||
isTLS: true,
|
||||
peeked: getPeeked(br),
|
||||
|
||||
@@ -72,7 +72,7 @@ func (f *RouterFactory) CreateRouters(rtConf *runtime.Configuration) (map[string
|
||||
|
||||
middlewaresBuilder := middleware.NewBuilder(rtConf.Middlewares, serviceManager, f.pluginBuilder)
|
||||
|
||||
routerManager := router.NewManager(rtConf, serviceManager, middlewaresBuilder, f.chainBuilder, f.metricsRegistry)
|
||||
routerManager := router.NewManager(rtConf, serviceManager, middlewaresBuilder, f.chainBuilder, f.metricsRegistry, f.tlsManager)
|
||||
|
||||
handlersNonTLS := routerManager.BuildHandlers(ctx, f.entryPointsTCP, false)
|
||||
handlersTLS := routerManager.BuildHandlers(ctx, f.entryPointsTCP, true)
|
||||
|
||||
@@ -48,18 +48,13 @@ func TestShutdownTCP(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
err = router.AddRoute("HostSNI(`*`)", 0, tcp.HandlerFunc(func(conn tcp.WriteCloser) {
|
||||
for {
|
||||
_, err := http.ReadRequest(bufio.NewReader(conn))
|
||||
|
||||
if errors.Is(err, io.EOF) || (err != nil && errors.Is(err, net.ErrClosed)) {
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
resp := http.Response{StatusCode: http.StatusOK}
|
||||
err = resp.Write(conn)
|
||||
require.NoError(t, err)
|
||||
_, err := http.ReadRequest(bufio.NewReader(conn))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
resp := http.Response{StatusCode: http.StatusOK}
|
||||
_ = resp.Write(conn)
|
||||
}))
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -89,6 +84,7 @@ func testShutdown(t *testing.T, router *tcprouter.Router) {
|
||||
|
||||
conn, err := startEntrypoint(entryPoint, router)
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() { _ = conn.Close() })
|
||||
|
||||
epAddr := entryPoint.listener.Addr().String()
|
||||
|
||||
@@ -97,14 +93,14 @@ func testShutdown(t *testing.T, router *tcprouter.Router) {
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
// We need to do a write on the conn before the shutdown to make it "exist".
|
||||
// We need to do a write on conn before the shutdown to make it "exist".
|
||||
// Because the connection indeed exists as far as TCP is concerned,
|
||||
// but since we only pass it along to the HTTP server after at least one byte is peeked,
|
||||
// the HTTP server (and hence its shutdown) does not know about the connection until that first byte peeked.
|
||||
err = request.Write(conn)
|
||||
require.NoError(t, err)
|
||||
|
||||
reader := bufio.NewReader(conn)
|
||||
reader := bufio.NewReaderSize(conn, 1)
|
||||
// Wait for first byte in response.
|
||||
_, err = reader.Peek(1)
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -32,16 +32,19 @@ func TestShutdownUDPConn(t *testing.T) {
|
||||
for {
|
||||
b := make([]byte, 1024*1024)
|
||||
n, err := conn.Read(b)
|
||||
require.NoError(t, err)
|
||||
// We control the termination, otherwise we would block on the Read above, until
|
||||
// conn is closed by a timeout. Which means we would get an error, and even though
|
||||
// we are in a goroutine and the current test might be over, go test would still
|
||||
// yell at us if this happens while other tests are still running.
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// We control the termination, otherwise we would block on the Read above,
|
||||
// until conn is closed by a timeout.
|
||||
// Which means we would get an error,
|
||||
// and even though we are in a goroutine and the current test might be over,
|
||||
// go test would still yell at us if this happens while other tests are still running.
|
||||
if string(b[:n]) == "CLOSE" {
|
||||
return
|
||||
}
|
||||
_, err = conn.Write(b[:n])
|
||||
require.NoError(t, err)
|
||||
_, _ = conn.Write(b[:n])
|
||||
}
|
||||
}))
|
||||
|
||||
@@ -68,9 +71,9 @@ func TestShutdownUDPConn(t *testing.T) {
|
||||
// Packet is accepted, but dropped
|
||||
require.NoError(t, err)
|
||||
|
||||
// Make sure that our session is yet again still live. This is specifically to
|
||||
// make sure we don't create a regression in listener's readLoop, i.e. that we only
|
||||
// terminate the listener's readLoop goroutine by closing its pConn.
|
||||
// Make sure that our session is yet again still live.
|
||||
// This is specifically to make sure we don't create a regression in listener's readLoop,
|
||||
// i.e. that we only terminate the listener's readLoop goroutine by closing its pConn.
|
||||
requireEcho(t, "TEST3", conn, time.Second)
|
||||
|
||||
done := make(chan bool)
|
||||
@@ -101,10 +104,11 @@ func TestShutdownUDPConn(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// requireEcho tests that the conn session is live and functional, by writing
|
||||
// data through it, and expecting the same data as a response when reading on it.
|
||||
// It fatals if the read blocks longer than timeout, which is useful to detect
|
||||
// regressions that would make a test wait forever.
|
||||
// requireEcho tests that conn session is live and functional,
|
||||
// by writing data through it,
|
||||
// and expecting the same data as a response when reading on it.
|
||||
// It fatals if the read blocks longer than timeout,
|
||||
// which is useful to detect regressions that would make a test wait forever.
|
||||
func requireEcho(t *testing.T, data string, conn io.ReadWriter, timeout time.Duration) {
|
||||
t.Helper()
|
||||
|
||||
|
||||
@@ -27,8 +27,8 @@ import (
|
||||
"github.com/traefik/traefik/v2/pkg/server/service/loadbalancer/failover"
|
||||
"github.com/traefik/traefik/v2/pkg/server/service/loadbalancer/mirror"
|
||||
"github.com/traefik/traefik/v2/pkg/server/service/loadbalancer/wrr"
|
||||
"github.com/vulcand/oxy/roundrobin"
|
||||
"github.com/vulcand/oxy/roundrobin/stickycookie"
|
||||
"github.com/vulcand/oxy/v2/roundrobin"
|
||||
"github.com/vulcand/oxy/v2/roundrobin/stickycookie"
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
@@ -157,19 +157,16 @@ func (m *Manager) Get(storeName, configName string) (*tls.Config, error) {
|
||||
m.lock.RLock()
|
||||
defer m.lock.RUnlock()
|
||||
|
||||
var tlsConfig *tls.Config
|
||||
var err error
|
||||
|
||||
sniStrict := false
|
||||
config, ok := m.configs[configName]
|
||||
if ok {
|
||||
sniStrict = config.SniStrict
|
||||
tlsConfig, err = buildTLSConfig(config)
|
||||
} else {
|
||||
err = fmt.Errorf("unknown TLS options: %s", configName)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unknown TLS options: %s", configName)
|
||||
}
|
||||
|
||||
sniStrict = config.SniStrict
|
||||
tlsConfig, err := buildTLSConfig(config)
|
||||
if err != nil {
|
||||
tlsConfig = &tls.Config{}
|
||||
return nil, fmt.Errorf("building TLS config: %w", err)
|
||||
}
|
||||
|
||||
store := m.getStore(storeName)
|
||||
@@ -177,7 +174,7 @@ func (m *Manager) Get(storeName, configName string) (*tls.Config, error) {
|
||||
err = fmt.Errorf("TLS store %s not found", storeName)
|
||||
}
|
||||
acmeTLSStore := m.getStore(tlsalpn01.ACMETLS1Protocol)
|
||||
if acmeTLSStore == nil {
|
||||
if acmeTLSStore == nil && err == nil {
|
||||
err = fmt.Errorf("ACME TLS store %s not found", tlsalpn01.ACMETLS1Protocol)
|
||||
}
|
||||
|
||||
@@ -188,15 +185,12 @@ func (m *Manager) Get(storeName, configName string) (*tls.Config, error) {
|
||||
certificate := acmeTLSStore.GetBestCertificate(clientHello)
|
||||
if certificate == nil {
|
||||
log.WithoutContext().Debugf("TLS: no certificate for TLSALPN challenge: %s", domainToCheck)
|
||||
// We want the user to eventually get the (alertUnrecognizedName) "unrecognized
|
||||
// name" error.
|
||||
// Unfortunately, if we returned an error here, since we can't use
|
||||
// the unexported error (errNoCertificates) that our caller (config.getCertificate
|
||||
// in crypto/tls) uses as a sentinel, it would report an (alertInternalError)
|
||||
// "internal error" instead of an alertUnrecognizedName.
|
||||
// Which is why we return no error, and we let the caller detect that there's
|
||||
// actually no certificate, and fall back into the flow that will report
|
||||
// the desired error.
|
||||
// We want the user to eventually get the (alertUnrecognizedName) "unrecognized name" error.
|
||||
// Unfortunately, if we returned an error here,
|
||||
// since we can't use the unexported error (errNoCertificates) that our caller (config.getCertificate in crypto/tls) uses as a sentinel,
|
||||
// it would report an (alertInternalError) "internal error" instead of an alertUnrecognizedName.
|
||||
// Which is why we return no error, and we let the caller detect that there's actually no certificate,
|
||||
// and fall back into the flow that will report the desired error.
|
||||
// https://cs.opensource.google/go/go/+/dev.boringcrypto.go1.17:src/crypto/tls/common.go;l=1058
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
@@ -119,8 +119,9 @@ func TestManager_Get(t *testing.T) {
|
||||
}}
|
||||
|
||||
tlsConfigs := map[string]Options{
|
||||
"foo": {MinVersion: "VersionTLS12"},
|
||||
"bar": {MinVersion: "VersionTLS11"},
|
||||
"foo": {MinVersion: "VersionTLS12"},
|
||||
"bar": {MinVersion: "VersionTLS11"},
|
||||
"invalid": {CurvePreferences: []string{"42"}},
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
@@ -140,15 +141,20 @@ func TestManager_Get(t *testing.T) {
|
||||
expectedMinVersion: uint16(tls.VersionTLS11),
|
||||
},
|
||||
{
|
||||
desc: "Get an tls config from an invalid name",
|
||||
desc: "Get a tls config from an invalid name",
|
||||
tlsOptionsName: "unknown",
|
||||
expectedError: true,
|
||||
},
|
||||
{
|
||||
desc: "Get an tls config from unexisting 'default' name",
|
||||
desc: "Get a tls config from unexisting 'default' name",
|
||||
tlsOptionsName: "default",
|
||||
expectedError: true,
|
||||
},
|
||||
{
|
||||
desc: "Get an invalid tls config",
|
||||
tlsOptionsName: "invalid",
|
||||
expectedError: true,
|
||||
},
|
||||
}
|
||||
|
||||
tlsManager := NewManager()
|
||||
@@ -161,42 +167,13 @@ func TestManager_Get(t *testing.T) {
|
||||
|
||||
config, err := tlsManager.Get("default", test.tlsOptionsName)
|
||||
if test.expectedError {
|
||||
assert.Error(t, err)
|
||||
require.Nil(t, config)
|
||||
require.Error(t, err)
|
||||
return
|
||||
}
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, config.MinVersion, test.expectedMinVersion)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestManager_Get_GetCertificate(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
expectedGetConfigErr require.ErrorAssertionFunc
|
||||
expectedCertificate assert.ValueAssertionFunc
|
||||
}{
|
||||
{
|
||||
desc: "Get a default certificate from non-existing store",
|
||||
expectedGetConfigErr: require.Error,
|
||||
expectedCertificate: assert.Nil,
|
||||
},
|
||||
}
|
||||
|
||||
tlsManager := NewManager()
|
||||
|
||||
for _, test := range testCases {
|
||||
test := test
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
config, err := tlsManager.Get("default", "foo")
|
||||
test.expectedGetConfigErr(t, err)
|
||||
|
||||
certificate, err := config.GetCertificate(&tls.ClientHelloInfo{})
|
||||
require.NoError(t, err)
|
||||
test.expectedCertificate(t, certificate)
|
||||
assert.Equal(t, config.MinVersion, test.expectedMinVersion)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,11 +4,11 @@ RepositoryName = "traefik"
|
||||
OutputType = "file"
|
||||
FileName = "traefik_changelog.md"
|
||||
|
||||
# example new bugfix v2.9.2
|
||||
# example new bugfix v2.9.6
|
||||
CurrentRef = "v2.9"
|
||||
PreviousRef = "v2.9.1"
|
||||
PreviousRef = "v2.9.5"
|
||||
BaseBranch = "v2.9"
|
||||
FutureCurrentRefName = "v2.9.2"
|
||||
FutureCurrentRefName = "v2.9.6"
|
||||
|
||||
ThresholdPreviousRef = 10
|
||||
ThresholdCurrentRef = 10
|
||||
|
||||
@@ -831,10 +831,16 @@
|
||||
<div class="text-subtitle2">Not Before</div>
|
||||
<boolean-state :value="!!exData(middleware).info.notBefore"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row items-start no-wrap">
|
||||
<div class="col">
|
||||
<div class="text-subtitle2">Sans</div>
|
||||
<boolean-state :value="!!exData(middleware).info.sans"/>
|
||||
</div>
|
||||
<div class="col">
|
||||
<div class="text-subtitle2">Serial Number</div>
|
||||
<boolean-state :value="!!exData(middleware).info.serialNumber"/>
|
||||
</div>
|
||||
</div>
|
||||
</q-card-section>
|
||||
<!-- EXTRA FIELDS FROM MIDDLEWARES - [passTLSClientCert] - info - subject -->
|
||||
|
||||
Reference in New Issue
Block a user