Merge current branch master into v2.7

This commit is contained in:
Tom Moulard 2022-06-10 16:17:55 +02:00
commit 59e66dfce5
No known key found for this signature in database
GPG key ID: 521ABE0C1A0DEAF6
72 changed files with 1162 additions and 605 deletions

View file

@ -174,6 +174,8 @@
"SA1019: cfg.SSLHost is deprecated", "SA1019: cfg.SSLHost is deprecated",
"SA1019: cfg.SSLForceHost is deprecated", "SA1019: cfg.SSLForceHost is deprecated",
"SA1019: cfg.FeaturePolicy is deprecated", "SA1019: cfg.FeaturePolicy is deprecated",
"SA1019: c.Providers.ConsulCatalog.Namespace is deprecated",
"SA1019: c.Providers.Consul.Namespace is deprecated",
] ]
[[issues.exclude-rules]] [[issues.exclude-rules]]
path = "(.+)_test.go" path = "(.+)_test.go"

View file

@ -3,9 +3,10 @@
This page is maintained and updated periodically to reflect our roadmap and any decisions around feature deprecation. This page is maintained and updated periodically to reflect our roadmap and any decisions around feature deprecation.
| Feature | Deprecated | End of Support | Removal | | Feature | Deprecated | End of Support | Removal |
|-------------------------------------------------------|------------|----------------|---------| |---------------------------------------------------------------|------------|----------------|---------|
| [Pilot Dashboard (Metrics)](#pilot-dashboard-metrics) | 2.7 | 2.8 | 2.9 | | [Pilot Dashboard (Metrics)](#pilot-dashboard-metrics) | 2.7 | 2.8 | 2.9 |
| [Pilot Plugins](#pilot-plugins) | 2.7 | 2.8 | 2.9 | | [Pilot Plugins](#pilot-plugins) | 2.7 | 2.8 | 2.9 |
| [Consul Enterprise Namespaces](#consul-enterprise-namespaces) | 2.8 | TBD | TBD |
## Impact ## Impact
@ -18,3 +19,8 @@ In 2.9, the Pilot platform and all Traefik integration code will be permanently
Starting on 2.7 the pilot token will not be a requirement anymore. Starting on 2.7 the pilot token will not be a requirement anymore.
At 2.9, a new plugin catalog home should be available, decoupled from pilot. At 2.9, a new plugin catalog home should be available, decoupled from pilot.
### Consul Enterprise Namespaces
Starting on 2.8 the `namespace` option of Consul and Consul Catalog providers is deprecated,
please use the `namespaces` options instead.

View file

@ -364,8 +364,9 @@ spec:
### Strict SNI Checking ### Strict SNI Checking
With strict SNI checking enabled, Traefik won't allow connections from clients With strict SNI checking enabled, Traefik won't allow connections from clients that do not specify a server_name extension
that do not specify a server_name extension or don't match any certificate configured on the tlsOption. or don't match any of the configured certificates.
The default certificate is irrelevant on that matter.
```yaml tab="File (YAML)" ```yaml tab="File (YAML)"
# Dynamic configuration # Dynamic configuration

View file

@ -176,15 +176,18 @@ This behavior cannot be configured.
### `CheckPeriod` ### `CheckPeriod`
The interval used to evaluate `expression` and decide if the state of the circuit breaker must change. _Optional, Default="100ms"_
By default, `CheckPeriod` is 100ms. This value cannot be configured.
The interval between successive checks of the circuit breaker condition (when in standby state).
### `FallbackDuration` ### `FallbackDuration`
By default, `FallbackDuration` is 10 seconds. This value cannot be configured. _Optional, Default="10s"_
### `RecoveringDuration` The duration for which the circuit breaker will wait before trying to recover (from a tripped state).
The duration of the recovering mode (recovering state). ### `RecoveryDuration`
By default, `RecoveringDuration` is 10 seconds. This value cannot be configured. _Optional, Default="10s"_
The duration for which the circuit breaker will try to recover (as soon as it is in recovering state).

View file

@ -1,16 +1,16 @@
--- ---
title: "Traefik ErrorPage Documentation" title: "Traefik Errors Documentation"
description: "In Traefik Proxy, the ErrorPage middleware returns custom pages according to configured ranges of HTTP Status codes. Read the technical documentation." description: "In Traefik Proxy, the Errors middleware returns custom pages according to configured ranges of HTTP Status codes. Read the technical documentation."
--- ---
# ErrorPage # Errors
It Has Never Been Easier to Say That Something Went Wrong It Has Never Been Easier to Say That Something Went Wrong
{: .subtitle } {: .subtitle }
![ErrorPages](../../assets/img/middleware/errorpages.png) ![Errors](../../assets/img/middleware/errorpages.png)
The ErrorPage middleware returns a custom page in lieu of the default, according to configured ranges of HTTP Status codes. The Errors middleware returns a custom page in lieu of the default, according to configured ranges of HTTP Status codes.
!!! important !!! important
@ -21,16 +21,16 @@ The ErrorPage middleware returns a custom page in lieu of the default, according
```yaml tab="Docker" ```yaml tab="Docker"
# Dynamic Custom Error Page for 5XX Status Code # Dynamic Custom Error Page for 5XX Status Code
labels: labels:
- "traefik.http.middlewares.test-errorpage.errors.status=500-599" - "traefik.http.middlewares.test-errors.errors.status=500-599"
- "traefik.http.middlewares.test-errorpage.errors.service=serviceError" - "traefik.http.middlewares.test-errors.errors.service=serviceError"
- "traefik.http.middlewares.test-errorpage.errors.query=/{status}.html" - "traefik.http.middlewares.test-errors.errors.query=/{status}.html"
``` ```
```yaml tab="Kubernetes" ```yaml tab="Kubernetes"
apiVersion: traefik.containo.us/v1alpha1 apiVersion: traefik.containo.us/v1alpha1
kind: Middleware kind: Middleware
metadata: metadata:
name: test-errorpage name: test-errors
spec: spec:
errors: errors:
status: status:
@ -43,32 +43,32 @@ spec:
```yaml tab="Consul Catalog" ```yaml tab="Consul Catalog"
# Dynamic Custom Error Page for 5XX Status Code # Dynamic Custom Error Page for 5XX Status Code
- "traefik.http.middlewares.test-errorpage.errors.status=500-599" - "traefik.http.middlewares.test-errors.errors.status=500-599"
- "traefik.http.middlewares.test-errorpage.errors.service=serviceError" - "traefik.http.middlewares.test-errors.errors.service=serviceError"
- "traefik.http.middlewares.test-errorpage.errors.query=/{status}.html" - "traefik.http.middlewares.test-errors.errors.query=/{status}.html"
``` ```
```json tab="Marathon" ```json tab="Marathon"
"labels": { "labels": {
"traefik.http.middlewares.test-errorpage.errors.status": "500-599", "traefik.http.middlewares.test-errors.errors.status": "500-599",
"traefik.http.middlewares.test-errorpage.errors.service": "serviceError", "traefik.http.middlewares.test-errors.errors.service": "serviceError",
"traefik.http.middlewares.test-errorpage.errors.query": "/{status}.html" "traefik.http.middlewares.test-errors.errors.query": "/{status}.html"
} }
``` ```
```yaml tab="Rancher" ```yaml tab="Rancher"
# Dynamic Custom Error Page for 5XX Status Code # Dynamic Custom Error Page for 5XX Status Code
labels: labels:
- "traefik.http.middlewares.test-errorpage.errors.status=500-599" - "traefik.http.middlewares.test-errors.errors.status=500-599"
- "traefik.http.middlewares.test-errorpage.errors.service=serviceError" - "traefik.http.middlewares.test-errors.errors.service=serviceError"
- "traefik.http.middlewares.test-errorpage.errors.query=/{status}.html" - "traefik.http.middlewares.test-errors.errors.query=/{status}.html"
``` ```
```yaml tab="File (YAML)" ```yaml tab="File (YAML)"
# Custom Error Page for 5XX # Custom Error Page for 5XX
http: http:
middlewares: middlewares:
test-errorpage: test-errors:
errors: errors:
status: status:
- "500-599" - "500-599"
@ -82,7 +82,7 @@ http:
```toml tab="File (TOML)" ```toml tab="File (TOML)"
# Custom Error Page for 5XX # Custom Error Page for 5XX
[http.middlewares] [http.middlewares]
[http.middlewares.test-errorpage.errors] [http.middlewares.test-errors.errors]
status = ["500-599"] status = ["500-599"]
service = "serviceError" service = "serviceError"
query = "/{status}.html" query = "/{status}.html"
@ -125,4 +125,13 @@ The service that will serve the new requested error page.
### `query` ### `query`
The URL for the error page (hosted by `service`). You can use the `{status}` variable in the `query` option in order to insert the status code in the URL. The URL for the error page (hosted by [`service`](#service))).
There are multiple variables that can be placed in the `query` option to insert values in the URL.
The table below lists all the available variables and their associated values.
| Variable | Value |
|------------|--------------------------------------------------------------------|
| `{status}` | The response status code. |
| `{url}` | The [escaped](https://pkg.go.dev/net/url#QueryEscape) request URL. |

View file

@ -426,70 +426,6 @@ http:
ca = "path/to/local.crt" ca = "path/to/local.crt"
``` ```
#### `caOptional`
_Optional_
The value of `caOptional` defines which policy should be used for the secure connection with TLS Client Authentication to the authentication server.
!!! warning ""
If `ca` is undefined, this option will be ignored, and no client certificate will be requested during the handshake. Any provided certificate will thus never be verified.
When this option is set to `true`, a client certificate is requested during the handshake but is not required. If a certificate is sent, it is required to be valid.
When this option is set to `false`, a client certificate is requested during the handshake, and at least one valid certificate should be sent by the client.
```yaml tab="Docker"
labels:
- "traefik.http.middlewares.test-auth.forwardauth.tls.caOptional=true"
```
```yaml tab="Kubernetes"
apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
name: test-auth
spec:
forwardAuth:
address: https://example.com/auth
tls:
caOptional: true
```
```yaml tab="Consul Catalog"
- "traefik.http.middlewares.test-auth.forwardauth.tls.caOptional=true"
```
```json tab="Marathon"
"labels": {
"traefik.http.middlewares.test-auth.forwardauth.tls.caOptional": "true"
}
```
```yaml tab="Rancher"
labels:
- "traefik.http.middlewares.test-auth.forwardauth.tls.caOptional=true"
```
```yaml tab="File (YAML)"
http:
middlewares:
test-auth:
forwardAuth:
address: "https://example.com/auth"
tls:
caOptional: true
```
```toml tab="File (TOML)"
[http.middlewares]
[http.middlewares.test-auth.forwardAuth]
address = "https://example.com/auth"
[http.middlewares.test-auth.forwardAuth.tls]
caOptional = true
```
#### `cert` #### `cert`
_Optional_ _Optional_

View file

@ -457,3 +457,14 @@ the value for the method label becomes `EXTENSION_METHOD`, instead of the reques
### Tracing ### Tracing
In `v2.6.1`, the Datadog tags added to a span changed from `service.name` to `traefik.service.name` and from `router.name` to `traefik.router.name`. In `v2.6.1`, the Datadog tags added to a span changed from `service.name` to `traefik.service.name` and from `router.name` to `traefik.router.name`.
## v2.8
### TLS client authentication
In `v2.8`, the `caOptional` option is deprecated as TLS client authentication is a server side option.
This option available in the ForwardAuth middleware, as well as in the HTTP, Consul, Etcd, Redis, ZooKeeper, Marathon, Consul Catalog, and Docker providers has no effect and must not be used anymore.
### Consul Enterprise Namespaces
In `v2.8`, the `namespace` option of Consul and Consul Catalog providers is deprecated, please use the `namespaces` options instead.

View file

@ -393,37 +393,6 @@ providers:
--providers.consulcatalog.endpoint.tls.ca=path/to/ca.crt --providers.consulcatalog.endpoint.tls.ca=path/to/ca.crt
``` ```
##### `caOptional`
_Optional_
The value of `caOptional` defines which policy should be used for the secure connection with TLS Client Authentication to Consul Catalog.
!!! warning ""
If `ca` is undefined, this option will be ignored, and no client certificate will be requested during the handshake. Any provided certificate will thus never be verified.
When this option is set to `true`, a client certificate is requested during the handshake but is not required. If a certificate is sent, it is required to be valid.
When this option is set to `false`, a client certificate is requested during the handshake, and at least one valid certificate should be sent by the client.
```yaml tab="File (YAML)"
providers:
consulCatalog:
endpoint:
tls:
caOptional: true
```
```toml tab="File (TOML)"
[providers.consulCatalog.endpoint.tls]
caOptional = true
```
```bash tab="CLI"
--providers.consulcatalog.endpoint.tls.caoptional=true
```
##### `cert` ##### `cert`
_Optional_ _Optional_
@ -556,7 +525,7 @@ providers:
``` ```
```bash tab="CLI" ```bash tab="CLI"
--providers.consulcatalog.defaultRule="Host(`{{ .Name }}.{{ index .Labels \"customLabel\"}}`)" --providers.consulcatalog.defaultRule=Host(`{{ .Name }}.{{ index .Labels \"customLabel\"}}`)
# ... # ...
``` ```
@ -700,30 +669,76 @@ For additional information, refer to [Restrict the Scope of Service Discovery](.
### `namespace` ### `namespace`
_Optional, Default=""_ ??? warning "Deprecated in favor of the [`namespaces`](#namespaces) option."
The `namespace` option defines the namespace in which the consul catalog services will be discovered. _Optional, Default=""_
!!! warning The `namespace` option defines the namespace in which the consul catalog services will be discovered.
!!! warning
The namespace option only works with [Consul Enterprise](https://www.consul.io/docs/enterprise), The namespace option only works with [Consul Enterprise](https://www.consul.io/docs/enterprise),
which provides the [Namespaces](https://www.consul.io/docs/enterprise/namespaces) feature. which provides the [Namespaces](https://www.consul.io/docs/enterprise/namespaces) feature.
!!! warning
One should only define either the `namespaces` option or the `namespace` option.
```yaml tab="File (YAML)"
providers:
consulCatalog:
namespace: "production"
# ...
```
```toml tab="File (TOML)"
[providers.consulCatalog]
namespace = "production"
# ...
```
```bash tab="CLI"
--providers.consulcatalog.namespace=production
# ...
```
### `namespaces`
_Optional, Default=""_
The `namespaces` option defines the namespaces in which the consul catalog services will be discovered.
When using the `namespaces` option, the discovered configuration object names will be suffixed as shown below:
```text
<resource-name>@consulcatalog-<namespace>
```
!!! warning
The namespaces option only works with [Consul Enterprise](https://www.consul.io/docs/enterprise),
which provides the [Namespaces](https://www.consul.io/docs/enterprise/namespaces) feature.
!!! warning
One should only define either the `namespaces` option or the `namespace` option.
```yaml tab="File (YAML)" ```yaml tab="File (YAML)"
providers: providers:
consulCatalog: consulCatalog:
namespace: "production" namespaces:
- "ns1"
- "ns2"
# ... # ...
``` ```
```toml tab="File (TOML)" ```toml tab="File (TOML)"
[providers.consulCatalog] [providers.consulCatalog]
namespace = "production" namespaces = ["ns1", "ns2"]
# ... # ...
``` ```
```bash tab="CLI" ```bash tab="CLI"
--providers.consulcatalog.namespace=production --providers.consulcatalog.namespaces=ns1,ns2
# ... # ...
``` ```

View file

@ -61,30 +61,76 @@ providers:
### `namespace` ### `namespace`
_Optional, Default=""_ ??? warning "Deprecated in favor of the [`namespaces`](#namespaces) option."
The `namespace` option defines the namespace to query. _Optional, Default=""_
!!! warning The `namespace` option defines the namespace to query.
!!! warning
The namespace option only works with [Consul Enterprise](https://www.consul.io/docs/enterprise), The namespace option only works with [Consul Enterprise](https://www.consul.io/docs/enterprise),
which provides the [Namespaces](https://www.consul.io/docs/enterprise/namespaces) feature. which provides the [Namespaces](https://www.consul.io/docs/enterprise/namespaces) feature.
```yaml tab="File (YAML)" !!! warning
providers:
One should only define either the `namespaces` option or the `namespace` option.
```yaml tab="File (YAML)"
providers:
consul: consul:
# ... # ...
namespace: "production" namespace: "production"
```
```toml tab="File (TOML)"
[providers.consul]
# ...
namespace = "production"
```
```bash tab="CLI"
--providers.consul.namespace=production
```
### `namespaces`
_Optional, Default=""_
The `namespaces` option defines the namespaces to query.
When using the `namespaces` option, the discovered configuration object names will be suffixed as shown below:
```text
<resource-name>@consul-<namespace>
```
!!! warning
The namespaces option only works with [Consul Enterprise](https://www.consul.io/docs/enterprise),
which provides the [Namespaces](https://www.consul.io/docs/enterprise/namespaces) feature.
!!! warning
One should only define either the `namespaces` option or the `namespace` option.
```yaml tab="File (YAML)"
providers:
consul:
namespaces:
- "ns1"
- "ns2"
# ...
``` ```
```toml tab="File (TOML)" ```toml tab="File (TOML)"
[providers.consul] [providers.consul]
namespaces = ["ns1", "ns2"]
# ... # ...
namespace = "production"
``` ```
```bash tab="CLI" ```bash tab="CLI"
--providers.consul.namespace=production --providers.consul.namespaces=ns1,ns2
# ...
``` ```
### `username` ### `username`
@ -185,36 +231,6 @@ providers:
--providers.consul.tls.ca=path/to/ca.crt --providers.consul.tls.ca=path/to/ca.crt
``` ```
#### `caOptional`
_Optional_
The value of `caOptional` defines which policy should be used for the secure connection with TLS Client Authentication to Consul.
!!! warning ""
If `ca` is undefined, this option will be ignored, and no client certificate will be requested during the handshake. Any provided certificate will thus never be verified.
When this option is set to `true`, a client certificate is requested during the handshake but is not required. If a certificate is sent, it is required to be valid.
When this option is set to `false`, a client certificate is requested during the handshake, and at least one valid certificate should be sent by the client.
```yaml tab="File (YAML)"
providers:
consul:
tls:
caOptional: true
```
```toml tab="File (TOML)"
[providers.consul.tls]
caOptional = true
```
```bash tab="CLI"
--providers.consul.tls.caOptional=true
```
#### `cert` #### `cert`
_Optional_ _Optional_

View file

@ -643,36 +643,6 @@ providers:
--providers.docker.tls.ca=path/to/ca.crt --providers.docker.tls.ca=path/to/ca.crt
``` ```
#### `caOptional`
_Optional_
The value of `caOptional` defines which policy should be used for the secure connection with TLS Client Authentication to Docker.
!!! warning ""
If `ca` is undefined, this option will be ignored, and no client certificate will be requested during the handshake. Any provided certificate will thus never be verified.
When this option is set to `true`, a client certificate is requested during the handshake but is not required. If a certificate is sent, it is required to be valid.
When this option is set to `false`, a client certificate is requested during the handshake, and at least one valid certificate should be sent by the client.
```yaml tab="File (YAML)"
providers:
docker:
tls:
caOptional: true
```
```toml tab="File (TOML)"
[providers.docker.tls]
caOptional = true
```
```bash tab="CLI"
--providers.docker.tls.caOptional=true
```
#### `cert` #### `cert`
`cert` is the path to the public certificate used for the secure connection to Docker. `cert` is the path to the public certificate used for the secure connection to Docker.

View file

@ -134,36 +134,6 @@ providers:
--providers.etcd.tls.ca=path/to/ca.crt --providers.etcd.tls.ca=path/to/ca.crt
``` ```
#### `caOptional`
_Optional_
The value of `caOptional` defines which policy should be used for the secure connection with TLS Client Authentication to etcd.
!!! warning ""
If `ca` is undefined, this option will be ignored, and no client certificate will be requested during the handshake. Any provided certificate will thus never be verified.
When this option is set to `true`, a client certificate is requested during the handshake but is not required. If a certificate is sent, it is required to be valid.
When this option is set to `false`, a client certificate is requested during the handshake, and at least one valid certificate should be sent by the client.
```yaml tab="File (YAML)"
providers:
etcd:
tls:
caOptional: true
```
```toml tab="File (TOML)"
[providers.etcd.tls]
caOptional = true
```
```bash tab="CLI"
--providers.etcd.tls.caOptional=true
```
#### `cert` #### `cert`
_Optional_ _Optional_

View file

@ -105,36 +105,6 @@ providers:
--providers.http.tls.ca=path/to/ca.crt --providers.http.tls.ca=path/to/ca.crt
``` ```
#### `caOptional`
_Optional_
The value of `caOptional` defines which policy should be used for the secure connection with TLS Client Authentication to the endpoint.
!!! warning ""
If `ca` is undefined, this option will be ignored, and no client certificate will be requested during the handshake. Any provided certificate will thus never be verified.
When this option is set to `true`, a client certificate is requested during the handshake but is not required. If a certificate is sent, it is required to be valid.
When this option is set to `false`, a client certificate is requested during the handshake, and at least one valid certificate should be sent by the client.
```yaml tab="File (YAML)"
providers:
http:
tls:
caOptional: true
```
```toml tab="File (TOML)"
[providers.http.tls]
caOptional = true
```
```bash tab="CLI"
--providers.http.tls.caOptional=true
```
#### `cert` #### `cert`
_Optional_ _Optional_

View file

@ -432,36 +432,6 @@ providers:
--providers.marathon.tls.ca=path/to/ca.crt --providers.marathon.tls.ca=path/to/ca.crt
``` ```
#### `caOptional`
_Optional_
The value of `caOptional` defines which policy should be used for the secure connection with TLS Client Authentication to Marathon.
!!! warning ""
If `ca` is undefined, this option will be ignored, and no client certificate will be requested during the handshake. Any provided certificate will thus never be verified.
When this option is set to `true`, a client certificate is requested during the handshake but is not required. If a certificate is sent, it is required to be valid.
When this option is set to `false`, a client certificate is requested during the handshake, and at least one valid certificate should be sent by the client.
```yaml tab="File (YAML)"
providers:
marathon:
tls:
caOptional: true
```
```toml tab="File (TOML)"
[providers.marathon.tls]
caOptional = true
```
```bash tab="CLI"
--providers.marathon.tls.caOptional=true
```
#### `cert` #### `cert`
_Optional_ _Optional_

View file

@ -134,36 +134,6 @@ providers:
--providers.redis.tls.ca=path/to/ca.crt --providers.redis.tls.ca=path/to/ca.crt
``` ```
#### `caOptional`
_Optional_
The value of `caOptional` defines which policy should be used for the secure connection with TLS Client Authentication to Redis.
!!! warning ""
If `ca` is undefined, this option will be ignored, and no client certificate will be requested during the handshake. Any provided certificate will thus never be verified.
When this option is set to `true`, a client certificate is requested during the handshake but is not required. If a certificate is sent, it is required to be valid.
When this option is set to `false`, a client certificate is requested during the handshake, and at least one valid certificate should be sent by the client.
```yaml tab="File (YAML)"
providers:
redis:
tls:
caOptional: true
```
```toml tab="File (TOML)"
[providers.redis.tls]
caOptional = true
```
```bash tab="CLI"
--providers.redis.tls.caOptional=true
```
#### `cert` #### `cert`
_Optional_ _Optional_

View file

@ -134,36 +134,6 @@ providers:
--providers.zookeeper.tls.ca=path/to/ca.crt --providers.zookeeper.tls.ca=path/to/ca.crt
``` ```
#### `caOptional`
_Optional_
The value of `caOptional` defines which policy should be used for the secure connection with TLS Client Authentication to Zookeeper.
!!! warning ""
If `ca` is undefined, this option will be ignored, and no client certificate will be requested during the handshake. Any provided certificate will thus never be verified.
When this option is set to `true`, a client certificate is requested during the handshake but is not required. If a certificate is sent, it is required to be valid.
When this option is set to `false`, a client certificate is requested during the handshake, and at least one valid certificate should be sent by the client.
```yaml tab="File (YAML)"
providers:
zooKeeper:
tls:
caOptional: true
```
```toml tab="File (TOML)"
[providers.zooKeeper.tls]
caOptional = true
```
```bash tab="CLI"
--providers.zookeeper.tls.caOptional=true
```
#### `cert` #### `cert`
_Optional_ _Optional_

View file

@ -11,6 +11,9 @@
- "traefik.http.middlewares.middleware02.buffering.retryexpression=foobar" - "traefik.http.middlewares.middleware02.buffering.retryexpression=foobar"
- "traefik.http.middlewares.middleware03.chain.middlewares=foobar, foobar" - "traefik.http.middlewares.middleware03.chain.middlewares=foobar, foobar"
- "traefik.http.middlewares.middleware04.circuitbreaker.expression=foobar" - "traefik.http.middlewares.middleware04.circuitbreaker.expression=foobar"
- "traefik.http.middlewares.middleware04.circuitbreaker.checkperiod=42s"
- "traefik.http.middlewares.middleware04.circuitbreaker.fallbackduration=42s"
- "traefik.http.middlewares.middleware04.circuitbreaker.recoveryduration=42s"
- "traefik.http.middlewares.middleware05.compress=true" - "traefik.http.middlewares.middleware05.compress=true"
- "traefik.http.middlewares.middleware05.compress.excludedcontenttypes=foobar, foobar" - "traefik.http.middlewares.middleware05.compress.excludedcontenttypes=foobar, foobar"
- "traefik.http.middlewares.middleware05.compress.minresponsebodybytes=42" - "traefik.http.middlewares.middleware05.compress.minresponsebodybytes=42"

View file

@ -125,6 +125,9 @@
[http.middlewares.Middleware04] [http.middlewares.Middleware04]
[http.middlewares.Middleware04.circuitBreaker] [http.middlewares.Middleware04.circuitBreaker]
expression = "foobar" expression = "foobar"
checkPeriod = "42s"
fallbackDuration = "42s"
recoveryDuration = "42s"
[http.middlewares.Middleware05] [http.middlewares.Middleware05]
[http.middlewares.Middleware05.compress] [http.middlewares.Middleware05.compress]
excludedContentTypes = ["foobar", "foobar"] excludedContentTypes = ["foobar", "foobar"]

View file

@ -128,6 +128,9 @@ http:
Middleware04: Middleware04:
circuitBreaker: circuitBreaker:
expression: foobar expression: foobar
checkPeriod: 42s
fallbackDuration: 42s
recoveryDuration: 42s
Middleware05: Middleware05:
compress: compress:
excludedContentTypes: excludedContentTypes:

View file

@ -12,7 +12,10 @@
| `traefik/http/middlewares/Middleware02/buffering/retryExpression` | `foobar` | | `traefik/http/middlewares/Middleware02/buffering/retryExpression` | `foobar` |
| `traefik/http/middlewares/Middleware03/chain/middlewares/0` | `foobar` | | `traefik/http/middlewares/Middleware03/chain/middlewares/0` | `foobar` |
| `traefik/http/middlewares/Middleware03/chain/middlewares/1` | `foobar` | | `traefik/http/middlewares/Middleware03/chain/middlewares/1` | `foobar` |
| `traefik/http/middlewares/Middleware04/circuitBreaker/checkPeriod` | `42s` |
| `traefik/http/middlewares/Middleware04/circuitBreaker/expression` | `foobar` | | `traefik/http/middlewares/Middleware04/circuitBreaker/expression` | `foobar` |
| `traefik/http/middlewares/Middleware04/circuitBreaker/fallbackDuration` | `42s` |
| `traefik/http/middlewares/Middleware04/circuitBreaker/recoveryDuration` | `42s` |
| `traefik/http/middlewares/Middleware05/compress/excludedContentTypes/0` | `foobar` | | `traefik/http/middlewares/Middleware05/compress/excludedContentTypes/0` | `foobar` |
| `traefik/http/middlewares/Middleware05/compress/excludedContentTypes/1` | `foobar` | | `traefik/http/middlewares/Middleware05/compress/excludedContentTypes/1` | `foobar` |
| `traefik/http/middlewares/Middleware05/compress/minResponseBodyBytes` | `42` | | `traefik/http/middlewares/Middleware05/compress/minResponseBodyBytes` | `42` |

View file

@ -11,6 +11,9 @@
"traefik.http.middlewares.middleware02.buffering.retryexpression": "foobar", "traefik.http.middlewares.middleware02.buffering.retryexpression": "foobar",
"traefik.http.middlewares.middleware03.chain.middlewares": "foobar, foobar", "traefik.http.middlewares.middleware03.chain.middlewares": "foobar, foobar",
"traefik.http.middlewares.middleware04.circuitbreaker.expression": "foobar", "traefik.http.middlewares.middleware04.circuitbreaker.expression": "foobar",
"traefik.http.middlewares.middleware04.circuitbreaker.checkperiod": "42s",
"traefik.http.middlewares.middleware04.circuitbreaker.fallbackduration": "42s",
"traefik.http.middlewares.middleware04.circuitbreaker.recoveryduration": "42s",
"traefik.http.middlewares.middleware05.compress": "true", "traefik.http.middlewares.middleware05.compress": "true",
"traefik.http.middlewares.middleware05.compress.excludedcontenttypes": "foobar, foobar", "traefik.http.middlewares.middleware05.compress.excludedcontenttypes": "foobar, foobar",
"traefik.http.middlewares.middleware05.compress.minresponsebodybytes": "42", "traefik.http.middlewares.middleware05.compress.minresponsebodybytes": "42",

View file

@ -91,8 +91,32 @@ spec:
circuitBreaker: circuitBreaker:
description: CircuitBreaker holds the circuit breaker configuration. description: CircuitBreaker holds the circuit breaker configuration.
properties: properties:
checkPeriod:
anyOf:
- type: integer
- type: string
description: CheckPeriod is the interval between successive checks
of the circuit breaker condition (when in standby state).
x-kubernetes-int-or-string: true
expression: expression:
description: Expression is the condition that triggers the tripped
state.
type: string type: string
fallbackDuration:
anyOf:
- type: integer
- type: string
description: FallbackDuration is the duration for which the circuit
breaker will wait before trying to recover (from a tripped state).
x-kubernetes-int-or-string: true
recoveryDuration:
anyOf:
- type: integer
- type: string
description: RecoveryDuration is the duration for which the circuit
breaker will try to recover (as soon as it is in recovering
state).
x-kubernetes-int-or-string: true
type: object type: object
compress: compress:
description: Compress holds the compress configuration. description: Compress holds the compress configuration.

View file

@ -36,9 +36,11 @@ spec:
spec: spec:
description: TLSStoreSpec configures a TLSStore resource. description: TLSStoreSpec configures a TLSStore resource.
properties: properties:
defaultCertificate: certificates:
description: DefaultCertificate holds a secret name for the TLSOption description: Certificates is a list of secret names, each secret holding
resource. a key/certificate pair to add to the store.
items:
description: Certificate holds a secret name for the TLSStore resource.
properties: properties:
secretName: secretName:
description: SecretName is the name of the referenced Kubernetes description: SecretName is the name of the referenced Kubernetes
@ -47,8 +49,18 @@ spec:
required: required:
- secretName - secretName
type: object type: object
type: array
defaultCertificate:
description: DefaultCertificate is the name of the secret holding
the default key/certificate pair for the store.
properties:
secretName:
description: SecretName is the name of the referenced Kubernetes
Secret to specify the certificate details.
type: string
required: required:
- defaultCertificate - secretName
type: object
type: object type: object
required: required:
- metadata - metadata

View file

@ -147,8 +147,11 @@ Subject alternative names.
`--entrypoints.<name>.http.tls.options`: `--entrypoints.<name>.http.tls.options`:
Default TLS options for the routers linked to the entry point. Default TLS options for the routers linked to the entry point.
`--entrypoints.<name>.http2.maxconcurrentstreams`:
Specifies the number of concurrent streams per connection that each client is allowed to initiate. (Default: ```250```)
`--entrypoints.<name>.http3`: `--entrypoints.<name>.http3`:
HTTP3 configuration. (Default: ```false```) HTTP/3 configuration. (Default: ```false```)
`--entrypoints.<name>.http3.advertisedport`: `--entrypoints.<name>.http3.advertisedport`:
UDP port to advertise, on which HTTP/3 is available. (Default: ```0```) UDP port to advertise, on which HTTP/3 is available. (Default: ```0```)
@ -400,7 +403,10 @@ Enable Consul backend with default settings. (Default: ```false```)
KV store endpoints (Default: ```127.0.0.1:8500```) KV store endpoints (Default: ```127.0.0.1:8500```)
`--providers.consul.namespace`: `--providers.consul.namespace`:
KV Namespace Sets the namespace used to discover the configuration (Consul Enterprise only).
`--providers.consul.namespaces`:
Sets the namespaces used to discover the configuration (Consul Enterprise only).
`--providers.consul.password`: `--providers.consul.password`:
KV Password KV Password
@ -489,11 +495,14 @@ Expose containers by default. (Default: ```true```)
`--providers.consulcatalog.namespace`: `--providers.consulcatalog.namespace`:
Sets the namespace used to discover services (Consul Enterprise only). Sets the namespace used to discover services (Consul Enterprise only).
`--providers.consulcatalog.namespaces`:
Sets the namespaces used to discover services (Consul Enterprise only).
`--providers.consulcatalog.prefix`: `--providers.consulcatalog.prefix`:
Prefix for consul service tags. Default 'traefik' (Default: ```traefik```) Prefix for consul service tags. (Default: ```traefik```)
`--providers.consulcatalog.refreshinterval`: `--providers.consulcatalog.refreshinterval`:
Interval for check Consul API. Default 15s (Default: ```15```) Interval for check Consul API. (Default: ```15```)
`--providers.consulcatalog.requireconsistent`: `--providers.consulcatalog.requireconsistent`:
Forces the read to be fully consistent. (Default: ```false```) Forces the read to be fully consistent. (Default: ```false```)
@ -591,9 +600,6 @@ Enable Etcd backend with default settings. (Default: ```false```)
`--providers.etcd.endpoints`: `--providers.etcd.endpoints`:
KV store endpoints (Default: ```127.0.0.1:2379```) KV store endpoints (Default: ```127.0.0.1:2379```)
`--providers.etcd.namespace`:
KV Namespace
`--providers.etcd.password`: `--providers.etcd.password`:
KV Password KV Password
@ -855,9 +861,6 @@ Enable Redis backend with default settings. (Default: ```false```)
`--providers.redis.endpoints`: `--providers.redis.endpoints`:
KV store endpoints (Default: ```127.0.0.1:6379```) KV store endpoints (Default: ```127.0.0.1:6379```)
`--providers.redis.namespace`:
KV Namespace
`--providers.redis.password`: `--providers.redis.password`:
KV Password KV Password
@ -897,9 +900,6 @@ Enable ZooKeeper backend with default settings. (Default: ```false```)
`--providers.zookeeper.endpoints`: `--providers.zookeeper.endpoints`:
KV store endpoints (Default: ```127.0.0.1:2181```) KV store endpoints (Default: ```127.0.0.1:2181```)
`--providers.zookeeper.namespace`:
KV Namespace
`--providers.zookeeper.password`: `--providers.zookeeper.password`:
KV Password KV Password

View file

@ -114,8 +114,11 @@ Trust only forwarded headers from selected IPs.
`TRAEFIK_ENTRYPOINTS_<NAME>_HTTP`: `TRAEFIK_ENTRYPOINTS_<NAME>_HTTP`:
HTTP configuration. HTTP configuration.
`TRAEFIK_ENTRYPOINTS_<NAME>_HTTP2_MAXCONCURRENTSTREAMS`:
Specifies the number of concurrent streams per connection that each client is allowed to initiate. (Default: ```250```)
`TRAEFIK_ENTRYPOINTS_<NAME>_HTTP3`: `TRAEFIK_ENTRYPOINTS_<NAME>_HTTP3`:
HTTP3 configuration. (Default: ```false```) HTTP/3 configuration. (Default: ```false```)
`TRAEFIK_ENTRYPOINTS_<NAME>_HTTP3_ADVERTISEDPORT`: `TRAEFIK_ENTRYPOINTS_<NAME>_HTTP3_ADVERTISEDPORT`:
UDP port to advertise, on which HTTP/3 is available. (Default: ```0```) UDP port to advertise, on which HTTP/3 is available. (Default: ```0```)
@ -456,11 +459,14 @@ Expose containers by default. (Default: ```true```)
`TRAEFIK_PROVIDERS_CONSULCATALOG_NAMESPACE`: `TRAEFIK_PROVIDERS_CONSULCATALOG_NAMESPACE`:
Sets the namespace used to discover services (Consul Enterprise only). Sets the namespace used to discover services (Consul Enterprise only).
`TRAEFIK_PROVIDERS_CONSULCATALOG_NAMESPACES`:
Sets the namespaces used to discover services (Consul Enterprise only).
`TRAEFIK_PROVIDERS_CONSULCATALOG_PREFIX`: `TRAEFIK_PROVIDERS_CONSULCATALOG_PREFIX`:
Prefix for consul service tags. Default 'traefik' (Default: ```traefik```) Prefix for consul service tags. (Default: ```traefik```)
`TRAEFIK_PROVIDERS_CONSULCATALOG_REFRESHINTERVAL`: `TRAEFIK_PROVIDERS_CONSULCATALOG_REFRESHINTERVAL`:
Interval for check Consul API. Default 15s (Default: ```15```) Interval for check Consul API. (Default: ```15```)
`TRAEFIK_PROVIDERS_CONSULCATALOG_REQUIRECONSISTENT`: `TRAEFIK_PROVIDERS_CONSULCATALOG_REQUIRECONSISTENT`:
Forces the read to be fully consistent. (Default: ```false```) Forces the read to be fully consistent. (Default: ```false```)
@ -478,7 +484,10 @@ Watch Consul API events. (Default: ```false```)
KV store endpoints (Default: ```127.0.0.1:8500```) KV store endpoints (Default: ```127.0.0.1:8500```)
`TRAEFIK_PROVIDERS_CONSUL_NAMESPACE`: `TRAEFIK_PROVIDERS_CONSUL_NAMESPACE`:
KV Namespace Sets the namespace used to discover the configuration (Consul Enterprise only).
`TRAEFIK_PROVIDERS_CONSUL_NAMESPACES`:
Sets the namespaces used to discover the configuration (Consul Enterprise only).
`TRAEFIK_PROVIDERS_CONSUL_PASSWORD`: `TRAEFIK_PROVIDERS_CONSUL_PASSWORD`:
KV Password KV Password
@ -591,9 +600,6 @@ Enable Etcd backend with default settings. (Default: ```false```)
`TRAEFIK_PROVIDERS_ETCD_ENDPOINTS`: `TRAEFIK_PROVIDERS_ETCD_ENDPOINTS`:
KV store endpoints (Default: ```127.0.0.1:2379```) KV store endpoints (Default: ```127.0.0.1:2379```)
`TRAEFIK_PROVIDERS_ETCD_NAMESPACE`:
KV Namespace
`TRAEFIK_PROVIDERS_ETCD_PASSWORD`: `TRAEFIK_PROVIDERS_ETCD_PASSWORD`:
KV Password KV Password
@ -855,9 +861,6 @@ Enable Redis backend with default settings. (Default: ```false```)
`TRAEFIK_PROVIDERS_REDIS_ENDPOINTS`: `TRAEFIK_PROVIDERS_REDIS_ENDPOINTS`:
KV store endpoints (Default: ```127.0.0.1:6379```) KV store endpoints (Default: ```127.0.0.1:6379```)
`TRAEFIK_PROVIDERS_REDIS_NAMESPACE`:
KV Namespace
`TRAEFIK_PROVIDERS_REDIS_PASSWORD`: `TRAEFIK_PROVIDERS_REDIS_PASSWORD`:
KV Password KV Password
@ -897,9 +900,6 @@ Enable ZooKeeper backend with default settings. (Default: ```false```)
`TRAEFIK_PROVIDERS_ZOOKEEPER_ENDPOINTS`: `TRAEFIK_PROVIDERS_ZOOKEEPER_ENDPOINTS`:
KV store endpoints (Default: ```127.0.0.1:2181```) KV store endpoints (Default: ```127.0.0.1:2181```)
`TRAEFIK_PROVIDERS_ZOOKEEPER_NAMESPACE`:
KV Namespace
`TRAEFIK_PROVIDERS_ZOOKEEPER_PASSWORD`: `TRAEFIK_PROVIDERS_ZOOKEEPER_PASSWORD`:
KV Password KV Password

View file

@ -47,6 +47,8 @@
[[entryPoints.EntryPoint0.http.tls.domains]] [[entryPoints.EntryPoint0.http.tls.domains]]
main = "foobar" main = "foobar"
sans = ["foobar", "foobar"] sans = ["foobar", "foobar"]
[entryPoints.EntryPoint0.http2]
maxConcurrentStreams = 42
[entryPoints.EntryPoint0.http3] [entryPoints.EntryPoint0.http3]
advertisedPort = 42 advertisedPort = 42
[entryPoints.EntryPoint0.udp] [entryPoints.EntryPoint0.udp]
@ -155,6 +157,7 @@
connectByDefault = true connectByDefault = true
serviceName = "foobar" serviceName = "foobar"
namespace = "foobar" namespace = "foobar"
namespaces = ["foobar", "foobar"]
watch = true watch = true
[providers.consulCatalog.endpoint] [providers.consulCatalog.endpoint]
address = "foobar" address = "foobar"
@ -188,6 +191,7 @@
password = "foobar" password = "foobar"
token = "foobar" token = "foobar"
namespace = "foobar" namespace = "foobar"
namespaces = ["foobar", "foobar"]
[providers.consul.tls] [providers.consul.tls]
ca = "foobar" ca = "foobar"
caOptional = true caOptional = true
@ -200,7 +204,6 @@
username = "foobar" username = "foobar"
password = "foobar" password = "foobar"
token = "foobar" token = "foobar"
namespace = "foobar"
[providers.etcd.tls] [providers.etcd.tls]
ca = "foobar" ca = "foobar"
caOptional = true caOptional = true
@ -213,7 +216,6 @@
username = "foobar" username = "foobar"
password = "foobar" password = "foobar"
token = "foobar" token = "foobar"
namespace = "foobar"
[providers.zooKeeper.tls] [providers.zooKeeper.tls]
ca = "foobar" ca = "foobar"
caOptional = true caOptional = true
@ -226,7 +228,6 @@
username = "foobar" username = "foobar"
password = "foobar" password = "foobar"
token = "foobar" token = "foobar"
namespace = "foobar"
[providers.redis.tls] [providers.redis.tls]
ca = "foobar" ca = "foobar"
caOptional = true caOptional = true

View file

@ -54,6 +54,8 @@ entryPoints:
sans: sans:
- foobar - foobar
- foobar - foobar
http2:
maxConcurrentStreams: 42
http3: http3:
advertisedPort: 42 advertisedPort: 42
udp: udp:
@ -167,6 +169,9 @@ providers:
connectByDefault: true connectByDefault: true
serviceName: foobar serviceName: foobar
namespace: foobar namespace: foobar
namespaces:
- foobar
- foobar
watch: true watch: true
endpoint: endpoint:
address: foobar address: foobar
@ -204,6 +209,9 @@ providers:
password: foobar password: foobar
token: foobar token: foobar
namespace: foobar namespace: foobar
namespaces:
- foobar
- foobar
tls: tls:
ca: foobar ca: foobar
caOptional: true caOptional: true
@ -218,7 +226,6 @@ providers:
username: foobar username: foobar
password: foobar password: foobar
token: foobar token: foobar
namespace: foobar
tls: tls:
ca: foobar ca: foobar
caOptional: true caOptional: true
@ -233,7 +240,6 @@ providers:
username: foobar username: foobar
password: foobar password: foobar
token: foobar token: foobar
namespace: foobar
tls: tls:
ca: foobar ca: foobar
caOptional: true caOptional: true
@ -248,7 +254,6 @@ providers:
username: foobar username: foobar
password: foobar password: foobar
token: foobar token: foobar
namespace: foobar
tls: tls:
ca: foobar ca: foobar
caOptional: true caOptional: true

View file

@ -105,6 +105,8 @@ They can be defined by using a file (YAML or TOML) or CLI arguments.
entryPoints: entryPoints:
name: name:
address: ":8888" # same as ":8888/tcp" address: ":8888" # same as ":8888/tcp"
http2:
maxConcurrentStreams: 42
http3: http3:
advertisedPort: 8888 advertisedPort: 8888
transport: transport:
@ -132,6 +134,8 @@ They can be defined by using a file (YAML or TOML) or CLI arguments.
[entryPoints] [entryPoints]
[entryPoints.name] [entryPoints.name]
address = ":8888" # same as ":8888/tcp" address = ":8888" # same as ":8888/tcp"
[entryPoints.name.http2]
maxConcurrentStreams = 42
[entryPoints.name.http3] [entryPoints.name.http3]
advertisedPort = 8888 advertisedPort = 8888
[entryPoints.name.transport] [entryPoints.name.transport]
@ -153,6 +157,7 @@ They can be defined by using a file (YAML or TOML) or CLI arguments.
```bash tab="CLI" ```bash tab="CLI"
## Static configuration ## Static configuration
--entryPoints.name.address=:8888 # same as :8888/tcp --entryPoints.name.address=:8888 # same as :8888/tcp
--entryPoints.name.http2.maxConcurrentStreams=42
--entryPoints.name.http3.advertisedport=8888 --entryPoints.name.http3.advertisedport=8888
--entryPoints.name.transport.lifeCycle.requestAcceptGraceTimeout=42 --entryPoints.name.transport.lifeCycle.requestAcceptGraceTimeout=42
--entryPoints.name.transport.lifeCycle.graceTimeOut=42 --entryPoints.name.transport.lifeCycle.graceTimeOut=42
@ -228,6 +233,32 @@ If both TCP and UDP are wanted for the same port, two entryPoints definitions ar
Full details for how to specify `address` can be found in [net.Listen](https://golang.org/pkg/net/#Listen) (and [net.Dial](https://golang.org/pkg/net/#Dial)) of the doc for go. Full details for how to specify `address` can be found in [net.Listen](https://golang.org/pkg/net/#Listen) (and [net.Dial](https://golang.org/pkg/net/#Dial)) of the doc for go.
### HTTP/2
#### `maxConcurrentStreams`
_Optional, Default=250_
`maxConcurrentStreams` specifies the number of concurrent streams per connection that each client is allowed to initiate.
The `maxConcurrentStreams` value must be greater than zero.
```yaml tab="File (YAML)"
entryPoints:
foo:
http2:
maxConcurrentStreams: 250
```
```toml tab="File (TOML)"
[entryPoints.foo]
[entryPoints.foo.http2]
maxConcurrentStreams = 250
```
```bash tab="CLI"
--entryPoints.name.http2.maxConcurrentStreams=250
```
### HTTP/3 ### HTTP/3
#### `http3` #### `http3`

View file

@ -1618,25 +1618,27 @@ or referencing TLS stores in the [`IngressRoute`](#kind-ingressroute) / [`Ingres
Traefik currently only uses the [TLS Store named "default"](../../https/tls.md#certificates-stores). Traefik currently only uses the [TLS Store named "default"](../../https/tls.md#certificates-stores).
This means that if you have two stores that are named default in different kubernetes namespaces, This means that if you have two stores that are named default in different kubernetes namespaces,
they may be randomly chosen. they may be randomly chosen.
For the time being, please only configure one TLSSTore named default. For the time being, please only configure one TLSStore named default.
!!! info "TLSStore Attributes" !!! info "TLSStore Attributes"
```yaml tab="TLSStore" ```yaml tab="TLSStore"
apiVersion: traefik.containo.us/v1alpha1 apiVersion: traefik.containo.us/v1alpha1
kind: TLSStore kind: TLSStore
metadata: metadata:
name: default name: default
namespace: default namespace: default
spec: spec:
defaultCertificate: certificates: # [1]
secretName: my-secret # [1] - secretName: foo
- secretName: bar
defaultCertificate: # [2]
secretName: secret
``` ```
| Ref | Attribute | Purpose | | Ref | Attribute | Purpose |
|-----|--------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------| |-----|----------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------|
| [1] | `secretName` | The name of the referenced Kubernetes [Secret](https://kubernetes.io/docs/concepts/configuration/secret/) that holds the default certificate for the store. | | [1] | `certificates` | List of Kubernetes [Secrets](https://kubernetes.io/docs/concepts/configuration/secret/), each of them holding a key/certificate pair to add to the store. |
| [2] | `defaultCertificate` | Name of a Kubernetes [Secret](https://kubernetes.io/docs/concepts/configuration/secret/) that holds the default key/certificate pair for the store. |
??? example "Declaring and referencing a TLSStore" ??? example "Declaring and referencing a TLSStore"

8
go.mod
View file

@ -36,7 +36,7 @@ require (
github.com/instana/go-sensor v1.38.3 github.com/instana/go-sensor v1.38.3
github.com/klauspost/compress v1.13.0 github.com/klauspost/compress v1.13.0
github.com/kvtools/valkeyrie v0.4.0 github.com/kvtools/valkeyrie v0.4.0
github.com/lucas-clemente/quic-go v0.25.0 github.com/lucas-clemente/quic-go v0.27.0
github.com/mailgun/ttlmap v0.0.0-20170619185759-c1c17f74874f github.com/mailgun/ttlmap v0.0.0-20170619185759-c1c17f74874f
github.com/miekg/dns v1.1.47 github.com/miekg/dns v1.1.47
github.com/mitchellh/copystructure v1.0.0 github.com/mitchellh/copystructure v1.0.0
@ -218,9 +218,9 @@ require (
github.com/mailgun/multibuf v0.1.2 // indirect github.com/mailgun/multibuf v0.1.2 // indirect
github.com/mailgun/timetools v0.0.0-20141028012446-7e6055773c51 // indirect github.com/mailgun/timetools v0.0.0-20141028012446-7e6055773c51 // indirect
github.com/marten-seemann/qpack v0.2.1 // indirect github.com/marten-seemann/qpack v0.2.1 // indirect
github.com/marten-seemann/qtls-go1-16 v0.1.4 // indirect github.com/marten-seemann/qtls-go1-16 v0.1.5 // indirect
github.com/marten-seemann/qtls-go1-17 v0.1.0 // indirect github.com/marten-seemann/qtls-go1-17 v0.1.1 // indirect
github.com/marten-seemann/qtls-go1-18 v0.1.0-beta.1 // indirect github.com/marten-seemann/qtls-go1-18 v0.1.1 // indirect
github.com/mattn/go-colorable v0.1.8 // indirect github.com/mattn/go-colorable v0.1.8 // indirect
github.com/mattn/go-isatty v0.0.12 // indirect github.com/mattn/go-isatty v0.0.12 // indirect
github.com/mattn/go-shellwords v1.0.12 // indirect github.com/mattn/go-shellwords v1.0.12 // indirect

17
go.sum
View file

@ -1137,8 +1137,8 @@ github.com/liquidweb/liquidweb-go v1.6.3/go.mod h1:SuXXp+thr28LnjEw18AYtWwIbWMHS
github.com/lithammer/dedent v1.1.0/go.mod h1:jrXYCQtgg0nJiN+StA2KgR7w6CiQNv9Fd/Z9BP0jIOc= github.com/lithammer/dedent v1.1.0/go.mod h1:jrXYCQtgg0nJiN+StA2KgR7w6CiQNv9Fd/Z9BP0jIOc=
github.com/looplab/fsm v0.1.0 h1:Qte7Zdn/5hBNbXzP7yxVU4OIFHWXBovyTT2LaBTyC20= github.com/looplab/fsm v0.1.0 h1:Qte7Zdn/5hBNbXzP7yxVU4OIFHWXBovyTT2LaBTyC20=
github.com/looplab/fsm v0.1.0/go.mod h1:m2VaOfDHxqXBBMgc26m6yUOwkFn8H2AlJDE+jd/uafI= github.com/looplab/fsm v0.1.0/go.mod h1:m2VaOfDHxqXBBMgc26m6yUOwkFn8H2AlJDE+jd/uafI=
github.com/lucas-clemente/quic-go v0.25.0 h1:K+X9Gvd7JXsOHtU0N2icZ2Nw3rx82uBej3mP4CLgibc= github.com/lucas-clemente/quic-go v0.27.0 h1:v6WY87q9zD4dKASbG8hy/LpzAVNzEQzw8sEIeloJsc4=
github.com/lucas-clemente/quic-go v0.25.0/go.mod h1:YtzP8bxRVCBlO77yRanE264+fY/T2U9ZlW1AaHOsMOg= github.com/lucas-clemente/quic-go v0.27.0/go.mod h1:AzgQoPda7N+3IqMMMkywBKggIFo2KT6pfnlrQ2QieeI=
github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
@ -1161,13 +1161,12 @@ github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJ
github.com/marstr/guid v1.1.0/go.mod h1:74gB1z2wpxxInTG6yaqA7KrtM0NZ+RbrcqDvYHefzho= github.com/marstr/guid v1.1.0/go.mod h1:74gB1z2wpxxInTG6yaqA7KrtM0NZ+RbrcqDvYHefzho=
github.com/marten-seemann/qpack v0.2.1 h1:jvTsT/HpCn2UZJdP+UUB53FfUUgeOyG5K1ns0OJOGVs= github.com/marten-seemann/qpack v0.2.1 h1:jvTsT/HpCn2UZJdP+UUB53FfUUgeOyG5K1ns0OJOGVs=
github.com/marten-seemann/qpack v0.2.1/go.mod h1:F7Gl5L1jIgN1D11ucXefiuJS9UMVP2opoCp2jDKb7wc= github.com/marten-seemann/qpack v0.2.1/go.mod h1:F7Gl5L1jIgN1D11ucXefiuJS9UMVP2opoCp2jDKb7wc=
github.com/marten-seemann/qtls-go1-15 v0.1.4/go.mod h1:GyFwywLKkRt+6mfU99csTEY1joMZz5vmB1WNZH3P81I= github.com/marten-seemann/qtls-go1-16 v0.1.5 h1:o9JrYPPco/Nukd/HpOHMHZoBDXQqoNtUCmny98/1uqQ=
github.com/marten-seemann/qtls-go1-16 v0.1.4 h1:xbHbOGGhrenVtII6Co8akhLEdrawwB2iHl5yhJRpnco= github.com/marten-seemann/qtls-go1-16 v0.1.5/go.mod h1:gNpI2Ol+lRS3WwSOtIUUtRwZEQMXjYK+dQSBFbethAk=
github.com/marten-seemann/qtls-go1-16 v0.1.4/go.mod h1:gNpI2Ol+lRS3WwSOtIUUtRwZEQMXjYK+dQSBFbethAk= github.com/marten-seemann/qtls-go1-17 v0.1.1 h1:DQjHPq+aOzUeh9/lixAGunn6rIOQyWChPSI4+hgW7jc=
github.com/marten-seemann/qtls-go1-17 v0.1.0 h1:P9ggrs5xtwiqXv/FHNwntmuLMNq3KaSIG93AtAZ48xk= github.com/marten-seemann/qtls-go1-17 v0.1.1/go.mod h1:C2ekUKcDdz9SDWxec1N/MvcXBpaX9l3Nx67XaR84L5s=
github.com/marten-seemann/qtls-go1-17 v0.1.0/go.mod h1:fz4HIxByo+LlWcreM4CZOYNuz3taBQ8rN2X6FqvaWo8= github.com/marten-seemann/qtls-go1-18 v0.1.1 h1:qp7p7XXUFL7fpBvSS1sWD+uSqPvzNQK43DH+/qEkj0Y=
github.com/marten-seemann/qtls-go1-18 v0.1.0-beta.1 h1:EnzzN9fPUkUck/1CuY1FlzBaIYMoiBsdwTNmNGkwUUM= github.com/marten-seemann/qtls-go1-18 v0.1.1/go.mod h1:mJttiymBAByA49mhlNZZGrH5u1uXYZJ+RW28Py7f4m4=
github.com/marten-seemann/qtls-go1-18 v0.1.0-beta.1/go.mod h1:PUhIQk19LoFt2174H4+an8TYvWOGjb/hHwphBeaDHwI=
github.com/matryer/moq v0.0.0-20190312154309-6cfb0558e1bd/go.mod h1:9ELz6aaclSIGnZBoaSLZ3NAl1VTufbOrXBPvtcy6WiQ= github.com/matryer/moq v0.0.0-20190312154309-6cfb0558e1bd/go.mod h1:9ELz6aaclSIGnZBoaSLZ3NAl1VTufbOrXBPvtcy6WiQ=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=

View file

@ -535,8 +535,32 @@ spec:
circuitBreaker: circuitBreaker:
description: CircuitBreaker holds the circuit breaker configuration. description: CircuitBreaker holds the circuit breaker configuration.
properties: properties:
checkPeriod:
anyOf:
- type: integer
- type: string
description: CheckPeriod is the interval between successive checks
of the circuit breaker condition (when in standby state).
x-kubernetes-int-or-string: true
expression: expression:
description: Expression is the condition that triggers the tripped
state.
type: string type: string
fallbackDuration:
anyOf:
- type: integer
- type: string
description: FallbackDuration is the duration for which the circuit
breaker will wait before trying to recover (from a tripped state).
x-kubernetes-int-or-string: true
recoveryDuration:
anyOf:
- type: integer
- type: string
description: RecoveryDuration is the duration for which the circuit
breaker will try to recover (as soon as it is in recovering
state).
x-kubernetes-int-or-string: true
type: object type: object
compress: compress:
description: Compress holds the compress configuration. description: Compress holds the compress configuration.
@ -1332,9 +1356,11 @@ spec:
spec: spec:
description: TLSStoreSpec configures a TLSStore resource. description: TLSStoreSpec configures a TLSStore resource.
properties: properties:
defaultCertificate: certificates:
description: DefaultCertificate holds a secret name for the TLSOption description: Certificates is a list of secret names, each secret holding
resource. a key/certificate pair to add to the store.
items:
description: Certificate holds a secret name for the TLSStore resource.
properties: properties:
secretName: secretName:
description: SecretName is the name of the referenced Kubernetes description: SecretName is the name of the referenced Kubernetes
@ -1343,8 +1369,18 @@ spec:
required: required:
- secretName - secretName
type: object type: object
type: array
defaultCertificate:
description: DefaultCertificate is the name of the secret holding
the default key/certificate pair for the store.
properties:
secretName:
description: SecretName is the name of the referenced Kubernetes
Secret to specify the certificate details.
type: string
required: required:
- defaultCertificate - secretName
type: object
type: object type: object
required: required:
- metadata - metadata

View file

@ -93,7 +93,21 @@ type Chain struct {
// CircuitBreaker holds the circuit breaker configuration. // CircuitBreaker holds the circuit breaker configuration.
type CircuitBreaker struct { type CircuitBreaker struct {
// Expression is the condition that triggers the tripped state.
Expression string `json:"expression,omitempty" toml:"expression,omitempty" yaml:"expression,omitempty" export:"true"` Expression string `json:"expression,omitempty" toml:"expression,omitempty" yaml:"expression,omitempty" export:"true"`
// CheckPeriod is the interval between successive checks of the circuit breaker condition (when in standby state).
CheckPeriod ptypes.Duration `json:"checkPeriod,omitempty" toml:"checkPeriod,omitempty" yaml:"checkPeriod,omitempty" export:"true"`
// FallbackDuration is the duration for which the circuit breaker will wait before trying to recover (from a tripped state).
FallbackDuration ptypes.Duration `json:"fallbackDuration,omitempty" toml:"fallbackDuration,omitempty" yaml:"fallbackDuration,omitempty" export:"true"`
// RecoveryDuration is the duration for which the circuit breaker will try to recover (as soon as it is in recovering state).
RecoveryDuration ptypes.Duration `json:"recoveryDuration,omitempty" toml:"recoveryDuration,omitempty" yaml:"recoveryDuration,omitempty" export:"true"`
}
// SetDefaults sets the default values on a RateLimit.
func (c *CircuitBreaker) SetDefaults() {
c.CheckPeriod = ptypes.Duration(100 * time.Millisecond)
c.FallbackDuration = ptypes.Duration(10 * time.Second)
c.RecoveryDuration = ptypes.Duration(10 * time.Second)
} }
// +k8s:deepcopy-gen=true // +k8s:deepcopy-gen=true

View file

@ -27,6 +27,9 @@ func TestDecodeConfiguration(t *testing.T) {
"traefik.http.middlewares.Middleware2.buffering.retryexpression": "foobar", "traefik.http.middlewares.Middleware2.buffering.retryexpression": "foobar",
"traefik.http.middlewares.Middleware3.chain.middlewares": "foobar, fiibar", "traefik.http.middlewares.Middleware3.chain.middlewares": "foobar, fiibar",
"traefik.http.middlewares.Middleware4.circuitbreaker.expression": "foobar", "traefik.http.middlewares.Middleware4.circuitbreaker.expression": "foobar",
"traefik.HTTP.Middlewares.Middleware4.circuitbreaker.checkperiod": "1s",
"traefik.HTTP.Middlewares.Middleware4.circuitbreaker.fallbackduration": "1s",
"traefik.HTTP.Middlewares.Middleware4.circuitbreaker.recoveryduration": "1s",
"traefik.http.middlewares.Middleware5.digestauth.headerfield": "foobar", "traefik.http.middlewares.Middleware5.digestauth.headerfield": "foobar",
"traefik.http.middlewares.Middleware5.digestauth.realm": "foobar", "traefik.http.middlewares.Middleware5.digestauth.realm": "foobar",
"traefik.http.middlewares.Middleware5.digestauth.removeheader": "true", "traefik.http.middlewares.Middleware5.digestauth.removeheader": "true",
@ -489,6 +492,9 @@ func TestDecodeConfiguration(t *testing.T) {
"Middleware4": { "Middleware4": {
CircuitBreaker: &dynamic.CircuitBreaker{ CircuitBreaker: &dynamic.CircuitBreaker{
Expression: "foobar", Expression: "foobar",
CheckPeriod: ptypes.Duration(time.Second),
FallbackDuration: ptypes.Duration(time.Second),
RecoveryDuration: ptypes.Duration(time.Second),
}, },
}, },
"Middleware5": { "Middleware5": {
@ -984,6 +990,9 @@ func TestEncodeConfiguration(t *testing.T) {
"Middleware4": { "Middleware4": {
CircuitBreaker: &dynamic.CircuitBreaker{ CircuitBreaker: &dynamic.CircuitBreaker{
Expression: "foobar", Expression: "foobar",
CheckPeriod: ptypes.Duration(time.Second),
FallbackDuration: ptypes.Duration(time.Second),
RecoveryDuration: ptypes.Duration(time.Second),
}, },
}, },
"Middleware5": { "Middleware5": {
@ -1191,6 +1200,9 @@ func TestEncodeConfiguration(t *testing.T) {
"traefik.HTTP.Middlewares.Middleware2.Buffering.RetryExpression": "foobar", "traefik.HTTP.Middlewares.Middleware2.Buffering.RetryExpression": "foobar",
"traefik.HTTP.Middlewares.Middleware3.Chain.Middlewares": "foobar, fiibar", "traefik.HTTP.Middlewares.Middleware3.Chain.Middlewares": "foobar, fiibar",
"traefik.HTTP.Middlewares.Middleware4.CircuitBreaker.Expression": "foobar", "traefik.HTTP.Middlewares.Middleware4.CircuitBreaker.Expression": "foobar",
"traefik.HTTP.Middlewares.Middleware4.CircuitBreaker.CheckPeriod": "1000000000",
"traefik.HTTP.Middlewares.Middleware4.CircuitBreaker.FallbackDuration": "1000000000",
"traefik.HTTP.Middlewares.Middleware4.CircuitBreaker.RecoveryDuration": "1000000000",
"traefik.HTTP.Middlewares.Middleware5.DigestAuth.HeaderField": "foobar", "traefik.HTTP.Middlewares.Middleware5.DigestAuth.HeaderField": "foobar",
"traefik.HTTP.Middlewares.Middleware5.DigestAuth.Realm": "foobar", "traefik.HTTP.Middlewares.Middleware5.DigestAuth.Realm": "foobar",
"traefik.HTTP.Middlewares.Middleware5.DigestAuth.RemoveHeader": "true", "traefik.HTTP.Middlewares.Middleware5.DigestAuth.RemoveHeader": "true",

View file

@ -16,7 +16,8 @@ type EntryPoint struct {
ProxyProtocol *ProxyProtocol `description:"Proxy-Protocol configuration." json:"proxyProtocol,omitempty" toml:"proxyProtocol,omitempty" yaml:"proxyProtocol,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` ProxyProtocol *ProxyProtocol `description:"Proxy-Protocol configuration." json:"proxyProtocol,omitempty" toml:"proxyProtocol,omitempty" yaml:"proxyProtocol,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
ForwardedHeaders *ForwardedHeaders `description:"Trust client forwarding headers." json:"forwardedHeaders,omitempty" toml:"forwardedHeaders,omitempty" yaml:"forwardedHeaders,omitempty" export:"true"` ForwardedHeaders *ForwardedHeaders `description:"Trust client forwarding headers." json:"forwardedHeaders,omitempty" toml:"forwardedHeaders,omitempty" yaml:"forwardedHeaders,omitempty" export:"true"`
HTTP HTTPConfig `description:"HTTP configuration." json:"http,omitempty" toml:"http,omitempty" yaml:"http,omitempty" export:"true"` HTTP HTTPConfig `description:"HTTP configuration." json:"http,omitempty" toml:"http,omitempty" yaml:"http,omitempty" export:"true"`
HTTP3 *HTTP3Config `description:"HTTP3 configuration." json:"http3,omitempty" toml:"http3,omitempty" yaml:"http3,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` HTTP2 *HTTP2Config `description:"HTTP/2 configuration." json:"http2,omitempty" toml:"http2,omitempty" yaml:"http2,omitempty" export:"true"`
HTTP3 *HTTP3Config `description:"HTTP/3 configuration." json:"http3,omitempty" toml:"http3,omitempty" yaml:"http3,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
UDP *UDPConfig `description:"UDP configuration." json:"udp,omitempty" toml:"udp,omitempty" yaml:"udp,omitempty"` UDP *UDPConfig `description:"UDP configuration." json:"udp,omitempty" toml:"udp,omitempty" yaml:"udp,omitempty"`
} }
@ -50,6 +51,8 @@ func (ep *EntryPoint) SetDefaults() {
ep.ForwardedHeaders = &ForwardedHeaders{} ep.ForwardedHeaders = &ForwardedHeaders{}
ep.UDP = &UDPConfig{} ep.UDP = &UDPConfig{}
ep.UDP.SetDefaults() ep.UDP.SetDefaults()
ep.HTTP2 = &HTTP2Config{}
ep.HTTP2.SetDefaults()
} }
// HTTPConfig is the HTTP configuration of an entry point. // HTTPConfig is the HTTP configuration of an entry point.
@ -59,9 +62,19 @@ type HTTPConfig struct {
TLS *TLSConfig `description:"Default TLS configuration for the routers linked to the entry point." json:"tls,omitempty" toml:"tls,omitempty" yaml:"tls,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` TLS *TLSConfig `description:"Default TLS configuration for the routers linked to the entry point." json:"tls,omitempty" toml:"tls,omitempty" yaml:"tls,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
} }
// HTTP2Config is the HTTP2 configuration of an entry point.
type HTTP2Config struct {
MaxConcurrentStreams int32 `description:"Specifies the number of concurrent streams per connection that each client is allowed to initiate." json:"maxConcurrentStreams,omitempty" toml:"maxConcurrentStreams,omitempty" yaml:"maxConcurrentStreams,omitempty" export:"true"`
}
// SetDefaults sets the default values.
func (c *HTTP2Config) SetDefaults() {
c.MaxConcurrentStreams = 250 // https://cs.opensource.google/go/x/net/+/cd36cc07:http2/server.go;l=58
}
// HTTP3Config is the HTTP3 configuration of an entry point. // HTTP3Config is the HTTP3 configuration of an entry point.
type HTTP3Config struct { type HTTP3Config struct {
AdvertisedPort int32 `description:"UDP port to advertise, on which HTTP/3 is available." json:"advertisedPort,omitempty" toml:"advertisedPort,omitempty" yaml:"advertisedPort,omitempty" export:"true"` AdvertisedPort int `description:"UDP port to advertise, on which HTTP/3 is available." json:"advertisedPort,omitempty" toml:"advertisedPort,omitempty" yaml:"advertisedPort,omitempty" export:"true"`
} }
// Redirections is a set of redirection for an entry point. // Redirections is a set of redirection for an entry point.

View file

@ -183,10 +183,10 @@ type Providers struct {
KubernetesGateway *gateway.Provider `description:"Enable Kubernetes gateway api provider with default settings." json:"kubernetesGateway,omitempty" toml:"kubernetesGateway,omitempty" yaml:"kubernetesGateway,omitempty" export:"true" label:"allowEmpty" file:"allowEmpty"` KubernetesGateway *gateway.Provider `description:"Enable Kubernetes gateway api provider with default settings." json:"kubernetesGateway,omitempty" toml:"kubernetesGateway,omitempty" yaml:"kubernetesGateway,omitempty" export:"true" label:"allowEmpty" file:"allowEmpty"`
Rest *rest.Provider `description:"Enable Rest backend with default settings." json:"rest,omitempty" toml:"rest,omitempty" yaml:"rest,omitempty" export:"true" label:"allowEmpty" file:"allowEmpty"` Rest *rest.Provider `description:"Enable Rest backend with default settings." json:"rest,omitempty" toml:"rest,omitempty" yaml:"rest,omitempty" export:"true" label:"allowEmpty" file:"allowEmpty"`
Rancher *rancher.Provider `description:"Enable Rancher backend with default settings." json:"rancher,omitempty" toml:"rancher,omitempty" yaml:"rancher,omitempty" export:"true" label:"allowEmpty" file:"allowEmpty"` Rancher *rancher.Provider `description:"Enable Rancher backend with default settings." json:"rancher,omitempty" toml:"rancher,omitempty" yaml:"rancher,omitempty" export:"true" label:"allowEmpty" file:"allowEmpty"`
ConsulCatalog *consulcatalog.Provider `description:"Enable ConsulCatalog backend with default settings." json:"consulCatalog,omitempty" toml:"consulCatalog,omitempty" yaml:"consulCatalog,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` ConsulCatalog *consulcatalog.ProviderBuilder `description:"Enable ConsulCatalog backend with default settings." json:"consulCatalog,omitempty" toml:"consulCatalog,omitempty" yaml:"consulCatalog,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
Ecs *ecs.Provider `description:"Enable AWS ECS backend with default settings." json:"ecs,omitempty" toml:"ecs,omitempty" yaml:"ecs,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` Ecs *ecs.Provider `description:"Enable AWS ECS backend with default settings." json:"ecs,omitempty" toml:"ecs,omitempty" yaml:"ecs,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
Consul *consul.Provider `description:"Enable Consul backend with default settings." json:"consul,omitempty" toml:"consul,omitempty" yaml:"consul,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` Consul *consul.ProviderBuilder `description:"Enable Consul backend with default settings." json:"consul,omitempty" toml:"consul,omitempty" yaml:"consul,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
Etcd *etcd.Provider `description:"Enable Etcd backend with default settings." json:"etcd,omitempty" toml:"etcd,omitempty" yaml:"etcd,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` Etcd *etcd.Provider `description:"Enable Etcd backend with default settings." json:"etcd,omitempty" toml:"etcd,omitempty" yaml:"etcd,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
ZooKeeper *zk.Provider `description:"Enable ZooKeeper backend with default settings." json:"zooKeeper,omitempty" toml:"zooKeeper,omitempty" yaml:"zooKeeper,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` ZooKeeper *zk.Provider `description:"Enable ZooKeeper backend with default settings." json:"zooKeeper,omitempty" toml:"zooKeeper,omitempty" yaml:"zooKeeper,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
Redis *redis.Provider `description:"Enable Redis backend with default settings." json:"redis,omitempty" toml:"redis,omitempty" yaml:"redis,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"` Redis *redis.Provider `description:"Enable Redis backend with default settings." json:"redis,omitempty" toml:"redis,omitempty" yaml:"redis,omitempty" label:"allowEmpty" file:"allowEmpty" export:"true"`
@ -257,7 +257,7 @@ func (c *Configuration) SetEffectiveConfiguration() {
c.Pilot.SetDefaults() c.Pilot.SetDefaults()
} }
// Disable Gateway API provider if not enabled in experimental // Disable Gateway API provider if not enabled in experimental.
if c.Experimental == nil || !c.Experimental.KubernetesGateway { if c.Experimental == nil || !c.Experimental.KubernetesGateway {
c.Providers.KubernetesGateway = nil c.Providers.KubernetesGateway = nil
} }
@ -328,6 +328,14 @@ func (c *Configuration) ValidateConfiguration() error {
acmeEmail = resolver.ACME.Email acmeEmail = resolver.ACME.Email
} }
if c.Providers.ConsulCatalog != nil && c.Providers.ConsulCatalog.Namespace != "" && len(c.Providers.ConsulCatalog.Namespaces) > 0 {
return fmt.Errorf("consul catalog provider cannot have both namespace and namespaces options configured")
}
if c.Providers.Consul != nil && c.Providers.Consul.Namespace != "" && len(c.Providers.Consul.Namespaces) > 0 {
return fmt.Errorf("consul provider cannot have both namespace and namespaces options configured")
}
return nil return nil
} }

View file

@ -3,6 +3,7 @@ package circuitbreaker
import ( import (
"context" "context"
"net/http" "net/http"
"time"
"github.com/opentracing/opentracing-go/ext" "github.com/opentracing/opentracing-go/ext"
"github.com/traefik/traefik/v2/pkg/config/dynamic" "github.com/traefik/traefik/v2/pkg/config/dynamic"
@ -12,9 +13,7 @@ import (
"github.com/vulcand/oxy/cbreaker" "github.com/vulcand/oxy/cbreaker"
) )
const ( const typeName = "CircuitBreaker"
typeName = "CircuitBreaker"
)
type circuitBreaker struct { type circuitBreaker struct {
circuitBreaker *cbreaker.CircuitBreaker circuitBreaker *cbreaker.CircuitBreaker
@ -27,9 +26,32 @@ func New(ctx context.Context, next http.Handler, confCircuitBreaker dynamic.Circ
logger := log.FromContext(middlewares.GetLoggerCtx(ctx, name, typeName)) logger := log.FromContext(middlewares.GetLoggerCtx(ctx, name, typeName))
logger.Debug("Creating middleware") logger.Debug("Creating middleware")
logger.Debug("Setting up with expression: %s", expression) logger.Debugf("Setting up with expression: %s", expression)
oxyCircuitBreaker, err := cbreaker.New(next, expression, createCircuitBreakerOptions(expression)) cbOpts := []cbreaker.CircuitBreakerOption{
cbreaker.Fallback(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
tracing.SetErrorWithEvent(req, "blocked by circuit-breaker (%q)", expression)
rw.WriteHeader(http.StatusServiceUnavailable)
if _, err := rw.Write([]byte(http.StatusText(http.StatusServiceUnavailable))); err != nil {
log.FromContext(req.Context()).Error(err)
}
})),
}
if confCircuitBreaker.CheckPeriod > 0 {
cbOpts = append(cbOpts, cbreaker.CheckPeriod(time.Duration(confCircuitBreaker.CheckPeriod)))
}
if confCircuitBreaker.FallbackDuration > 0 {
cbOpts = append(cbOpts, cbreaker.FallbackDuration(time.Duration(confCircuitBreaker.FallbackDuration)))
}
if confCircuitBreaker.RecoveryDuration > 0 {
cbOpts = append(cbOpts, cbreaker.RecoveryDuration(time.Duration(confCircuitBreaker.RecoveryDuration)))
}
oxyCircuitBreaker, err := cbreaker.New(next, expression, cbOpts...)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -39,18 +61,6 @@ func New(ctx context.Context, next http.Handler, confCircuitBreaker dynamic.Circ
}, nil }, nil
} }
// NewCircuitBreakerOptions returns a new CircuitBreakerOption.
func createCircuitBreakerOptions(expression string) cbreaker.CircuitBreakerOption {
return cbreaker.Fallback(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
tracing.SetErrorWithEvent(req, "blocked by circuit-breaker (%q)", expression)
rw.WriteHeader(http.StatusServiceUnavailable)
if _, err := rw.Write([]byte(http.StatusText(http.StatusServiceUnavailable))); err != nil {
log.FromContext(req.Context()).Error(err)
}
}))
}
func (c *circuitBreaker) GetTracingInformation() (string, ext.SpanKindEnum) { func (c *circuitBreaker) GetTracingInformation() (string, ext.SpanKindEnum) {
return c.name, tracing.SpanKindNoneEnum return c.name, tracing.SpanKindNoneEnum
} }

View file

@ -93,6 +93,7 @@ func (c *customErrors) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
if len(c.backendQuery) > 0 { if len(c.backendQuery) > 0 {
query = "/" + strings.TrimPrefix(c.backendQuery, "/") query = "/" + strings.TrimPrefix(c.backendQuery, "/")
query = strings.ReplaceAll(query, "{status}", strconv.Itoa(code)) query = strings.ReplaceAll(query, "{status}", strconv.Itoa(code))
query = strings.ReplaceAll(query, "{url}", url.QueryEscape(req.URL.String()))
} }
pageReq, err := newRequest("http://" + req.Host + query) pageReq, err := newRequest("http://" + req.Host + query)

View file

@ -133,6 +133,24 @@ func TestHandler(t *testing.T) {
assert.Contains(t, recorder.Body.String(), "localhost") assert.Contains(t, recorder.Body.String(), "localhost")
}, },
}, },
{
desc: "full query replacement",
errorPage: &dynamic.ErrorPage{Service: "error", Query: "/?status={status}&url={url}", Status: []string{"503"}},
backendCode: http.StatusServiceUnavailable,
backendErrorHandler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.RequestURI != "/?status=503&url=http%3A%2F%2Flocalhost%2Ftest%3Ffoo%3Dbar%26baz%3Dbuz" {
t.Log(r.RequestURI)
return
}
_, _ = fmt.Fprintln(w, "My 503 page.")
}),
validate: func(t *testing.T, recorder *httptest.ResponseRecorder) {
t.Helper()
assert.Equal(t, http.StatusServiceUnavailable, recorder.Code, "HTTP status")
assert.Contains(t, recorder.Body.String(), "My 503 page.")
},
},
} }
for _, test := range testCases { for _, test := range testCases {
@ -153,7 +171,7 @@ func TestHandler(t *testing.T) {
errorPageHandler, err := New(context.Background(), handler, *test.errorPage, serviceBuilderMock, "test") errorPageHandler, err := New(context.Background(), handler, *test.errorPage, serviceBuilderMock, "test")
require.NoError(t, err) require.NoError(t, err)
req := testhelpers.MustNewRequest(http.MethodGet, "http://localhost/test", nil) req := testhelpers.MustNewRequest(http.MethodGet, "http://localhost/test?foo=bar&baz=buz", nil)
recorder := httptest.NewRecorder() recorder := httptest.NewRecorder()
errorPageHandler.ServeHTTP(recorder, req) errorPageHandler.ServeHTTP(recorder, req)

View file

@ -109,11 +109,15 @@ func NewProviderAggregator(conf static.Providers) ProviderAggregator {
} }
if conf.ConsulCatalog != nil { if conf.ConsulCatalog != nil {
p.quietAddProvider(conf.ConsulCatalog) for _, pvd := range conf.ConsulCatalog.BuildProviders() {
p.quietAddProvider(pvd)
}
} }
if conf.Consul != nil { if conf.Consul != nil {
p.quietAddProvider(conf.Consul) for _, pvd := range conf.Consul.BuildProviders() {
p.quietAddProvider(pvd)
}
} }
if conf.Etcd != nil { if conf.Etcd != nil {

View file

@ -219,7 +219,7 @@ func TestDefaultRule(t *testing.T) {
Status: api.HealthPassing, Status: api.HealthPassing,
}, },
}, },
defaultRule: DefaultTemplateRule, defaultRule: defaultTemplateRule,
expected: &dynamic.Configuration{ expected: &dynamic.Configuration{
TCP: &dynamic.TCPConfiguration{ TCP: &dynamic.TCPConfiguration{
Routers: map[string]*dynamic.TCPRouter{}, Routers: map[string]*dynamic.TCPRouter{},
@ -262,8 +262,10 @@ func TestDefaultRule(t *testing.T) {
t.Parallel() t.Parallel()
p := Provider{ p := Provider{
Configuration: Configuration{
ExposedByDefault: true, ExposedByDefault: true,
DefaultRule: test.defaultRule, DefaultRule: test.defaultRule,
},
} }
err := p.Init() err := p.Init()
@ -2618,10 +2620,12 @@ func Test_buildConfiguration(t *testing.T) {
t.Parallel() t.Parallel()
p := Provider{ p := Provider{
Configuration: Configuration{
ExposedByDefault: true, ExposedByDefault: true,
DefaultRule: "Host(`{{ normalize .Name }}.traefik.wtf`)", DefaultRule: "Host(`{{ normalize .Name }}.traefik.wtf`)",
ConnectAware: test.ConnectAware, ConnectAware: test.ConnectAware,
Constraints: test.constraints, Constraints: test.constraints,
},
} }
err := p.Init() err := p.Init()
@ -2651,3 +2655,55 @@ func Test_buildConfiguration(t *testing.T) {
}) })
} }
} }
func TestNamespaces(t *testing.T) {
testCases := []struct {
desc string
namespace string
namespaces []string
expectedNamespaces []string
}{
{
desc: "no defined namespaces",
expectedNamespaces: []string{""},
},
{
desc: "deprecated: use of defined namespace",
namespace: "test-ns",
expectedNamespaces: []string{"test-ns"},
},
{
desc: "use of 1 defined namespaces",
namespaces: []string{"test-ns"},
expectedNamespaces: []string{"test-ns"},
},
{
desc: "use of multiple defined namespaces",
namespaces: []string{"test-ns1", "test-ns2", "test-ns3", "test-ns4"},
expectedNamespaces: []string{"test-ns1", "test-ns2", "test-ns3", "test-ns4"},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
pb := &ProviderBuilder{
Namespace: test.namespace,
Namespaces: test.namespaces,
}
assert.Equal(t, test.expectedNamespaces, extractNSFromProvider(pb.BuildProviders()))
})
}
}
func extractNSFromProvider(providers []*Provider) []string {
res := make([]string, len(providers))
for i, p := range providers {
res[i] = p.namespace
}
return res
}

View file

@ -22,8 +22,11 @@ import (
"github.com/traefik/traefik/v2/pkg/types" "github.com/traefik/traefik/v2/pkg/types"
) )
// DefaultTemplateRule The default template for the default rule. // defaultTemplateRule is the default template for the default rule.
const DefaultTemplateRule = "Host(`{{ normalize .Name }}`)" const defaultTemplateRule = "Host(`{{ normalize .Name }}`)"
// providerName is the Consul Catalog provider name.
const providerName = "consulcatalog"
var _ provider.Provider = (*Provider)(nil) var _ provider.Provider = (*Provider)(nil)
@ -41,12 +44,50 @@ type itemData struct {
ExtraConf configuration ExtraConf configuration
} }
// Provider holds configurations of the provider. // ProviderBuilder is responsible for constructing namespaced instances of the Consul Catalog provider.
type Provider struct { type ProviderBuilder struct {
Configuration `export:"true"`
// Deprecated: use Namespaces option instead.
Namespace string `description:"Sets the namespace used to discover services (Consul Enterprise only)." json:"namespace,omitempty" toml:"namespace,omitempty" yaml:"namespace,omitempty"`
Namespaces []string `description:"Sets the namespaces used to discover services (Consul Enterprise only)." json:"namespaces,omitempty" toml:"namespaces,omitempty" yaml:"namespaces,omitempty"`
}
// BuildProviders builds Consul Catalog provider instances for the given namespaces configuration.
func (p *ProviderBuilder) BuildProviders() []*Provider {
// We can warn about that, because we've already made sure before that
// Namespace and Namespaces are mutually exclusive.
if p.Namespace != "" {
log.WithoutContext().Warnf("Namespace option is deprecated, please use the Namespaces option instead.")
}
if len(p.Namespaces) == 0 {
return []*Provider{{
Configuration: p.Configuration,
name: providerName,
// p.Namespace could very well be empty.
namespace: p.Namespace,
}}
}
var providers []*Provider
for _, namespace := range p.Namespaces {
providers = append(providers, &Provider{
Configuration: p.Configuration,
name: providerName + "-" + namespace,
namespace: namespace,
})
}
return providers
}
// Configuration represents the Consul Catalog provider configuration.
type Configuration struct {
Constraints string `description:"Constraints is an expression that Traefik matches against the container's labels to determine whether to create any route for that container." json:"constraints,omitempty" toml:"constraints,omitempty" yaml:"constraints,omitempty" export:"true"` Constraints string `description:"Constraints is an expression that Traefik matches against the container's labels to determine whether to create any route for that container." json:"constraints,omitempty" toml:"constraints,omitempty" yaml:"constraints,omitempty" export:"true"`
Endpoint *EndpointConfig `description:"Consul endpoint settings" json:"endpoint,omitempty" toml:"endpoint,omitempty" yaml:"endpoint,omitempty" export:"true"` Endpoint *EndpointConfig `description:"Consul endpoint settings" json:"endpoint,omitempty" toml:"endpoint,omitempty" yaml:"endpoint,omitempty" export:"true"`
Prefix string `description:"Prefix for consul service tags. Default 'traefik'" json:"prefix,omitempty" toml:"prefix,omitempty" yaml:"prefix,omitempty" export:"true"` Prefix string `description:"Prefix for consul service tags." json:"prefix,omitempty" toml:"prefix,omitempty" yaml:"prefix,omitempty" export:"true"`
RefreshInterval ptypes.Duration `description:"Interval for check Consul API. Default 15s" json:"refreshInterval,omitempty" toml:"refreshInterval,omitempty" yaml:"refreshInterval,omitempty" export:"true"` RefreshInterval ptypes.Duration `description:"Interval for check Consul API." json:"refreshInterval,omitempty" toml:"refreshInterval,omitempty" yaml:"refreshInterval,omitempty" export:"true"`
RequireConsistent bool `description:"Forces the read to be fully consistent." json:"requireConsistent,omitempty" toml:"requireConsistent,omitempty" yaml:"requireConsistent,omitempty" export:"true"` RequireConsistent bool `description:"Forces the read to be fully consistent." json:"requireConsistent,omitempty" toml:"requireConsistent,omitempty" yaml:"requireConsistent,omitempty" export:"true"`
Stale bool `description:"Use stale consistency for catalog reads." json:"stale,omitempty" toml:"stale,omitempty" yaml:"stale,omitempty" export:"true"` Stale bool `description:"Use stale consistency for catalog reads." json:"stale,omitempty" toml:"stale,omitempty" yaml:"stale,omitempty" export:"true"`
Cache bool `description:"Use local agent caching for catalog reads." json:"cache,omitempty" toml:"cache,omitempty" yaml:"cache,omitempty" export:"true"` Cache bool `description:"Use local agent caching for catalog reads." json:"cache,omitempty" toml:"cache,omitempty" yaml:"cache,omitempty" export:"true"`
@ -55,9 +96,25 @@ type Provider struct {
ConnectAware bool `description:"Enable Consul Connect support." json:"connectAware,omitempty" toml:"connectAware,omitempty" yaml:"connectAware,omitempty" export:"true"` ConnectAware bool `description:"Enable Consul Connect support." json:"connectAware,omitempty" toml:"connectAware,omitempty" yaml:"connectAware,omitempty" export:"true"`
ConnectByDefault bool `description:"Consider every service as Connect capable by default." json:"connectByDefault,omitempty" toml:"connectByDefault,omitempty" yaml:"connectByDefault,omitempty" export:"true"` ConnectByDefault bool `description:"Consider every service as Connect capable by default." json:"connectByDefault,omitempty" toml:"connectByDefault,omitempty" yaml:"connectByDefault,omitempty" export:"true"`
ServiceName string `description:"Name of the Traefik service in Consul Catalog (needs to be registered via the orchestrator or manually)." json:"serviceName,omitempty" toml:"serviceName,omitempty" yaml:"serviceName,omitempty" export:"true"` ServiceName string `description:"Name of the Traefik service in Consul Catalog (needs to be registered via the orchestrator or manually)." json:"serviceName,omitempty" toml:"serviceName,omitempty" yaml:"serviceName,omitempty" export:"true"`
Namespace string `description:"Sets the namespace used to discover services (Consul Enterprise only)." json:"namespace,omitempty" toml:"namespace,omitempty" yaml:"namespace,omitempty" export:"true"`
Watch bool `description:"Watch Consul API events." json:"watch,omitempty" toml:"watch,omitempty" yaml:"watch,omitempty" export:"true"` Watch bool `description:"Watch Consul API events." json:"watch,omitempty" toml:"watch,omitempty" yaml:"watch,omitempty" export:"true"`
}
// SetDefaults sets the default values.
func (c *Configuration) SetDefaults() {
c.Endpoint = &EndpointConfig{}
c.RefreshInterval = ptypes.Duration(15 * time.Second)
c.Prefix = "traefik"
c.ExposedByDefault = true
c.DefaultRule = defaultTemplateRule
c.ServiceName = "traefik"
}
// Provider is the Consul Catalog provider implementation.
type Provider struct {
Configuration
name string
namespace string
client *api.Client client *api.Client
defaultRuleTpl *template.Template defaultRuleTpl *template.Template
certChan chan *connectCert certChan chan *connectCert
@ -81,17 +138,6 @@ type EndpointHTTPAuthConfig struct {
Password string `description:"Basic Auth password" json:"password,omitempty" toml:"password,omitempty" yaml:"password,omitempty" loggable:"false"` Password string `description:"Basic Auth password" json:"password,omitempty" toml:"password,omitempty" yaml:"password,omitempty" loggable:"false"`
} }
// SetDefaults sets the default values.
func (p *Provider) SetDefaults() {
endpoint := &EndpointConfig{}
p.Endpoint = endpoint
p.RefreshInterval = ptypes.Duration(15 * time.Second)
p.Prefix = "traefik"
p.ExposedByDefault = true
p.DefaultRule = DefaultTemplateRule
p.ServiceName = "traefik"
}
// Init the provider. // Init the provider.
func (p *Provider) Init() error { func (p *Provider) Init() error {
defaultRuleTpl, err := provider.MakeDefaultRuleTemplate(p.DefaultRule, nil) defaultRuleTpl, err := provider.MakeDefaultRuleTemplate(p.DefaultRule, nil)
@ -103,19 +149,24 @@ func (p *Provider) Init() error {
p.certChan = make(chan *connectCert, 1) p.certChan = make(chan *connectCert, 1)
p.watchServicesChan = make(chan struct{}, 1) p.watchServicesChan = make(chan struct{}, 1)
// In case they didn't initialize Provider with BuildProviders.
if p.name == "" {
p.name = providerName
}
return nil return nil
} }
// Provide allows the consul catalog provider to provide configurations to traefik using the given configuration channel. // Provide allows the consul catalog provider to provide configurations to traefik using the given configuration channel.
func (p *Provider) Provide(configurationChan chan<- dynamic.Message, pool *safe.Pool) error { func (p *Provider) Provide(configurationChan chan<- dynamic.Message, pool *safe.Pool) error {
var err error var err error
p.client, err = createClient(p.Namespace, p.Endpoint) p.client, err = createClient(p.namespace, p.Endpoint)
if err != nil { if err != nil {
return fmt.Errorf("failed to create consul client: %w", err) return fmt.Errorf("failed to create consul client: %w", err)
} }
pool.GoCtx(func(routineCtx context.Context) { pool.GoCtx(func(routineCtx context.Context) {
ctxLog := log.With(routineCtx, log.Str(log.ProviderName, "consulcatalog")) ctxLog := log.With(routineCtx, log.Str(log.ProviderName, p.name))
logger := log.FromContext(ctxLog) logger := log.FromContext(ctxLog)
operation := func() error { operation := func() error {
@ -210,7 +261,7 @@ func (p *Provider) loadConfiguration(ctx context.Context, certInfo *connectCert,
} }
configurationChan <- dynamic.Message{ configurationChan <- dynamic.Message{
ProviderName: "consulcatalog", ProviderName: p.name,
Configuration: p.buildConfiguration(ctx, data, certInfo), Configuration: p.buildConfiguration(ctx, data, certInfo),
} }

View file

@ -0,0 +1,43 @@
apiVersion: traefik.containo.us/v1alpha1
kind: TLSStore
metadata:
name: default
namespace: default
spec:
certificates:
- secretName: supersecret
---
apiVersion: v1
kind: Secret
metadata:
name: supersecret
namespace: default
data:
tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0=
tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCi0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0=
---
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
name: test.route
namespace: default
spec:
entryPoints:
- web
routes:
- match: Host(`foo.com`) && PathPrefix(`/bar`)
kind: Rule
priority: 12
services:
- name: whoami
port: 80
tls:
store:
name: default

View file

@ -179,18 +179,25 @@ func (p *Provider) Provide(configurationChan chan<- dynamic.Message, pool *safe.
} }
func (p *Provider) loadConfigurationFromCRD(ctx context.Context, client Client) *dynamic.Configuration { func (p *Provider) loadConfigurationFromCRD(ctx context.Context, client Client) *dynamic.Configuration {
tlsConfigs := make(map[string]*tls.CertAndStores) stores, tlsConfigs := buildTLSStores(ctx, client)
if tlsConfigs == nil {
tlsConfigs = make(map[string]*tls.CertAndStores)
}
conf := &dynamic.Configuration{ conf := &dynamic.Configuration{
// TODO: choose between mutating and returning tlsConfigs
HTTP: p.loadIngressRouteConfiguration(ctx, client, tlsConfigs), HTTP: p.loadIngressRouteConfiguration(ctx, client, tlsConfigs),
TCP: p.loadIngressRouteTCPConfiguration(ctx, client, tlsConfigs), TCP: p.loadIngressRouteTCPConfiguration(ctx, client, tlsConfigs),
UDP: p.loadIngressRouteUDPConfiguration(ctx, client), UDP: p.loadIngressRouteUDPConfiguration(ctx, client),
TLS: &dynamic.TLSConfiguration{ TLS: &dynamic.TLSConfiguration{
Certificates: getTLSConfig(tlsConfigs),
Options: buildTLSOptions(ctx, client), Options: buildTLSOptions(ctx, client),
Stores: buildTLSStores(ctx, client), Stores: stores,
}, },
} }
// Done after because tlsConfigs is mutated by the others above.
conf.TLS.Certificates = getTLSConfig(tlsConfigs)
for _, middleware := range client.GetMiddlewares() { for _, middleware := range client.GetMiddlewares() {
id := provider.Normalize(makeID(middleware.Namespace, middleware.Name)) id := provider.Normalize(makeID(middleware.Namespace, middleware.Name))
ctxMid := log.With(ctx, log.Str(log.MiddlewareName, id)) ctxMid := log.With(ctx, log.Str(log.MiddlewareName, id))
@ -243,6 +250,12 @@ func (p *Provider) loadConfigurationFromCRD(ctx context.Context, client Client)
continue continue
} }
circuitBreaker, err := createCircuitBreakerMiddleware(middleware.Spec.CircuitBreaker)
if err != nil {
log.FromContext(ctxMid).Errorf("Error while reading circuit breaker middleware: %v", err)
continue
}
conf.HTTP.Middlewares[id] = &dynamic.Middleware{ conf.HTTP.Middlewares[id] = &dynamic.Middleware{
AddPrefix: middleware.Spec.AddPrefix, AddPrefix: middleware.Spec.AddPrefix,
StripPrefix: middleware.Spec.StripPrefix, StripPrefix: middleware.Spec.StripPrefix,
@ -261,7 +274,7 @@ func (p *Provider) loadConfigurationFromCRD(ctx context.Context, client Client)
ForwardAuth: forwardAuth, ForwardAuth: forwardAuth,
InFlightReq: middleware.Spec.InFlightReq, InFlightReq: middleware.Spec.InFlightReq,
Buffering: middleware.Spec.Buffering, Buffering: middleware.Spec.Buffering,
CircuitBreaker: middleware.Spec.CircuitBreaker, CircuitBreaker: circuitBreaker,
Compress: middleware.Spec.Compress, Compress: middleware.Spec.Compress,
PassTLSClientCert: middleware.Spec.PassTLSClientCert, PassTLSClientCert: middleware.Spec.PassTLSClientCert,
Retry: retry, Retry: retry,
@ -425,6 +438,35 @@ func createPluginMiddleware(plugins map[string]apiextensionv1.JSON) (map[string]
return pc, nil return pc, nil
} }
func createCircuitBreakerMiddleware(circuitBreaker *v1alpha1.CircuitBreaker) (*dynamic.CircuitBreaker, error) {
if circuitBreaker == nil {
return nil, nil
}
cb := &dynamic.CircuitBreaker{Expression: circuitBreaker.Expression}
cb.SetDefaults()
if circuitBreaker.CheckPeriod != nil {
if err := cb.CheckPeriod.Set(circuitBreaker.CheckPeriod.String()); err != nil {
return nil, err
}
}
if circuitBreaker.FallbackDuration != nil {
if err := cb.FallbackDuration.Set(circuitBreaker.FallbackDuration.String()); err != nil {
return nil, err
}
}
if circuitBreaker.RecoveryDuration != nil {
if err := cb.RecoveryDuration.Set(circuitBreaker.RecoveryDuration.String()); err != nil {
return nil, err
}
}
return cb, nil
}
func createRateLimitMiddleware(rateLimit *v1alpha1.RateLimit) (*dynamic.RateLimit, error) { func createRateLimitMiddleware(rateLimit *v1alpha1.RateLimit) (*dynamic.RateLimit, error) {
if rateLimit == nil { if rateLimit == nil {
return nil, nil return nil, nil
@ -793,57 +835,86 @@ func buildTLSOptions(ctx context.Context, client Client) map[string]tls.Options
return tlsOptions return tlsOptions
} }
func buildTLSStores(ctx context.Context, client Client) map[string]tls.Store { func buildTLSStores(ctx context.Context, client Client) (map[string]tls.Store, map[string]*tls.CertAndStores) {
tlsStoreCRD := client.GetTLSStores() tlsStoreCRD := client.GetTLSStores()
var tlsStores map[string]tls.Store
if len(tlsStoreCRD) == 0 { if len(tlsStoreCRD) == 0 {
return tlsStores return nil, nil
} }
tlsStores = make(map[string]tls.Store)
var nsDefault []string var nsDefault []string
tlsStores := make(map[string]tls.Store)
tlsConfigs := make(map[string]*tls.CertAndStores)
for _, tlsStore := range tlsStoreCRD { for _, t := range tlsStoreCRD {
namespace := tlsStore.Namespace logger := log.FromContext(log.With(ctx, log.Str("TLSStore", t.Name), log.Str("namespace", t.Namespace)))
secretName := tlsStore.Spec.DefaultCertificate.SecretName
logger := log.FromContext(log.With(ctx, log.Str("tlsStore", tlsStore.Name), log.Str("namespace", namespace), log.Str("secretName", secretName)))
secret, exists, err := client.GetSecret(namespace, secretName) id := makeID(t.Namespace, t.Name)
// If the name is default, we override the default config.
if t.Name == tls.DefaultTLSStoreName {
id = t.Name
nsDefault = append(nsDefault, t.Namespace)
}
var tlsStore tls.Store
if t.Spec.DefaultCertificate != nil {
secretName := t.Spec.DefaultCertificate.SecretName
secret, exists, err := client.GetSecret(t.Namespace, secretName)
if err != nil { if err != nil {
logger.Errorf("Failed to fetch secret %s/%s: %v", namespace, secretName, err) logger.Errorf("Failed to fetch secret %s/%s: %v", t.Namespace, secretName, err)
continue continue
} }
if !exists { if !exists {
logger.Errorf("Secret %s/%s does not exist", namespace, secretName) logger.Errorf("Secret %s/%s does not exist", t.Namespace, secretName)
continue continue
} }
cert, key, err := getCertificateBlocks(secret, namespace, secretName) cert, key, err := getCertificateBlocks(secret, t.Namespace, secretName)
if err != nil { if err != nil {
logger.Errorf("Could not get certificate blocks: %v", err) logger.Errorf("Could not get certificate blocks: %v", err)
continue continue
} }
id := makeID(tlsStore.Namespace, tlsStore.Name) tlsStore.DefaultCertificate = &tls.Certificate{
// If the name is default, we override the default config.
if tlsStore.Name == tls.DefaultTLSStoreName {
id = tlsStore.Name
nsDefault = append(nsDefault, tlsStore.Namespace)
}
tlsStores[id] = tls.Store{
DefaultCertificate: &tls.Certificate{
CertFile: tls.FileOrContent(cert), CertFile: tls.FileOrContent(cert),
KeyFile: tls.FileOrContent(key), KeyFile: tls.FileOrContent(key),
},
} }
} }
if err := buildCertificates(client, id, t.Namespace, t.Spec.Certificates, tlsConfigs); err != nil {
logger.Errorf("Failed to load certificates: %v", err)
continue
}
tlsStores[id] = tlsStore
}
if len(nsDefault) > 1 { if len(nsDefault) > 1 {
delete(tlsStores, tls.DefaultTLSStoreName) delete(tlsStores, tls.DefaultTLSStoreName)
log.FromContext(ctx).Errorf("Default TLS Stores defined in multiple namespaces: %v", nsDefault) log.FromContext(ctx).Errorf("Default TLS Stores defined in multiple namespaces: %v", nsDefault)
} }
return tlsStores return tlsStores, tlsConfigs
}
// buildCertificates loads TLSStore certificates from secrets and sets them into tlsConfigs.
func buildCertificates(client Client, tlsStore, namespace string, certificates []v1alpha1.Certificate, tlsConfigs map[string]*tls.CertAndStores) error {
for _, c := range certificates {
configKey := namespace + "/" + c.SecretName
if _, tlsExists := tlsConfigs[configKey]; !tlsExists {
certAndStores, err := getTLS(client, c.SecretName, namespace)
if err != nil {
return fmt.Errorf("unable to read secret %s: %w", configKey, err)
}
certAndStores.Stores = []string{tlsStore}
tlsConfigs[configKey] = certAndStores
}
}
return nil
} }
func makeServiceKey(rule, ingressName string) (string, error) { func makeServiceKey(rule, ingressName string) (string, error) {

View file

@ -495,6 +495,7 @@ func namespaceOrFallback(lb v1alpha1.LoadBalancerSpec, fallback string) string {
return fallback return fallback
} }
// getTLSHTTP mutates tlsConfigs.
func getTLSHTTP(ctx context.Context, ingressRoute *v1alpha1.IngressRoute, k8sClient Client, tlsConfigs map[string]*tls.CertAndStores) error { func getTLSHTTP(ctx context.Context, ingressRoute *v1alpha1.IngressRoute, k8sClient Client, tlsConfigs map[string]*tls.CertAndStores) error {
if ingressRoute.Spec.TLS == nil { if ingressRoute.Spec.TLS == nil {
return nil return nil

View file

@ -269,6 +269,7 @@ func (p *Provider) loadTCPServers(client Client, namespace string, svc v1alpha1.
return servers, nil return servers, nil
} }
// getTLSTCP mutates tlsConfigs.
func getTLSTCP(ctx context.Context, ingressRoute *v1alpha1.IngressRouteTCP, k8sClient Client, tlsConfigs map[string]*tls.CertAndStores) error { func getTLSTCP(ctx context.Context, ingressRoute *v1alpha1.IngressRouteTCP, k8sClient Client, tlsConfigs map[string]*tls.CertAndStores) error {
if ingressRoute.Spec.TLS == nil { if ingressRoute.Spec.TLS == nil {
return nil return nil

View file

@ -3480,6 +3480,63 @@ func TestLoadIngressRoutes(t *testing.T) {
}, },
}, },
}, },
{
desc: "TLS with tls store containing certificates",
paths: []string{"services.yml", "with_tls_store_certificates.yml"},
expected: &dynamic.Configuration{
TLS: &dynamic.TLSConfiguration{
Certificates: []*tls.CertAndStores{
{
Certificate: tls.Certificate{
CertFile: tls.FileOrContent("-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----"),
KeyFile: tls.FileOrContent("-----BEGIN PRIVATE KEY-----\n-----END PRIVATE KEY-----"),
},
Stores: []string{"default"},
},
},
Stores: map[string]tls.Store{
"default": {},
},
},
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
TCP: &dynamic.TCPConfiguration{
Routers: map[string]*dynamic.TCPRouter{},
Middlewares: map[string]*dynamic.TCPMiddleware{},
Services: map[string]*dynamic.TCPService{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{
"default-test-route-6b204d94623b3df4370c": {
EntryPoints: []string{"web"},
Service: "default-test-route-6b204d94623b3df4370c",
Rule: "Host(`foo.com`) && PathPrefix(`/bar`)",
Priority: 12,
TLS: &dynamic.RouterTLSConfig{},
},
},
Middlewares: map[string]*dynamic.Middleware{},
Services: map[string]*dynamic.Service{
"default-test-route-6b204d94623b3df4370c": {
LoadBalancer: &dynamic.ServersLoadBalancer{
Servers: []dynamic.Server{
{
URL: "http://10.10.0.1:80",
},
{
URL: "http://10.10.0.2:80",
},
},
PassHostHeader: Bool(true),
},
},
},
ServersTransports: map[string]*dynamic.ServersTransport{},
},
},
},
{ {
desc: "TLS with tls store default two times", desc: "TLS with tls store default two times",
paths: []string{"services.yml", "with_tls_store.yml", "with_default_tls_store.yml"}, paths: []string{"services.yml", "with_tls_store.yml", "with_default_tls_store.yml"},

View file

@ -40,7 +40,7 @@ type MiddlewareSpec struct {
ForwardAuth *ForwardAuth `json:"forwardAuth,omitempty"` ForwardAuth *ForwardAuth `json:"forwardAuth,omitempty"`
InFlightReq *dynamic.InFlightReq `json:"inFlightReq,omitempty"` InFlightReq *dynamic.InFlightReq `json:"inFlightReq,omitempty"`
Buffering *dynamic.Buffering `json:"buffering,omitempty"` Buffering *dynamic.Buffering `json:"buffering,omitempty"`
CircuitBreaker *dynamic.CircuitBreaker `json:"circuitBreaker,omitempty"` CircuitBreaker *CircuitBreaker `json:"circuitBreaker,omitempty"`
Compress *dynamic.Compress `json:"compress,omitempty"` Compress *dynamic.Compress `json:"compress,omitempty"`
PassTLSClientCert *dynamic.PassTLSClientCert `json:"passTLSClientCert,omitempty"` PassTLSClientCert *dynamic.PassTLSClientCert `json:"passTLSClientCert,omitempty"`
Retry *Retry `json:"retry,omitempty"` Retry *Retry `json:"retry,omitempty"`
@ -59,6 +59,20 @@ type ErrorPage struct {
// +k8s:deepcopy-gen=true // +k8s:deepcopy-gen=true
// CircuitBreaker holds the circuit breaker configuration.
type CircuitBreaker struct {
// Expression is the condition that triggers the tripped state.
Expression string `json:"expression,omitempty" toml:"expression,omitempty" yaml:"expression,omitempty" export:"true"`
// CheckPeriod is the interval between successive checks of the circuit breaker condition (when in standby state).
CheckPeriod *intstr.IntOrString `json:"checkPeriod,omitempty" toml:"checkPeriod,omitempty" yaml:"checkPeriod,omitempty" export:"true"`
// FallbackDuration is the duration for which the circuit breaker will wait before trying to recover (from a tripped state).
FallbackDuration *intstr.IntOrString `json:"fallbackDuration,omitempty" toml:"fallbackDuration,omitempty" yaml:"fallbackDuration,omitempty" export:"true"`
// RecoveryDuration is the duration for which the circuit breaker will try to recover (as soon as it is in recovering state).
RecoveryDuration *intstr.IntOrString `json:"recoveryDuration,omitempty" toml:"recoveryDuration,omitempty" yaml:"recoveryDuration,omitempty" export:"true"`
}
// +k8s:deepcopy-gen=true
// Chain holds a chain of middlewares. // Chain holds a chain of middlewares.
type Chain struct { type Chain struct {
Middlewares []MiddlewareRef `json:"middlewares,omitempty"` Middlewares []MiddlewareRef `json:"middlewares,omitempty"`

View file

@ -20,13 +20,16 @@ type TLSStore struct {
// TLSStoreSpec configures a TLSStore resource. // TLSStoreSpec configures a TLSStore resource.
type TLSStoreSpec struct { type TLSStoreSpec struct {
DefaultCertificate DefaultCertificate `json:"defaultCertificate"` // DefaultCertificate is the name of the secret holding the default key/certificate pair for the store.
DefaultCertificate *Certificate `json:"defaultCertificate,omitempty"`
// Certificates is a list of secret names, each secret holding a key/certificate pair to add to the store.
Certificates []Certificate `json:"certificates,omitempty"`
} }
// +k8s:deepcopy-gen=true // +k8s:deepcopy-gen=true
// DefaultCertificate holds a secret name for the TLSOption resource. // Certificate holds a secret name for the TLSStore resource.
type DefaultCertificate struct { type Certificate struct {
// SecretName is the name of the referenced Kubernetes Secret to specify the certificate details. // SecretName is the name of the referenced Kubernetes Secret to specify the certificate details.
SecretName string `json:"secretName"` SecretName string `json:"secretName"`
} }

View file

@ -53,6 +53,22 @@ func (in *BasicAuth) DeepCopy() *BasicAuth {
return out return out
} }
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Certificate) DeepCopyInto(out *Certificate) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Certificate.
func (in *Certificate) DeepCopy() *Certificate {
if in == nil {
return nil
}
out := new(Certificate)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Chain) DeepCopyInto(out *Chain) { func (in *Chain) DeepCopyInto(out *Chain) {
*out = *in *out = *in
@ -74,6 +90,37 @@ func (in *Chain) DeepCopy() *Chain {
return out return out
} }
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *CircuitBreaker) DeepCopyInto(out *CircuitBreaker) {
*out = *in
if in.CheckPeriod != nil {
in, out := &in.CheckPeriod, &out.CheckPeriod
*out = new(intstr.IntOrString)
**out = **in
}
if in.FallbackDuration != nil {
in, out := &in.FallbackDuration, &out.FallbackDuration
*out = new(intstr.IntOrString)
**out = **in
}
if in.RecoveryDuration != nil {
in, out := &in.RecoveryDuration, &out.RecoveryDuration
*out = new(intstr.IntOrString)
**out = **in
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CircuitBreaker.
func (in *CircuitBreaker) DeepCopy() *CircuitBreaker {
if in == nil {
return nil
}
out := new(CircuitBreaker)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ClientAuth) DeepCopyInto(out *ClientAuth) { func (in *ClientAuth) DeepCopyInto(out *ClientAuth) {
*out = *in *out = *in
@ -111,22 +158,6 @@ func (in *ClientTLS) DeepCopy() *ClientTLS {
return out return out
} }
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *DefaultCertificate) DeepCopyInto(out *DefaultCertificate) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DefaultCertificate.
func (in *DefaultCertificate) DeepCopy() *DefaultCertificate {
if in == nil {
return nil
}
out := new(DefaultCertificate)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *DigestAuth) DeepCopyInto(out *DigestAuth) { func (in *DigestAuth) DeepCopyInto(out *DigestAuth) {
*out = *in *out = *in
@ -714,8 +745,8 @@ func (in *MiddlewareSpec) DeepCopyInto(out *MiddlewareSpec) {
} }
if in.CircuitBreaker != nil { if in.CircuitBreaker != nil {
in, out := &in.CircuitBreaker, &out.CircuitBreaker in, out := &in.CircuitBreaker, &out.CircuitBreaker
*out = new(dynamic.CircuitBreaker) *out = new(CircuitBreaker)
**out = **in (*in).DeepCopyInto(*out)
} }
if in.Compress != nil { if in.Compress != nil {
in, out := &in.Compress, &out.Compress in, out := &in.Compress, &out.Compress
@ -1382,7 +1413,7 @@ func (in *TLSStore) DeepCopyInto(out *TLSStore) {
*out = *in *out = *in
out.TypeMeta = in.TypeMeta out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
out.Spec = in.Spec in.Spec.DeepCopyInto(&out.Spec)
return return
} }
@ -1456,7 +1487,16 @@ func (in *TLSStoreRef) DeepCopy() *TLSStoreRef {
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *TLSStoreSpec) DeepCopyInto(out *TLSStoreSpec) { func (in *TLSStoreSpec) DeepCopyInto(out *TLSStoreSpec) {
*out = *in *out = *in
out.DefaultCertificate = in.DefaultCertificate if in.DefaultCertificate != nil {
in, out := &in.DefaultCertificate, &out.DefaultCertificate
*out = new(Certificate)
**out = **in
}
if in.Certificates != nil {
in, out := &in.Certificates, &out.Certificates
*out = make([]Certificate, len(*in))
copy(*out, *in)
}
return return
} }

View file

@ -4,30 +4,80 @@ import (
"errors" "errors"
"github.com/kvtools/valkeyrie/store" "github.com/kvtools/valkeyrie/store"
"github.com/traefik/traefik/v2/pkg/log"
"github.com/traefik/traefik/v2/pkg/provider" "github.com/traefik/traefik/v2/pkg/provider"
"github.com/traefik/traefik/v2/pkg/provider/kv" "github.com/traefik/traefik/v2/pkg/provider/kv"
) )
// providerName is the Consul provider name.
const providerName = "consul"
var _ provider.Provider = (*Provider)(nil) var _ provider.Provider = (*Provider)(nil)
// Provider holds configurations of the provider. // ProviderBuilder is responsible for constructing namespaced instances of the Consul provider.
type Provider struct { type ProviderBuilder struct {
kv.Provider `export:"true"` kv.Provider `export:"true"`
// Deprecated: use Namespaces instead.
Namespace string `description:"Sets the namespace used to discover the configuration (Consul Enterprise only)." json:"namespace,omitempty" toml:"namespace,omitempty" yaml:"namespace,omitempty"`
Namespaces []string `description:"Sets the namespaces used to discover the configuration (Consul Enterprise only)." json:"namespaces,omitempty" toml:"namespaces,omitempty" yaml:"namespaces,omitempty"`
} }
// SetDefaults sets the default values. // SetDefaults sets the default values.
func (p *Provider) SetDefaults() { func (p *ProviderBuilder) SetDefaults() {
p.Provider.SetDefaults() p.Provider.SetDefaults()
p.Endpoints = []string{"127.0.0.1:8500"} p.Endpoints = []string{"127.0.0.1:8500"}
} }
// BuildProviders builds Consul provider instances for the given namespaces configuration.
func (p *ProviderBuilder) BuildProviders() []*Provider {
// We can warn about that, because we've already made sure before that
// Namespace and Namespaces are mutually exclusive.
if p.Namespace != "" {
log.WithoutContext().Warnf("Namespace option is deprecated, please use the Namespaces option instead.")
}
if len(p.Namespaces) == 0 {
return []*Provider{{
Provider: p.Provider,
name: providerName,
// p.Namespace could very well be empty.
namespace: p.Namespace,
}}
}
var providers []*Provider
for _, namespace := range p.Namespaces {
providers = append(providers, &Provider{
Provider: p.Provider,
name: providerName + "-" + namespace,
namespace: namespace,
})
}
return providers
}
// Provider holds configurations of the provider.
type Provider struct {
kv.Provider
name string
namespace string
}
// Init the provider. // Init the provider.
func (p *Provider) Init() error { func (p *Provider) Init() error {
// Wildcard namespace allows fetching KV values from any namespace for recursive requests (see https://www.consul.io/api/kv#ns). // Wildcard namespace allows fetching KV values from any namespace for recursive requests (see https://www.consul.io/api/kv#ns).
// As we are not supporting multiple namespaces at the same time, wildcard namespace is not allowed. // As we are not supporting multiple namespaces at the same time, wildcard namespace is not allowed.
if p.Namespace == "*" { if p.namespace == "*" {
return errors.New("wildcard namespace is not supported") return errors.New("wildcard namespace is not supported")
} }
return p.Provider.Init(store.CONSUL, "consul") // In case they didn't initialize with BuildProviders.
if p.name == "" {
p.name = providerName
}
return p.Provider.Init(store.CONSUL, p.name, p.namespace)
} }

View file

@ -0,0 +1,59 @@
package consul
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestNamespaces(t *testing.T) {
testCases := []struct {
desc string
namespace string
namespaces []string
expectedNamespaces []string
}{
{
desc: "no defined namespaces",
expectedNamespaces: []string{""},
},
{
desc: "deprecated: use of defined namespace",
namespace: "test-ns",
expectedNamespaces: []string{"test-ns"},
},
{
desc: "use of 1 defined namespaces",
namespaces: []string{"test-ns"},
expectedNamespaces: []string{"test-ns"},
},
{
desc: "use of multiple defined namespaces",
namespaces: []string{"test-ns1", "test-ns2", "test-ns3", "test-ns4"},
expectedNamespaces: []string{"test-ns1", "test-ns2", "test-ns3", "test-ns4"},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
pb := &ProviderBuilder{
Namespace: test.namespace,
Namespaces: test.namespaces,
}
assert.Equal(t, test.expectedNamespaces, extractNSFromProvider(pb.BuildProviders()))
})
}
}
func extractNSFromProvider(providers []*Provider) []string {
res := make([]string, len(providers))
for i, p := range providers {
res[i] = p.namespace
}
return res
}

View file

@ -21,5 +21,5 @@ func (p *Provider) SetDefaults() {
// Init the provider. // Init the provider.
func (p *Provider) Init() error { func (p *Provider) Init() error {
return p.Provider.Init(store.ETCDV3, "etcd") return p.Provider.Init(store.ETCDV3, "etcd", "")
} }

View file

@ -30,12 +30,12 @@ type Provider struct {
Username string `description:"KV Username" json:"username,omitempty" toml:"username,omitempty" yaml:"username,omitempty" loggable:"false"` Username string `description:"KV Username" json:"username,omitempty" toml:"username,omitempty" yaml:"username,omitempty" loggable:"false"`
Password string `description:"KV Password" json:"password,omitempty" toml:"password,omitempty" yaml:"password,omitempty" loggable:"false"` Password string `description:"KV Password" json:"password,omitempty" toml:"password,omitempty" yaml:"password,omitempty" loggable:"false"`
Token string `description:"KV Token" json:"token,omitempty" toml:"token,omitempty" yaml:"token,omitempty" loggable:"false"` Token string `description:"KV Token" json:"token,omitempty" toml:"token,omitempty" yaml:"token,omitempty" loggable:"false"`
Namespace string `description:"KV Namespace" json:"namespace,omitempty" toml:"namespace,omitempty" yaml:"namespace,omitempty"`
TLS *types.ClientTLS `description:"Enable TLS support" json:"tls,omitempty" toml:"tls,omitempty" yaml:"tls,omitempty" export:"true" ` TLS *types.ClientTLS `description:"Enable TLS support" json:"tls,omitempty" toml:"tls,omitempty" yaml:"tls,omitempty" export:"true" `
name string
namespace string
storeType store.Backend storeType store.Backend
kvClient store.Store kvClient store.Store
name string
} }
// SetDefaults sets the default values. // SetDefaults sets the default values.
@ -44,11 +44,12 @@ func (p *Provider) SetDefaults() {
} }
// Init the provider. // Init the provider.
func (p *Provider) Init(storeType store.Backend, name string) error { func (p *Provider) Init(storeType store.Backend, name, namespace string) error {
ctx := log.With(context.Background(), log.Str(log.ProviderName, name)) ctx := log.With(context.Background(), log.Str(log.ProviderName, name))
p.storeType = storeType
p.name = name p.name = name
p.namespace = namespace
p.storeType = storeType
kvClient, err := p.createKVClient(ctx) kvClient, err := p.createKVClient(ctx)
if err != nil { if err != nil {
@ -167,7 +168,7 @@ func (p *Provider) createKVClient(ctx context.Context) (store.Store, error) {
Username: p.Username, Username: p.Username,
Password: p.Password, Password: p.Password,
Token: p.Token, Token: p.Token,
Namespace: p.Namespace, Namespace: p.namespace,
} }
if p.TLS != nil { if p.TLS != nil {

View file

@ -173,6 +173,9 @@ func Test_buildConfiguration(t *testing.T) {
"traefik/http/middlewares/Middleware03/chain/middlewares/0": "foobar", "traefik/http/middlewares/Middleware03/chain/middlewares/0": "foobar",
"traefik/http/middlewares/Middleware03/chain/middlewares/1": "foobar", "traefik/http/middlewares/Middleware03/chain/middlewares/1": "foobar",
"traefik/http/middlewares/Middleware04/circuitBreaker/expression": "foobar", "traefik/http/middlewares/Middleware04/circuitBreaker/expression": "foobar",
"traefik/http/middlewares/Middleware04/circuitBreaker/checkPeriod": "1s",
"traefik/http/middlewares/Middleware04/circuitBreaker/fallbackDuration": "1s",
"traefik/http/middlewares/Middleware04/circuitBreaker/recoveryDuration": "1s",
"traefik/http/middlewares/Middleware07/errors/status/0": "foobar", "traefik/http/middlewares/Middleware07/errors/status/0": "foobar",
"traefik/http/middlewares/Middleware07/errors/status/1": "foobar", "traefik/http/middlewares/Middleware07/errors/status/1": "foobar",
"traefik/http/middlewares/Middleware07/errors/service": "foobar", "traefik/http/middlewares/Middleware07/errors/service": "foobar",
@ -394,6 +397,9 @@ func Test_buildConfiguration(t *testing.T) {
"Middleware04": { "Middleware04": {
CircuitBreaker: &dynamic.CircuitBreaker{ CircuitBreaker: &dynamic.CircuitBreaker{
Expression: "foobar", Expression: "foobar",
CheckPeriod: ptypes.Duration(time.Second),
FallbackDuration: ptypes.Duration(time.Second),
RecoveryDuration: ptypes.Duration(time.Second),
}, },
}, },
"Middleware05": { "Middleware05": {

View file

@ -21,5 +21,5 @@ func (p *Provider) SetDefaults() {
// Init the provider. // Init the provider.
func (p *Provider) Init() error { func (p *Provider) Init() error {
return p.Provider.Init(store.REDIS, "redis") return p.Provider.Init(store.REDIS, "redis", "")
} }

View file

@ -21,5 +21,5 @@ func (p *Provider) SetDefaults() {
// Init the provider. // Init the provider.
func (p *Provider) Init() error { func (p *Provider) Init() error {
return p.Provider.Init(store.ZK, "zookeeper") return p.Provider.Init(store.ZK, "zookeeper", "")
} }

View file

@ -682,7 +682,8 @@ func TestDo_staticConfiguration(t *testing.T) {
Prefix: "MyPrefix", Prefix: "MyPrefix",
} }
config.Providers.ConsulCatalog = &consulcatalog.Provider{ config.Providers.ConsulCatalog = &consulcatalog.ProviderBuilder{
Configuration: consulcatalog.Configuration{
Constraints: `Label("foo", "bar")`, Constraints: `Label("foo", "bar")`,
Endpoint: &consulcatalog.EndpointConfig{ Endpoint: &consulcatalog.EndpointConfig{
Address: "MyAddress", Address: "MyAddress",
@ -709,6 +710,9 @@ func TestDo_staticConfiguration(t *testing.T) {
Cache: true, Cache: true,
ExposedByDefault: true, ExposedByDefault: true,
DefaultRule: "PathPrefix(`/`)", DefaultRule: "PathPrefix(`/`)",
},
Namespace: "ns",
Namespaces: []string{"ns1", "ns2"},
} }
config.Providers.Ecs = &ecs.Provider{ config.Providers.Ecs = &ecs.Provider{
@ -723,7 +727,7 @@ func TestDo_staticConfiguration(t *testing.T) {
SecretAccessKey: "AwsSecretAccessKey", SecretAccessKey: "AwsSecretAccessKey",
} }
config.Providers.Consul = &consul.Provider{ config.Providers.Consul = &consul.ProviderBuilder{
Provider: kv.Provider{ Provider: kv.Provider{
RootKey: "RootKey", RootKey: "RootKey",
Endpoints: nil, Endpoints: nil,
@ -737,6 +741,8 @@ func TestDo_staticConfiguration(t *testing.T) {
InsecureSkipVerify: true, InsecureSkipVerify: true,
}, },
}, },
Namespace: "ns",
Namespaces: []string{"ns1", "ns2"},
} }
config.Providers.Etcd = &etcd.Provider{ config.Providers.Etcd = &etcd.Provider{

View file

@ -206,7 +206,12 @@
"stale": true, "stale": true,
"cache": true, "cache": true,
"exposedByDefault": true, "exposedByDefault": true,
"defaultRule": "xxxx" "defaultRule": "xxxx",
"namespace": "xxxx",
"namespaces": [
"xxxx",
"xxxx"
]
}, },
"ecs": { "ecs": {
"constraints": "Label(\"foo\", \"bar\")", "constraints": "Label(\"foo\", \"bar\")",
@ -232,7 +237,12 @@
"cert": "xxxx", "cert": "xxxx",
"key": "xxxx", "key": "xxxx",
"insecureSkipVerify": true "insecureSkipVerify": true
} },
"namespace": "xxxx",
"namespaces": [
"xxxx",
"xxxx"
]
}, },
"etcd": { "etcd": {
"rootKey": "xxxx", "rootKey": "xxxx",

View file

@ -7,6 +7,8 @@ import (
stdlog "log" stdlog "log"
"net" "net"
"net/http" "net/http"
"os"
"strings"
"sync" "sync"
"syscall" "syscall"
"time" "time"
@ -507,6 +509,10 @@ type httpServer struct {
} }
func createHTTPServer(ctx context.Context, ln net.Listener, configuration *static.EntryPoint, withH2c bool, reqDecorator *requestdecorator.RequestDecorator) (*httpServer, error) { func createHTTPServer(ctx context.Context, ln net.Listener, configuration *static.EntryPoint, withH2c bool, reqDecorator *requestdecorator.RequestDecorator) (*httpServer, error) {
if configuration.HTTP2.MaxConcurrentStreams < 0 {
return nil, errors.New("max concurrent streams value must be greater than or equal to zero")
}
httpSwitcher := middlewares.NewHandlerSwitcher(router.BuildDefaultHTTPRouter()) httpSwitcher := middlewares.NewHandlerSwitcher(router.BuildDefaultHTTPRouter())
next, err := alice.New(requestdecorator.WrapHandler(reqDecorator)).Then(httpSwitcher) next, err := alice.New(requestdecorator.WrapHandler(reqDecorator)).Then(httpSwitcher)
@ -524,7 +530,9 @@ func createHTTPServer(ctx context.Context, ln net.Listener, configuration *stati
} }
if withH2c { if withH2c {
handler = h2c.NewHandler(handler, &http2.Server{}) handler = h2c.NewHandler(handler, &http2.Server{
MaxConcurrentStreams: uint32(configuration.HTTP2.MaxConcurrentStreams),
})
} }
serverHTTP := &http.Server{ serverHTTP := &http.Server{
@ -535,6 +543,20 @@ func createHTTPServer(ctx context.Context, ln net.Listener, configuration *stati
IdleTimeout: time.Duration(configuration.Transport.RespondingTimeouts.IdleTimeout), IdleTimeout: time.Duration(configuration.Transport.RespondingTimeouts.IdleTimeout),
} }
// ConfigureServer configures HTTP/2 with the MaxConcurrentStreams option for the given server.
// Also keeping behavior the same as
// https://cs.opensource.google/go/go/+/refs/tags/go1.17.7:src/net/http/server.go;l=3262
if !strings.Contains(os.Getenv("GODEBUG"), "http2server=0") {
err = http2.ConfigureServer(serverHTTP, &http2.Server{
MaxConcurrentStreams: uint32(configuration.HTTP2.MaxConcurrentStreams),
NewWriteScheduler: func() http2.WriteScheduler { return http2.NewPriorityWriteScheduler(nil) },
})
if err != nil {
return nil, fmt.Errorf("configure HTTP/2 server: %w", err)
}
}
listener := newHTTPForwarder(ln) listener := newHTTPForwarder(ln)
go func() { go func() {
err := serverHTTP.Serve(listener) err := serverHTTP.Serve(listener)

View file

@ -47,7 +47,7 @@ func newHTTP3Server(ctx context.Context, configuration *static.EntryPoint, https
} }
h3.Server = &http3.Server{ h3.Server = &http3.Server{
Port: uint32(configuration.HTTP3.AdvertisedPort), Port: configuration.HTTP3.AdvertisedPort,
Server: &http.Server{ Server: &http.Server{
Addr: configuration.GetAddress(), Addr: configuration.GetAddress(),
Handler: httpsServer.Server.(*http.Server).Handler, Handler: httpsServer.Server.(*http.Server).Handler,

View file

@ -88,6 +88,7 @@ func TestHTTP3AdvertisedPort(t *testing.T) {
Address: "127.0.0.1:8090", Address: "127.0.0.1:8090",
Transport: epConfig, Transport: epConfig,
ForwardedHeaders: &static.ForwardedHeaders{}, ForwardedHeaders: &static.ForwardedHeaders{},
HTTP2: &static.HTTP2Config{},
HTTP3: &static.HTTP3Config{ HTTP3: &static.HTTP3Config{
AdvertisedPort: 8080, AdvertisedPort: 8080,
}, },

View file

@ -83,6 +83,7 @@ func testShutdown(t *testing.T, router *tcprouter.Router) {
Address: "127.0.0.1:0", Address: "127.0.0.1:0",
Transport: epConfig, Transport: epConfig,
ForwardedHeaders: &static.ForwardedHeaders{}, ForwardedHeaders: &static.ForwardedHeaders{},
HTTP2: &static.HTTP2Config{},
}, nil) }, nil)
require.NoError(t, err) require.NoError(t, err)
@ -166,6 +167,7 @@ func TestReadTimeoutWithoutFirstByte(t *testing.T) {
Address: ":0", Address: ":0",
Transport: epConfig, Transport: epConfig,
ForwardedHeaders: &static.ForwardedHeaders{}, ForwardedHeaders: &static.ForwardedHeaders{},
HTTP2: &static.HTTP2Config{},
}, nil) }, nil)
require.NoError(t, err) require.NoError(t, err)
@ -202,6 +204,7 @@ func TestReadTimeoutWithFirstByte(t *testing.T) {
Address: ":0", Address: ":0",
Transport: epConfig, Transport: epConfig,
ForwardedHeaders: &static.ForwardedHeaders{}, ForwardedHeaders: &static.ForwardedHeaders{},
HTTP2: &static.HTTP2Config{},
}, nil) }, nil)
require.NoError(t, err) require.NoError(t, err)

View file

@ -46,7 +46,7 @@ func NewProxy(address string, terminationDelay time.Duration, proxyProtocol *dyn
// ServeTCP forwards the connection to a service. // ServeTCP forwards the connection to a service.
func (p *Proxy) ServeTCP(conn WriteCloser) { func (p *Proxy) ServeTCP(conn WriteCloser) {
log.WithoutContext().Debugf("Handling connection from %s", conn.RemoteAddr()) log.WithoutContext().Debugf("Handling connection from %s to %s", conn.RemoteAddr(), p.address)
// needed because of e.g. server.trackedConnection // needed because of e.g. server.trackedConnection
defer conn.Close() defer conn.Close()

View file

@ -17,6 +17,7 @@ import (
// CA, Cert and Key can be either path or file contents. // CA, Cert and Key can be either path or file contents.
type ClientTLS struct { type ClientTLS struct {
CA string `description:"TLS CA" json:"ca,omitempty" toml:"ca,omitempty" yaml:"ca,omitempty"` CA string `description:"TLS CA" json:"ca,omitempty" toml:"ca,omitempty" yaml:"ca,omitempty"`
// Deprecated: TLS client authentication is a server side option (see https://github.com/golang/go/blob/740a490f71d026bb7d2d13cb8fa2d6d6e0572b70/src/crypto/tls/common.go#L634).
CAOptional bool `description:"TLS CA.Optional" json:"caOptional,omitempty" toml:"caOptional,omitempty" yaml:"caOptional,omitempty" export:"true"` CAOptional bool `description:"TLS CA.Optional" json:"caOptional,omitempty" toml:"caOptional,omitempty" yaml:"caOptional,omitempty" export:"true"`
Cert string `description:"TLS cert" json:"cert,omitempty" toml:"cert,omitempty" yaml:"cert,omitempty"` Cert string `description:"TLS cert" json:"cert,omitempty" toml:"cert,omitempty" yaml:"cert,omitempty"`
Key string `description:"TLS key" json:"key,omitempty" toml:"key,omitempty" yaml:"key,omitempty" loggable:"false"` Key string `description:"TLS key" json:"key,omitempty" toml:"key,omitempty" yaml:"key,omitempty" loggable:"false"`
@ -30,10 +31,13 @@ func (clientTLS *ClientTLS) CreateTLSConfig(ctx context.Context) (*tls.Config, e
return nil, nil return nil, nil
} }
if clientTLS.CAOptional {
log.FromContext(ctx).Warn("CAOptional is deprecated, TLS client authentication is a server side option.")
}
// Not initialized, to rely on system bundle. // Not initialized, to rely on system bundle.
var caPool *x509.CertPool var caPool *x509.CertPool
clientAuth := tls.NoClientCert
if clientTLS.CA != "" { if clientTLS.CA != "" {
var ca []byte var ca []byte
if _, errCA := os.Stat(clientTLS.CA); errCA == nil { if _, errCA := os.Stat(clientTLS.CA); errCA == nil {
@ -50,12 +54,6 @@ func (clientTLS *ClientTLS) CreateTLSConfig(ctx context.Context) (*tls.Config, e
if !caPool.AppendCertsFromPEM(ca) { if !caPool.AppendCertsFromPEM(ca) {
return nil, errors.New("failed to parse CA") return nil, errors.New("failed to parse CA")
} }
if clientTLS.CAOptional {
clientAuth = tls.VerifyClientCertIfGiven
} else {
clientAuth = tls.RequireAndVerifyClientCert
}
} }
hasCert := len(clientTLS.Cert) > 0 hasCert := len(clientTLS.Cert) > 0
@ -69,7 +67,6 @@ func (clientTLS *ClientTLS) CreateTLSConfig(ctx context.Context) (*tls.Config, e
return &tls.Config{ return &tls.Config{
RootCAs: caPool, RootCAs: caPool,
InsecureSkipVerify: clientTLS.InsecureSkipVerify, InsecureSkipVerify: clientTLS.InsecureSkipVerify,
ClientAuth: clientAuth,
}, nil }, nil
} }
@ -82,7 +79,6 @@ func (clientTLS *ClientTLS) CreateTLSConfig(ctx context.Context) (*tls.Config, e
Certificates: []tls.Certificate{cert}, Certificates: []tls.Certificate{cert},
RootCAs: caPool, RootCAs: caPool,
InsecureSkipVerify: clientTLS.InsecureSkipVerify, InsecureSkipVerify: clientTLS.InsecureSkipVerify,
ClientAuth: clientAuth,
}, nil }, nil
} }

View file

@ -20,7 +20,7 @@ func NewProxy(address string) (*Proxy, error) {
// ServeUDP implements the Handler interface. // ServeUDP implements the Handler interface.
func (p *Proxy) ServeUDP(conn *Conn) { func (p *Proxy) ServeUDP(conn *Conn) {
log.WithoutContext().Debugf("Handling connection from %s", conn.rAddr) log.WithoutContext().Debugf("Handling connection from %s to %s", conn.rAddr, p.target)
// needed because of e.g. server.trackedConnection // needed because of e.g. server.trackedConnection
defer conn.Close() defer conn.Close()

View file

@ -1142,9 +1142,15 @@ export default {
getProviderLogoPath (provider) { getProviderLogoPath (provider) {
const name = provider.toLowerCase() const name = provider.toLowerCase()
if (name.includes('plugin-')) { if (name.startsWith('plugin-')) {
return 'statics/providers/plugin.svg' return 'statics/providers/plugin.svg'
} }
if (name.startsWith('consul-')) {
return `statics/providers/consul.svg`
}
if (name.startsWith('consulcatalog-')) {
return `statics/providers/consulcatalog.svg`
}
return `statics/providers/${name}.svg` return `statics/providers/${name}.svg`
} }

View file

@ -66,9 +66,15 @@ export default {
const provider = this.getProvider(service) const provider = this.getProvider(service)
const name = provider.toLowerCase() const name = provider.toLowerCase()
if (name.includes('plugin-')) { if (name.startsWith('plugin-')) {
return 'statics/providers/plugin.svg' return 'statics/providers/plugin.svg'
} }
if (name.startsWith('consul-')) {
return `statics/providers/consul.svg`
}
if (name.startsWith('consulcatalog-')) {
return `statics/providers/consulcatalog.svg`
}
return `statics/providers/${name}.svg` return `statics/providers/${name}.svg`
} }

View file

@ -132,9 +132,15 @@ export default {
getProviderLogoPath () { getProviderLogoPath () {
const name = this.data.provider.toLowerCase() const name = this.data.provider.toLowerCase()
if (name.includes('plugin-')) { if (name.startsWith('plugin-')) {
return 'statics/providers/plugin.svg' return 'statics/providers/plugin.svg'
} }
if (name.startsWith('consul-')) {
return `statics/providers/consul.svg`
}
if (name.startsWith('consulcatalog-')) {
return `statics/providers/consulcatalog.svg`
}
return `statics/providers/${name}.svg` return `statics/providers/${name}.svg`
} }

View file

@ -146,9 +146,15 @@ export default {
getProviderLogoPath () { getProviderLogoPath () {
const name = this.data.provider.toLowerCase() const name = this.data.provider.toLowerCase()
if (name.includes('plugin-')) { if (name.startsWith('plugin-')) {
return 'statics/providers/plugin.svg' return 'statics/providers/plugin.svg'
} }
if (name.startsWith('consul-')) {
return `statics/providers/consul.svg`
}
if (name.startsWith('consulcatalog-')) {
return `statics/providers/consulcatalog.svg`
}
return `statics/providers/${name}.svg` return `statics/providers/${name}.svg`
} }

View file

@ -66,9 +66,15 @@ export default {
const provider = this.getProvider(service) const provider = this.getProvider(service)
const name = provider.toLowerCase() const name = provider.toLowerCase()
if (name.includes('plugin-')) { if (name.startsWith('plugin-')) {
return 'statics/providers/plugin.svg' return 'statics/providers/plugin.svg'
} }
if (name.startsWith('consul-')) {
return `statics/providers/consul.svg`
}
if (name.startsWith('consulcatalog-')) {
return `statics/providers/consulcatalog.svg`
}
return `statics/providers/${name}.svg` return `statics/providers/${name}.svg`
} }

View file

@ -11,9 +11,15 @@ export default {
getLogoPath () { getLogoPath () {
const name = this.name.toLowerCase() const name = this.name.toLowerCase()
if (name.includes('plugin-')) { if (name.startsWith('plugin-')) {
return 'statics/providers/plugin.svg' return 'statics/providers/plugin.svg'
} }
if (name.startsWith('consul-')) {
return `statics/providers/consul.svg`
}
if (name.startsWith('consulcatalog-')) {
return `statics/providers/consulcatalog.svg`
}
return `statics/providers/${name}.svg` return `statics/providers/${name}.svg`
} }

View file

@ -28,9 +28,15 @@ export default {
getLogoPath () { getLogoPath () {
const name = this.getName.toLowerCase() const name = this.getName.toLowerCase()
if (name.includes('plugin-')) { if (name.startsWith('plugin-')) {
return 'statics/providers/plugin.svg' return 'statics/providers/plugin.svg'
} }
if (name.startsWith('consul-')) {
return `statics/providers/consul.svg`
}
if (name.startsWith('consulcatalog-')) {
return `statics/providers/consulcatalog.svg`
}
return `statics/providers/${name}.svg` return `statics/providers/${name}.svg`
} }