xMerge github.com:traefik/traefik

Signed-off-by: baalajimaestro <me@baalajimaestro.me>
This commit is contained in:
baalajimaestro 2022-12-08 20:48:02 +05:30
commit ab4ec9a63a
Signed by: baalajimaestro
GPG key ID: F93C394FE9BBAFD5
129 changed files with 5851 additions and 2916 deletions

View file

@ -2,16 +2,16 @@
PLEASE READ THIS MESSAGE.
Documentation fixes or enhancements:
- for Traefik v1: use branch v1.7
- for Traefik v2: use branch v2.9
- for Traefik v3: use branch master
Bug fixes:
- for Traefik v1: use branch v1.7
- for Traefik v2: use branch v2.9
- for Traefik v3: use branch master
Enhancements:
- for Traefik v1: we only accept bug fixes
- for Traefik v2: use branch master
- for Traefik v2: we only accept bug fixes
- for Traefik v3: use branch master
HOW TO WRITE A GOOD PULL REQUEST? https://doc.traefik.io/traefik/contributing/submitting-pull-requests/

View file

@ -64,7 +64,7 @@ blocks:
- name: GH_VERSION
value: 1.12.1
- name: CODENAME
value: "banon"
value: "beaufort"
- name: IN_DOCKER
value: ""
prologue:

View file

@ -1,3 +1,81 @@
## [v3.0.0-beta2](https://github.com/traefik/traefik/tree/v3.0.0-beta2) (2022-12-07)
[All Commits](https://github.com/traefik/traefik/compare/v3.0.0-beta1...v3.0.0-beta2)
**Enhancements:**
- **[http3]** Moves HTTP/3 outside the experimental section ([#9570](https://github.com/traefik/traefik/pull/9570) by [sdelicata](https://github.com/sdelicata))
**Bug fixes:**
- **[logs]** Change traefik cmd error log to error level ([#9569](https://github.com/traefik/traefik/pull/9569) by [tomMoulard](https://github.com/tomMoulard))
- **[rules]** Rework Host and HostRegexp matchers ([#9559](https://github.com/traefik/traefik/pull/9559) by [tomMoulard](https://github.com/tomMoulard))
**Misc:**
- Merge current v2.9 into master ([#9586](https://github.com/traefik/traefik/pull/9586) by [tomMoulard](https://github.com/tomMoulard))
## [v2.9.6](https://github.com/traefik/traefik/tree/v2.9.6) (2022-12-07)
[All Commits](https://github.com/traefik/traefik/compare/v2.9.5...v2.9.6)
**Bug fixes:**
- **[acme]** Update go-acme/lego to v4.9.1 ([#9550](https://github.com/traefik/traefik/pull/9550) by [ldez](https://github.com/ldez))
- **[k8s/crd]** Support of allowEmptyServices in TraefikService ([#9424](https://github.com/traefik/traefik/pull/9424) by [jeromeguiard](https://github.com/jeromeguiard))
- **[logs]** Remove logs of the request ([#9574](https://github.com/traefik/traefik/pull/9574) by [ldez](https://github.com/ldez))
- **[plugins]** Increase the timeout on plugin download ([#9529](https://github.com/traefik/traefik/pull/9529) by [ldez](https://github.com/ldez))
- **[server]** Update golang.org/x/net ([#9582](https://github.com/traefik/traefik/pull/9582) by [ldez](https://github.com/ldez))
- **[tls]** Handle broken TLS conf better ([#9572](https://github.com/traefik/traefik/pull/9572) by [mpl](https://github.com/mpl))
- **[tracing]** Update DataDog tracing dependency to v1.43.1 ([#9526](https://github.com/traefik/traefik/pull/9526) by [rtribotte](https://github.com/rtribotte))
- **[webui]** Add missing serialNumber passTLSClientCert option to middleware panel ([#9539](https://github.com/traefik/traefik/pull/9539) by [rtribotte](https://github.com/rtribotte))
**Documentation:**
- **[docker]** Add networking example ([#9542](https://github.com/traefik/traefik/pull/9542) by [Janik-Haag](https://github.com/Janik-Haag))
- **[hub]** Add information about the Hub Agent ([#9560](https://github.com/traefik/traefik/pull/9560) by [nmengin](https://github.com/nmengin))
- **[k8s/helm]** Update Helm installation section ([#9564](https://github.com/traefik/traefik/pull/9564) by [mloiseleur](https://github.com/mloiseleur))
- **[middleware]** Clarify PathPrefix matcher greediness ([#9519](https://github.com/traefik/traefik/pull/9519) by [mpl](https://github.com/mpl))
## [v3.0.0-beta1](https://github.com/traefik/traefik/tree/v3.0.0-beta1) (2022-12-05)
[All Commits](https://github.com/traefik/traefik/compare/v2.9.0-rc1...v3.0.0-beta1)
**Enhancements:**
- **[ecs]** Add option to keep only healthy ECS tasks ([#8027](https://github.com/traefik/traefik/pull/8027) by [Michampt](https://github.com/Michampt))
- **[healthcheck]** Support gRPC healthcheck ([#8583](https://github.com/traefik/traefik/pull/8583) by [jjacque](https://github.com/jjacque))
- **[healthcheck]** Add a status option to the service health check ([#9463](https://github.com/traefik/traefik/pull/9463) by [guoard](https://github.com/guoard))
- **[http]** Support custom headers when fetching configuration through HTTP ([#9421](https://github.com/traefik/traefik/pull/9421) by [kevinpollet](https://github.com/kevinpollet))
- **[logs,performance]** New logger for the Traefik logs ([#9515](https://github.com/traefik/traefik/pull/9515) by [ldez](https://github.com/ldez))
- **[logs,plugins]** Retry on plugin API calls ([#9530](https://github.com/traefik/traefik/pull/9530) by [ldez](https://github.com/ldez))
- **[logs,provider]** Improve provider logs ([#9562](https://github.com/traefik/traefik/pull/9562) by [ldez](https://github.com/ldez))
- **[logs]** Improve test logger assertions ([#9533](https://github.com/traefik/traefik/pull/9533) by [ldez](https://github.com/ldez))
- **[metrics]** Support gRPC and gRPC-Web protocol in metrics ([#9483](https://github.com/traefik/traefik/pull/9483) by [longit644](https://github.com/longit644))
- **[middleware,accesslogs]** Log TLS client subject ([#9285](https://github.com/traefik/traefik/pull/9285) by [xmessi](https://github.com/xmessi))
- **[middleware,metrics,tracing]** Add OpenTelemetry tracing and metrics support ([#8999](https://github.com/traefik/traefik/pull/8999) by [tomMoulard](https://github.com/tomMoulard))
- **[middleware]** Disable Content-Type auto-detection by default ([#9546](https://github.com/traefik/traefik/pull/9546) by [sdelicata](https://github.com/sdelicata))
- **[middleware]** Add gRPC-Web middleware ([#9451](https://github.com/traefik/traefik/pull/9451) by [juliens](https://github.com/juliens))
- **[middleware]** Add support for Brotli ([#9387](https://github.com/traefik/traefik/pull/9387) by [glinton](https://github.com/glinton))
- **[middleware]** Renaming IPWhiteList to IPAllowList ([#9457](https://github.com/traefik/traefik/pull/9457) by [wxmbugu](https://github.com/wxmbugu))
- **[nomad]** Support multiple namespaces in the Nomad Provider ([#9332](https://github.com/traefik/traefik/pull/9332) by [0teh](https://github.com/0teh))
- **[rules]** Update routing syntax ([#9531](https://github.com/traefik/traefik/pull/9531) by [skwair](https://github.com/skwair))
- **[server]** Rework servers load-balancer to use the WRR ([#9431](https://github.com/traefik/traefik/pull/9431) by [juliens](https://github.com/juliens))
- **[server]** Allow default entrypoints definition ([#9100](https://github.com/traefik/traefik/pull/9100) by [jilleJr](https://github.com/jilleJr))
- **[tls,service]** Support SPIFFE mTLS between Traefik and Backend servers ([#9394](https://github.com/traefik/traefik/pull/9394) by [jlevesy](https://github.com/jlevesy))
- **[tls]** Add Tailscale certificate resolver ([#9237](https://github.com/traefik/traefik/pull/9237) by [kevinpollet](https://github.com/kevinpollet))
- **[tls]** Support SNI routing with Postgres STARTTLS connections ([#9377](https://github.com/traefik/traefik/pull/9377) by [rtribotte](https://github.com/rtribotte))
- Remove deprecated options ([#9527](https://github.com/traefik/traefik/pull/9527) by [sdelicata](https://github.com/sdelicata))
**Bug fixes:**
- **[logs]** Fix log level ([#9545](https://github.com/traefik/traefik/pull/9545) by [ldez](https://github.com/ldez))
- **[metrics]** Fix ServerUp metric ([#9534](https://github.com/traefik/traefik/pull/9534) by [kevinpollet](https://github.com/kevinpollet))
- **[tls,service]** Enforce default servers transport SPIFFE config ([#9444](https://github.com/traefik/traefik/pull/9444) by [jlevesy](https://github.com/jlevesy))
**Documentation:**
- **[metrics]** Update and publish official Grafana Dashboard ([#9493](https://github.com/traefik/traefik/pull/9493) by [mloiseleur](https://github.com/mloiseleur))
**Misc:**
- Merge branch v2.9 into master ([#9554](https://github.com/traefik/traefik/pull/9554) by [ldez](https://github.com/ldez))
- Merge branch v2.9 into master ([#9536](https://github.com/traefik/traefik/pull/9536) by [ldez](https://github.com/ldez))
- Merge branch v2.9 into master ([#9532](https://github.com/traefik/traefik/pull/9532) by [ldez](https://github.com/ldez))
- Merge branch v2.9 into master ([#9482](https://github.com/traefik/traefik/pull/9482) by [kevinpollet](https://github.com/kevinpollet))
- Merge branch v2.9 into master ([#9464](https://github.com/traefik/traefik/pull/9464) by [ldez](https://github.com/ldez))
- Merge branch v2.9 into master ([#9449](https://github.com/traefik/traefik/pull/9449) by [kevinpollet](https://github.com/kevinpollet))
- Merge branch v2.9 into master ([#9419](https://github.com/traefik/traefik/pull/9419) by [kevinpollet](https://github.com/kevinpollet))
- Merge branch v2.9 into master ([#9351](https://github.com/traefik/traefik/pull/9351) by [rtribotte](https://github.com/rtribotte))
## [v2.9.5](https://github.com/traefik/traefik/tree/v2.9.5) (2022-11-17)
[All Commits](https://github.com/traefik/traefik/compare/v2.9.4...v2.9.5)

View file

@ -57,7 +57,7 @@ _(But if you'd rather configure some of your routes manually, Traefik supports t
- Provides HTTPS to your microservices by leveraging [Let's Encrypt](https://letsencrypt.org) (wildcard certificates support)
- Circuit breakers, retry
- See the magic through its clean web UI
- Websocket, HTTP/2, GRPC ready
- Websocket, HTTP/2, gRPC ready
- Provides metrics (Rest, Prometheus, Datadog, Statsd, InfluxDB)
- Keeps access logs (JSON, CLF)
- Fast

View file

@ -79,7 +79,7 @@ Complete documentation is available at https://traefik.io`,
err = cli.Execute(cmdTraefik)
if err != nil {
stdlog.Println(err)
log.Error().Err(err).Msg("Command error")
logrus.Exit(1)
}
@ -504,24 +504,30 @@ func registerMetricClients(metricsConfig *types.Metrics) []metrics.Registry {
logger := log.With().Str(logs.MetricsProviderName, "datadog").Logger()
registries = append(registries, metrics.RegisterDatadog(logger.WithContext(context.Background()), metricsConfig.Datadog))
logger.Debug().Msgf("Configured Datadog metrics: pushing to %s once every %s",
metricsConfig.Datadog.Address, metricsConfig.Datadog.PushInterval)
logger.Debug().
Str("address", metricsConfig.Datadog.Address).
Str("pushInterval", metricsConfig.Datadog.PushInterval.String()).
Msgf("Configured Datadog metrics")
}
if metricsConfig.StatsD != nil {
logger := log.With().Str(logs.MetricsProviderName, "statsd").Logger()
registries = append(registries, metrics.RegisterStatsd(logger.WithContext(context.Background()), metricsConfig.StatsD))
logger.Debug().Msgf("Configured StatsD metrics: pushing to %s once every %s",
metricsConfig.StatsD.Address, metricsConfig.StatsD.PushInterval)
logger.Debug().
Str("address", metricsConfig.StatsD.Address).
Str("pushInterval", metricsConfig.StatsD.PushInterval.String()).
Msg("Configured StatsD metrics")
}
if metricsConfig.InfluxDB != nil {
logger := log.With().Str(logs.MetricsProviderName, "influxdb").Logger()
registries = append(registries, metrics.RegisterInfluxDB(logger.WithContext(context.Background()), metricsConfig.InfluxDB))
logger.Debug().Msgf("Configured InfluxDB metrics: pushing to %s once every %s",
metricsConfig.InfluxDB.Address, metricsConfig.InfluxDB.PushInterval)
logger.Debug().
Str("address", metricsConfig.InfluxDB.Address).
Str("pushInterval", metricsConfig.InfluxDB.PushInterval.String()).
Msg("Configured InfluxDB metrics")
}
if metricsConfig.InfluxDB2 != nil {
@ -530,8 +536,25 @@ func registerMetricClients(metricsConfig *types.Metrics) []metrics.Registry {
influxDB2Register := metrics.RegisterInfluxDB2(logger.WithContext(context.Background()), metricsConfig.InfluxDB2)
if influxDB2Register != nil {
registries = append(registries, influxDB2Register)
logger.Debug().Msgf("Configured InfluxDB v2 metrics: pushing to %s (%s org/%s bucket) once every %s",
metricsConfig.InfluxDB2.Address, metricsConfig.InfluxDB2.Org, metricsConfig.InfluxDB2.Bucket, metricsConfig.InfluxDB2.PushInterval)
logger.Debug().
Str("address", metricsConfig.InfluxDB2.Address).
Str("bucket", metricsConfig.InfluxDB2.Bucket).
Str("organization", metricsConfig.InfluxDB2.Org).
Str("pushInterval", metricsConfig.InfluxDB2.PushInterval.String()).
Msg("Configured InfluxDB v2 metrics")
}
}
if metricsConfig.OpenTelemetry != nil {
logger := log.With().Str(logs.MetricsProviderName, "openTelemetry").Logger()
openTelemetryRegistry := metrics.RegisterOpenTelemetry(logger.WithContext(context.Background()), metricsConfig.OpenTelemetry)
if openTelemetryRegistry != nil {
registries = append(registries, openTelemetryRegistry)
logger.Debug().
Str("address", metricsConfig.OpenTelemetry.Address).
Str("pushInterval", metricsConfig.OpenTelemetry.PushInterval.String()).
Msg("Configured OpenTelemetry metrics")
}
}
@ -617,6 +640,14 @@ func setupTracing(conf *static.Tracing) *tracing.Tracing {
}
}
if conf.OpenTelemetry != nil {
if backend != nil {
log.Error().Msg("Tracing backends are all mutually exclusive: cannot create OpenTelemetry backend.")
} else {
backend = conf.OpenTelemetry
}
}
if backend == nil {
log.Debug().Msg("Could not initialize tracing, using Jaeger by default")
defaultBackend := &jaeger.Config{}

View file

@ -79,7 +79,7 @@ traefik --help
# or
docker run traefik[:version] --help
# ex: docker run traefik:v2.9 --help
# ex: docker run traefik:v3.0 --help
```
All available arguments can also be found [here](../reference/static-configuration/cli.md).

View file

@ -21,7 +21,7 @@ Choose one of the [official Docker images](https://hub.docker.com/_/traefik) and
```bash
docker run -d -p 8080:8080 -p 80:80 \
-v $PWD/traefik.yml:/etc/traefik/traefik.yml traefik:v2.9
-v $PWD/traefik.yml:/etc/traefik/traefik.yml traefik:v3.0
```
For more details, go to the [Docker provider documentation](../providers/docker.md)
@ -29,7 +29,7 @@ For more details, go to the [Docker provider documentation](../providers/docker.
!!! tip
* Prefer a fixed version than the latest that could be an unexpected version.
ex: `traefik:v2.9`
ex: `traefik:v3.0`
* Docker images are based from the [Alpine Linux Official image](https://hub.docker.com/_/alpine).
* Any orchestrator using docker images can fetch the official Traefik docker image.
@ -44,10 +44,10 @@ Traefik can be installed in Kubernetes using the Helm chart from <https://github
Ensure that the following requirements are met:
* Kubernetes 1.14+
* Helm version 3.x is [installed](https://helm.sh/docs/intro/install/)
* Kubernetes 1.16+
* Helm version 3.9+ is [installed](https://helm.sh/docs/intro/install/)
Add Traefik's chart repository to Helm:
Add Traefik Labs chart repository to Helm:
```bash
helm repo add traefik https://traefik.github.io/charts
@ -68,6 +68,9 @@ helm install traefik traefik/traefik
!!! tip "Helm Features"
All [Helm features](https://helm.sh/docs/intro/using_helm/) are supported.
Examples are provided [here](https://github.com/traefik/traefik-helm-chart/blob/master/EXAMPLES.md).
For instance, installing the chart in a dedicated namespace:
```bash tab="Install in a Dedicated Namespace"
@ -83,8 +86,7 @@ helm install traefik traefik/traefik
as with [any helm chart](https://helm.sh/docs/intro/using_helm/#customizing-the-chart-before-installing).
{: #helm-custom-values }
The values are not (yet) documented, but are self-explanatory:
you can look at the [default `values.yaml`](https://github.com/traefik/traefik-helm-chart/blob/master/traefik/values.yaml) file to explore possibilities.
All parameters are documented in the default [`values.yaml`](https://github.com/traefik/traefik-helm-chart/blob/master/traefik/values.yaml).
You can also set Traefik command line flags using `additionalArguments`.
Example of installation with logging set to `DEBUG`:

View file

@ -130,7 +130,7 @@ spec:
serviceAccountName: traefik-account
containers:
- name: traefik
image: traefik:v2.9
image: traefik:v3.0
args:
- --api.insecure
- --providers.kubernetesingress

View file

@ -20,7 +20,7 @@ version: '3'
services:
reverse-proxy:
# The official v2 Traefik docker image
image: traefik:v2.9
image: traefik:v3.0
# Enables the web UI and tells Traefik to listen to docker
command: --api.insecure=true --providers.docker
ports:
@ -50,7 +50,12 @@ Now that we have a Traefik instance up and running, we will deploy new services.
Edit your `docker-compose.yml` file and add the following at the end of your file.
```yaml
# ...
version: '3'
services:
...
whoami:
# A container that exposes an API to show its IP address
image: traefik/whoami

View file

@ -1,6 +1,6 @@
---
title: "Traefik ContentType Documentation"
description: "Traefik Proxy's HTTP middleware can automatically specify the content-type header if it has not been defined by the backend. Read the technical documentation."
description: "Traefik Proxy's HTTP middleware automatically sets the `Content-Type` header value when it is not set by the backend. Read the technical documentation."
---
# ContentType
@ -8,84 +8,59 @@ description: "Traefik Proxy's HTTP middleware can automatically specify the cont
Handling Content-Type auto-detection
{: .subtitle }
The Content-Type middleware - or rather its `autoDetect` option -
specifies whether to let the `Content-Type` header,
if it has not been defined by the backend,
be automatically set to a value derived from the contents of the response.
As a proxy, the default behavior should be to leave the header alone,
regardless of what the backend did with it.
However, the historic default was to always auto-detect and set the header if it was not already defined,
and altering this behavior would be a breaking change which would impact many users.
This middleware exists to enable the correct behavior until at least the default one can be changed in a future version.
The Content-Type middleware sets the `Content-Type` header value to the media type detected from the response content,
when it is not set by the backend.
!!! info
As explained above, for compatibility reasons the default behavior on a router (without this middleware),
is still to automatically set the `Content-Type` header.
Therefore, given the default value of the `autoDetect` option (false),
simply enabling this middleware for a router switches the router's behavior.
The scope of the Content-Type middleware is the MIME type detection done by the core of Traefik (the server part).
Therefore, it has no effect against any other `Content-Type` header modifications (e.g.: in another middleware such as compress).
## Configuration Examples
```yaml tab="Docker"
# Disable auto-detection
# Enable auto-detection
labels:
- "traefik.http.middlewares.autodetect.contenttype.autodetect=false"
- "traefik.http.middlewares.autodetect.contenttype=true"
```
```yaml tab="Kubernetes"
# Disable auto-detection
# Enable auto-detection
apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
name: autodetect
spec:
contentType:
autoDetect: false
contentType: {}
```
```yaml tab="Consul Catalog"
# Disable auto-detection
- "traefik.http.middlewares.autodetect.contenttype.autodetect=false"
# Enable auto-detection
- "traefik.http.middlewares.autodetect.contenttype=true"
```
```json tab="Marathon"
"labels": {
"traefik.http.middlewares.autodetect.contenttype.autodetect": "false"
"traefik.http.middlewares.autodetect.contenttype": "true"
}
```
```yaml tab="Rancher"
# Disable auto-detection
# Enable auto-detection
labels:
- "traefik.http.middlewares.autodetect.contenttype.autodetect=false"
- "traefik.http.middlewares.autodetect.contenttype=true"
```
```yaml tab="File (YAML)"
# Disable auto-detection
# Enable auto-detection
http:
middlewares:
autodetect:
contentType:
autoDetect: false
contentType: {}
```
```toml tab="File (TOML)"
# Disable auto-detection
# Enable auto-detection
[http.middlewares]
[http.middlewares.autodetect.contentType]
autoDetect=false
```
## Configuration Options
### `autoDetect`
`autoDetect` specifies whether to let the `Content-Type` header,
if it has not been set by the backend,
be automatically set to a value derived from the contents of the response.

View file

@ -30,3 +30,28 @@ In v3, the reported status code for gRPC requests is now the value of the `Grpc-
- `sslRedirect`, `sslTemporaryRedirect`, `sslHost`, `sslForceHost` and `featurePolicy` options of the Headers middleware have been removed.
- The `forceSlash` option of the StripPrefix middleware has been removed.
- the `preferServerCipherSuites` option has been removed.
## Matchers
In v3, the `Headers` and `HeadersRegexp` matchers have been renamed to `Header` and `HeaderRegexp` respectively.
`QueryRegexp` has been introduced to match query values using a regular expression.
`HeaderRegexp`, `HostRegexp`, `PathRegexp`, `QueryRegexp`, and `HostSNIRegexp` matchers now uses the [Go regexp syntax](https://golang.org/pkg/regexp/syntax/).
All matchers now take a single value (except `Headers`, `HeaderRegexp`, `Query`, and `QueryRegexp` which take two)
and should be explicitly combined using logical operators to mimic previous behavior.
`Query` can take a single value to match is the query value that has no value (e.g. `/search?mobile`).
`HostHeader` has been removed, use `Host` instead.
## Content-Type Auto-Detection
In v3, the `Content-Type` header is not auto-detected anymore when it is not set by the backend.
One should use the `ContentType` middleware to enable the `Content-Type` header value auto-detection.
## HTTP/3
In v3, HTTP/3 is no longer an experimental feature.
The `experimental.http3` option has been removed from the static configuration.

View file

@ -255,7 +255,7 @@ version: "3.7"
services:
traefik:
image: traefik:v2.9
image: traefik:v3.0
environment:
- TZ=US/Alaska
command:

View file

@ -0,0 +1,353 @@
---
title: "Traefik OpenTelemetry Documentation"
description: "Traefik supports several metrics backends, including OpenTelemetry. Learn how to implement it for observability in Traefik Proxy. Read the technical documentation."
---
# OpenTelemetry
To enable the OpenTelemetry:
```yaml tab="File (YAML)"
metrics:
openTelemetry: {}
```
```toml tab="File (TOML)"
[metrics]
[metrics.openTelemetry]
```
```bash tab="CLI"
--metrics.openTelemetry=true
```
!!! info "The OpenTelemetry exporter will export metrics to the collector by using HTTP by default, see the [gRPC Section](#grpc-configuration) to use gRPC."
#### `address`
_Required, Default="localhost:4318", Format="`<host>:<port>`"_
Address of the OpenTelemetry Collector to send metrics to.
```yaml tab="File (YAML)"
metrics:
openTelemetry:
address: localhost:4318
```
```toml tab="File (TOML)"
[metrics]
[metrics.openTelemetry]
address = "localhost:4318"
```
```bash tab="CLI"
--metrics.openTelemetry.address=localhost:4318
```
#### `addEntryPointsLabels`
_Optional, Default=true_
Enable metrics on entry points.
```yaml tab="File (YAML)"
metrics:
openTelemetry:
addEntryPointsLabels: true
```
```toml tab="File (TOML)"
[metrics]
[metrics.openTelemetry]
addEntryPointsLabels = true
```
```bash tab="CLI"
--metrics.openTelemetry.addEntryPointsLabels=true
```
#### `addRoutersLabels`
_Optional, Default=false_
Enable metrics on routers.
```yaml tab="File (YAML)"
metrics:
openTelemetry:
addRoutersLabels: true
```
```toml tab="File (TOML)"
[metrics]
[metrics.openTelemetry]
addRoutersLabels = true
```
```bash tab="CLI"
--metrics.openTelemetry.addRoutersLabels=true
```
#### `addServicesLabels`
_Optional, Default=true_
Enable metrics on services.
```yaml tab="File (YAML)"
metrics:
openTelemetry:
addServicesLabels: true
```
```toml tab="File (TOML)"
[metrics]
[metrics.openTelemetry]
addServicesLabels = true
```
```bash tab="CLI"
--metrics.openTelemetry.addServicesLabels=true
```
#### `explicitBoundaries`
_Optional, Default=".005, .01, .025, .05, .1, .25, .5, 1, 2.5, 5, 10"_
Explicit boundaries for Histogram data points.
```yaml tab="File (YAML)"
metrics:
openTelemetry:
explicitBoundaries:
- 0.1
- 0.3
- 1.2
- 5.0
```
```toml tab="File (TOML)"
[metrics]
[metrics.openTelemetry]
explicitBoundaries = [0.1,0.3,1.2,5.0]
```
```bash tab="CLI"
--metrics.openTelemetry.explicitBoundaries=0.1,0.3,1.2,5.0
```
#### `headers`
_Optional, Default={}_
Additional headers sent with metrics by the reporter to the OpenTelemetry Collector.
```yaml tab="File (YAML)"
metrics:
openTelemetry:
headers:
foo: bar
baz: buz
```
```toml tab="File (TOML)"
[metrics]
[metrics.openTelemetry.headers]
foo = "bar"
baz = "buz"
```
```bash tab="CLI"
--metrics.openTelemetry.headers.foo=bar --metrics.openTelemetry.headers.baz=buz
```
#### `insecure`
_Optional, Default=false_
Allows reporter to send metrics to the OpenTelemetry Collector without using a secured protocol.
```yaml tab="File (YAML)"
metrics:
openTelemetry:
insecure: true
```
```toml tab="File (TOML)"
[metrics]
[metrics.openTelemetry]
insecure = true
```
```bash tab="CLI"
--metrics.openTelemetry.insecure=true
```
#### `pushInterval`
_Optional, Default=10s_
Interval at which metrics are sent to the OpenTelemetry Collector.
```yaml tab="File (YAML)"
metrics:
openTelemetry:
pushInterval: 10s
```
```toml tab="File (TOML)"
[metrics]
[metrics.openTelemetry]
pushInterval = "10s"
```
```bash tab="CLI"
--metrics.openTelemetry.pushInterval=10s
```
#### `path`
_Required, Default="/v1/traces"_
Allows to override the default URL path used for sending metrics.
This option has no effect when using gRPC transport.
```yaml tab="File (YAML)"
metrics:
openTelemetry:
path: /foo/v1/traces
```
```toml tab="File (TOML)"
[metrics]
[metrics.openTelemetry]
path = "/foo/v1/traces"
```
```bash tab="CLI"
--metrics.openTelemetry.path=/foo/v1/traces
```
#### `tls`
_Optional_
Defines the TLS configuration used by the reporter to send metrics to the OpenTelemetry Collector.
##### `ca`
_Optional_
`ca` is the path to the certificate authority used for the secure connection to the OpenTelemetry Collector,
it defaults to the system bundle.
```yaml tab="File (YAML)"
metrics:
openTelemetry:
tls:
ca: path/to/ca.crt
```
```toml tab="File (TOML)"
[metrics.openTelemetry.tls]
ca = "path/to/ca.crt"
```
```bash tab="CLI"
--metrics.openTelemetry.tls.ca=path/to/ca.crt
```
##### `cert`
_Optional_
`cert` is the path to the public certificate used for the secure connection to the OpenTelemetry Collector.
When using this option, setting the `key` option is required.
```yaml tab="File (YAML)"
metrics:
openTelemetry:
tls:
cert: path/to/foo.cert
key: path/to/foo.key
```
```toml tab="File (TOML)"
[metrics.openTelemetry.tls]
cert = "path/to/foo.cert"
key = "path/to/foo.key"
```
```bash tab="CLI"
--metrics.openTelemetry.tls.cert=path/to/foo.cert
--metrics.openTelemetry.tls.key=path/to/foo.key
```
##### `key`
_Optional_
`key` is the path to the private key used for the secure connection to the OpenTelemetry Collector.
When using this option, setting the `cert` option is required.
```yaml tab="File (YAML)"
metrics:
openTelemetry:
tls:
cert: path/to/foo.cert
key: path/to/foo.key
```
```toml tab="File (TOML)"
[metrics.openTelemetry.tls]
cert = "path/to/foo.cert"
key = "path/to/foo.key"
```
```bash tab="CLI"
--metrics.openTelemetry.tls.cert=path/to/foo.cert
--metrics.openTelemetry.tls.key=path/to/foo.key
```
##### `insecureSkipVerify`
_Optional, Default=false_
If `insecureSkipVerify` is `true`,
the TLS connection to the OpenTelemetry Collector accepts any certificate presented by the server regardless of the hostnames it covers.
```yaml tab="File (YAML)"
metrics:
openTelemetry:
tls:
insecureSkipVerify: true
```
```toml tab="File (TOML)"
[metrics.openTelemetry.tls]
insecureSkipVerify = true
```
```bash tab="CLI"
--metrics.openTelemetry.tls.insecureSkipVerify=true
```
#### gRPC configuration
This instructs the reporter to send metrics to the OpenTelemetry Collector using gRPC.
```yaml tab="File (YAML)"
metrics:
openTelemetry:
grpc: {}
```
```toml tab="File (TOML)"
[metrics]
[metrics.openTelemetry.grpc]
```
```bash tab="CLI"
--metrics.openTelemetry.grpc=true
```

View file

@ -0,0 +1,246 @@
---
title: "Traefik OpenTelemetry Documentation"
description: "Traefik supports several tracing backends, including OpenTelemetry. Learn how to implement it for observability in Traefik Proxy. Read the technical documentation."
---
# OpenTelemetry
To enable the OpenTelemetry tracer:
```yaml tab="File (YAML)"
tracing:
openTelemetry: {}
```
```toml tab="File (TOML)"
[tracing]
[tracing.openTelemetry]
```
```bash tab="CLI"
--tracing.openTelemetry=true
```
!!! info "The OpenTelemetry trace reporter will export traces to the collector using HTTP by default, see the [gRPC Section](#grpc-configuration) to use gRPC."
!!! info "Trace sampling"
By default, the OpenTelemetry trace reporter will sample 100% of traces.
See [OpenTelemetry's SDK configuration](https://opentelemetry.io/docs/reference/specification/sdk-environment-variables/#general-sdk-configuration) to customize the sampling strategy.
#### `address`
_Required, Default="localhost:4318", Format="`<host>:<port>`"_
Address of the OpenTelemetry Collector to send spans to.
```yaml tab="File (YAML)"
tracing:
openTelemetry:
address: localhost:4318
```
```toml tab="File (TOML)"
[tracing]
[tracing.openTelemetry]
address = "localhost:4318"
```
```bash tab="CLI"
--tracing.openTelemetry.address=localhost:4318
```
#### `headers`
_Optional, Default={}_
Additional headers sent with spans by the reporter to the OpenTelemetry Collector.
```yaml tab="File (YAML)"
tracing:
openTelemetry:
headers:
foo: bar
baz: buz
```
```toml tab="File (TOML)"
[tracing]
[tracing.openTelemetry.headers]
foo = "bar"
baz = "buz"
```
```bash tab="CLI"
--tracing.openTelemetry.headers.foo=bar --tracing.openTelemetry.headers.baz=buz
```
#### `insecure`
_Optional, Default=false_
Allows reporter to send spans to the OpenTelemetry Collector without using a secured protocol.
```yaml tab="File (YAML)"
tracing:
openTelemetry:
insecure: true
```
```toml tab="File (TOML)"
[tracing]
[tracing.openTelemetry]
insecure = true
```
```bash tab="CLI"
--tracing.openTelemetry.insecure=true
```
#### `path`
_Required, Default="/v1/traces"_
Allows to override the default URL path used for sending traces.
This option has no effect when using gRPC transport.
```yaml tab="File (YAML)"
tracing:
openTelemetry:
path: /foo/v1/traces
```
```toml tab="File (TOML)"
[tracing]
[tracing.openTelemetry]
path = "/foo/v1/traces"
```
```bash tab="CLI"
--tracing.openTelemetry.path=/foo/v1/traces
```
#### `tls`
_Optional_
Defines the TLS configuration used by the reporter to send spans to the OpenTelemetry Collector.
##### `ca`
_Optional_
`ca` is the path to the certificate authority used for the secure connection to the OpenTelemetry Collector,
it defaults to the system bundle.
```yaml tab="File (YAML)"
tracing:
openTelemetry:
tls:
ca: path/to/ca.crt
```
```toml tab="File (TOML)"
[tracing.openTelemetry.tls]
ca = "path/to/ca.crt"
```
```bash tab="CLI"
--tracing.openTelemetry.tls.ca=path/to/ca.crt
```
##### `cert`
_Optional_
`cert` is the path to the public certificate used for the secure connection to the OpenTelemetry Collector.
When using this option, setting the `key` option is required.
```yaml tab="File (YAML)"
tracing:
openTelemetry:
tls:
cert: path/to/foo.cert
key: path/to/foo.key
```
```toml tab="File (TOML)"
[tracing.openTelemetry.tls]
cert = "path/to/foo.cert"
key = "path/to/foo.key"
```
```bash tab="CLI"
--tracing.openTelemetry.tls.cert=path/to/foo.cert
--tracing.openTelemetry.tls.key=path/to/foo.key
```
##### `key`
_Optional_
`key` is the path to the private key used for the secure connection to the OpenTelemetry Collector.
When using this option, setting the `cert` option is required.
```yaml tab="File (YAML)"
tracing:
openTelemetry:
tls:
cert: path/to/foo.cert
key: path/to/foo.key
```
```toml tab="File (TOML)"
[tracing.openTelemetry.tls]
cert = "path/to/foo.cert"
key = "path/to/foo.key"
```
```bash tab="CLI"
--tracing.openTelemetry.tls.cert=path/to/foo.cert
--tracing.openTelemetry.tls.key=path/to/foo.key
```
##### `insecureSkipVerify`
_Optional, Default=false_
If `insecureSkipVerify` is `true`,
the TLS connection to the OpenTelemetry Collector accepts any certificate presented by the server regardless of the hostnames it covers.
```yaml tab="File (YAML)"
tracing:
openTelemetry:
tls:
insecureSkipVerify: true
```
```toml tab="File (TOML)"
[tracing.openTelemetry.tls]
insecureSkipVerify = true
```
```bash tab="CLI"
--tracing.openTelemetry.tls.insecureSkipVerify=true
```
#### gRPC configuration
_Optional_
This instructs the reporter to send spans to the OpenTelemetry Collector using gRPC.
```yaml tab="File (YAML)"
tracing:
openTelemetry:
grpc: {}
```
```toml tab="File (TOML)"
[tracing]
[tracing.openTelemetry.grpc]
```
```bash tab="CLI"
--tracing.openTelemetry.grpc=true
```

View file

@ -93,12 +93,12 @@ rule = "Host(`traefik.example.com`)"
```bash tab="Path Prefix Rule"
# The dashboard can be accessed on http://example.com/dashboard/ or http://traefik.example.com/dashboard/
rule = "PathPrefix(`/api`, `/dashboard`)"
rule = "PathPrefix(`/api`) || PathPrefix(`/dashboard`)"
```
```bash tab="Combination of Rules"
# The dashboard can be accessed on http://traefik.example.com/dashboard/
rule = "Host(`traefik.example.com`) && PathPrefix(`/api`, `/dashboard`)"
rule = "Host(`traefik.example.com`) && (PathPrefix(`/api`) || PathPrefix(`/dashboard`))"
```
??? example "Dashboard Dynamic Configuration Examples"

View file

@ -265,7 +265,7 @@ See the sections [Docker API Access](#docker-api-access) and [Docker Swarm API A
services:
traefik:
image: traefik:v2.9 # The official v2 Traefik docker image
image: traefik:v3.0 # The official v2 Traefik docker image
ports:
- "80:80"
volumes:

View file

@ -17,7 +17,7 @@
- "traefik.http.middlewares.middleware05.compress=true"
- "traefik.http.middlewares.middleware05.compress.excludedcontenttypes=foobar, foobar"
- "traefik.http.middlewares.middleware05.compress.minresponsebodybytes=42"
- "traefik.http.middlewares.middleware06.contenttype.autodetect=true"
- "traefik.http.middlewares.middleware06.contenttype=true"
- "traefik.http.middlewares.middleware07.digestauth.headerfield=foobar"
- "traefik.http.middlewares.middleware07.digestauth.realm=foobar"
- "traefik.http.middlewares.middleware07.digestauth.removeheader=true"

View file

@ -137,7 +137,6 @@
minResponseBodyBytes = 42
[http.middlewares.Middleware06]
[http.middlewares.Middleware06.contentType]
autoDetect = true
[http.middlewares.Middleware07]
[http.middlewares.Middleware07.digestAuth]
users = ["foobar", "foobar"]

View file

@ -141,8 +141,7 @@ http:
- foobar
minResponseBodyBytes: 42
Middleware06:
contentType:
autoDetect: true
contentType: {}
Middleware07:
digestAuth:
users:

View file

@ -39,7 +39,7 @@ spec:
entryPoints:
description: 'EntryPoints defines the list of entry point names to
bind to. Entry points have to be configured in the static configuration.
More info: https://doc.traefik.io/traefik/v2.9/routing/entrypoints/
More info: https://doc.traefik.io/traefik/v3.0/routing/entrypoints/
Default: all.'
items:
type: string
@ -56,11 +56,11 @@ spec:
- Rule
type: string
match:
description: 'Match defines the router''s rule. More info: https://doc.traefik.io/traefik/v2.9/routing/routers/#rule'
description: 'Match defines the router''s rule. More info: https://doc.traefik.io/traefik/v3.0/routing/routers/#rule'
type: string
middlewares:
description: 'Middlewares defines the list of references to
Middleware resources. More info: https://doc.traefik.io/traefik/v2.9/routing/providers/kubernetes-crd/#kind-middleware'
Middleware resources. More info: https://doc.traefik.io/traefik/v3.0/routing/providers/kubernetes-crd/#kind-middleware'
items:
description: MiddlewareRef is a reference to a Middleware
resource.
@ -79,7 +79,7 @@ spec:
type: array
priority:
description: 'Priority defines the router''s priority. More
info: https://doc.traefik.io/traefik/v2.9/routing/routers/#priority'
info: https://doc.traefik.io/traefik/v3.0/routing/routers/#priority'
type: integer
services:
description: Services defines the list of Service. It can contain
@ -145,7 +145,7 @@ spec:
type: string
sticky:
description: 'Sticky defines the sticky sessions configuration.
More info: https://doc.traefik.io/traefik/v2.9/routing/services/#sticky-sessions'
More info: https://doc.traefik.io/traefik/v3.0/routing/services/#sticky-sessions'
properties:
cookie:
description: Cookie defines the sticky cookie configuration.
@ -190,16 +190,16 @@ spec:
type: object
type: array
tls:
description: 'TLS defines the TLS configuration. More info: https://doc.traefik.io/traefik/v2.9/routing/routers/#tls'
description: 'TLS defines the TLS configuration. More info: https://doc.traefik.io/traefik/v3.0/routing/routers/#tls'
properties:
certResolver:
description: 'CertResolver defines the name of the certificate
resolver to use. Cert resolvers have to be configured in the
static configuration. More info: https://doc.traefik.io/traefik/v2.9/https/acme/#certificate-resolvers'
static configuration. More info: https://doc.traefik.io/traefik/v3.0/https/acme/#certificate-resolvers'
type: string
domains:
description: 'Domains defines the list of domains that will be
used to issue certificates. More info: https://doc.traefik.io/traefik/v2.9/routing/routers/#domains'
used to issue certificates. More info: https://doc.traefik.io/traefik/v3.0/routing/routers/#domains'
items:
description: Domain holds a domain name with SANs.
properties:
@ -217,15 +217,15 @@ spec:
options:
description: 'Options defines the reference to a TLSOption, that
specifies the parameters of the TLS connection. If not defined,
the `default` TLSOption is used. More info: https://doc.traefik.io/traefik/v2.9/https/tls/#tls-options'
the `default` TLSOption is used. More info: https://doc.traefik.io/traefik/v3.0/https/tls/#tls-options'
properties:
name:
description: 'Name defines the name of the referenced TLSOption.
More info: https://doc.traefik.io/traefik/v2.9/routing/providers/kubernetes-crd/#kind-tlsoption'
More info: https://doc.traefik.io/traefik/v3.0/routing/providers/kubernetes-crd/#kind-tlsoption'
type: string
namespace:
description: 'Namespace defines the namespace of the referenced
TLSOption. More info: https://doc.traefik.io/traefik/v2.9/routing/providers/kubernetes-crd/#kind-tlsoption'
TLSOption. More info: https://doc.traefik.io/traefik/v3.0/routing/providers/kubernetes-crd/#kind-tlsoption'
type: string
required:
- name
@ -241,11 +241,11 @@ spec:
properties:
name:
description: 'Name defines the name of the referenced TLSStore.
More info: https://doc.traefik.io/traefik/v2.9/routing/providers/kubernetes-crd/#kind-tlsstore'
More info: https://doc.traefik.io/traefik/v3.0/routing/providers/kubernetes-crd/#kind-tlsstore'
type: string
namespace:
description: 'Namespace defines the namespace of the referenced
TLSStore. More info: https://doc.traefik.io/traefik/v2.9/routing/providers/kubernetes-crd/#kind-tlsstore'
TLSStore. More info: https://doc.traefik.io/traefik/v3.0/routing/providers/kubernetes-crd/#kind-tlsstore'
type: string
required:
- name
@ -307,7 +307,7 @@ spec:
entryPoints:
description: 'EntryPoints defines the list of entry point names to
bind to. Entry points have to be configured in the static configuration.
More info: https://doc.traefik.io/traefik/v2.9/routing/entrypoints/
More info: https://doc.traefik.io/traefik/v3.0/routing/entrypoints/
Default: all.'
items:
type: string
@ -318,7 +318,7 @@ spec:
description: RouteTCP holds the TCP route configuration.
properties:
match:
description: 'Match defines the router''s rule. More info: https://doc.traefik.io/traefik/v2.9/routing/routers/#rule_1'
description: 'Match defines the router''s rule. More info: https://doc.traefik.io/traefik/v3.0/routing/routers/#rule_1'
type: string
middlewares:
description: Middlewares defines the list of references to MiddlewareTCP
@ -341,7 +341,7 @@ spec:
type: array
priority:
description: 'Priority defines the router''s priority. More
info: https://doc.traefik.io/traefik/v2.9/routing/routers/#priority_1'
info: https://doc.traefik.io/traefik/v3.0/routing/routers/#priority_1'
type: integer
services:
description: Services defines the list of TCP services.
@ -366,7 +366,7 @@ spec:
x-kubernetes-int-or-string: true
proxyProtocol:
description: 'ProxyProtocol defines the PROXY protocol
configuration. More info: https://doc.traefik.io/traefik/v2.9/routing/services/#proxy-protocol'
configuration. More info: https://doc.traefik.io/traefik/v3.0/routing/services/#proxy-protocol'
properties:
version:
description: Version defines the PROXY Protocol version
@ -397,16 +397,16 @@ spec:
type: array
tls:
description: 'TLS defines the TLS configuration on a layer 4 / TCP
Route. More info: https://doc.traefik.io/traefik/v2.9/routing/routers/#tls_1'
Route. More info: https://doc.traefik.io/traefik/v3.0/routing/routers/#tls_1'
properties:
certResolver:
description: 'CertResolver defines the name of the certificate
resolver to use. Cert resolvers have to be configured in the
static configuration. More info: https://doc.traefik.io/traefik/v2.9/https/acme/#certificate-resolvers'
static configuration. More info: https://doc.traefik.io/traefik/v3.0/https/acme/#certificate-resolvers'
type: string
domains:
description: 'Domains defines the list of domains that will be
used to issue certificates. More info: https://doc.traefik.io/traefik/v2.9/routing/routers/#domains'
used to issue certificates. More info: https://doc.traefik.io/traefik/v3.0/routing/routers/#domains'
items:
description: Domain holds a domain name with SANs.
properties:
@ -424,7 +424,7 @@ spec:
options:
description: 'Options defines the reference to a TLSOption, that
specifies the parameters of the TLS connection. If not defined,
the `default` TLSOption is used. More info: https://doc.traefik.io/traefik/v2.9/https/tls/#tls-options'
the `default` TLSOption is used. More info: https://doc.traefik.io/traefik/v3.0/https/tls/#tls-options'
properties:
name:
description: Name defines the name of the referenced Traefik
@ -518,7 +518,7 @@ spec:
entryPoints:
description: 'EntryPoints defines the list of entry point names to
bind to. Entry points have to be configured in the static configuration.
More info: https://doc.traefik.io/traefik/v2.9/routing/entrypoints/
More info: https://doc.traefik.io/traefik/v3.0/routing/entrypoints/
Default: all.'
items:
type: string
@ -597,7 +597,7 @@ spec:
schema:
openAPIV3Schema:
description: 'Middleware is the CRD implementation of a Traefik Middleware.
More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/overview/'
More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/overview/'
properties:
apiVersion:
description: 'APIVersion defines the versioned schema of this representation
@ -617,7 +617,7 @@ spec:
addPrefix:
description: 'AddPrefix holds the add prefix middleware configuration.
This middleware updates the path of a request before forwarding
it. More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/addprefix/'
it. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/addprefix/'
properties:
prefix:
description: Prefix is the string to add before the current path
@ -627,11 +627,11 @@ spec:
basicAuth:
description: 'BasicAuth holds the basic auth middleware configuration.
This middleware restricts access to your services to known users.
More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/basicauth/'
More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/basicauth/'
properties:
headerField:
description: 'HeaderField defines a header field to store the
authenticated user. More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/basicauth/#headerfield'
authenticated user. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/basicauth/#headerfield'
type: string
realm:
description: 'Realm allows the protected resources on a server
@ -651,7 +651,7 @@ spec:
buffering:
description: 'Buffering holds the buffering middleware configuration.
This middleware retries or limits the size of requests that can
be forwarded to backends. More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/buffering/#maxrequestbodybytes'
be forwarded to backends. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/buffering/#maxrequestbodybytes'
properties:
maxRequestBodyBytes:
description: 'MaxRequestBodyBytes defines the maximum allowed
@ -684,13 +684,13 @@ spec:
retryExpression:
description: 'RetryExpression defines the retry conditions. It
is a logical combination of functions with operators AND (&&)
and OR (||). More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/buffering/#retryexpression'
and OR (||). More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/buffering/#retryexpression'
type: string
type: object
chain:
description: 'Chain holds the configuration of the chain middleware.
This middleware enables to define reusable combinations of other
pieces of middleware. More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/chain/'
pieces of middleware. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/chain/'
properties:
middlewares:
description: Middlewares is the list of MiddlewareRef which composes
@ -744,7 +744,7 @@ spec:
compress:
description: 'Compress holds the compress middleware configuration.
This middleware compresses responses before sending them to the
client, using gzip compression. More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/compress/'
client, using gzip compression. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/compress/'
properties:
excludedContentTypes:
description: ExcludedContentTypes defines the list of content
@ -762,28 +762,18 @@ spec:
type: object
contentType:
description: ContentType holds the content-type middleware configuration.
This middleware exists to enable the correct behavior until at least
the default one can be changed in a future version.
properties:
autoDetect:
description: AutoDetect specifies whether to let the `Content-Type`
header, if it has not been set by the backend, be automatically
set to a value derived from the contents of the response. As
a proxy, the default behavior should be to leave the header
alone, regardless of what the backend did with it. However,
the historic default was to always auto-detect and set the header
if it was nil, and it is going to be kept that way in order
to support users currently relying on it.
type: boolean
This middleware sets the `Content-Type` header value to the media
type detected from the response content, when it is not set by the
backend.
type: object
digestAuth:
description: 'DigestAuth holds the digest auth middleware configuration.
This middleware restricts access to your services to known users.
More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/digestauth/'
More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/digestauth/'
properties:
headerField:
description: 'HeaderField defines a header field to store the
authenticated user. More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/basicauth/#headerfield'
authenticated user. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/basicauth/#headerfield'
type: string
realm:
description: 'Realm allows the protected resources on a server
@ -802,7 +792,7 @@ spec:
errors:
description: 'ErrorPage holds the custom error middleware configuration.
This middleware returns a custom page in lieu of the default, according
to configured ranges of HTTP Status codes. More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/errorpages/'
to configured ranges of HTTP Status codes. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/errorpages/'
properties:
query:
description: Query defines the URL for the error page (hosted
@ -811,7 +801,7 @@ spec:
type: string
service:
description: 'Service defines the reference to a Kubernetes Service
that will serve the error page. More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/errorpages/#service'
that will serve the error page. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/errorpages/#service'
properties:
kind:
description: Kind defines the kind of the Service.
@ -868,7 +858,7 @@ spec:
type: string
sticky:
description: 'Sticky defines the sticky sessions configuration.
More info: https://doc.traefik.io/traefik/v2.9/routing/services/#sticky-sessions'
More info: https://doc.traefik.io/traefik/v3.0/routing/services/#sticky-sessions'
properties:
cookie:
description: Cookie defines the sticky cookie configuration.
@ -917,7 +907,7 @@ spec:
forwardAuth:
description: 'ForwardAuth holds the forward auth middleware configuration.
This middleware delegates the request authentication to a Service.
More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/forwardauth/'
More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/forwardauth/'
properties:
address:
description: Address defines the authentication server address.
@ -940,7 +930,7 @@ spec:
description: 'AuthResponseHeadersRegex defines the regex to match
headers to copy from the authentication server response and
set on forwarded request, after stripping all headers that match
the regex. More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/forwardauth/#authresponseheadersregex'
the regex. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/forwardauth/#authresponseheadersregex'
type: string
tls:
description: TLS defines the configuration used to secure the
@ -980,7 +970,7 @@ spec:
headers:
description: 'Headers holds the headers middleware configuration.
This middleware manages the requests and responses headers. More
info: https://doc.traefik.io/traefik/v2.9/middlewares/http/headers/#customrequestheaders'
info: https://doc.traefik.io/traefik/v3.0/middlewares/http/headers/#customrequestheaders'
properties:
accessControlAllowCredentials:
description: AccessControlAllowCredentials defines whether the
@ -1124,7 +1114,7 @@ spec:
inFlightReq:
description: 'InFlightReq holds the in-flight request middleware configuration.
This middleware limits the number of requests being processed and
served concurrently. More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/inflightreq/'
served concurrently. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/inflightreq/'
properties:
amount:
description: Amount defines the maximum amount of allowed simultaneous
@ -1138,11 +1128,11 @@ spec:
group requests as originating from a common source. If several
strategies are defined at the same time, an error will be raised.
If none are set, the default is to use the requestHost. More
info: https://doc.traefik.io/traefik/v2.9/middlewares/http/inflightreq/#sourcecriterion'
info: https://doc.traefik.io/traefik/v3.0/middlewares/http/inflightreq/#sourcecriterion'
properties:
ipStrategy:
description: 'IPStrategy holds the IP strategy configuration
used by Traefik to determine the client IP. More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/ipallowlist/#ipstrategy'
used by Traefik to determine the client IP. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/ipallowlist/#ipstrategy'
properties:
depth:
description: Depth tells Traefik to use the X-Forwarded-For
@ -1170,11 +1160,11 @@ spec:
ipAllowList:
description: 'IPAllowList holds the IP allowlist middleware configuration.
This middleware accepts / refuses requests based on the client IP.
More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/ipallowlist/'
More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/ipallowlist/'
properties:
ipStrategy:
description: 'IPStrategy holds the IP strategy configuration used
by Traefik to determine the client IP. More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/ipallowlist/#ipstrategy'
by Traefik to determine the client IP. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/ipallowlist/#ipstrategy'
properties:
depth:
description: Depth tells Traefik to use the X-Forwarded-For
@ -1198,7 +1188,7 @@ spec:
passTLSClientCert:
description: 'PassTLSClientCert holds the pass TLS client cert middleware
configuration. This middleware adds the selected data from the passed
client TLS certificate to a header. More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/passtlsclientcert/'
client TLS certificate to a header. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/passtlsclientcert/'
properties:
info:
description: Info selects the specific client certificate details
@ -1305,7 +1295,7 @@ spec:
rateLimit:
description: 'RateLimit holds the rate limit configuration. This middleware
ensures that services will receive a fair amount of requests, and
allows one to define what fair is. More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/ratelimit/'
allows one to define what fair is. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/ratelimit/'
properties:
average:
description: Average is the maximum rate, by default in requests/s,
@ -1338,7 +1328,7 @@ spec:
properties:
ipStrategy:
description: 'IPStrategy holds the IP strategy configuration
used by Traefik to determine the client IP. More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/ipallowlist/#ipstrategy'
used by Traefik to determine the client IP. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/ipallowlist/#ipstrategy'
properties:
depth:
description: Depth tells Traefik to use the X-Forwarded-For
@ -1366,7 +1356,7 @@ spec:
redirectRegex:
description: 'RedirectRegex holds the redirect regex middleware configuration.
This middleware redirects a request using regex matching and replacement.
More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/redirectregex/#regex'
More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/redirectregex/#regex'
properties:
permanent:
description: Permanent defines whether the redirection is permanent
@ -1384,7 +1374,7 @@ spec:
redirectScheme:
description: 'RedirectScheme holds the redirect scheme middleware
configuration. This middleware redirects requests from a scheme/port
to another. More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/redirectscheme/'
to another. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/redirectscheme/'
properties:
permanent:
description: Permanent defines whether the redirection is permanent
@ -1400,7 +1390,7 @@ spec:
replacePath:
description: 'ReplacePath holds the replace path middleware configuration.
This middleware replaces the path of the request URL and store the
original path in an X-Replaced-Path header. More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/replacepath/'
original path in an X-Replaced-Path header. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/replacepath/'
properties:
path:
description: Path defines the path to use as replacement in the
@ -1410,7 +1400,7 @@ spec:
replacePathRegex:
description: 'ReplacePathRegex holds the replace path regex middleware
configuration. This middleware replaces the path of a URL using
regex matching and replacement. More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/replacepathregex/'
regex matching and replacement. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/replacepathregex/'
properties:
regex:
description: Regex defines the regular expression used to match
@ -1426,7 +1416,7 @@ spec:
middleware reissues requests a given number of times to a backend
server if that server does not reply. As soon as the server answers,
the middleware stops retrying, regardless of the response status.
More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/retry/'
More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/retry/'
properties:
attempts:
description: Attempts defines how many times the request should
@ -1446,7 +1436,7 @@ spec:
stripPrefix:
description: 'StripPrefix holds the strip prefix middleware configuration.
This middleware removes the specified prefixes from the URL path.
More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/stripprefix/'
More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/stripprefix/'
properties:
prefixes:
description: Prefixes defines the prefixes to strip from the request
@ -1458,7 +1448,7 @@ spec:
stripPrefixRegex:
description: 'StripPrefixRegex holds the strip prefix regex middleware
configuration. This middleware removes the matching prefixes from
the URL path. More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/stripprefixregex/'
the URL path. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/stripprefixregex/'
properties:
regex:
description: Regex defines the regular expression to match the
@ -1502,7 +1492,7 @@ spec:
schema:
openAPIV3Schema:
description: 'MiddlewareTCP is the CRD implementation of a Traefik TCP middleware.
More info: https://doc.traefik.io/traefik/v2.9/middlewares/overview/'
More info: https://doc.traefik.io/traefik/v3.0/middlewares/overview/'
properties:
apiVersion:
description: 'APIVersion defines the versioned schema of this representation
@ -1576,7 +1566,7 @@ spec:
description: 'ServersTransport is the CRD implementation of a ServersTransport.
If no serversTransport is specified, the default@internal will be used.
The default@internal serversTransport is created from the static configuration.
More info: https://doc.traefik.io/traefik/v2.9/routing/services/#serverstransport_1'
More info: https://doc.traefik.io/traefik/v3.0/routing/services/#serverstransport_1'
properties:
apiVersion:
description: 'APIVersion defines the versioned schema of this representation
@ -1716,7 +1706,7 @@ spec:
openAPIV3Schema:
description: 'TLSOption is the CRD implementation of a Traefik TLS Option,
allowing to configure some parameters of the TLS connection. More info:
https://doc.traefik.io/traefik/v2.9/https/tls/#tls-options'
https://doc.traefik.io/traefik/v3.0/https/tls/#tls-options'
properties:
apiVersion:
description: 'APIVersion defines the versioned schema of this representation
@ -1736,13 +1726,13 @@ spec:
alpnProtocols:
description: 'ALPNProtocols defines the list of supported application
level protocols for the TLS handshake, in order of preference. More
info: https://doc.traefik.io/traefik/v2.9/https/tls/#alpn-protocols'
info: https://doc.traefik.io/traefik/v3.0/https/tls/#alpn-protocols'
items:
type: string
type: array
cipherSuites:
description: 'CipherSuites defines the list of supported cipher suites
for TLS versions up to TLS 1.2. More info: https://doc.traefik.io/traefik/v2.9/https/tls/#cipher-suites'
for TLS versions up to TLS 1.2. More info: https://doc.traefik.io/traefik/v3.0/https/tls/#cipher-suites'
items:
type: string
type: array
@ -1769,7 +1759,7 @@ spec:
type: object
curvePreferences:
description: 'CurvePreferences defines the preferred elliptic curves
in a specific order. More info: https://doc.traefik.io/traefik/v2.9/https/tls/#curve-preferences'
in a specific order. More info: https://doc.traefik.io/traefik/v3.0/https/tls/#curve-preferences'
items:
type: string
type: array
@ -1824,7 +1814,7 @@ spec:
description: 'TLSStore is the CRD implementation of a Traefik TLS Store. For
the time being, only the TLSStore named default is supported. This means
that you cannot have two stores that are named default in different Kubernetes
namespaces. More info: https://doc.traefik.io/traefik/v2.9/https/tls/#certificates-stores'
namespaces. More info: https://doc.traefik.io/traefik/v3.0/https/tls/#certificates-stores'
properties:
apiVersion:
description: 'APIVersion defines the versioned schema of this representation
@ -1922,7 +1912,7 @@ spec:
openAPIV3Schema:
description: 'TraefikService is the CRD implementation of a Traefik Service.
TraefikService object allows to: - Apply weight to Services on load-balancing
- Mirror traffic on services More info: https://doc.traefik.io/traefik/v2.9/routing/providers/kubernetes-crd/#kind-traefikservice'
- Mirror traffic on services More info: https://doc.traefik.io/traefik/v3.0/routing/providers/kubernetes-crd/#kind-traefikservice'
properties:
apiVersion:
description: 'APIVersion defines the versioned schema of this representation
@ -2021,7 +2011,7 @@ spec:
type: string
sticky:
description: 'Sticky defines the sticky sessions configuration.
More info: https://doc.traefik.io/traefik/v2.9/routing/services/#sticky-sessions'
More info: https://doc.traefik.io/traefik/v3.0/routing/services/#sticky-sessions'
properties:
cookie:
description: Cookie defines the sticky cookie configuration.
@ -2105,7 +2095,7 @@ spec:
type: string
sticky:
description: 'Sticky defines the sticky sessions configuration.
More info: https://doc.traefik.io/traefik/v2.9/routing/services/#sticky-sessions'
More info: https://doc.traefik.io/traefik/v3.0/routing/services/#sticky-sessions'
properties:
cookie:
description: Cookie defines the sticky cookie configuration.
@ -2205,7 +2195,7 @@ spec:
type: string
sticky:
description: 'Sticky defines the sticky sessions configuration.
More info: https://doc.traefik.io/traefik/v2.9/routing/services/#sticky-sessions'
More info: https://doc.traefik.io/traefik/v3.0/routing/services/#sticky-sessions'
properties:
cookie:
description: Cookie defines the sticky cookie configuration.
@ -2244,7 +2234,7 @@ spec:
type: array
sticky:
description: 'Sticky defines whether sticky sessions are enabled.
More info: https://doc.traefik.io/traefik/v2.9/routing/providers/kubernetes-crd/#stickiness-and-load-balancing'
More info: https://doc.traefik.io/traefik/v3.0/routing/providers/kubernetes-crd/#stickiness-and-load-balancing'
properties:
cookie:
description: Cookie defines the sticky cookie configuration.

View file

@ -25,7 +25,7 @@ spec:
serviceAccountName: traefik-controller
containers:
- name: traefik
image: traefik:v2.9
image: traefik:v3.0
args:
- --entrypoints.web.address=:80
- --entrypoints.websecure.address=:443

View file

@ -19,7 +19,7 @@
| `traefik/http/middlewares/Middleware05/compress/excludedContentTypes/0` | `foobar` |
| `traefik/http/middlewares/Middleware05/compress/excludedContentTypes/1` | `foobar` |
| `traefik/http/middlewares/Middleware05/compress/minResponseBodyBytes` | `42` |
| `traefik/http/middlewares/Middleware06/contentType/autoDetect` | `true` |
| `traefik/http/middlewares/Middleware06/contentType` | `` |
| `traefik/http/middlewares/Middleware07/digestAuth/headerField` | `foobar` |
| `traefik/http/middlewares/Middleware07/digestAuth/realm` | `foobar` |
| `traefik/http/middlewares/Middleware07/digestAuth/removeHeader` | `true` |

View file

@ -17,7 +17,7 @@
"traefik.http.middlewares.middleware05.compress": "true",
"traefik.http.middlewares.middleware05.compress.excludedcontenttypes": "foobar, foobar",
"traefik.http.middlewares.middleware05.compress.minresponsebodybytes": "42",
"traefik.http.middlewares.middleware06.contenttype.autodetect": "true",
"traefik.http.middlewares.middleware06.contenttype": "true",
"traefik.http.middlewares.middleware07.digestauth.headerfield": "foobar",
"traefik.http.middlewares.middleware07.digestauth.realm": "foobar",
"traefik.http.middlewares.middleware07.digestauth.removeheader": "true",

View file

@ -39,7 +39,7 @@ spec:
entryPoints:
description: 'EntryPoints defines the list of entry point names to
bind to. Entry points have to be configured in the static configuration.
More info: https://doc.traefik.io/traefik/v2.9/routing/entrypoints/
More info: https://doc.traefik.io/traefik/v3.0/routing/entrypoints/
Default: all.'
items:
type: string
@ -56,11 +56,11 @@ spec:
- Rule
type: string
match:
description: 'Match defines the router''s rule. More info: https://doc.traefik.io/traefik/v2.9/routing/routers/#rule'
description: 'Match defines the router''s rule. More info: https://doc.traefik.io/traefik/v3.0/routing/routers/#rule'
type: string
middlewares:
description: 'Middlewares defines the list of references to
Middleware resources. More info: https://doc.traefik.io/traefik/v2.9/routing/providers/kubernetes-crd/#kind-middleware'
Middleware resources. More info: https://doc.traefik.io/traefik/v3.0/routing/providers/kubernetes-crd/#kind-middleware'
items:
description: MiddlewareRef is a reference to a Middleware
resource.
@ -79,7 +79,7 @@ spec:
type: array
priority:
description: 'Priority defines the router''s priority. More
info: https://doc.traefik.io/traefik/v2.9/routing/routers/#priority'
info: https://doc.traefik.io/traefik/v3.0/routing/routers/#priority'
type: integer
services:
description: Services defines the list of Service. It can contain
@ -145,7 +145,7 @@ spec:
type: string
sticky:
description: 'Sticky defines the sticky sessions configuration.
More info: https://doc.traefik.io/traefik/v2.9/routing/services/#sticky-sessions'
More info: https://doc.traefik.io/traefik/v3.0/routing/services/#sticky-sessions'
properties:
cookie:
description: Cookie defines the sticky cookie configuration.
@ -190,16 +190,16 @@ spec:
type: object
type: array
tls:
description: 'TLS defines the TLS configuration. More info: https://doc.traefik.io/traefik/v2.9/routing/routers/#tls'
description: 'TLS defines the TLS configuration. More info: https://doc.traefik.io/traefik/v3.0/routing/routers/#tls'
properties:
certResolver:
description: 'CertResolver defines the name of the certificate
resolver to use. Cert resolvers have to be configured in the
static configuration. More info: https://doc.traefik.io/traefik/v2.9/https/acme/#certificate-resolvers'
static configuration. More info: https://doc.traefik.io/traefik/v3.0/https/acme/#certificate-resolvers'
type: string
domains:
description: 'Domains defines the list of domains that will be
used to issue certificates. More info: https://doc.traefik.io/traefik/v2.9/routing/routers/#domains'
used to issue certificates. More info: https://doc.traefik.io/traefik/v3.0/routing/routers/#domains'
items:
description: Domain holds a domain name with SANs.
properties:
@ -217,15 +217,15 @@ spec:
options:
description: 'Options defines the reference to a TLSOption, that
specifies the parameters of the TLS connection. If not defined,
the `default` TLSOption is used. More info: https://doc.traefik.io/traefik/v2.9/https/tls/#tls-options'
the `default` TLSOption is used. More info: https://doc.traefik.io/traefik/v3.0/https/tls/#tls-options'
properties:
name:
description: 'Name defines the name of the referenced TLSOption.
More info: https://doc.traefik.io/traefik/v2.9/routing/providers/kubernetes-crd/#kind-tlsoption'
More info: https://doc.traefik.io/traefik/v3.0/routing/providers/kubernetes-crd/#kind-tlsoption'
type: string
namespace:
description: 'Namespace defines the namespace of the referenced
TLSOption. More info: https://doc.traefik.io/traefik/v2.9/routing/providers/kubernetes-crd/#kind-tlsoption'
TLSOption. More info: https://doc.traefik.io/traefik/v3.0/routing/providers/kubernetes-crd/#kind-tlsoption'
type: string
required:
- name
@ -241,11 +241,11 @@ spec:
properties:
name:
description: 'Name defines the name of the referenced TLSStore.
More info: https://doc.traefik.io/traefik/v2.9/routing/providers/kubernetes-crd/#kind-tlsstore'
More info: https://doc.traefik.io/traefik/v3.0/routing/providers/kubernetes-crd/#kind-tlsstore'
type: string
namespace:
description: 'Namespace defines the namespace of the referenced
TLSStore. More info: https://doc.traefik.io/traefik/v2.9/routing/providers/kubernetes-crd/#kind-tlsstore'
TLSStore. More info: https://doc.traefik.io/traefik/v3.0/routing/providers/kubernetes-crd/#kind-tlsstore'
type: string
required:
- name

View file

@ -39,7 +39,7 @@ spec:
entryPoints:
description: 'EntryPoints defines the list of entry point names to
bind to. Entry points have to be configured in the static configuration.
More info: https://doc.traefik.io/traefik/v2.9/routing/entrypoints/
More info: https://doc.traefik.io/traefik/v3.0/routing/entrypoints/
Default: all.'
items:
type: string
@ -50,7 +50,7 @@ spec:
description: RouteTCP holds the TCP route configuration.
properties:
match:
description: 'Match defines the router''s rule. More info: https://doc.traefik.io/traefik/v2.9/routing/routers/#rule_1'
description: 'Match defines the router''s rule. More info: https://doc.traefik.io/traefik/v3.0/routing/routers/#rule_1'
type: string
middlewares:
description: Middlewares defines the list of references to MiddlewareTCP
@ -73,7 +73,7 @@ spec:
type: array
priority:
description: 'Priority defines the router''s priority. More
info: https://doc.traefik.io/traefik/v2.9/routing/routers/#priority_1'
info: https://doc.traefik.io/traefik/v3.0/routing/routers/#priority_1'
type: integer
services:
description: Services defines the list of TCP services.
@ -98,7 +98,7 @@ spec:
x-kubernetes-int-or-string: true
proxyProtocol:
description: 'ProxyProtocol defines the PROXY protocol
configuration. More info: https://doc.traefik.io/traefik/v2.9/routing/services/#proxy-protocol'
configuration. More info: https://doc.traefik.io/traefik/v3.0/routing/services/#proxy-protocol'
properties:
version:
description: Version defines the PROXY Protocol version
@ -129,16 +129,16 @@ spec:
type: array
tls:
description: 'TLS defines the TLS configuration on a layer 4 / TCP
Route. More info: https://doc.traefik.io/traefik/v2.9/routing/routers/#tls_1'
Route. More info: https://doc.traefik.io/traefik/v3.0/routing/routers/#tls_1'
properties:
certResolver:
description: 'CertResolver defines the name of the certificate
resolver to use. Cert resolvers have to be configured in the
static configuration. More info: https://doc.traefik.io/traefik/v2.9/https/acme/#certificate-resolvers'
static configuration. More info: https://doc.traefik.io/traefik/v3.0/https/acme/#certificate-resolvers'
type: string
domains:
description: 'Domains defines the list of domains that will be
used to issue certificates. More info: https://doc.traefik.io/traefik/v2.9/routing/routers/#domains'
used to issue certificates. More info: https://doc.traefik.io/traefik/v3.0/routing/routers/#domains'
items:
description: Domain holds a domain name with SANs.
properties:
@ -156,7 +156,7 @@ spec:
options:
description: 'Options defines the reference to a TLSOption, that
specifies the parameters of the TLS connection. If not defined,
the `default` TLSOption is used. More info: https://doc.traefik.io/traefik/v2.9/https/tls/#tls-options'
the `default` TLSOption is used. More info: https://doc.traefik.io/traefik/v3.0/https/tls/#tls-options'
properties:
name:
description: Name defines the name of the referenced Traefik

View file

@ -39,7 +39,7 @@ spec:
entryPoints:
description: 'EntryPoints defines the list of entry point names to
bind to. Entry points have to be configured in the static configuration.
More info: https://doc.traefik.io/traefik/v2.9/routing/entrypoints/
More info: https://doc.traefik.io/traefik/v3.0/routing/entrypoints/
Default: all.'
items:
type: string

View file

@ -20,7 +20,7 @@ spec:
schema:
openAPIV3Schema:
description: 'Middleware is the CRD implementation of a Traefik Middleware.
More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/overview/'
More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/overview/'
properties:
apiVersion:
description: 'APIVersion defines the versioned schema of this representation
@ -40,7 +40,7 @@ spec:
addPrefix:
description: 'AddPrefix holds the add prefix middleware configuration.
This middleware updates the path of a request before forwarding
it. More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/addprefix/'
it. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/addprefix/'
properties:
prefix:
description: Prefix is the string to add before the current path
@ -50,11 +50,11 @@ spec:
basicAuth:
description: 'BasicAuth holds the basic auth middleware configuration.
This middleware restricts access to your services to known users.
More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/basicauth/'
More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/basicauth/'
properties:
headerField:
description: 'HeaderField defines a header field to store the
authenticated user. More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/basicauth/#headerfield'
authenticated user. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/basicauth/#headerfield'
type: string
realm:
description: 'Realm allows the protected resources on a server
@ -74,7 +74,7 @@ spec:
buffering:
description: 'Buffering holds the buffering middleware configuration.
This middleware retries or limits the size of requests that can
be forwarded to backends. More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/buffering/#maxrequestbodybytes'
be forwarded to backends. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/buffering/#maxrequestbodybytes'
properties:
maxRequestBodyBytes:
description: 'MaxRequestBodyBytes defines the maximum allowed
@ -107,13 +107,13 @@ spec:
retryExpression:
description: 'RetryExpression defines the retry conditions. It
is a logical combination of functions with operators AND (&&)
and OR (||). More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/buffering/#retryexpression'
and OR (||). More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/buffering/#retryexpression'
type: string
type: object
chain:
description: 'Chain holds the configuration of the chain middleware.
This middleware enables to define reusable combinations of other
pieces of middleware. More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/chain/'
pieces of middleware. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/chain/'
properties:
middlewares:
description: Middlewares is the list of MiddlewareRef which composes
@ -167,7 +167,7 @@ spec:
compress:
description: 'Compress holds the compress middleware configuration.
This middleware compresses responses before sending them to the
client, using gzip compression. More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/compress/'
client, using gzip compression. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/compress/'
properties:
excludedContentTypes:
description: ExcludedContentTypes defines the list of content
@ -185,28 +185,18 @@ spec:
type: object
contentType:
description: ContentType holds the content-type middleware configuration.
This middleware exists to enable the correct behavior until at least
the default one can be changed in a future version.
properties:
autoDetect:
description: AutoDetect specifies whether to let the `Content-Type`
header, if it has not been set by the backend, be automatically
set to a value derived from the contents of the response. As
a proxy, the default behavior should be to leave the header
alone, regardless of what the backend did with it. However,
the historic default was to always auto-detect and set the header
if it was nil, and it is going to be kept that way in order
to support users currently relying on it.
type: boolean
This middleware sets the `Content-Type` header value to the media
type detected from the response content, when it is not set by the
backend.
type: object
digestAuth:
description: 'DigestAuth holds the digest auth middleware configuration.
This middleware restricts access to your services to known users.
More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/digestauth/'
More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/digestauth/'
properties:
headerField:
description: 'HeaderField defines a header field to store the
authenticated user. More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/basicauth/#headerfield'
authenticated user. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/basicauth/#headerfield'
type: string
realm:
description: 'Realm allows the protected resources on a server
@ -225,7 +215,7 @@ spec:
errors:
description: 'ErrorPage holds the custom error middleware configuration.
This middleware returns a custom page in lieu of the default, according
to configured ranges of HTTP Status codes. More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/errorpages/'
to configured ranges of HTTP Status codes. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/errorpages/'
properties:
query:
description: Query defines the URL for the error page (hosted
@ -234,7 +224,7 @@ spec:
type: string
service:
description: 'Service defines the reference to a Kubernetes Service
that will serve the error page. More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/errorpages/#service'
that will serve the error page. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/errorpages/#service'
properties:
kind:
description: Kind defines the kind of the Service.
@ -291,7 +281,7 @@ spec:
type: string
sticky:
description: 'Sticky defines the sticky sessions configuration.
More info: https://doc.traefik.io/traefik/v2.9/routing/services/#sticky-sessions'
More info: https://doc.traefik.io/traefik/v3.0/routing/services/#sticky-sessions'
properties:
cookie:
description: Cookie defines the sticky cookie configuration.
@ -340,7 +330,7 @@ spec:
forwardAuth:
description: 'ForwardAuth holds the forward auth middleware configuration.
This middleware delegates the request authentication to a Service.
More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/forwardauth/'
More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/forwardauth/'
properties:
address:
description: Address defines the authentication server address.
@ -363,7 +353,7 @@ spec:
description: 'AuthResponseHeadersRegex defines the regex to match
headers to copy from the authentication server response and
set on forwarded request, after stripping all headers that match
the regex. More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/forwardauth/#authresponseheadersregex'
the regex. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/forwardauth/#authresponseheadersregex'
type: string
tls:
description: TLS defines the configuration used to secure the
@ -403,7 +393,7 @@ spec:
headers:
description: 'Headers holds the headers middleware configuration.
This middleware manages the requests and responses headers. More
info: https://doc.traefik.io/traefik/v2.9/middlewares/http/headers/#customrequestheaders'
info: https://doc.traefik.io/traefik/v3.0/middlewares/http/headers/#customrequestheaders'
properties:
accessControlAllowCredentials:
description: AccessControlAllowCredentials defines whether the
@ -547,7 +537,7 @@ spec:
inFlightReq:
description: 'InFlightReq holds the in-flight request middleware configuration.
This middleware limits the number of requests being processed and
served concurrently. More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/inflightreq/'
served concurrently. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/inflightreq/'
properties:
amount:
description: Amount defines the maximum amount of allowed simultaneous
@ -561,11 +551,11 @@ spec:
group requests as originating from a common source. If several
strategies are defined at the same time, an error will be raised.
If none are set, the default is to use the requestHost. More
info: https://doc.traefik.io/traefik/v2.9/middlewares/http/inflightreq/#sourcecriterion'
info: https://doc.traefik.io/traefik/v3.0/middlewares/http/inflightreq/#sourcecriterion'
properties:
ipStrategy:
description: 'IPStrategy holds the IP strategy configuration
used by Traefik to determine the client IP. More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/ipallowlist/#ipstrategy'
used by Traefik to determine the client IP. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/ipallowlist/#ipstrategy'
properties:
depth:
description: Depth tells Traefik to use the X-Forwarded-For
@ -593,11 +583,11 @@ spec:
ipAllowList:
description: 'IPAllowList holds the IP allowlist middleware configuration.
This middleware accepts / refuses requests based on the client IP.
More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/ipallowlist/'
More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/ipallowlist/'
properties:
ipStrategy:
description: 'IPStrategy holds the IP strategy configuration used
by Traefik to determine the client IP. More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/ipallowlist/#ipstrategy'
by Traefik to determine the client IP. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/ipallowlist/#ipstrategy'
properties:
depth:
description: Depth tells Traefik to use the X-Forwarded-For
@ -621,7 +611,7 @@ spec:
passTLSClientCert:
description: 'PassTLSClientCert holds the pass TLS client cert middleware
configuration. This middleware adds the selected data from the passed
client TLS certificate to a header. More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/passtlsclientcert/'
client TLS certificate to a header. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/passtlsclientcert/'
properties:
info:
description: Info selects the specific client certificate details
@ -728,7 +718,7 @@ spec:
rateLimit:
description: 'RateLimit holds the rate limit configuration. This middleware
ensures that services will receive a fair amount of requests, and
allows one to define what fair is. More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/ratelimit/'
allows one to define what fair is. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/ratelimit/'
properties:
average:
description: Average is the maximum rate, by default in requests/s,
@ -761,7 +751,7 @@ spec:
properties:
ipStrategy:
description: 'IPStrategy holds the IP strategy configuration
used by Traefik to determine the client IP. More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/ipallowlist/#ipstrategy'
used by Traefik to determine the client IP. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/ipallowlist/#ipstrategy'
properties:
depth:
description: Depth tells Traefik to use the X-Forwarded-For
@ -789,7 +779,7 @@ spec:
redirectRegex:
description: 'RedirectRegex holds the redirect regex middleware configuration.
This middleware redirects a request using regex matching and replacement.
More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/redirectregex/#regex'
More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/redirectregex/#regex'
properties:
permanent:
description: Permanent defines whether the redirection is permanent
@ -807,7 +797,7 @@ spec:
redirectScheme:
description: 'RedirectScheme holds the redirect scheme middleware
configuration. This middleware redirects requests from a scheme/port
to another. More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/redirectscheme/'
to another. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/redirectscheme/'
properties:
permanent:
description: Permanent defines whether the redirection is permanent
@ -823,7 +813,7 @@ spec:
replacePath:
description: 'ReplacePath holds the replace path middleware configuration.
This middleware replaces the path of the request URL and store the
original path in an X-Replaced-Path header. More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/replacepath/'
original path in an X-Replaced-Path header. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/replacepath/'
properties:
path:
description: Path defines the path to use as replacement in the
@ -833,7 +823,7 @@ spec:
replacePathRegex:
description: 'ReplacePathRegex holds the replace path regex middleware
configuration. This middleware replaces the path of a URL using
regex matching and replacement. More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/replacepathregex/'
regex matching and replacement. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/replacepathregex/'
properties:
regex:
description: Regex defines the regular expression used to match
@ -849,7 +839,7 @@ spec:
middleware reissues requests a given number of times to a backend
server if that server does not reply. As soon as the server answers,
the middleware stops retrying, regardless of the response status.
More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/retry/'
More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/retry/'
properties:
attempts:
description: Attempts defines how many times the request should
@ -869,7 +859,7 @@ spec:
stripPrefix:
description: 'StripPrefix holds the strip prefix middleware configuration.
This middleware removes the specified prefixes from the URL path.
More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/stripprefix/'
More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/stripprefix/'
properties:
prefixes:
description: Prefixes defines the prefixes to strip from the request
@ -881,7 +871,7 @@ spec:
stripPrefixRegex:
description: 'StripPrefixRegex holds the strip prefix regex middleware
configuration. This middleware removes the matching prefixes from
the URL path. More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/stripprefixregex/'
the URL path. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/stripprefixregex/'
properties:
regex:
description: Regex defines the regular expression to match the

View file

@ -20,7 +20,7 @@ spec:
schema:
openAPIV3Schema:
description: 'MiddlewareTCP is the CRD implementation of a Traefik TCP middleware.
More info: https://doc.traefik.io/traefik/v2.9/middlewares/overview/'
More info: https://doc.traefik.io/traefik/v3.0/middlewares/overview/'
properties:
apiVersion:
description: 'APIVersion defines the versioned schema of this representation

View file

@ -22,7 +22,7 @@ spec:
description: 'ServersTransport is the CRD implementation of a ServersTransport.
If no serversTransport is specified, the default@internal will be used.
The default@internal serversTransport is created from the static configuration.
More info: https://doc.traefik.io/traefik/v2.9/routing/services/#serverstransport_1'
More info: https://doc.traefik.io/traefik/v3.0/routing/services/#serverstransport_1'
properties:
apiVersion:
description: 'APIVersion defines the versioned schema of this representation

View file

@ -21,7 +21,7 @@ spec:
openAPIV3Schema:
description: 'TLSOption is the CRD implementation of a Traefik TLS Option,
allowing to configure some parameters of the TLS connection. More info:
https://doc.traefik.io/traefik/v2.9/https/tls/#tls-options'
https://doc.traefik.io/traefik/v3.0/https/tls/#tls-options'
properties:
apiVersion:
description: 'APIVersion defines the versioned schema of this representation
@ -41,13 +41,13 @@ spec:
alpnProtocols:
description: 'ALPNProtocols defines the list of supported application
level protocols for the TLS handshake, in order of preference. More
info: https://doc.traefik.io/traefik/v2.9/https/tls/#alpn-protocols'
info: https://doc.traefik.io/traefik/v3.0/https/tls/#alpn-protocols'
items:
type: string
type: array
cipherSuites:
description: 'CipherSuites defines the list of supported cipher suites
for TLS versions up to TLS 1.2. More info: https://doc.traefik.io/traefik/v2.9/https/tls/#cipher-suites'
for TLS versions up to TLS 1.2. More info: https://doc.traefik.io/traefik/v3.0/https/tls/#cipher-suites'
items:
type: string
type: array
@ -74,7 +74,7 @@ spec:
type: object
curvePreferences:
description: 'CurvePreferences defines the preferred elliptic curves
in a specific order. More info: https://doc.traefik.io/traefik/v2.9/https/tls/#curve-preferences'
in a specific order. More info: https://doc.traefik.io/traefik/v3.0/https/tls/#curve-preferences'
items:
type: string
type: array

View file

@ -22,7 +22,7 @@ spec:
description: 'TLSStore is the CRD implementation of a Traefik TLS Store. For
the time being, only the TLSStore named default is supported. This means
that you cannot have two stores that are named default in different Kubernetes
namespaces. More info: https://doc.traefik.io/traefik/v2.9/https/tls/#certificates-stores'
namespaces. More info: https://doc.traefik.io/traefik/v3.0/https/tls/#certificates-stores'
properties:
apiVersion:
description: 'APIVersion defines the versioned schema of this representation

View file

@ -21,7 +21,7 @@ spec:
openAPIV3Schema:
description: 'TraefikService is the CRD implementation of a Traefik Service.
TraefikService object allows to: - Apply weight to Services on load-balancing
- Mirror traffic on services More info: https://doc.traefik.io/traefik/v2.9/routing/providers/kubernetes-crd/#kind-traefikservice'
- Mirror traffic on services More info: https://doc.traefik.io/traefik/v3.0/routing/providers/kubernetes-crd/#kind-traefikservice'
properties:
apiVersion:
description: 'APIVersion defines the versioned schema of this representation
@ -120,7 +120,7 @@ spec:
type: string
sticky:
description: 'Sticky defines the sticky sessions configuration.
More info: https://doc.traefik.io/traefik/v2.9/routing/services/#sticky-sessions'
More info: https://doc.traefik.io/traefik/v3.0/routing/services/#sticky-sessions'
properties:
cookie:
description: Cookie defines the sticky cookie configuration.
@ -204,7 +204,7 @@ spec:
type: string
sticky:
description: 'Sticky defines the sticky sessions configuration.
More info: https://doc.traefik.io/traefik/v2.9/routing/services/#sticky-sessions'
More info: https://doc.traefik.io/traefik/v3.0/routing/services/#sticky-sessions'
properties:
cookie:
description: Cookie defines the sticky cookie configuration.
@ -304,7 +304,7 @@ spec:
type: string
sticky:
description: 'Sticky defines the sticky sessions configuration.
More info: https://doc.traefik.io/traefik/v2.9/routing/services/#sticky-sessions'
More info: https://doc.traefik.io/traefik/v3.0/routing/services/#sticky-sessions'
properties:
cookie:
description: Cookie defines the sticky cookie configuration.
@ -343,7 +343,7 @@ spec:
type: array
sticky:
description: 'Sticky defines whether sticky sessions are enabled.
More info: https://doc.traefik.io/traefik/v2.9/routing/providers/kubernetes-crd/#stickiness-and-load-balancing'
More info: https://doc.traefik.io/traefik/v3.0/routing/providers/kubernetes-crd/#stickiness-and-load-balancing'
properties:
cookie:
description: Cookie defines the sticky cookie configuration.

View file

@ -189,9 +189,6 @@ WriteTimeout is the maximum duration before timing out writes of the response. I
`--entrypoints.<name>.udp.timeout`:
Timeout defines how long to wait on an idle session before releasing the related resources. (Default: ```3```)
`--experimental.http3`:
Enable HTTP3. (Default: ```false```)
`--experimental.hub`:
Enable the Traefik Hub provider. (Default: ```false```)
@ -357,6 +354,51 @@ InfluxDB v2 push interval. (Default: ```10```)
`--metrics.influxdb2.token`:
InfluxDB v2 access token.
`--metrics.opentelemetry`:
OpenTelemetry metrics exporter type. (Default: ```false```)
`--metrics.opentelemetry.addentrypointslabels`:
Enable metrics on entry points. (Default: ```true```)
`--metrics.opentelemetry.address`:
Address (host:port) of the collector endpoint. (Default: ```localhost:4318```)
`--metrics.opentelemetry.addrouterslabels`:
Enable metrics on routers. (Default: ```false```)
`--metrics.opentelemetry.addserviceslabels`:
Enable metrics on services. (Default: ```true```)
`--metrics.opentelemetry.explicitboundaries`:
Boundaries for latency metrics. (Default: ```0.005000, 0.010000, 0.025000, 0.050000, 0.100000, 0.250000, 0.500000, 1.000000, 2.500000, 5.000000, 10.000000```)
`--metrics.opentelemetry.grpc`:
gRPC specific configuration for the OpenTelemetry collector. (Default: ```true```)
`--metrics.opentelemetry.headers.<name>`:
Headers sent with payload.
`--metrics.opentelemetry.insecure`:
Disables client transport security for the exporter. (Default: ```false```)
`--metrics.opentelemetry.path`:
Set the URL path of the collector endpoint.
`--metrics.opentelemetry.pushinterval`:
Period between calls to collect a checkpoint. (Default: ```10```)
`--metrics.opentelemetry.tls.ca`:
TLS CA
`--metrics.opentelemetry.tls.cert`:
TLS cert
`--metrics.opentelemetry.tls.insecureskipverify`:
TLS insecure skip verify (Default: ```false```)
`--metrics.opentelemetry.tls.key`:
TLS key
`--metrics.prometheus`:
Prometheus metrics exporter type. (Default: ```false```)
@ -1095,6 +1137,36 @@ Sets the sampling type. (Default: ```const```)
`--tracing.jaeger.tracecontextheadername`:
Sets the header name used to store the trace ID. (Default: ```uber-trace-id```)
`--tracing.opentelemetry`:
Settings for OpenTelemetry. (Default: ```false```)
`--tracing.opentelemetry.address`:
Sets the address (host:port) of the collector endpoint. (Default: ```localhost:4318```)
`--tracing.opentelemetry.grpc`:
gRPC specific configuration for the OpenTelemetry collector. (Default: ```true```)
`--tracing.opentelemetry.headers.<name>`:
Defines additional headers to be sent with the payloads.
`--tracing.opentelemetry.insecure`:
Disables client transport security for the exporter. (Default: ```false```)
`--tracing.opentelemetry.path`:
Sets the URL path of the collector endpoint.
`--tracing.opentelemetry.tls.ca`:
TLS CA
`--tracing.opentelemetry.tls.cert`:
TLS cert
`--tracing.opentelemetry.tls.insecureskipverify`:
TLS insecure skip verify (Default: ```false```)
`--tracing.opentelemetry.tls.key`:
TLS key
`--tracing.servicename`:
Set the name for this service. (Default: ```traefik```)

View file

@ -189,9 +189,6 @@ WriteTimeout is the maximum duration before timing out writes of the response. I
`TRAEFIK_ENTRYPOINTS_<NAME>_UDP_TIMEOUT`:
Timeout defines how long to wait on an idle session before releasing the related resources. (Default: ```3```)
`TRAEFIK_EXPERIMENTAL_HTTP3`:
Enable HTTP3. (Default: ```false```)
`TRAEFIK_EXPERIMENTAL_HUB`:
Enable the Traefik Hub provider. (Default: ```false```)
@ -357,6 +354,51 @@ InfluxDB retention policy used when protocol is http.
`TRAEFIK_METRICS_INFLUXDB_USERNAME`:
InfluxDB username (only with http).
`TRAEFIK_METRICS_OPENTELEMETRY`:
OpenTelemetry metrics exporter type. (Default: ```false```)
`TRAEFIK_METRICS_OPENTELEMETRY_ADDENTRYPOINTSLABELS`:
Enable metrics on entry points. (Default: ```true```)
`TRAEFIK_METRICS_OPENTELEMETRY_ADDRESS`:
Address (host:port) of the collector endpoint. (Default: ```localhost:4318```)
`TRAEFIK_METRICS_OPENTELEMETRY_ADDROUTERSLABELS`:
Enable metrics on routers. (Default: ```false```)
`TRAEFIK_METRICS_OPENTELEMETRY_ADDSERVICESLABELS`:
Enable metrics on services. (Default: ```true```)
`TRAEFIK_METRICS_OPENTELEMETRY_EXPLICITBOUNDARIES`:
Boundaries for latency metrics. (Default: ```0.005000, 0.010000, 0.025000, 0.050000, 0.100000, 0.250000, 0.500000, 1.000000, 2.500000, 5.000000, 10.000000```)
`TRAEFIK_METRICS_OPENTELEMETRY_GRPC`:
gRPC specific configuration for the OpenTelemetry collector. (Default: ```true```)
`TRAEFIK_METRICS_OPENTELEMETRY_HEADERS_<NAME>`:
Headers sent with payload.
`TRAEFIK_METRICS_OPENTELEMETRY_INSECURE`:
Disables client transport security for the exporter. (Default: ```false```)
`TRAEFIK_METRICS_OPENTELEMETRY_PATH`:
Set the URL path of the collector endpoint.
`TRAEFIK_METRICS_OPENTELEMETRY_PUSHINTERVAL`:
Period between calls to collect a checkpoint. (Default: ```10```)
`TRAEFIK_METRICS_OPENTELEMETRY_TLS_CA`:
TLS CA
`TRAEFIK_METRICS_OPENTELEMETRY_TLS_CERT`:
TLS cert
`TRAEFIK_METRICS_OPENTELEMETRY_TLS_INSECURESKIPVERIFY`:
TLS insecure skip verify (Default: ```false```)
`TRAEFIK_METRICS_OPENTELEMETRY_TLS_KEY`:
TLS key
`TRAEFIK_METRICS_PROMETHEUS`:
Prometheus metrics exporter type. (Default: ```false```)
@ -1095,6 +1137,36 @@ Sets the sampling type. (Default: ```const```)
`TRAEFIK_TRACING_JAEGER_TRACECONTEXTHEADERNAME`:
Sets the header name used to store the trace ID. (Default: ```uber-trace-id```)
`TRAEFIK_TRACING_OPENTELEMETRY`:
Settings for OpenTelemetry. (Default: ```false```)
`TRAEFIK_TRACING_OPENTELEMETRY_ADDRESS`:
Sets the address (host:port) of the collector endpoint. (Default: ```localhost:4318```)
`TRAEFIK_TRACING_OPENTELEMETRY_GRPC`:
gRPC specific configuration for the OpenTelemetry collector. (Default: ```true```)
`TRAEFIK_TRACING_OPENTELEMETRY_HEADERS_<NAME>`:
Defines additional headers to be sent with the payloads.
`TRAEFIK_TRACING_OPENTELEMETRY_INSECURE`:
Disables client transport security for the exporter. (Default: ```false```)
`TRAEFIK_TRACING_OPENTELEMETRY_PATH`:
Sets the URL path of the collector endpoint.
`TRAEFIK_TRACING_OPENTELEMETRY_TLS_CA`:
TLS CA
`TRAEFIK_TRACING_OPENTELEMETRY_TLS_CERT`:
TLS cert
`TRAEFIK_TRACING_OPENTELEMETRY_TLS_INSECURESKIPVERIFY`:
TLS insecure skip verify (Default: ```false```)
`TRAEFIK_TRACING_OPENTELEMETRY_TLS_KEY`:
TLS key
`TRAEFIK_TRACING_SERVICENAME`:
Set the name for this service. (Default: ```traefik```)

View file

@ -307,6 +307,25 @@
[metrics.influxDB2.additionalLabels]
name0 = "foobar"
name1 = "foobar"
[metrics.openTelemetry]
address = "foobar"
addEntryPointsLabels = true
addRoutersLabels = true
addServicesLabels = true
pushInterval = "42s"
path = "foobar"
explicitBoundaries = [42.0, 42.0]
insecure = true
[metrics.openTelemetry.headers]
name0 = "foobar"
name1 = "foobar"
[metrics.openTelemetry.tls]
ca = "foobar"
caOptional = true
cert = "foobar"
insecureSkipVerify = true
key = "foobar"
[metrics.openTelemetry.grpc]
[ping]
entryPoint = "foobar"
@ -391,6 +410,20 @@
serverURL = "foobar"
secretToken = "foobar"
serviceEnvironment = "foobar"
[tracing.openTelemetry]
address = "foobar"
insecure = true
path = "foobar"
[tracing.openTelemetry.headers]
name0 = "foobar"
name1 = "foobar"
[tracing.openTelemetry.tls]
ca = "foobar"
caOptional = true
cert = "foobar"
key = "foobar"
insecureSkipVerify = true
[tracing.openTelemetry.grpc]
[hostResolver]
cnameFlattening = true
@ -428,7 +461,6 @@
[experimental]
kubernetesGateway = true
http3 = true
hub = true
[experimental.plugins]
[experimental.plugins.Descriptor0]

View file

@ -335,6 +335,28 @@ metrics:
additionalLabels:
name0: foobar
name1: foobar
openTelemetry:
address: foobar
addEntryPointsLabels: true
addRoutersLabels: true
addServicesLabels: true
explicitBoundaries:
- 42
- 42
headers:
name0: foobar
name1: foobar
insecure: true
path: foobar
pushInterval: 42s
tls:
ca: foobar
caOptional: true
cert: foobar
insecureSkipVerify: true
key: foobar
grpc: {}
ping:
entryPoint: foobar
manualRouting: true
@ -417,6 +439,20 @@ tracing:
serverURL: foobar
secretToken: foobar
serviceEnvironment: foobar
openTelemetry:
address: foobar
headers:
name0: foobar
name1: foobar
insecure: true
path: foobar
tls:
ca: foobar
caOptional: true
cert: foobar
key: foobar
insecureSkipVerify: true
grpc: {}
hostResolver:
cnameFlattening: true
resolvConfig: foobar
@ -453,7 +489,6 @@ hub:
key: foobar
experimental:
kubernetesGateway: true
http3: true
hub: true
plugins:
Descriptor0:

View file

@ -312,40 +312,33 @@ entryPoints:
#### `http3`
`http3` enables HTTP/3 protocol on the entryPoint.
HTTP/3 requires a TCP entryPoint, as HTTP/3 always starts as a TCP connection that then gets upgraded to UDP.
In most scenarios, this entryPoint is the same as the one used for TLS traffic.
??? info "HTTP/3 uses UDP+TLS"
As HTTP/3 uses UDP, you can't have a TCP entryPoint with HTTP/3 on the same port as a UDP entryPoint.
Since HTTP/3 requires the use of TLS, only routers with TLS enabled will be usable with HTTP/3.
!!! warning "Enabling Experimental HTTP/3"
As the HTTP/3 spec is still in draft, HTTP/3 support in Traefik is an experimental feature and needs to be activated
in the experimental section of the static configuration.
HTTP/3 requires a TCP entryPoint,
as HTTP/3 always starts as a TCP connection that then gets upgraded to UDP.
In most scenarios,
this entryPoint is the same as the one used for TLS traffic.
```yaml tab="File (YAML)"
experimental:
http3: true
entryPoints:
name:
http3: {}
```
```toml tab="File (TOML)"
[experimental]
http3 = true
[entryPoints.name.http3]
```
```bash tab="CLI"
--experimental.http3=true
--entrypoints.name.http3
```
??? info "HTTP/3 uses UDP+TLS"
As HTTP/3 actually uses UDP, when traefik is configured with a TCP entryPoint on port N with HTTP/3 enabled,
the underlying HTTP/3 server that is started automatically listens on UDP port N too. As a consequence,
it means port N cannot be used by another UDP entryPoint.
Since HTTP/3 requires the use of TLS,
only routers with TLS enabled will be usable with HTTP/3.
#### `advertisedPort`
`http3.advertisedPort` defines which UDP port to advertise as the HTTP/3 authority.
@ -355,9 +348,6 @@ It can be used to override the authority in the `alt-svc` header, for example if
!!! info "http3.advertisedPort"
```yaml tab="File (YAML)"
experimental:
http3: true
entryPoints:
name:
http3:
@ -365,15 +355,11 @@ It can be used to override the authority in the `alt-svc` header, for example if
```
```toml tab="File (TOML)"
[experimental]
http3 = true
[entryPoints.name.http3]
advertisedPort = 443
```
```bash tab="CLI"
--experimental.http3=true
--entrypoints.name.http3.advertisedport=443
```

View file

@ -48,7 +48,7 @@ The Kubernetes Ingress Controller, The Custom Resource Way.
serviceAccountName: traefik-ingress-controller
containers:
- name: traefik
image: traefik:v2.9
image: traefik:v3.0
args:
- --log.level=DEBUG
- --api

View file

@ -147,7 +147,7 @@ which in turn will create the resulting routers, services, handlers, etc.
serviceAccountName: traefik-ingress-controller
containers:
- name: traefik
image: traefik:v2.9
image: traefik:v3.0
args:
- --entrypoints.web.address=:80
- --providers.kubernetesingress
@ -539,7 +539,7 @@ This way, any Ingress attached to this Entrypoint will have TLS termination by d
serviceAccountName: traefik-ingress-controller
containers:
- name: traefik
image: traefik:v2.9
image: traefik:v3.0
args:
- --entrypoints.websecure.address=:443
- --entrypoints.websecure.http.tls
@ -749,7 +749,7 @@ For more options, please refer to the available [annotations](#on-ingress).
serviceAccountName: traefik-ingress-controller
containers:
- name: traefik
image: traefik:v2.9
image: traefik:v3.0
args:
- --entrypoints.websecure.address=:443
- --providers.kubernetesingress

View file

@ -214,78 +214,226 @@ If you want to limit the router scope to a set of entry points, set the `entryPo
Rules are a set of matchers configured with values, that determine if a particular request matches specific criteria.
If the rule is verified, the router becomes active, calls middlewares, and then forwards the request to the service.
??? tip "Backticks or Quotes?"
To set the value of a rule, use [backticks](https://en.wiktionary.org/wiki/backtick) ``` ` ``` or escaped double-quotes `\"`.
Single quotes `'` are not accepted since the values are [Golang's String Literals](https://golang.org/ref/spec#String_literals).
!!! example "Host is example.com"
```toml
rule = "Host(`example.com`)"
```
!!! example "Host is example.com OR Host is example.org AND path is /traefik"
```toml
rule = "Host(`example.com`) || (Host(`example.org`) && Path(`/traefik`))"
```
The table below lists all the available matchers:
| Rule | Description |
|------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------|
| ```Headers(`key`, `value`)``` | Check if there is a key `key`defined in the headers, with the value `value` |
| ```HeadersRegexp(`key`, `regexp`)``` | Check if there is a key `key`defined in the headers, with a value that matches the regular expression `regexp` |
| ```Host(`example.com`, ...)``` | Check if the request domain (host header value) targets one of the given `domains`. |
| ```HostHeader(`example.com`, ...)``` | Same as `Host`, only exists for historical reasons. |
| ```HostRegexp(`example.com`, `{subdomain:[a-z]+}.example.com`, ...)``` | Match the request domain. See "Regexp Syntax" below. |
| ```Method(`GET`, ...)``` | Check if the request method is one of the given `methods` (`GET`, `POST`, `PUT`, `DELETE`, `PATCH`, `HEAD`) |
| ```Path(`/path`, `/articles/{cat:[a-z]+}/{id:[0-9]+}`, ...)``` | Match exact request path. See "Regexp Syntax" below. |
| ```PathPrefix(`/products/`, `/articles/{cat:[a-z]+}/{id:[0-9]+}`)``` | Match request prefix path. See "Regexp Syntax" below. |
| ```Query(`foo=bar`, `bar=baz`)``` | Match Query String parameters. It accepts a sequence of key=value pairs. |
| ```ClientIP(`10.0.0.0/16`, `::1`)``` | Match if the request client IP is one of the given IP/CIDR. It accepts IPv4, IPv6 and CIDR formats. |
|-----------------------------------------------------------------|:-------------------------------------------------------------------------------|
| [```Header(`key`, `value`)```](#header-and-headerregexp) | Matches requests containing a header named `key` set to `value`. |
| [```HeaderRegexp(`key`, `regexp`)```](#header-and-headerregexp) | Matches requests containing a header named `key` matching `regexp`. |
| [```Host(`domain`)```](#host-and-hostregexp) | Matches requests host set to `domain`. |
| [```HostRegexp(`regexp`)```](#host-and-hostregexp) | Matches requests host matching `regexp`. |
| [```Method(`method`)```](#method) | Matches requests method set to `method`. |
| [```Path(`path`)```](#path-pathprefix-and-pathregexp) | Matches requests path set to `path`. |
| [```PathPrefix(`prefix`)```](#path-pathprefix-and-pathregexp) | Matches requests path prefix set to `prefix`. |
| [```PathRegexp(`regexp`)```](#path-pathprefix-and-pathregexp) | Matches request path using `regexp`. |
| [```Query(`key`, `value`)```](#query-and-queryregexp) | Matches requests query parameters named `key` set to `value`. |
| [```QueryRegexp(`key`, `regexp`)```](#query-and-queryregexp) | Matches requests query parameters named `key` matching `regexp`. |
| [```ClientIP(`ip`)```](#clientip) | Matches requests client IP using `ip`. It accepts IPv4, IPv6 and CIDR formats. |
!!! important "Non-ASCII Domain Names"
!!! tip "Backticks or Quotes?"
Non-ASCII characters are not supported in `Host` and `HostRegexp` expressions, and by doing so the associated router will be invalid.
For the `Host` expression, domain names containing non-ASCII characters must be provided as punycode encoded values ([rfc 3492](https://tools.ietf.org/html/rfc3492)).
As well, when using the `HostRegexp` expressions, in order to match domain names containing non-ASCII characters, the regular expression should match a punycode encoded domain name.
To set the value of a rule, use [backticks](https://en.wiktionary.org/wiki/backtick) ``` ` ``` or escaped double-quotes `\"`.
Single quotes `'` are not accepted since the values are [Go's String Literals](https://golang.org/ref/spec#String_literals).
!!! important "Regexp Syntax"
`HostRegexp`, `PathPrefix`, and `Path` accept an expression with zero or more groups enclosed by curly braces, which are called named regexps.
Named regexps, of the form `{name:regexp}`, are the only expressions considered for regexp matching.
The regexp name (`name` in the above example) is an arbitrary value, that exists only for historical reasons.
Matchers that accept a regexp as their value use a [Go](https://golang.org/pkg/regexp/) flavored syntax.
Any `regexp` supported by [Go's regexp package](https://golang.org/pkg/regexp/) may be used.
For example, here is a case insensitive path matcher syntax: ```Path(`/{path:(?i:Products)}`)```.
!!! info "Combining Matchers Using Operators and Parenthesis"
!!! info "Expressing Complex Rules Using Operators and Parenthesis"
The usual AND (`&&`) and OR (`||`) logical operators can be used, with the expected precedence rules,
as well as parentheses.
!!! info "Inverting a matcher"
One can invert a matcher by using the NOT (`!`) operator.
One can invert a matcher by using the `!` operator.
The following rule matches requests where:
!!! important "Rule, Middleware, and Services"
- either host is `example.com` OR,
- host is `example.org` AND path is NOT `/traefik`
The rule is evaluated "before" any middleware has the opportunity to work, and "before" the request is forwarded to the service.
```yaml
Host(`example.com`) || (Host(`example.org`) && !Path(`/traefik`))
```
!!! info "Path Vs PathPrefix"
#### Header and HeaderRegexp
Use `Path` if your service listens on the exact path only. For instance, ```Path(`/products`)``` would match `/products` but not `/products/shoes`.
The `Header` and `HeaderRegexp` matchers allow to match requests that contain specific header.
Use a `*Prefix*` matcher if your service listens on a particular base path but also serves requests on sub-paths.
For instance, ```PathPrefix(`/products`)``` would match `/products` and `/products/shoes`,
as well as `/productsforsale`, and `/productsforsale/shoes`.
Since the path is forwarded as-is, your service is expected to listen on `/products`.
!!! example "Examples"
!!! info "ClientIP matcher"
Match requests with a `Content-Type` header set to `application/yaml`:
The `ClientIP` matcher will only match the request client IP and does not use the `X-Forwarded-For` header for matching.
```yaml
Header(`Content-Type`, `application/yaml`)
```
Match requests with a `Content-Type` header set to either `application/json` or `application/yaml`:
```yaml
HeaderRegexp(`Content-Type`, `^application/(json|yaml)$`)
```
To match headers [case-insensitively](https://en.wikipedia.org/wiki/Case_sensitivity), use the `(?i)` option:
```yaml
HeaderRegexp(`Content-Type`, `(?i)^application/(json|yaml)$`)
```
#### Host and HostRegexp
The `Host` and `HostRegexp` matchers allow to match requests that are targeted to a given host.
These matchers do not support non-ASCII characters, use punycode encoded values ([rfc 3492](https://tools.ietf.org/html/rfc3492)) to match such domains.
If no Host is set in the request URL (e.g., it's an IP address), these matchers will look at the `Host` header.
These matchers will match the request's host in lowercase.
!!! example "Examples"
Match requests with `Host` set to `example.com`:
```yaml
Host(`example.com`)
```
Match requests sent to any subdomain of `example.com`:
```yaml
HostRegexp(`^.+\.example\.com$`)
```
Match requests with `Host` set to either `example.com` or `example.org`:
```yaml
HostRegexp(`^example\.(com|org)$`)
```
To match domains [case-insensitively](https://en.wikipedia.org/wiki/Case_sensitivity), use the `(?i)` option:
```yaml
HostRegexp(`(?i)^example\.(com|org)$`)
```
#### Method
The `Method` matchers allows to match requests sent with the given method.
!!! example "Example"
Match `OPTIONS` requests:
```yaml
Method(`OPTIONS`)
```
#### Path, PathPrefix, and PathRegexp
These matchers allow matching requests based on their URL path.
For exact matches, use `Path` and its prefixed alternative `PathPrefix`, for regexp matches, use `PathRegexp`.
Path are always starting with a `/`, except for `PathRegexp`.
!!! example "Examples"
Match `/products` but neither `/products/shoes` nor `/products/`:
```yaml
Path(`/products`)
```
Match `/products` as well as everything under `/products`,
such as `/products/shoes`, `/products/` but also `/products-for-sale`:
```yaml
PathPrefix(`/products`)
```
Match both `/products/shoes` and `/products/socks` with and ID like `/products/shoes/57`:
```yaml
PathRegexp(`^/products/(shoes|socks)/[0-9]+$`)
```
Match requests with a path ending in either `.jpeg`, `.jpg` or `.png`:
```yaml
PathRegexp(`\.(jpeg|jpg|png)$`)
```
Match `/products` as well as everything under `/products`,
such as `/products/shoes`, `/products/` but also `/products-for-sale`,
[case-insensitively](https://en.wikipedia.org/wiki/Case_sensitivity):
```yaml
HostRegexp(`(?i)^/products`)
```
#### Query and QueryRegexp
The `Query` and `QueryRegexp` matchers allow to match requests based on query parameters.
!!! example "Examples"
Match requests with a `mobile` query parameter set to `true`, such as in `/search?mobile=true`:
```yaml
Query(`mobile`, `true`)
```
To match requests with a query parameter `mobile` that has no value, such as in `/search?mobile`, use:
```yaml
Query(`mobile`)
```
Match requests with a `mobile` query parameter set to either `true` or `yes`:
```yaml
QueryRegexp(`mobile`, `^(true|yes)$`)
```
Match requests with a `mobile` query parameter set to any value (including the empty value):
```yaml
QueryRegexp(`mobile`, `^.*$`)
```
To match query parameters [case-insensitively](https://en.wikipedia.org/wiki/Case_sensitivity), use the `(?i)` option:
```yaml
QueryRegexp(`mobile`, `(?i)^(true|yes)$`)
```
#### ClientIP
The `ClientIP` matcher allows matching requests sent from the given client IP.
It only matches the request client IP and does not use the `X-Forwarded-For` header for matching.
!!! example "Examples"
Match requests coming from a given IP:
```yaml tab="IPv4"
ClientIP(`10.76.105.11`)
```
```yaml tab="IPv6"
ClientIP(`::1`)
```
Match requests coming from a given subnet:
```yaml tab="IPv4"
ClientIP(`192.168.1.0/24`)
```
```yaml tab="IPv6"
ClientIP(`fe80::/10`)
```
### Priority
@ -300,7 +448,7 @@ A value of `0` for the priority is ignored: `priority = 0` means that the defaul
http:
routers:
Router-1:
rule: "HostRegexp(`{subdomain:[a-z]+}.traefik.com`)"
rule: "HostRegexp(`[a-z]+\.traefik\.com`)"
# ...
Router-2:
rule: "Host(`foobar.traefik.com`)"
@ -311,7 +459,7 @@ A value of `0` for the priority is ignored: `priority = 0` means that the defaul
## Dynamic configuration
[http.routers]
[http.routers.Router-1]
rule = "HostRegexp(`{subdomain:[a-z]+}.traefik.com`)"
rule = "HostRegexp(`[a-z]+\\.traefik\\.com`)"
# ...
[http.routers.Router-2]
rule = "Host(`foobar.traefik.com`)"
@ -321,8 +469,8 @@ A value of `0` for the priority is ignored: `priority = 0` means that the defaul
In this case, all requests with host `foobar.traefik.com` will be routed through `Router-1` instead of `Router-2`.
| Name | Rule | Priority |
|----------|----------------------------------------------------|----------|
| Router-1 | ```HostRegexp(`{subdomain:[a-z]+}.traefik.com`)``` | 44 |
|----------|------------------------------------------|----------|
| Router-1 | ```HostRegexp(`[a-z]+\.traefik\.com`)``` | 44 |
| Router-2 | ```Host(`foobar.traefik.com`)``` | 26 |
The previous table shows that `Router-1` has a higher priority than `Router-2`.
@ -336,7 +484,7 @@ A value of `0` for the priority is ignored: `priority = 0` means that the defaul
http:
routers:
Router-1:
rule: "HostRegexp(`{subdomain:[a-z]+}.traefik.com`)"
rule: "HostRegexp(`[a-z]+\\.traefik\\.com`)"
entryPoints:
- "web"
service: service-1
@ -353,7 +501,7 @@ A value of `0` for the priority is ignored: `priority = 0` means that the defaul
## Dynamic configuration
[http.routers]
[http.routers.Router-1]
rule = "HostRegexp(`{subdomain:[a-z]+}.traefik.com`)"
rule = "HostRegexp(`[a-z]+\\.traefik\\.com`)"
entryPoints = ["web"]
service = "service-1"
priority = 1
@ -818,48 +966,49 @@ If you want to limit the router scope to a set of entry points, set the entry po
### Rule
Rules are a set of matchers configured with values, that determine if a particular request matches specific criteria.
Rules are a set of matchers configured with values, that determine if a particular connection matches specific criteria.
If the rule is verified, the router becomes active, calls middlewares, and then forwards the request to the service.
??? tip "Backticks or Quotes?"
To set the value of a rule, use [backticks](https://en.wiktionary.org/wiki/backtick) ``` ` ``` or escaped double-quotes `\"`.
Single quotes `'` are not accepted since the values are [Golang's String Literals](https://golang.org/ref/spec#String_literals).
!!! example "HostSNI is example.com"
```toml
rule = "HostSNI(`example.com`)"
```
!!! example "HostSNI is example.com OR HostSNI is example.org AND ClientIP is 0.0.0.0"
```toml
rule = "HostSNI(`example.com`) || (HostSNI(`example.org`) && ClientIP(`0.0.0.0`))"
```
The table below lists all the available matchers:
| Rule | Description |
|---------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------|
| ```HostSNI(`domain-1`, ...)``` | Checks if the Server Name Indication corresponds to the given `domains`. |
| ```HostSNIRegexp(`example.com`, `{subdomain:[a-z]+}.example.com`, ...)``` | Checks if the Server Name Indication matches the given regular expressions. See "Regexp Syntax" below. |
| ```ClientIP(`10.0.0.0/16`, `::1`)``` | Checks if the connection client IP is one of the given IP/CIDR. It accepts IPv4, IPv6 and CIDR formats. |
| ```ALPN(`mqtt`, `h2c`)``` | Checks if any of the connection ALPN protocols is one of the given protocols. |
|-------------------------------------------------------------|:-------------------------------------------------------------------------------------------------|
| [```HostSNI(`domain`)```](#hostsni-and-hostsniregexp) | Checks if the connection's Server Name Indication is equal to `domain`. |
| [```HostSNIRegexp(`regexp`)```](#hostsni-and-hostsniregexp) | Checks if the connection's Server Name Indication matches `regexp`. |
| [```ClientIP(`ip`)```](#clientip_1) | Checks if the connection's client IP correspond to `ip`. It accepts IPv4, IPv6 and CIDR formats. |
| [```ALPN(`protocol`)```](#alpn) | Checks if the connection's ALPN protocol equals `protocol`. |
!!! important "Non-ASCII Domain Names"
!!! tip "Backticks or Quotes?"
Non-ASCII characters are not supported in the `HostSNI` and `HostSNIRegexp` expressions, and so using them would invalidate the associated TCP router.
Domain names containing non-ASCII characters must be provided as punycode encoded values ([rfc 3492](https://tools.ietf.org/html/rfc3492)).
To set the value of a rule, use [backticks](https://en.wiktionary.org/wiki/backtick) ``` ` ``` or escaped double-quotes `\"`.
Single quotes `'` are not accepted since the values are [Go's String Literals](https://golang.org/ref/spec#String_literals).
!!! important "Regexp Syntax"
`HostSNIRegexp` accepts an expression with zero or more groups enclosed by curly braces, which are called named regexps.
Named regexps, of the form `{name:regexp}`, are the only expressions considered for regexp matching.
The regexp name (`name` in the above example) is an arbitrary value, that exists only for historical reasons.
Matchers that accept a regexp as their value use a [Go](https://golang.org/pkg/regexp/) flavored syntax.
Any `regexp` supported by [Go's regexp package](https://golang.org/pkg/regexp/) may be used.
!!! info "Expressing Complex Rules Using Operators and Parenthesis"
The usual AND (`&&`) and OR (`||`) logical operators can be used, with the expected precedence rules,
as well as parentheses.
One can invert a matcher by using the NOT (`!`) operator.
The following rule matches connections where:
- either Server Name Indication is `example.com` OR,
- Server Name Indication is `example.org` AND ALPN protocol is NOT `h2`
```yaml
HostSNI(`example.com`) || (HostSNI(`example.org`) && !ALPN(`h2`))
```
#### HostSNI and HostSNIRegexp
`HostSNI` and `HostSNIRegexp` matchers allow to match connections targeted to a given domain.
These matchers do not support non-ASCII characters, use punycode encoded values ([rfc 3492](https://tools.ietf.org/html/rfc3492)) to match such domains.
!!! important "HostSNI & TLS"
@ -869,26 +1018,73 @@ The table below lists all the available matchers:
when one wants a non-TLS router that matches all (non-TLS) requests,
one should use the specific ```HostSNI(`*`)``` syntax.
!!! info "Combining Matchers Using Operators and Parenthesis"
!!! example "Examples"
The usual AND (`&&`) and OR (`||`) logical operators can be used, with the expected precedence rules,
as well as parentheses.
Match all connections:
!!! info "Inverting a matcher"
```yaml tab="HostSNI"
HostSNI(`*`)
```
One can invert a matcher by using the `!` operator.
```yaml tab="HostSNIRegexp"
HostSNIRegexp(`^.*$`)
```
!!! important "Rule, Middleware, and Services"
Match TCP connections sent to `example.com`:
The rule is evaluated "before" any middleware has the opportunity to work, and "before" the request is forwarded to the service.
```yaml
HostSNI(`example.com`)
```
!!! important "ALPN ACME-TLS/1"
Match TCP connections openned on any subdomain of `example.com`:
```yaml
HostSNIRegexp(`^.+\.example\.com$`)
```
#### ClientIP
The `ClientIP` matcher allows matching connections opened by a client with the given IP.
!!! example "Examples"
Match connections opened by a given IP:
```yaml tab="IPv4"
ClientIP(`10.76.105.11`)
```
```yaml tab="IPv6"
ClientIP(`::1`)
```
Match connections coming from a given subnet:
```yaml tab="IPv4"
ClientIP(`192.168.1.0/24`)
```
```yaml tab="IPv6"
ClientIP(`fe80::/10`)
```
#### ALPN
The `ALPN` matcher allows matching connections the given protocol.
It would be a security issue to let a user-defined router catch the response to
an ACME TLS challenge previously initiated by Traefik.
For this reason, the `ALPN` matcher is not allowed to match the `ACME-TLS/1`
protocol, and Traefik returns an error if this is attempted.
!!! example "Example"
Match connections using the ALPN protocol `h2`:
```yaml
ALPN(`h2`)
```
### Priority
To avoid path overlap, routes are sorted, by default, in descending order using rules length.

View file

@ -29,6 +29,12 @@ This agent can:
* The Traefik Hub Agent must be installed to connect to the Traefik Hub platform.
* Activate this feature in the experimental section of the static configuration.
!!! information "Configuration Discovery"
According to installation options, the Traefik Hub Agent listens to the Docker or Kubernetes API to discover containers/services.
It doesn't support the routers discovered by Traefik Proxy using other providers, e.g., using the File provider.
!!! example "Minimal Static Configuration to Activate Traefik Hub for Docker"
```yaml tab="File (YAML)"

View file

@ -26,7 +26,7 @@ spec:
serviceAccountName: traefik-ingress-controller
containers:
- name: traefik
image: traefik:v2.9
image: traefik:v3.0
args:
- --api.insecure
- --accesslog

View file

@ -26,5 +26,5 @@ node:
- K3S_CLUSTER_SECRET=somethingtotallyrandom
volumes:
# this is where you would place a alternative traefik image (saved as a .tar file with
# 'docker save'), if you want to use it, instead of the traefik:v2.9 image.
# 'docker save'), if you want to use it, instead of the traefik:v3.0 image.
- /somewhere/on/your/host/custom-image:/var/lib/rancher/k3s/agent/images

View file

@ -3,7 +3,7 @@ version: "3.3"
services:
traefik:
image: "traefik:v2.9"
image: "traefik:v3.0"
container_name: "traefik"
command:
#- "--log.level=DEBUG"

View file

@ -13,7 +13,7 @@ secrets:
services:
traefik:
image: "traefik:v2.9"
image: "traefik:v3.0"
container_name: "traefik"
command:
#- "--log.level=DEBUG"

View file

@ -3,7 +3,7 @@ version: "3.3"
services:
traefik:
image: "traefik:v2.9"
image: "traefik:v3.0"
container_name: "traefik"
command:
#- "--log.level=DEBUG"

View file

@ -3,7 +3,7 @@ version: "3.3"
services:
traefik:
image: "traefik:v2.9"
image: "traefik:v3.0"
container_name: "traefik"
command:
#- "--log.level=DEBUG"

View file

@ -3,7 +3,7 @@ version: "3.3"
services:
traefik:
image: "traefik:v2.9"
image: "traefik:v3.0"
container_name: "traefik"
command:
#- "--log.level=DEBUG"

View file

@ -16,6 +16,35 @@ This will also be used as a starting point for the other docker-compose guides.
--8<-- "content/user-guides/docker-compose/basic-example/docker-compose.yml"
```
??? Networking
The Traefik container has to be attached to the same network as the containers to be exposed.
If no networks are specified in the docker-compose file, Docker creates a default one that allows Traefik to reach the containers defined in the same file.
You can [customize the network](https://docs.docker.com/compose/networking/#specify-custom-networks) as described in the example below.
You can use a [pre-existing network](https://docs.docker.com/compose/networking/#use-a-pre-existing-network) too.
```yaml
version: "3.3"
networks:
traefiknet: {}
services:
traefik:
image: "traefik:v3.0"
...
networks:
- traefiknet
whoami:
image: "traefik/whoami"
...
networks:
- traefiknet
```
- Replace `whoami.localhost` by your **own domain** within the `traefik.http.routers.whoami.rule` label of the `whoami` service.
- Run `docker-compose up -d` within the folder where you created the previous file.
- Wait a bit and visit `http://your_own_domain` to confirm everything went fine.

View file

@ -157,6 +157,7 @@ nav:
- 'Datadog': 'observability/metrics/datadog.md'
- 'InfluxDB': 'observability/metrics/influxdb.md'
- 'InfluxDB2': 'observability/metrics/influxdb2.md'
- 'OpenTelemetry': 'observability/metrics/opentelemetry.md'
- 'Prometheus': 'observability/metrics/prometheus.md'
- 'StatsD': 'observability/metrics/statsd.md'
- 'Tracing':
@ -167,6 +168,7 @@ nav:
- 'Instana': 'observability/tracing/instana.md'
- 'Haystack': 'observability/tracing/haystack.md'
- 'Elastic': 'observability/tracing/elastic.md'
- 'OpenTelemetry': 'observability/tracing/opentelemetry.md'
- 'User Guides':
- 'Kubernetes and Let''s Encrypt': 'user-guides/crd-acme/index.md'
- 'gRPC Examples': 'user-guides/grpc.md'

105
go.mod
View file

@ -20,7 +20,7 @@ require (
github.com/docker/go-connections v0.4.0
github.com/fatih/structs v1.1.0
github.com/gambol99/go-marathon v0.0.0-20180614232016-99a156b96fb2
github.com/go-acme/lego/v4 v4.9.0
github.com/go-acme/lego/v4 v4.9.1
github.com/go-check/check v0.0.0-00010101000000-000000000000
github.com/go-kit/kit v0.10.1-0.20200915143503-439c4d2ed3ea
github.com/golang/protobuf v1.5.2
@ -47,7 +47,7 @@ require (
github.com/lucas-clemente/quic-go v0.28.1
github.com/mailgun/ttlmap v0.0.0-20170619185759-c1c17f74874f
github.com/miekg/dns v1.1.50
github.com/mitchellh/copystructure v1.0.0
github.com/mitchellh/copystructure v1.2.0
github.com/mitchellh/hashstructure v1.0.0
github.com/mitchellh/mapstructure v1.5.0
github.com/natefinch/lumberjack v0.0.0-20201021141957-47ffae23317c
@ -77,33 +77,45 @@ require (
github.com/vulcand/predicate v1.2.0
go.elastic.co/apm v1.13.1
go.elastic.co/apm/module/apmot v1.13.1
golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4
golang.org/x/net v0.1.0
golang.org/x/text v0.4.0
golang.org/x/time v0.0.0-20220224211638-0e9765cccd65
golang.org/x/tools v0.1.12
google.golang.org/grpc v1.46.0
go.opentelemetry.io/collector/pdata v0.64.1
go.opentelemetry.io/otel v1.11.1
go.opentelemetry.io/otel/bridge/opentracing v1.11.1
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.33.0
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.33.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.11.1
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.11.1
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.11.1
go.opentelemetry.io/otel/metric v0.33.0
go.opentelemetry.io/otel/sdk v1.11.1
go.opentelemetry.io/otel/sdk/metric v0.33.0
go.opentelemetry.io/otel/trace v1.11.1
golang.org/x/exp v0.0.0-20221114191408-850992195362
golang.org/x/mod v0.6.0
golang.org/x/net v0.3.1-0.20221206200815-1e63c2f08a10
golang.org/x/text v0.5.0
golang.org/x/time v0.0.0-20220609170525-579cf78fd858
golang.org/x/tools v0.2.0
google.golang.org/grpc v1.50.1
gopkg.in/DataDog/dd-trace-go.v1 v1.43.1
gopkg.in/fsnotify.v1 v1.4.7
gopkg.in/yaml.v3 v3.0.1
k8s.io/api v0.22.1
k8s.io/apiextensions-apiserver v0.21.3
k8s.io/apimachinery v0.22.1
k8s.io/client-go v0.22.1
k8s.io/utils v0.0.0-20210820185131-d34e5cb4466e
k8s.io/api v0.25.0
k8s.io/apiextensions-apiserver v0.25.0
k8s.io/apimachinery v0.25.0
k8s.io/client-go v0.25.0
k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed
mvdan.cc/xurls/v2 v2.1.0
sigs.k8s.io/gateway-api v0.4.0
)
require (
cloud.google.com/go v0.81.0 // indirect
cloud.google.com/go v0.97.0 // indirect
github.com/AlecAivazis/survey/v2 v2.2.3 // indirect
github.com/Azure/azure-sdk-for-go v40.3.0+incompatible // indirect
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
github.com/Azure/go-autorest v14.2.0+incompatible // indirect
github.com/Azure/go-autorest/autorest v0.11.24 // indirect
github.com/Azure/go-autorest/autorest/adal v0.9.18 // indirect
github.com/Azure/go-autorest/autorest v0.11.27 // indirect
github.com/Azure/go-autorest/autorest/adal v0.9.20 // indirect
github.com/Azure/go-autorest/autorest/azure/auth v0.5.11 // indirect
github.com/Azure/go-autorest/autorest/azure/cli v0.4.5 // indirect
github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect
@ -121,6 +133,8 @@ require (
github.com/Microsoft/go-winio v0.5.2 // indirect
github.com/Microsoft/hcsshim v0.8.23 // indirect
github.com/OpenDNS/vegadns2client v0.0.0-20180418235048-a3fa4a771d87 // indirect
github.com/PuerkitoBio/purell v1.1.1 // indirect
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
github.com/Shopify/sarama v1.23.1 // indirect
github.com/VividCortex/gohistogram v1.0.0 // indirect
github.com/agl/ed25519 v0.0.0-20170116200512-5312a6153412 // indirect
@ -165,7 +179,8 @@ require (
github.com/elastic/go-licenser v0.3.1 // indirect
github.com/elastic/go-sysinfo v1.1.1 // indirect
github.com/elastic/go-windows v1.0.0 // indirect
github.com/evanphx/json-patch v4.11.0+incompatible // indirect
github.com/emicklei/go-restful/v3 v3.8.0 // indirect
github.com/evanphx/json-patch v4.12.0+incompatible // indirect
github.com/exoscale/egoscale v0.90.0 // indirect
github.com/fatih/color v1.13.0 // indirect
github.com/fsnotify/fsnotify v1.5.4 // indirect
@ -173,7 +188,11 @@ require (
github.com/ghodss/yaml v1.0.0 // indirect
github.com/go-errors/errors v1.0.1 // indirect
github.com/go-logfmt/logfmt v0.5.1 // indirect
github.com/go-logr/logr v0.4.0 // indirect
github.com/go-logr/logr v1.2.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-openapi/jsonpointer v0.19.5 // indirect
github.com/go-openapi/jsonreference v0.19.5 // indirect
github.com/go-openapi/swag v0.19.14 // indirect
github.com/go-redis/redis/v8 v8.11.5 // indirect
github.com/go-resty/resty/v2 v2.1.1-0.20191201195748-d7b97669fe48 // indirect
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect
@ -182,21 +201,22 @@ require (
github.com/gogo/googleapis v1.4.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang-jwt/jwt/v4 v4.2.0 // indirect
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b // indirect
github.com/golang/glog v1.0.0 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/mock v1.6.0 // indirect
github.com/google/btree v1.0.1 // indirect
github.com/google/go-cmp v0.5.8 // indirect
github.com/google/gnostic v0.5.7-v3refs // indirect
github.com/google/go-cmp v0.5.9 // indirect
github.com/google/go-querystring v1.1.0 // indirect
github.com/google/gofuzz v1.2.0 // indirect
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/googleapis/gax-go/v2 v2.0.5 // indirect
github.com/googleapis/gnostic v0.5.5 // indirect
github.com/googleapis/gax-go/v2 v2.1.0 // indirect
github.com/gophercloud/gophercloud v1.0.0 // indirect
github.com/gophercloud/utils v0.0.0-20210216074907-f6de111f2eae // indirect
github.com/gravitational/trace v1.1.16-0.20220114165159-14a9a7dd6aaf // indirect
github.com/grpc-ecosystem/go-grpc-middleware v1.2.2 // indirect
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0 // indirect
github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645 // indirect
github.com/hashicorp/consul/sdk v0.10.0 // indirect
github.com/hashicorp/cronexpr v1.1.1 // indirect
@ -237,6 +257,7 @@ require (
github.com/liquidweb/liquidweb-cli v0.6.9 // indirect
github.com/liquidweb/liquidweb-go v1.6.3 // indirect
github.com/looplab/fsm v0.1.0 // indirect
github.com/magiconair/properties v1.8.6 // indirect
github.com/mailgun/minheap v0.0.0-20170619185613-3dbe6c6bf55f // indirect
github.com/mailgun/multibuf v0.1.2 // indirect
github.com/mailgun/timetools v0.0.0-20141028012446-7e6055773c51 // indirect
@ -256,7 +277,7 @@ require (
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/mitchellh/go-ps v1.0.0 // indirect
github.com/mitchellh/go-testing-interface v1.14.1 // indirect
github.com/mitchellh/reflectwalk v1.0.1 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/moby/buildkit v0.8.2-0.20210401015549-df49b648c8bf // indirect
github.com/moby/locker v1.0.1 // indirect
github.com/moby/sys/mount v0.2.0 // indirect
@ -265,6 +286,7 @@ require (
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/morikuni/aec v1.0.0 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04 // indirect
github.com/nrdcg/auroradns v1.1.0 // indirect
github.com/nrdcg/desec v0.6.0 // indirect
@ -300,7 +322,7 @@ require (
github.com/softlayer/softlayer-go v1.0.6 // indirect
github.com/softlayer/xmlrpc v0.0.0-20200409220501-5f089df7cb7e // indirect
github.com/spf13/cast v1.3.1 // indirect
github.com/spf13/cobra v1.2.1 // indirect
github.com/spf13/cobra v1.4.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/stretchr/objx v0.5.0 // indirect
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.490 // indirect
@ -325,22 +347,26 @@ require (
go.etcd.io/etcd/client/pkg/v3 v3.5.4 // indirect
go.etcd.io/etcd/client/v3 v3.5.4 // indirect
go.opencensus.io v0.23.0 // indirect
go.uber.org/atomic v1.7.0 // indirect
go.uber.org/multierr v1.6.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.11.1 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.33.0 // indirect
go.opentelemetry.io/proto/otlp v0.19.0 // indirect
go.uber.org/atomic v1.9.0 // indirect
go.uber.org/multierr v1.8.0 // indirect
go.uber.org/ratelimit v0.2.0 // indirect
go.uber.org/zap v1.18.1 // indirect
go.uber.org/zap v1.21.0 // indirect
go4.org/intern v0.0.0-20211027215823-ae77deb06f29 // indirect
go4.org/unsafe/assume-no-moving-gc v0.0.0-20220617031537-928513b29760 // indirect
golang.org/x/crypto v0.1.0 // indirect
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 // indirect
golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1 // indirect
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 // indirect
golang.org/x/sys v0.1.0 // indirect
golang.org/x/term v0.1.0 // indirect
golang.org/x/sys v0.3.0 // indirect
golang.org/x/term v0.3.0 // indirect
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
google.golang.org/api v0.44.0 // indirect
google.golang.org/api v0.57.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto v0.0.0-20211021150943-2b146023228c // indirect
google.golang.org/protobuf v1.28.0 // indirect
google.golang.org/genproto v0.0.0-20220502173005-c8bf987b8c21 // indirect
google.golang.org/protobuf v1.28.1 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/ini.v1 v1.66.6 // indirect
gopkg.in/ns1/ns1-go.v2 v2.6.5 // indirect
@ -349,11 +375,12 @@ require (
gopkg.in/yaml.v2 v2.4.0 // indirect
howett.net/plist v0.0.0-20181124034731-591f970eefbb // indirect
inet.af/netaddr v0.0.0-20220617031823-097006376321 // indirect
k8s.io/klog/v2 v2.10.0 // indirect
k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e // indirect
k8s.io/klog/v2 v2.70.1 // indirect
k8s.io/kube-openapi v0.0.0-20220803162953-67bda5d908f1 // indirect
nhooyr.io/websocket v1.8.7 // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.1.2 // indirect
sigs.k8s.io/yaml v1.2.0 // indirect
sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect
sigs.k8s.io/yaml v1.3.0 // indirect
)
// Containous forks
@ -370,3 +397,5 @@ replace github.com/jaguilar/vt100 => github.com/tonistiigi/vt100 v0.0.0-20190402
// ambiguous import: found package github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/http in multiple modules
// tencentcloud uses monorepo with multimodule but the go.mod files are incomplete.
exclude github.com/tencentcloud/tencentcloud-sdk-go v3.0.83+incompatible
// replace github.com/go-logr/logr => github.com/go-logr/logr v0.4.0

232
go.sum
View file

@ -24,8 +24,15 @@ cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKP
cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk=
cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg=
cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8=
cloud.google.com/go v0.81.0 h1:at8Tk2zUz63cLPR0JPWm5vp77pEZmzxEQBEfRKn1VV8=
cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0=
cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY=
cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM=
cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY=
cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ=
cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI=
cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4=
cloud.google.com/go v0.97.0 h1:3DXvAyifywvq64LfkKaMOmkWPS1CikIQdMe2lY9vxU8=
cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
@ -70,8 +77,9 @@ github.com/Azure/go-autorest/autorest v0.10.0/go.mod h1:/FALq9T/kS7b5J5qsQ+RSTUd
github.com/Azure/go-autorest/autorest v0.11.1/go.mod h1:JFgpikqFJ/MleTTxwepExTKnFUKKszPS8UavbQYUMuw=
github.com/Azure/go-autorest/autorest v0.11.12/go.mod h1:eipySxLmqSyC5s5k1CLupqet0PSENBEDP93LQ9a8QYw=
github.com/Azure/go-autorest/autorest v0.11.18/go.mod h1:dSiJPy22c3u0OtOKDNttNgqpNFY/GeWa7GH/Pz56QRA=
github.com/Azure/go-autorest/autorest v0.11.24 h1:1fIGgHKqVm54KIPT+q8Zmd1QlVsmHqeUGso5qm2BqqE=
github.com/Azure/go-autorest/autorest v0.11.24/go.mod h1:G6kyRlFnTuSbEYkQGawPfsCswgme4iYf6rfSKUDzbCc=
github.com/Azure/go-autorest/autorest v0.11.27 h1:F3R3q42aWytozkV8ihzcgMO4OA4cuqr3bNlsEuF6//A=
github.com/Azure/go-autorest/autorest v0.11.27/go.mod h1:7l8ybrIdUmGqZMTD0sRtAr8NvbHjfofbf8RSP2q7w7U=
github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0=
github.com/Azure/go-autorest/autorest/adal v0.8.0/go.mod h1:Z6vX6WXXuyieHAXwMj0S6HY6e6wcHn37qQMBQlvY3lc=
github.com/Azure/go-autorest/autorest/adal v0.8.1/go.mod h1:ZjhuQClTqx435SRJ2iMlOxPYt3d2C/T/7TiQCVZSn3Q=
@ -79,8 +87,9 @@ github.com/Azure/go-autorest/autorest/adal v0.8.2/go.mod h1:ZjhuQClTqx435SRJ2iMl
github.com/Azure/go-autorest/autorest/adal v0.9.0/go.mod h1:/c022QCutn2P7uY+/oQWWNcK9YU+MH96NgK+jErpbcg=
github.com/Azure/go-autorest/autorest/adal v0.9.5/go.mod h1:B7KF7jKIeC9Mct5spmyCB/A8CG/sEz1vwIRGv/bbw7A=
github.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M=
github.com/Azure/go-autorest/autorest/adal v0.9.18 h1:kLnPsRjzZZUF3K5REu/Kc+qMQrvuza2bwSnNdhmzLfQ=
github.com/Azure/go-autorest/autorest/adal v0.9.18/go.mod h1:XVVeme+LZwABT8K5Lc3hA4nAe8LDBVle26gTrguhhPQ=
github.com/Azure/go-autorest/autorest/adal v0.9.20 h1:gJ3E98kMpFB1MFqQCvA1yFab8vthOeD4VlFRQULxahg=
github.com/Azure/go-autorest/autorest/adal v0.9.20/go.mod h1:XVVeme+LZwABT8K5Lc3hA4nAe8LDBVle26gTrguhhPQ=
github.com/Azure/go-autorest/autorest/azure/auth v0.4.2/go.mod h1:90gmfKdlmKgfjUpnCEpOJzsUEjrWDSLwHIG73tSXddM=
github.com/Azure/go-autorest/autorest/azure/auth v0.5.11 h1:P6bYXFoao05z5uhOQzbC3Qd8JqF3jUoocoTeIxkp2cA=
github.com/Azure/go-autorest/autorest/azure/auth v0.5.11/go.mod h1:84w/uV8E37feW2NCJ08uT9VBfjfUHpgLVnG2InYD6cg=
@ -95,8 +104,9 @@ github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxB
github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0=
github.com/Azure/go-autorest/autorest/mocks v0.3.0/go.mod h1:a8FDP3DYzQ4RYfVAxAN3SVSiiO77gL2j2ronKKP0syM=
github.com/Azure/go-autorest/autorest/mocks v0.4.0/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k=
github.com/Azure/go-autorest/autorest/mocks v0.4.1 h1:K0laFcLE6VLTOwNgSxaGbUcLPuGXlNkbVvq4cW4nIHk=
github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k=
github.com/Azure/go-autorest/autorest/mocks v0.4.2 h1:PGN4EDXnuQbojHbU0UWoNvmu9AGVwYHG9/fkDYhtAfw=
github.com/Azure/go-autorest/autorest/mocks v0.4.2/go.mod h1:Vy7OitM9Kei0i1Oj+LvyAWMXJHeKH1MVlzFugfVrmyU=
github.com/Azure/go-autorest/autorest/to v0.3.0/go.mod h1:MgwOyqaIuKdG4TL/2ywSsIWKAfJfgHDo8ObuUk3t5sA=
github.com/Azure/go-autorest/autorest/to v0.4.0 h1:oXVqrxakqqV1UZdSazDOPOLvOIz+XA683u8EctwboHk=
github.com/Azure/go-autorest/autorest/to v0.4.0/go.mod h1:fE8iZBn7LQR7zH/9XU2NcPR4o9jEImooCeWJcYV/zLE=
@ -175,8 +185,10 @@ github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAE
github.com/OpenDNS/vegadns2client v0.0.0-20180418235048-a3fa4a771d87 h1:xPMsUicZ3iosVPSIP7bW5EcGUzjiiMl1OYTe14y/R24=
github.com/OpenDNS/vegadns2client v0.0.0-20180418235048-a3fa4a771d87/go.mod h1:iGLljf5n9GjT6kc0HBvyI1nOKnGQbNB66VzSNbK5iks=
github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI=
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d h1:UrqY+r/OJnIp5u0s1SbQ8dVfLCZJsnvazdBP5hS4iRs=
github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ=
@ -480,6 +492,7 @@ github.com/cpu/goacmedns v0.1.1/go.mod h1:MuaouqEhPAHxsbqjgnck5zeghuwBP1dLnPoobe
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/creack/pty v1.1.11 h1:07n33Z8lZxZ2qwegKbObQohDhXDQxiMMz1NOUGYlesw=
@ -608,6 +621,8 @@ github.com/elazarl/goproxy v0.0.0-20191011121108-aa519ddbe484/go.mod h1:Ro8st/El
github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2/go.mod h1:gNh8nYJoAm43RfaxurUnxr+N1PwuFV3ZMl/efxlIlY8=
github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
github.com/emicklei/go-restful/v3 v3.8.0 h1:eCZ8ulSerjdAiaNpF7GxXIE7ZCMo1moN1qX+S609eVw=
github.com/emicklei/go-restful/v3 v3.8.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
@ -624,8 +639,9 @@ github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a
github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ=
github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/evanphx/json-patch v4.11.0+incompatible h1:glyUF9yIYtMHzn8xaKw5rMhdWcwsYV8dZHIq5567/xs=
github.com/evanphx/json-patch v4.11.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/evanphx/json-patch v4.12.0+incompatible h1:4onqiflcdA9EOZ4RxV643DvftH5pOlLGNtQ5lPWQu84=
github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/exoscale/egoscale v0.90.0 h1:DZBXVU3iHqu5Ju5lQ5jWVlPo0IpI98SUo8Aa1UQVrmo=
github.com/exoscale/egoscale v0.90.0/go.mod h1:wyXE5zrnFynMXA0jMhwQqSe24CfUhmBk2WI5wFZcq6Y=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
@ -669,8 +685,8 @@ github.com/gin-gonic/gin v1.7.4/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjX
github.com/gin-gonic/gin v1.7.7 h1:3DoBmSbJbZAWqXJC3SLjAPfutPJJRN1U5pALB7EeTTs=
github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
github.com/go-acme/lego/v4 v4.9.0 h1:8Hjj44IqRS7cigshMyFQ+0pIZvwgkG/+9A0UnNh7G8A=
github.com/go-acme/lego/v4 v4.9.0/go.mod h1:g3JRUyWS3L/VObpp4bCxzJftKyf/Wba8QrSSnoiqjg4=
github.com/go-acme/lego/v4 v4.9.1 h1:n9Z5MQwANeGSQKlVE3bEh9SDvAySK9oVYOKCGCESqQE=
github.com/go-acme/lego/v4 v4.9.1/go.mod h1:g3JRUyWS3L/VObpp4bCxzJftKyf/Wba8QrSSnoiqjg4=
github.com/go-asn1-ber/asn1-ber v1.3.1/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
github.com/go-chi/chi/v5 v5.0.0/go.mod h1:BBug9lr0cqtdAhsu6R4AAdvufI0/XBzAQSsUqJpoZOs=
github.com/go-cmd/cmd v1.0.5/go.mod h1:y8q8qlK5wQibcw63djSl/ntiHUHXHGdCkPk0j4QeW4s=
@ -695,17 +711,24 @@ github.com/go-logfmt/logfmt v0.5.1 h1:otpy5pqBCBZ1ng9RQ0dPu4PN7ba75Y/aA+UpowDyNV
github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas=
github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU=
github.com/go-logr/logr v0.4.0 h1:K7/B1jt6fIBQVd4Owv2MqGQClcgf0R266+7C/QjRcLc=
github.com/go-logr/logr v0.4.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU=
github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0=
github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-logr/zapr v0.4.0/go.mod h1:tabnROwaDl0UNxkVeFRbY8bwB37GwRv0P8lg6aAiEnk=
github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM=
github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0=
github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg=
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY=
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg=
github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc=
github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8=
github.com/go-openapi/jsonreference v0.19.5 h1:1WJP/wi4OjB4iV8KVbH73rQaoialJrqv8gitZLxGLtM=
github.com/go-openapi/jsonreference v0.19.5/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg=
github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc=
github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo=
@ -713,6 +736,7 @@ github.com/go-openapi/spec v0.19.5/go.mod h1:Hm2Jr4jv8G1ciIAo+frC/Ft+rR2kQDh8JHK
github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I=
github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
github.com/go-openapi/swag v0.19.14 h1:gm3vOOXfiuw5i9p5N9xJvfjvuofpyvLA9Wr6QfK5Fng=
github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
@ -784,8 +808,9 @@ github.com/golang-jwt/jwt/v4 v4.1.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzw
github.com/golang-jwt/jwt/v4 v4.2.0 h1:besgBTC8w8HjP6NzQdxwKH9Z5oQMZ24ThTrHp3cZ8eU=
github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/glog v1.0.0 h1:nfP3RFugxnNRyKgeWd4oI1nYvXpxrx8ck8ZrcizshdQ=
github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4=
github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
@ -826,6 +851,7 @@ github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
github.com/golangci/lint-1 v0.0.0-20181222135242-d2cdd8c08219/go.mod h1:/X8TswGSh1pIozq4ZwCfxS0WA5JGXguxk94ar/4c87Y=
github.com/gomodule/redigo v1.8.2/go.mod h1:P9dn9mFrCBvWhGE1wpxx6fgq7BAeLBk+UUUzlpkBYO0=
@ -836,6 +862,8 @@ github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4=
github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA=
github.com/google/certificate-transparency-go v1.0.21 h1:Yf1aXowfZ2nuboBsg7iYGLmwsOARdV86pfH3g95wXmE=
github.com/google/certificate-transparency-go v1.0.21/go.mod h1:QeJfpSbVSfYc7RgB3gJFj9cbuQMMchQxrWXz8Ruopmg=
github.com/google/gnostic v0.5.7-v3refs h1:FhTMOKj2VhjpouxvWJAV1TL304uMlb9zcDqkl6cEI54=
github.com/google/gnostic v0.5.7-v3refs/go.mod h1:73MKFl6jIHelAJNaBGFzt3SPtZULs9dYrGFt8OiIsHQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
@ -849,8 +877,8 @@ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-containerregistry v0.0.0-20191015185424-71da34e4d9b3/go.mod h1:ZXFeSndFcK4vB1NR4voH1Zm38K7ViUNiYtfIBDxrwf0=
github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ=
github.com/google/go-github/v28 v28.1.1 h1:kORf5ekX5qwXO2mGzXXOjMe/g6ap8ahVe0sBEulhSxo=
@ -869,6 +897,7 @@ github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
@ -882,6 +911,8 @@ github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLe
github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
@ -895,15 +926,15 @@ github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+
github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY=
github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/googleapis/gax-go/v2 v2.1.0 h1:6DWmvNpomjL1+3liNSZbVns3zsYzzCjm6pRBO1tLeso=
github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0=
github.com/googleapis/gnostic v0.0.0-20170426233943-68f4ded48ba9/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
github.com/googleapis/gnostic v0.2.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
github.com/googleapis/gnostic v0.2.2/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg=
github.com/googleapis/gnostic v0.5.1/go.mod h1:6U4PtQXGIEt/Z3h5MAT7FNofLnw9vXk2cUuW7uA/OeU=
github.com/googleapis/gnostic v0.5.5 h1:9fHAtK0uDfpveeqqo1hkEZJcFvYXAiCN3UutL8F9xHw=
github.com/googleapis/gnostic v0.5.5/go.mod h1:7+EbHbldMins07ALC74bsA81Ovc97DwqyJO1AENw9kA=
github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8=
github.com/gophercloud/gophercloud v0.15.1-0.20210202035223-633d73521055/go.mod h1:wRtmUelyIIv3CSSDI47aUwbs075O6i+LY+pXsKCBsb4=
@ -929,13 +960,16 @@ github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:Fecb
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-middleware v1.2.0/go.mod h1:mJzapYve32yjrKlk9GbyCZHuPgZsrbyIbyKhSzOpg6s=
github.com/grpc-ecosystem/go-grpc-middleware v1.2.2 h1:FlFbCRLd5Jr4iYXZufAvgWN6Ao0JrI5chLINnUXDDr0=
github.com/grpc-ecosystem/go-grpc-middleware v1.2.2/go.mod h1:EaizFBKfUKtMIF5iaDEhniwNedqGo9FuLFzppDr3uwI=
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 h1:+9834+KizmvFV7pXQGSXQTsaWhq2GjuNUt0aUU0YBYw=
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw=
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0 h1:BZHcxBETFHIdVyhyEfOvn/RdU/QGdLI4y34qQGjGWO0=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0/go.mod h1:hgWBS7lorOAVIJEQMi4ZsPv9hVvWI6+ch50m39Pf2Ks=
github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645 h1:MJG/KsmcqMwFAkh8mTnAwhyKoB+sTAnY4CACC110tbU=
github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645/go.mod h1:6iZfnjpejD4L/4DwD7NryNaJyCQdzwWwH2MWhCA90Kw=
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw=
@ -1249,8 +1283,9 @@ github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0Q
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/magiconair/properties v1.8.4/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
github.com/magiconair/properties v1.8.5 h1:b6kJs+EmPFMYGkow9GiUyCyOvIwYetYJ3fSaWak/Gls=
github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo=
github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
github.com/mailgun/multibuf v0.1.2 h1:QE9kE27lK6LFZB4aYNVtUPlWVHVCT0zpgUr2uoq/+jk=
github.com/mailgun/multibuf v0.1.2/go.mod h1:E+sUhIy69qgT6EM57kCPdUTlHnjTuxQBO/yf6af9Hes=
github.com/mailgun/timetools v0.0.0-20141028012446-7e6055773c51 h1:Kg/NPZLLC3aAFr1YToMs98dbCdhootQ1hZIvZU28hAQ=
@ -1326,8 +1361,9 @@ github.com/mimuret/golang-iij-dpf v0.7.1/go.mod h1:IXWYcQVIHYzuM+W7kDWX0mseHDfUo
github.com/mistifyio/go-zfs v2.1.2-0.20190413222219-f784269be439+incompatible/go.mod h1:8AuVvqP/mXw1px98n46wfvcGfQ4ci2FwoAjKYxuo3Z4=
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI=
github.com/mitchellh/copystructure v1.0.0 h1:Laisrj+bAB6b/yJwB5Bt3ITZhGJdqmxquMKeZ+mmkFQ=
github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
@ -1358,8 +1394,9 @@ github.com/mitchellh/osext v0.0.0-20151018003038-5e2d6d41470f/go.mod h1:OkQIRizQ
github.com/mitchellh/pointerstructure v1.0.0 h1:ATSdz4NWrmWPOF1CeCBU4sMCno2hgqdbSrRPFWQSVZI=
github.com/mitchellh/pointerstructure v1.0.0/go.mod h1:k4XwG94++jLVsSiTxo7qdIfXA9pj9EAeo0QsNNJOLZ8=
github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/mitchellh/reflectwalk v1.0.1 h1:FVzMWA5RllMAKIdUSC8mdWo3XtwoecrH79BY70sEEpE=
github.com/mitchellh/reflectwalk v1.0.1/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/moby/buildkit v0.8.2-0.20210401015549-df49b648c8bf h1:dHwWBX8OhYb69qVcT27rFSwzKsn4CRbg0HInQyVh33c=
github.com/moby/buildkit v0.8.2-0.20210401015549-df49b648c8bf/go.mod h1:GJcrUlTGFAPlEmPQtbrTsJYn+cy+Jwl7vTZS7jYAoow=
github.com/moby/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg=
@ -1393,6 +1430,7 @@ github.com/mreiferson/go-httpclient v0.0.0-20160630210159-31f0106b4474/go.mod h1
github.com/mrunalp/fileutils v0.0.0-20200520151820-abd8a0e76976/go.mod h1:x8F1gnqOkIEiO4rqoeEEEqQbo7HjGMTvyoq3gej4iT0=
github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ=
github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f h1:KUppIJq7/+SVif2QVs3tOP0zanoHgBEVAwHxUSIzRqU=
@ -1456,6 +1494,7 @@ github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
github.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c=
github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c=
github.com/onsi/ginkgo/v2 v2.1.4 h1:GNapqRSid3zijZ9H77KrgVG4/8KqiyRsxcSxe+7ApXY=
github.com/onsi/gomega v0.0.0-20151007035656-2152b45fa28a/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
@ -1751,8 +1790,9 @@ github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tL
github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE=
github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI=
github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo=
github.com/spf13/cobra v1.2.1 h1:+KmjbUw1hriSNMF55oPrkZcb27aECyrj8V2ytv7kWDw=
github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk=
github.com/spf13/cobra v1.4.0 h1:y+wJpx64xcgO1V+RcnwW0LEHxTKRi2ZDPSBjWnrg88Q=
github.com/spf13/cobra v1.4.0/go.mod h1:Wo4iy3BUC+X2Fybo0PDqwJIv3dNRiZLHQymsfxlB84g=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
@ -1956,26 +1996,60 @@ go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M=
go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
go.opentelemetry.io/collector/pdata v0.64.1 h1:8E06uHr0nnenGftFwhwdenA88QhVnF4dJam+qVXgdVg=
go.opentelemetry.io/collector/pdata v0.64.1/go.mod h1:IzvXUGQml2mrnvdb8zIlEW3qQs9oFLdD2hLwJdZ+pek=
go.opentelemetry.io/otel v1.11.1 h1:4WLLAmcfkmDk2ukNXJyq3/kiz/3UzCaYq6PskJsaou4=
go.opentelemetry.io/otel v1.11.1/go.mod h1:1nNhXBbWSD0nsL38H6btgnFN2k4i0sNLHNNMZMSbUGE=
go.opentelemetry.io/otel/bridge/opentracing v1.11.1 h1:/ZBsgjXWUpiZ5M9zm+Ft3kuDUGErIGcEJbKRIsFN6jA=
go.opentelemetry.io/otel/bridge/opentracing v1.11.1/go.mod h1:vw9hN4H+G0ek+XQtxP+Mm1McLcmdx2FXHNrWn2bBqxU=
go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.11.1 h1:X2GndnMCsUPh6CiY2a+frAbNsXaPLbB0soHRYhAZ5Ig=
go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.11.1/go.mod h1:i8vjiSzbiUC7wOQplijSXMYUpNM93DtlS5CbUT+C6oQ=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.33.0 h1:OT/UjHcjog4A1s1UMCtyehIKS+vpjM5Du0r7KGsH6TE=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.33.0/go.mod h1:0XctNDHEWmiSDIU8NPbJElrK05gBJFcYlGP4FMGo4g4=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.33.0 h1:1SVtGtRsNyGgv1fRfNXfh+sJowIwzF0gkf+61lvTgdg=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.33.0/go.mod h1:ryB27ubOBXsiqfh6MwtSdx5knzbSZtjvPnMMmt3AykQ=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.33.0 h1:NoG4v01cdLZfOeNGBQmSe4f4SeP+fx8I/0qzRgTKsGI=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.33.0/go.mod h1:6anbDXBcTp3Qit87pfFmT0paxTJ8sWRccTNYVywN/H8=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.11.1 h1:MEQNafcNCB0uQIti/oHgU7CZpUMYQ7qigBwMVKycHvc=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.11.1/go.mod h1:19O5I2U5iys38SsmT2uDJja/300woyzE1KPIQxEUBUc=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.11.1 h1:LYyG/f1W/jzAix16jbksJfMQFpOH/Ma6T639pVPMgfI=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.11.1/go.mod h1:QrRRQiY3kzAoYPNLP0W/Ikg0gR6V3LMc+ODSxr7yyvg=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.11.1 h1:tFl63cpAAcD9TOU6U8kZU7KyXuSRYAZlbx1C61aaB74=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.11.1/go.mod h1:X620Jww3RajCJXw/unA+8IRTgxkdS7pi+ZwK9b7KUJk=
go.opentelemetry.io/otel/metric v0.33.0 h1:xQAyl7uGEYvrLAiV/09iTJlp1pZnQ9Wl793qbVvED1E=
go.opentelemetry.io/otel/metric v0.33.0/go.mod h1:QlTYc+EnYNq/M2mNk1qDDMRLpqCOj2f/r5c7Fd5FYaI=
go.opentelemetry.io/otel/sdk v1.11.1 h1:F7KmQgoHljhUuJyA+9BiU+EkJfyX5nVVF4wyzWZpKxs=
go.opentelemetry.io/otel/sdk v1.11.1/go.mod h1:/l3FE4SupHJ12TduVjUkZtlfFqDCQJlOlithYrdktys=
go.opentelemetry.io/otel/sdk/metric v0.33.0 h1:oTqyWfksgKoJmbrs2q7O7ahkJzt+Ipekihf8vhpa9qo=
go.opentelemetry.io/otel/sdk/metric v0.33.0/go.mod h1:xdypMeA21JBOvjjzDUtD0kzIcHO/SPez+a8HOzJPGp0=
go.opentelemetry.io/otel/trace v1.11.1 h1:ofxdnzsNrGBYXbP7t7zpUK281+go5rF7dvdIZXF8gdQ=
go.opentelemetry.io/otel/trace v1.11.1/go.mod h1:f/Q9G7vzk5u91PhbmKbg1Qn0rzH1LJ4vbPHFGkTPtOk=
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
go.opentelemetry.io/proto/otlp v0.19.0 h1:IVN6GR+mhC4s5yfcTbmzHYODqvWAp3ZedA2SJPI1Nnw=
go.opentelemetry.io/proto/otlp v0.19.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U=
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/goleak v1.1.10 h1:z+mqJhf6ss6BSfSM671tgKyZBFPTTJM+HLxnhPC3wu0=
go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A=
go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4=
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
go.uber.org/multierr v1.8.0 h1:dg6GjLku4EH+249NNmoIciG9N/jURbDG+pFlTkhzIC8=
go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak=
go.uber.org/ratelimit v0.2.0 h1:UQE2Bgi7p2B85uP5dC2bbRtig0C+OeNRnNEafLjsLPA=
go.uber.org/ratelimit v0.2.0/go.mod h1:YYBV4e4naJvhpitQrWJu1vCpgB7CboMe0qhltKt6mUg=
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
go.uber.org/zap v1.18.1 h1:CSUJ2mjFszzEWt4CdKISEuChVIXGBn3lAPwkRGyVrc4=
go.uber.org/zap v1.18.1/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI=
go.uber.org/zap v1.21.0 h1:WefMeulhovoZ2sYXz7st6K0sLj7bBhpiFaud4r4zST8=
go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw=
go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE=
go4.org/intern v0.0.0-20211027215823-ae77deb06f29 h1:UXLjNohABv4S58tHmeuIZDO6e3mHpW2Dx33gaNt03LE=
go4.org/intern v0.0.0-20211027215823-ae77deb06f29/go.mod h1:cS2ma+47FKrLPdXFpr7CuxiTW3eyJbWew4qx0qtQWDA=
@ -2028,8 +2102,8 @@ golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5y
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20211202192323-5770296d904e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f h1:OeJjE6G4dgCY4PIXvIRQbE8+RX+uXZyGhUy/ksMGJoc=
golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU=
golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@ -2044,7 +2118,8 @@ golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u0
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/exp v0.0.0-20200331195152-e8c3332aa8e5/go.mod h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw=
golang.org/x/exp v0.0.0-20200908183739-ae8ad444f925 h1:5XVKs2rlCg8EFyRcvO8/XFwYxh1oKJO1Q3X5vttIf9c=
golang.org/x/exp v0.0.0-20221114191408-850992195362 h1:NoHlPRbyl1VFI6FjwHtPQCN7wAMXI6cKcqrmXhOOfBQ=
golang.org/x/exp v0.0.0-20221114191408-850992195362/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
@ -2074,8 +2149,8 @@ golang.org/x/mod v0.3.1-0.20200828183125-ce943fd02449/go.mod h1:s0Qsj1ACt9ePp/hM
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.6.0 h1:b9gGHsz9/HhJ3HF5DHQytPpuwocVTChQJK3AvoLRD5I=
golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI=
golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180811021610-c39426892332/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@ -2138,6 +2213,7 @@ golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLd
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8=
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210520170846-37e1c6afe023/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
@ -2148,8 +2224,8 @@ golang.org/x/net v0.0.0-20211216030914-fe4d6282115f/go.mod h1:9nx3DQGgdP8bBQD5qx
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.1.0 h1:hZ/3BUoy5aId7sCpA/Tc5lt8DkFgdVS2onTpJsZ/fl0=
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
golang.org/x/net v0.3.1-0.20221206200815-1e63c2f08a10 h1:Frnccbp+ok2GkUS2tC84yAq/U9Vg+0sIO7aRL3T4Xnc=
golang.org/x/net v0.3.1-0.20221206200815-1e63c2f08a10/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
golang.org/x/oauth2 v0.0.0-20180724155351-3d292e4d0cdc/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@ -2166,6 +2242,10 @@ golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ
golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1 h1:lxqLZaMad/dJHMFZH0NiNpiEZI/nhgWhe4wgzpE+MuA=
golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg=
@ -2296,12 +2376,16 @@ golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210426230700-d19ff857e887/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@ -2311,14 +2395,14 @@ golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ=
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.1.0 h1:g6Z6vPFA9dYBAF7DWcH6sCcOntplXsDKcliusYijMlw=
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.3.0 h1:qoo4akIqOcDME5bhc/NgxUdovd6BSS2uMsVjB56q1xI=
golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=
golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@ -2329,8 +2413,8 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.5.0 h1:OLmvp0KP+FVG99Ct/qFiL/Fhk4zp4QQnZ7b2U+5piUM=
golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@ -2340,8 +2424,8 @@ golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxb
golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20220224211638-0e9765cccd65 h1:M73Iuj3xbbb9Uk1DYhzydthsj6oOd6l9bpuFcNoUvTs=
golang.org/x/time v0.0.0-20220224211638-0e9765cccd65/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20220609170525-579cf78fd858 h1:Dpdu/EMxGMFgq0CeYMh4fazTD2vtlZRYE7wyynxJb9U=
golang.org/x/time v0.0.0-20220609170525-579cf78fd858/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@ -2415,10 +2499,12 @@ golang.org/x/tools v0.0.0-20210114065538-d78b04bdf963/go.mod h1:emZCQorbCU4vsT4f
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.2.0 h1:G6AHpWxTMGY1KyEYoAQ5WTtIekUUvDNjan3ugu60JvE=
golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@ -2457,8 +2543,15 @@ google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34q
google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU=
google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94=
google.golang.org/api v0.44.0 h1:URs6qR1lAxDsqWITsQXI4ZkGiYJ5dHtRNiCpfs2OeKA=
google.golang.org/api v0.44.0/go.mod h1:EBOGZqzyhtvMDoxwS97ctnh0zUmYY6CxqXsc1AvkYD8=
google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo=
google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4=
google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw=
google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU=
google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k=
google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE=
google.golang.org/api v0.57.0 h1:4t9zuDlHLcIx0ZEhmXEeFVCRsiOgpgn2QOH9N0MNjPI=
google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
@ -2525,9 +2618,25 @@ google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6D
google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A=
google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=
google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
google.golang.org/genproto v0.0.0-20211021150943-2b146023228c h1:FqrtZMB5Wr+/RecOM3uPJNPfWR8Upb5hAPnt7PU6i4k=
google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24=
google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k=
google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k=
google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48=
google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48=
google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w=
google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20211021150943-2b146023228c/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20220502173005-c8bf987b8c21 h1:hrbNEivu7Zn1pxvHk6MBrq9iE22woVILTHqexqBxe6I=
google.golang.org/genproto v0.0.0-20220502173005-c8bf987b8c21/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4=
google.golang.org/grpc v0.0.0-20160317175043-d3ddb4469d5a/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
@ -2558,11 +2667,18 @@ google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA5
google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=
google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=
google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
google.golang.org/grpc v1.41.0/go.mod h1:U3l9uK9J0sini8mHphKoXyaqDA/8VyGnDee1zzIUK6k=
google.golang.org/grpc v1.46.0 h1:oCjezcn6g6A75TGoKYBPgKmVBLexhYLM6MebdrPApP8=
google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
google.golang.org/grpc v1.50.1 h1:DS/BukOZWp8s6p4Dt/tOaJaTQyPyOoCcrjroHuCeLzY=
google.golang.org/grpc v1.50.1/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI=
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=
google.golang.org/grpc/examples v0.0.0-20201130180447-c456688b1860/go.mod h1:Ly7ZA/ARzg8fnPU9TyZIxoz33sEUuWX7txiqs8lPTgE=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
@ -2577,8 +2693,9 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/DataDog/dd-trace-go.v1 v1.43.1 h1:Dez4VzRQWAI5YXJRBx58BiC0gONGuW/oY4l8fWKzOXY=
gopkg.in/DataDog/dd-trace-go.v1 v1.43.1/go.mod h1:YL9g+nlUY7ByCffD5pDytAqy99GNbytRV0EBpKuldM4=
gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U=
@ -2676,10 +2793,12 @@ k8s.io/api v0.20.4/go.mod h1:++lNL1AJMkDymriNniQsWRkMDzRaX2Y/POTUi8yvqYQ=
k8s.io/api v0.20.6/go.mod h1:X9e8Qag6JV/bL5G6bU8sdVRltWKmdHsFUGS3eVndqE8=
k8s.io/api v0.21.0/go.mod h1:+YbrhBBGgsxbF6o6Kj4KJPJnBmAKuXDeS3E18bgHNVU=
k8s.io/api v0.21.3/go.mod h1:hUgeYHUbBp23Ue4qdX9tR8/ANi/g3ehylAqDn9NWVOg=
k8s.io/api v0.22.1 h1:ISu3tD/jRhYfSW8jI/Q1e+lRxkR7w9UwQEZ7FgslrwY=
k8s.io/api v0.22.1/go.mod h1:bh13rkTp3F1XEaLGykbyRD2QaTTzPm0e/BMd8ptFONY=
k8s.io/apiextensions-apiserver v0.21.3 h1:+B6biyUWpqt41kz5x6peIsljlsuwvNAp/oFax/j2/aY=
k8s.io/api v0.25.0 h1:H+Q4ma2U/ww0iGB78ijZx6DRByPz6/733jIuFpX70e0=
k8s.io/api v0.25.0/go.mod h1:ttceV1GyV1i1rnmvzT3BST08N6nGt+dudGrquzVQWPk=
k8s.io/apiextensions-apiserver v0.21.3/go.mod h1:kl6dap3Gd45+21Jnh6utCx8Z2xxLm8LGDkprcd+KbsE=
k8s.io/apiextensions-apiserver v0.25.0 h1:CJ9zlyXAbq0FIW8CD7HHyozCMBpDSiH7EdrSTCZcZFY=
k8s.io/apiextensions-apiserver v0.25.0/go.mod h1:3pAjZiN4zw7R8aZC5gR0y3/vCkGlAjCazcg1me8iB/E=
k8s.io/apimachinery v0.0.0-20180904193909-def12e63c512/go.mod h1:ccL7Eh7zubPUSh9A3USN90/OzHNSVN6zxzde07TDCL0=
k8s.io/apimachinery v0.0.0-20190806215851-162a2dabc72f/go.mod h1:+ntn62igV2hyNj7/0brOvXSMONE2KxcePkSxK7/9FFQ=
k8s.io/apimachinery v0.0.0-20191004115801-a2eda9f80ab8/go.mod h1:llRdnznGEAqC3DcNm6yEj472xaFVfLM7hnYofMb12tQ=
@ -2689,8 +2808,9 @@ k8s.io/apimachinery v0.20.4/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRp
k8s.io/apimachinery v0.20.6/go.mod h1:ejZXtW1Ra6V1O5H8xPBGz+T3+4gfkTCeExAHKU57MAc=
k8s.io/apimachinery v0.21.0/go.mod h1:jbreFvJo3ov9rj7eWT7+sYiRx+qZuCYXwWT1bcDswPY=
k8s.io/apimachinery v0.21.3/go.mod h1:H/IM+5vH9kZRNJ4l3x/fXP/5bOPJaVP/guptnZPeCFI=
k8s.io/apimachinery v0.22.1 h1:DTARnyzmdHMz7bFWFDDm22AM4pLWTQECMpRTFu2d2OM=
k8s.io/apimachinery v0.22.1/go.mod h1:O3oNtNadZdeOMxHFVxOreoznohCpy0z6mocxbZr7oJ0=
k8s.io/apimachinery v0.25.0 h1:MlP0r6+3XbkUG2itd6vp3oxbtdQLQI94fD5gCS+gnoU=
k8s.io/apimachinery v0.25.0/go.mod h1:qMx9eAk0sZQGsXGu86fab8tZdffHbwUfsvzqKn4mfB0=
k8s.io/apiserver v0.20.1/go.mod h1:ro5QHeQkgMS7ZGpvf4tSMx6bBOgPfE+f52KwvXfScaU=
k8s.io/apiserver v0.20.4/go.mod h1:Mc80thBKOyy7tbvFtB4kJv1kbdD0eIH8k8vianJcbFM=
k8s.io/apiserver v0.20.6/go.mod h1:QIJXNt6i6JB+0YQRNcS0hdRHJlMhflFmsBDeSgT1r8Q=
@ -2703,8 +2823,9 @@ k8s.io/client-go v0.20.4/go.mod h1:LiMv25ND1gLUdBeYxBIwKpkSC5IsozMMmOOeSJboP+k=
k8s.io/client-go v0.20.6/go.mod h1:nNQMnOvEUEsOzRRFIIkdmYOjAZrC8bgq0ExboWSU1I0=
k8s.io/client-go v0.21.0/go.mod h1:nNBytTF9qPFDEhoqgEPaarobC8QPae13bElIVHzIglA=
k8s.io/client-go v0.21.3/go.mod h1:+VPhCgTsaFmGILxR/7E1N0S+ryO010QBeNCv5JwRGYU=
k8s.io/client-go v0.22.1 h1:jW0ZSHi8wW260FvcXHkIa0NLxFBQszTlhiAVsU5mopw=
k8s.io/client-go v0.22.1/go.mod h1:BquC5A4UOo4qVDUtoc04/+Nxp1MeHcVc1HJm1KmG8kk=
k8s.io/client-go v0.25.0 h1:CVWIaCETLMBNiTUta3d5nzRbXvY5Hy9Dpl+VvREpu5E=
k8s.io/client-go v0.25.0/go.mod h1:lxykvypVfKilxhTklov0wz1FoaUZ8X4EwbhS6rpRfN8=
k8s.io/code-generator v0.21.3/go.mod h1:K3y0Bv9Cz2cOW2vXUrNZlFbflhuPvuadW6JdnN6gGKo=
k8s.io/code-generator v0.22.0/go.mod h1:eV77Y09IopzeXOJzndrDyCI88UBok2h6WxAlBwpxa+o=
k8s.io/component-base v0.20.1/go.mod h1:guxkoJnNoh8LNrbtiQOlyp2Y2XFCZQmrcg2n/DeYNLk=
@ -2730,23 +2851,26 @@ k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y=
k8s.io/klog/v2 v2.4.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y=
k8s.io/klog/v2 v2.8.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec=
k8s.io/klog/v2 v2.9.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec=
k8s.io/klog/v2 v2.10.0 h1:R2HDMDJsHVTHA2n4RjwbeYXdOcBymXdX/JRb1v0VGhE=
k8s.io/klog/v2 v2.10.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec=
k8s.io/klog/v2 v2.70.1 h1:7aaoSdahviPmR+XkS7FyxlkkXs6tHISSG03RxleQAVQ=
k8s.io/klog/v2 v2.70.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0=
k8s.io/kube-openapi v0.0.0-20180731170545-e3762e86a74c/go.mod h1:BXM9ceUBTj2QnfH2MK1odQs778ajze1RxcmP6S8RVVc=
k8s.io/kube-openapi v0.0.0-20190709113604-33be087ad058/go.mod h1:nfDlWeOsu3pUf4yWGL+ERqohP4YsZcBJXWMK+gkzOA4=
k8s.io/kube-openapi v0.0.0-20190816220812-743ec37842bf/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E=
k8s.io/kube-openapi v0.0.0-20201113171705-d219536bb9fd/go.mod h1:WOJ3KddDSol4tAGcJo0Tvi+dK12EcqSLqcWsryKMpfM=
k8s.io/kube-openapi v0.0.0-20210305001622-591a79e4bda7/go.mod h1:wXW5VT87nVfh/iLV8FpR2uDvrFyomxbtb1KivDbvPTE=
k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e h1:KLHHjkdQFomZy8+06csTWZ0m1343QqxZhR2LJ1OxCYM=
k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e/go.mod h1:vHXdDvt9+2spS2Rx9ql3I8tycm3H9FDfdUoIuKCefvw=
k8s.io/kube-openapi v0.0.0-20220803162953-67bda5d908f1 h1:MQ8BAZPZlWk3S9K4a9NCkIFQtZShWqoha7snGixVgEA=
k8s.io/kube-openapi v0.0.0-20220803162953-67bda5d908f1/go.mod h1:C/N6wCaBHeBHkHUesQOQy2/MZqGgMAFPqGsGQLdbZBU=
k8s.io/kubernetes v1.11.10/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk=
k8s.io/kubernetes v1.13.0/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk=
k8s.io/utils v0.0.0-20190801114015-581e00157fb1/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew=
k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
k8s.io/utils v0.0.0-20210707171843-4b05e18ac7d9/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
k8s.io/utils v0.0.0-20210722164352-7f3ee0f31471/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
k8s.io/utils v0.0.0-20210820185131-d34e5cb4466e h1:ldQh+neBabomh7+89dTpiFAB8tGdfVmuIzAHbvtl+9I=
k8s.io/utils v0.0.0-20210820185131-d34e5cb4466e/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed h1:jAne/RjBTyawwAy0utX5eqigAwz/lQhTmy+Hr/Cpue4=
k8s.io/utils v0.0.0-20220728103510-ee6ede2d64ed/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
launchpad.net/gocheck v0.0.0-20140225173054-000000000087/go.mod h1:hj7XX3B/0A+80Vse0e+BUHsHMTEhd0O4cpUHr/e/BUM=
mvdan.cc/xurls/v2 v2.1.0 h1:KaMb5GLhlcSX+e+qhbRJODnUUBvlw01jt4yrjFIHAuA=
mvdan.cc/xurls/v2 v2.1.0/go.mod h1:5GrSd9rOnKOpZaji1OZLYL/yeAAtGDlo/cFe+8K5n8E=
@ -2764,15 +2888,19 @@ sigs.k8s.io/controller-runtime v0.9.6/go.mod h1:q6PpkM5vqQubEKUKOM6qr06oXGzOBcCb
sigs.k8s.io/controller-tools v0.6.2/go.mod h1:oaeGpjXn6+ZSEIQkUe/+3I40PNiDYp9aeawbt3xTgJ8=
sigs.k8s.io/gateway-api v0.4.0 h1:07IJkTt21NetZTHtPKJk2I4XIgDN4BAlTIq1wK7V11o=
sigs.k8s.io/gateway-api v0.4.0/go.mod h1:r3eiNP+0el+NTLwaTfOrCNXy8TukC+dIM3ggc+fbNWk=
sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 h1:iXTIw73aPyC+oRdyqqvVJuloN1p0AC/kzH07hu3NE+k=
sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0=
sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI=
sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw=
sigs.k8s.io/structured-merge-diff/v4 v4.0.3/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw=
sigs.k8s.io/structured-merge-diff/v4 v4.1.0/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw=
sigs.k8s.io/structured-merge-diff/v4 v4.1.2 h1:Hr/htKFmJEbtMgS/UD0N+gtgctAqz81t3nu+sPzynno=
sigs.k8s.io/structured-merge-diff/v4 v4.1.2/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4=
sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE=
sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E=
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q=
sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=
sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo=
sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8=
sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU=
sourcegraph.com/sourcegraph/go-diff v0.5.0/go.mod h1:kuch7UrkMzY0X+p9CRK03kfuPQ2zzQcaEFbx8wA8rck=
sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0=

View file

@ -0,0 +1,60 @@
[global]
checkNewVersion = false
sendAnonymousUsage = false
[log]
level = "DEBUG"
[entryPoints.websecure]
address = ":4443"
[api]
insecure = true
[providers.file]
filename = "{{ .SelfFilename }}"
## dynamic configuration ##
[http.routers]
[http.routers.router1]
entryPoints = ["websecure"]
service = "service1"
rule = "Host(`snitest.com`)"
[http.routers.router1.tls]
options = "invalidTLSOptions"
[http.routers.router2]
entryPoints = ["websecure"]
service = "service1"
rule = "Host(`snitest.org`)"
[http.routers.router2.tls]
# fallback router
[http.routers.router3]
entryPoints = ["websecure"]
service = "service1"
rule = "Path(`/`)"
[http.routers.router3.tls]
[[http.services.service1.loadBalancer.servers]]
url = "http://127.0.0.1:9010"
[[tls.certificates]]
certFile = "fixtures/https/snitest.com.cert"
keyFile = "fixtures/https/snitest.com.key"
[[tls.certificates]]
certFile = "fixtures/https/snitest.org.cert"
keyFile = "fixtures/https/snitest.org.key"
[tls.options]
[tls.options.default.clientAuth]
# Missing caFile to have an invalid mTLS configuration.
clientAuthType = "RequireAndVerifyClientCert"
[tls.options.invalidTLSOptions.clientAuth]
# Missing caFile to have an invalid mTLS configuration.
clientAuthType = "RequireAndVerifyClientCert"

View file

@ -20,12 +20,12 @@
[http.routers]
[http.routers.router1]
rule = "HostRegexp(`{subdomain:[a-z1-9-]+}.snitest.com`)"
rule = "HostRegexp(`[a-z1-9-]+\\.snitest\\.com`)"
service = "service1"
[http.routers.router1.tls]
[http.routers.router2]
rule = "HostRegexp(`{subdomain:[a-z1-9-]+}.www.snitest.com`)"
rule = "HostRegexp(`[a-z1-9-]+\\.www\\.snitest\\.com`)"
service = "service1"
[http.routers.router2.tls]

View file

@ -39,7 +39,7 @@ spec:
entryPoints:
description: 'EntryPoints defines the list of entry point names to
bind to. Entry points have to be configured in the static configuration.
More info: https://doc.traefik.io/traefik/v2.9/routing/entrypoints/
More info: https://doc.traefik.io/traefik/v3.0/routing/entrypoints/
Default: all.'
items:
type: string
@ -56,11 +56,11 @@ spec:
- Rule
type: string
match:
description: 'Match defines the router''s rule. More info: https://doc.traefik.io/traefik/v2.9/routing/routers/#rule'
description: 'Match defines the router''s rule. More info: https://doc.traefik.io/traefik/v3.0/routing/routers/#rule'
type: string
middlewares:
description: 'Middlewares defines the list of references to
Middleware resources. More info: https://doc.traefik.io/traefik/v2.9/routing/providers/kubernetes-crd/#kind-middleware'
Middleware resources. More info: https://doc.traefik.io/traefik/v3.0/routing/providers/kubernetes-crd/#kind-middleware'
items:
description: MiddlewareRef is a reference to a Middleware
resource.
@ -79,7 +79,7 @@ spec:
type: array
priority:
description: 'Priority defines the router''s priority. More
info: https://doc.traefik.io/traefik/v2.9/routing/routers/#priority'
info: https://doc.traefik.io/traefik/v3.0/routing/routers/#priority'
type: integer
services:
description: Services defines the list of Service. It can contain
@ -145,7 +145,7 @@ spec:
type: string
sticky:
description: 'Sticky defines the sticky sessions configuration.
More info: https://doc.traefik.io/traefik/v2.9/routing/services/#sticky-sessions'
More info: https://doc.traefik.io/traefik/v3.0/routing/services/#sticky-sessions'
properties:
cookie:
description: Cookie defines the sticky cookie configuration.
@ -190,16 +190,16 @@ spec:
type: object
type: array
tls:
description: 'TLS defines the TLS configuration. More info: https://doc.traefik.io/traefik/v2.9/routing/routers/#tls'
description: 'TLS defines the TLS configuration. More info: https://doc.traefik.io/traefik/v3.0/routing/routers/#tls'
properties:
certResolver:
description: 'CertResolver defines the name of the certificate
resolver to use. Cert resolvers have to be configured in the
static configuration. More info: https://doc.traefik.io/traefik/v2.9/https/acme/#certificate-resolvers'
static configuration. More info: https://doc.traefik.io/traefik/v3.0/https/acme/#certificate-resolvers'
type: string
domains:
description: 'Domains defines the list of domains that will be
used to issue certificates. More info: https://doc.traefik.io/traefik/v2.9/routing/routers/#domains'
used to issue certificates. More info: https://doc.traefik.io/traefik/v3.0/routing/routers/#domains'
items:
description: Domain holds a domain name with SANs.
properties:
@ -217,15 +217,15 @@ spec:
options:
description: 'Options defines the reference to a TLSOption, that
specifies the parameters of the TLS connection. If not defined,
the `default` TLSOption is used. More info: https://doc.traefik.io/traefik/v2.9/https/tls/#tls-options'
the `default` TLSOption is used. More info: https://doc.traefik.io/traefik/v3.0/https/tls/#tls-options'
properties:
name:
description: 'Name defines the name of the referenced TLSOption.
More info: https://doc.traefik.io/traefik/v2.9/routing/providers/kubernetes-crd/#kind-tlsoption'
More info: https://doc.traefik.io/traefik/v3.0/routing/providers/kubernetes-crd/#kind-tlsoption'
type: string
namespace:
description: 'Namespace defines the namespace of the referenced
TLSOption. More info: https://doc.traefik.io/traefik/v2.9/routing/providers/kubernetes-crd/#kind-tlsoption'
TLSOption. More info: https://doc.traefik.io/traefik/v3.0/routing/providers/kubernetes-crd/#kind-tlsoption'
type: string
required:
- name
@ -241,11 +241,11 @@ spec:
properties:
name:
description: 'Name defines the name of the referenced TLSStore.
More info: https://doc.traefik.io/traefik/v2.9/routing/providers/kubernetes-crd/#kind-tlsstore'
More info: https://doc.traefik.io/traefik/v3.0/routing/providers/kubernetes-crd/#kind-tlsstore'
type: string
namespace:
description: 'Namespace defines the namespace of the referenced
TLSStore. More info: https://doc.traefik.io/traefik/v2.9/routing/providers/kubernetes-crd/#kind-tlsstore'
TLSStore. More info: https://doc.traefik.io/traefik/v3.0/routing/providers/kubernetes-crd/#kind-tlsstore'
type: string
required:
- name
@ -307,7 +307,7 @@ spec:
entryPoints:
description: 'EntryPoints defines the list of entry point names to
bind to. Entry points have to be configured in the static configuration.
More info: https://doc.traefik.io/traefik/v2.9/routing/entrypoints/
More info: https://doc.traefik.io/traefik/v3.0/routing/entrypoints/
Default: all.'
items:
type: string
@ -318,7 +318,7 @@ spec:
description: RouteTCP holds the TCP route configuration.
properties:
match:
description: 'Match defines the router''s rule. More info: https://doc.traefik.io/traefik/v2.9/routing/routers/#rule_1'
description: 'Match defines the router''s rule. More info: https://doc.traefik.io/traefik/v3.0/routing/routers/#rule_1'
type: string
middlewares:
description: Middlewares defines the list of references to MiddlewareTCP
@ -341,7 +341,7 @@ spec:
type: array
priority:
description: 'Priority defines the router''s priority. More
info: https://doc.traefik.io/traefik/v2.9/routing/routers/#priority_1'
info: https://doc.traefik.io/traefik/v3.0/routing/routers/#priority_1'
type: integer
services:
description: Services defines the list of TCP services.
@ -366,7 +366,7 @@ spec:
x-kubernetes-int-or-string: true
proxyProtocol:
description: 'ProxyProtocol defines the PROXY protocol
configuration. More info: https://doc.traefik.io/traefik/v2.9/routing/services/#proxy-protocol'
configuration. More info: https://doc.traefik.io/traefik/v3.0/routing/services/#proxy-protocol'
properties:
version:
description: Version defines the PROXY Protocol version
@ -397,16 +397,16 @@ spec:
type: array
tls:
description: 'TLS defines the TLS configuration on a layer 4 / TCP
Route. More info: https://doc.traefik.io/traefik/v2.9/routing/routers/#tls_1'
Route. More info: https://doc.traefik.io/traefik/v3.0/routing/routers/#tls_1'
properties:
certResolver:
description: 'CertResolver defines the name of the certificate
resolver to use. Cert resolvers have to be configured in the
static configuration. More info: https://doc.traefik.io/traefik/v2.9/https/acme/#certificate-resolvers'
static configuration. More info: https://doc.traefik.io/traefik/v3.0/https/acme/#certificate-resolvers'
type: string
domains:
description: 'Domains defines the list of domains that will be
used to issue certificates. More info: https://doc.traefik.io/traefik/v2.9/routing/routers/#domains'
used to issue certificates. More info: https://doc.traefik.io/traefik/v3.0/routing/routers/#domains'
items:
description: Domain holds a domain name with SANs.
properties:
@ -424,7 +424,7 @@ spec:
options:
description: 'Options defines the reference to a TLSOption, that
specifies the parameters of the TLS connection. If not defined,
the `default` TLSOption is used. More info: https://doc.traefik.io/traefik/v2.9/https/tls/#tls-options'
the `default` TLSOption is used. More info: https://doc.traefik.io/traefik/v3.0/https/tls/#tls-options'
properties:
name:
description: Name defines the name of the referenced Traefik
@ -518,7 +518,7 @@ spec:
entryPoints:
description: 'EntryPoints defines the list of entry point names to
bind to. Entry points have to be configured in the static configuration.
More info: https://doc.traefik.io/traefik/v2.9/routing/entrypoints/
More info: https://doc.traefik.io/traefik/v3.0/routing/entrypoints/
Default: all.'
items:
type: string
@ -597,7 +597,7 @@ spec:
schema:
openAPIV3Schema:
description: 'Middleware is the CRD implementation of a Traefik Middleware.
More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/overview/'
More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/overview/'
properties:
apiVersion:
description: 'APIVersion defines the versioned schema of this representation
@ -617,7 +617,7 @@ spec:
addPrefix:
description: 'AddPrefix holds the add prefix middleware configuration.
This middleware updates the path of a request before forwarding
it. More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/addprefix/'
it. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/addprefix/'
properties:
prefix:
description: Prefix is the string to add before the current path
@ -627,11 +627,11 @@ spec:
basicAuth:
description: 'BasicAuth holds the basic auth middleware configuration.
This middleware restricts access to your services to known users.
More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/basicauth/'
More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/basicauth/'
properties:
headerField:
description: 'HeaderField defines a header field to store the
authenticated user. More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/basicauth/#headerfield'
authenticated user. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/basicauth/#headerfield'
type: string
realm:
description: 'Realm allows the protected resources on a server
@ -651,7 +651,7 @@ spec:
buffering:
description: 'Buffering holds the buffering middleware configuration.
This middleware retries or limits the size of requests that can
be forwarded to backends. More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/buffering/#maxrequestbodybytes'
be forwarded to backends. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/buffering/#maxrequestbodybytes'
properties:
maxRequestBodyBytes:
description: 'MaxRequestBodyBytes defines the maximum allowed
@ -684,13 +684,13 @@ spec:
retryExpression:
description: 'RetryExpression defines the retry conditions. It
is a logical combination of functions with operators AND (&&)
and OR (||). More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/buffering/#retryexpression'
and OR (||). More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/buffering/#retryexpression'
type: string
type: object
chain:
description: 'Chain holds the configuration of the chain middleware.
This middleware enables to define reusable combinations of other
pieces of middleware. More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/chain/'
pieces of middleware. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/chain/'
properties:
middlewares:
description: Middlewares is the list of MiddlewareRef which composes
@ -744,7 +744,7 @@ spec:
compress:
description: 'Compress holds the compress middleware configuration.
This middleware compresses responses before sending them to the
client, using gzip compression. More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/compress/'
client, using gzip compression. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/compress/'
properties:
excludedContentTypes:
description: ExcludedContentTypes defines the list of content
@ -762,28 +762,18 @@ spec:
type: object
contentType:
description: ContentType holds the content-type middleware configuration.
This middleware exists to enable the correct behavior until at least
the default one can be changed in a future version.
properties:
autoDetect:
description: AutoDetect specifies whether to let the `Content-Type`
header, if it has not been set by the backend, be automatically
set to a value derived from the contents of the response. As
a proxy, the default behavior should be to leave the header
alone, regardless of what the backend did with it. However,
the historic default was to always auto-detect and set the header
if it was nil, and it is going to be kept that way in order
to support users currently relying on it.
type: boolean
This middleware sets the `Content-Type` header value to the media
type detected from the response content, when it is not set by the
backend.
type: object
digestAuth:
description: 'DigestAuth holds the digest auth middleware configuration.
This middleware restricts access to your services to known users.
More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/digestauth/'
More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/digestauth/'
properties:
headerField:
description: 'HeaderField defines a header field to store the
authenticated user. More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/basicauth/#headerfield'
authenticated user. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/basicauth/#headerfield'
type: string
realm:
description: 'Realm allows the protected resources on a server
@ -802,7 +792,7 @@ spec:
errors:
description: 'ErrorPage holds the custom error middleware configuration.
This middleware returns a custom page in lieu of the default, according
to configured ranges of HTTP Status codes. More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/errorpages/'
to configured ranges of HTTP Status codes. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/errorpages/'
properties:
query:
description: Query defines the URL for the error page (hosted
@ -811,7 +801,7 @@ spec:
type: string
service:
description: 'Service defines the reference to a Kubernetes Service
that will serve the error page. More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/errorpages/#service'
that will serve the error page. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/errorpages/#service'
properties:
kind:
description: Kind defines the kind of the Service.
@ -868,7 +858,7 @@ spec:
type: string
sticky:
description: 'Sticky defines the sticky sessions configuration.
More info: https://doc.traefik.io/traefik/v2.9/routing/services/#sticky-sessions'
More info: https://doc.traefik.io/traefik/v3.0/routing/services/#sticky-sessions'
properties:
cookie:
description: Cookie defines the sticky cookie configuration.
@ -917,7 +907,7 @@ spec:
forwardAuth:
description: 'ForwardAuth holds the forward auth middleware configuration.
This middleware delegates the request authentication to a Service.
More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/forwardauth/'
More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/forwardauth/'
properties:
address:
description: Address defines the authentication server address.
@ -940,7 +930,7 @@ spec:
description: 'AuthResponseHeadersRegex defines the regex to match
headers to copy from the authentication server response and
set on forwarded request, after stripping all headers that match
the regex. More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/forwardauth/#authresponseheadersregex'
the regex. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/forwardauth/#authresponseheadersregex'
type: string
tls:
description: TLS defines the configuration used to secure the
@ -980,7 +970,7 @@ spec:
headers:
description: 'Headers holds the headers middleware configuration.
This middleware manages the requests and responses headers. More
info: https://doc.traefik.io/traefik/v2.9/middlewares/http/headers/#customrequestheaders'
info: https://doc.traefik.io/traefik/v3.0/middlewares/http/headers/#customrequestheaders'
properties:
accessControlAllowCredentials:
description: AccessControlAllowCredentials defines whether the
@ -1124,7 +1114,7 @@ spec:
inFlightReq:
description: 'InFlightReq holds the in-flight request middleware configuration.
This middleware limits the number of requests being processed and
served concurrently. More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/inflightreq/'
served concurrently. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/inflightreq/'
properties:
amount:
description: Amount defines the maximum amount of allowed simultaneous
@ -1138,11 +1128,11 @@ spec:
group requests as originating from a common source. If several
strategies are defined at the same time, an error will be raised.
If none are set, the default is to use the requestHost. More
info: https://doc.traefik.io/traefik/v2.9/middlewares/http/inflightreq/#sourcecriterion'
info: https://doc.traefik.io/traefik/v3.0/middlewares/http/inflightreq/#sourcecriterion'
properties:
ipStrategy:
description: 'IPStrategy holds the IP strategy configuration
used by Traefik to determine the client IP. More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/ipallowlist/#ipstrategy'
used by Traefik to determine the client IP. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/ipallowlist/#ipstrategy'
properties:
depth:
description: Depth tells Traefik to use the X-Forwarded-For
@ -1170,11 +1160,11 @@ spec:
ipAllowList:
description: 'IPAllowList holds the IP allowlist middleware configuration.
This middleware accepts / refuses requests based on the client IP.
More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/ipallowlist/'
More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/ipallowlist/'
properties:
ipStrategy:
description: 'IPStrategy holds the IP strategy configuration used
by Traefik to determine the client IP. More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/ipallowlist/#ipstrategy'
by Traefik to determine the client IP. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/ipallowlist/#ipstrategy'
properties:
depth:
description: Depth tells Traefik to use the X-Forwarded-For
@ -1198,7 +1188,7 @@ spec:
passTLSClientCert:
description: 'PassTLSClientCert holds the pass TLS client cert middleware
configuration. This middleware adds the selected data from the passed
client TLS certificate to a header. More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/passtlsclientcert/'
client TLS certificate to a header. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/passtlsclientcert/'
properties:
info:
description: Info selects the specific client certificate details
@ -1305,7 +1295,7 @@ spec:
rateLimit:
description: 'RateLimit holds the rate limit configuration. This middleware
ensures that services will receive a fair amount of requests, and
allows one to define what fair is. More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/ratelimit/'
allows one to define what fair is. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/ratelimit/'
properties:
average:
description: Average is the maximum rate, by default in requests/s,
@ -1338,7 +1328,7 @@ spec:
properties:
ipStrategy:
description: 'IPStrategy holds the IP strategy configuration
used by Traefik to determine the client IP. More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/ipallowlist/#ipstrategy'
used by Traefik to determine the client IP. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/ipallowlist/#ipstrategy'
properties:
depth:
description: Depth tells Traefik to use the X-Forwarded-For
@ -1366,7 +1356,7 @@ spec:
redirectRegex:
description: 'RedirectRegex holds the redirect regex middleware configuration.
This middleware redirects a request using regex matching and replacement.
More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/redirectregex/#regex'
More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/redirectregex/#regex'
properties:
permanent:
description: Permanent defines whether the redirection is permanent
@ -1384,7 +1374,7 @@ spec:
redirectScheme:
description: 'RedirectScheme holds the redirect scheme middleware
configuration. This middleware redirects requests from a scheme/port
to another. More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/redirectscheme/'
to another. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/redirectscheme/'
properties:
permanent:
description: Permanent defines whether the redirection is permanent
@ -1400,7 +1390,7 @@ spec:
replacePath:
description: 'ReplacePath holds the replace path middleware configuration.
This middleware replaces the path of the request URL and store the
original path in an X-Replaced-Path header. More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/replacepath/'
original path in an X-Replaced-Path header. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/replacepath/'
properties:
path:
description: Path defines the path to use as replacement in the
@ -1410,7 +1400,7 @@ spec:
replacePathRegex:
description: 'ReplacePathRegex holds the replace path regex middleware
configuration. This middleware replaces the path of a URL using
regex matching and replacement. More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/replacepathregex/'
regex matching and replacement. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/replacepathregex/'
properties:
regex:
description: Regex defines the regular expression used to match
@ -1426,7 +1416,7 @@ spec:
middleware reissues requests a given number of times to a backend
server if that server does not reply. As soon as the server answers,
the middleware stops retrying, regardless of the response status.
More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/retry/'
More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/retry/'
properties:
attempts:
description: Attempts defines how many times the request should
@ -1446,7 +1436,7 @@ spec:
stripPrefix:
description: 'StripPrefix holds the strip prefix middleware configuration.
This middleware removes the specified prefixes from the URL path.
More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/stripprefix/'
More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/stripprefix/'
properties:
prefixes:
description: Prefixes defines the prefixes to strip from the request
@ -1458,7 +1448,7 @@ spec:
stripPrefixRegex:
description: 'StripPrefixRegex holds the strip prefix regex middleware
configuration. This middleware removes the matching prefixes from
the URL path. More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/stripprefixregex/'
the URL path. More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/stripprefixregex/'
properties:
regex:
description: Regex defines the regular expression to match the
@ -1502,7 +1492,7 @@ spec:
schema:
openAPIV3Schema:
description: 'MiddlewareTCP is the CRD implementation of a Traefik TCP middleware.
More info: https://doc.traefik.io/traefik/v2.9/middlewares/overview/'
More info: https://doc.traefik.io/traefik/v3.0/middlewares/overview/'
properties:
apiVersion:
description: 'APIVersion defines the versioned schema of this representation
@ -1576,7 +1566,7 @@ spec:
description: 'ServersTransport is the CRD implementation of a ServersTransport.
If no serversTransport is specified, the default@internal will be used.
The default@internal serversTransport is created from the static configuration.
More info: https://doc.traefik.io/traefik/v2.9/routing/services/#serverstransport_1'
More info: https://doc.traefik.io/traefik/v3.0/routing/services/#serverstransport_1'
properties:
apiVersion:
description: 'APIVersion defines the versioned schema of this representation
@ -1716,7 +1706,7 @@ spec:
openAPIV3Schema:
description: 'TLSOption is the CRD implementation of a Traefik TLS Option,
allowing to configure some parameters of the TLS connection. More info:
https://doc.traefik.io/traefik/v2.9/https/tls/#tls-options'
https://doc.traefik.io/traefik/v3.0/https/tls/#tls-options'
properties:
apiVersion:
description: 'APIVersion defines the versioned schema of this representation
@ -1736,13 +1726,13 @@ spec:
alpnProtocols:
description: 'ALPNProtocols defines the list of supported application
level protocols for the TLS handshake, in order of preference. More
info: https://doc.traefik.io/traefik/v2.9/https/tls/#alpn-protocols'
info: https://doc.traefik.io/traefik/v3.0/https/tls/#alpn-protocols'
items:
type: string
type: array
cipherSuites:
description: 'CipherSuites defines the list of supported cipher suites
for TLS versions up to TLS 1.2. More info: https://doc.traefik.io/traefik/v2.9/https/tls/#cipher-suites'
for TLS versions up to TLS 1.2. More info: https://doc.traefik.io/traefik/v3.0/https/tls/#cipher-suites'
items:
type: string
type: array
@ -1769,7 +1759,7 @@ spec:
type: object
curvePreferences:
description: 'CurvePreferences defines the preferred elliptic curves
in a specific order. More info: https://doc.traefik.io/traefik/v2.9/https/tls/#curve-preferences'
in a specific order. More info: https://doc.traefik.io/traefik/v3.0/https/tls/#curve-preferences'
items:
type: string
type: array
@ -1824,7 +1814,7 @@ spec:
description: 'TLSStore is the CRD implementation of a Traefik TLS Store. For
the time being, only the TLSStore named default is supported. This means
that you cannot have two stores that are named default in different Kubernetes
namespaces. More info: https://doc.traefik.io/traefik/v2.9/https/tls/#certificates-stores'
namespaces. More info: https://doc.traefik.io/traefik/v3.0/https/tls/#certificates-stores'
properties:
apiVersion:
description: 'APIVersion defines the versioned schema of this representation
@ -1922,7 +1912,7 @@ spec:
openAPIV3Schema:
description: 'TraefikService is the CRD implementation of a Traefik Service.
TraefikService object allows to: - Apply weight to Services on load-balancing
- Mirror traffic on services More info: https://doc.traefik.io/traefik/v2.9/routing/providers/kubernetes-crd/#kind-traefikservice'
- Mirror traffic on services More info: https://doc.traefik.io/traefik/v3.0/routing/providers/kubernetes-crd/#kind-traefikservice'
properties:
apiVersion:
description: 'APIVersion defines the versioned schema of this representation
@ -2021,7 +2011,7 @@ spec:
type: string
sticky:
description: 'Sticky defines the sticky sessions configuration.
More info: https://doc.traefik.io/traefik/v2.9/routing/services/#sticky-sessions'
More info: https://doc.traefik.io/traefik/v3.0/routing/services/#sticky-sessions'
properties:
cookie:
description: Cookie defines the sticky cookie configuration.
@ -2105,7 +2095,7 @@ spec:
type: string
sticky:
description: 'Sticky defines the sticky sessions configuration.
More info: https://doc.traefik.io/traefik/v2.9/routing/services/#sticky-sessions'
More info: https://doc.traefik.io/traefik/v3.0/routing/services/#sticky-sessions'
properties:
cookie:
description: Cookie defines the sticky cookie configuration.
@ -2205,7 +2195,7 @@ spec:
type: string
sticky:
description: 'Sticky defines the sticky sessions configuration.
More info: https://doc.traefik.io/traefik/v2.9/routing/services/#sticky-sessions'
More info: https://doc.traefik.io/traefik/v3.0/routing/services/#sticky-sessions'
properties:
cookie:
description: Cookie defines the sticky cookie configuration.
@ -2244,7 +2234,7 @@ spec:
type: array
sticky:
description: 'Sticky defines whether sticky sessions are enabled.
More info: https://doc.traefik.io/traefik/v2.9/routing/providers/kubernetes-crd/#stickiness-and-load-balancing'
More info: https://doc.traefik.io/traefik/v3.0/routing/providers/kubernetes-crd/#stickiness-and-load-balancing'
properties:
cookie:
description: Cookie defines the sticky cookie configuration.

View file

@ -21,32 +21,12 @@
[http.routers]
[http.routers.router1]
service = "service1"
rule = "PathPrefix(`/css/ct/nomiddleware`) || PathPrefix(`/pdf/ct/nomiddleware`)"
rule = "PathPrefix(`/`)"
[http.routers.router2]
service = "service1"
middlewares = ["autodetect"]
rule = "PathPrefix(`/css/ct/middlewareauto`) || PathPrefix(`/pdf/ct/middlewareauto`)"
[http.routers.router3]
service = "service1"
middlewares = ["noautodetect"]
rule = "PathPrefix(`/css/ct/middlewarenoauto`) || PathPrefix(`/pdf/ct/middlewarenoauto`)"
[http.routers.router4]
service = "service1"
rule = "PathPrefix(`/css/noct/nomiddleware`) || PathPrefix(`/pdf/noct/nomiddleware`)"
[http.routers.router5]
service = "service1"
middlewares = ["autodetect"]
rule = "PathPrefix(`/css/noct/middlewareauto`) || PathPrefix(`/pdf/noct/middlewareauto`)"
[http.routers.router6]
service = "service1"
middlewares = ["noautodetect"]
rule = "PathPrefix(`/css/noct/middlewarenoauto`) || PathPrefix(`/pdf/noct/middlewarenoauto`)"
rule = "PathPrefix(`/autodetect`)"
[http.services]
[http.services.service1]
@ -56,7 +36,3 @@
url = "{{ .Server }}"
[http.middlewares.autodetect.contentType]
autoDetect=true
[http.middlewares.noautodetect.contentType]
autoDetect=false

View file

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

View file

@ -4,6 +4,7 @@
[log]
level = "DEBUG"
noColor = true
[entryPoints]
[entryPoints.webHost]
@ -30,12 +31,12 @@
[http.routers.router2]
entryPoints = ["webHostRegexp"]
service = "service1"
rule = "!HostRegexp(`test.localhost`)"
rule = "!HostRegexp(`test\\.localhost`)"
[http.routers.router3]
entryPoints = ["webQuery"]
service = "service1"
rule = "!Query(`foo=`)"
rule = "!QueryRegexp(`foo`, `.*`)"
[http.services]

View file

@ -34,6 +34,13 @@
[tcp.routers.to-whoami-sni-strict.tls]
options = "bar"
[tcp.routers.to-whoami-invalid-tls]
rule = "HostSNI(`whoami-i.test`)"
service = "whoami-no-cert"
entryPoints = [ "tcp" ]
[tcp.routers.to-whoami-invalid-tls.tls]
options = "invalid"
[tcp.services.whoami-no-cert]
[tcp.services.whoami-no-cert.loadBalancer]
[[tcp.services.whoami-no-cert.loadBalancer.servers]]
@ -46,3 +53,7 @@
[tls.options.bar]
minVersion = "VersionTLS13"
[tls.options.invalid.clientAuth]
# Missing CA files to have an invalid mTLS configuration.
clientAuthType = "RequireAndVerifyClientCert"

View file

@ -24,7 +24,7 @@
[http.routers]
[http.routers.router1]
service = "service1"
rule = "Path(`/echo`,`/ws`)"
rule = "Path(`/echo`) || Path(`/ws`)"
[http.routers.router1.tls]
[http.services]

View file

@ -1092,7 +1092,7 @@ func (s *HTTPSSuite) TestWithSNIDynamicCaseInsensitive(c *check.C) {
defer s.killCmd(cmd)
// wait for Traefik
err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 500*time.Millisecond, try.BodyContains("HostRegexp(`{subdomain:[a-z1-9-]+}.www.snitest.com`)"))
err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 500*time.Millisecond, try.BodyContains("HostRegexp(`[a-z1-9-]+\\\\.www\\\\.snitest\\\\.com`)"))
c.Assert(err, checker.IsNil)
tlsConfig := &tls.Config{
@ -1226,3 +1226,53 @@ func (s *HTTPSSuite) TestWithDomainFronting(c *check.C) {
c.Assert(err, checker.IsNil)
}
}
// TestWithInvalidTLSOption verifies the behavior when using an invalid tlsOption configuration.
func (s *HTTPSSuite) TestWithInvalidTLSOption(c *check.C) {
backend := startTestServer("9010", http.StatusOK, "server1")
defer backend.Close()
file := s.adaptFile(c, "fixtures/https/https_invalid_tls_options.toml", struct{}{})
defer os.Remove(file)
cmd, display := s.traefikCmd(withConfigFile(file))
defer display(c)
err := cmd.Start()
c.Assert(err, checker.IsNil)
defer s.killCmd(cmd)
// wait for Traefik
err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 500*time.Millisecond, try.BodyContains("Host(`snitest.com`)"))
c.Assert(err, checker.IsNil)
testCases := []struct {
desc string
serverName string
}{
{
desc: "With invalid TLS Options specified",
serverName: "snitest.com",
},
{
desc: "With invalid Default TLS Options",
serverName: "snitest.org",
},
{
desc: "With TLS Options without servername (fallback to default)",
},
}
for _, test := range testCases {
test := test
tlsConfig := &tls.Config{
InsecureSkipVerify: true,
}
if test.serverName != "" {
tlsConfig.ServerName = test.serverName
}
conn, err := tls.Dial("tcp", "127.0.0.1:4443", tlsConfig)
c.Assert(err, checker.NotNil, check.Commentf("connected to server successfully"))
c.Assert(conn, checker.IsNil)
}
}

View file

@ -11,6 +11,7 @@ import (
"net/http"
"net/http/httptest"
"os"
"regexp"
"strings"
"sync/atomic"
"syscall"
@ -1166,9 +1167,10 @@ func (s *SimpleSuite) TestSecureAPI(c *check.C) {
func (s *SimpleSuite) TestContentTypeDisableAutoDetect(c *check.C) {
srv1 := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
rw.Header()["Content-Type"] = nil
switch req.URL.Path[:4] {
path := strings.TrimPrefix(req.URL.Path, "/autodetect")
switch path[:4] {
case "/css":
if !strings.Contains(req.URL.Path, "noct") {
if strings.Contains(req.URL.Path, "/ct") {
rw.Header().Set("Content-Type", "text/css")
}
@ -1177,7 +1179,7 @@ func (s *SimpleSuite) TestContentTypeDisableAutoDetect(c *check.C) {
_, err := rw.Write([]byte(".testcss { }"))
c.Assert(err, checker.IsNil)
case "/pdf":
if !strings.Contains(req.URL.Path, "noct") {
if strings.Contains(req.URL.Path, "/ct") {
rw.Header().Set("Content-Type", "application/pdf")
}
@ -1211,37 +1213,13 @@ func (s *SimpleSuite) TestContentTypeDisableAutoDetect(c *check.C) {
err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 10*time.Second, try.BodyContains("127.0.0.1"))
c.Assert(err, checker.IsNil)
err = try.GetRequest("http://127.0.0.1:8000/css/ct/nomiddleware", time.Second, try.HasHeaderValue("Content-Type", "text/css", false))
err = try.GetRequest("http://127.0.0.1:8000/css/ct", time.Second, try.HasHeaderValue("Content-Type", "text/css", false))
c.Assert(err, checker.IsNil)
err = try.GetRequest("http://127.0.0.1:8000/pdf/ct/nomiddleware", time.Second, try.HasHeaderValue("Content-Type", "application/pdf", false))
err = try.GetRequest("http://127.0.0.1:8000/pdf/ct", time.Second, try.HasHeaderValue("Content-Type", "application/pdf", false))
c.Assert(err, checker.IsNil)
err = try.GetRequest("http://127.0.0.1:8000/css/ct/middlewareauto", time.Second, try.HasHeaderValue("Content-Type", "text/css", false))
c.Assert(err, checker.IsNil)
err = try.GetRequest("http://127.0.0.1:8000/pdf/ct/nomiddlewareauto", time.Second, try.HasHeaderValue("Content-Type", "application/pdf", false))
c.Assert(err, checker.IsNil)
err = try.GetRequest("http://127.0.0.1:8000/css/ct/middlewarenoauto", time.Second, try.HasHeaderValue("Content-Type", "text/css", false))
c.Assert(err, checker.IsNil)
err = try.GetRequest("http://127.0.0.1:8000/pdf/ct/nomiddlewarenoauto", time.Second, try.HasHeaderValue("Content-Type", "application/pdf", false))
c.Assert(err, checker.IsNil)
err = try.GetRequest("http://127.0.0.1:8000/css/noct/nomiddleware", time.Second, try.HasHeaderValue("Content-Type", "text/plain; charset=utf-8", false))
c.Assert(err, checker.IsNil)
err = try.GetRequest("http://127.0.0.1:8000/pdf/noct/nomiddleware", time.Second, try.HasHeaderValue("Content-Type", "application/pdf", false))
c.Assert(err, checker.IsNil)
err = try.GetRequest("http://127.0.0.1:8000/css/noct/middlewareauto", time.Second, try.HasHeaderValue("Content-Type", "text/plain; charset=utf-8", false))
c.Assert(err, checker.IsNil)
err = try.GetRequest("http://127.0.0.1:8000/pdf/noct/nomiddlewareauto", time.Second, try.HasHeaderValue("Content-Type", "application/pdf", false))
c.Assert(err, checker.IsNil)
err = try.GetRequest("http://127.0.0.1:8000/css/noct/middlewarenoauto", time.Second, func(res *http.Response) error {
err = try.GetRequest("http://127.0.0.1:8000/css/noct", time.Second, func(res *http.Response) error {
if ct, ok := res.Header["Content-Type"]; ok {
return fmt.Errorf("should have no content type and %s is present", ct)
}
@ -1249,13 +1227,25 @@ func (s *SimpleSuite) TestContentTypeDisableAutoDetect(c *check.C) {
})
c.Assert(err, checker.IsNil)
err = try.GetRequest("http://127.0.0.1:8000/pdf/noct/middlewarenoauto", time.Second, func(res *http.Response) error {
err = try.GetRequest("http://127.0.0.1:8000/pdf/noct", time.Second, func(res *http.Response) error {
if ct, ok := res.Header["Content-Type"]; ok {
return fmt.Errorf("should have no content type and %s is present", ct)
}
return nil
})
c.Assert(err, checker.IsNil)
err = try.GetRequest("http://127.0.0.1:8000/autodetect/css/ct", time.Second, try.HasHeaderValue("Content-Type", "text/css", false))
c.Assert(err, checker.IsNil)
err = try.GetRequest("http://127.0.0.1:8000/autodetect/pdf/ct", time.Second, try.HasHeaderValue("Content-Type", "application/pdf", false))
c.Assert(err, checker.IsNil)
err = try.GetRequest("http://127.0.0.1:8000/autodetect/css/noct", time.Second, try.HasHeaderValue("Content-Type", "text/plain; charset=utf-8", false))
c.Assert(err, checker.IsNil)
err = try.GetRequest("http://127.0.0.1:8000/autodetect/pdf/noct", time.Second, try.HasHeaderValue("Content-Type", "application/pdf", false))
c.Assert(err, checker.IsNil)
}
func (s *SimpleSuite) TestMuxer(c *check.C) {
@ -1337,7 +1327,7 @@ func (s *SimpleSuite) TestMuxer(c *check.C) {
expected: http.StatusOK,
},
{
desc: "!Query with semicolon, no match",
desc: "!Query with semicolon and empty query param value, no match",
request: "GET /?foo=; HTTP/1.1\r\nHost: other.localhost\r\n\r\n",
target: "127.0.0.1:8002",
expected: http.StatusNotFound,
@ -1367,9 +1357,7 @@ func (s *SimpleSuite) TestMuxer(c *check.C) {
resp, err := http.ReadResponse(bufio.NewReader(conn), nil)
c.Assert(err, checker.IsNil)
if resp.StatusCode != test.expected {
c.Errorf("%s failed with %d instead of %d", test.desc, resp.StatusCode, test.expected)
}
c.Assert(resp.StatusCode, checker.Equals, test.expected, check.Commentf(test.desc))
if test.body != "" {
body, err := io.ReadAll(resp.Body)
@ -1378,3 +1366,36 @@ func (s *SimpleSuite) TestMuxer(c *check.C) {
}
}
}
func (s *SimpleSuite) TestDebugLog(c *check.C) {
s.createComposeProject(c, "base")
s.composeUp(c)
defer s.composeDown(c)
file := s.adaptFile(c, "fixtures/simple_debug_log.toml", struct{}{})
defer os.Remove(file)
cmd, output := s.cmdTraefik(withConfigFile(file))
err := cmd.Start()
c.Assert(err, checker.IsNil)
defer s.killCmd(cmd)
err = try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("PathPrefix(`/whoami`)"))
c.Assert(err, checker.IsNil)
req, err := http.NewRequest(http.MethodGet, "http://localhost:8000/whoami", http.NoBody)
c.Assert(err, checker.IsNil)
req.Header.Set("Autorization", "Bearer ThisIsABearerToken")
response, err := http.DefaultClient.Do(req)
c.Assert(err, checker.IsNil)
c.Assert(response.StatusCode, checker.Equals, http.StatusOK)
if regexp.MustCompile("ThisIsABearerToken").MatchReader(output) {
c.Logf("Traefik Logs: %s", output.String())
c.Log("Found Authorization Header in Traefik DEBUG logs")
c.Fail()
}
}

View file

@ -116,6 +116,14 @@ func (s *TCPSuite) TestTLSOptions(c *check.C) {
_, err = guessWhoTLSMaxVersion("127.0.0.1:8093", "whoami-d.test", true, tls.VersionTLS12)
c.Assert(err, checker.NotNil)
c.Assert(err.Error(), checker.Contains, "protocol version not supported")
// Check that we can't reach a route with an invalid mTLS configuration.
conn, err := tls.Dial("tcp", "127.0.0.1:8093", &tls.Config{
ServerName: "whoami-i.test",
InsecureSkipVerify: true,
})
c.Assert(conn, checker.IsNil)
c.Assert(err, checker.NotNil)
}
func (s *TCPSuite) TestNonTLSFallback(c *check.C) {

View file

@ -33,7 +33,7 @@ type Middleware struct {
Compress *Compress `json:"compress,omitempty" toml:"compress,omitempty" yaml:"compress,omitempty" label:"allowEmpty" file:"allowEmpty" kv:"allowEmpty" export:"true"`
PassTLSClientCert *PassTLSClientCert `json:"passTLSClientCert,omitempty" toml:"passTLSClientCert,omitempty" yaml:"passTLSClientCert,omitempty" export:"true"`
Retry *Retry `json:"retry,omitempty" toml:"retry,omitempty" yaml:"retry,omitempty" export:"true"`
ContentType *ContentType `json:"contentType,omitempty" toml:"contentType,omitempty" yaml:"contentType,omitempty" export:"true"`
ContentType *ContentType `json:"contentType,omitempty" toml:"contentType,omitempty" yaml:"contentType,omitempty" label:"allowEmpty" file:"allowEmpty" kv:"allowEmpty" export:"true"`
GrpcWeb *GrpcWeb `json:"grpcWeb,omitempty" toml:"grpcWeb,omitempty" yaml:"grpcWeb,omitempty" export:"true"`
Plugin map[string]PluginConf `json:"plugin,omitempty" toml:"plugin,omitempty" yaml:"plugin,omitempty" export:"true"`
@ -52,21 +52,15 @@ type GrpcWeb struct {
// +k8s:deepcopy-gen=true
// ContentType holds the content-type middleware configuration.
// This middleware exists to enable the correct behavior until at least the default one can be changed in a future version.
type ContentType struct {
// AutoDetect specifies whether to let the `Content-Type` header, if it has not been set by the backend,
// be automatically set to a value derived from the contents of the response.
// As a proxy, the default behavior should be to leave the header alone, regardless of what the backend did with it.
// However, the historic default was to always auto-detect and set the header if it was nil,
// and it is going to be kept that way in order to support users currently relying on it.
AutoDetect bool `json:"autoDetect,omitempty" toml:"autoDetect,omitempty" yaml:"autoDetect,omitempty" export:"true"`
}
// This middleware sets the `Content-Type` header value to the media type detected from the response content,
// when it is not set by the backend.
type ContentType struct{}
// +k8s:deepcopy-gen=true
// AddPrefix holds the add prefix middleware configuration.
// This middleware updates the path of a request before forwarding it.
// More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/addprefix/
// More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/addprefix/
type AddPrefix struct {
// Prefix is the string to add before the current path in the requested URL.
// It should include a leading slash (/).
@ -77,7 +71,7 @@ type AddPrefix struct {
// BasicAuth holds the basic auth middleware configuration.
// This middleware restricts access to your services to known users.
// More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/basicauth/
// More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/basicauth/
type BasicAuth struct {
// Users is an array of authorized users.
// Each user must be declared using the name:hashed-password format.
@ -92,7 +86,7 @@ type BasicAuth struct {
// Default: false.
RemoveHeader bool `json:"removeHeader,omitempty" toml:"removeHeader,omitempty" yaml:"removeHeader,omitempty" export:"true"`
// HeaderField defines a header field to store the authenticated user.
// More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/basicauth/#headerfield
// More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/basicauth/#headerfield
HeaderField string `json:"headerField,omitempty" toml:"headerField,omitempty" yaml:"headerField,omitempty" export:"true"`
}
@ -100,7 +94,7 @@ type BasicAuth struct {
// Buffering holds the buffering middleware configuration.
// This middleware retries or limits the size of requests that can be forwarded to backends.
// More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/buffering/#maxrequestbodybytes
// More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/buffering/#maxrequestbodybytes
type Buffering struct {
// MaxRequestBodyBytes defines the maximum allowed body size for the request (in bytes).
// If the request exceeds the allowed size, it is not forwarded to the service, and the client gets a 413 (Request Entity Too Large) response.
@ -118,7 +112,7 @@ type Buffering struct {
MemResponseBodyBytes int64 `json:"memResponseBodyBytes,omitempty" toml:"memResponseBodyBytes,omitempty" yaml:"memResponseBodyBytes,omitempty" export:"true"`
// RetryExpression defines the retry conditions.
// It is a logical combination of functions with operators AND (&&) and OR (||).
// More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/buffering/#retryexpression
// More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/buffering/#retryexpression
RetryExpression string `json:"retryExpression,omitempty" toml:"retryExpression,omitempty" yaml:"retryExpression,omitempty" export:"true"`
}
@ -135,7 +129,7 @@ type Chain struct {
// CircuitBreaker holds the circuit breaker middleware configuration.
// This middleware protects the system from stacking requests to unhealthy services, resulting in cascading failures.
// More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/circuitbreaker/
// More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/circuitbreaker/
type CircuitBreaker struct {
// Expression defines the expression that, once matched, opens the circuit breaker and applies the fallback mechanism instead of calling the services.
Expression string `json:"expression,omitempty" toml:"expression,omitempty" yaml:"expression,omitempty" export:"true"`
@ -158,7 +152,7 @@ func (c *CircuitBreaker) SetDefaults() {
// Compress holds the compress middleware configuration.
// This middleware compresses responses before sending them to the client, using gzip compression.
// More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/compress/
// More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/compress/
type Compress struct {
// ExcludedContentTypes defines the list of content types to compare the Content-Type header of the incoming requests and responses before compressing.
// `application/grpc` is always excluded.
@ -172,7 +166,7 @@ type Compress struct {
// DigestAuth holds the digest auth middleware configuration.
// This middleware restricts access to your services to known users.
// More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/digestauth/
// More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/digestauth/
type DigestAuth struct {
// Users defines the authorized users.
// Each user should be declared using the name:realm:encoded-password format.
@ -185,7 +179,7 @@ type DigestAuth struct {
// Default: traefik.
Realm string `json:"realm,omitempty" toml:"realm,omitempty" yaml:"realm,omitempty"`
// HeaderField defines a header field to store the authenticated user.
// More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/basicauth/#headerfield
// More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/basicauth/#headerfield
HeaderField string `json:"headerField,omitempty" toml:"headerField,omitempty" yaml:"headerField,omitempty" export:"true"`
}
@ -211,7 +205,7 @@ type ErrorPage struct {
// ForwardAuth holds the forward auth middleware configuration.
// This middleware delegates the request authentication to a Service.
// More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/forwardauth/
// More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/forwardauth/
type ForwardAuth struct {
// Address defines the authentication server address.
Address string `json:"address,omitempty" toml:"address,omitempty" yaml:"address,omitempty"`
@ -222,7 +216,7 @@ type ForwardAuth struct {
// AuthResponseHeaders defines the list of headers to copy from the authentication server response and set on forwarded request, replacing any existing conflicting headers.
AuthResponseHeaders []string `json:"authResponseHeaders,omitempty" toml:"authResponseHeaders,omitempty" yaml:"authResponseHeaders,omitempty" export:"true"`
// AuthResponseHeadersRegex defines the regex to match headers to copy from the authentication server response and set on forwarded request, after stripping all headers that match the regex.
// More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/forwardauth/#authresponseheadersregex
// More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/forwardauth/#authresponseheadersregex
AuthResponseHeadersRegex string `json:"authResponseHeadersRegex,omitempty" toml:"authResponseHeadersRegex,omitempty" yaml:"authResponseHeadersRegex,omitempty" export:"true"`
// AuthRequestHeaders defines the list of the headers to copy from the request to the authentication server.
// If not set or empty then all request headers are passed.
@ -233,7 +227,7 @@ type ForwardAuth struct {
// Headers holds the headers middleware configuration.
// This middleware manages the requests and responses headers.
// More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/headers/#customrequestheaders
// More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/headers/#customrequestheaders
type Headers struct {
// CustomRequestHeaders defines the header names and values to apply to the request.
CustomRequestHeaders map[string]string `json:"customRequestHeaders,omitempty" toml:"customRequestHeaders,omitempty" yaml:"customRequestHeaders,omitempty" export:"true"`
@ -343,7 +337,7 @@ func (h *Headers) HasSecureHeadersDefined() bool {
// +k8s:deepcopy-gen=true
// IPStrategy holds the IP strategy configuration used by Traefik to determine the client IP.
// More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/ipallowlist/#ipstrategy
// More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/ipallowlist/#ipstrategy
type IPStrategy struct {
// Depth tells Traefik to use the X-Forwarded-For header and take the IP located at the depth position (starting from the right).
Depth int `json:"depth,omitempty" toml:"depth,omitempty" yaml:"depth,omitempty" export:"true"`
@ -384,7 +378,7 @@ func (s *IPStrategy) Get() (ip.Strategy, error) {
// IPAllowList holds the IP allowlist middleware configuration.
// This middleware accepts / refuses requests based on the client IP.
// More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/ipallowlist/
// More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/ipallowlist/
type IPAllowList struct {
// SourceRange defines the set of allowed IPs (or ranges of allowed IPs by using CIDR notation).
SourceRange []string `json:"sourceRange,omitempty" toml:"sourceRange,omitempty" yaml:"sourceRange,omitempty"`
@ -395,7 +389,7 @@ type IPAllowList struct {
// InFlightReq holds the in-flight request middleware configuration.
// This middleware limits the number of requests being processed and served concurrently.
// More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/inflightreq/
// More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/inflightreq/
type InFlightReq struct {
// Amount defines the maximum amount of allowed simultaneous in-flight request.
// The middleware responds with HTTP 429 Too Many Requests if there are already amount requests in progress (based on the same sourceCriterion strategy).
@ -403,7 +397,7 @@ type InFlightReq struct {
// SourceCriterion defines what criterion is used to group requests as originating from a common source.
// If several strategies are defined at the same time, an error will be raised.
// If none are set, the default is to use the requestHost.
// More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/inflightreq/#sourcecriterion
// More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/inflightreq/#sourcecriterion
SourceCriterion *SourceCriterion `json:"sourceCriterion,omitempty" toml:"sourceCriterion,omitempty" yaml:"sourceCriterion,omitempty" export:"true"`
}
@ -411,7 +405,7 @@ type InFlightReq struct {
// PassTLSClientCert holds the pass TLS client cert middleware configuration.
// This middleware adds the selected data from the passed client TLS certificate to a header.
// More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/passtlsclientcert/
// More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/passtlsclientcert/
type PassTLSClientCert struct {
// PEM sets the X-Forwarded-Tls-Client-Cert header with the certificate.
PEM bool `json:"pem,omitempty" toml:"pem,omitempty" yaml:"pem,omitempty" export:"true"`
@ -467,7 +461,7 @@ func (r *RateLimit) SetDefaults() {
// RedirectRegex holds the redirect regex middleware configuration.
// This middleware redirects a request using regex matching and replacement.
// More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/redirectregex/#regex
// More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/redirectregex/#regex
type RedirectRegex struct {
// Regex defines the regex used to match and capture elements from the request URL.
Regex string `json:"regex,omitempty" toml:"regex,omitempty" yaml:"regex,omitempty"`
@ -481,7 +475,7 @@ type RedirectRegex struct {
// RedirectScheme holds the redirect scheme middleware configuration.
// This middleware redirects requests from a scheme/port to another.
// More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/redirectscheme/
// More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/redirectscheme/
type RedirectScheme struct {
// Scheme defines the scheme of the new URL.
Scheme string `json:"scheme,omitempty" toml:"scheme,omitempty" yaml:"scheme,omitempty" export:"true"`
@ -495,7 +489,7 @@ type RedirectScheme struct {
// ReplacePath holds the replace path middleware configuration.
// This middleware replaces the path of the request URL and store the original path in an X-Replaced-Path header.
// More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/replacepath/
// More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/replacepath/
type ReplacePath struct {
// Path defines the path to use as replacement in the request URL.
Path string `json:"path,omitempty" toml:"path,omitempty" yaml:"path,omitempty" export:"true"`
@ -505,7 +499,7 @@ type ReplacePath struct {
// ReplacePathRegex holds the replace path regex middleware configuration.
// This middleware replaces the path of a URL using regex matching and replacement.
// More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/replacepathregex/
// More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/replacepathregex/
type ReplacePathRegex struct {
// Regex defines the regular expression used to match and capture the path from the request URL.
Regex string `json:"regex,omitempty" toml:"regex,omitempty" yaml:"regex,omitempty" export:"true"`
@ -518,7 +512,7 @@ type ReplacePathRegex struct {
// Retry holds the retry middleware configuration.
// This middleware reissues requests a given number of times to a backend server if that server does not reply.
// As soon as the server answers, the middleware stops retrying, regardless of the response status.
// More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/retry/
// More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/retry/
type Retry struct {
// Attempts defines how many times the request should be retried.
Attempts int `json:"attempts,omitempty" toml:"attempts,omitempty" yaml:"attempts,omitempty" export:"true"`
@ -534,7 +528,7 @@ type Retry struct {
// StripPrefix holds the strip prefix middleware configuration.
// This middleware removes the specified prefixes from the URL path.
// More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/stripprefix/
// More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/stripprefix/
type StripPrefix struct {
// Prefixes defines the prefixes to strip from the request URL.
Prefixes []string `json:"prefixes,omitempty" toml:"prefixes,omitempty" yaml:"prefixes,omitempty" export:"true"`
@ -544,7 +538,7 @@ type StripPrefix struct {
// StripPrefixRegex holds the strip prefix regex middleware configuration.
// This middleware removes the matching prefixes from the URL path.
// More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/stripprefixregex/
// More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/stripprefixregex/
type StripPrefixRegex struct {
// Regex defines the regular expression to match the path prefix from the request URL.
Regex []string `json:"regex,omitempty" toml:"regex,omitempty" yaml:"regex,omitempty" export:"true"`

View file

@ -114,7 +114,7 @@ type TCPServer struct {
// +k8s:deepcopy-gen=true
// ProxyProtocol holds the PROXY Protocol configuration.
// More info: https://doc.traefik.io/traefik/v2.9/routing/services/#proxy-protocol
// More info: https://doc.traefik.io/traefik/v3.0/routing/services/#proxy-protocol
type ProxyProtocol struct {
// Version defines the PROXY Protocol version to use.
Version int `json:"version,omitempty" toml:"version,omitempty" yaml:"version,omitempty" export:"true"`

View file

@ -13,7 +13,7 @@ type TCPMiddleware struct {
// TCPInFlightConn holds the TCP InFlightConn middleware configuration.
// This middleware prevents services from being overwhelmed with high load,
// by limiting the number of allowed simultaneous connections for one IP.
// More info: https://doc.traefik.io/traefik/v2.9/middlewares/tcp/inflightconn/
// More info: https://doc.traefik.io/traefik/v3.0/middlewares/tcp/inflightconn/
type TCPInFlightConn struct {
// Amount defines the maximum amount of allowed simultaneous connections.
// The middleware closes the connection if there are already amount connections opened.

View file

@ -8,6 +8,5 @@ type Experimental struct {
LocalPlugins map[string]plugins.LocalDescriptor `description:"Local plugins configuration." json:"localPlugins,omitempty" toml:"localPlugins,omitempty" yaml:"localPlugins,omitempty" export:"true"`
KubernetesGateway bool `description:"Allow the Kubernetes gateway api provider usage." json:"kubernetesGateway,omitempty" toml:"kubernetesGateway,omitempty" yaml:"kubernetesGateway,omitempty" export:"true"`
HTTP3 bool `description:"Enable HTTP3." json:"http3,omitempty" toml:"http3,omitempty" yaml:"http3,omitempty" export:"true"`
Hub bool `description:"Enable the Traefik Hub provider." json:"hub,omitempty" toml:"hub,omitempty" yaml:"hub,omitempty" export:"true"`
}

View file

@ -35,6 +35,7 @@ import (
"github.com/traefik/traefik/v2/pkg/tracing/haystack"
"github.com/traefik/traefik/v2/pkg/tracing/instana"
"github.com/traefik/traefik/v2/pkg/tracing/jaeger"
"github.com/traefik/traefik/v2/pkg/tracing/opentelemetry"
"github.com/traefik/traefik/v2/pkg/tracing/zipkin"
"github.com/traefik/traefik/v2/pkg/types"
)
@ -177,6 +178,7 @@ type Tracing struct {
Instana *instana.Config `description:"Settings for Instana." json:"instana,omitempty" toml:"instana,omitempty" yaml:"instana,omitempty" export:"true" label:"allowEmpty" file:"allowEmpty"`
Haystack *haystack.Config `description:"Settings for Haystack." json:"haystack,omitempty" toml:"haystack,omitempty" yaml:"haystack,omitempty" export:"true" label:"allowEmpty" file:"allowEmpty"`
Elastic *elastic.Config `description:"Settings for Elastic." json:"elastic,omitempty" toml:"elastic,omitempty" yaml:"elastic,omitempty" export:"true" label:"allowEmpty" file:"allowEmpty"`
OpenTelemetry *opentelemetry.Config `description:"Settings for OpenTelemetry." json:"openTelemetry,omitempty" toml:"openTelemetry,omitempty" yaml:"openTelemetry,omitempty" export:"true" label:"allowEmpty" file:"allowEmpty"`
}
// SetDefaults sets the default values.
@ -266,16 +268,6 @@ func (c *Configuration) SetEffectiveConfiguration() {
c.Providers.KubernetesGateway = nil
}
if c.Experimental == nil || !c.Experimental.HTTP3 {
for epName, ep := range c.EntryPoints {
if ep.HTTP3 != nil {
ep.HTTP3 = nil
log.Debug().Str(logs.EntryPointName, epName).
Msgf("Disabling HTTP3 configuration for entryPoint %q: HTTP3 is disabled in the experimental configuration section", epName)
}
}
}
// Configure Gateway API provider
if c.Providers.KubernetesGateway != nil {
log.Debug().Msg("Experimental Kubernetes Gateway provider has been activated")

View file

@ -0,0 +1,422 @@
package metrics
import (
"context"
"fmt"
"net"
"strings"
"sync"
"time"
"github.com/go-kit/kit/metrics"
"github.com/rs/zerolog/log"
"github.com/traefik/traefik/v2/pkg/types"
"github.com/traefik/traefik/v2/pkg/version"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc"
"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp"
"go.opentelemetry.io/otel/metric"
"go.opentelemetry.io/otel/metric/global"
"go.opentelemetry.io/otel/metric/instrument"
"go.opentelemetry.io/otel/metric/instrument/asyncfloat64"
"go.opentelemetry.io/otel/metric/instrument/syncfloat64"
"go.opentelemetry.io/otel/metric/unit"
sdkmetric "go.opentelemetry.io/otel/sdk/metric"
"go.opentelemetry.io/otel/sdk/metric/aggregation"
"go.opentelemetry.io/otel/sdk/metric/view"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/encoding/gzip"
)
var (
openTelemetryMeterProvider *sdkmetric.MeterProvider
openTelemetryGaugeCollector *gaugeCollector
)
// RegisterOpenTelemetry registers all OpenTelemetry metrics.
func RegisterOpenTelemetry(ctx context.Context, config *types.OpenTelemetry) Registry {
if openTelemetryMeterProvider == nil {
var err error
if openTelemetryMeterProvider, err = newOpenTelemetryMeterProvider(ctx, config); err != nil {
log.Ctx(ctx).Err(err).Msg("Unable to create OpenTelemetry meter provider")
return nil
}
}
if openTelemetryGaugeCollector == nil {
openTelemetryGaugeCollector = newOpenTelemetryGaugeCollector()
}
meter := global.Meter("github.com/traefik/traefik",
metric.WithInstrumentationVersion(version.Version))
reg := &standardRegistry{
epEnabled: config.AddEntryPointsLabels,
routerEnabled: config.AddRoutersLabels,
svcEnabled: config.AddServicesLabels,
configReloadsCounter: newOTLPCounterFrom(meter, configReloadsTotalName, "Config reloads"),
configReloadsFailureCounter: newOTLPCounterFrom(meter, configReloadsFailuresTotalName, "Config reload failures"),
lastConfigReloadSuccessGauge: newOTLPGaugeFrom(meter, configLastReloadSuccessName, "Last config reload success", unit.Milliseconds),
lastConfigReloadFailureGauge: newOTLPGaugeFrom(meter, configLastReloadFailureName, "Last config reload failure", unit.Milliseconds),
tlsCertsNotAfterTimestampGauge: newOTLPGaugeFrom(meter, tlsCertsNotAfterTimestamp, "Certificate expiration timestamp", unit.Milliseconds),
}
if config.AddEntryPointsLabels {
reg.entryPointReqsCounter = newOTLPCounterFrom(meter, entryPointReqsTotalName,
"How many HTTP requests processed on an entrypoint, partitioned by status code, protocol, and method.")
reg.entryPointReqsTLSCounter = newOTLPCounterFrom(meter, entryPointReqsTLSTotalName,
"How many HTTP requests with TLS processed on an entrypoint, partitioned by TLS Version and TLS cipher Used.")
reg.entryPointReqDurationHistogram, _ = NewHistogramWithScale(newOTLPHistogramFrom(meter, entryPointReqDurationName,
"How long it took to process the request on an entrypoint, partitioned by status code, protocol, and method.",
unit.Milliseconds), time.Second)
reg.entryPointOpenConnsGauge = newOTLPGaugeFrom(meter, entryPointOpenConnsName,
"How many open connections exist on an entrypoint, partitioned by method and protocol.",
unit.Dimensionless)
}
if config.AddRoutersLabels {
reg.routerReqsCounter = newOTLPCounterFrom(meter, routerReqsTotalName,
"How many HTTP requests are processed on a router, partitioned by service, status code, protocol, and method.")
reg.routerReqsTLSCounter = newOTLPCounterFrom(meter, routerReqsTLSTotalName,
"How many HTTP requests with TLS are processed on a router, partitioned by service, TLS Version, and TLS cipher Used.")
reg.routerReqDurationHistogram, _ = NewHistogramWithScale(newOTLPHistogramFrom(meter, routerReqDurationName,
"How long it took to process the request on a router, partitioned by service, status code, protocol, and method.",
unit.Milliseconds), time.Second)
reg.routerOpenConnsGauge = newOTLPGaugeFrom(meter, routerOpenConnsName,
"How many open connections exist on a router, partitioned by service, method, and protocol.",
unit.Dimensionless)
}
if config.AddServicesLabels {
reg.serviceReqsCounter = newOTLPCounterFrom(meter, serviceReqsTotalName,
"How many HTTP requests processed on a service, partitioned by status code, protocol, and method.")
reg.serviceReqsTLSCounter = newOTLPCounterFrom(meter, serviceReqsTLSTotalName,
"How many HTTP requests with TLS processed on a service, partitioned by TLS version and TLS cipher.")
reg.serviceReqDurationHistogram, _ = NewHistogramWithScale(newOTLPHistogramFrom(meter, serviceReqDurationName,
"How long it took to process the request on a service, partitioned by status code, protocol, and method.",
unit.Milliseconds), time.Second)
reg.serviceOpenConnsGauge = newOTLPGaugeFrom(meter, serviceOpenConnsName,
"How many open connections exist on a service, partitioned by method and protocol.",
unit.Dimensionless)
reg.serviceRetriesCounter = newOTLPCounterFrom(meter, serviceRetriesTotalName,
"How many request retries happened on a service.")
reg.serviceServerUpGauge = newOTLPGaugeFrom(meter, serviceServerUpName,
"service server is up, described by gauge value of 0 or 1.",
unit.Dimensionless)
}
return reg
}
// StopOpenTelemetry stops and resets Open-Telemetry client.
func StopOpenTelemetry() {
if openTelemetryMeterProvider == nil {
return
}
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := openTelemetryMeterProvider.Shutdown(ctx); err != nil {
log.Err(err).Msg("Unable to shutdown OpenTelemetry meter provider")
}
openTelemetryMeterProvider = nil
}
// newOpenTelemetryMeterProvider creates a new controller.Controller.
func newOpenTelemetryMeterProvider(ctx context.Context, config *types.OpenTelemetry) (*sdkmetric.MeterProvider, error) {
var (
exporter sdkmetric.Exporter
err error
)
if config.GRPC != nil {
exporter, err = newGRPCExporter(ctx, config)
} else {
exporter, err = newHTTPExporter(ctx, config)
}
if err != nil {
return nil, fmt.Errorf("creating exporter: %w", err)
}
opts := []sdkmetric.PeriodicReaderOption{
sdkmetric.WithInterval(time.Duration(config.PushInterval)),
}
// View to customize histogram buckets and rename a single histogram instrument.
customBucketsView, err := view.New(
// Match* to match instruments
view.MatchInstrumentName("traefik_*_request_duration_seconds"),
view.WithSetAggregation(aggregation.ExplicitBucketHistogram{
Boundaries: config.ExplicitBoundaries,
}),
)
if err != nil {
return nil, fmt.Errorf("creating histogram view: %w", err)
}
meterProvider := sdkmetric.NewMeterProvider(sdkmetric.WithReader(
sdkmetric.NewPeriodicReader(exporter, opts...),
customBucketsView,
))
global.SetMeterProvider(meterProvider)
return meterProvider, nil
}
func newHTTPExporter(ctx context.Context, config *types.OpenTelemetry) (sdkmetric.Exporter, error) {
host, port, err := net.SplitHostPort(config.Address)
if err != nil {
return nil, fmt.Errorf("invalid collector address %q: %w", config.Address, err)
}
opts := []otlpmetrichttp.Option{
otlpmetrichttp.WithEndpoint(fmt.Sprintf("%s:%s", host, port)),
otlpmetrichttp.WithHeaders(config.Headers),
otlpmetrichttp.WithCompression(otlpmetrichttp.GzipCompression),
}
if config.Insecure {
opts = append(opts, otlpmetrichttp.WithInsecure())
}
if config.Path != "" {
opts = append(opts, otlpmetrichttp.WithURLPath(config.Path))
}
if config.TLS != nil {
tlsConfig, err := config.TLS.CreateTLSConfig(ctx)
if err != nil {
return nil, fmt.Errorf("creating TLS client config: %w", err)
}
opts = append(opts, otlpmetrichttp.WithTLSClientConfig(tlsConfig))
}
return otlpmetrichttp.New(ctx, opts...)
}
func newGRPCExporter(ctx context.Context, config *types.OpenTelemetry) (sdkmetric.Exporter, error) {
host, port, err := net.SplitHostPort(config.Address)
if err != nil {
return nil, fmt.Errorf("invalid collector address %q: %w", config.Address, err)
}
opts := []otlpmetricgrpc.Option{
otlpmetricgrpc.WithEndpoint(fmt.Sprintf("%s:%s", host, port)),
otlpmetricgrpc.WithHeaders(config.Headers),
otlpmetricgrpc.WithCompressor(gzip.Name),
}
if config.Insecure {
opts = append(opts, otlpmetricgrpc.WithInsecure())
}
if config.TLS != nil {
tlsConfig, err := config.TLS.CreateTLSConfig(ctx)
if err != nil {
return nil, fmt.Errorf("creating TLS client config: %w", err)
}
opts = append(opts, otlpmetricgrpc.WithTLSCredentials(credentials.NewTLS(tlsConfig)))
}
return otlpmetricgrpc.New(ctx, opts...)
}
func newOTLPCounterFrom(meter metric.Meter, name, desc string) *otelCounter {
c, _ := meter.SyncFloat64().Counter(name,
instrument.WithDescription(desc),
instrument.WithUnit(unit.Dimensionless),
)
return &otelCounter{
ip: c,
}
}
type otelCounter struct {
labelNamesValues otelLabelNamesValues
ip syncfloat64.Counter
}
func (c *otelCounter) With(labelValues ...string) metrics.Counter {
return &otelCounter{
labelNamesValues: c.labelNamesValues.With(labelValues...),
ip: c.ip,
}
}
func (c *otelCounter) Add(delta float64) {
c.ip.Add(context.Background(), delta, c.labelNamesValues.ToLabels()...)
}
type gaugeValue struct {
attributes otelLabelNamesValues
value float64
}
type gaugeCollector struct {
mu sync.Mutex
values map[string]map[string]gaugeValue
}
func newOpenTelemetryGaugeCollector() *gaugeCollector {
return &gaugeCollector{
values: make(map[string]map[string]gaugeValue),
}
}
func (c *gaugeCollector) add(name string, delta float64, attributes otelLabelNamesValues) {
c.mu.Lock()
defer c.mu.Unlock()
str := strings.Join(attributes, "")
if _, exists := c.values[name]; !exists {
c.values[name] = map[string]gaugeValue{
str: {
attributes: attributes,
value: delta,
},
}
return
}
v, exists := c.values[name][str]
if !exists {
c.values[name][str] = gaugeValue{
attributes: attributes,
value: delta,
}
return
}
c.values[name][str] = gaugeValue{
attributes: attributes,
value: v.value + delta,
}
}
func (c *gaugeCollector) set(name string, value float64, attributes otelLabelNamesValues) {
c.mu.Lock()
defer c.mu.Unlock()
if _, exists := c.values[name]; !exists {
c.values[name] = make(map[string]gaugeValue)
}
c.values[name][strings.Join(attributes, "")] = gaugeValue{
attributes: attributes,
value: value,
}
}
func newOTLPGaugeFrom(meter metric.Meter, name, desc string, u unit.Unit) *otelGauge {
openTelemetryGaugeCollector.values[name] = make(map[string]gaugeValue)
c, _ := meter.AsyncFloat64().Gauge(name,
instrument.WithDescription(desc),
instrument.WithUnit(u),
)
err := meter.RegisterCallback([]instrument.Asynchronous{c}, func(ctx context.Context) {
openTelemetryGaugeCollector.mu.Lock()
defer openTelemetryGaugeCollector.mu.Unlock()
values, exists := openTelemetryGaugeCollector.values[name]
if !exists {
return
}
for _, value := range values {
c.Observe(ctx, value.value, value.attributes.ToLabels()...)
}
})
if err != nil {
log.Err(err).Msg("Unable to register OpenTelemetry meter callback")
}
return &otelGauge{
ip: c,
name: name,
}
}
type otelGauge struct {
labelNamesValues otelLabelNamesValues
ip asyncfloat64.Gauge
name string
}
func (g *otelGauge) With(labelValues ...string) metrics.Gauge {
return &otelGauge{
labelNamesValues: g.labelNamesValues.With(labelValues...),
ip: g.ip,
name: g.name,
}
}
func (g *otelGauge) Add(delta float64) {
openTelemetryGaugeCollector.add(g.name, delta, g.labelNamesValues)
}
func (g *otelGauge) Set(value float64) {
openTelemetryGaugeCollector.set(g.name, value, g.labelNamesValues)
}
func newOTLPHistogramFrom(meter metric.Meter, name, desc string, u unit.Unit) *otelHistogram {
c, _ := meter.SyncFloat64().Histogram(name,
instrument.WithDescription(desc),
instrument.WithUnit(u),
)
return &otelHistogram{
ip: c,
}
}
type otelHistogram struct {
labelNamesValues otelLabelNamesValues
ip syncfloat64.Histogram
}
func (h *otelHistogram) With(labelValues ...string) metrics.Histogram {
return &otelHistogram{
labelNamesValues: h.labelNamesValues.With(labelValues...),
ip: h.ip,
}
}
func (h *otelHistogram) Observe(incr float64) {
h.ip.Record(context.Background(), incr, h.labelNamesValues.ToLabels()...)
}
// otelLabelNamesValues is the equivalent of prometheus' labelNamesValues
// but adapted to OpenTelemetry.
// otelLabelNamesValues is a type alias that provides validation on its With
// method.
// Metrics may include it as a member to help them satisfy With semantics and
// save some code duplication.
type otelLabelNamesValues []string
// With validates the input, and returns a new aggregate otelLabelNamesValues.
func (lvs otelLabelNamesValues) With(labelValues ...string) otelLabelNamesValues {
if len(labelValues)%2 != 0 {
labelValues = append(labelValues, "unknown")
}
return append(lvs, labelValues...)
}
// ToLabels is a convenience method to convert a otelLabelNamesValues
// to the native attribute.KeyValue.
func (lvs otelLabelNamesValues) ToLabels() []attribute.KeyValue {
labels := make([]attribute.KeyValue, len(lvs)/2)
for i := 0; i < len(labels); i++ {
labels[i] = attribute.String(lvs[2*i], lvs[2*i+1])
}
return labels
}

View file

@ -0,0 +1,446 @@
package metrics
import (
"compress/gzip"
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"net/http/httptest"
"net/url"
"strconv"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
ptypes "github.com/traefik/paerser/types"
"github.com/traefik/traefik/v2/pkg/types"
"go.opentelemetry.io/collector/pdata/pmetric/pmetricotlp"
"go.opentelemetry.io/otel/attribute"
)
func TestOpenTelemetry_labels(t *testing.T) {
tests := []struct {
desc string
values otelLabelNamesValues
with []string
expect []attribute.KeyValue
}{
{
desc: "with no starting value",
values: otelLabelNamesValues{},
expect: []attribute.KeyValue{},
},
{
desc: "with one starting value",
values: otelLabelNamesValues{"foo"},
expect: []attribute.KeyValue{},
},
{
desc: "with two starting value",
values: otelLabelNamesValues{"foo", "bar"},
expect: []attribute.KeyValue{attribute.String("foo", "bar")},
},
{
desc: "with no starting value, and with one other value",
values: otelLabelNamesValues{},
with: []string{"baz"},
expect: []attribute.KeyValue{attribute.String("baz", "unknown")},
},
{
desc: "with no starting value, and with two other value",
values: otelLabelNamesValues{},
with: []string{"baz", "buz"},
expect: []attribute.KeyValue{attribute.String("baz", "buz")},
},
{
desc: "with one starting value, and with one other value",
values: otelLabelNamesValues{"foo"},
with: []string{"baz"},
expect: []attribute.KeyValue{attribute.String("foo", "baz")},
},
{
desc: "with one starting value, and with two other value",
values: otelLabelNamesValues{"foo"},
with: []string{"baz", "buz"},
expect: []attribute.KeyValue{attribute.String("foo", "baz")},
},
{
desc: "with two starting value, and with one other value",
values: otelLabelNamesValues{"foo", "bar"},
with: []string{"baz"},
expect: []attribute.KeyValue{
attribute.String("foo", "bar"),
attribute.String("baz", "unknown"),
},
},
{
desc: "with two starting value, and with two other value",
values: otelLabelNamesValues{"foo", "bar"},
with: []string{"baz", "buz"},
expect: []attribute.KeyValue{
attribute.String("foo", "bar"),
attribute.String("baz", "buz"),
},
},
}
for _, test := range tests {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
assert.Equal(t, test.expect, test.values.With(test.with...).ToLabels())
})
}
}
func TestOpenTelemetry_GaugeCollectorAdd(t *testing.T) {
tests := []struct {
desc string
gc *gaugeCollector
delta float64
name string
attributes otelLabelNamesValues
expect map[string]map[string]gaugeValue
}{
{
desc: "empty collector",
gc: newOpenTelemetryGaugeCollector(),
delta: 1,
name: "foo",
expect: map[string]map[string]gaugeValue{
"foo": {"": {value: 1}},
},
},
{
desc: "initialized collector",
gc: &gaugeCollector{
values: map[string]map[string]gaugeValue{
"foo": {"": {value: 1}},
},
},
delta: 1,
name: "foo",
expect: map[string]map[string]gaugeValue{
"foo": {"": {value: 2}},
},
},
{
desc: "initialized collector, values with label (only the last one counts)",
gc: &gaugeCollector{
values: map[string]map[string]gaugeValue{
"foo": {
"bar": {
attributes: otelLabelNamesValues{"bar"},
value: 1,
},
},
},
},
delta: 1,
name: "foo",
expect: map[string]map[string]gaugeValue{
"foo": {
"": {
value: 1,
},
"bar": {
attributes: otelLabelNamesValues{"bar"},
value: 1,
},
},
},
},
{
desc: "initialized collector, values with label on set",
gc: &gaugeCollector{
values: map[string]map[string]gaugeValue{
"foo": {"bar": {value: 1}},
},
},
delta: 1,
name: "foo",
attributes: otelLabelNamesValues{"baz"},
expect: map[string]map[string]gaugeValue{
"foo": {
"bar": {
value: 1,
},
"baz": {
value: 1,
attributes: otelLabelNamesValues{"baz"},
},
},
},
},
}
for _, test := range tests {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
test.gc.add(test.name, test.delta, test.attributes)
assert.Equal(t, test.expect, test.gc.values)
})
}
}
func TestOpenTelemetry_GaugeCollectorSet(t *testing.T) {
tests := []struct {
desc string
gc *gaugeCollector
value float64
name string
attributes otelLabelNamesValues
expect map[string]map[string]gaugeValue
}{
{
desc: "empty collector",
gc: newOpenTelemetryGaugeCollector(),
value: 1,
name: "foo",
expect: map[string]map[string]gaugeValue{
"foo": {"": {value: 1}},
},
},
{
desc: "initialized collector",
gc: &gaugeCollector{
values: map[string]map[string]gaugeValue{
"foo": {"": {value: 1}},
},
},
value: 1,
name: "foo",
expect: map[string]map[string]gaugeValue{
"foo": {"": {value: 1}},
},
},
{
desc: "initialized collector, values with label",
gc: &gaugeCollector{
values: map[string]map[string]gaugeValue{
"foo": {
"bar": {
attributes: otelLabelNamesValues{"bar"},
value: 1,
},
},
},
},
value: 1,
name: "foo",
expect: map[string]map[string]gaugeValue{
"foo": {
"": {
value: 1,
},
"bar": {
attributes: otelLabelNamesValues{"bar"},
value: 1,
},
},
},
},
{
desc: "initialized collector, values with label on set",
gc: &gaugeCollector{
values: map[string]map[string]gaugeValue{
"foo": {"": {value: 1}},
},
},
value: 1,
name: "foo",
attributes: otelLabelNamesValues{"bar"},
expect: map[string]map[string]gaugeValue{
"foo": {
"": {
value: 1,
},
"bar": {
value: 1,
attributes: otelLabelNamesValues{"bar"},
},
},
},
},
}
for _, test := range tests {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
test.gc.set(test.name, test.value, test.attributes)
assert.Equal(t, test.expect, test.gc.values)
})
}
}
func TestOpenTelemetry(t *testing.T) {
t.Parallel()
c := make(chan *string)
defer close(c)
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
gzr, err := gzip.NewReader(r.Body)
require.NoError(t, err)
body, err := io.ReadAll(gzr)
require.NoError(t, err)
req := pmetricotlp.NewExportRequest()
err = req.UnmarshalProto(body)
require.NoError(t, err)
marshalledReq, err := json.Marshal(req)
require.NoError(t, err)
bodyStr := string(marshalledReq)
c <- &bodyStr
_, err = fmt.Fprintln(w, "ok")
require.NoError(t, err)
}))
defer ts.Close()
sURL, err := url.Parse(ts.URL)
require.NoError(t, err)
var cfg types.OpenTelemetry
(&cfg).SetDefaults()
cfg.AddRoutersLabels = true
cfg.Address = sURL.Host
cfg.Insecure = true
cfg.PushInterval = ptypes.Duration(10 * time.Millisecond)
registry := RegisterOpenTelemetry(context.Background(), &cfg)
require.NotNil(t, registry)
if !registry.IsEpEnabled() || !registry.IsRouterEnabled() || !registry.IsSvcEnabled() {
t.Fatalf("registry should return true for IsEnabled(), IsRouterEnabled() and IsSvcEnabled()")
}
// TODO: the len of startUnixNano is no supposed to be 20, it should be 19
expectedServer := []string{
`({"name":"traefik_config_reloads_total","description":"Config reloads","unit":"1","sum":{"dataPoints":\[{"startTimeUnixNano":"[\d]{19}","timeUnixNano":"[\d]{19}","asDouble":1}\],"aggregationTemporality":2,"isMonotonic":true}})`,
`({"name":"traefik_config_reloads_failure_total","description":"Config reload failures","unit":"1","sum":{"dataPoints":\[{"startTimeUnixNano":"[\d]{19}","timeUnixNano":"[\d]{19}","asDouble":1}\],"aggregationTemporality":2,"isMonotonic":true}})`,
`({"name":"traefik_config_last_reload_success","description":"Last config reload success","unit":"ms","gauge":{"dataPoints":\[{"startTimeUnixNano":"[\d]{20}","timeUnixNano":"[\d]{19}","asDouble":1}\]}})`,
`({"name":"traefik_config_last_reload_failure","description":"Last config reload failure","unit":"ms","gauge":{"dataPoints":\[{"startTimeUnixNano":"[\d]{20}","timeUnixNano":"[\d]{19}","asDouble":1}\]}})`,
}
registry.ConfigReloadsCounter().Add(1)
registry.ConfigReloadsFailureCounter().Add(1)
registry.LastConfigReloadSuccessGauge().Set(1)
registry.LastConfigReloadFailureGauge().Set(1)
msgServer := <-c
assertMessage(t, *msgServer, expectedServer)
expectedTLS := []string{
`({"name":"traefik_tls_certs_not_after","description":"Certificate expiration timestamp","unit":"ms","gauge":{"dataPoints":\[{"attributes":\[{"key":"key","value":{"stringValue":"value"}}\],"startTimeUnixNano":"[\d]{20}","timeUnixNano":"[\d]{19}","asDouble":1}\]}})`,
}
registry.TLSCertsNotAfterTimestampGauge().With("key", "value").Set(1)
msgTLS := <-c
assertMessage(t, *msgTLS, expectedTLS)
expectedEntrypoint := []string{
`({"name":"traefik_entrypoint_requests_total","description":"How many HTTP requests processed on an entrypoint, partitioned by status code, protocol, and method.","unit":"1","sum":{"dataPoints":\[{"attributes":\[{"key":"code","value":{"stringValue":"200"}},{"key":"entrypoint","value":{"stringValue":"test1"}},{"key":"method","value":{"stringValue":"GET"}}\],"startTimeUnixNano":"[\d]{19}","timeUnixNano":"[\d]{19}","asDouble":1}\],"aggregationTemporality":2,"isMonotonic":true}})`,
`({"name":"traefik_entrypoint_requests_tls_total","description":"How many HTTP requests with TLS processed on an entrypoint, partitioned by TLS Version and TLS cipher Used.","unit":"1","sum":{"dataPoints":\[{"attributes":\[{"key":"entrypoint","value":{"stringValue":"test2"}},{"key":"tls_cipher","value":{"stringValue":"bar"}},{"key":"tls_version","value":{"stringValue":"foo"}}\],"startTimeUnixNano":"[\d]{19}","timeUnixNano":"[\d]{19}","asDouble":1}\],"aggregationTemporality":2,"isMonotonic":true}})`,
`({"name":"traefik_entrypoint_request_duration_seconds","description":"How long it took to process the request on an entrypoint, partitioned by status code, protocol, and method.","unit":"ms","histogram":{"dataPoints":\[{"attributes":\[{"key":"entrypoint","value":{"stringValue":"test3"}}\],"startTimeUnixNano":"[\d]{19}","timeUnixNano":"[\d]{19}","count":"1","sum":10000,"bucketCounts":\["0","0","0","0","0","0","0","0","0","0","0","1"\],"explicitBounds":\[0.005,0.01,0.025,0.05,0.1,0.25,0.5,1,2.5,5,10\],"min":10000,"max":10000}\],"aggregationTemporality":2}})`,
`({"name":"traefik_entrypoint_open_connections","description":"How many open connections exist on an entrypoint, partitioned by method and protocol.","unit":"1","gauge":{"dataPoints":\[{"attributes":\[{"key":"entrypoint","value":{"stringValue":"test4"}}\],"startTimeUnixNano":"[\d]{20}","timeUnixNano":"[\d]{19}","asDouble":1}\]}})`,
}
registry.EntryPointReqsCounter().With("entrypoint", "test1", "code", strconv.Itoa(http.StatusOK), "method", http.MethodGet).Add(1)
registry.EntryPointReqsTLSCounter().With("entrypoint", "test2", "tls_version", "foo", "tls_cipher", "bar").Add(1)
registry.EntryPointReqDurationHistogram().With("entrypoint", "test3").Observe(10000)
registry.EntryPointOpenConnsGauge().With("entrypoint", "test4").Set(1)
msgEntrypoint := <-c
assertMessage(t, *msgEntrypoint, expectedEntrypoint)
expectedRouter := []string{
`({"name":"traefik_router_requests_total","description":"How many HTTP requests are processed on a router, partitioned by service, status code, protocol, and method.","unit":"1","sum":{"dataPoints":\[{"attributes":\[{"key":"code","value":{"stringValue":"(?:200|404)"}},{"key":"method","value":{"stringValue":"GET"}},{"key":"router","value":{"stringValue":"RouterReqsCounter"}},{"key":"service","value":{"stringValue":"test"}}\],"startTimeUnixNano":"[\d]{19}","timeUnixNano":"[\d]{19}","asDouble":1},{"attributes":\[{"key":"code","value":{"stringValue":"(?:200|404)"}},{"key":"method","value":{"stringValue":"GET"}},{"key":"router","value":{"stringValue":"RouterReqsCounter"}},{"key":"service","value":{"stringValue":"test"}}\],"startTimeUnixNano":"[\d]{19}","timeUnixNano":"[\d]{19}","asDouble":1}\],"aggregationTemporality":2,"isMonotonic":true}})`,
`({"name":"traefik_router_requests_tls_total","description":"How many HTTP requests with TLS are processed on a router, partitioned by service, TLS Version, and TLS cipher Used.","unit":"1","sum":{"dataPoints":\[{"attributes":\[{"key":"router","value":{"stringValue":"demo"}},{"key":"service","value":{"stringValue":"test"}},{"key":"tls_cipher","value":{"stringValue":"bar"}},{"key":"tls_version","value":{"stringValue":"foo"}}\],"startTimeUnixNano":"[\d]{19}","timeUnixNano":"[\d]{19}","asDouble":1}\],"aggregationTemporality":2,"isMonotonic":true}})`,
`({"name":"traefik_router_request_duration_seconds","description":"How long it took to process the request on a router, partitioned by service, status code, protocol, and method.","unit":"ms","histogram":{"dataPoints":\[{"attributes":\[{"key":"code","value":{"stringValue":"200"}},{"key":"router","value":{"stringValue":"demo"}},{"key":"service","value":{"stringValue":"test"}}\],"startTimeUnixNano":"[\d]{19}","timeUnixNano":"[\d]{19}","count":"1","sum":10000,"bucketCounts":\["0","0","0","0","0","0","0","0","0","0","0","1"\],"explicitBounds":\[0.005,0.01,0.025,0.05,0.1,0.25,0.5,1,2.5,5,10\],"min":10000,"max":10000}\],"aggregationTemporality":2}})`,
`({"name":"traefik_router_open_connections","description":"How many open connections exist on a router, partitioned by service, method, and protocol.","unit":"1","gauge":{"dataPoints":\[{"attributes":\[{"key":"router","value":{"stringValue":"demo"}},{"key":"service","value":{"stringValue":"test"}}\],"startTimeUnixNano":"[\d]{20}","timeUnixNano":"[\d]{19}","asDouble":1}\]}})`,
}
registry.RouterReqsCounter().With("router", "RouterReqsCounter", "service", "test", "code", strconv.Itoa(http.StatusNotFound), "method", http.MethodGet).Add(1)
registry.RouterReqsCounter().With("router", "RouterReqsCounter", "service", "test", "code", strconv.Itoa(http.StatusOK), "method", http.MethodGet).Add(1)
registry.RouterReqsTLSCounter().With("router", "demo", "service", "test", "tls_version", "foo", "tls_cipher", "bar").Add(1)
registry.RouterReqDurationHistogram().With("router", "demo", "service", "test", "code", strconv.Itoa(http.StatusOK)).Observe(10000)
registry.RouterOpenConnsGauge().With("router", "demo", "service", "test").Set(1)
msgRouter := <-c
assertMessage(t, *msgRouter, expectedRouter)
expectedService := []string{
`({"name":"traefik_service_requests_total","description":"How many HTTP requests processed on a service, partitioned by status code, protocol, and method.","unit":"1","sum":{"dataPoints":\[{"attributes":\[{"key":"code","value":{"stringValue":"(?:200|404)"}},{"key":"method","value":{"stringValue":"GET"}},{"key":"service","value":{"stringValue":"ServiceReqsCounter"}}\],"startTimeUnixNano":"[\d]{19}","timeUnixNano":"[\d]{19}","asDouble":1},{"attributes":\[{"key":"code","value":{"stringValue":"(?:200|404)"}},{"key":"method","value":{"stringValue":"GET"}},{"key":"service","value":{"stringValue":"ServiceReqsCounter"}}\],"startTimeUnixNano":"[\d]{19}","timeUnixNano":"[\d]{19}","asDouble":1}\],"aggregationTemporality":2,"isMonotonic":true}})`,
`({"name":"traefik_service_requests_tls_total","description":"How many HTTP requests with TLS processed on a service, partitioned by TLS version and TLS cipher.","unit":"1","sum":{"dataPoints":\[{"attributes":\[{"key":"service","value":{"stringValue":"test"}},{"key":"tls_cipher","value":{"stringValue":"bar"}},{"key":"tls_version","value":{"stringValue":"foo"}}\],"startTimeUnixNano":"[\d]{19}","timeUnixNano":"[\d]{19}","asDouble":1}\],"aggregationTemporality":2,"isMonotonic":true}})`,
`({"name":"traefik_service_request_duration_seconds","description":"How long it took to process the request on a service, partitioned by status code, protocol, and method.","unit":"ms","histogram":{"dataPoints":\[{"attributes":\[{"key":"code","value":{"stringValue":"200"}},{"key":"service","value":{"stringValue":"test"}}\],"startTimeUnixNano":"[\d]{19}","timeUnixNano":"[\d]{19}","count":"1","sum":10000,"bucketCounts":\["0","0","0","0","0","0","0","0","0","0","0","1"\],"explicitBounds":\[0.005,0.01,0.025,0.05,0.1,0.25,0.5,1,2.5,5,10\],"min":10000,"max":10000}\],"aggregationTemporality":2}})`,
`({"name":"traefik_service_server_up","description":"service server is up, described by gauge value of 0 or 1.","unit":"1","gauge":{"dataPoints":\[{"attributes":\[{"key":"service","value":{"stringValue":"test"}},{"key":"url","value":{"stringValue":"http://127.0.0.1"}}\],"startTimeUnixNano":"[\d]{20}","timeUnixNano":"[\d]{19}","asDouble":1}\]}})`,
}
registry.ServiceReqsCounter().With("service", "ServiceReqsCounter", "code", strconv.Itoa(http.StatusOK), "method", http.MethodGet).Add(1)
registry.ServiceReqsCounter().With("service", "ServiceReqsCounter", "code", strconv.Itoa(http.StatusNotFound), "method", http.MethodGet).Add(1)
registry.ServiceReqsTLSCounter().With("service", "test", "tls_version", "foo", "tls_cipher", "bar").Add(1)
registry.ServiceReqDurationHistogram().With("service", "test", "code", strconv.Itoa(http.StatusOK)).Observe(10000)
registry.ServiceServerUpGauge().With("service", "test", "url", "http://127.0.0.1").Set(1)
msgService := <-c
assertMessage(t, *msgService, expectedService)
expectedServiceRetries := []string{
`({"attributes":\[{"key":"service","value":{"stringValue":"foobar"}}\],"startTimeUnixNano":"[\d]{19}","timeUnixNano":"[\d]{19}","asDouble":1})`,
`({"attributes":\[{"key":"service","value":{"stringValue":"test"}}\],"startTimeUnixNano":"[\d]{19}","timeUnixNano":"[\d]{19}","asDouble":2})`,
}
registry.ServiceRetriesCounter().With("service", "test").Add(1)
registry.ServiceRetriesCounter().With("service", "test").Add(1)
registry.ServiceRetriesCounter().With("service", "foobar").Add(1)
msgServiceRetries := <-c
assertMessage(t, *msgServiceRetries, expectedServiceRetries)
expectedServiceOpenConns := []string{
`({"attributes":\[{"key":"service","value":{"stringValue":"test"}}\],"startTimeUnixNano":"[\d]{20}","timeUnixNano":"[\d]{19}","asDouble":3})`,
`({"attributes":\[{"key":"service","value":{"stringValue":"foobar"}}\],"startTimeUnixNano":"[\d]{20}","timeUnixNano":"[\d]{19}","asDouble":1})`,
}
registry.ServiceOpenConnsGauge().With("service", "test").Set(1)
registry.ServiceOpenConnsGauge().With("service", "test").Add(1)
registry.ServiceOpenConnsGauge().With("service", "test").Add(1)
registry.ServiceOpenConnsGauge().With("service", "foobar").Add(1)
msgServiceOpenConns := <-c
assertMessage(t, *msgServiceOpenConns, expectedServiceOpenConns)
expectedEntryPointReqDuration := []string{
`({"attributes":\[{"key":"entrypoint","value":{"stringValue":"myEntrypoint"}}\],"startTimeUnixNano":"[\d]{19}","timeUnixNano":"[\d]{19}","count":"2","sum":30000,"bucketCounts":\["0","0","0","0","0","0","0","0","0","0","0","2"\],"explicitBounds":\[0.005,0.01,0.025,0.05,0.1,0.25,0.5,1,2.5,5,10\],"min":10000,"max":20000})`,
}
registry.EntryPointReqDurationHistogram().With("entrypoint", "myEntrypoint").Observe(10000)
registry.EntryPointReqDurationHistogram().With("entrypoint", "myEntrypoint").Observe(20000)
msgEntryPointReqDurationHistogram := <-c
assertMessage(t, *msgEntryPointReqDurationHistogram, expectedEntryPointReqDuration)
// We need to unlock the HTTP Server for the last export call when stopping
// OpenTelemetry.
go func() {
<-c
}()
StopOpenTelemetry()
}

View file

@ -0,0 +1,46 @@
package contenttype
import (
"context"
"net/http"
"github.com/traefik/traefik/v2/pkg/middlewares"
)
const (
typeName = "ContentType"
)
// ContentType is a middleware used to activate Content-Type auto-detection.
type contentType struct {
next http.Handler
name string
}
// New creates a new handler.
func New(ctx context.Context, next http.Handler, name string) (http.Handler, error) {
middlewares.GetLogger(ctx, name, typeName).Debug().Msg("Creating middleware")
return &contentType{next: next, name: name}, nil
}
func (c *contentType) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
// Re-enable auto-detection.
if ct, ok := rw.Header()["Content-Type"]; ok && ct == nil {
middlewares.GetLogger(req.Context(), c.name, typeName).
Debug().Msg("Enable Content-Type auto-detection.")
delete(rw.Header(), "Content-Type")
}
c.next.ServeHTTP(rw, req)
}
func DisableAutoDetection(next http.Handler) http.HandlerFunc {
return func(rw http.ResponseWriter, req *http.Request) {
// Prevent Content-Type auto-detection.
if _, ok := rw.Header()["Content-Type"]; !ok {
rw.Header()["Content-Type"] = nil
}
next.ServeHTTP(rw, req)
}
}

View file

@ -0,0 +1,79 @@
package contenttype
import (
"context"
"net/http"
"net/http/httptest"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/traefik/traefik/v2/pkg/testhelpers"
)
func TestAutoDetection(t *testing.T) {
testCases := []struct {
desc string
autoDetect bool
contentType string
wantContentType string
}{
{
desc: "Keep the Content-Type returned by the server",
autoDetect: false,
contentType: "application/json",
wantContentType: "application/json",
},
{
desc: "Don't auto-detect Content-Type header by default when not set by the server",
autoDetect: false,
contentType: "",
wantContentType: "",
},
{
desc: "Keep the Content-Type returned by the server with auto-detection middleware",
autoDetect: true,
contentType: "application/json",
wantContentType: "application/json",
},
{
desc: "Auto-detect when Content-Type header is not already set by the server with auto-detection middleware",
autoDetect: true,
contentType: "",
wantContentType: "text/plain; charset=utf-8",
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
var next http.Handler
next = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if test.contentType != "" {
w.Header().Set("Content-Type", test.contentType)
}
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte("Test"))
})
if test.autoDetect {
var err error
next, err = New(context.Background(), next, "foo-content-type")
require.NoError(t, err)
}
server := httptest.NewServer(
DisableAutoDetection(next),
)
t.Cleanup(server.Close)
req := testhelpers.MustNewRequest(http.MethodGet, server.URL, nil)
res, err := server.Client().Do(req)
require.NoError(t, err)
assert.Equal(t, test.wantContentType, res.Header.Get("Content-Type"))
})
}
}

240
pkg/muxer/http/matcher.go Normal file
View file

@ -0,0 +1,240 @@
package http
import (
"fmt"
"net/http"
"regexp"
"strings"
"unicode/utf8"
"github.com/gorilla/mux"
"github.com/rs/zerolog/log"
"github.com/traefik/traefik/v2/pkg/ip"
"github.com/traefik/traefik/v2/pkg/middlewares/requestdecorator"
"golang.org/x/exp/slices"
)
var httpFuncs = map[string]func(*mux.Route, ...string) error{
"ClientIP": expectNParameters(clientIP, 1),
"Method": expectNParameters(method, 1),
"Host": expectNParameters(host, 1),
"HostRegexp": expectNParameters(hostRegexp, 1),
"Path": expectNParameters(path, 1),
"PathRegexp": expectNParameters(pathRegexp, 1),
"PathPrefix": expectNParameters(pathPrefix, 1),
"Header": expectNParameters(header, 2),
"HeaderRegexp": expectNParameters(headerRegexp, 2),
"Query": expectNParameters(query, 1, 2),
"QueryRegexp": expectNParameters(queryRegexp, 1, 2),
}
func expectNParameters(fn func(*mux.Route, ...string) error, n ...int) func(*mux.Route, ...string) error {
return func(route *mux.Route, s ...string) error {
if !slices.Contains(n, len(s)) {
return fmt.Errorf("unexpected number of parameters; got %d, expected one of %v", len(s), n)
}
return fn(route, s...)
}
}
func clientIP(route *mux.Route, clientIP ...string) error {
checker, err := ip.NewChecker(clientIP)
if err != nil {
return fmt.Errorf("initializing IP checker for ClientIP matcher: %w", err)
}
strategy := ip.RemoteAddrStrategy{}
route.MatcherFunc(func(req *http.Request, _ *mux.RouteMatch) bool {
ok, err := checker.Contains(strategy.GetIP(req))
if err != nil {
log.Ctx(req.Context()).Warn().Err(err).Msg("ClientIP matcher: could not match remote address")
return false
}
return ok
})
return nil
}
func method(route *mux.Route, methods ...string) error {
return route.Methods(methods...).GetError()
}
func host(route *mux.Route, hosts ...string) error {
host := hosts[0]
if !IsASCII(host) {
return fmt.Errorf("invalid value %q for Host matcher, non-ASCII characters are not allowed", host)
}
host = strings.ToLower(host)
route.MatcherFunc(func(req *http.Request, _ *mux.RouteMatch) bool {
reqHost := requestdecorator.GetCanonizedHost(req.Context())
if len(reqHost) == 0 {
return false
}
if reqHost == host {
return true
}
flatH := requestdecorator.GetCNAMEFlatten(req.Context())
if len(flatH) > 0 {
return strings.EqualFold(flatH, host)
}
// Check for match on trailing period on host
if last := len(host) - 1; last >= 0 && host[last] == '.' {
h := host[:last]
if reqHost == h {
return true
}
}
// Check for match on trailing period on request
if last := len(reqHost) - 1; last >= 0 && reqHost[last] == '.' {
h := reqHost[:last]
if h == host {
return true
}
}
return false
})
return nil
}
func hostRegexp(route *mux.Route, hosts ...string) error {
host := hosts[0]
if !IsASCII(host) {
return fmt.Errorf("invalid value %q for HostRegexp matcher, non-ASCII characters are not allowed", host)
}
re, err := regexp.Compile(host)
if err != nil {
return fmt.Errorf("compiling HostRegexp matcher: %w", err)
}
route.MatcherFunc(func(req *http.Request, _ *mux.RouteMatch) bool {
return re.MatchString(requestdecorator.GetCanonizedHost(req.Context())) ||
re.MatchString(requestdecorator.GetCNAMEFlatten(req.Context()))
})
return nil
}
func path(route *mux.Route, paths ...string) error {
path := paths[0]
if !strings.HasPrefix(path, "/") {
return fmt.Errorf("path %q does not start with a '/'", path)
}
route.MatcherFunc(func(req *http.Request, _ *mux.RouteMatch) bool {
return req.URL.Path == path
})
return nil
}
func pathRegexp(route *mux.Route, paths ...string) error {
path := paths[0]
re, err := regexp.Compile(path)
if err != nil {
return fmt.Errorf("compiling PathPrefix matcher: %w", err)
}
route.MatcherFunc(func(req *http.Request, _ *mux.RouteMatch) bool {
return re.MatchString(req.URL.Path)
})
return nil
}
func pathPrefix(route *mux.Route, paths ...string) error {
path := paths[0]
if !strings.HasPrefix(path, "/") {
return fmt.Errorf("path %q does not start with a '/'", path)
}
route.MatcherFunc(func(req *http.Request, _ *mux.RouteMatch) bool {
return strings.HasPrefix(req.URL.Path, path)
})
return nil
}
func header(route *mux.Route, headers ...string) error {
return route.Headers(headers...).GetError()
}
func headerRegexp(route *mux.Route, headers ...string) error {
return route.HeadersRegexp(headers...).GetError()
}
func query(route *mux.Route, queries ...string) error {
key := queries[0]
var value string
if len(queries) == 2 {
value = queries[1]
}
route.MatcherFunc(func(req *http.Request, _ *mux.RouteMatch) bool {
values, ok := req.URL.Query()[key]
if !ok {
return false
}
return slices.Contains(values, value)
})
return nil
}
func queryRegexp(route *mux.Route, queries ...string) error {
if len(queries) == 1 {
return query(route, queries...)
}
key, value := queries[0], queries[1]
re, err := regexp.Compile(value)
if err != nil {
return fmt.Errorf("compiling QueryRegexp matcher: %w", err)
}
route.MatcherFunc(func(req *http.Request, _ *mux.RouteMatch) bool {
values, ok := req.URL.Query()[key]
if !ok {
return false
}
idx := slices.IndexFunc(values, func(value string) bool {
return re.MatchString(value)
})
return idx >= 0
})
return nil
}
// IsASCII checks if the given string contains only ASCII characters.
func IsASCII(s string) bool {
for i := 0; i < len(s); i++ {
if s[i] >= utf8.RuneSelf {
return false
}
}
return true
}

View file

@ -0,0 +1,999 @@
package http
import (
"net/http"
"net/http/httptest"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/traefik/traefik/v2/pkg/middlewares/requestdecorator"
)
func TestClientIPMatcher(t *testing.T) {
testCases := []struct {
desc string
rule string
expected map[string]int
expectedError bool
}{
{
desc: "invalid ClientIP matcher",
rule: "ClientIP(`1`)",
expectedError: true,
},
{
desc: "invalid ClientIP matcher (no parameter)",
rule: "ClientIP()",
expectedError: true,
},
{
desc: "invalid ClientIP matcher (empty parameter)",
rule: "ClientIP(``)",
expectedError: true,
},
{
desc: "invalid ClientIP matcher (too many parameters)",
rule: "ClientIP(`127.0.0.1`, `192.168.1.0/24`)",
expectedError: true,
},
{
desc: "valid ClientIP matcher",
rule: "ClientIP(`127.0.0.1`)",
expected: map[string]int{
"127.0.0.1": http.StatusOK,
"192.168.1.1": http.StatusNotFound,
},
},
{
desc: "valid ClientIP matcher but invalid remote address",
rule: "ClientIP(`127.0.0.1`)",
expected: map[string]int{
"1": http.StatusNotFound,
},
},
{
desc: "valid ClientIP matcher using CIDR",
rule: "ClientIP(`192.168.1.0/24`)",
expected: map[string]int{
"192.168.1.1": http.StatusOK,
"192.168.1.100": http.StatusOK,
"192.168.2.1": http.StatusNotFound,
},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
muxer, err := NewMuxer()
require.NoError(t, err)
err = muxer.AddRoute(test.rule, 0, handler)
if test.expectedError {
require.Error(t, err)
return
}
require.NoError(t, err)
results := make(map[string]int)
for remoteAddr := range test.expected {
w := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodGet, "https://example.com", http.NoBody)
req.RemoteAddr = remoteAddr
muxer.ServeHTTP(w, req)
results[remoteAddr] = w.Code
}
assert.Equal(t, test.expected, results)
})
}
}
func TestMethodMatcher(t *testing.T) {
testCases := []struct {
desc string
rule string
expected map[string]int
expectedError bool
}{
{
desc: "invalid Method matcher (no parameter)",
rule: "Method()",
expectedError: true,
},
{
desc: "invalid Method matcher (empty parameter)",
rule: "Method(``)",
expectedError: true,
},
{
desc: "invalid Method matcher (too many parameters)",
rule: "Method(`GET`, `POST`)",
expectedError: true,
},
{
desc: "valid Method matcher",
rule: "Method(`GET`)",
expected: map[string]int{
http.MethodGet: http.StatusOK,
http.MethodPost: http.StatusMethodNotAllowed,
},
},
{
desc: "valid Method matcher (lower case)",
rule: "Method(`get`)",
expected: map[string]int{
http.MethodGet: http.StatusOK,
http.MethodPost: http.StatusMethodNotAllowed,
},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
muxer, err := NewMuxer()
require.NoError(t, err)
err = muxer.AddRoute(test.rule, 0, handler)
if test.expectedError {
require.Error(t, err)
return
}
require.NoError(t, err)
results := make(map[string]int)
for method := range test.expected {
w := httptest.NewRecorder()
req := httptest.NewRequest(method, "https://example.com", http.NoBody)
muxer.ServeHTTP(w, req)
results[method] = w.Code
}
assert.Equal(t, test.expected, results)
})
}
}
func TestHostMatcher(t *testing.T) {
testCases := []struct {
desc string
rule string
expected map[string]int
expectedError bool
}{
{
desc: "invalid Host matcher (no parameter)",
rule: "Host()",
expectedError: true,
},
{
desc: "invalid Host matcher (empty parameter)",
rule: "Host(``)",
expectedError: true,
},
{
desc: "invalid Host matcher (non-ASCII)",
rule: "Host(`🦭.com`)",
expectedError: true,
},
{
desc: "invalid Host matcher (too many parameters)",
rule: "Host(`example.com`, `example.org`)",
expectedError: true,
},
{
desc: "valid Host matcher",
rule: "Host(`example.com`)",
expected: map[string]int{
"https://example.com": http.StatusOK,
"https://example.com:8080": http.StatusOK,
"https://example.com/path": http.StatusOK,
"https://example.org": http.StatusNotFound,
"https://example.org/path": http.StatusNotFound,
},
},
{
desc: "valid Host matcher - matcher ending with a dot",
rule: "Host(`example.com.`)",
expected: map[string]int{
"https://example.com": http.StatusOK,
"https://example.com/path": http.StatusOK,
"https://example.org": http.StatusNotFound,
"https://example.org/path": http.StatusNotFound,
"https://example.com.": http.StatusOK,
"https://example.com./path": http.StatusOK,
"https://example.org.": http.StatusNotFound,
"https://example.org./path": http.StatusNotFound,
},
},
{
desc: "valid Host matcher - URL ending with a dot",
rule: "Host(`example.com`)",
expected: map[string]int{
"https://example.com.": http.StatusOK,
"https://example.com./path": http.StatusOK,
"https://example.org.": http.StatusNotFound,
"https://example.org./path": http.StatusNotFound,
},
},
{
desc: "valid Host matcher - matcher with UPPER case",
rule: "Host(`EXAMPLE.COM`)",
expected: map[string]int{
"https://example.com": http.StatusOK,
"https://example.com/path": http.StatusOK,
"https://example.org": http.StatusNotFound,
"https://example.org/path": http.StatusNotFound,
},
},
{
desc: "valid Host matcher - puny-coded emoji",
rule: "Host(`xn--9t9h.com`)",
expected: map[string]int{
"https://xn--9t9h.com": http.StatusOK,
"https://xn--9t9h.com/path": http.StatusOK,
"https://example.com": http.StatusNotFound,
"https://example.com/path": http.StatusNotFound,
// The request's sender must use puny-code.
"https://🦭.com": http.StatusNotFound,
},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
muxer, err := NewMuxer()
require.NoError(t, err)
err = muxer.AddRoute(test.rule, 0, handler)
if test.expectedError {
require.Error(t, err)
return
}
require.NoError(t, err)
// RequestDecorator is necessary for the Host matcher
reqHost := requestdecorator.New(nil)
results := make(map[string]int)
for calledURL := range test.expected {
w := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodGet, calledURL, http.NoBody)
reqHost.ServeHTTP(w, req, muxer.ServeHTTP)
results[calledURL] = w.Code
}
assert.Equal(t, test.expected, results)
})
}
}
func TestHostRegexpMatcher(t *testing.T) {
testCases := []struct {
desc string
rule string
expected map[string]int
expectedError bool
}{
{
desc: "invalid HostRegexp matcher (no parameter)",
rule: "HostRegexp()",
expectedError: true,
},
{
desc: "invalid HostRegexp matcher (empty parameter)",
rule: "HostRegexp(``)",
expectedError: true,
},
{
desc: "invalid HostRegexp matcher (non-ASCII)",
rule: "HostRegexp(`🦭.com`)",
expectedError: true,
},
{
desc: "invalid HostRegexp matcher (invalid regexp)",
rule: "HostRegexp(`(example.com`)",
expectedError: true,
},
{
desc: "invalid HostRegexp matcher (too many parameters)",
rule: "HostRegexp(`example.com`, `example.org`)",
expectedError: true,
},
{
desc: "valid HostRegexp matcher",
rule: "HostRegexp(`^[a-zA-Z-]+\\.com$`)",
expected: map[string]int{
"https://example.com": http.StatusOK,
"https://example.com:8080": http.StatusOK,
"https://example.com/path": http.StatusOK,
"https://example.org": http.StatusNotFound,
"https://example.org/path": http.StatusNotFound,
},
},
{
desc: "valid HostRegexp matcher with case sensitive regexp",
rule: "HostRegexp(`^[A-Z]+\\.com$`)",
expected: map[string]int{
"https://example.com": http.StatusNotFound,
"https://EXAMPLE.com": http.StatusNotFound,
"https://example.com/path": http.StatusNotFound,
"https://example.org": http.StatusNotFound,
"https://example.org/path": http.StatusNotFound,
},
},
{
desc: "valid HostRegexp matcher with Traefik v2 syntax",
rule: "HostRegexp(`{domain:[a-zA-Z-]+\\.com}`)",
expected: map[string]int{
"https://example.com": http.StatusNotFound,
"https://example.com/path": http.StatusNotFound,
"https://example.org": http.StatusNotFound,
"https://example.org/path": http.StatusNotFound,
},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
muxer, err := NewMuxer()
require.NoError(t, err)
err = muxer.AddRoute(test.rule, 0, handler)
if test.expectedError {
require.Error(t, err)
return
}
require.NoError(t, err)
// RequestDecorator is necessary for the HostRegexp matcher
reqHost := requestdecorator.New(nil)
results := make(map[string]int)
for calledURL := range test.expected {
w := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodGet, calledURL, http.NoBody)
reqHost.ServeHTTP(w, req, muxer.ServeHTTP)
results[calledURL] = w.Code
}
assert.Equal(t, test.expected, results)
})
}
}
func TestPathMatcher(t *testing.T) {
testCases := []struct {
desc string
rule string
expected map[string]int
expectedError bool
}{
{
desc: "invalid Path matcher (no parameter)",
rule: "Path()",
expectedError: true,
},
{
desc: "invalid Path matcher (empty parameter)",
rule: "Path(``)",
expectedError: true,
},
{
desc: "invalid Path matcher (no leading /)",
rule: "Path(`css`)",
expectedError: true,
},
{
desc: "invalid Path matcher (too many parameters)",
rule: "Path(`/css`, `/js`)",
expectedError: true,
},
{
desc: "valid Path matcher",
rule: "Path(`/css`)",
expected: map[string]int{
"https://example.com": http.StatusNotFound,
"https://example.com/html": http.StatusNotFound,
"https://example.org/css": http.StatusOK,
"https://example.com/css": http.StatusOK,
"https://example.com/css/": http.StatusNotFound,
"https://example.com/css/main.css": http.StatusNotFound,
},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
muxer, err := NewMuxer()
require.NoError(t, err)
err = muxer.AddRoute(test.rule, 0, handler)
if test.expectedError {
require.Error(t, err)
return
}
require.NoError(t, err)
results := make(map[string]int)
for calledURL := range test.expected {
w := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodGet, calledURL, http.NoBody)
muxer.ServeHTTP(w, req)
results[calledURL] = w.Code
}
assert.Equal(t, test.expected, results)
})
}
}
func TestPathRegexpMatcher(t *testing.T) {
testCases := []struct {
desc string
rule string
expected map[string]int
expectedError bool
}{
{
desc: "invalid PathRegexp matcher (no parameter)",
rule: "PathRegexp()",
expectedError: true,
},
{
desc: "invalid PathRegexp matcher (empty parameter)",
rule: "PathRegexp(``)",
expectedError: true,
},
{
desc: "invalid PathRegexp matcher (invalid regexp)",
rule: "PathRegexp(`/(css`)",
expectedError: true,
},
{
desc: "invalid PathRegexp matcher (too many parameters)",
rule: "PathRegexp(`/css`, `/js`)",
expectedError: true,
},
{
desc: "valid PathRegexp matcher",
rule: "PathRegexp(`^/(css|js)`)",
expected: map[string]int{
"https://example.com": http.StatusNotFound,
"https://example.com/html": http.StatusNotFound,
"https://example.org/css": http.StatusOK,
"https://example.com/CSS": http.StatusNotFound,
"https://example.com/css": http.StatusOK,
"https://example.com/css/": http.StatusOK,
"https://example.com/css/main.css": http.StatusOK,
"https://example.com/js": http.StatusOK,
"https://example.com/js/": http.StatusOK,
"https://example.com/js/main.js": http.StatusOK,
},
},
{
desc: "valid PathRegexp matcher with Traefik v2 syntax",
rule: `PathRegexp("/{path:(css|js)}")`,
expected: map[string]int{
"https://example.com": http.StatusNotFound,
"https://example.com/html": http.StatusNotFound,
"https://example.org/css": http.StatusNotFound,
"https://example.com/{path:css}": http.StatusOK,
"https://example.com/{path:css}/": http.StatusOK,
"https://example.com/%7Bpath:css%7D": http.StatusOK,
"https://example.com/%7Bpath:css%7D/": http.StatusOK,
"https://example.com/{path:js}": http.StatusOK,
"https://example.com/{path:js}/": http.StatusOK,
"https://example.com/%7Bpath:js%7D": http.StatusOK,
"https://example.com/%7Bpath:js%7D/": http.StatusOK,
},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
muxer, err := NewMuxer()
require.NoError(t, err)
err = muxer.AddRoute(test.rule, 0, handler)
if test.expectedError {
require.Error(t, err)
return
}
require.NoError(t, err)
results := make(map[string]int)
for calledURL := range test.expected {
w := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodGet, calledURL, http.NoBody)
muxer.ServeHTTP(w, req)
results[calledURL] = w.Code
}
assert.Equal(t, test.expected, results)
})
}
}
func TestPathPrefixMatcher(t *testing.T) {
testCases := []struct {
desc string
rule string
expected map[string]int
expectedError bool
}{
{
desc: "invalid PathPrefix matcher (no parameter)",
rule: "PathPrefix()",
expectedError: true,
},
{
desc: "invalid PathPrefix matcher (empty parameter)",
rule: "PathPrefix(``)",
expectedError: true,
},
{
desc: "invalid PathPrefix matcher (no leading /)",
rule: "PathPrefix(`css`)",
expectedError: true,
},
{
desc: "invalid PathPrefix matcher (too many parameters)",
rule: "PathPrefix(`/css`, `/js`)",
expectedError: true,
},
{
desc: "valid PathPrefix matcher",
rule: `PathPrefix("/css")`,
expected: map[string]int{
"https://example.com": http.StatusNotFound,
"https://example.com/html": http.StatusNotFound,
"https://example.org/css": http.StatusOK,
"https://example.com/css": http.StatusOK,
"https://example.com/css/": http.StatusOK,
"https://example.com/css/main.css": http.StatusOK,
},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
muxer, err := NewMuxer()
require.NoError(t, err)
err = muxer.AddRoute(test.rule, 0, handler)
if test.expectedError {
require.Error(t, err)
return
}
require.NoError(t, err)
results := make(map[string]int)
for calledURL := range test.expected {
w := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodGet, calledURL, http.NoBody)
muxer.ServeHTTP(w, req)
results[calledURL] = w.Code
}
assert.Equal(t, test.expected, results)
})
}
}
func TestHeaderMatcher(t *testing.T) {
testCases := []struct {
desc string
rule string
expected map[*http.Header]int
expectedError bool
}{
{
desc: "invalid Header matcher (no parameter)",
rule: "Header()",
expectedError: true,
},
{
desc: "invalid Header matcher (missing value parameter)",
rule: "Header(`X-Forwarded-Host`)",
expectedError: true,
},
{
desc: "invalid Header matcher (missing value parameter)",
rule: "Header(`X-Forwarded-Host`, ``)",
expectedError: true,
},
{
desc: "invalid Header matcher (missing key parameter)",
rule: "Header(``, `example.com`)",
expectedError: true,
},
{
desc: "invalid Header matcher (too many parameters)",
rule: "Header(`X-Forwarded-Host`, `example.com`, `example.org`)",
expectedError: true,
},
{
desc: "valid Header matcher",
rule: "Header(`X-Forwarded-Proto`, `https`)",
expected: map[*http.Header]int{
{"X-Forwarded-Proto": []string{"https"}}: http.StatusOK,
{"x-forwarded-proto": []string{"https"}}: http.StatusNotFound,
{"X-Forwarded-Proto": []string{"http", "https"}}: http.StatusOK,
{"X-Forwarded-Proto": []string{"https", "http"}}: http.StatusOK,
{"X-Forwarded-Host": []string{"example.com"}}: http.StatusNotFound,
},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
muxer, err := NewMuxer()
require.NoError(t, err)
err = muxer.AddRoute(test.rule, 0, handler)
if test.expectedError {
require.Error(t, err)
return
}
require.NoError(t, err)
for headers := range test.expected {
w := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodGet, "https://example.com", http.NoBody)
req.Header = *headers
muxer.ServeHTTP(w, req)
assert.Equal(t, test.expected[headers], w.Code, headers)
}
})
}
}
func TestHeaderRegexpMatcher(t *testing.T) {
testCases := []struct {
desc string
rule string
expected map[*http.Header]int
expectedError bool
}{
{
desc: "invalid HeaderRegexp matcher (no parameter)",
rule: "HeaderRegexp()",
expectedError: true,
},
{
desc: "invalid HeaderRegexp matcher (missing value parameter)",
rule: "HeaderRegexp(`X-Forwarded-Host`)",
expectedError: true,
},
{
desc: "invalid HeaderRegexp matcher (missing value parameter)",
rule: "HeaderRegexp(`X-Forwarded-Host`, ``)",
expectedError: true,
},
{
desc: "invalid HeaderRegexp matcher (missing key parameter)",
rule: "HeaderRegexp(``, `example.com`)",
expectedError: true,
},
{
desc: "invalid HeaderRegexp matcher (invalid regexp)",
rule: "HeaderRegexp(`X-Forwarded-Host`,`(example.com`)",
expectedError: true,
},
{
desc: "invalid HeaderRegexp matcher (too many parameters)",
rule: "HeaderRegexp(`X-Forwarded-Host`, `example.com`, `example.org`)",
expectedError: true,
},
{
desc: "valid HeaderRegexp matcher",
rule: "HeaderRegexp(`X-Forwarded-Proto`, `^https?$`)",
expected: map[*http.Header]int{
{"X-Forwarded-Proto": []string{"http"}}: http.StatusOK,
{"x-forwarded-proto": []string{"http"}}: http.StatusNotFound,
{"X-Forwarded-Proto": []string{"https"}}: http.StatusOK,
{"X-Forwarded-Proto": []string{"HTTPS"}}: http.StatusNotFound,
{"X-Forwarded-Proto": []string{"ws", "https"}}: http.StatusOK,
{"X-Forwarded-Host": []string{"example.com"}}: http.StatusNotFound,
},
},
{
desc: "valid HeaderRegexp matcher with Traefik v2 syntax",
rule: "HeaderRegexp(`X-Forwarded-Proto`, `http{secure:s?}`)",
expected: map[*http.Header]int{
{"X-Forwarded-Proto": []string{"http"}}: http.StatusNotFound,
{"X-Forwarded-Proto": []string{"https"}}: http.StatusNotFound,
{"X-Forwarded-Proto": []string{"http{secure:}"}}: http.StatusOK,
{"X-Forwarded-Proto": []string{"HTTP{secure:}"}}: http.StatusNotFound,
{"X-Forwarded-Proto": []string{"http{secure:s}"}}: http.StatusOK,
{"X-Forwarded-Proto": []string{"http{secure:S}"}}: http.StatusNotFound,
{"X-Forwarded-Proto": []string{"HTTPS"}}: http.StatusNotFound,
{"X-Forwarded-Proto": []string{"ws", "http{secure:s}"}}: http.StatusOK,
{"X-Forwarded-Host": []string{"example.com"}}: http.StatusNotFound,
},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
muxer, err := NewMuxer()
require.NoError(t, err)
err = muxer.AddRoute(test.rule, 0, handler)
if test.expectedError {
require.Error(t, err)
return
}
require.NoError(t, err)
for headers := range test.expected {
w := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodGet, "https://example.com", http.NoBody)
req.Header = *headers
muxer.ServeHTTP(w, req)
assert.Equal(t, test.expected[headers], w.Code, *headers)
}
})
}
}
func TestQueryMatcher(t *testing.T) {
testCases := []struct {
desc string
rule string
expected map[string]int
expectedError bool
}{
{
desc: "invalid Query matcher (no parameter)",
rule: "Query()",
expectedError: true,
},
{
desc: "invalid Query matcher (empty key, one parameter)",
rule: "Query(``)",
expectedError: true,
},
{
desc: "invalid Query matcher (empty key)",
rule: "Query(``, `traefik`)",
expectedError: true,
},
{
desc: "invalid Query matcher (empty value)",
rule: "Query(`q`, ``)",
expectedError: true,
},
{
desc: "invalid Query matcher (too many parameters)",
rule: "Query(`q`, `traefik`, `proxy`)",
expectedError: true,
},
{
desc: "valid Query matcher",
rule: "Query(`q`, `traefik`)",
expected: map[string]int{
"https://example.com": http.StatusNotFound,
"https://example.com?q=traefik": http.StatusOK,
"https://example.com?rel=ddg&q=traefik": http.StatusOK,
"https://example.com?q=traefik&q=proxy": http.StatusOK,
"https://example.com?q=awesome&q=traefik": http.StatusOK,
"https://example.com?q=nginx": http.StatusNotFound,
"https://example.com?rel=ddg": http.StatusNotFound,
"https://example.com?q=TRAEFIK": http.StatusNotFound,
"https://example.com?Q=traefik": http.StatusNotFound,
"https://example.com?rel=traefik": http.StatusNotFound,
},
},
{
desc: "valid Query matcher with empty value",
rule: "Query(`mobile`)",
expected: map[string]int{
"https://example.com": http.StatusNotFound,
"https://example.com?mobile": http.StatusOK,
"https://example.com?mobile=true": http.StatusNotFound,
},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
muxer, err := NewMuxer()
require.NoError(t, err)
err = muxer.AddRoute(test.rule, 0, handler)
if test.expectedError {
require.Error(t, err)
return
}
require.NoError(t, err)
results := make(map[string]int)
for calledURL := range test.expected {
w := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodGet, calledURL, http.NoBody)
muxer.ServeHTTP(w, req)
results[calledURL] = w.Code
}
assert.Equal(t, test.expected, results)
})
}
}
func TestQueryRegexpMatcher(t *testing.T) {
testCases := []struct {
desc string
rule string
expected map[string]int
expectedError bool
}{
{
desc: "invalid QueryRegexp matcher (no parameter)",
rule: "QueryRegexp()",
expectedError: true,
},
{
desc: "invalid QueryRegexp matcher (empty parameter)",
rule: "QueryRegexp(``)",
expectedError: true,
},
{
desc: "invalid QueryRegexp matcher (invalid regexp)",
rule: "QueryRegexp(`q`, `(traefik`)",
expectedError: true,
},
{
desc: "invalid QueryRegexp matcher (too many parameters)",
rule: "QueryRegexp(`q`, `traefik`, `proxy`)",
expectedError: true,
},
{
desc: "valid QueryRegexp matcher",
rule: "QueryRegexp(`q`, `^(traefik|nginx)$`)",
expected: map[string]int{
"https://example.com": http.StatusNotFound,
"https://example.com?q=traefik": http.StatusOK,
"https://example.com?rel=ddg&q=traefik": http.StatusOK,
"https://example.com?q=traefik&q=proxy": http.StatusOK,
"https://example.com?q=awesome&q=traefik": http.StatusOK,
"https://example.com?q=TRAEFIK": http.StatusNotFound,
"https://example.com?Q=traefik": http.StatusNotFound,
"https://example.com?rel=traefik": http.StatusNotFound,
"https://example.com?q=nginx": http.StatusOK,
"https://example.com?rel=ddg&q=nginx": http.StatusOK,
"https://example.com?q=nginx&q=proxy": http.StatusOK,
"https://example.com?q=awesome&q=nginx": http.StatusOK,
"https://example.com?q=NGINX": http.StatusNotFound,
"https://example.com?Q=nginx": http.StatusNotFound,
"https://example.com?rel=nginx": http.StatusNotFound,
"https://example.com?q=haproxy": http.StatusNotFound,
"https://example.com?rel=ddg": http.StatusNotFound,
},
},
{
desc: "valid QueryRegexp matcher",
rule: "QueryRegexp(`q`, `^.*$`)",
expected: map[string]int{
"https://example.com": http.StatusNotFound,
"https://example.com?q=traefik": http.StatusOK,
"https://example.com?rel=ddg&q=traefik": http.StatusOK,
"https://example.com?q=traefik&q=proxy": http.StatusOK,
"https://example.com?q=awesome&q=traefik": http.StatusOK,
"https://example.com?q=TRAEFIK": http.StatusOK,
"https://example.com?Q=traefik": http.StatusNotFound,
"https://example.com?rel=traefik": http.StatusNotFound,
"https://example.com?q=nginx": http.StatusOK,
"https://example.com?rel=ddg&q=nginx": http.StatusOK,
"https://example.com?q=nginx&q=proxy": http.StatusOK,
"https://example.com?q=awesome&q=nginx": http.StatusOK,
"https://example.com?q=NGINX": http.StatusOK,
"https://example.com?Q=nginx": http.StatusNotFound,
"https://example.com?rel=nginx": http.StatusNotFound,
"https://example.com?q=haproxy": http.StatusOK,
"https://example.com?rel=ddg": http.StatusNotFound,
},
},
{
desc: "valid QueryRegexp matcher with Traefik v2 syntax",
rule: "QueryRegexp(`q`, `{value:(traefik|nginx)}`)",
expected: map[string]int{
"https://example.com?q=traefik": http.StatusNotFound,
"https://example.com?q={value:traefik}": http.StatusOK,
},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
muxer, err := NewMuxer()
require.NoError(t, err)
err = muxer.AddRoute(test.rule, 0, handler)
if test.expectedError {
require.Error(t, err)
return
}
require.NoError(t, err)
results := make(map[string]int)
for calledURL := range test.expected {
w := httptest.NewRecorder()
req := httptest.NewRequest(http.MethodGet, calledURL, http.NoBody)
muxer.ServeHTTP(w, req)
results[calledURL] = w.Code
}
assert.Equal(t, test.expected, results)
})
}
}

View file

@ -3,32 +3,12 @@ package http
import (
"fmt"
"net/http"
"strings"
"unicode/utf8"
"github.com/gorilla/mux"
"github.com/rs/zerolog/log"
"github.com/traefik/traefik/v2/pkg/ip"
"github.com/traefik/traefik/v2/pkg/middlewares/requestdecorator"
"github.com/traefik/traefik/v2/pkg/rules"
"github.com/vulcand/predicate"
)
const hostMatcher = "Host"
var httpFuncs = map[string]func(*mux.Route, ...string) error{
hostMatcher: host,
"HostHeader": host,
"HostRegexp": hostRegexp,
"ClientIP": clientIP,
"Path": path,
"PathPrefix": pathPrefix,
"Method": methods,
"Headers": headers,
"HeadersRegexp": headersRegexp,
"Query": query,
}
// Muxer handles routing with rules.
type Muxer struct {
*mux.Router
@ -80,171 +60,6 @@ func (r *Muxer) AddRoute(rule string, priority int, handler http.Handler) error
return nil
}
// ParseDomains extract domains from rule.
func ParseDomains(rule string) ([]string, error) {
var matchers []string
for matcher := range httpFuncs {
matchers = append(matchers, matcher)
}
parser, err := rules.NewParser(matchers)
if err != nil {
return nil, err
}
parse, err := parser.Parse(rule)
if err != nil {
return nil, err
}
buildTree, ok := parse.(rules.TreeBuilder)
if !ok {
return nil, fmt.Errorf("error while parsing rule %s", rule)
}
return buildTree().ParseMatchers([]string{hostMatcher}), nil
}
func path(route *mux.Route, paths ...string) error {
rt := route.Subrouter()
for _, path := range paths {
if err := rt.Path(path).GetError(); err != nil {
return err
}
}
return nil
}
func pathPrefix(route *mux.Route, paths ...string) error {
rt := route.Subrouter()
for _, path := range paths {
if err := rt.PathPrefix(path).GetError(); err != nil {
return err
}
}
return nil
}
func host(route *mux.Route, hosts ...string) error {
for i, host := range hosts {
if !IsASCII(host) {
return fmt.Errorf("invalid value %q for \"Host\" matcher, non-ASCII characters are not allowed", host)
}
hosts[i] = strings.ToLower(host)
}
route.MatcherFunc(func(req *http.Request, _ *mux.RouteMatch) bool {
reqHost := requestdecorator.GetCanonizedHost(req.Context())
if len(reqHost) == 0 {
// If the request is an HTTP/1.0 request, then a Host may not be defined.
if req.ProtoAtLeast(1, 1) {
log.Ctx(req.Context()).Warn().Msgf("Could not retrieve CanonizedHost, rejecting %s", req.Host)
}
return false
}
flatH := requestdecorator.GetCNAMEFlatten(req.Context())
if len(flatH) > 0 {
for _, host := range hosts {
if strings.EqualFold(reqHost, host) || strings.EqualFold(flatH, host) {
return true
}
log.Ctx(req.Context()).Debug().Msgf("CNAMEFlattening: request %s which resolved to %s, is not matched to route %s", reqHost, flatH, host)
}
return false
}
for _, host := range hosts {
if reqHost == host {
return true
}
// Check for match on trailing period on host
if last := len(host) - 1; last >= 0 && host[last] == '.' {
h := host[:last]
if reqHost == h {
return true
}
}
// Check for match on trailing period on request
if last := len(reqHost) - 1; last >= 0 && reqHost[last] == '.' {
h := reqHost[:last]
if h == host {
return true
}
}
}
return false
})
return nil
}
func clientIP(route *mux.Route, clientIPs ...string) error {
checker, err := ip.NewChecker(clientIPs)
if err != nil {
return fmt.Errorf("could not initialize IP Checker for \"ClientIP\" matcher: %w", err)
}
strategy := ip.RemoteAddrStrategy{}
route.MatcherFunc(func(req *http.Request, _ *mux.RouteMatch) bool {
ok, err := checker.Contains(strategy.GetIP(req))
if err != nil {
log.Ctx(req.Context()).Warn().Err(err).Msg("\"ClientIP\" matcher: could not match remote address")
return false
}
return ok
})
return nil
}
func hostRegexp(route *mux.Route, hosts ...string) error {
router := route.Subrouter()
for _, host := range hosts {
if !IsASCII(host) {
return fmt.Errorf("invalid value %q for HostRegexp matcher, non-ASCII characters are not allowed", host)
}
tmpRt := router.Host(host)
if tmpRt.GetError() != nil {
return tmpRt.GetError()
}
}
return nil
}
func methods(route *mux.Route, methods ...string) error {
return route.Methods(methods...).GetError()
}
func headers(route *mux.Route, headers ...string) error {
return route.Headers(headers...).GetError()
}
func headersRegexp(route *mux.Route, headers ...string) error {
return route.HeadersRegexp(headers...).GetError()
}
func query(route *mux.Route, query ...string) error {
var queries []string
for _, elem := range query {
queries = append(queries, strings.SplitN(elem, "=", 2)...)
}
route.Queries(queries...)
// Queries can return nil so we can't chain the GetError()
return route.GetError()
}
func addRuleOnRouter(router *mux.Router, rule *rules.Tree) error {
switch rule.Matcher {
case "and":
@ -276,20 +91,6 @@ func addRuleOnRouter(router *mux.Router, rule *rules.Tree) error {
}
}
func not(m func(*mux.Route, ...string) error) func(*mux.Route, ...string) error {
return func(r *mux.Route, v ...string) error {
router := mux.NewRouter()
err := m(router.NewRoute(), v...)
if err != nil {
return err
}
r.MatcherFunc(func(req *http.Request, ma *mux.RouteMatch) bool {
return !router.Match(req, ma)
})
return nil
}
}
func addRuleOnRoute(route *mux.Route, rule *rules.Tree) error {
switch rule.Matcher {
case "and":
@ -322,13 +123,44 @@ func addRuleOnRoute(route *mux.Route, rule *rules.Tree) error {
}
}
// IsASCII checks if the given string contains only ASCII characters.
func IsASCII(s string) bool {
for i := 0; i < len(s); i++ {
if s[i] >= utf8.RuneSelf {
return false
func not(m func(*mux.Route, ...string) error) func(*mux.Route, ...string) error {
return func(r *mux.Route, v ...string) error {
router := mux.NewRouter()
err := m(router.NewRoute(), v...)
if err != nil {
return err
}
r.MatcherFunc(func(req *http.Request, ma *mux.RouteMatch) bool {
return !router.Match(req, ma)
})
return nil
}
}
return true
// ParseDomains extract domains from rule.
func ParseDomains(rule string) ([]string, error) {
var matchers []string
for matcher := range httpFuncs {
matchers = append(matchers, matcher)
}
parser, err := rules.NewParser(matchers)
if err != nil {
return nil, err
}
parse, err := parser.Parse(rule)
if err != nil {
return nil, err
}
buildTree, ok := parse.(rules.TreeBuilder)
if !ok {
return nil, fmt.Errorf("error while parsing rule %s", rule)
}
return buildTree().ParseMatchers([]string{"Host"}), nil
}

View file

@ -7,14 +7,13 @@ import (
"net/http/httptest"
"testing"
"github.com/gorilla/mux"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/traefik/traefik/v2/pkg/middlewares/requestdecorator"
"github.com/traefik/traefik/v2/pkg/testhelpers"
)
func Test_addRoute(t *testing.T) {
func TestMuxer(t *testing.T) {
testCases := []struct {
desc string
rule string
@ -33,607 +32,177 @@ func Test_addRoute(t *testing.T) {
expectedError: true,
},
{
desc: "Host empty",
rule: "Host(``)",
desc: "Rule without quote",
rule: "Host(example.com)",
expectedError: true,
},
{
desc: "PathPrefix empty",
rule: "PathPrefix(``)",
expectedError: true,
},
{
desc: "PathPrefix",
rule: "PathPrefix(`/foo`)",
expected: map[string]int{
"http://localhost/foo": http.StatusOK,
},
},
{
desc: "wrong PathPrefix",
rule: "PathPrefix(`/bar`)",
expected: map[string]int{
"http://localhost/foo": http.StatusNotFound,
},
},
{
desc: "Host",
rule: "Host(`localhost`)",
expected: map[string]int{
"http://localhost/foo": http.StatusOK,
},
},
{
desc: "Non-ASCII Host",
rule: "Host(`locàlhost`)",
expectedError: true,
},
{
desc: "Non-ASCII HostRegexp",
rule: "HostRegexp(`locàlhost`)",
expectedError: true,
},
{
desc: "HostHeader equivalent to Host",
rule: "HostHeader(`localhost`)",
expected: map[string]int{
"http://localhost/foo": http.StatusOK,
"http://bar/foo": http.StatusNotFound,
},
},
{
desc: "Host with trailing period in rule",
rule: "Host(`localhost.`)",
expected: map[string]int{
"http://localhost/foo": http.StatusOK,
},
},
{
desc: "Host with trailing period in domain",
rule: "Host(`localhost`)",
expected: map[string]int{
"http://localhost./foo": http.StatusOK,
},
},
{
desc: "Host with trailing period in domain and rule",
rule: "Host(`localhost.`)",
expected: map[string]int{
"http://localhost./foo": http.StatusOK,
},
},
{
desc: "wrong Host",
rule: "Host(`nope`)",
expected: map[string]int{
"http://localhost/foo": http.StatusNotFound,
},
},
{
desc: "Host and PathPrefix",
rule: "Host(`localhost`) && PathPrefix(`/foo`)",
rule: "Host(`localhost`) && PathPrefix(`/css`)",
expected: map[string]int{
"http://localhost/foo": http.StatusOK,
},
},
{
desc: "Host and PathPrefix wrong PathPrefix",
rule: "Host(`localhost`) && PathPrefix(`/bar`)",
expected: map[string]int{
"http://localhost/foo": http.StatusNotFound,
},
},
{
desc: "Host and PathPrefix wrong Host",
rule: "Host(`nope`) && PathPrefix(`/foo`)",
expected: map[string]int{
"http://localhost/foo": http.StatusNotFound,
},
},
{
desc: "Host and PathPrefix Host OR, first host",
rule: "Host(`nope`,`localhost`) && PathPrefix(`/foo`)",
expected: map[string]int{
"http://localhost/foo": http.StatusOK,
},
},
{
desc: "Host and PathPrefix Host OR, second host",
rule: "Host(`nope`,`localhost`) && PathPrefix(`/foo`)",
expected: map[string]int{
"http://nope/foo": http.StatusOK,
},
},
{
desc: "Host and PathPrefix Host OR, first host and wrong PathPrefix",
rule: "Host(`nope,localhost`) && PathPrefix(`/bar`)",
expected: map[string]int{
"http://localhost/foo": http.StatusNotFound,
},
},
{
desc: "HostRegexp with capturing group",
rule: "HostRegexp(`{subdomain:(foo\\.)?bar\\.com}`)",
expected: map[string]int{
"http://foo.bar.com": http.StatusOK,
"http://bar.com": http.StatusOK,
"http://fooubar.com": http.StatusNotFound,
"http://barucom": http.StatusNotFound,
"http://barcom": http.StatusNotFound,
},
},
{
desc: "HostRegexp with non capturing group",
rule: "HostRegexp(`{subdomain:(?:foo\\.)?bar\\.com}`)",
expected: map[string]int{
"http://foo.bar.com": http.StatusOK,
"http://bar.com": http.StatusOK,
"http://fooubar.com": http.StatusNotFound,
"http://barucom": http.StatusNotFound,
"http://barcom": http.StatusNotFound,
},
},
{
desc: "Methods with GET",
rule: "Method(`GET`)",
expected: map[string]int{
"http://localhost/foo": http.StatusOK,
},
},
{
desc: "Methods with GET and POST",
rule: "Method(`GET`,`POST`)",
expected: map[string]int{
"http://localhost/foo": http.StatusOK,
},
},
{
desc: "Methods with POST",
rule: "Method(`POST`)",
expected: map[string]int{
"http://localhost/foo": http.StatusMethodNotAllowed,
},
},
{
desc: "Header with matching header",
rule: "Headers(`Content-Type`,`application/json`)",
headers: map[string]string{
"Content-Type": "application/json",
},
expected: map[string]int{
"http://localhost/foo": http.StatusOK,
},
},
{
desc: "Header without matching header",
rule: "Headers(`Content-Type`,`application/foo`)",
headers: map[string]string{
"Content-Type": "application/json",
},
expected: map[string]int{
"http://localhost/foo": http.StatusNotFound,
},
},
{
desc: "HeaderRegExp with matching header",
rule: "HeadersRegexp(`Content-Type`, `application/(text|json)`)",
headers: map[string]string{
"Content-Type": "application/json",
},
expected: map[string]int{
"http://localhost/foo": http.StatusOK,
},
},
{
desc: "HeaderRegExp without matching header",
rule: "HeadersRegexp(`Content-Type`, `application/(text|json)`)",
headers: map[string]string{
"Content-Type": "application/foo",
},
expected: map[string]int{
"http://localhost/foo": http.StatusNotFound,
},
},
{
desc: "HeaderRegExp with matching second header",
rule: "HeadersRegexp(`Content-Type`, `application/(text|json)`)",
headers: map[string]string{
"Content-Type": "application/text",
},
expected: map[string]int{
"http://localhost/foo": http.StatusOK,
},
},
{
desc: "Query with multiple params",
rule: "Query(`foo=bar`, `bar=baz`)",
expected: map[string]int{
"http://localhost/foo?foo=bar&bar=baz": http.StatusOK,
"http://localhost/foo?bar=baz": http.StatusNotFound,
},
},
{
desc: "Query with multiple equals",
rule: "Query(`foo=b=ar`)",
expected: map[string]int{
"http://localhost/foo?foo=b=ar": http.StatusOK,
"http://localhost/foo?foo=bar": http.StatusNotFound,
},
},
{
desc: "Rule with simple path",
rule: `Path("/a")`,
expected: map[string]int{
"http://plop/a": http.StatusOK,
},
},
{
desc: `Rule with a simple host`,
rule: `Host("plop")`,
expected: map[string]int{
"http://plop": http.StatusOK,
},
},
{
desc: "Rule with Path AND Host",
rule: `Path("/a") && Host("plop")`,
expected: map[string]int{
"http://plop/a": http.StatusOK,
"http://plopi/a": http.StatusNotFound,
"https://localhost/css": http.StatusOK,
"https://localhost/js": http.StatusNotFound,
},
},
{
desc: "Rule with Host OR Host",
rule: `Host("tchouk") || Host("pouet")`,
rule: "Host(`example.com`) || Host(`example.org`)",
expected: map[string]int{
"http://tchouk/toto": http.StatusOK,
"http://pouet/a": http.StatusOK,
"http://plopi/a": http.StatusNotFound,
"https://example.com/css": http.StatusOK,
"https://example.org/js": http.StatusOK,
"https://example.eu/html": http.StatusNotFound,
},
},
{
desc: "Rule with host OR (host AND path)",
rule: `Host("tchouk") || (Host("pouet") && Path("/powpow"))`,
rule: `Host("example.com") || (Host("example.org") && Path("/css"))`,
expected: map[string]int{
"http://tchouk/toto": http.StatusOK,
"http://tchouk/powpow": http.StatusOK,
"http://pouet/powpow": http.StatusOK,
"http://pouet/toto": http.StatusNotFound,
"http://plopi/a": http.StatusNotFound,
"https://example.com/css": http.StatusOK,
"https://example.com/js": http.StatusOK,
"https://example.org/css": http.StatusOK,
"https://example.org/js": http.StatusNotFound,
"https://example.eu/css": http.StatusNotFound,
},
},
{
desc: "Rule with host OR host AND path",
rule: `Host("tchouk") || Host("pouet") && Path("/powpow")`,
rule: `Host("example.com") || Host("example.org") && Path("/css")`,
expected: map[string]int{
"http://tchouk/toto": http.StatusOK,
"http://tchouk/powpow": http.StatusOK,
"http://pouet/powpow": http.StatusOK,
"http://pouet/toto": http.StatusNotFound,
"http://plopi/a": http.StatusNotFound,
"https://example.com/css": http.StatusOK,
"https://example.com/js": http.StatusOK,
"https://example.org/css": http.StatusOK,
"https://example.org/js": http.StatusNotFound,
"https://example.eu/css": http.StatusNotFound,
},
},
{
desc: "Rule with (host OR host) AND path",
rule: `(Host("tchouk") || Host("pouet")) && Path("/powpow")`,
rule: `(Host("example.com") || Host("example.org")) && Path("/css")`,
expected: map[string]int{
"http://tchouk/toto": http.StatusNotFound,
"http://tchouk/powpow": http.StatusOK,
"http://pouet/powpow": http.StatusOK,
"http://pouet/toto": http.StatusNotFound,
"http://plopi/a": http.StatusNotFound,
},
},
{
desc: "Rule with multiple host AND path",
rule: `(Host("tchouk","pouet")) && Path("/powpow")`,
expected: map[string]int{
"http://tchouk/toto": http.StatusNotFound,
"http://tchouk/powpow": http.StatusOK,
"http://pouet/powpow": http.StatusOK,
"http://pouet/toto": http.StatusNotFound,
"http://plopi/a": http.StatusNotFound,
},
},
{
desc: "Rule with multiple host AND multiple path",
rule: `Host("tchouk","pouet") && Path("/powpow", "/titi")`,
expected: map[string]int{
"http://tchouk/toto": http.StatusNotFound,
"http://tchouk/powpow": http.StatusOK,
"http://pouet/powpow": http.StatusOK,
"http://tchouk/titi": http.StatusOK,
"http://pouet/titi": http.StatusOK,
"http://pouet/toto": http.StatusNotFound,
"http://plopi/a": http.StatusNotFound,
"https://example.com/css": http.StatusOK,
"https://example.com/js": http.StatusNotFound,
"https://example.org/css": http.StatusOK,
"https://example.org/js": http.StatusNotFound,
"https://example.eu/css": http.StatusNotFound,
},
},
{
desc: "Rule with (host AND path) OR (host AND path)",
rule: `(Host("tchouk") && Path("/titi")) || ((Host("pouet")) && Path("/powpow"))`,
rule: `(Host("example.com") && Path("/js")) || ((Host("example.org")) && Path("/css"))`,
expected: map[string]int{
"http://tchouk/titi": http.StatusOK,
"http://tchouk/powpow": http.StatusNotFound,
"http://pouet/powpow": http.StatusOK,
"http://pouet/toto": http.StatusNotFound,
"http://plopi/a": http.StatusNotFound,
"https://example.com/css": http.StatusNotFound,
"https://example.com/js": http.StatusOK,
"https://example.org/css": http.StatusOK,
"https://example.org/js": http.StatusNotFound,
"https://example.eu/css": http.StatusNotFound,
},
},
{
desc: "Rule without quote",
rule: `Host(tchouk)`,
expectedError: true,
},
{
desc: "Rule case UPPER",
rule: `(HOST("tchouk") && PATHPREFIX("/titi"))`,
rule: `PATHPREFIX("/css")`,
expected: map[string]int{
"http://tchouk/titi": http.StatusOK,
"http://tchouk/powpow": http.StatusNotFound,
"https://example.com/css": http.StatusOK,
"https://example.com/js": http.StatusNotFound,
},
},
{
desc: "Rule case lower",
rule: `(host("tchouk") && pathprefix("/titi"))`,
rule: `pathprefix("/css")`,
expected: map[string]int{
"http://tchouk/titi": http.StatusOK,
"http://tchouk/powpow": http.StatusNotFound,
"https://example.com/css": http.StatusOK,
"https://example.com/js": http.StatusNotFound,
},
},
{
desc: "Rule case CamelCase",
rule: `(Host("tchouk") && PathPrefix("/titi"))`,
rule: `PathPrefix("/css")`,
expected: map[string]int{
"http://tchouk/titi": http.StatusOK,
"http://tchouk/powpow": http.StatusNotFound,
"https://example.com/css": http.StatusOK,
"https://example.com/js": http.StatusNotFound,
},
},
{
desc: "Rule case Title",
rule: `(Host("tchouk") && Pathprefix("/titi"))`,
rule: `Pathprefix("/css")`,
expected: map[string]int{
"http://tchouk/titi": http.StatusOK,
"http://tchouk/powpow": http.StatusNotFound,
"https://example.com/css": http.StatusOK,
"https://example.com/js": http.StatusNotFound,
},
},
{
desc: "Rule Path with error",
rule: `Path("titi")`,
expectedError: true,
},
{
desc: "Rule PathPrefix with error",
rule: `PathPrefix("titi")`,
expectedError: true,
},
{
desc: "Rule HostRegexp with error",
rule: `HostRegexp("{test")`,
expectedError: true,
},
{
desc: "Rule Headers with error",
rule: `Headers("titi")`,
expectedError: true,
},
{
desc: "Rule HeadersRegexp with error",
rule: `HeadersRegexp("titi")`,
expectedError: true,
},
{
desc: "Rule Query",
rule: `Query("titi")`,
expectedError: true,
},
{
desc: "Rule Query with bad syntax",
rule: `Query("titi={test")`,
expectedError: true,
},
{
desc: "Rule with Path without args",
rule: `Host("tchouk") && Path()`,
expectedError: true,
},
{
desc: "Rule with an empty path",
rule: `Host("tchouk") && Path("")`,
expectedError: true,
},
{
desc: "Rule with an empty path",
rule: `Host("tchouk") && Path("", "/titi")`,
expectedError: true,
},
{
desc: "Rule with not",
rule: `!Host("tchouk")`,
rule: `!Host("example.com")`,
expected: map[string]int{
"http://tchouk/titi": http.StatusNotFound,
"http://test/powpow": http.StatusOK,
},
},
{
desc: "Rule with not on Path",
rule: `!Path("/titi")`,
expected: map[string]int{
"http://tchouk/titi": http.StatusNotFound,
"http://tchouk/powpow": http.StatusOK,
"https://example.org": http.StatusOK,
"https://example.com": http.StatusNotFound,
},
},
{
desc: "Rule with not on multiple route with or",
rule: `!(Host("tchouk") || Host("toto"))`,
rule: `!(Host("example.com") || Host("example.org"))`,
expected: map[string]int{
"http://tchouk/titi": http.StatusNotFound,
"http://toto/powpow": http.StatusNotFound,
"http://test/powpow": http.StatusOK,
"https://example.eu/js": http.StatusOK,
"https://example.com/css": http.StatusNotFound,
"https://example.org/js": http.StatusNotFound,
},
},
{
desc: "Rule with not on multiple route with and",
rule: `!(Host("tchouk") && Path("/titi"))`,
rule: `!(Host("example.com") && Path("/css"))`,
expected: map[string]int{
"http://tchouk/titi": http.StatusNotFound,
"http://tchouk/toto": http.StatusOK,
"http://test/titi": http.StatusOK,
"https://example.com/js": http.StatusOK,
"https://example.eu/css": http.StatusOK,
"https://example.com/css": http.StatusNotFound,
},
},
{
desc: "Rule with not on multiple route with and another not",
rule: `!(Host("tchouk") && !Path("/titi"))`,
rule: `!(Host("example.com") && !Path("/css"))`,
expected: map[string]int{
"http://tchouk/titi": http.StatusOK,
"http://toto/titi": http.StatusOK,
"http://tchouk/toto": http.StatusNotFound,
"https://example.com/css": http.StatusOK,
"https://example.org/css": http.StatusOK,
"https://example.com/js": http.StatusNotFound,
},
},
{
desc: "Rule with not on two rule",
rule: `!Host("tchouk") || !Path("/titi")`,
rule: `!Host("example.com") || !Path("/css")`,
expected: map[string]int{
"http://tchouk/titi": http.StatusNotFound,
"http://tchouk/toto": http.StatusOK,
"http://test/titi": http.StatusOK,
"https://example.com/js": http.StatusOK,
"https://example.org/css": http.StatusOK,
"https://example.com/css": http.StatusNotFound,
},
},
{
desc: "Rule case with double not",
rule: `!(!(Host("tchouk") && Pathprefix("/titi")))`,
rule: `!(!(Host("example.com") && Pathprefix("/css")))`,
expected: map[string]int{
"http://tchouk/titi": http.StatusOK,
"http://tchouk/powpow": http.StatusNotFound,
"http://test/titi": http.StatusNotFound,
"https://example.com/css": http.StatusOK,
"https://example.com/js": http.StatusNotFound,
"https://example.org/css": http.StatusNotFound,
},
},
{
desc: "Rule case with not domain",
rule: `!Host("tchouk") && Pathprefix("/titi")`,
rule: `!Host("example.com") && Pathprefix("/css")`,
expected: map[string]int{
"http://tchouk/titi": http.StatusNotFound,
"http://tchouk/powpow": http.StatusNotFound,
"http://toto/powpow": http.StatusNotFound,
"http://toto/titi": http.StatusOK,
"https://example.org/css": http.StatusOK,
"https://example.org/js": http.StatusNotFound,
"https://example.com/css": http.StatusNotFound,
"https://example.com/js": http.StatusNotFound,
},
},
{
desc: "Rule with multiple host AND multiple path AND not",
rule: `!(Host("tchouk","pouet") && Path("/powpow", "/titi"))`,
rule: `!(Host("example.com") && Path("/js"))`,
expected: map[string]int{
"http://tchouk/toto": http.StatusOK,
"http://tchouk/powpow": http.StatusNotFound,
"http://pouet/powpow": http.StatusNotFound,
"http://tchouk/titi": http.StatusNotFound,
"http://pouet/titi": http.StatusNotFound,
"http://pouet/toto": http.StatusOK,
"http://plopi/a": http.StatusOK,
},
},
{
desc: "ClientIP empty",
rule: "ClientIP(``)",
expectedError: true,
},
{
desc: "Invalid ClientIP",
rule: "ClientIP(`invalid`)",
expectedError: true,
},
{
desc: "Non matching ClientIP",
rule: "ClientIP(`10.10.1.1`)",
remoteAddr: "10.0.0.0",
expected: map[string]int{
"http://tchouk/toto": http.StatusNotFound,
},
},
{
desc: "Non matching IPv6",
rule: "ClientIP(`10::10`)",
remoteAddr: "::1",
expected: map[string]int{
"http://tchouk/toto": http.StatusNotFound,
},
},
{
desc: "Matching IP",
rule: "ClientIP(`10.0.0.0`)",
remoteAddr: "10.0.0.0:8456",
expected: map[string]int{
"http://tchouk/toto": http.StatusOK,
},
},
{
desc: "Matching IPv6",
rule: "ClientIP(`10::10`)",
remoteAddr: "10::10",
expected: map[string]int{
"http://tchouk/toto": http.StatusOK,
},
},
{
desc: "Matching IP among several IP",
rule: "ClientIP(`10.0.0.1`, `10.0.0.0`)",
remoteAddr: "10.0.0.0",
expected: map[string]int{
"http://tchouk/toto": http.StatusOK,
},
},
{
desc: "Non Matching IP with CIDR",
rule: "ClientIP(`11.0.0.0/24`)",
remoteAddr: "10.0.0.0",
expected: map[string]int{
"http://tchouk/toto": http.StatusNotFound,
},
},
{
desc: "Non Matching IPv6 with CIDR",
rule: "ClientIP(`11::/16`)",
remoteAddr: "10::",
expected: map[string]int{
"http://tchouk/toto": http.StatusNotFound,
},
},
{
desc: "Matching IP with CIDR",
rule: "ClientIP(`10.0.0.0/16`)",
remoteAddr: "10.0.0.0",
expected: map[string]int{
"http://tchouk/toto": http.StatusOK,
},
},
{
desc: "Matching IPv6 with CIDR",
rule: "ClientIP(`10::/16`)",
remoteAddr: "10::10",
expected: map[string]int{
"http://tchouk/toto": http.StatusOK,
},
},
{
desc: "Matching IP among several CIDR",
rule: "ClientIP(`11.0.0.0/16`, `10.0.0.0/16`)",
remoteAddr: "10.0.0.0",
expected: map[string]int{
"http://tchouk/toto": http.StatusOK,
},
},
{
desc: "Matching IP among non matching CIDR and matching IP",
rule: "ClientIP(`11.0.0.0/16`, `10.0.0.0`)",
remoteAddr: "10.0.0.0",
expected: map[string]int{
"http://tchouk/toto": http.StatusOK,
},
},
{
desc: "Matching IP among matching CIDR and non matching IP",
rule: "ClientIP(`11.0.0.0`, `10.0.0.0/16`)",
remoteAddr: "10.0.0.0",
expected: map[string]int{
"http://tchouk/toto": http.StatusOK,
"https://example.com/js": http.StatusNotFound,
"https://example.com/html": http.StatusOK,
"https://example.org/js": http.StatusOK,
"https://example.com/css": http.StatusOK,
"https://example.org/css": http.StatusOK,
"https://example.org/html": http.StatusOK,
"https://example.eu/images": http.StatusOK,
},
},
}
@ -644,14 +213,15 @@ func Test_addRoute(t *testing.T) {
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
muxer, err := NewMuxer()
require.NoError(t, err)
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
err = muxer.AddRoute(test.rule, 0, handler)
if test.expectedError {
require.Error(t, err)
} else {
return
}
require.NoError(t, err)
// RequestDecorator is necessary for the host rule
@ -659,9 +229,7 @@ func Test_addRoute(t *testing.T) {
results := make(map[string]int)
for calledURL := range test.expected {
w := httptest.NewRecorder()
req := testhelpers.MustNewRequest(http.MethodGet, calledURL, nil)
req := testhelpers.MustNewRequest(http.MethodGet, calledURL, http.NoBody)
// Useful for the ClientIP matcher
req.RemoteAddr = test.remoteAddr
@ -669,11 +237,13 @@ func Test_addRoute(t *testing.T) {
for key, value := range test.headers {
req.Header.Set(key, value)
}
w := httptest.NewRecorder()
reqHost.ServeHTTP(w, req, muxer.ServeHTTP)
results[calledURL] = w.Code
}
assert.Equal(t, test.expected, results)
}
})
}
}
@ -813,7 +383,7 @@ func Test_addRoutePriority(t *testing.T) {
muxer.SortRoutes()
w := httptest.NewRecorder()
req := testhelpers.MustNewRequest(http.MethodGet, test.path, nil)
req := testhelpers.MustNewRequest(http.MethodGet, test.path, http.NoBody)
muxer.ServeHTTP(w, req)
@ -822,86 +392,6 @@ func Test_addRoutePriority(t *testing.T) {
}
}
func TestHostRegexp(t *testing.T) {
testCases := []struct {
desc string
hostExp string
urls map[string]bool
}{
{
desc: "capturing group",
hostExp: "{subdomain:(foo\\.)?bar\\.com}",
urls: map[string]bool{
"http://foo.bar.com": true,
"http://bar.com": true,
"http://fooubar.com": false,
"http://barucom": false,
"http://barcom": false,
},
},
{
desc: "non capturing group",
hostExp: "{subdomain:(?:foo\\.)?bar\\.com}",
urls: map[string]bool{
"http://foo.bar.com": true,
"http://bar.com": true,
"http://fooubar.com": false,
"http://barucom": false,
"http://barcom": false,
},
},
{
desc: "regex insensitive",
hostExp: "{dummy:[A-Za-z-]+\\.bar\\.com}",
urls: map[string]bool{
"http://FOO.bar.com": true,
"http://foo.bar.com": true,
"http://fooubar.com": false,
"http://barucom": false,
"http://barcom": false,
},
},
{
desc: "insensitive host",
hostExp: "{dummy:[a-z-]+\\.bar\\.com}",
urls: map[string]bool{
"http://FOO.bar.com": true,
"http://foo.bar.com": true,
"http://fooubar.com": false,
"http://barucom": false,
"http://barcom": false,
},
},
{
desc: "insensitive host simple",
hostExp: "foo.bar.com",
urls: map[string]bool{
"http://FOO.bar.com": true,
"http://foo.bar.com": true,
"http://fooubar.com": false,
"http://barucom": false,
"http://barcom": false,
},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
rt := &mux.Route{}
err := hostRegexp(rt, test.hostExp)
require.NoError(t, err)
for testURL, match := range test.urls {
req := testhelpers.MustNewRequest(http.MethodGet, testURL, nil)
assert.Equal(t, match, rt.Match(req, &mux.RouteMatch{}), testURL)
}
})
}
}
func TestParseDomains(t *testing.T) {
testCases := []struct {
description string
@ -914,21 +404,6 @@ func TestParseDomains(t *testing.T) {
expression: "Foobar(`foo.bar`,`test.bar`)",
errorExpected: true,
},
{
description: "Several host rules",
expression: "Host(`foo.bar`,`test.bar`)",
domain: []string{"foo.bar", "test.bar"},
},
{
description: "Several host rules upper",
expression: "HOST(`foo.bar`,`test.bar`)",
domain: []string{"foo.bar", "test.bar"},
},
{
description: "Several host rules lower",
expression: "host(`foo.bar`,`test.bar`)",
domain: []string{"foo.bar", "test.bar"},
},
{
description: "No host rule",
expression: "Path(`/test`)",
@ -938,6 +413,11 @@ func TestParseDomains(t *testing.T) {
expression: "Host(`foo.bar`) && Path(`/test`)",
domain: []string{"foo.bar"},
},
{
description: "Host rule to trim and another rule",
expression: "Host(`Foo.Bar`) || Host(`bar.buz`) && Path(`/test`)",
domain: []string{"foo.bar", "bar.buz"},
},
{
description: "Host rule to trim and another rule",
expression: "Host(`Foo.Bar`) && Path(`/test`)",
@ -967,7 +447,9 @@ func TestParseDomains(t *testing.T) {
}
}
func TestAbsoluteFormURL(t *testing.T) {
// TestEmptyHost is a non regression test for
// https://github.com/traefik/traefik/pull/9131
func TestEmptyHost(t *testing.T) {
testCases := []struct {
desc string
request string
@ -975,41 +457,41 @@ func TestAbsoluteFormURL(t *testing.T) {
expected int
}{
{
desc: "!HostRegexp with absolute-form URL with empty host with non-matching host header",
request: "GET http://@/ HTTP/1.1\r\nHost: test.localhost\r\n\r\n",
rule: "!HostRegexp(`test.localhost`)",
expected: http.StatusNotFound,
},
{
desc: "!Host with absolute-form URL with empty host with non-matching host header",
request: "GET http://@/ HTTP/1.1\r\nHost: test.localhost\r\n\r\n",
rule: "!Host(`test.localhost`)",
expected: http.StatusNotFound,
},
{
desc: "!HostRegexp with absolute-form URL with matching host header",
request: "GET http://test.localhost/ HTTP/1.1\r\nHost: toto.localhost\r\n\r\n",
rule: "!HostRegexp(`test.localhost`)",
expected: http.StatusNotFound,
},
{
desc: "!Host with absolute-form URL with matching host header",
request: "GET http://test.localhost/ HTTP/1.1\r\nHost: toto.localhost\r\n\r\n",
rule: "!Host(`test.localhost`)",
expected: http.StatusNotFound,
},
{
desc: "!HostRegexp with absolute-form URL with non-matching host header",
request: "GET http://test.localhost/ HTTP/1.1\r\nHost: toto.localhost\r\n\r\n",
rule: "!HostRegexp(`toto.localhost`)",
desc: "HostRegexp with absolute-form URL with empty host with non-matching host header",
request: "GET http://@/ HTTP/1.1\r\nHost: example.com\r\n\r\n",
rule: "HostRegexp(`example.com`)",
expected: http.StatusOK,
},
{
desc: "!Host with absolute-form URL with non-matching host header",
request: "GET http://test.localhost/ HTTP/1.1\r\nHost: toto.localhost\r\n\r\n",
rule: "!Host(`toto.localhost`)",
desc: "Host with absolute-form URL with empty host with non-matching host header",
request: "GET http://@/ HTTP/1.1\r\nHost: example.com\r\n\r\n",
rule: "Host(`example.com`)",
expected: http.StatusOK,
},
{
desc: "HostRegexp with absolute-form URL with matching host header",
request: "GET http://example.com/ HTTP/1.1\r\nHost: example.org\r\n\r\n",
rule: "HostRegexp(`example.com`)",
expected: http.StatusOK,
},
{
desc: "Host with absolute-form URL with matching host header",
request: "GET http://example.com/ HTTP/1.1\r\nHost: example.org\r\n\r\n",
rule: "Host(`example.com`)",
expected: http.StatusOK,
},
{
desc: "HostRegexp with absolute-form URL with non-matching host header",
request: "GET http://example.com/ HTTP/1.1\r\nHost: example.org\r\n\r\n",
rule: "HostRegexp(`example.org`)",
expected: http.StatusNotFound,
},
{
desc: "Host with absolute-form URL with non-matching host header",
request: "GET http://example.com/ HTTP/1.1\r\nHost: example.org\r\n\r\n",
rule: "Host(`example.org`)",
expected: http.StatusNotFound,
},
}
for _, test := range testCases {

134
pkg/muxer/tcp/matcher.go Normal file
View file

@ -0,0 +1,134 @@
package tcp
import (
"fmt"
"regexp"
"strings"
"unicode/utf8"
"github.com/go-acme/lego/v4/challenge/tlsalpn01"
"github.com/rs/zerolog/log"
"github.com/traefik/traefik/v2/pkg/ip"
)
var tcpFuncs = map[string]func(*matchersTree, ...string) error{
"ALPN": expect1Parameter(alpn),
"ClientIP": expect1Parameter(clientIP),
"HostSNI": expect1Parameter(hostSNI),
"HostSNIRegexp": expect1Parameter(hostSNIRegexp),
}
func expect1Parameter(fn func(*matchersTree, ...string) error) func(*matchersTree, ...string) error {
return func(route *matchersTree, s ...string) error {
if len(s) != 1 {
return fmt.Errorf("unexpected number of parameters; got %d, expected 1", len(s))
}
return fn(route, s...)
}
}
// alpn checks if any of the connection ALPN protocols matches one of the matcher protocols.
func alpn(tree *matchersTree, protos ...string) error {
proto := protos[0]
if proto == tlsalpn01.ACMETLS1Protocol {
return fmt.Errorf("invalid protocol value for ALPN matcher, %q is not allowed", proto)
}
tree.matcher = func(meta ConnData) bool {
for _, alpnProto := range meta.alpnProtos {
if alpnProto == proto {
return true
}
}
return false
}
return nil
}
func clientIP(tree *matchersTree, clientIP ...string) error {
checker, err := ip.NewChecker(clientIP)
if err != nil {
return fmt.Errorf("initializing IP checker for ClientIP matcher: %w", err)
}
tree.matcher = func(meta ConnData) bool {
ok, err := checker.Contains(meta.remoteIP)
if err != nil {
log.Warn().Err(err).Msg("ClientIP matcher: could not match remote address")
return false
}
return ok
}
return nil
}
var almostFQDN = regexp.MustCompile(`^[[:alnum:]\.-]+$`)
// hostSNI checks if the SNI Host of the connection match the matcher host.
func hostSNI(tree *matchersTree, hosts ...string) error {
host := hosts[0]
if host == "*" {
// Since a HostSNI(`*`) rule has been provided as catchAll for non-TLS TCP,
// it allows matching with an empty serverName.
tree.matcher = func(meta ConnData) bool { return true }
return nil
}
if !almostFQDN.MatchString(host) {
return fmt.Errorf("invalid value for HostSNI matcher, %q is not a valid hostname", host)
}
tree.matcher = func(meta ConnData) bool {
if meta.serverName == "" {
return false
}
if host == meta.serverName {
return true
}
// trim trailing period in case of FQDN
host = strings.TrimSuffix(host, ".")
return host == meta.serverName
}
return nil
}
// hostSNIRegexp checks if the SNI Host of the connection matches the matcher host regexp.
func hostSNIRegexp(tree *matchersTree, templates ...string) error {
template := templates[0]
if !isASCII(template) {
return fmt.Errorf("invalid value for HostSNIRegexp matcher, %q is not a valid hostname", template)
}
re, err := regexp.Compile(template)
if err != nil {
return fmt.Errorf("compiling HostSNIRegexp matcher: %w", err)
}
tree.matcher = func(meta ConnData) bool {
return re.MatchString(meta.serverName)
}
return nil
}
// isASCII checks if the given string contains only ASCII characters.
func isASCII(s string) bool {
for i := 0; i < len(s); i++ {
if s[i] >= utf8.RuneSelf {
return false
}
}
return true
}

View file

@ -0,0 +1,383 @@
package tcp
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/traefik/traefik/v2/pkg/tcp"
)
func Test_HostSNICatchAll(t *testing.T) {
testCases := []struct {
desc string
rule string
isCatchAll bool
}{
{
desc: "HostSNI(`example.com`) is not catchAll",
rule: "HostSNI(`example.com`)",
},
{
desc: "HostSNI(`*`) is catchAll",
rule: "HostSNI(`*`)",
isCatchAll: true,
},
{
desc: "HostSNIRegexp(`^.*$`) is not catchAll",
rule: "HostSNIRegexp(`.*`)",
isCatchAll: false,
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
muxer, err := NewMuxer()
require.NoError(t, err)
err = muxer.AddRoute(test.rule, 0, tcp.HandlerFunc(func(conn tcp.WriteCloser) {}))
require.NoError(t, err)
handler, catchAll := muxer.Match(ConnData{
serverName: "example.com",
})
require.NotNil(t, handler)
assert.Equal(t, test.isCatchAll, catchAll)
})
}
}
func Test_HostSNI(t *testing.T) {
testCases := []struct {
desc string
rule string
serverName string
buildErr bool
match bool
}{
{
desc: "Empty",
buildErr: true,
},
{
desc: "Invalid HostSNI matcher (empty host)",
rule: "HostSNI(``)",
buildErr: true,
},
{
desc: "Invalid HostSNI matcher (too many parameters)",
rule: "HostSNI(`example.com`, `example.org`)",
buildErr: true,
},
{
desc: "Invalid HostSNI matcher (globing sub domain)",
rule: "HostSNI(`*.com`)",
buildErr: true,
},
{
desc: "Invalid HostSNI matcher (non ASCII host)",
rule: "HostSNI(`🦭.com`)",
buildErr: true,
},
{
desc: "Valid HostSNI matcher - puny-coded emoji",
rule: "HostSNI(`xn--9t9h.com`)",
serverName: "xn--9t9h.com",
match: true,
},
{
desc: "Valid HostSNI matcher - puny-coded emoji but emoji in server name",
rule: "HostSNI(`xn--9t9h.com`)",
serverName: "🦭.com",
},
{
desc: "Matching hosts",
rule: "HostSNI(`example.com`)",
serverName: "example.com",
match: true,
},
{
desc: "No matching hosts",
rule: "HostSNI(`example.com`)",
serverName: "example.org",
},
{
desc: "Matching globing host `*`",
rule: "HostSNI(`*`)",
serverName: "example.com",
match: true,
},
{
desc: "Matching globing host `*` and empty server name",
rule: "HostSNI(`*`)",
serverName: "",
match: true,
},
{
desc: "Matching host with trailing dot",
rule: "HostSNI(`example.com.`)",
serverName: "example.com.",
match: true,
},
{
desc: "Matching host with trailing dot but not in server name",
rule: "HostSNI(`example.com.`)",
serverName: "example.com",
match: true,
},
{
desc: "Matching hosts with subdomains",
rule: "HostSNI(`foo.example.com`)",
serverName: "foo.example.com",
match: true,
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
muxer, err := NewMuxer()
require.NoError(t, err)
err = muxer.AddRoute(test.rule, 0, tcp.HandlerFunc(func(conn tcp.WriteCloser) {}))
if test.buildErr {
require.Error(t, err)
return
}
require.NoError(t, err)
meta := ConnData{
serverName: test.serverName,
}
handler, _ := muxer.Match(meta)
require.Equal(t, test.match, handler != nil)
})
}
}
func Test_HostSNIRegexp(t *testing.T) {
testCases := []struct {
desc string
rule string
expected map[string]bool
buildErr bool
match bool
}{
{
desc: "Empty",
buildErr: true,
},
{
desc: "Invalid HostSNIRegexp matcher (empty host)",
rule: "HostSNIRegexp(``)",
buildErr: true,
},
{
desc: "Invalid HostSNIRegexp matcher (non ASCII host)",
rule: "HostSNIRegexp(`🦭.com`)",
buildErr: true,
},
{
desc: "Invalid HostSNIRegexp matcher (invalid regexp)",
rule: "HostSNIRegexp(`(example.com`)",
buildErr: true,
},
{
desc: "Invalid HostSNIRegexp matcher (too many parameters)",
rule: "HostSNIRegexp(`example.com`, `example.org`)",
buildErr: true,
},
{
desc: "valid HostSNIRegexp matcher",
rule: "HostSNIRegexp(`^example\\.(com|org)$`)",
expected: map[string]bool{
"example.com": true,
"example.com.": false,
"EXAMPLE.com": false,
"example.org": true,
"exampleuorg": false,
"": false,
},
},
{
desc: "valid HostSNIRegexp matcher with Traefik v2 syntax",
rule: "HostSNIRegexp(`example.{tld:(com|org)}`)",
expected: map[string]bool{
"example.com": false,
"example.com.": false,
"EXAMPLE.com": false,
"example.org": false,
"exampleuorg": false,
"": false,
},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
muxer, err := NewMuxer()
require.NoError(t, err)
err = muxer.AddRoute(test.rule, 0, tcp.HandlerFunc(func(conn tcp.WriteCloser) {}))
if test.buildErr {
require.Error(t, err)
return
}
require.NoError(t, err)
for serverName, match := range test.expected {
meta := ConnData{
serverName: serverName,
}
handler, _ := muxer.Match(meta)
assert.Equal(t, match, handler != nil, serverName)
}
})
}
}
func Test_ClientIP(t *testing.T) {
testCases := []struct {
desc string
rule string
expected map[string]bool
buildErr bool
}{
{
desc: "Empty",
buildErr: true,
},
{
desc: "Invalid ClientIP matcher (empty host)",
rule: "ClientIP(``)",
buildErr: true,
},
{
desc: "Invalid ClientIP matcher (non ASCII host)",
rule: "ClientIP(`🦭/32`)",
buildErr: true,
},
{
desc: "Invalid ClientIP matcher (too many parameters)",
rule: "ClientIP(`127.0.0.1`, `127.0.0.2`)",
buildErr: true,
},
{
desc: "valid ClientIP matcher",
rule: "ClientIP(`20.20.20.20`)",
expected: map[string]bool{
"20.20.20.20": true,
"10.10.10.10": false,
},
},
{
desc: "valid ClientIP matcher with CIDR",
rule: "ClientIP(`20.20.20.20/24`)",
expected: map[string]bool{
"20.20.20.20": true,
"20.20.20.40": true,
"10.10.10.10": false,
},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
muxer, err := NewMuxer()
require.NoError(t, err)
err = muxer.AddRoute(test.rule, 0, tcp.HandlerFunc(func(conn tcp.WriteCloser) {}))
if test.buildErr {
require.Error(t, err)
return
}
require.NoError(t, err)
for remoteIP, match := range test.expected {
meta := ConnData{
remoteIP: remoteIP,
}
handler, _ := muxer.Match(meta)
assert.Equal(t, match, handler != nil, remoteIP)
}
})
}
}
func Test_ALPN(t *testing.T) {
testCases := []struct {
desc string
rule string
expected map[string]bool
buildErr bool
}{
{
desc: "Empty",
buildErr: true,
},
{
desc: "Invalid ALPN matcher (TLS proto)",
rule: "ALPN(`acme-tls/1`)",
buildErr: true,
},
{
desc: "Invalid ALPN matcher (empty parameters)",
rule: "ALPN(``)",
buildErr: true,
},
{
desc: "Invalid ALPN matcher (too many parameters)",
rule: "ALPN(`h2`, `mqtt`)",
buildErr: true,
},
{
desc: "Valid ALPN matcher",
rule: "ALPN(`h2`)",
expected: map[string]bool{
"h2": true,
"mqtt": false,
"": false,
},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
muxer, err := NewMuxer()
require.NoError(t, err)
err = muxer.AddRoute(test.rule, 0, tcp.HandlerFunc(func(conn tcp.WriteCloser) {}))
if test.buildErr {
require.Error(t, err)
return
}
require.NoError(t, err)
for proto, match := range test.expected {
meta := ConnData{
alpnProtos: []string{proto},
}
handler, _ := muxer.Match(meta)
assert.Equal(t, match, handler != nil, proto)
}
})
}
}

View file

@ -1,31 +1,18 @@
package tcp
import (
"bytes"
"errors"
"fmt"
"net"
"regexp"
"sort"
"strconv"
"strings"
"github.com/go-acme/lego/v4/challenge/tlsalpn01"
"github.com/rs/zerolog/log"
"github.com/traefik/traefik/v2/pkg/ip"
"github.com/traefik/traefik/v2/pkg/rules"
"github.com/traefik/traefik/v2/pkg/tcp"
"github.com/traefik/traefik/v2/pkg/types"
"github.com/vulcand/predicate"
)
var tcpFuncs = map[string]func(*matchersTree, ...string) error{
"HostSNI": hostSNI,
"HostSNIRegexp": hostSNIRegexp,
"ClientIP": clientIP,
"ALPN": alpn,
}
// ParseHostSNI extracts the HostSNIs declared in a rule.
// This is a first naive implementation used in TCP routing.
func ParseHostSNI(rule string) ([]string, error) {
@ -261,233 +248,7 @@ func (m *matchersTree) match(meta ConnData) bool {
return m.left.match(meta) && m.right.match(meta)
default:
// This should never happen as it should have been detected during parsing.
log.Warn().Msgf("Invalid rule operator %s", m.operator)
log.Warn().Str("operator", m.operator).Msg("Invalid rule operator")
return false
}
}
func clientIP(tree *matchersTree, clientIPs ...string) error {
checker, err := ip.NewChecker(clientIPs)
if err != nil {
return fmt.Errorf("could not initialize IP Checker for \"ClientIP\" matcher: %w", err)
}
tree.matcher = func(meta ConnData) bool {
if meta.remoteIP == "" {
return false
}
ok, err := checker.Contains(meta.remoteIP)
if err != nil {
log.Warn().Err(err).Msg("\"ClientIP\" matcher: could not match remote address")
return false
}
return ok
}
return nil
}
// alpn checks if any of the connection ALPN protocols matches one of the matcher protocols.
func alpn(tree *matchersTree, protos ...string) error {
if len(protos) == 0 {
return errors.New("empty value for \"ALPN\" matcher is not allowed")
}
for _, proto := range protos {
if proto == tlsalpn01.ACMETLS1Protocol {
return fmt.Errorf("invalid protocol value for \"ALPN\" matcher, %q is not allowed", proto)
}
}
tree.matcher = func(meta ConnData) bool {
for _, proto := range meta.alpnProtos {
for _, filter := range protos {
if proto == filter {
return true
}
}
}
return false
}
return nil
}
var almostFQDN = regexp.MustCompile(`^[[:alnum:]\.-]+$`)
// hostSNI checks if the SNI Host of the connection match the matcher host.
func hostSNI(tree *matchersTree, hosts ...string) error {
if len(hosts) == 0 {
return errors.New("empty value for \"HostSNI\" matcher is not allowed")
}
for i, host := range hosts {
// Special case to allow global wildcard
if host == "*" {
continue
}
if !almostFQDN.MatchString(host) {
return fmt.Errorf("invalid value for \"HostSNI\" matcher, %q is not a valid hostname", host)
}
hosts[i] = strings.ToLower(host)
}
tree.matcher = func(meta ConnData) bool {
// Since a HostSNI(`*`) rule has been provided as catchAll for non-TLS TCP,
// it allows matching with an empty serverName.
// Which is why we make sure to take that case into account before
// checking meta.serverName.
if hosts[0] == "*" {
return true
}
if meta.serverName == "" {
return false
}
for _, host := range hosts {
if host == "*" {
return true
}
if host == meta.serverName {
return true
}
// trim trailing period in case of FQDN
host = strings.TrimSuffix(host, ".")
if host == meta.serverName {
return true
}
}
return false
}
return nil
}
// hostSNIRegexp checks if the SNI Host of the connection matches the matcher host regexp.
func hostSNIRegexp(tree *matchersTree, templates ...string) error {
if len(templates) == 0 {
return fmt.Errorf("empty value for \"HostSNIRegexp\" matcher is not allowed")
}
var regexps []*regexp.Regexp
for _, template := range templates {
preparedPattern, err := preparePattern(template)
if err != nil {
return fmt.Errorf("invalid pattern value for \"HostSNIRegexp\" matcher, %q is not a valid pattern: %w", template, err)
}
regexp, err := regexp.Compile(preparedPattern)
if err != nil {
return err
}
regexps = append(regexps, regexp)
}
tree.matcher = func(meta ConnData) bool {
for _, regexp := range regexps {
if regexp.MatchString(meta.serverName) {
return true
}
}
return false
}
return nil
}
// TODO: expose more of containous/mux fork to get rid of the following copied code (https://github.com/containous/mux/blob/8ffa4f6d063c/regexp.go).
// preparePattern builds a regexp pattern from the initial user defined expression.
// This function reuses the code dedicated to host matching of the newRouteRegexp func from the gorilla/mux library.
// https://github.com/containous/mux/tree/8ffa4f6d063c1e2b834a73be6a1515cca3992618.
func preparePattern(template string) (string, error) {
// Check if it is well-formed.
idxs, errBraces := braceIndices(template)
if errBraces != nil {
return "", errBraces
}
defaultPattern := "[^.]+"
pattern := bytes.NewBufferString("")
// Host SNI matching is case-insensitive
fmt.Fprint(pattern, "(?i)")
pattern.WriteByte('^')
var end int
var err error
for i := 0; i < len(idxs); i += 2 {
// Set all values we are interested in.
raw := template[end:idxs[i]]
end = idxs[i+1]
parts := strings.SplitN(template[idxs[i]+1:end-1], ":", 2)
name := parts[0]
patt := defaultPattern
if len(parts) == 2 {
patt = parts[1]
}
// Name or pattern can't be empty.
if name == "" || patt == "" {
return "", fmt.Errorf("mux: missing name or pattern in %q",
template[idxs[i]:end])
}
// Build the regexp pattern.
fmt.Fprintf(pattern, "%s(?P<%s>%s)", regexp.QuoteMeta(raw), varGroupName(i/2), patt)
// Append variable name and compiled pattern.
if err != nil {
return "", err
}
}
// Add the remaining.
raw := template[end:]
pattern.WriteString(regexp.QuoteMeta(raw))
pattern.WriteByte('$')
return pattern.String(), nil
}
// varGroupName builds a capturing group name for the indexed variable.
// This function is a copy of varGroupName func from the gorilla/mux library.
// https://github.com/containous/mux/tree/8ffa4f6d063c1e2b834a73be6a1515cca3992618.
func varGroupName(idx int) string {
return "v" + strconv.Itoa(idx)
}
// braceIndices returns the first level curly brace indices from a string.
// This function is a copy of braceIndices func from the gorilla/mux library.
// https://github.com/containous/mux/tree/8ffa4f6d063c1e2b834a73be6a1515cca3992618.
func braceIndices(s string) ([]int, error) {
var level, idx int
var idxs []int
for i := 0; i < len(s); i++ {
switch s[i] {
case '{':
if level++; level == 1 {
idx = i
}
case '}':
if level--; level == 0 {
idxs = append(idxs, idx, i+1)
} else if level < 0 {
return nil, fmt.Errorf("mux: unbalanced braces in %q", s)
}
}
}
if level != 0 {
return nil, fmt.Errorf("mux: unbalanced braces in %q", s)
}
return idxs, nil
}

File diff suppressed because it is too large Load diff

View file

@ -233,12 +233,12 @@ func (p *Provider) Provide(configurationChan chan<- dynamic.Message, pool *safe.
}
notify := func(err error, time time.Duration) {
logger.Error().Err(err).Msgf("Provider connection error, retrying in %s", time)
logger.Error().Err(err).Msgf("Provider error, retrying in %s", time)
}
err := backoff.RetryNotify(safe.OperationWithRecover(operation), backoff.WithContext(job.NewBackOff(backoff.NewExponentialBackOff()), ctxLog), notify)
if err != nil {
logger.Error().Err(err).Msg("Cannot connect to consul catalog server")
logger.Error().Err(err).Msg("Cannot retrieve data")
}
})

View file

@ -329,11 +329,11 @@ func (p *Provider) Provide(configurationChan chan<- dynamic.Message, pool *safe.
}
notify := func(err error, time time.Duration) {
logger.Error().Err(err).Msgf("Provider connection error, retrying in %s", time)
logger.Error().Err(err).Msgf("Provider error, retrying in %s", time)
}
err := backoff.RetryNotify(safe.OperationWithRecover(operation), backoff.WithContext(job.NewBackOff(backoff.NewExponentialBackOff()), ctxLog), notify)
if err != nil {
logger.Error().Err(err).Msg("Cannot connect to docker server")
logger.Error().Err(err).Msg("Cannot retrieve data")
}
})

View file

@ -183,11 +183,11 @@ func (p *Provider) Provide(configurationChan chan<- dynamic.Message, pool *safe.
}
notify := func(err error, time time.Duration) {
logger.Error().Err(err).Msgf("Provider connection error, retrying in %s", time)
logger.Error().Err(err).Msgf("Provider error, retrying in %s", time)
}
err := backoff.RetryNotify(safe.OperationWithRecover(operation), backoff.WithContext(job.NewBackOff(backoff.NewExponentialBackOff()), routineCtx), notify)
if err != nil {
logger.Error().Err(err).Msg("Cannot connect to Provider api")
logger.Error().Err(err).Msg("Cannot retrieve data")
}
})

View file

@ -97,11 +97,11 @@ func (p *Provider) Provide(configurationChan chan<- dynamic.Message, pool *safe.
}
notify := func(err error, time time.Duration) {
logger.Error().Err(err).Msgf("Provider connection error, retrying in %s", time)
logger.Error().Err(err).Msgf("Provider error, retrying in %s", time)
}
err := backoff.RetryNotify(safe.OperationWithRecover(operation), backoff.WithContext(job.NewBackOff(backoff.NewExponentialBackOff()), ctxLog), notify)
if err != nil {
logger.Error().Err(err).Msg("Cannot connect to server endpoint")
logger.Error().Err(err).Msg("Cannot retrieve data")
}
})

View file

@ -146,7 +146,7 @@ func TestProvider_decodeConfiguration(t *testing.T) {
},
{
desc: "should return the decoded dynamic configuration",
configData: []byte("{\"tcp\":{\"routers\":{\"foo\":{}}}}"),
configData: []byte(`{"tcp":{"routers":{"foo":{}}}}`),
expConfig: &dynamic.Configuration{
HTTP: &dynamic.HTTPConfiguration{
Routers: make(map[string]*dynamic.Router),

View file

@ -101,7 +101,7 @@ func patchDynamicConfiguration(cfg *dynamic.Configuration, ep string, port int,
cfg.HTTP.Routers["traefik-hub-agent-service"] = &dynamic.Router{
EntryPoints: []string{ep},
Service: "traefik-hub-agent-service",
Rule: "Host(`proxy.traefik`) && PathPrefix(`/config`, `/discover-ip`, `/state`)",
Rule: "Host(`proxy.traefik`) && (PathPrefix(`/config`) || PathPrefix(`/discover-ip`) || PathPrefix(`/state`))",
}
cfg.HTTP.Services["traefik-hub-agent-service"] = &dynamic.Service{

View file

@ -168,11 +168,11 @@ func (p *Provider) Provide(configurationChan chan<- dynamic.Message, pool *safe.
}
notify := func(err error, time time.Duration) {
logger.Error().Err(err).Msgf("Provider connection error, retrying in %s", time)
logger.Error().Err(err).Msgf("Provider error, retrying in %s", time)
}
err := backoff.RetryNotify(safe.OperationWithRecover(operation), backoff.WithContext(job.NewBackOff(backoff.NewExponentialBackOff()), ctxPool), notify)
if err != nil {
logger.Error().Err(err).Msg("Cannot connect to Provider")
logger.Error().Err(err).Msg("Cannot retrieve data")
}
})

View file

@ -13,72 +13,72 @@ type IngressRouteSpec struct {
Routes []Route `json:"routes"`
// EntryPoints defines the list of entry point names to bind to.
// Entry points have to be configured in the static configuration.
// More info: https://doc.traefik.io/traefik/v2.9/routing/entrypoints/
// More info: https://doc.traefik.io/traefik/v3.0/routing/entrypoints/
// Default: all.
EntryPoints []string `json:"entryPoints,omitempty"`
// TLS defines the TLS configuration.
// More info: https://doc.traefik.io/traefik/v2.9/routing/routers/#tls
// More info: https://doc.traefik.io/traefik/v3.0/routing/routers/#tls
TLS *TLS `json:"tls,omitempty"`
}
// Route holds the HTTP route configuration.
type Route struct {
// Match defines the router's rule.
// More info: https://doc.traefik.io/traefik/v2.9/routing/routers/#rule
// More info: https://doc.traefik.io/traefik/v3.0/routing/routers/#rule
Match string `json:"match"`
// Kind defines the kind of the route.
// Rule is the only supported kind.
// +kubebuilder:validation:Enum=Rule
Kind string `json:"kind"`
// Priority defines the router's priority.
// More info: https://doc.traefik.io/traefik/v2.9/routing/routers/#priority
// More info: https://doc.traefik.io/traefik/v3.0/routing/routers/#priority
Priority int `json:"priority,omitempty"`
// Services defines the list of Service.
// It can contain any combination of TraefikService and/or reference to a Kubernetes Service.
Services []Service `json:"services,omitempty"`
// Middlewares defines the list of references to Middleware resources.
// More info: https://doc.traefik.io/traefik/v2.9/routing/providers/kubernetes-crd/#kind-middleware
// More info: https://doc.traefik.io/traefik/v3.0/routing/providers/kubernetes-crd/#kind-middleware
Middlewares []MiddlewareRef `json:"middlewares,omitempty"`
}
// TLS holds the TLS configuration.
// More info: https://doc.traefik.io/traefik/v2.9/routing/routers/#tls
// More info: https://doc.traefik.io/traefik/v3.0/routing/routers/#tls
type TLS struct {
// SecretName is the name of the referenced Kubernetes Secret to specify the certificate details.
SecretName string `json:"secretName,omitempty"`
// Options defines the reference to a TLSOption, that specifies the parameters of the TLS connection.
// If not defined, the `default` TLSOption is used.
// More info: https://doc.traefik.io/traefik/v2.9/https/tls/#tls-options
// More info: https://doc.traefik.io/traefik/v3.0/https/tls/#tls-options
Options *TLSOptionRef `json:"options,omitempty"`
// Store defines the reference to the TLSStore, that will be used to store certificates.
// Please note that only `default` TLSStore can be used.
Store *TLSStoreRef `json:"store,omitempty"`
// CertResolver defines the name of the certificate resolver to use.
// Cert resolvers have to be configured in the static configuration.
// More info: https://doc.traefik.io/traefik/v2.9/https/acme/#certificate-resolvers
// More info: https://doc.traefik.io/traefik/v3.0/https/acme/#certificate-resolvers
CertResolver string `json:"certResolver,omitempty"`
// Domains defines the list of domains that will be used to issue certificates.
// More info: https://doc.traefik.io/traefik/v2.9/routing/routers/#domains
// More info: https://doc.traefik.io/traefik/v3.0/routing/routers/#domains
Domains []types.Domain `json:"domains,omitempty"`
}
// TLSOptionRef is a reference to a TLSOption resource.
type TLSOptionRef struct {
// Name defines the name of the referenced TLSOption.
// More info: https://doc.traefik.io/traefik/v2.9/routing/providers/kubernetes-crd/#kind-tlsoption
// More info: https://doc.traefik.io/traefik/v3.0/routing/providers/kubernetes-crd/#kind-tlsoption
Name string `json:"name"`
// Namespace defines the namespace of the referenced TLSOption.
// More info: https://doc.traefik.io/traefik/v2.9/routing/providers/kubernetes-crd/#kind-tlsoption
// More info: https://doc.traefik.io/traefik/v3.0/routing/providers/kubernetes-crd/#kind-tlsoption
Namespace string `json:"namespace,omitempty"`
}
// TLSStoreRef is a reference to a TLSStore resource.
type TLSStoreRef struct {
// Name defines the name of the referenced TLSStore.
// More info: https://doc.traefik.io/traefik/v2.9/routing/providers/kubernetes-crd/#kind-tlsstore
// More info: https://doc.traefik.io/traefik/v3.0/routing/providers/kubernetes-crd/#kind-tlsstore
Name string `json:"name"`
// Namespace defines the namespace of the referenced TLSStore.
// More info: https://doc.traefik.io/traefik/v2.9/routing/providers/kubernetes-crd/#kind-tlsstore
// More info: https://doc.traefik.io/traefik/v3.0/routing/providers/kubernetes-crd/#kind-tlsstore
Namespace string `json:"namespace,omitempty"`
}
@ -95,7 +95,7 @@ type LoadBalancerSpec struct {
// Namespace defines the namespace of the referenced Kubernetes Service or TraefikService.
Namespace string `json:"namespace,omitempty"`
// Sticky defines the sticky sessions configuration.
// More info: https://doc.traefik.io/traefik/v2.9/routing/services/#sticky-sessions
// More info: https://doc.traefik.io/traefik/v3.0/routing/services/#sticky-sessions
Sticky *dynamic.Sticky `json:"sticky,omitempty"`
// Port defines the port of a Kubernetes Service.
// This can be a reference to a named port.

View file

@ -13,21 +13,21 @@ type IngressRouteTCPSpec struct {
Routes []RouteTCP `json:"routes"`
// EntryPoints defines the list of entry point names to bind to.
// Entry points have to be configured in the static configuration.
// More info: https://doc.traefik.io/traefik/v2.9/routing/entrypoints/
// More info: https://doc.traefik.io/traefik/v3.0/routing/entrypoints/
// Default: all.
EntryPoints []string `json:"entryPoints,omitempty"`
// TLS defines the TLS configuration on a layer 4 / TCP Route.
// More info: https://doc.traefik.io/traefik/v2.9/routing/routers/#tls_1
// More info: https://doc.traefik.io/traefik/v3.0/routing/routers/#tls_1
TLS *TLSTCP `json:"tls,omitempty"`
}
// RouteTCP holds the TCP route configuration.
type RouteTCP struct {
// Match defines the router's rule.
// More info: https://doc.traefik.io/traefik/v2.9/routing/routers/#rule_1
// More info: https://doc.traefik.io/traefik/v3.0/routing/routers/#rule_1
Match string `json:"match"`
// Priority defines the router's priority.
// More info: https://doc.traefik.io/traefik/v2.9/routing/routers/#priority_1
// More info: https://doc.traefik.io/traefik/v3.0/routing/routers/#priority_1
Priority int `json:"priority,omitempty"`
// Services defines the list of TCP services.
Services []ServiceTCP `json:"services,omitempty"`
@ -36,7 +36,7 @@ type RouteTCP struct {
}
// TLSTCP holds the TLS configuration for an IngressRouteTCP.
// More info: https://doc.traefik.io/traefik/v2.9/routing/routers/#tls_1
// More info: https://doc.traefik.io/traefik/v3.0/routing/routers/#tls_1
type TLSTCP struct {
// SecretName is the name of the referenced Kubernetes Secret to specify the certificate details.
SecretName string `json:"secretName,omitempty"`
@ -44,17 +44,17 @@ type TLSTCP struct {
Passthrough bool `json:"passthrough,omitempty"`
// Options defines the reference to a TLSOption, that specifies the parameters of the TLS connection.
// If not defined, the `default` TLSOption is used.
// More info: https://doc.traefik.io/traefik/v2.9/https/tls/#tls-options
// More info: https://doc.traefik.io/traefik/v3.0/https/tls/#tls-options
Options *ObjectReference `json:"options,omitempty"`
// Store defines the reference to the TLSStore, that will be used to store certificates.
// Please note that only `default` TLSStore can be used.
Store *ObjectReference `json:"store,omitempty"`
// CertResolver defines the name of the certificate resolver to use.
// Cert resolvers have to be configured in the static configuration.
// More info: https://doc.traefik.io/traefik/v2.9/https/acme/#certificate-resolvers
// More info: https://doc.traefik.io/traefik/v3.0/https/acme/#certificate-resolvers
CertResolver string `json:"certResolver,omitempty"`
// Domains defines the list of domains that will be used to issue certificates.
// More info: https://doc.traefik.io/traefik/v2.9/routing/routers/#domains
// More info: https://doc.traefik.io/traefik/v3.0/routing/routers/#domains
Domains []types.Domain `json:"domains,omitempty"`
}
@ -76,7 +76,7 @@ type ServiceTCP struct {
// A negative value means an infinite deadline (i.e. the reading capability is never closed).
TerminationDelay *int `json:"terminationDelay,omitempty"`
// ProxyProtocol defines the PROXY protocol configuration.
// More info: https://doc.traefik.io/traefik/v2.9/routing/services/#proxy-protocol
// More info: https://doc.traefik.io/traefik/v3.0/routing/services/#proxy-protocol
ProxyProtocol *dynamic.ProxyProtocol `json:"proxyProtocol,omitempty"`
}

View file

@ -11,7 +11,7 @@ type IngressRouteUDPSpec struct {
Routes []RouteUDP `json:"routes"`
// EntryPoints defines the list of entry point names to bind to.
// Entry points have to be configured in the static configuration.
// More info: https://doc.traefik.io/traefik/v2.9/routing/entrypoints/
// More info: https://doc.traefik.io/traefik/v3.0/routing/entrypoints/
// Default: all.
EntryPoints []string `json:"entryPoints,omitempty"`
}

View file

@ -12,7 +12,7 @@ import (
// +kubebuilder:storageversion
// Middleware is the CRD implementation of a Traefik Middleware.
// More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/overview/
// More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/overview/
type Middleware struct {
metav1.TypeMeta `json:",inline"`
// Standard object's metadata.
@ -58,7 +58,7 @@ type MiddlewareSpec struct {
// ErrorPage holds the custom error middleware configuration.
// This middleware returns a custom page in lieu of the default, according to configured ranges of HTTP Status codes.
// More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/errorpages/
// More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/errorpages/
type ErrorPage struct {
// Status defines which status or range of statuses should result in an error page.
// It can be either a status code as a number (500),
@ -67,7 +67,7 @@ type ErrorPage struct {
// or a combination of the two (404,418,500-599).
Status []string `json:"status,omitempty"`
// Service defines the reference to a Kubernetes Service that will serve the error page.
// More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/errorpages/#service
// More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/errorpages/#service
Service Service `json:"service,omitempty"`
// Query defines the URL for the error page (hosted by service).
// The {status} variable can be used in order to insert the status code in the URL.
@ -92,7 +92,7 @@ type CircuitBreaker struct {
// Chain holds the configuration of the chain middleware.
// This middleware enables to define reusable combinations of other pieces of middleware.
// More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/chain/
// More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/chain/
type Chain struct {
// Middlewares is the list of MiddlewareRef which composes the chain.
Middlewares []MiddlewareRef `json:"middlewares,omitempty"`
@ -102,7 +102,7 @@ type Chain struct {
// BasicAuth holds the basic auth middleware configuration.
// This middleware restricts access to your services to known users.
// More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/basicauth/
// More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/basicauth/
type BasicAuth struct {
// Secret is the name of the referenced Kubernetes Secret containing user credentials.
Secret string `json:"secret,omitempty"`
@ -113,7 +113,7 @@ type BasicAuth struct {
// Default: false.
RemoveHeader bool `json:"removeHeader,omitempty"`
// HeaderField defines a header field to store the authenticated user.
// More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/basicauth/#headerfield
// More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/basicauth/#headerfield
HeaderField string `json:"headerField,omitempty"`
}
@ -121,7 +121,7 @@ type BasicAuth struct {
// DigestAuth holds the digest auth middleware configuration.
// This middleware restricts access to your services to known users.
// More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/digestauth/
// More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/digestauth/
type DigestAuth struct {
// Secret is the name of the referenced Kubernetes Secret containing user credentials.
Secret string `json:"secret,omitempty"`
@ -131,7 +131,7 @@ type DigestAuth struct {
// Default: traefik.
Realm string `json:"realm,omitempty"`
// HeaderField defines a header field to store the authenticated user.
// More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/basicauth/#headerfield
// More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/basicauth/#headerfield
HeaderField string `json:"headerField,omitempty"`
}
@ -139,7 +139,7 @@ type DigestAuth struct {
// ForwardAuth holds the forward auth middleware configuration.
// This middleware delegates the request authentication to a Service.
// More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/forwardauth/
// More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/forwardauth/
type ForwardAuth struct {
// Address defines the authentication server address.
Address string `json:"address,omitempty"`
@ -148,7 +148,7 @@ type ForwardAuth struct {
// AuthResponseHeaders defines the list of headers to copy from the authentication server response and set on forwarded request, replacing any existing conflicting headers.
AuthResponseHeaders []string `json:"authResponseHeaders,omitempty"`
// AuthResponseHeadersRegex defines the regex to match headers to copy from the authentication server response and set on forwarded request, after stripping all headers that match the regex.
// More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/forwardauth/#authresponseheadersregex
// More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/forwardauth/#authresponseheadersregex
AuthResponseHeadersRegex string `json:"authResponseHeadersRegex,omitempty"`
// AuthRequestHeaders defines the list of the headers to copy from the request to the authentication server.
// If not set or empty then all request headers are passed.
@ -173,7 +173,7 @@ type ClientTLS struct {
// RateLimit holds the rate limit configuration.
// This middleware ensures that services will receive a fair amount of requests, and allows one to define what fair is.
// More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/ratelimit/
// More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/ratelimit/
type RateLimit struct {
// Average is the maximum rate, by default in requests/s, allowed for the given source.
// It defaults to 0, which means no rate limiting.
@ -197,7 +197,7 @@ type RateLimit struct {
// Retry holds the retry middleware configuration.
// This middleware reissues requests a given number of times to a backend server if that server does not reply.
// As soon as the server answers, the middleware stops retrying, regardless of the response status.
// More info: https://doc.traefik.io/traefik/v2.9/middlewares/http/retry/
// More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/retry/
type Retry struct {
// Attempts defines how many times the request should be retried.
Attempts int `json:"attempts,omitempty"`

View file

@ -9,7 +9,7 @@ import (
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// MiddlewareTCP is the CRD implementation of a Traefik TCP middleware.
// More info: https://doc.traefik.io/traefik/v2.9/middlewares/overview/
// More info: https://doc.traefik.io/traefik/v3.0/middlewares/overview/
type MiddlewareTCP struct {
metav1.TypeMeta `json:",inline"`
// Standard object's metadata.

View file

@ -13,7 +13,7 @@ import (
// ServersTransport is the CRD implementation of a ServersTransport.
// If no serversTransport is specified, the default@internal will be used.
// The default@internal serversTransport is created from the static configuration.
// More info: https://doc.traefik.io/traefik/v2.9/routing/services/#serverstransport_1
// More info: https://doc.traefik.io/traefik/v3.0/routing/services/#serverstransport_1
type ServersTransport struct {
metav1.TypeMeta `json:",inline"`
// Standard object's metadata.

View file

@ -13,7 +13,7 @@ import (
// TraefikService object allows to:
// - Apply weight to Services on load-balancing
// - Mirror traffic on services
// More info: https://doc.traefik.io/traefik/v2.9/routing/providers/kubernetes-crd/#kind-traefikservice
// More info: https://doc.traefik.io/traefik/v3.0/routing/providers/kubernetes-crd/#kind-traefikservice
type TraefikService struct {
metav1.TypeMeta `json:",inline"`
// Standard object's metadata.
@ -49,7 +49,7 @@ type TraefikServiceSpec struct {
// +k8s:deepcopy-gen=true
// Mirroring holds the mirroring service configuration.
// More info: https://doc.traefik.io/traefik/v2.9/routing/services/#mirroring-service
// More info: https://doc.traefik.io/traefik/v3.0/routing/services/#mirroring-service
type Mirroring struct {
LoadBalancerSpec `json:",inline"`
@ -75,11 +75,11 @@ type MirrorService struct {
// +k8s:deepcopy-gen=true
// WeightedRoundRobin holds the weighted round-robin configuration.
// More info: https://doc.traefik.io/traefik/v2.9/routing/services/#weighted-round-robin-service
// More info: https://doc.traefik.io/traefik/v3.0/routing/services/#weighted-round-robin-service
type WeightedRoundRobin struct {
// Services defines the list of Kubernetes Service and/or TraefikService to load-balance, with weight.
Services []Service `json:"services,omitempty"`
// Sticky defines whether sticky sessions are enabled.
// More info: https://doc.traefik.io/traefik/v2.9/routing/providers/kubernetes-crd/#stickiness-and-load-balancing
// More info: https://doc.traefik.io/traefik/v3.0/routing/providers/kubernetes-crd/#stickiness-and-load-balancing
Sticky *dynamic.Sticky `json:"sticky,omitempty"`
}

View file

@ -9,7 +9,7 @@ import (
// +kubebuilder:storageversion
// TLSOption is the CRD implementation of a Traefik TLS Option, allowing to configure some parameters of the TLS connection.
// More info: https://doc.traefik.io/traefik/v2.9/https/tls/#tls-options
// More info: https://doc.traefik.io/traefik/v3.0/https/tls/#tls-options
type TLSOption struct {
metav1.TypeMeta `json:",inline"`
// Standard object's metadata.
@ -32,17 +32,17 @@ type TLSOptionSpec struct {
// Default: None.
MaxVersion string `json:"maxVersion,omitempty"`
// CipherSuites defines the list of supported cipher suites for TLS versions up to TLS 1.2.
// More info: https://doc.traefik.io/traefik/v2.9/https/tls/#cipher-suites
// More info: https://doc.traefik.io/traefik/v3.0/https/tls/#cipher-suites
CipherSuites []string `json:"cipherSuites,omitempty"`
// CurvePreferences defines the preferred elliptic curves in a specific order.
// More info: https://doc.traefik.io/traefik/v2.9/https/tls/#curve-preferences
// More info: https://doc.traefik.io/traefik/v3.0/https/tls/#curve-preferences
CurvePreferences []string `json:"curvePreferences,omitempty"`
// ClientAuth defines the server's policy for TLS Client Authentication.
ClientAuth ClientAuth `json:"clientAuth,omitempty"`
// SniStrict defines whether Traefik allows connections from clients connections that do not specify a server_name extension.
SniStrict bool `json:"sniStrict,omitempty"`
// ALPNProtocols defines the list of supported application level protocols for the TLS handshake, in order of preference.
// More info: https://doc.traefik.io/traefik/v2.9/https/tls/#alpn-protocols
// More info: https://doc.traefik.io/traefik/v3.0/https/tls/#alpn-protocols
ALPNProtocols []string `json:"alpnProtocols,omitempty"`
}

View file

@ -12,7 +12,7 @@ import (
// TLSStore is the CRD implementation of a Traefik TLS Store.
// For the time being, only the TLSStore named default is supported.
// This means that you cannot have two stores that are named default in different Kubernetes namespaces.
// More info: https://doc.traefik.io/traefik/v2.9/https/tls/#certificates-stores
// More info: https://doc.traefik.io/traefik/v3.0/https/tls/#certificates-stores
type TLSStore struct {
metav1.TypeMeta `json:",inline"`
// Standard object's metadata.

View file

@ -7,6 +7,7 @@ import (
"fmt"
"net"
"os"
"regexp"
"sort"
"strconv"
"strings"
@ -161,11 +162,11 @@ func (p *Provider) Provide(configurationChan chan<- dynamic.Message, pool *safe.
}
notify := func(err error, time time.Duration) {
logger.Error().Err(err).Msgf("Provider connection error, retrying in %s", time)
logger.Error().Err(err).Msgf("Provider error, retrying in %s", time)
}
err := backoff.RetryNotify(safe.OperationWithRecover(operation), backoff.WithContext(job.NewBackOff(backoff.NewExponentialBackOff()), ctxPool), notify)
if err != nil {
logger.Error().Err(err).Msg("Cannot connect to Provider")
logger.Error().Err(err).Msg("Cannot retrieve data")
}
})
@ -1163,8 +1164,7 @@ func getRouteBindingSelectorNamespace(client Client, gatewayNamespace string, ro
}
func hostRule(hostnames []v1alpha2.Hostname) (string, error) {
var hostNames []string
var hostRegexNames []string
var rules []string
for _, hostname := range hostnames {
host := string(hostname)
@ -1177,7 +1177,7 @@ func hostRule(hostnames []v1alpha2.Hostname) (string, error) {
wildcard := strings.Count(host, "*")
if wildcard == 0 {
hostNames = append(hostNames, host)
rules = append(rules, fmt.Sprintf("Host(`%s`)", host))
continue
}
@ -1186,25 +1186,18 @@ func hostRule(hostnames []v1alpha2.Hostname) (string, error) {
return "", fmt.Errorf("invalid rule: %q", host)
}
hostRegexNames = append(hostRegexNames, strings.Replace(host, "*.", "{subdomain:[a-zA-Z0-9-]+}.", 1))
host = strings.Replace(regexp.QuoteMeta(host), `\*\.`, `[a-zA-Z0-9-]+\.`, 1)
rules = append(rules, fmt.Sprintf("HostRegexp(`^%s$`)", host))
}
var res string
if len(hostNames) > 0 {
res = "Host(`" + strings.Join(hostNames, "`, `") + "`)"
switch len(rules) {
case 0:
return "", nil
case 1:
return rules[0], nil
default:
return fmt.Sprintf("(%s)", strings.Join(rules, " || ")), nil
}
if len(hostRegexNames) == 0 {
return res, nil
}
hostRegexp := "HostRegexp(`" + strings.Join(hostRegexNames, "`, `") + "`)"
if len(res) > 0 {
return "(" + res + " || " + hostRegexp + ")", nil
}
return hostRegexp, nil
}
func hostSNIRule(hostnames []v1alpha2.Hostname) (string, error) {
@ -1227,15 +1220,18 @@ func hostSNIRule(hostnames []v1alpha2.Hostname) (string, error) {
return "", fmt.Errorf("wildcard hostname is not supported: %q", h)
}
matchers = append(matchers, "`"+h+"`")
matchers = append(matchers, fmt.Sprintf("HostSNI(`%s`)", h))
uniqHostnames[hostname] = struct{}{}
}
if len(matchers) == 0 {
switch len(matchers) {
case 0:
return "HostSNI(`*`)", nil
case 1:
return matchers[0], nil
default:
return fmt.Sprintf("(%s)", strings.Join(matchers, " || ")), nil
}
return "HostSNI(" + strings.Join(matchers, ",") + ")", nil
}
func extractRule(routeRule v1alpha2.HTTPRouteRule, hostRule string) (string, error) {

View file

@ -744,15 +744,15 @@ func TestLoadHTTPRoutes(t *testing.T) {
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{
"default-http-app-1-my-gateway-web-75dd1ad561e42725558a": {
"default-http-app-1-my-gateway-web-66e726cd8903b49727ae": {
EntryPoints: []string{"web"},
Service: "default-http-app-1-my-gateway-web-75dd1ad561e42725558a-wrr",
Rule: "Host(`foo.com`, `bar.com`) && PathPrefix(`/`)",
Service: "default-http-app-1-my-gateway-web-66e726cd8903b49727ae-wrr",
Rule: "(Host(`foo.com`) || Host(`bar.com`)) && PathPrefix(`/`)",
},
},
Middlewares: map[string]*dynamic.Middleware{},
Services: map[string]*dynamic.Service{
"default-http-app-1-my-gateway-web-75dd1ad561e42725558a-wrr": {
"default-http-app-1-my-gateway-web-66e726cd8903b49727ae-wrr": {
Weighted: &dynamic.WeightedRoundRobin{
Services: []dynamic.WRRService{
{
@ -802,15 +802,15 @@ func TestLoadHTTPRoutes(t *testing.T) {
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{
"default-http-app-1-my-gateway-web-2dbd7883f5537db39bca": {
"default-http-app-1-my-gateway-web-3b78e2feb3295ddd87f0": {
EntryPoints: []string{"web"},
Service: "default-http-app-1-my-gateway-web-2dbd7883f5537db39bca-wrr",
Rule: "(Host(`foo.com`) || HostRegexp(`{subdomain:[a-zA-Z0-9-]+}.bar.com`)) && PathPrefix(`/`)",
Service: "default-http-app-1-my-gateway-web-3b78e2feb3295ddd87f0-wrr",
Rule: "(Host(`foo.com`) || HostRegexp(`^[a-zA-Z0-9-]+\\.bar\\.com$`)) && PathPrefix(`/`)",
},
},
Middlewares: map[string]*dynamic.Middleware{},
Services: map[string]*dynamic.Service{
"default-http-app-1-my-gateway-web-2dbd7883f5537db39bca-wrr": {
"default-http-app-1-my-gateway-web-3b78e2feb3295ddd87f0-wrr": {
Weighted: &dynamic.WeightedRoundRobin{
Services: []dynamic.WRRService{
{
@ -860,15 +860,15 @@ func TestLoadHTTPRoutes(t *testing.T) {
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{
"default-http-app-1-my-gateway-web-eb1490f180299bf5ed29": {
"default-http-app-1-my-gateway-web-b0521a61fb43068694b4": {
EntryPoints: []string{"web"},
Service: "default-http-app-1-my-gateway-web-eb1490f180299bf5ed29-wrr",
Rule: "(Host(`foo.com`) || HostRegexp(`{subdomain:[a-zA-Z0-9-]+}.foo.com`)) && PathPrefix(`/`)",
Service: "default-http-app-1-my-gateway-web-b0521a61fb43068694b4-wrr",
Rule: "(Host(`foo.com`) || HostRegexp(`^[a-zA-Z0-9-]+\\.foo\\.com$`)) && PathPrefix(`/`)",
},
},
Middlewares: map[string]*dynamic.Middleware{},
Services: map[string]*dynamic.Service{
"default-http-app-1-my-gateway-web-eb1490f180299bf5ed29-wrr": {
"default-http-app-1-my-gateway-web-b0521a61fb43068694b4-wrr": {
Weighted: &dynamic.WeightedRoundRobin{
Services: []dynamic.WRRService{
{
@ -3011,10 +3011,10 @@ func TestLoadTLSRoutes(t *testing.T) {
},
TCP: &dynamic.TCPConfiguration{
Routers: map[string]*dynamic.TCPRouter{
"default-tls-app-1-my-gateway-tls-339184c3296a9c2c39fa": {
"default-tls-app-1-my-gateway-tls-dfc5c7506ac1b172c8b7": {
EntryPoints: []string{"tls"},
Service: "default-tls-app-1-my-gateway-tls-339184c3296a9c2c39fa-wrr-0",
Rule: "HostSNI(`foo.example.com`,`bar.example.com`)",
Service: "default-tls-app-1-my-gateway-tls-dfc5c7506ac1b172c8b7-wrr-0",
Rule: "(HostSNI(`foo.example.com`) || HostSNI(`bar.example.com`))",
TLS: &dynamic.RouterTCPTLSConfig{
Passthrough: true,
},
@ -3022,7 +3022,7 @@ func TestLoadTLSRoutes(t *testing.T) {
},
Middlewares: map[string]*dynamic.TCPMiddleware{},
Services: map[string]*dynamic.TCPService{
"default-tls-app-1-my-gateway-tls-339184c3296a9c2c39fa-wrr-0": {
"default-tls-app-1-my-gateway-tls-dfc5c7506ac1b172c8b7-wrr-0": {
Weighted: &dynamic.TCPWeightedRoundRobin{
Services: []dynamic.TCPWRRService{
{
@ -4362,7 +4362,7 @@ func Test_hostRule(t *testing.T) {
"Bar",
"Bir",
},
expectedRule: "Host(`Foo`, `Bar`, `Bir`)",
expectedRule: "(Host(`Foo`) || Host(`Bar`) || Host(`Bir`))",
},
{
desc: "Multiple Hosts with empty one",
@ -4389,14 +4389,14 @@ func Test_hostRule(t *testing.T) {
"bar.foo",
"foo.foo",
},
expectedRule: "(Host(`bar.foo`, `foo.foo`) || HostRegexp(`{subdomain:[a-zA-Z0-9-]+}.bar.foo`))",
expectedRule: "(HostRegexp(`^[a-zA-Z0-9-]+\\.bar\\.foo$`) || Host(`bar.foo`) || Host(`foo.foo`))",
},
{
desc: "Host with wildcard",
hostnames: []v1alpha2.Hostname{
"*.bar.foo",
},
expectedRule: "HostRegexp(`{subdomain:[a-zA-Z0-9-]+}.bar.foo`)",
expectedRule: "HostRegexp(`^[a-zA-Z0-9-]+\\.bar\\.foo$`)",
},
{
desc: "Alone wildcard",
@ -4708,7 +4708,7 @@ func Test_hostSNIRule(t *testing.T) {
{
desc: "Some empty hostnames",
hostnames: []v1alpha2.Hostname{"foo", "", "bar"},
expectedRule: "HostSNI(`foo`,`bar`)",
expectedRule: "(HostSNI(`foo`) || HostSNI(`bar`))",
},
{
desc: "Valid hostname",
@ -4718,12 +4718,12 @@ func Test_hostSNIRule(t *testing.T) {
{
desc: "Multiple valid hostnames",
hostnames: []v1alpha2.Hostname{"foo", "bar"},
expectedRule: "HostSNI(`foo`,`bar`)",
expectedRule: "(HostSNI(`foo`) || HostSNI(`bar`))",
},
{
desc: "Multiple overlapping hostnames",
hostnames: []v1alpha2.Hostname{"foo", "bar", "foo", "baz"},
expectedRule: "HostSNI(`foo`,`bar`,`baz`)",
expectedRule: "(HostSNI(`foo`) || HostSNI(`bar`) || HostSNI(`baz`))",
},
}

View file

@ -8,6 +8,7 @@ import (
"math"
"net"
"os"
"regexp"
"sort"
"strconv"
"strings"
@ -168,12 +169,12 @@ func (p *Provider) Provide(configurationChan chan<- dynamic.Message, pool *safe.
}
notify := func(err error, time time.Duration) {
logger.Error().Err(err).Msgf("Provider connection error, retrying in %s", time)
logger.Error().Err(err).Msgf("Provider error, retrying in %s", time)
}
err := backoff.RetryNotify(safe.OperationWithRecover(operation), backoff.WithContext(job.NewBackOff(backoff.NewExponentialBackOff()), ctxPool), notify)
if err != nil {
logger.Error().Err(err).Msg("Cannot connect to Provider")
logger.Error().Err(err).Msg("Cannot retrieve data")
}
})
@ -400,10 +401,11 @@ func (p *Provider) shouldProcessIngress(ingress *networkingv1.Ingress, ingressCl
func buildHostRule(host string) string {
if strings.HasPrefix(host, "*.") {
return "HostRegexp(`" + strings.Replace(host, "*.", "{subdomain:[a-zA-Z0-9-]+}.", 1) + "`)"
host = strings.Replace(regexp.QuoteMeta(host), `\*\.`, `[a-zA-Z0-9-]+\.`, 1)
return fmt.Sprintf("HostRegexp(`^%s$`)", host)
}
return "Host(`" + host + "`)"
return fmt.Sprintf("Host(`%s`)", host)
}
func getCertificates(ctx context.Context, ingress *networkingv1.Ingress, k8sClient Client, tlsConfigs map[string]*tls.CertAndStores) error {

View file

@ -189,8 +189,8 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
HTTP: &dynamic.HTTPConfiguration{
Middlewares: map[string]*dynamic.Middleware{},
Routers: map[string]*dynamic.Router{
"testing-bar-bar-3be6cfd7daba66cf2fdd": {
Rule: "HostRegexp(`{subdomain:[a-zA-Z0-9-]+}.bar`) && PathPrefix(`/bar`)",
"testing-bar-bar-aba9a7d00e9b06a78e16": {
Rule: "HostRegexp(`^[a-zA-Z0-9-]+\\.bar$`) && PathPrefix(`/bar`)",
Service: "testing-service1-80",
},
"testing-bar-bar-636bf36c00fedaab3d44": {
@ -1104,7 +1104,7 @@ func TestLoadConfigurationFromIngresses(t *testing.T) {
Middlewares: map[string]*dynamic.Middleware{},
Routers: map[string]*dynamic.Router{
"testing-foobar-com-bar": {
Rule: "HostRegexp(`{subdomain:[a-zA-Z0-9-]+}.foobar.com`) && PathPrefix(`/bar`)",
Rule: "HostRegexp(`^[a-zA-Z0-9-]+\\.foobar\\.com$`) && PathPrefix(`/bar`)",
Service: "testing-service1-80",
},
},

Some files were not shown because too many files have changed in this diff Show more