Compare commits

...

25 Commits

Author SHA1 Message Date
Tom Moulard
d97d3a6726 Prepare release v2.9.6 2022-12-07 15:14:05 +01:00
Ludovic Fernandez
a8df674dcf fix: flaky tests 2022-12-07 10:56:05 +01:00
Ludovic Fernandez
abd569701f fix: update golang.org/x/net 2022-12-07 10:02:04 +01:00
mpl
7e3fe48b80 Handle broken TLS conf better
Co-authored-by: Jean-Baptiste Doumenjou <925513+jbdoumenjou@users.noreply.github.com>
Co-authored-by: Romain <rtribotte@users.noreply.github.com>
2022-12-06 18:28:05 +01:00
Ludovic Fernandez
778188ed34 fix: remove logs of the request 2022-12-05 11:30:05 +01:00
Nicolas Mengin
88603810a8 Add information about the Hub Agent 2022-12-01 14:30:06 +01:00
mloiseleur
c7647b4938 doc: Update Helm installation section 2022-12-01 10:10:05 +01:00
Janik
af71443b61 Added networking example 2022-11-30 15:04:05 +01:00
Ludovic Fernandez
18d66d7432 Update go-acme/lego to v4.9.1 2022-11-28 08:48:04 +01:00
Romain
7c72780820 Add missing serialNumber passTLSClientCert option to middleware panel 2022-11-24 12:30:05 +01:00
Kevin Pollet
68e8eb2435 Update k3s image to rancher/k3s:v1.20.15-k3s1 2022-11-23 17:28:04 +01:00
Ludovic Fernandez
81a5b1b4c8 Increase the timeout on plugin download 2022-11-22 18:30:05 +01:00
Romain
52e6ce95cf Update DataDog tracing dependency to v1.43.1 2022-11-22 15:12:06 +01:00
Jérôme Guiard
d547718fdd Support of allowEmptyServices in TraefikService 2022-11-22 10:18:04 +01:00
mpl
af4e74c39d doc: clarify PathPrefix greediness 2022-11-21 17:30:06 +01:00
Romain
f6b7940b76 Prepare release v2.9.5 (#9513) 2022-11-17 15:57:23 +01:00
Simon Delicata
f1b91a119d Create a new capture instance for each incoming request
Co-authored-by: Romain <rtribotte@users.noreply.github.com>
2022-11-17 10:26:06 +01:00
sven
35d8281f4d docs(contributing): enhance wording of building-testing page 2022-11-15 19:34:04 +01:00
sven
00de5c711a docs(contributing): add link descriptions and update wording 2022-11-15 10:28:07 +01:00
Charlie Haley
b935c80dbd docs: update helm repository 2022-11-14 16:04:16 +01:00
tfny
22c6630412 Removes the experimental tag on the Traefik Hub header 2022-11-09 00:12:05 +01:00
Kevin Pollet
b2c4221429 Update vulcand/oxy to v1.4.2 2022-11-07 10:28:08 +01:00
Ludovic Fernandez
97de552e06 chore: update github.com/opencontainers/runc 2022-11-03 16:28:05 +01:00
Fernandez Ludovic
454f552691 Prepare release v2.9.4 2022-10-27 20:40:05 +02:00
Fernandez Ludovic
7258048403 Prepare release v2.9.3 2022-10-27 17:50:54 +02:00
52 changed files with 928 additions and 582 deletions

View File

@@ -22,22 +22,23 @@ builds:
- openbsd
goarch:
- amd64
- 386
- '386'
- arm
- arm64
- ppc64le
- s390x
goarm:
- 7
- 6
- 5
- '7'
- '6'
ignore:
- goos: darwin
goarch: 386
goarch: '386'
- goos: openbsd
goarch: arm
- goos: openbsd
goarch: arm64
- goos: freebsd
goarch: arm
- goos: freebsd
goarch: arm64
- goos: windows

View File

@@ -1,5 +1,36 @@
## [v2.9.2](https://github.com/traefik/traefik/tree/v2.9.2) (2022-10-27)
[All Commits](https://github.com/traefik/traefik/compare/v2.9.1...v2.9.2)
## [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)
**Bug fixes:**
- **[acme]** Update go-acme/lego to v4.9.0 ([#9413](https://github.com/traefik/traefik/pull/9413) by [tony-defa](https://github.com/tony-defa))
@@ -14,6 +45,16 @@
- Simplify dashboard rule example ([#9454](https://github.com/traefik/traefik/pull/9454) by [sosoba](https://github.com/sosoba))
- Add v2.9 to release page ([#9438](https://github.com/traefik/traefik/pull/9438) by [kevinpollet](https://github.com/kevinpollet))
## [v2.9.3](https://github.com/traefik/traefik/tree/v2.9.3) (2022-10-27)
[All Commits](https://github.com/traefik/traefik/compare/v2.9.1...v2.9.3)
Release canceled.
## [v2.9.2](https://github.com/traefik/traefik/tree/v2.9.2) (2022-10-27)
[All Commits](https://github.com/traefik/traefik/compare/v2.9.1...v2.9.2)
Release canceled.
## [v2.9.1](https://github.com/traefik/traefik/tree/v2.9.1) (2022-10-03)
[All Commits](https://github.com/traefik/traefik/compare/v2.9.0-rc1...v2.9.1)

View File

@@ -189,7 +189,7 @@ generate-genconf:
.PHONY: release-packages
release-packages: generate-webui build-dev-image
rm -rf dist
$(if $(IN_DOCKER),$(DOCKER_RUN_TRAEFIK_NOTTY)) goreleaser release --skip-publish --timeout="90m"
$(if $(IN_DOCKER),$(DOCKER_RUN_TRAEFIK_NOTTY)) goreleaser release --skip-publish -p 4 --timeout="90m"
$(if $(IN_DOCKER),$(DOCKER_RUN_TRAEFIK_NOTTY)) tar cfz dist/traefik-${VERSION}.src.tar.gz \
--exclude-vcs \
--exclude .idea \

View File

@@ -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() {

View File

@@ -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`

View File

@@ -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

View File

@@ -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`:

View File

@@ -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

View File

@@ -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"

View File

@@ -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)"

View File

@@ -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
View File

@@ -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

319
go.sum

File diff suppressed because it is too large Load Diff

View 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"

View File

@@ -0,0 +1,15 @@
[global]
checkNewVersion = false
sendAnonymousUsage = false
[log]
level = "DEBUG"
[api]
insecure = true
[providers.docker]
[entryPoints]
[entryPoints.webHost]
address = ":8000"

View File

@@ -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"

View File

@@ -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)
}
}

View File

@@ -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

View File

@@ -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()
}
}

View File

@@ -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) {

View File

@@ -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 (

View File

@@ -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 (

View File

@@ -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.

View File

@@ -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 (

View File

@@ -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) {

View File

@@ -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

View File

@@ -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()
}

View File

@@ -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)

View File

@@ -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.

View File

@@ -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) {

View File

@@ -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.

View File

@@ -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 (

View File

@@ -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"
)

View File

@@ -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) {

View File

@@ -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 (

View File

@@ -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,

View File

@@ -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

View File

@@ -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
}

View File

@@ -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 {

View File

@@ -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)

View File

@@ -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

View File

@@ -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)

View File

@@ -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) {

View File

@@ -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),

View File

@@ -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)

View File

@@ -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)

View File

@@ -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()

View File

@@ -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 (

View File

@@ -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
}

View File

@@ -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)
})
}
}

View File

@@ -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

View File

@@ -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 -->