forked from Ivasoft/traefik
Compare commits
25 Commits
v3.0.0-bet
...
v2.9
| Author | SHA1 | Date | |
|---|---|---|---|
| 2f1d7e806d | |||
| 00de525eae | |||
| 8844a68a74 | |||
| 56d58752ed | |||
| dfb69146f5 | |||
| b34837b2c6 | |||
| 91d3832c73 | |||
|
|
e50bf21a84 | ||
|
|
d66875f903 | ||
|
|
b995a11d63 | ||
|
|
f01a668d53 | ||
|
|
e82976e001 | ||
|
|
c9e9e8dee2 | ||
|
|
0861c47e54 | ||
|
|
e1e86763e3 | ||
|
|
b9a175f5c2 | ||
|
|
943811fad6 | ||
|
|
29b8b6911e | ||
|
|
e7baf44a2e | ||
|
|
74ef79ea23 | ||
|
|
da93dab828 | ||
|
|
d97d3a6726 | ||
|
|
a8df674dcf | ||
|
|
abd569701f | ||
|
|
7e3fe48b80 |
17
.drone.yml
Normal file
17
.drone.yml
Normal file
@@ -0,0 +1,17 @@
|
||||
kind: pipeline
|
||||
name: default
|
||||
|
||||
steps:
|
||||
- name: docker
|
||||
image: plugins/docker
|
||||
settings:
|
||||
dockerfile: exp.Dockerfile
|
||||
registry: https://git.ivasoft.cz
|
||||
username:
|
||||
from_secret: repo_user
|
||||
password:
|
||||
from_secret: repo_pass
|
||||
repo: git.ivasoft.cz/sw/traefik
|
||||
tags:
|
||||
- latest
|
||||
- ${DRONE_TAG:-latest}
|
||||
2
.github/workflows/documentation.yml
vendored
2
.github/workflows/documentation.yml
vendored
@@ -7,7 +7,7 @@ on:
|
||||
- v*
|
||||
|
||||
env:
|
||||
STRUCTOR_VERSION: v1.11.2
|
||||
STRUCTOR_VERSION: v1.12.0
|
||||
MIXTUS_VERSION: v0.4.1
|
||||
|
||||
jobs:
|
||||
|
||||
19
CHANGELOG.md
19
CHANGELOG.md
@@ -1,3 +1,22 @@
|
||||
## [v2.9.6](https://github.com/traefik/traefik/tree/v2.9.6) (2022-12-07)
|
||||
[All Commits](https://github.com/traefik/traefik/compare/v2.9.5...v2.9.6)
|
||||
|
||||
**Bug fixes:**
|
||||
- **[acme]** Update go-acme/lego to v4.9.1 ([#9550](https://github.com/traefik/traefik/pull/9550) by [ldez](https://github.com/ldez))
|
||||
- **[k8s/crd]** Support of allowEmptyServices in TraefikService ([#9424](https://github.com/traefik/traefik/pull/9424) by [jeromeguiard](https://github.com/jeromeguiard))
|
||||
- **[logs]** Remove logs of the request ([#9574](https://github.com/traefik/traefik/pull/9574) by [ldez](https://github.com/ldez))
|
||||
- **[plugins]** Increase the timeout on plugin download ([#9529](https://github.com/traefik/traefik/pull/9529) by [ldez](https://github.com/ldez))
|
||||
- **[server]** Update golang.org/x/net ([#9582](https://github.com/traefik/traefik/pull/9582) by [ldez](https://github.com/ldez))
|
||||
- **[tls]** Handle broken TLS conf better ([#9572](https://github.com/traefik/traefik/pull/9572) by [mpl](https://github.com/mpl))
|
||||
- **[tracing]** Update DataDog tracing dependency to v1.43.1 ([#9526](https://github.com/traefik/traefik/pull/9526) by [rtribotte](https://github.com/rtribotte))
|
||||
- **[webui]** Add missing serialNumber passTLSClientCert option to middleware panel ([#9539](https://github.com/traefik/traefik/pull/9539) by [rtribotte](https://github.com/rtribotte))
|
||||
|
||||
**Documentation:**
|
||||
- **[docker]** Add networking example ([#9542](https://github.com/traefik/traefik/pull/9542) by [Janik-Haag](https://github.com/Janik-Haag))
|
||||
- **[hub]** Add information about the Hub Agent ([#9560](https://github.com/traefik/traefik/pull/9560) by [nmengin](https://github.com/nmengin))
|
||||
- **[k8s/helm]** Update Helm installation section ([#9564](https://github.com/traefik/traefik/pull/9564) by [mloiseleur](https://github.com/mloiseleur))
|
||||
- **[middleware]** Clarify PathPrefix matcher greediness ([#9519](https://github.com/traefik/traefik/pull/9519) by [mpl](https://github.com/mpl))
|
||||
|
||||
## [v2.9.5](https://github.com/traefik/traefik/tree/v2.9.5) (2022-11-17)
|
||||
[All Commits](https://github.com/traefik/traefik/compare/v2.9.4...v2.9.5)
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016-2020 Containous SAS; 2020-2022 Traefik Labs
|
||||
Copyright (c) 2016-2020 Containous SAS; 2020-2023 Traefik Labs
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -56,6 +56,7 @@ Merging a PR requires the following steps to be completed before it is merged au
|
||||
* Do not open the PR from an organization repository.
|
||||
* Keep "allows edit from maintainer" checked.
|
||||
* Use semantic line breaks for documentation.
|
||||
* Ensure your PR is not a draft. We do not review drafts, but do answer questions and confer with developers on them as needed.
|
||||
* Pass the validation check.
|
||||
* Pass all tests.
|
||||
* Receive 3 approving reviews maintainers.
|
||||
|
||||
@@ -11,7 +11,11 @@ Automatic HTTPS
|
||||
You can configure Traefik to use an ACME provider (like Let's Encrypt) for automatic certificate generation.
|
||||
|
||||
!!! warning "Let's Encrypt and Rate Limiting"
|
||||
Note that Let's Encrypt API has [rate limiting](https://letsencrypt.org/docs/rate-limits).
|
||||
Note that Let's Encrypt API has [rate limiting](https://letsencrypt.org/docs/rate-limits). These last up to __one week__, and can not be overridden.
|
||||
|
||||
When running Traefik in a container this file should be persisted across restarts.
|
||||
If Traefik requests new certificates each time it starts up, a crash-looping container can quickly reach Let's Encrypt's ratelimits.
|
||||
To configure where certificates are stored, please take a look at the [storage](#storage) configuration.
|
||||
|
||||
Use Let's Encrypt staging server with the [`caServer`](#caserver) configuration option
|
||||
when experimenting to avoid hitting this limit too fast.
|
||||
|
||||
@@ -95,7 +95,7 @@ and [Docker Swarm Mode](https://docs.docker.com/engine/swarm/).
|
||||
## Routing Configuration
|
||||
|
||||
When using Docker as a [provider](./overview.md),
|
||||
Traefik uses [container labels](https://docs.docker.com/engine/reference/commandline/run/#set-metadata-on-container--l---label---label-file) to retrieve its routing configuration.
|
||||
Traefik uses [container labels](https://docs.docker.com/engine/reference/commandline/run/#-set-metadata-on-container--l---label---label-file) to retrieve its routing configuration.
|
||||
|
||||
See the list of labels in the dedicated [routing](../routing/providers/docker.md) section.
|
||||
|
||||
|
||||
@@ -1609,14 +1609,14 @@ or referencing TLS options in the [`IngressRoute`](#kind-ingressroute) / [`Ingre
|
||||
|
||||
`TLSStore` is the CRD implementation of a [Traefik "TLS Store"](../../https/tls.md#certificates-stores).
|
||||
|
||||
Register the `TLSStore` kind in the Kubernetes cluster before creating `TLSStore` objects
|
||||
or referencing TLS stores in the [`IngressRoute`](#kind-ingressroute) / [`IngressRouteTCP`](#kind-ingressroutetcp) objects.
|
||||
Register the `TLSStore` kind in the Kubernetes cluster before creating `TLSStore` objects.
|
||||
|
||||
!!! important "Default TLS Store"
|
||||
|
||||
Traefik currently only uses the [TLS Store named "default"](../../https/tls.md#certificates-stores).
|
||||
This _default_ `TLSStore` should be in a namespace discoverable by Traefik. Since it is used by default on [`IngressRoute`](#kind-ingressroute) and [`IngressRouteTCP`](#kind-ingressroutetcp) objects, there never is a need to actually reference it.
|
||||
This means that you cannot have two stores that are named default in different Kubernetes namespaces.
|
||||
For the time being, please only configure one TLSStore named default.
|
||||
As a consequence, with respect to TLS stores, the only change that makes sense (and only if needed) is to configure the default TLSStore.
|
||||
|
||||
!!! info "TLSStore Attributes"
|
||||
```yaml tab="TLSStore"
|
||||
@@ -1624,7 +1624,7 @@ or referencing TLS stores in the [`IngressRoute`](#kind-ingressroute) / [`Ingres
|
||||
kind: TLSStore
|
||||
metadata:
|
||||
name: default
|
||||
namespace: default
|
||||
|
||||
spec:
|
||||
certificates: # [1]
|
||||
- secretName: foo
|
||||
@@ -1645,8 +1645,7 @@ or referencing TLS stores in the [`IngressRoute`](#kind-ingressroute) / [`Ingres
|
||||
kind: TLSStore
|
||||
metadata:
|
||||
name: default
|
||||
namespace: default
|
||||
|
||||
|
||||
spec:
|
||||
defaultCertificate:
|
||||
secretName: supersecret
|
||||
@@ -1660,16 +1659,14 @@ or referencing TLS stores in the [`IngressRoute`](#kind-ingressroute) / [`Ingres
|
||||
|
||||
spec:
|
||||
entryPoints:
|
||||
- web
|
||||
- websecure
|
||||
routes:
|
||||
- match: Host(`example.com`) && PathPrefix(`/stripit`)
|
||||
kind: Rule
|
||||
services:
|
||||
- name: whoami
|
||||
port: 80
|
||||
tls:
|
||||
store:
|
||||
name: default
|
||||
tls: {}
|
||||
```
|
||||
|
||||
```yaml tab="Secret"
|
||||
|
||||
@@ -888,14 +888,20 @@ TLS certificates can be managed in Secrets objects.
|
||||
|
||||
### Communication Between Traefik and Pods
|
||||
|
||||
!!! info "It is not possible to route requests directly to [Kubernetes services](https://kubernetes.io/docs/concepts/services-networking/service/ "Link to Kubernetes service docs")"
|
||||
|
||||
You can use an `ExternalName` service to forward requests to the Kubernetes service through DNS.
|
||||
|
||||
For doing so, you have to [allow external name services](https://doc.traefik.io/traefik/providers/kubernetes-ingress/#allowexternalnameservices "Link to docs about allowing external name services").
|
||||
|
||||
Traefik automatically requests endpoint information based on the service provided in the ingress spec.
|
||||
Although Traefik will connect directly to the endpoints (pods),
|
||||
it still checks the service port to see if TLS communication is required.
|
||||
|
||||
There are 3 ways to configure Traefik to use https to communicate with pods:
|
||||
There are 3 ways to configure Traefik to use HTTPS to communicate with pods:
|
||||
|
||||
1. If the service port defined in the ingress spec is `443` (note that you can still use `targetPort` to use a different port on your pod).
|
||||
1. If the service port defined in the ingress spec has a name that starts with https (such as `https-api`, `https-web` or just `https`).
|
||||
1. If the service port defined in the ingress spec has a name that starts with `https` (such as `https-api`, `https-web` or just `https`).
|
||||
1. If the service spec includes the annotation `traefik.ingress.kubernetes.io/service.serversscheme: https`.
|
||||
|
||||
If either of those configuration options exist, then the backend communication protocol is assumed to be TLS,
|
||||
|
||||
@@ -27,7 +27,7 @@ theme:
|
||||
prev: 'Previous'
|
||||
next: 'Next'
|
||||
|
||||
copyright: "Copyright © 2016-2020 Containous; 2020-2022 Traefik Labs"
|
||||
copyright: 'Traefik Labs • Copyright © 2016-2023'
|
||||
|
||||
extra_javascript:
|
||||
- assets/js/hljs/highlight.pack.js # Download from https://highlightjs.org/download/ and enable YAML, TOML and Dockerfile
|
||||
|
||||
18
go.mod
18
go.mod
@@ -5,7 +5,7 @@ go 1.19
|
||||
require (
|
||||
github.com/BurntSushi/toml v1.2.1
|
||||
github.com/ExpediaDotCom/haystack-client-go v0.0.0-20190315171017-e7edbdf53a61
|
||||
github.com/Masterminds/sprig/v3 v3.2.2
|
||||
github.com/Masterminds/sprig/v3 v3.2.3
|
||||
github.com/abbot/go-http-auth v0.0.0-00010101000000-000000000000
|
||||
github.com/aws/aws-sdk-go v1.44.47
|
||||
github.com/cenkalti/backoff/v4 v4.1.3
|
||||
@@ -59,7 +59,7 @@ require (
|
||||
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/paerser v0.2.0
|
||||
github.com/traefik/yaegi v0.14.3
|
||||
github.com/uber/jaeger-client-go v2.30.0+incompatible
|
||||
github.com/uber/jaeger-lib v2.2.0+incompatible
|
||||
@@ -71,8 +71,8 @@ require (
|
||||
go.elastic.co/apm v1.13.1
|
||||
go.elastic.co/apm/module/apmot v1.13.1
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4
|
||||
golang.org/x/net v0.1.0
|
||||
golang.org/x/text v0.4.0
|
||||
golang.org/x/net v0.3.1-0.20221206200815-1e63c2f08a10
|
||||
golang.org/x/text v0.5.0
|
||||
golang.org/x/time v0.0.0-20220224211638-0e9765cccd65
|
||||
golang.org/x/tools v0.1.12
|
||||
google.golang.org/grpc v1.41.0
|
||||
@@ -109,7 +109,7 @@ require (
|
||||
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
|
||||
github.com/Masterminds/semver/v3 v3.2.0 // indirect
|
||||
github.com/Microsoft/go-winio v0.5.1 // indirect
|
||||
github.com/Microsoft/hcsshim v0.8.23 // indirect
|
||||
github.com/OpenDNS/vegadns2client v0.0.0-20180418235048-a3fa4a771d87 // indirect
|
||||
@@ -206,7 +206,7 @@ require (
|
||||
github.com/hashicorp/raft-autopilot v0.1.5 // indirect
|
||||
github.com/hashicorp/serf v0.9.7 // indirect
|
||||
github.com/hashicorp/yamux v0.0.0-20210826001029-26ff87cf9493 // indirect
|
||||
github.com/huandu/xstrings v1.3.2 // indirect
|
||||
github.com/huandu/xstrings v1.3.3 // indirect
|
||||
github.com/iij/doapi v0.0.0-20190504054126-0bbf12d6d7df // indirect
|
||||
github.com/imdario/mergo v0.3.12 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.0.0 // indirect
|
||||
@@ -321,12 +321,12 @@ require (
|
||||
go.uber.org/zap v1.18.1 // indirect
|
||||
go4.org/intern v0.0.0-20211027215823-ae77deb06f29 // indirect
|
||||
go4.org/unsafe/assume-no-moving-gc v0.0.0-20220617031537-928513b29760 // indirect
|
||||
golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f // indirect
|
||||
golang.org/x/crypto v0.3.0 // indirect
|
||||
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 // indirect
|
||||
golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1 // indirect
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 // indirect
|
||||
golang.org/x/sys v0.1.0 // indirect
|
||||
golang.org/x/term v0.1.0 // indirect
|
||||
golang.org/x/sys v0.3.0 // indirect
|
||||
golang.org/x/term v0.3.0 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
|
||||
google.golang.org/api v0.44.0 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
|
||||
43
go.sum
43
go.sum
@@ -136,10 +136,10 @@ github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go
|
||||
github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
|
||||
github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
|
||||
github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
|
||||
github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc=
|
||||
github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
|
||||
github.com/Masterminds/sprig/v3 v3.2.2 h1:17jRggJu518dr3QaafizSXOjKYp94wKfABxUmyxvxX8=
|
||||
github.com/Masterminds/sprig/v3 v3.2.2/go.mod h1:UoaO7Yp8KlPnJIYWTFkMaqPUYKTfGFPhxNuwnnxkKlk=
|
||||
github.com/Masterminds/semver/v3 v3.2.0 h1:3MEsd0SM6jqZojhjLWWeBY+Kcjy9i6MQAeY7YgDP83g=
|
||||
github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ=
|
||||
github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj9n6YA=
|
||||
github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBaRMhvYXJNkGuM=
|
||||
github.com/Microsoft/go-winio v0.4.3/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA=
|
||||
github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA=
|
||||
github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
|
||||
@@ -1036,9 +1036,8 @@ github.com/hashicorp/yamux v0.0.0-20210826001029-26ff87cf9493/go.mod h1:CtWFDAQg
|
||||
github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174 h1:WlZsjVhE8Af9IcZDGgJGQpNflI3+MJSBhsgT5PCtzBQ=
|
||||
github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174/go.mod h1:DqJ97dSdRW1W22yXSB90986pcOyQ7r45iio1KN2ez1A=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
||||
github.com/huandu/xstrings v1.3.2 h1:L18LIDzqlW6xN2rEkpdV8+oL/IXWJ1APd+vsdYy4Wdw=
|
||||
github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
||||
github.com/huandu/xstrings v1.3.3 h1:/Gcsuc1x8JVbJ9/rlye4xZnVAbEkGauT8lbebqcQws4=
|
||||
github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
||||
github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
@@ -1776,8 +1775,8 @@ github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea h1:SXhTLE6pb6eld/
|
||||
github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea/go.mod h1:WPnis/6cRcDZSUvVmezrxJPkiO87ThFYsoUiMwWNDJk=
|
||||
github.com/tonistiigi/vt100 v0.0.0-20190402012908-ad4c4a574305 h1:y/1cL5AL2oRcfzz8CAHHhR6kDDfIOT0WEyH5k40sccM=
|
||||
github.com/tonistiigi/vt100 v0.0.0-20190402012908-ad4c4a574305/go.mod h1:gXOLibKqQTRAVuVZ9gX7G9Ykky8ll8yb4slxsEMoY0c=
|
||||
github.com/traefik/paerser v0.1.9 h1:x5hZafOt/yogLvr6upoSOYIAn2nh2GsnLb236MOzd4I=
|
||||
github.com/traefik/paerser v0.1.9/go.mod h1:Dk3Bfz6Zyj13/S8pJyRdx/FNvXlsVRVbtp0UK4ZSiA0=
|
||||
github.com/traefik/paerser v0.2.0 h1:zqCLGSXoNlcBd+mzqSCLjon/I6phqIjeJL2xFB2ysgQ=
|
||||
github.com/traefik/paerser v0.2.0/go.mod h1:afzaVcgF8A+MpTnPG4wBr4whjanCSYA6vK5RwaYVtRc=
|
||||
github.com/traefik/yaegi v0.14.3 h1:LqA0k8DKwvRMc+msfQjNusphHJc+r6WC5tZU5TmUFOM=
|
||||
github.com/traefik/yaegi v0.14.3/go.mod h1:AVRxhaI2G+nUsaM1zyktzwXn69G3t/AuTDrCiTds9p0=
|
||||
github.com/transip/gotransip/v6 v6.17.0 h1:2RCyqYqz5+Ej8z96EyE4sf6tQrrfEBaFDO0LliSl6+8=
|
||||
@@ -1857,6 +1856,7 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
|
||||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43/go.mod h1:aX5oPXxHm3bOH+xeAttToC8pqch2ScQN/JoXYupl6xs=
|
||||
github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50/go.mod h1:NUSPSUX/bi6SeDMUh6brw0nXpxHnc96TguQh0+r/ssA=
|
||||
github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f/go.mod h1:GlGEuHIJweS1mbCqG+7vt2nvWLzLLnRHbXz5JKd/Qbg=
|
||||
@@ -1958,7 +1958,6 @@ golang.org/x/crypto v0.0.0-20200128174031-69ecbb4d6d5d/go.mod h1:LzIPMQfyMNhhGPh
|
||||
golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200317142112-1b76d66859c6/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200414173820-0848c9571904/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200422194213-44a606286825/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
@@ -1976,8 +1975,8 @@ golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5y
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20211202192323-5770296d904e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f h1:OeJjE6G4dgCY4PIXvIRQbE8+RX+uXZyGhUy/ksMGJoc=
|
||||
golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.3.0 h1:a06MkbcxBrEFc0w0QIZWXrH/9cCX6KJyWbBOIwAn+7A=
|
||||
golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
|
||||
golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
@@ -2093,8 +2092,10 @@ golang.org/x/net v0.0.0-20211216030914-fe4d6282115f/go.mod h1:9nx3DQGgdP8bBQD5qx
|
||||
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.1.0 h1:hZ/3BUoy5aId7sCpA/Tc5lt8DkFgdVS2onTpJsZ/fl0=
|
||||
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
|
||||
golang.org/x/net v0.3.1-0.20221206200815-1e63c2f08a10 h1:Frnccbp+ok2GkUS2tC84yAq/U9Vg+0sIO7aRL3T4Xnc=
|
||||
golang.org/x/net v0.3.1-0.20221206200815-1e63c2f08a10/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
|
||||
golang.org/x/oauth2 v0.0.0-20180724155351-3d292e4d0cdc/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
@@ -2253,15 +2254,18 @@ golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ=
|
||||
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.1.0 h1:g6Z6vPFA9dYBAF7DWcH6sCcOntplXsDKcliusYijMlw=
|
||||
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
|
||||
golang.org/x/term v0.3.0 h1:qoo4akIqOcDME5bhc/NgxUdovd6BSS2uMsVjB56q1xI=
|
||||
golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=
|
||||
golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
@@ -2272,8 +2276,9 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg=
|
||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.5.0 h1:OLmvp0KP+FVG99Ct/qFiL/Fhk4zp4QQnZ7b2U+5piUM=
|
||||
golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
|
||||
@@ -2,6 +2,7 @@ package integration
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
@@ -29,7 +30,7 @@ func (s *ErrorPagesSuite) TestSimpleConfiguration(c *check.C) {
|
||||
file := s.adaptFile(c, "fixtures/error_pages/simple.toml", struct {
|
||||
Server1 string
|
||||
Server2 string
|
||||
}{s.BackendIP, s.ErrorPageIP})
|
||||
}{"http://" + s.BackendIP + ":80", s.ErrorPageIP})
|
||||
defer os.Remove(file)
|
||||
|
||||
cmd, display := s.traefikCmd(withConfigFile(file))
|
||||
@@ -67,3 +68,33 @@ func (s *ErrorPagesSuite) TestErrorPage(c *check.C) {
|
||||
err = try.Request(frontendReq, 2*time.Second, try.BodyContains("An error occurred."))
|
||||
c.Assert(err, checker.IsNil)
|
||||
}
|
||||
|
||||
func (s *ErrorPagesSuite) TestErrorPageFlush(c *check.C) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
||||
rw.Header().Add("Transfer-Encoding", "chunked")
|
||||
rw.WriteHeader(http.StatusInternalServerError)
|
||||
_, _ = rw.Write([]byte("KO"))
|
||||
}))
|
||||
|
||||
file := s.adaptFile(c, "fixtures/error_pages/simple.toml", struct {
|
||||
Server1 string
|
||||
Server2 string
|
||||
}{srv.URL, s.ErrorPageIP})
|
||||
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)
|
||||
|
||||
frontendReq, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8080", nil)
|
||||
c.Assert(err, checker.IsNil)
|
||||
frontendReq.Host = "test.local"
|
||||
|
||||
err = try.Request(frontendReq, 2*time.Second,
|
||||
try.BodyContains("An error occurred."),
|
||||
try.HasHeaderValue("Content-Type", "text/html", true),
|
||||
)
|
||||
c.Assert(err, checker.IsNil)
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
[http.services.service1.loadBalancer]
|
||||
passHostHeader = true
|
||||
[[http.services.service1.loadBalancer.servers]]
|
||||
url = "http://{{.Server1}}:80"
|
||||
url = "{{.Server1}}"
|
||||
|
||||
[http.services.error.loadBalancer]
|
||||
[[http.services.error.loadBalancer.servers]]
|
||||
|
||||
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"
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
/*
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016-2020 Containous SAS; 2020-2022 Traefik Labs
|
||||
Copyright (c) 2016-2020 Containous SAS; 2020-2023 Traefik Labs
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -233,6 +233,15 @@ func (cc *codeCatcher) Flush() {
|
||||
// Otherwise, cc.code is actually a 200 here.
|
||||
cc.WriteHeader(cc.code)
|
||||
|
||||
// We don't care about the contents of the response,
|
||||
// since we want to serve the ones from the error page,
|
||||
// so we just don't flush.
|
||||
// (e.g., To prevent superfluous WriteHeader on request with a
|
||||
// `Transfert-Encoding: chunked` header).
|
||||
if cc.caughtFilteredCode {
|
||||
return
|
||||
}
|
||||
|
||||
if flusher, ok := cc.responseWriter.(http.Flusher); ok {
|
||||
flusher.Flush()
|
||||
}
|
||||
|
||||
@@ -79,10 +79,12 @@ func New(ctx context.Context, next http.Handler, config dynamic.RateLimit, name
|
||||
period = time.Second
|
||||
}
|
||||
|
||||
// if config.Average == 0, in that case,
|
||||
// the value of maxDelay does not matter since the reservation will (buggily) give us a delay of 0 anyway.
|
||||
// Initialized at rate.Inf to enforce no rate limiting when config.Average == 0
|
||||
rtl := float64(rate.Inf)
|
||||
// No need to set any particular value for maxDelay as the reservation's delay
|
||||
// will be <= 0 in the Inf case (i.e. the average == 0 case).
|
||||
var maxDelay time.Duration
|
||||
var rtl float64
|
||||
|
||||
if config.Average > 0 {
|
||||
rtl = float64(config.Average*int64(time.Second)) / float64(period)
|
||||
// maxDelay does not scale well for rates below 1,
|
||||
@@ -153,10 +155,6 @@ func (rl *rateLimiter) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
// time/rate is bugged, since a rate.Limiter with a 0 Limit not only allows a Reservation to take place,
|
||||
// but also gives a 0 delay below (because of a division by zero, followed by a multiplication that flips into the negatives),
|
||||
// regardless of the current load.
|
||||
// However, for now we take advantage of this behavior to provide the no-limit ratelimiter when config.Average is 0.
|
||||
res := bucket.Reserve()
|
||||
if !res.OK() {
|
||||
http.Error(w, "No bursty traffic allowed", http.StatusTooManyRequests)
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
"github.com/traefik/traefik/v2/pkg/config/dynamic"
|
||||
"github.com/traefik/traefik/v2/pkg/testhelpers"
|
||||
"github.com/vulcand/oxy/v2/utils"
|
||||
"golang.org/x/time/rate"
|
||||
)
|
||||
|
||||
func TestNewRateLimiter(t *testing.T) {
|
||||
@@ -25,7 +26,16 @@ func TestNewRateLimiter(t *testing.T) {
|
||||
expectedSourceIP string
|
||||
requestHeader string
|
||||
expectedError string
|
||||
expectedRTL rate.Limit
|
||||
}{
|
||||
{
|
||||
desc: "no ratelimit on Average == 0",
|
||||
config: dynamic.RateLimit{
|
||||
Average: 0,
|
||||
Burst: 10,
|
||||
},
|
||||
expectedRTL: rate.Inf,
|
||||
},
|
||||
{
|
||||
desc: "maxDelay computation",
|
||||
config: dynamic.RateLimit{
|
||||
@@ -120,6 +130,9 @@ func TestNewRateLimiter(t *testing.T) {
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, test.requestHeader, hd)
|
||||
}
|
||||
if test.expectedRTL != 0 {
|
||||
assert.Equal(t, test.expectedRTL, rtl.rate)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,12 @@ const (
|
||||
xForwardedProto = "X-Forwarded-Proto"
|
||||
)
|
||||
|
||||
type redirectScheme struct {
|
||||
http.Handler
|
||||
|
||||
name string
|
||||
}
|
||||
|
||||
// NewRedirectScheme creates a new RedirectScheme middleware.
|
||||
func NewRedirectScheme(ctx context.Context, next http.Handler, conf dynamic.RedirectScheme, name string) (http.Handler, error) {
|
||||
logger := log.FromContext(middlewares.GetLoggerCtx(ctx, name, typeSchemeName))
|
||||
@@ -33,10 +39,19 @@ func NewRedirectScheme(ctx context.Context, next http.Handler, conf dynamic.Redi
|
||||
port = ":" + conf.Port
|
||||
}
|
||||
|
||||
return newRedirect(next, uriPattern, conf.Scheme+"://${2}"+port+"${4}", conf.Permanent, clientRequestURL, name)
|
||||
rs := &redirectScheme{name: name}
|
||||
|
||||
handler, err := newRedirect(next, uriPattern, conf.Scheme+"://${2}"+port+"${4}", conf.Permanent, rs.clientRequestURL, name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rs.Handler = handler
|
||||
|
||||
return rs, nil
|
||||
}
|
||||
|
||||
func clientRequestURL(req *http.Request) string {
|
||||
func (r *redirectScheme) clientRequestURL(req *http.Request) string {
|
||||
scheme := schemeHTTP
|
||||
host, port, err := net.SplitHostPort(req.Host)
|
||||
if err != nil {
|
||||
@@ -71,12 +86,12 @@ func clientRequestURL(req *http.Request) string {
|
||||
// Given that we're in a middleware that is only used in the context of HTTP(s) requests,
|
||||
// the only possible valid schemes are one of "http" or "https", so we convert back to them.
|
||||
switch {
|
||||
case strings.EqualFold(xProto, "ws"):
|
||||
case strings.EqualFold(xProto, schemeHTTP), strings.EqualFold(xProto, "ws"):
|
||||
scheme = schemeHTTP
|
||||
case strings.EqualFold(xProto, "wss"):
|
||||
case strings.EqualFold(xProto, schemeHTTPS), strings.EqualFold(xProto, "wss"):
|
||||
scheme = schemeHTTPS
|
||||
default:
|
||||
scheme = xProto
|
||||
log.FromContext(middlewares.GetLoggerCtx(req.Context(), r.name, typeSchemeName)).Debugf("invalid X-Forwarded-Proto: %s", xProto)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -72,7 +72,7 @@ func TestRedirectSchemeHandler(t *testing.T) {
|
||||
headers: map[string]string{
|
||||
"X-Forwarded-Proto": "bar",
|
||||
},
|
||||
expectedURL: "https://bar://foo",
|
||||
expectedURL: "https://foo",
|
||||
expectedStatus: http.StatusFound,
|
||||
},
|
||||
{
|
||||
|
||||
@@ -507,7 +507,22 @@ func (p *Provider) parseService(ctx context.Context, service swarmtypes.Service,
|
||||
if service.Spec.EndpointSpec != nil {
|
||||
if service.Spec.EndpointSpec.Mode == swarmtypes.ResolutionModeDNSRR {
|
||||
if dData.ExtraConf.Docker.LBSwarm {
|
||||
logger.Warnf("Ignored %s endpoint-mode not supported, service name: %s. Fallback to Traefik load balancing", swarmtypes.ResolutionModeDNSRR, service.Spec.Annotations.Name)
|
||||
dData.NetworkSettings.Networks = make(map[string]*networkData)
|
||||
for _, net := range service.Spec.TaskTemplate.Networks {
|
||||
networkService := networkMap[net.Target]
|
||||
if networkService != nil {
|
||||
network := &networkData{
|
||||
Name: networkService.Name,
|
||||
ID: net.Target,
|
||||
Addr: "tasks." + service.Spec.Name,
|
||||
}
|
||||
dData.NetworkSettings.Networks[network.Name] = network
|
||||
} else {
|
||||
logger.Debugf("Network not found, id: %s", net.Target)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Each service task IP will get extracted later on in parseTasks
|
||||
}
|
||||
} else if service.Spec.EndpointSpec.Mode == swarmtypes.ResolutionModeVIP {
|
||||
dData.NetworkSettings.Networks = make(map[string]*networkData)
|
||||
|
||||
@@ -315,6 +315,11 @@ func (p *Provider) listInstances(ctx context.Context, client *awsClient) ([]ecsI
|
||||
|
||||
var mach *machine
|
||||
if len(task.Attachments) != 0 {
|
||||
if len(container.NetworkInterfaces) == 0 {
|
||||
logger.Errorf("Skip container %s: no network interfaces", aws.StringValue(container.Name))
|
||||
continue
|
||||
}
|
||||
|
||||
var ports []portMapping
|
||||
for _, mapping := range containerDefinition.PortMappings {
|
||||
if mapping != nil {
|
||||
@@ -378,7 +383,7 @@ func (p *Provider) listInstances(ctx context.Context, client *awsClient) ([]ecsI
|
||||
|
||||
extraConf, err := p.getConfiguration(instance)
|
||||
if err != nil {
|
||||
log.FromContext(ctx).Errorf("Skip container %s: %w", getServiceName(instance), err)
|
||||
logger.Errorf("Skip container %s: %w", getServiceName(instance), err)
|
||||
continue
|
||||
}
|
||||
instance.ExtraConf = extraConf
|
||||
|
||||
@@ -46,11 +46,6 @@ func (p *Provider) Init() error {
|
||||
// Provide allows the file provider to provide configurations to traefik
|
||||
// using the given configuration channel.
|
||||
func (p *Provider) Provide(configurationChan chan<- dynamic.Message, pool *safe.Pool) error {
|
||||
configuration, err := p.BuildConfiguration()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if p.Watch {
|
||||
var watchItem string
|
||||
|
||||
@@ -68,6 +63,15 @@ func (p *Provider) Provide(configurationChan chan<- dynamic.Message, pool *safe.
|
||||
}
|
||||
}
|
||||
|
||||
configuration, err := p.BuildConfiguration()
|
||||
if err != nil {
|
||||
if p.Watch {
|
||||
log.WithoutContext().WithField(log.ProviderName, providerName).Errorf("Error while building configuration (for the first time): %v", err)
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
sendConfigToChannel(configurationChan, configuration)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016-2020 Containous SAS; 2020-2022 Traefik Labs
|
||||
Copyright (c) 2016-2020 Containous SAS; 2020-2023 Traefik Labs
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016-2020 Containous SAS; 2020-2022 Traefik Labs
|
||||
Copyright (c) 2016-2020 Containous SAS; 2020-2023 Traefik Labs
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016-2020 Containous SAS; 2020-2022 Traefik Labs
|
||||
Copyright (c) 2016-2020 Containous SAS; 2020-2023 Traefik Labs
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016-2020 Containous SAS; 2020-2022 Traefik Labs
|
||||
Copyright (c) 2016-2020 Containous SAS; 2020-2023 Traefik Labs
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016-2020 Containous SAS; 2020-2022 Traefik Labs
|
||||
Copyright (c) 2016-2020 Containous SAS; 2020-2023 Traefik Labs
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016-2020 Containous SAS; 2020-2022 Traefik Labs
|
||||
Copyright (c) 2016-2020 Containous SAS; 2020-2023 Traefik Labs
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016-2020 Containous SAS; 2020-2022 Traefik Labs
|
||||
Copyright (c) 2016-2020 Containous SAS; 2020-2023 Traefik Labs
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016-2020 Containous SAS; 2020-2022 Traefik Labs
|
||||
Copyright (c) 2016-2020 Containous SAS; 2020-2023 Traefik Labs
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016-2020 Containous SAS; 2020-2022 Traefik Labs
|
||||
Copyright (c) 2016-2020 Containous SAS; 2020-2023 Traefik Labs
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016-2020 Containous SAS; 2020-2022 Traefik Labs
|
||||
Copyright (c) 2016-2020 Containous SAS; 2020-2023 Traefik Labs
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016-2020 Containous SAS; 2020-2022 Traefik Labs
|
||||
Copyright (c) 2016-2020 Containous SAS; 2020-2023 Traefik Labs
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016-2020 Containous SAS; 2020-2022 Traefik Labs
|
||||
Copyright (c) 2016-2020 Containous SAS; 2020-2023 Traefik Labs
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016-2020 Containous SAS; 2020-2022 Traefik Labs
|
||||
Copyright (c) 2016-2020 Containous SAS; 2020-2023 Traefik Labs
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016-2020 Containous SAS; 2020-2022 Traefik Labs
|
||||
Copyright (c) 2016-2020 Containous SAS; 2020-2023 Traefik Labs
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016-2020 Containous SAS; 2020-2022 Traefik Labs
|
||||
Copyright (c) 2016-2020 Containous SAS; 2020-2023 Traefik Labs
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016-2020 Containous SAS; 2020-2022 Traefik Labs
|
||||
Copyright (c) 2016-2020 Containous SAS; 2020-2023 Traefik Labs
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016-2020 Containous SAS; 2020-2022 Traefik Labs
|
||||
Copyright (c) 2016-2020 Containous SAS; 2020-2023 Traefik Labs
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016-2020 Containous SAS; 2020-2022 Traefik Labs
|
||||
Copyright (c) 2016-2020 Containous SAS; 2020-2023 Traefik Labs
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016-2020 Containous SAS; 2020-2022 Traefik Labs
|
||||
Copyright (c) 2016-2020 Containous SAS; 2020-2023 Traefik Labs
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016-2020 Containous SAS; 2020-2022 Traefik Labs
|
||||
Copyright (c) 2016-2020 Containous SAS; 2020-2023 Traefik Labs
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016-2020 Containous SAS; 2020-2022 Traefik Labs
|
||||
Copyright (c) 2016-2020 Containous SAS; 2020-2023 Traefik Labs
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016-2020 Containous SAS; 2020-2022 Traefik Labs
|
||||
Copyright (c) 2016-2020 Containous SAS; 2020-2023 Traefik Labs
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016-2020 Containous SAS; 2020-2022 Traefik Labs
|
||||
Copyright (c) 2016-2020 Containous SAS; 2020-2023 Traefik Labs
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016-2020 Containous SAS; 2020-2022 Traefik Labs
|
||||
Copyright (c) 2016-2020 Containous SAS; 2020-2023 Traefik Labs
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016-2020 Containous SAS; 2020-2022 Traefik Labs
|
||||
Copyright (c) 2016-2020 Containous SAS; 2020-2023 Traefik Labs
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016-2020 Containous SAS; 2020-2022 Traefik Labs
|
||||
Copyright (c) 2016-2020 Containous SAS; 2020-2023 Traefik Labs
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016-2020 Containous SAS; 2020-2022 Traefik Labs
|
||||
Copyright (c) 2016-2020 Containous SAS; 2020-2023 Traefik Labs
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016-2020 Containous SAS; 2020-2022 Traefik Labs
|
||||
Copyright (c) 2016-2020 Containous SAS; 2020-2023 Traefik Labs
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016-2020 Containous SAS; 2020-2022 Traefik Labs
|
||||
Copyright (c) 2016-2020 Containous SAS; 2020-2023 Traefik Labs
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016-2020 Containous SAS; 2020-2022 Traefik Labs
|
||||
Copyright (c) 2016-2020 Containous SAS; 2020-2023 Traefik Labs
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016-2020 Containous SAS; 2020-2022 Traefik Labs
|
||||
Copyright (c) 2016-2020 Containous SAS; 2020-2023 Traefik Labs
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016-2020 Containous SAS; 2020-2022 Traefik Labs
|
||||
Copyright (c) 2016-2020 Containous SAS; 2020-2023 Traefik Labs
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016-2020 Containous SAS; 2020-2022 Traefik Labs
|
||||
Copyright (c) 2016-2020 Containous SAS; 2020-2023 Traefik Labs
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016-2020 Containous SAS; 2020-2022 Traefik Labs
|
||||
Copyright (c) 2016-2020 Containous SAS; 2020-2023 Traefik Labs
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016-2020 Containous SAS; 2020-2022 Traefik Labs
|
||||
Copyright (c) 2016-2020 Containous SAS; 2020-2023 Traefik Labs
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016-2020 Containous SAS; 2020-2022 Traefik Labs
|
||||
Copyright (c) 2016-2020 Containous SAS; 2020-2023 Traefik Labs
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016-2020 Containous SAS; 2020-2022 Traefik Labs
|
||||
Copyright (c) 2016-2020 Containous SAS; 2020-2023 Traefik Labs
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016-2020 Containous SAS; 2020-2022 Traefik Labs
|
||||
Copyright (c) 2016-2020 Containous SAS; 2020-2023 Traefik Labs
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016-2020 Containous SAS; 2020-2022 Traefik Labs
|
||||
Copyright (c) 2016-2020 Containous SAS; 2020-2023 Traefik Labs
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016-2020 Containous SAS; 2020-2022 Traefik Labs
|
||||
Copyright (c) 2016-2020 Containous SAS; 2020-2023 Traefik Labs
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016-2020 Containous SAS; 2020-2022 Traefik Labs
|
||||
Copyright (c) 2016-2020 Containous SAS; 2020-2023 Traefik Labs
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016-2020 Containous SAS; 2020-2022 Traefik Labs
|
||||
Copyright (c) 2016-2020 Containous SAS; 2020-2023 Traefik Labs
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016-2020 Containous SAS; 2020-2022 Traefik Labs
|
||||
Copyright (c) 2016-2020 Containous SAS; 2020-2023 Traefik Labs
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016-2020 Containous SAS; 2020-2022 Traefik Labs
|
||||
Copyright (c) 2016-2020 Containous SAS; 2020-2023 Traefik Labs
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016-2020 Containous SAS; 2020-2022 Traefik Labs
|
||||
Copyright (c) 2016-2020 Containous SAS; 2020-2023 Traefik Labs
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016-2020 Containous SAS; 2020-2022 Traefik Labs
|
||||
Copyright (c) 2016-2020 Containous SAS; 2020-2023 Traefik Labs
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016-2020 Containous SAS; 2020-2022 Traefik Labs
|
||||
Copyright (c) 2016-2020 Containous SAS; 2020-2023 Traefik Labs
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016-2020 Containous SAS; 2020-2022 Traefik Labs
|
||||
Copyright (c) 2016-2020 Containous SAS; 2020-2023 Traefik Labs
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016-2020 Containous SAS; 2020-2022 Traefik Labs
|
||||
Copyright (c) 2016-2020 Containous SAS; 2020-2023 Traefik Labs
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016-2020 Containous SAS; 2020-2022 Traefik Labs
|
||||
Copyright (c) 2016-2020 Containous SAS; 2020-2023 Traefik Labs
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016-2020 Containous SAS; 2020-2022 Traefik Labs
|
||||
Copyright (c) 2016-2020 Containous SAS; 2020-2023 Traefik Labs
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016-2020 Containous SAS; 2020-2022 Traefik Labs
|
||||
Copyright (c) 2016-2020 Containous SAS; 2020-2023 Traefik Labs
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016-2020 Containous SAS; 2020-2022 Traefik Labs
|
||||
Copyright (c) 2016-2020 Containous SAS; 2020-2023 Traefik Labs
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016-2020 Containous SAS; 2020-2022 Traefik Labs
|
||||
Copyright (c) 2016-2020 Containous SAS; 2020-2023 Traefik Labs
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
/*
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016-2020 Containous SAS; 2020-2022 Traefik Labs
|
||||
Copyright (c) 2016-2020 Containous SAS; 2020-2023 Traefik Labs
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"crypto/tls"
|
||||
"net/http"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
@@ -113,6 +114,9 @@ func TestHTTP3AdvertisedPort(t *testing.T) {
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
// We are racing with the http3Server readiness happening in the goroutine starting the entrypoint
|
||||
time.Sleep(time.Second)
|
||||
|
||||
request, err := http.NewRequest(http.MethodGet, "https://127.0.0.1:8090", nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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,7 +4,7 @@
|
||||
/*
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016-2020 Containous SAS; 2020-2022 Traefik Labs
|
||||
Copyright (c) 2016-2020 Containous SAS; 2020-2023 Traefik Labs
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
/*
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016-2020 Containous SAS; 2020-2022 Traefik Labs
|
||||
Copyright (c) 2016-2020 Containous SAS; 2020-2023 Traefik Labs
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -4,11 +4,11 @@ RepositoryName = "traefik"
|
||||
OutputType = "file"
|
||||
FileName = "traefik_changelog.md"
|
||||
|
||||
# example new bugfix v2.9.5
|
||||
# example new bugfix v2.9.6
|
||||
CurrentRef = "v2.9"
|
||||
PreviousRef = "v2.9.4"
|
||||
PreviousRef = "v2.9.5"
|
||||
BaseBranch = "v2.9"
|
||||
FutureCurrentRefName = "v2.9.5"
|
||||
FutureCurrentRefName = "v2.9.6"
|
||||
|
||||
ThresholdPreviousRef = 10
|
||||
ThresholdCurrentRef = 10
|
||||
|
||||
Reference in New Issue
Block a user