Merge branch v2.5 into master
This commit is contained in:
commit
ce47f200d5
70 changed files with 834 additions and 500 deletions
2
.github/workflows/validate.yaml
vendored
2
.github/workflows/validate.yaml
vendored
|
@ -7,7 +7,7 @@ on:
|
||||||
|
|
||||||
env:
|
env:
|
||||||
GO_VERSION: 1.17
|
GO_VERSION: 1.17
|
||||||
GOLANGCI_LINT_VERSION: v1.42.1
|
GOLANGCI_LINT_VERSION: v1.43.0
|
||||||
MISSSPELL_VERSION: v0.3.4
|
MISSSPELL_VERSION: v0.3.4
|
||||||
PRE_TARGET: ""
|
PRE_TARGET: ""
|
||||||
|
|
||||||
|
|
|
@ -96,6 +96,10 @@
|
||||||
"godox", # Too strict
|
"godox", # Too strict
|
||||||
"forcetypeassert", # Too strict
|
"forcetypeassert", # Too strict
|
||||||
"tagliatelle", # Not compatible with current tags.
|
"tagliatelle", # Not compatible with current tags.
|
||||||
|
"varnamelen", # not relevant
|
||||||
|
"nilnil", # not relevant
|
||||||
|
"ireturn", # not relevant
|
||||||
|
"contextcheck", # too many false-positive
|
||||||
]
|
]
|
||||||
|
|
||||||
[issues]
|
[issues]
|
||||||
|
|
|
@ -25,7 +25,7 @@ global_job_config:
|
||||||
- export "PATH=${GOPATH}/bin:${PATH}"
|
- export "PATH=${GOPATH}/bin:${PATH}"
|
||||||
- mkdir -vp "${SEMAPHORE_GIT_DIR}" "${GOPATH}/bin"
|
- mkdir -vp "${SEMAPHORE_GIT_DIR}" "${GOPATH}/bin"
|
||||||
- export GOPROXY=https://proxy.golang.org,direct
|
- export GOPROXY=https://proxy.golang.org,direct
|
||||||
- curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b "${GOPATH}/bin" v1.42.1
|
- curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b "${GOPATH}/bin" v1.43.0
|
||||||
- curl -sfL https://install.goreleaser.com/github.com/goreleaser/goreleaser.sh | bash -s -- -b "${GOPATH}/bin"
|
- curl -sfL https://install.goreleaser.com/github.com/goreleaser/goreleaser.sh | bash -s -- -b "${GOPATH}/bin"
|
||||||
- checkout
|
- checkout
|
||||||
- cache restore traefik-$(checksum go.sum)
|
- cache restore traefik-$(checksum go.sum)
|
||||||
|
|
30
CHANGELOG.md
30
CHANGELOG.md
|
@ -1,3 +1,33 @@
|
||||||
|
## [v2.5.4](https://github.com/traefik/traefik/tree/v2.5.4) (2021-11-08)
|
||||||
|
[All Commits](https://github.com/traefik/traefik/compare/v2.5.3...v2.5.4)
|
||||||
|
|
||||||
|
**Bug fixes:**
|
||||||
|
- **[acme]** Update go-acme/lego to v4.5.0 ([#8481](https://github.com/traefik/traefik/pull/8481) by [ldez](https://github.com/ldez))
|
||||||
|
- **[k8s/crd,k8s]** fix: add missing RequireAnyClientCert value to TLSOption CRD ([#8464](https://github.com/traefik/traefik/pull/8464) by [kevinpollet](https://github.com/kevinpollet))
|
||||||
|
- **[k8s/crd,k8s]** fix: normalize middleware names in ingress route config ([#8484](https://github.com/traefik/traefik/pull/8484) by [aaronraff](https://github.com/aaronraff))
|
||||||
|
- **[middleware,provider,tls]** fix: do not require a TLS client cert when InsecureSkipVerify is false ([#8525](https://github.com/traefik/traefik/pull/8525) by [kevinpollet](https://github.com/kevinpollet))
|
||||||
|
- **[middleware,tls]** fix: use host's root CA set if ClientTLS ca is not defined ([#8545](https://github.com/traefik/traefik/pull/8545) by [kevinpollet](https://github.com/kevinpollet))
|
||||||
|
- **[middleware]** fix: forward request Host to errors middleware service ([#8460](https://github.com/traefik/traefik/pull/8460) by [kevinpollet](https://github.com/kevinpollet))
|
||||||
|
- **[middleware]** fix: use EscapedPath as header value when RawPath is empty ([#8251](https://github.com/traefik/traefik/pull/8251) by [dtomcej](https://github.com/dtomcej))
|
||||||
|
- **[tcp,udp]** fix: TCP/UDP wrr when all servers have a weight set to 0 ([#8553](https://github.com/traefik/traefik/pull/8553) by [tomMoulard](https://github.com/tomMoulard))
|
||||||
|
- **[webui]** fix: bug parsing weighted service provider name ([#8522](https://github.com/traefik/traefik/pull/8522) by [cocoanton](https://github.com/cocoanton))
|
||||||
|
|
||||||
|
**Documentation:**
|
||||||
|
- **[acme]** docs: remove quotes in certificatesresolvers CLI examples ([#8544](https://github.com/traefik/traefik/pull/8544) by [rdxmb](https://github.com/rdxmb))
|
||||||
|
- **[k8s/ingress,k8s]** docs: clarify usage for cross provider references in Kubernetes ingress annotations ([#8536](https://github.com/traefik/traefik/pull/8536) by [rtribotte](https://github.com/rtribotte))
|
||||||
|
- **[k8s/ingress]** docs: networking.k8s.io/v1beta1 to networking.k8s.io/v1 ([#8523](https://github.com/traefik/traefik/pull/8523) by [pmareke](https://github.com/pmareke))
|
||||||
|
- **[k8s]** docs: replace links to French translation of k8s docs with English ones ([#8457](https://github.com/traefik/traefik/pull/8457) by [FoseFx](https://github.com/FoseFx))
|
||||||
|
- **[k8s]** docs: remove non-working kind config in IngressRouteTCP/UDP examples ([#8538](https://github.com/traefik/traefik/pull/8538) by [kevinpollet](https://github.com/kevinpollet))
|
||||||
|
- **[kv]** docs: fix typo in KV providers documentation ([#8477](https://github.com/traefik/traefik/pull/8477) by [rondoe](https://github.com/rondoe))
|
||||||
|
- **[metrics]** docs: fix typo in addRoutersLabels option title ([#8561](https://github.com/traefik/traefik/pull/8561) by [kevinpollet](https://github.com/kevinpollet))
|
||||||
|
- **[middleware]** fix: sourceCriterion documentation for InFlightReq and RateLimit middlewares ([#8524](https://github.com/traefik/traefik/pull/8524) by [pmareke](https://github.com/pmareke))
|
||||||
|
- **[middleware]** Mention escaping escape characters in YAML for regex usage ([#8496](https://github.com/traefik/traefik/pull/8496) by [JackMorganNZ](https://github.com/JackMorganNZ))
|
||||||
|
- **[rules]** docs: add named groups details to Regexp Syntax section ([#8559](https://github.com/traefik/traefik/pull/8559) by [kerrsmith](https://github.com/kerrsmith))
|
||||||
|
- **[tracing]** docs: reword tracing config descriptions to be consistent ([#8473](https://github.com/traefik/traefik/pull/8473) by [kevinpollet](https://github.com/kevinpollet))
|
||||||
|
- docs: remove link to microbadger.com ([#8555](https://github.com/traefik/traefik/pull/8555) by [CrispyBaguette](https://github.com/CrispyBaguette))
|
||||||
|
- docs: remove http scheme urls in documentation ([#8507](https://github.com/traefik/traefik/pull/8507) by [tomMoulard](https://github.com/tomMoulard))
|
||||||
|
- docs: update traefik image version ([#8533](https://github.com/traefik/traefik/pull/8533) by [tomMoulard](https://github.com/tomMoulard))
|
||||||
|
|
||||||
## [v2.5.3](https://github.com/traefik/traefik/tree/v2.5.3) (2021-09-20)
|
## [v2.5.3](https://github.com/traefik/traefik/tree/v2.5.3) (2021-09-20)
|
||||||
[All Commits](https://github.com/traefik/traefik/compare/v2.5.2...v2.5.3)
|
[All Commits](https://github.com/traefik/traefik/compare/v2.5.2...v2.5.3)
|
||||||
|
|
||||||
|
|
|
@ -63,7 +63,7 @@ _(But if you'd rather configure some of your routes manually, Traefik supports t
|
||||||
- Keeps access logs (JSON, CLF)
|
- Keeps access logs (JSON, CLF)
|
||||||
- Fast
|
- Fast
|
||||||
- Exposes a Rest API
|
- Exposes a Rest API
|
||||||
- Packaged as a single binary file (made with :heart: with go) and available as a [tiny](https://microbadger.com/images/traefik) [official](https://hub.docker.com/r/_/traefik/) docker image
|
- Packaged as a single binary file (made with :heart: with go) and available as an [official](https://hub.docker.com/r/_/traefik/) docker image
|
||||||
|
|
||||||
|
|
||||||
## Supported Backends
|
## Supported Backends
|
||||||
|
|
|
@ -14,7 +14,7 @@ RUN mkdir -p /usr/local/bin \
|
||||||
| tar -xzC /usr/local/bin --transform 's#^.+/##x'
|
| tar -xzC /usr/local/bin --transform 's#^.+/##x'
|
||||||
|
|
||||||
# Download golangci-lint binary to bin folder in $GOPATH
|
# Download golangci-lint binary to bin folder in $GOPATH
|
||||||
RUN curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | bash -s -- -b $GOPATH/bin v1.42.1
|
RUN curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | bash -s -- -b $GOPATH/bin v1.43.0
|
||||||
|
|
||||||
# Download misspell binary to bin folder in $GOPATH
|
# Download misspell binary to bin folder in $GOPATH
|
||||||
RUN curl -sfL https://raw.githubusercontent.com/client9/misspell/master/install-misspell.sh | bash -s -- -b $GOPATH/bin v0.3.4
|
RUN curl -sfL https://raw.githubusercontent.com/client9/misspell/master/install-misspell.sh | bash -s -- -b $GOPATH/bin v0.3.4
|
||||||
|
|
|
@ -24,7 +24,7 @@ For more details, go to the [Docker provider documentation](../providers/docker.
|
||||||
!!! tip
|
!!! tip
|
||||||
|
|
||||||
* Prefer a fixed version than the latest that could be an unexpected version.
|
* Prefer a fixed version than the latest that could be an unexpected version.
|
||||||
ex: `traefik:v2.1.4`
|
ex: `traefik:v2.5`
|
||||||
* Docker images are based from the [Alpine Linux Official image](https://hub.docker.com/_/alpine).
|
* 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.
|
* Any orchestrator using docker images can fetch the official Traefik docker image.
|
||||||
|
|
||||||
|
@ -101,13 +101,13 @@ helm install traefik traefik/traefik
|
||||||
|
|
||||||
This HelmChart does not expose the Traefik dashboard by default, for security concerns.
|
This HelmChart does not expose the Traefik dashboard by default, for security concerns.
|
||||||
Thus, there are multiple ways to expose the dashboard.
|
Thus, there are multiple ways to expose the dashboard.
|
||||||
For instance, the dashboard access could be achieved through a port-forward :
|
For instance, the dashboard access could be achieved through a port-forward:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
kubectl port-forward $(kubectl get pods --selector "app.kubernetes.io/name=traefik" --output=name) 9000:9000
|
kubectl port-forward $(kubectl get pods --selector "app.kubernetes.io/name=traefik" --output=name) 9000:9000
|
||||||
```
|
```
|
||||||
|
|
||||||
Accessible with the url: http://127.0.0.1:9000/dashboard/
|
It can then be reached at: `http://127.0.0.1:9000/dashboard/`
|
||||||
|
|
||||||
Another way would be to apply your own configuration, for instance,
|
Another way would be to apply your own configuration, for instance,
|
||||||
by defining and applying an IngressRoute CRD (`kubectl apply -f dashboard.yaml`):
|
by defining and applying an IngressRoute CRD (`kubectl apply -f dashboard.yaml`):
|
||||||
|
|
|
@ -36,7 +36,7 @@ Start your `reverse-proxy` with the following command:
|
||||||
docker-compose up -d reverse-proxy
|
docker-compose up -d reverse-proxy
|
||||||
```
|
```
|
||||||
|
|
||||||
You can open a browser and go to <http://localhost:8080/api/rawdata> to see Traefik's API rawdata (we'll go back there once we have launched a service in step 2).
|
You can open a browser and go to `http://localhost:8080/api/rawdata` to see Traefik's API rawdata (we'll go back there once we have launched a service in step 2).
|
||||||
|
|
||||||
## Traefik Detects New Services and Creates the Route for You
|
## Traefik Detects New Services and Creates the Route for You
|
||||||
|
|
||||||
|
@ -61,7 +61,7 @@ Start the `whoami` service with the following command:
|
||||||
docker-compose up -d whoami
|
docker-compose up -d whoami
|
||||||
```
|
```
|
||||||
|
|
||||||
Go back to your browser (<http://localhost:8080/api/rawdata>) and see that Traefik has automatically detected the new container and updated its own configuration.
|
Go back to your browser (`http://localhost:8080/api/rawdata`) and see that Traefik has automatically detected the new container and updated its own configuration.
|
||||||
|
|
||||||
When Traefik detects new services, it creates the corresponding routes so you can call them ... _let's see!_ (Here, we're using curl)
|
When Traefik detects new services, it creates the corresponding routes so you can call them ... _let's see!_ (Here, we're using curl)
|
||||||
|
|
||||||
|
@ -85,7 +85,7 @@ Run more instances of your `whoami` service with the following command:
|
||||||
docker-compose up -d --scale whoami=2
|
docker-compose up -d --scale whoami=2
|
||||||
```
|
```
|
||||||
|
|
||||||
Go back to your browser (<http://localhost:8080/api/rawdata>) and see that Traefik has automatically detected the new instance of the container.
|
Go back to your browser (`http://localhost:8080/api/rawdata`) and see that Traefik has automatically detected the new instance of the container.
|
||||||
|
|
||||||
Finally, see that Traefik load-balances between the two instances of your service by running the following command twice:
|
Finally, see that Traefik load-balances between the two instances of your service by running the following command twice:
|
||||||
|
|
||||||
|
|
|
@ -560,7 +560,7 @@ certificatesResolvers:
|
||||||
|
|
||||||
```bash tab="CLI"
|
```bash tab="CLI"
|
||||||
# ...
|
# ...
|
||||||
--certificatesresolvers.myresolver.acme.preferredChain="ISRG Root X1"
|
--certificatesresolvers.myresolver.acme.preferredChain=ISRG Root X1
|
||||||
# ...
|
# ...
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -588,7 +588,7 @@ certificatesResolvers:
|
||||||
|
|
||||||
```bash tab="CLI"
|
```bash tab="CLI"
|
||||||
# ...
|
# ...
|
||||||
--certificatesresolvers.myresolver.acme.keyType="RSA4096"
|
--certificatesresolvers.myresolver.acme.keyType=RSA4096
|
||||||
# ...
|
# ...
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
@ -353,7 +353,8 @@ The `tls` option is the TLS configuration from Traefik to the authentication ser
|
||||||
|
|
||||||
#### `tls.ca`
|
#### `tls.ca`
|
||||||
|
|
||||||
Certificate Authority used for the secured connection to the authentication server.
|
Certificate Authority used for the secured connection to the authentication server,
|
||||||
|
defaults to the system bundle.
|
||||||
|
|
||||||
```yaml tab="Docker"
|
```yaml tab="Docker"
|
||||||
labels:
|
labels:
|
||||||
|
|
|
@ -115,7 +115,7 @@ http:
|
||||||
### `sourceCriterion`
|
### `sourceCriterion`
|
||||||
|
|
||||||
The `sourceCriterion` option defines what criterion is used to group requests as originating from a common source.
|
The `sourceCriterion` option defines what criterion is used to group requests as originating from a common source.
|
||||||
The precedence order is `ipStrategy`, then `requestHeaderName`, then `requestHost`.
|
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`.
|
If none are set, the default is to use the `requestHost`.
|
||||||
|
|
||||||
#### `sourceCriterion.ipStrategy`
|
#### `sourceCriterion.ipStrategy`
|
||||||
|
|
|
@ -250,7 +250,7 @@ http:
|
||||||
### `sourceCriterion`
|
### `sourceCriterion`
|
||||||
|
|
||||||
The `sourceCriterion` option defines what criterion is used to group requests as originating from a common source.
|
The `sourceCriterion` option defines what criterion is used to group requests as originating from a common source.
|
||||||
The precedence order is `ipStrategy`, then `requestHeaderName`, then `requestHost`.
|
If several strategies are defined at the same time, an error will be raised.
|
||||||
If none are set, the default is to use the request's remote address field (as an `ipStrategy`).
|
If none are set, the default is to use the request's remote address field (as an `ipStrategy`).
|
||||||
|
|
||||||
#### `sourceCriterion.ipStrategy`
|
#### `sourceCriterion.ipStrategy`
|
||||||
|
|
|
@ -179,7 +179,7 @@ To enable HTTPS, it is not sufficient anymore to only rely on a TLS section in t
|
||||||
|
|
||||||
#### Expose an Ingress on 80 and 443
|
#### Expose an Ingress on 80 and 443
|
||||||
|
|
||||||
Define the default TLS configuration on the HTTPS entry point.
|
Define the default TLS configuration on the HTTPS entry point.
|
||||||
|
|
||||||
```yaml tab="Ingress"
|
```yaml tab="Ingress"
|
||||||
kind: Ingress
|
kind: Ingress
|
||||||
|
@ -335,7 +335,7 @@ The file parser has been changed, since v2.3 the unknown options/fields in a dyn
|
||||||
### IngressClass
|
### IngressClass
|
||||||
|
|
||||||
In `v2.3`, the support of `IngressClass`, which is available since Kubernetes version `1.18`, has been introduced.
|
In `v2.3`, the support of `IngressClass`, which is available since Kubernetes version `1.18`, has been introduced.
|
||||||
In order to be able to use this new resource the [Kubernetes RBAC](../reference/dynamic-configuration/kubernetes-crd.md#rbac) must be updated.
|
In order to be able to use this new resource the [Kubernetes RBAC](../reference/dynamic-configuration/kubernetes-crd.md#rbac) must be updated.
|
||||||
|
|
||||||
## v2.3 to v2.4
|
## v2.3 to v2.4
|
||||||
|
|
||||||
|
@ -350,7 +350,7 @@ It is therefore necessary to update [RBAC](../reference/dynamic-configuration/ku
|
||||||
|
|
||||||
In `v2.4.8`, we introduced a new check on domain names used in HTTP router rule `Host` and `HostRegexp` expressions,
|
In `v2.4.8`, we introduced a new check on domain names used in HTTP router rule `Host` and `HostRegexp` expressions,
|
||||||
and in TCP router rule `HostSNI` expression.
|
and in TCP router rule `HostSNI` expression.
|
||||||
This check ensures that provided domain names don't contain non-ASCII characters.
|
This check ensures that provided domain names don't contain non-ASCII characters.
|
||||||
If not, an error is raised, and the associated router will be shown as invalid in the dashboard.
|
If not, an error is raised, and the associated router will be shown as invalid in the dashboard.
|
||||||
|
|
||||||
This new behavior is intended to show what was failing silently previously and to help troubleshooting configuration issues.
|
This new behavior is intended to show what was failing silently previously and to help troubleshooting configuration issues.
|
||||||
|
@ -380,8 +380,8 @@ To allow it, the `allowExternalNameServices` option should be set to `true`.
|
||||||
|
|
||||||
### Kubernetes CRD
|
### Kubernetes CRD
|
||||||
|
|
||||||
In `v2.5`, the [Traefik CRDs](../reference/dynamic-configuration/kubernetes-crd.md#definitions) have been updated to support the new API version `apiextensions.k8s.io/v1`.
|
In `v2.5`, the [Traefik CRDs](../reference/dynamic-configuration/kubernetes-crd.md#definitions) have been updated to support the new API version `apiextensions.k8s.io/v1`.
|
||||||
As required by `apiextensions.k8s.io/v1`, we have included the OpenAPI validation schema.
|
As required by `apiextensions.k8s.io/v1`, we have included the OpenAPI validation schema.
|
||||||
|
|
||||||
After deploying the new [Traefik CRDs](../reference/dynamic-configuration/kubernetes-crd.md#definitions), the resources will be validated only on creation or update.
|
After deploying the new [Traefik CRDs](../reference/dynamic-configuration/kubernetes-crd.md#definitions), the resources will be validated only on creation or update.
|
||||||
|
|
||||||
|
@ -420,7 +420,7 @@ the legacy behavior related to the CommonName field can not be enabled at all an
|
||||||
|
|
||||||
### Errors middleware
|
### Errors middleware
|
||||||
|
|
||||||
In `v2.5.4`, when the errors service is configured with the [`PassHostHeader`](../routing/services/index.md#pass-host-header) option to `true` (default),
|
In `v2.5.4`, when the errors service is configured with the [`PassHostHeader`](../routing/services/index.md#pass-host-header) option to `true` (default),
|
||||||
the forwarded Host header value is now set to the client request Host value and not `0.0.0.0`.
|
the forwarded Host header value is now set to the client request Host value and not `0.0.0.0`.
|
||||||
Check out the [Errors middleware](../middlewares/http/errorpages.md#service) documentation for more details.
|
Check out the [Errors middleware](../middlewares/http/errorpages.md#service) documentation for more details.
|
||||||
|
|
||||||
|
|
|
@ -247,7 +247,7 @@ version: "3.7"
|
||||||
|
|
||||||
services:
|
services:
|
||||||
traefik:
|
traefik:
|
||||||
image: traefik:v2.2
|
image: traefik:v2.5
|
||||||
environment:
|
environment:
|
||||||
- TZ=US/Alaska
|
- TZ=US/Alaska
|
||||||
command:
|
command:
|
||||||
|
|
|
@ -59,7 +59,7 @@ metrics:
|
||||||
```bash tab="CLI"
|
```bash tab="CLI"
|
||||||
--metrics.datadog.addEntryPointsLabels=true
|
--metrics.datadog.addEntryPointsLabels=true
|
||||||
```
|
```
|
||||||
#### `AddRoutersLabels`
|
#### `addRoutersLabels`
|
||||||
|
|
||||||
_Optional, Default=false_
|
_Optional, Default=false_
|
||||||
|
|
||||||
|
|
|
@ -170,7 +170,7 @@ metrics:
|
||||||
--metrics.influxdb.addEntryPointsLabels=true
|
--metrics.influxdb.addEntryPointsLabels=true
|
||||||
```
|
```
|
||||||
|
|
||||||
#### `AddRoutersLabels`
|
#### `addRoutersLabels`
|
||||||
|
|
||||||
_Optional, Default=false_
|
_Optional, Default=false_
|
||||||
|
|
||||||
|
|
|
@ -64,7 +64,7 @@ metrics:
|
||||||
--metrics.prometheus.addEntryPointsLabels=true
|
--metrics.prometheus.addEntryPointsLabels=true
|
||||||
```
|
```
|
||||||
|
|
||||||
#### `AddRoutersLabels`
|
#### `addRoutersLabels`
|
||||||
|
|
||||||
_Optional, Default=false_
|
_Optional, Default=false_
|
||||||
|
|
||||||
|
|
|
@ -60,7 +60,7 @@ metrics:
|
||||||
--metrics.statsd.addEntryPointsLabels=true
|
--metrics.statsd.addEntryPointsLabels=true
|
||||||
```
|
```
|
||||||
|
|
||||||
#### `AddRoutersLabels`
|
#### `addRoutersLabels`
|
||||||
|
|
||||||
_Optional, Default=false_
|
_Optional, Default=false_
|
||||||
|
|
||||||
|
|
|
@ -368,7 +368,8 @@ Defines TLS options for Consul server endpoint.
|
||||||
|
|
||||||
_Optional_
|
_Optional_
|
||||||
|
|
||||||
`ca` is the path to the CA certificate used for Consul communication, defaults to the system bundle if not specified.
|
Certificate Authority used for the secure connection to Consul,
|
||||||
|
defaults to the system bundle.
|
||||||
|
|
||||||
```yaml tab="File (YAML)"
|
```yaml tab="File (YAML)"
|
||||||
providers:
|
providers:
|
||||||
|
|
|
@ -106,7 +106,8 @@ _Optional_
|
||||||
|
|
||||||
#### `tls.ca`
|
#### `tls.ca`
|
||||||
|
|
||||||
Certificate Authority used for the secure connection to Consul.
|
Certificate Authority used for the secure connection to Consul,
|
||||||
|
defaults to the system bundle.
|
||||||
|
|
||||||
```yaml tab="File (YAML)"
|
```yaml tab="File (YAML)"
|
||||||
providers:
|
providers:
|
||||||
|
|
|
@ -615,7 +615,8 @@ _Optional_
|
||||||
|
|
||||||
#### `tls.ca`
|
#### `tls.ca`
|
||||||
|
|
||||||
Certificate Authority used for the secure connection to Docker.
|
Certificate Authority used for the secure connection to Docker,
|
||||||
|
defaults to the system bundle.
|
||||||
|
|
||||||
```yaml tab="File (YAML)"
|
```yaml tab="File (YAML)"
|
||||||
providers:
|
providers:
|
||||||
|
|
|
@ -106,7 +106,8 @@ _Optional_
|
||||||
|
|
||||||
#### `tls.ca`
|
#### `tls.ca`
|
||||||
|
|
||||||
Certificate Authority used for the secure connection to etcd.
|
Certificate Authority used for the secure connection to etcd,
|
||||||
|
defaults to the system bundle.
|
||||||
|
|
||||||
```yaml tab="File (YAML)"
|
```yaml tab="File (YAML)"
|
||||||
providers:
|
providers:
|
||||||
|
|
|
@ -78,7 +78,8 @@ _Optional_
|
||||||
|
|
||||||
#### `tls.ca`
|
#### `tls.ca`
|
||||||
|
|
||||||
Certificate Authority used for the secure connection to the configured endpoint.
|
Certificate Authority used for the secure connection to the configured endpoint,
|
||||||
|
defaults to the system bundle.
|
||||||
|
|
||||||
```yaml tab="File (YAML)"
|
```yaml tab="File (YAML)"
|
||||||
providers:
|
providers:
|
||||||
|
|
|
@ -62,7 +62,7 @@ Previous versions of Traefik used a [KV store](https://doc.traefik.io/traefik/v1
|
||||||
|
|
||||||
If you need Let's Encrypt with HA in a Kubernetes environment, we recommend using [Traefik Enterprise](https://traefik.io/traefik-enterprise/), which includes distributed Let's Encrypt as a supported feature.
|
If you need Let's Encrypt with HA in a Kubernetes environment, we recommend using [Traefik Enterprise](https://traefik.io/traefik-enterprise/), which includes distributed Let's Encrypt as a supported feature.
|
||||||
|
|
||||||
If you want to keep using Traefik Proxy, high availability for Let's Encrypt can be achieved by using a Certificate Controller such as [Cert-Manager](https://docs.cert-manager.io/en/latest/index.html).
|
If you want to keep using Traefik Proxy, high availability for Let's Encrypt can be achieved by using a Certificate Controller such as [Cert-Manager](https://cert-manager.io/docs/).
|
||||||
When using Cert-Manager to manage certificates, it creates secrets in your namespaces that can be referenced as TLS secrets in your [ingress objects](https://kubernetes.io/docs/concepts/services-networking/ingress/#tls).
|
When using Cert-Manager to manage certificates, it creates secrets in your namespaces that can be referenced as TLS secrets in your [ingress objects](https://kubernetes.io/docs/concepts/services-networking/ingress/#tls).
|
||||||
When using the Traefik Kubernetes CRD Provider, unfortunately Cert-Manager cannot yet interface directly with the CRDs.
|
When using the Traefik Kubernetes CRD Provider, unfortunately Cert-Manager cannot yet interface directly with the CRDs.
|
||||||
A workaround is to enable the [Kubernetes Ingress provider](./kubernetes-ingress.md) to allow Cert-Manager to create ingress objects to complete the challenges.
|
A workaround is to enable the [Kubernetes Ingress provider](./kubernetes-ingress.md) to allow Cert-Manager to create ingress objects to complete the challenges.
|
||||||
|
|
|
@ -36,10 +36,10 @@ and derives the corresponding dynamic configuration from it,
|
||||||
which in turn creates the resulting routers, services, handlers, etc.
|
which in turn creates the resulting routers, services, handlers, etc.
|
||||||
|
|
||||||
```yaml tab="Ingress"
|
```yaml tab="Ingress"
|
||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
kind: Ingress
|
kind: Ingress
|
||||||
apiVersion: networking.k8s.io/v1beta1
|
|
||||||
metadata:
|
metadata:
|
||||||
name: "foo"
|
name: foo
|
||||||
namespace: production
|
namespace: production
|
||||||
|
|
||||||
spec:
|
spec:
|
||||||
|
@ -48,20 +48,26 @@ spec:
|
||||||
http:
|
http:
|
||||||
paths:
|
paths:
|
||||||
- path: /bar
|
- path: /bar
|
||||||
|
pathType: Exact
|
||||||
backend:
|
backend:
|
||||||
serviceName: service1
|
service:
|
||||||
servicePort: 80
|
name: service1
|
||||||
|
port:
|
||||||
|
number: 80
|
||||||
- path: /foo
|
- path: /foo
|
||||||
|
pathType: Exact
|
||||||
backend:
|
backend:
|
||||||
serviceName: service1
|
service:
|
||||||
servicePort: 80
|
name: service1
|
||||||
|
port:
|
||||||
|
number: 80
|
||||||
```
|
```
|
||||||
|
|
||||||
```yaml tab="Ingress Kubernetes v1.19+"
|
```yaml tab="Ingress v1beta1 (deprecated)"
|
||||||
|
apiVersion: networking.k8s.io/v1beta1
|
||||||
kind: Ingress
|
kind: Ingress
|
||||||
apiVersion: networking.k8s.io/v1
|
|
||||||
metadata:
|
metadata:
|
||||||
name: "foo"
|
name: foo
|
||||||
namespace: production
|
namespace: production
|
||||||
|
|
||||||
spec:
|
spec:
|
||||||
|
@ -70,19 +76,13 @@ spec:
|
||||||
http:
|
http:
|
||||||
paths:
|
paths:
|
||||||
- path: /bar
|
- path: /bar
|
||||||
pathType: Exact
|
|
||||||
backend:
|
backend:
|
||||||
service:
|
serviceName: service1
|
||||||
name: service1
|
servicePort: 80
|
||||||
port:
|
|
||||||
number: 80
|
|
||||||
- path: /foo
|
- path: /foo
|
||||||
pathType: Exact
|
|
||||||
backend:
|
backend:
|
||||||
service:
|
serviceName: service1
|
||||||
name: service1
|
servicePort: 80
|
||||||
port:
|
|
||||||
number: 80
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## LetsEncrypt Support with the Ingress Provider
|
## LetsEncrypt Support with the Ingress Provider
|
||||||
|
@ -104,7 +104,7 @@ If you need Let's Encrypt with high availability in a Kubernetes environment,
|
||||||
we recommend using [Traefik Enterprise](https://traefik.io/traefik-enterprise/) which includes distributed Let's Encrypt as a supported feature.
|
we recommend using [Traefik Enterprise](https://traefik.io/traefik-enterprise/) which includes distributed Let's Encrypt as a supported feature.
|
||||||
|
|
||||||
If you want to keep using Traefik Proxy,
|
If you want to keep using Traefik Proxy,
|
||||||
LetsEncrypt HA can be achieved by using a Certificate Controller such as [Cert-Manager](https://docs.cert-manager.io/en/latest/index.html).
|
LetsEncrypt HA can be achieved by using a Certificate Controller such as [Cert-Manager](https://cert-manager.io/docs/).
|
||||||
When using Cert-Manager to manage certificates,
|
When using Cert-Manager to manage certificates,
|
||||||
it creates secrets in your namespaces that can be referenced as TLS secrets in your [ingress objects](https://kubernetes.io/docs/concepts/services-networking/ingress/#tls).
|
it creates secrets in your namespaces that can be referenced as TLS secrets in your [ingress objects](https://kubernetes.io/docs/concepts/services-networking/ingress/#tls).
|
||||||
|
|
||||||
|
@ -272,19 +272,19 @@ Otherwise, Ingresses missing the annotation, having an empty value, or the value
|
||||||
```
|
```
|
||||||
|
|
||||||
```yaml tab="Ingress"
|
```yaml tab="Ingress"
|
||||||
apiVersion: "networking.k8s.io/v1beta1"
|
apiVersion: networking.k8s.io/v1beta1
|
||||||
kind: "Ingress"
|
kind: Ingress
|
||||||
metadata:
|
metadata:
|
||||||
name: "example-ingress"
|
name: example-ingress
|
||||||
spec:
|
spec:
|
||||||
ingressClassName: "traefik-lb"
|
ingressClassName: traefik-lb
|
||||||
rules:
|
rules:
|
||||||
- host: "*.example.com"
|
- host: "*.example.com"
|
||||||
http:
|
http:
|
||||||
paths:
|
paths:
|
||||||
- path: "/example"
|
- path: /example
|
||||||
backend:
|
backend:
|
||||||
serviceName: "example-service"
|
serviceName: example-service
|
||||||
servicePort: 80
|
servicePort: 80
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -303,21 +303,21 @@ Otherwise, Ingresses missing the annotation, having an empty value, or the value
|
||||||
```
|
```
|
||||||
|
|
||||||
```yaml tab="Ingress"
|
```yaml tab="Ingress"
|
||||||
apiVersion: "networking.k8s.io/v1"
|
apiVersion: networking.k8s.io/v1
|
||||||
kind: "Ingress"
|
kind: Ingress
|
||||||
metadata:
|
metadata:
|
||||||
name: "example-ingress"
|
name: example-ingress
|
||||||
spec:
|
spec:
|
||||||
ingressClassName: "traefik-lb"
|
ingressClassName: traefik-lb
|
||||||
rules:
|
rules:
|
||||||
- host: "*.example.com"
|
- host: "*.example.com"
|
||||||
http:
|
http:
|
||||||
paths:
|
paths:
|
||||||
- path: "/example"
|
- path: /example
|
||||||
pathType: Exact
|
pathType: Exact
|
||||||
backend:
|
backend:
|
||||||
service:
|
service:
|
||||||
name: "example-service"
|
name: example-service
|
||||||
port:
|
port:
|
||||||
number: 80
|
number: 80
|
||||||
```
|
```
|
||||||
|
|
|
@ -406,7 +406,8 @@ _Optional_
|
||||||
|
|
||||||
#### `tls.ca`
|
#### `tls.ca`
|
||||||
|
|
||||||
Certificate Authority used for the secure connection to Marathon.
|
Certificate Authority used for the secure connection to Marathon,
|
||||||
|
defaults to the system bundle.
|
||||||
|
|
||||||
```yaml tab="File (YAML)"
|
```yaml tab="File (YAML)"
|
||||||
providers:
|
providers:
|
||||||
|
|
|
@ -106,7 +106,8 @@ _Optional_
|
||||||
|
|
||||||
#### `tls.ca`
|
#### `tls.ca`
|
||||||
|
|
||||||
Certificate Authority used for the secure connection to Redis.
|
Certificate Authority used for the secure connection to Redis,
|
||||||
|
defaults to the system bundle.
|
||||||
|
|
||||||
```yaml tab="File (YAML)"
|
```yaml tab="File (YAML)"
|
||||||
providers:
|
providers:
|
||||||
|
|
|
@ -106,7 +106,8 @@ _Optional_
|
||||||
|
|
||||||
#### `tls.ca`
|
#### `tls.ca`
|
||||||
|
|
||||||
Certificate Authority used for the secure connection to ZooKeeper.
|
Certificate Authority used for the secure connection to ZooKeeper,
|
||||||
|
defaults to the system bundle.
|
||||||
|
|
||||||
```yaml tab="File (YAML)"
|
```yaml tab="File (YAML)"
|
||||||
providers:
|
providers:
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
kind: ClusterRole
|
kind: ClusterRole
|
||||||
apiVersion: rbac.authorization.k8s.io/v1beta1
|
|
||||||
metadata:
|
metadata:
|
||||||
name: traefik-ingress-controller
|
name: traefik-ingress-controller
|
||||||
|
|
||||||
|
@ -48,8 +48,8 @@ rules:
|
||||||
- watch
|
- watch
|
||||||
|
|
||||||
---
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
kind: ClusterRoleBinding
|
kind: ClusterRoleBinding
|
||||||
apiVersion: rbac.authorization.k8s.io/v1beta1
|
|
||||||
metadata:
|
metadata:
|
||||||
name: traefik-ingress-controller
|
name: traefik-ingress-controller
|
||||||
|
|
||||||
|
|
|
@ -45,8 +45,8 @@ rules:
|
||||||
- update
|
- update
|
||||||
|
|
||||||
---
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
kind: ClusterRoleBinding
|
kind: ClusterRoleBinding
|
||||||
apiVersion: rbac.authorization.k8s.io/v1beta1
|
|
||||||
metadata:
|
metadata:
|
||||||
name: gateway-controller
|
name: gateway-controller
|
||||||
|
|
||||||
|
|
|
@ -400,7 +400,7 @@ spec:
|
||||||
info configuration.
|
info configuration.
|
||||||
properties:
|
properties:
|
||||||
issuer:
|
issuer:
|
||||||
description: TLSCLientCertificateIssuerDNInfo holds the client
|
description: TLSClientCertificateIssuerDNInfo holds the client
|
||||||
TLS certificate distinguished name info configuration. cf
|
TLS certificate distinguished name info configuration. cf
|
||||||
https://tools.ietf.org/html/rfc3739
|
https://tools.ietf.org/html/rfc3739
|
||||||
properties:
|
properties:
|
||||||
|
@ -428,7 +428,7 @@ spec:
|
||||||
serialNumber:
|
serialNumber:
|
||||||
type: boolean
|
type: boolean
|
||||||
subject:
|
subject:
|
||||||
description: TLSCLientCertificateSubjectDNInfo holds the client
|
description: TLSClientCertificateSubjectDNInfo holds the client
|
||||||
TLS certificate distinguished name info configuration. cf
|
TLS certificate distinguished name info configuration. cf
|
||||||
https://tools.ietf.org/html/rfc3739
|
https://tools.ietf.org/html/rfc3739
|
||||||
properties:
|
properties:
|
||||||
|
|
|
@ -131,7 +131,6 @@ The Kubernetes Ingress Controller, The Custom Resource Way.
|
||||||
- tcpep
|
- tcpep
|
||||||
routes:
|
routes:
|
||||||
- match: HostSNI(`bar`)
|
- match: HostSNI(`bar`)
|
||||||
kind: Rule
|
|
||||||
services:
|
services:
|
||||||
- name: whoamitcp
|
- name: whoamitcp
|
||||||
port: 8080
|
port: 8080
|
||||||
|
@ -147,8 +146,7 @@ The Kubernetes Ingress Controller, The Custom Resource Way.
|
||||||
entryPoints:
|
entryPoints:
|
||||||
- udpep
|
- udpep
|
||||||
routes:
|
routes:
|
||||||
- kind: Rule
|
- services:
|
||||||
services:
|
|
||||||
- name: whoamiudp
|
- name: whoamiudp
|
||||||
port: 8080
|
port: 8080
|
||||||
```
|
```
|
||||||
|
@ -1224,7 +1222,6 @@ Register the `IngressRouteTCP` [kind](../../reference/dynamic-configuration/kube
|
||||||
|
|
||||||
routes:
|
routes:
|
||||||
- match: HostSNI(`*`)
|
- match: HostSNI(`*`)
|
||||||
kind: Rule
|
|
||||||
services:
|
services:
|
||||||
- name: external-svc
|
- name: external-svc
|
||||||
port: 80
|
port: 80
|
||||||
|
@ -1254,7 +1251,6 @@ Register the `IngressRouteTCP` [kind](../../reference/dynamic-configuration/kube
|
||||||
|
|
||||||
routes:
|
routes:
|
||||||
- match: HostSNI(`*`)
|
- match: HostSNI(`*`)
|
||||||
kind: Rule
|
|
||||||
services:
|
services:
|
||||||
- name: external-svc
|
- name: external-svc
|
||||||
port: 80
|
port: 80
|
||||||
|
|
|
@ -15,8 +15,8 @@ which in turn will create the resulting routers, services, handlers, etc.
|
||||||
|
|
||||||
```yaml tab="RBAC"
|
```yaml tab="RBAC"
|
||||||
---
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
kind: ClusterRole
|
kind: ClusterRole
|
||||||
apiVersion: rbac.authorization.k8s.io/v1beta1
|
|
||||||
metadata:
|
metadata:
|
||||||
name: traefik-ingress-controller
|
name: traefik-ingress-controller
|
||||||
rules:
|
rules:
|
||||||
|
@ -48,8 +48,8 @@ which in turn will create the resulting routers, services, handlers, etc.
|
||||||
- update
|
- update
|
||||||
|
|
||||||
---
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
kind: ClusterRoleBinding
|
kind: ClusterRoleBinding
|
||||||
apiVersion: rbac.authorization.k8s.io/v1beta1
|
|
||||||
metadata:
|
metadata:
|
||||||
name: traefik-ingress-controller
|
name: traefik-ingress-controller
|
||||||
roleRef:
|
roleRef:
|
||||||
|
@ -63,8 +63,37 @@ which in turn will create the resulting routers, services, handlers, etc.
|
||||||
```
|
```
|
||||||
|
|
||||||
```yaml tab="Ingress"
|
```yaml tab="Ingress"
|
||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
kind: Ingress
|
kind: Ingress
|
||||||
|
metadata:
|
||||||
|
name: myingress
|
||||||
|
annotations:
|
||||||
|
traefik.ingress.kubernetes.io/router.entrypoints: web
|
||||||
|
|
||||||
|
spec:
|
||||||
|
rules:
|
||||||
|
- host: example.com
|
||||||
|
http:
|
||||||
|
paths:
|
||||||
|
- path: /bar
|
||||||
|
pathType: Exact
|
||||||
|
backend:
|
||||||
|
service:
|
||||||
|
name: whoami
|
||||||
|
port:
|
||||||
|
number: 80
|
||||||
|
- path: /foo
|
||||||
|
pathType: Exact
|
||||||
|
backend:
|
||||||
|
service:
|
||||||
|
name: whoami
|
||||||
|
port:
|
||||||
|
number: 80
|
||||||
|
```
|
||||||
|
|
||||||
|
```yaml tab="Ingress v1beta1 (deprecated)"
|
||||||
apiVersion: networking.k8s.io/v1beta1
|
apiVersion: networking.k8s.io/v1beta1
|
||||||
|
kind: Ingress
|
||||||
metadata:
|
metadata:
|
||||||
name: myingress
|
name: myingress
|
||||||
annotations:
|
annotations:
|
||||||
|
@ -84,36 +113,7 @@ which in turn will create the resulting routers, services, handlers, etc.
|
||||||
serviceName: whoami
|
serviceName: whoami
|
||||||
servicePort: 80
|
servicePort: 80
|
||||||
```
|
```
|
||||||
|
|
||||||
```yaml tab="Ingress Kubernetes v1.19+"
|
|
||||||
kind: Ingress
|
|
||||||
apiVersion: networking.k8s.io/v1
|
|
||||||
metadata:
|
|
||||||
name: myingress
|
|
||||||
annotations:
|
|
||||||
traefik.ingress.kubernetes.io/router.entrypoints: web
|
|
||||||
|
|
||||||
spec:
|
|
||||||
rules:
|
|
||||||
- host: example.com
|
|
||||||
http:
|
|
||||||
paths:
|
|
||||||
- path: /bar
|
|
||||||
pathType: Exact
|
|
||||||
backend:
|
|
||||||
service:
|
|
||||||
name: whoami
|
|
||||||
port:
|
|
||||||
number: 80
|
|
||||||
- path: /foo
|
|
||||||
pathType: Exact
|
|
||||||
backend:
|
|
||||||
service:
|
|
||||||
name: whoami
|
|
||||||
port:
|
|
||||||
number: 80
|
|
||||||
```
|
|
||||||
|
|
||||||
```yaml tab="Traefik"
|
```yaml tab="Traefik"
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: ServiceAccount
|
kind: ServiceAccount
|
||||||
|
@ -121,8 +121,8 @@ which in turn will create the resulting routers, services, handlers, etc.
|
||||||
name: traefik-ingress-controller
|
name: traefik-ingress-controller
|
||||||
|
|
||||||
---
|
---
|
||||||
kind: Deployment
|
|
||||||
apiVersion: apps/v1
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
metadata:
|
metadata:
|
||||||
name: traefik
|
name: traefik
|
||||||
labels:
|
labels:
|
||||||
|
@ -166,8 +166,8 @@ which in turn will create the resulting routers, services, handlers, etc.
|
||||||
```
|
```
|
||||||
|
|
||||||
```yaml tab="Whoami"
|
```yaml tab="Whoami"
|
||||||
kind: Deployment
|
|
||||||
apiVersion: apps/v1
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
metadata:
|
metadata:
|
||||||
name: whoami
|
name: whoami
|
||||||
labels:
|
labels:
|
||||||
|
@ -209,6 +209,11 @@ which in turn will create the resulting routers, services, handlers, etc.
|
||||||
|
|
||||||
## Annotations
|
## Annotations
|
||||||
|
|
||||||
|
!!! warning "Referencing resources in annotations"
|
||||||
|
|
||||||
|
In an annotation, when referencing a resource defined by another provider,
|
||||||
|
the [provider namespace syntax](../../providers/overview.md#provider-namespace) must be used.
|
||||||
|
|
||||||
#### On Ingress
|
#### On Ingress
|
||||||
|
|
||||||
??? info "`traefik.ingress.kubernetes.io/router.entrypoints`"
|
??? info "`traefik.ingress.kubernetes.io/router.entrypoints`"
|
||||||
|
@ -224,7 +229,7 @@ which in turn will create the resulting routers, services, handlers, etc.
|
||||||
See [middlewares](../routers/index.md#middlewares) and [middlewares overview](../../middlewares/overview.md) for more information.
|
See [middlewares](../routers/index.md#middlewares) and [middlewares overview](../../middlewares/overview.md) for more information.
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
traefik.ingress.kubernetes.io/router.middlewares: auth@file,prefix@kubernetescrd,cb@file
|
traefik.ingress.kubernetes.io/router.middlewares: auth@file,default-prefix@kubernetescrd
|
||||||
```
|
```
|
||||||
|
|
||||||
??? info "`traefik.ingress.kubernetes.io/router.priority`"
|
??? info "`traefik.ingress.kubernetes.io/router.priority`"
|
||||||
|
@ -237,7 +242,7 @@ which in turn will create the resulting routers, services, handlers, etc.
|
||||||
|
|
||||||
??? info "`traefik.ingress.kubernetes.io/router.pathmatcher`"
|
??? info "`traefik.ingress.kubernetes.io/router.pathmatcher`"
|
||||||
|
|
||||||
Overrides the default router rule type used for a path.
|
Overrides the default router rule type used for a path.
|
||||||
Only path-related matcher name can be specified: `Path`, `PathPrefix`.
|
Only path-related matcher name can be specified: `Path`, `PathPrefix`.
|
||||||
|
|
||||||
Default `PathPrefix`
|
Default `PathPrefix`
|
||||||
|
@ -283,7 +288,7 @@ which in turn will create the resulting routers, services, handlers, etc.
|
||||||
See [options](../routers/index.md#options) for more information.
|
See [options](../routers/index.md#options) for more information.
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
traefik.ingress.kubernetes.io/router.tls.options: foobar
|
traefik.ingress.kubernetes.io/router.tls.options: foobar@file
|
||||||
```
|
```
|
||||||
|
|
||||||
#### On Service
|
#### On Service
|
||||||
|
@ -401,8 +406,8 @@ This way, any Ingress attached to this Entrypoint will have TLS termination by d
|
||||||
|
|
||||||
```yaml tab="RBAC"
|
```yaml tab="RBAC"
|
||||||
---
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
kind: ClusterRole
|
kind: ClusterRole
|
||||||
apiVersion: rbac.authorization.k8s.io/v1beta1
|
|
||||||
metadata:
|
metadata:
|
||||||
name: traefik-ingress-controller
|
name: traefik-ingress-controller
|
||||||
rules:
|
rules:
|
||||||
|
@ -434,8 +439,8 @@ This way, any Ingress attached to this Entrypoint will have TLS termination by d
|
||||||
- update
|
- update
|
||||||
|
|
||||||
---
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
kind: ClusterRoleBinding
|
kind: ClusterRoleBinding
|
||||||
apiVersion: rbac.authorization.k8s.io/v1beta1
|
|
||||||
metadata:
|
metadata:
|
||||||
name: traefik-ingress-controller
|
name: traefik-ingress-controller
|
||||||
roleRef:
|
roleRef:
|
||||||
|
@ -449,8 +454,37 @@ This way, any Ingress attached to this Entrypoint will have TLS termination by d
|
||||||
```
|
```
|
||||||
|
|
||||||
```yaml tab="Ingress"
|
```yaml tab="Ingress"
|
||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
kind: Ingress
|
kind: Ingress
|
||||||
|
metadata:
|
||||||
|
name: myingress
|
||||||
|
annotations:
|
||||||
|
traefik.ingress.kubernetes.io/router.entrypoints: websecure
|
||||||
|
|
||||||
|
spec:
|
||||||
|
rules:
|
||||||
|
- host: example.com
|
||||||
|
http:
|
||||||
|
paths:
|
||||||
|
- path: /bar
|
||||||
|
pathType: Exact
|
||||||
|
backend:
|
||||||
|
service:
|
||||||
|
name: whoami
|
||||||
|
port:
|
||||||
|
number: 80
|
||||||
|
- path: /foo
|
||||||
|
pathType: Exact
|
||||||
|
backend:
|
||||||
|
service:
|
||||||
|
name: whoami
|
||||||
|
port:
|
||||||
|
number: 80
|
||||||
|
```
|
||||||
|
|
||||||
|
```yaml tab="Ingress v1beta1 (deprecated)"
|
||||||
apiVersion: networking.k8s.io/v1beta1
|
apiVersion: networking.k8s.io/v1beta1
|
||||||
|
kind: Ingress
|
||||||
metadata:
|
metadata:
|
||||||
name: myingress
|
name: myingress
|
||||||
annotations:
|
annotations:
|
||||||
|
@ -470,36 +504,7 @@ This way, any Ingress attached to this Entrypoint will have TLS termination by d
|
||||||
serviceName: whoami
|
serviceName: whoami
|
||||||
servicePort: 80
|
servicePort: 80
|
||||||
```
|
```
|
||||||
|
|
||||||
```yaml tab="Ingress Kubernetes v1.19+"
|
|
||||||
kind: Ingress
|
|
||||||
apiVersion: networking.k8s.io/v1
|
|
||||||
metadata:
|
|
||||||
name: myingress
|
|
||||||
annotations:
|
|
||||||
traefik.ingress.kubernetes.io/router.entrypoints: websecure
|
|
||||||
|
|
||||||
spec:
|
|
||||||
rules:
|
|
||||||
- host: example.com
|
|
||||||
http:
|
|
||||||
paths:
|
|
||||||
- path: /bar
|
|
||||||
pathType: Exact
|
|
||||||
backend:
|
|
||||||
service:
|
|
||||||
name: whoami
|
|
||||||
port:
|
|
||||||
number: 80
|
|
||||||
- path: /foo
|
|
||||||
pathType: Exact
|
|
||||||
backend:
|
|
||||||
service:
|
|
||||||
name: whoami
|
|
||||||
port:
|
|
||||||
number: 80
|
|
||||||
```
|
|
||||||
|
|
||||||
```yaml tab="Traefik"
|
```yaml tab="Traefik"
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: ServiceAccount
|
kind: ServiceAccount
|
||||||
|
@ -507,8 +512,8 @@ This way, any Ingress attached to this Entrypoint will have TLS termination by d
|
||||||
name: traefik-ingress-controller
|
name: traefik-ingress-controller
|
||||||
|
|
||||||
---
|
---
|
||||||
kind: Deployment
|
|
||||||
apiVersion: apps/v1
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
metadata:
|
metadata:
|
||||||
name: traefik
|
name: traefik
|
||||||
labels:
|
labels:
|
||||||
|
@ -553,8 +558,8 @@ This way, any Ingress attached to this Entrypoint will have TLS termination by d
|
||||||
```
|
```
|
||||||
|
|
||||||
```yaml tab="Whoami"
|
```yaml tab="Whoami"
|
||||||
kind: Deployment
|
|
||||||
apiVersion: apps/v1
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
metadata:
|
metadata:
|
||||||
name: whoami
|
name: whoami
|
||||||
labels:
|
labels:
|
||||||
|
@ -608,8 +613,8 @@ For more options, please refer to the available [annotations](#on-ingress).
|
||||||
|
|
||||||
```yaml tab="RBAC"
|
```yaml tab="RBAC"
|
||||||
---
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
kind: ClusterRole
|
kind: ClusterRole
|
||||||
apiVersion: rbac.authorization.k8s.io/v1beta1
|
|
||||||
metadata:
|
metadata:
|
||||||
name: traefik-ingress-controller
|
name: traefik-ingress-controller
|
||||||
rules:
|
rules:
|
||||||
|
@ -641,8 +646,8 @@ For more options, please refer to the available [annotations](#on-ingress).
|
||||||
- update
|
- update
|
||||||
|
|
||||||
---
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
kind: ClusterRoleBinding
|
kind: ClusterRoleBinding
|
||||||
apiVersion: rbac.authorization.k8s.io/v1beta1
|
|
||||||
metadata:
|
metadata:
|
||||||
name: traefik-ingress-controller
|
name: traefik-ingress-controller
|
||||||
roleRef:
|
roleRef:
|
||||||
|
@ -656,8 +661,38 @@ For more options, please refer to the available [annotations](#on-ingress).
|
||||||
```
|
```
|
||||||
|
|
||||||
```yaml tab="Ingress"
|
```yaml tab="Ingress"
|
||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
kind: Ingress
|
kind: Ingress
|
||||||
|
metadata:
|
||||||
|
name: myingress
|
||||||
|
annotations:
|
||||||
|
traefik.ingress.kubernetes.io/router.entrypoints: websecure
|
||||||
|
traefik.ingress.kubernetes.io/router.tls: true
|
||||||
|
|
||||||
|
spec:
|
||||||
|
rules:
|
||||||
|
- host: example.com
|
||||||
|
http:
|
||||||
|
paths:
|
||||||
|
- path: /bar
|
||||||
|
pathType: Exact
|
||||||
|
backend:
|
||||||
|
service:
|
||||||
|
name: whoami
|
||||||
|
port:
|
||||||
|
number: 80
|
||||||
|
- path: /foo
|
||||||
|
pathType: Exact
|
||||||
|
backend:
|
||||||
|
service:
|
||||||
|
name: whoami
|
||||||
|
port:
|
||||||
|
number: 80
|
||||||
|
```
|
||||||
|
|
||||||
|
```yaml tab="Ingress v1beta1 (deprecated)"
|
||||||
apiVersion: networking.k8s.io/v1beta1
|
apiVersion: networking.k8s.io/v1beta1
|
||||||
|
kind: Ingress
|
||||||
metadata:
|
metadata:
|
||||||
name: myingress
|
name: myingress
|
||||||
annotations:
|
annotations:
|
||||||
|
@ -678,37 +713,7 @@ For more options, please refer to the available [annotations](#on-ingress).
|
||||||
serviceName: whoami
|
serviceName: whoami
|
||||||
servicePort: 80
|
servicePort: 80
|
||||||
```
|
```
|
||||||
|
|
||||||
```yaml tab="Ingress Kubernetes v1.19+"
|
|
||||||
kind: Ingress
|
|
||||||
apiVersion: networking.k8s.io/v1
|
|
||||||
metadata:
|
|
||||||
name: myingress
|
|
||||||
annotations:
|
|
||||||
traefik.ingress.kubernetes.io/router.entrypoints: websecure
|
|
||||||
traefik.ingress.kubernetes.io/router.tls: true
|
|
||||||
|
|
||||||
spec:
|
|
||||||
rules:
|
|
||||||
- host: example.com
|
|
||||||
http:
|
|
||||||
paths:
|
|
||||||
- path: /bar
|
|
||||||
pathType: Exact
|
|
||||||
backend:
|
|
||||||
service:
|
|
||||||
name: whoami
|
|
||||||
port:
|
|
||||||
number: 80
|
|
||||||
- path: /foo
|
|
||||||
pathType: Exact
|
|
||||||
backend:
|
|
||||||
service:
|
|
||||||
name: whoami
|
|
||||||
port:
|
|
||||||
number: 80
|
|
||||||
```
|
|
||||||
|
|
||||||
```yaml tab="Traefik"
|
```yaml tab="Traefik"
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: ServiceAccount
|
kind: ServiceAccount
|
||||||
|
@ -716,8 +721,8 @@ For more options, please refer to the available [annotations](#on-ingress).
|
||||||
name: traefik-ingress-controller
|
name: traefik-ingress-controller
|
||||||
|
|
||||||
---
|
---
|
||||||
kind: Deployment
|
|
||||||
apiVersion: apps/v1
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
metadata:
|
metadata:
|
||||||
name: traefik
|
name: traefik
|
||||||
labels:
|
labels:
|
||||||
|
@ -761,8 +766,8 @@ For more options, please refer to the available [annotations](#on-ingress).
|
||||||
```
|
```
|
||||||
|
|
||||||
```yaml tab="Whoami"
|
```yaml tab="Whoami"
|
||||||
kind: Deployment
|
|
||||||
apiVersion: apps/v1
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
metadata:
|
metadata:
|
||||||
name: whoami
|
name: whoami
|
||||||
labels:
|
labels:
|
||||||
|
@ -807,8 +812,34 @@ For more options, please refer to the available [annotations](#on-ingress).
|
||||||
??? example "Using a secret"
|
??? example "Using a secret"
|
||||||
|
|
||||||
```yaml tab="Ingress"
|
```yaml tab="Ingress"
|
||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
kind: Ingress
|
kind: Ingress
|
||||||
|
metadata:
|
||||||
|
name: foo
|
||||||
|
namespace: production
|
||||||
|
|
||||||
|
spec:
|
||||||
|
rules:
|
||||||
|
- host: example.net
|
||||||
|
http:
|
||||||
|
paths:
|
||||||
|
- path: /bar
|
||||||
|
pathType: Exact
|
||||||
|
backend:
|
||||||
|
service:
|
||||||
|
name: service1
|
||||||
|
port:
|
||||||
|
number: 80
|
||||||
|
# Only selects which certificate(s) should be loaded from the secret, in order to terminate TLS.
|
||||||
|
# Doesn't enable TLS for that ingress (hence for the underlying router).
|
||||||
|
# Please see the TLS annotations on ingress made for that purpose.
|
||||||
|
tls:
|
||||||
|
- secretName: supersecret
|
||||||
|
```
|
||||||
|
|
||||||
|
```yaml tab="Ingress v1beta1 (deprecated)"
|
||||||
apiVersion: networking.k8s.io/v1beta1
|
apiVersion: networking.k8s.io/v1beta1
|
||||||
|
kind: Ingress
|
||||||
metadata:
|
metadata:
|
||||||
name: foo
|
name: foo
|
||||||
namespace: production
|
namespace: production
|
||||||
|
@ -829,32 +860,6 @@ For more options, please refer to the available [annotations](#on-ingress).
|
||||||
- secretName: supersecret
|
- secretName: supersecret
|
||||||
```
|
```
|
||||||
|
|
||||||
```yaml tab="Ingress Kubernetes v1.19+"
|
|
||||||
kind: Ingress
|
|
||||||
apiVersion: networking.k8s.io/v1
|
|
||||||
metadata:
|
|
||||||
name: foo
|
|
||||||
namespace: production
|
|
||||||
|
|
||||||
spec:
|
|
||||||
rules:
|
|
||||||
- host: example.net
|
|
||||||
http:
|
|
||||||
paths:
|
|
||||||
- path: /bar
|
|
||||||
pathType: Exact
|
|
||||||
backend:
|
|
||||||
service:
|
|
||||||
name: service1
|
|
||||||
port:
|
|
||||||
number: 80
|
|
||||||
# Only selects which certificate(s) should be loaded from the secret, in order to terminate TLS.
|
|
||||||
# Doesn't enable TLS for that ingress (hence for the underlying router).
|
|
||||||
# Please see the TLS annotations on ingress made for that purpose.
|
|
||||||
tls:
|
|
||||||
- secretName: supersecret
|
|
||||||
```
|
|
||||||
|
|
||||||
```yaml tab="Secret"
|
```yaml tab="Secret"
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: Secret
|
kind: Secret
|
||||||
|
@ -900,18 +905,6 @@ and will connect via TLS automatically.
|
||||||
Ingresses can be created that look like the following:
|
Ingresses can be created that look like the following:
|
||||||
|
|
||||||
```yaml tab="Ingress"
|
```yaml tab="Ingress"
|
||||||
apiVersion: networking.k8s.io/v1beta1
|
|
||||||
kind: Ingress
|
|
||||||
metadata:
|
|
||||||
name: cheese
|
|
||||||
|
|
||||||
spec:
|
|
||||||
defaultBackend:
|
|
||||||
serviceName: stilton
|
|
||||||
serverPort: 80
|
|
||||||
```
|
|
||||||
|
|
||||||
```yaml tab="Ingress Kubernetes v1.19+"
|
|
||||||
apiVersion: networking.k8s.io/v1
|
apiVersion: networking.k8s.io/v1
|
||||||
kind: Ingress
|
kind: Ingress
|
||||||
metadata:
|
metadata:
|
||||||
|
@ -925,6 +918,18 @@ spec:
|
||||||
number: 80
|
number: 80
|
||||||
```
|
```
|
||||||
|
|
||||||
|
```yaml tab="Ingress v1beta1 (deprecated)"
|
||||||
|
apiVersion: networking.k8s.io/v1beta1
|
||||||
|
kind: Ingress
|
||||||
|
metadata:
|
||||||
|
name: cheese
|
||||||
|
|
||||||
|
spec:
|
||||||
|
defaultBackend:
|
||||||
|
serviceName: stilton
|
||||||
|
serverPort: 80
|
||||||
|
```
|
||||||
|
|
||||||
This ingress follows the Global Default Backend property of ingresses.
|
This ingress follows the Global Default Backend property of ingresses.
|
||||||
This will allow users to create a "default router" that will match all unmatched requests.
|
This will allow users to create a "default router" that will match all unmatched requests.
|
||||||
|
|
||||||
|
|
|
@ -251,6 +251,7 @@ The table below lists all the available matchers:
|
||||||
|
|
||||||
`HostRegexp` and `Path` accept an expression with zero or more groups enclosed by curly braces.
|
`HostRegexp` and `Path` accept an expression with zero or more groups enclosed by curly braces.
|
||||||
Named groups can be like `{name:pattern}` that matches the given regexp pattern or like `{name}` that matches anything until the next dot.
|
Named groups can be like `{name:pattern}` that matches the given regexp pattern or like `{name}` that matches anything until the next dot.
|
||||||
|
The group name (`name` is the above examples) is an arbitrary value.
|
||||||
Any pattern supported by [Go's regexp package](https://golang.org/pkg/regexp/) may be used (example: `{subdomain:[a-z]+}.{domain}.com`).
|
Any pattern supported by [Go's regexp package](https://golang.org/pkg/regexp/) may be used (example: `{subdomain:[a-z]+}.{domain}.com`).
|
||||||
|
|
||||||
!!! info "Combining Matchers Using Operators and Parenthesis"
|
!!! info "Combining Matchers Using Operators and Parenthesis"
|
||||||
|
|
|
@ -842,7 +842,7 @@ spec:
|
||||||
info configuration.
|
info configuration.
|
||||||
properties:
|
properties:
|
||||||
issuer:
|
issuer:
|
||||||
description: TLSCLientCertificateIssuerDNInfo holds the client
|
description: TLSClientCertificateIssuerDNInfo holds the client
|
||||||
TLS certificate distinguished name info configuration. cf
|
TLS certificate distinguished name info configuration. cf
|
||||||
https://tools.ietf.org/html/rfc3739
|
https://tools.ietf.org/html/rfc3739
|
||||||
properties:
|
properties:
|
||||||
|
@ -870,7 +870,7 @@ spec:
|
||||||
serialNumber:
|
serialNumber:
|
||||||
type: boolean
|
type: boolean
|
||||||
subject:
|
subject:
|
||||||
description: TLSCLientCertificateSubjectDNInfo holds the client
|
description: TLSClientCertificateSubjectDNInfo holds the client
|
||||||
TLS certificate distinguished name info configuration. cf
|
TLS certificate distinguished name info configuration. cf
|
||||||
https://tools.ietf.org/html/rfc3739
|
https://tools.ietf.org/html/rfc3739
|
||||||
properties:
|
properties:
|
||||||
|
|
|
@ -272,7 +272,7 @@ func TestDo_dynamicConfiguration(t *testing.T) {
|
||||||
},
|
},
|
||||||
ForwardAuth: &dynamic.ForwardAuth{
|
ForwardAuth: &dynamic.ForwardAuth{
|
||||||
Address: "127.0.0.1",
|
Address: "127.0.0.1",
|
||||||
TLS: &dynamic.ClientTLS{
|
TLS: &types.ClientTLS{
|
||||||
CA: "ca.pem",
|
CA: "ca.pem",
|
||||||
CAOptional: true,
|
CAOptional: true,
|
||||||
Cert: "cert.pem",
|
Cert: "cert.pem",
|
||||||
|
@ -314,7 +314,7 @@ func TestDo_dynamicConfiguration(t *testing.T) {
|
||||||
NotAfter: true,
|
NotAfter: true,
|
||||||
NotBefore: true,
|
NotBefore: true,
|
||||||
Sans: true,
|
Sans: true,
|
||||||
Subject: &dynamic.TLSCLientCertificateSubjectDNInfo{
|
Subject: &dynamic.TLSClientCertificateSubjectDNInfo{
|
||||||
Country: true,
|
Country: true,
|
||||||
Province: true,
|
Province: true,
|
||||||
Locality: true,
|
Locality: true,
|
||||||
|
@ -324,7 +324,7 @@ func TestDo_dynamicConfiguration(t *testing.T) {
|
||||||
SerialNumber: true,
|
SerialNumber: true,
|
||||||
DomainComponent: true,
|
DomainComponent: true,
|
||||||
},
|
},
|
||||||
Issuer: &dynamic.TLSCLientCertificateIssuerDNInfo{
|
Issuer: &dynamic.TLSClientCertificateIssuerDNInfo{
|
||||||
Country: true,
|
Country: true,
|
||||||
Province: true,
|
Province: true,
|
||||||
Locality: true,
|
Locality: true,
|
||||||
|
|
|
@ -1,14 +1,11 @@
|
||||||
package dynamic
|
package dynamic
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/tls"
|
|
||||||
"crypto/x509"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
ptypes "github.com/traefik/paerser/types"
|
ptypes "github.com/traefik/paerser/types"
|
||||||
"github.com/traefik/traefik/v2/pkg/ip"
|
"github.com/traefik/traefik/v2/pkg/ip"
|
||||||
|
"github.com/traefik/traefik/v2/pkg/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
// +k8s:deepcopy-gen=true
|
// +k8s:deepcopy-gen=true
|
||||||
|
@ -131,12 +128,12 @@ type ErrorPage struct {
|
||||||
|
|
||||||
// ForwardAuth holds the http forward authentication configuration.
|
// ForwardAuth holds the http forward authentication configuration.
|
||||||
type ForwardAuth struct {
|
type ForwardAuth struct {
|
||||||
Address string `json:"address,omitempty" toml:"address,omitempty" yaml:"address,omitempty"`
|
Address string `json:"address,omitempty" toml:"address,omitempty" yaml:"address,omitempty"`
|
||||||
TLS *ClientTLS `json:"tls,omitempty" toml:"tls,omitempty" yaml:"tls,omitempty" export:"true"`
|
TLS *types.ClientTLS `json:"tls,omitempty" toml:"tls,omitempty" yaml:"tls,omitempty" export:"true"`
|
||||||
TrustForwardHeader bool `json:"trustForwardHeader,omitempty" toml:"trustForwardHeader,omitempty" yaml:"trustForwardHeader,omitempty" export:"true"`
|
TrustForwardHeader bool `json:"trustForwardHeader,omitempty" toml:"trustForwardHeader,omitempty" yaml:"trustForwardHeader,omitempty" export:"true"`
|
||||||
AuthResponseHeaders []string `json:"authResponseHeaders,omitempty" toml:"authResponseHeaders,omitempty" yaml:"authResponseHeaders,omitempty" export:"true"`
|
AuthResponseHeaders []string `json:"authResponseHeaders,omitempty" toml:"authResponseHeaders,omitempty" yaml:"authResponseHeaders,omitempty" export:"true"`
|
||||||
AuthResponseHeadersRegex string `json:"authResponseHeadersRegex,omitempty" toml:"authResponseHeadersRegex,omitempty" yaml:"authResponseHeadersRegex,omitempty" export:"true"`
|
AuthResponseHeadersRegex string `json:"authResponseHeadersRegex,omitempty" toml:"authResponseHeadersRegex,omitempty" yaml:"authResponseHeadersRegex,omitempty" export:"true"`
|
||||||
AuthRequestHeaders []string `json:"authRequestHeaders,omitempty" toml:"authRequestHeaders,omitempty" yaml:"authRequestHeaders,omitempty" export:"true"`
|
AuthRequestHeaders []string `json:"authRequestHeaders,omitempty" toml:"authRequestHeaders,omitempty" yaml:"authRequestHeaders,omitempty" export:"true"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// +k8s:deepcopy-gen=true
|
// +k8s:deepcopy-gen=true
|
||||||
|
@ -403,16 +400,16 @@ type TLSClientCertificateInfo struct {
|
||||||
NotAfter bool `json:"notAfter,omitempty" toml:"notAfter,omitempty" yaml:"notAfter,omitempty" export:"true"`
|
NotAfter bool `json:"notAfter,omitempty" toml:"notAfter,omitempty" yaml:"notAfter,omitempty" export:"true"`
|
||||||
NotBefore bool `json:"notBefore,omitempty" toml:"notBefore,omitempty" yaml:"notBefore,omitempty" export:"true"`
|
NotBefore bool `json:"notBefore,omitempty" toml:"notBefore,omitempty" yaml:"notBefore,omitempty" export:"true"`
|
||||||
Sans bool `json:"sans,omitempty" toml:"sans,omitempty" yaml:"sans,omitempty" export:"true"`
|
Sans bool `json:"sans,omitempty" toml:"sans,omitempty" yaml:"sans,omitempty" export:"true"`
|
||||||
Subject *TLSCLientCertificateSubjectDNInfo `json:"subject,omitempty" toml:"subject,omitempty" yaml:"subject,omitempty" export:"true"`
|
Subject *TLSClientCertificateSubjectDNInfo `json:"subject,omitempty" toml:"subject,omitempty" yaml:"subject,omitempty" export:"true"`
|
||||||
Issuer *TLSCLientCertificateIssuerDNInfo `json:"issuer,omitempty" toml:"issuer,omitempty" yaml:"issuer,omitempty" export:"true"`
|
Issuer *TLSClientCertificateIssuerDNInfo `json:"issuer,omitempty" toml:"issuer,omitempty" yaml:"issuer,omitempty" export:"true"`
|
||||||
SerialNumber bool `json:"serialNumber,omitempty" toml:"serialNumber,omitempty" yaml:"serialNumber,omitempty" export:"true"`
|
SerialNumber bool `json:"serialNumber,omitempty" toml:"serialNumber,omitempty" yaml:"serialNumber,omitempty" export:"true"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// +k8s:deepcopy-gen=true
|
// +k8s:deepcopy-gen=true
|
||||||
|
|
||||||
// TLSCLientCertificateIssuerDNInfo holds the client TLS certificate distinguished name info configuration.
|
// TLSClientCertificateIssuerDNInfo holds the client TLS certificate distinguished name info configuration.
|
||||||
// cf https://tools.ietf.org/html/rfc3739
|
// cf https://tools.ietf.org/html/rfc3739
|
||||||
type TLSCLientCertificateIssuerDNInfo struct {
|
type TLSClientCertificateIssuerDNInfo struct {
|
||||||
Country bool `json:"country,omitempty" toml:"country,omitempty" yaml:"country,omitempty" export:"true"`
|
Country bool `json:"country,omitempty" toml:"country,omitempty" yaml:"country,omitempty" export:"true"`
|
||||||
Province bool `json:"province,omitempty" toml:"province,omitempty" yaml:"province,omitempty" export:"true"`
|
Province bool `json:"province,omitempty" toml:"province,omitempty" yaml:"province,omitempty" export:"true"`
|
||||||
Locality bool `json:"locality,omitempty" toml:"locality,omitempty" yaml:"locality,omitempty" export:"true"`
|
Locality bool `json:"locality,omitempty" toml:"locality,omitempty" yaml:"locality,omitempty" export:"true"`
|
||||||
|
@ -424,9 +421,9 @@ type TLSCLientCertificateIssuerDNInfo struct {
|
||||||
|
|
||||||
// +k8s:deepcopy-gen=true
|
// +k8s:deepcopy-gen=true
|
||||||
|
|
||||||
// TLSCLientCertificateSubjectDNInfo holds the client TLS certificate distinguished name info configuration.
|
// TLSClientCertificateSubjectDNInfo holds the client TLS certificate distinguished name info configuration.
|
||||||
// cf https://tools.ietf.org/html/rfc3739
|
// cf https://tools.ietf.org/html/rfc3739
|
||||||
type TLSCLientCertificateSubjectDNInfo struct {
|
type TLSClientCertificateSubjectDNInfo struct {
|
||||||
Country bool `json:"country,omitempty" toml:"country,omitempty" yaml:"country,omitempty" export:"true"`
|
Country bool `json:"country,omitempty" toml:"country,omitempty" yaml:"country,omitempty" export:"true"`
|
||||||
Province bool `json:"province,omitempty" toml:"province,omitempty" yaml:"province,omitempty" export:"true"`
|
Province bool `json:"province,omitempty" toml:"province,omitempty" yaml:"province,omitempty" export:"true"`
|
||||||
Locality bool `json:"locality,omitempty" toml:"locality,omitempty" yaml:"locality,omitempty" export:"true"`
|
Locality bool `json:"locality,omitempty" toml:"locality,omitempty" yaml:"locality,omitempty" export:"true"`
|
||||||
|
@ -441,83 +438,3 @@ type TLSCLientCertificateSubjectDNInfo struct {
|
||||||
|
|
||||||
// Users holds a list of users.
|
// Users holds a list of users.
|
||||||
type Users []string
|
type Users []string
|
||||||
|
|
||||||
// +k8s:deepcopy-gen=true
|
|
||||||
|
|
||||||
// ClientTLS holds the TLS specific configurations as client
|
|
||||||
// CA, Cert and Key can be either path or file contents.
|
|
||||||
type ClientTLS struct {
|
|
||||||
CA string `json:"ca,omitempty" toml:"ca,omitempty" yaml:"ca,omitempty"`
|
|
||||||
CAOptional bool `json:"caOptional,omitempty" toml:"caOptional,omitempty" yaml:"caOptional,omitempty" export:"true"`
|
|
||||||
Cert string `json:"cert,omitempty" toml:"cert,omitempty" yaml:"cert,omitempty"`
|
|
||||||
Key string `json:"key,omitempty" toml:"key,omitempty" yaml:"key,omitempty"`
|
|
||||||
InsecureSkipVerify bool `json:"insecureSkipVerify,omitempty" toml:"insecureSkipVerify,omitempty" yaml:"insecureSkipVerify,omitempty" export:"true"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateTLSConfig creates a TLS config from ClientTLS structures.
|
|
||||||
func (c *ClientTLS) CreateTLSConfig() (*tls.Config, error) {
|
|
||||||
if c == nil {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var err error
|
|
||||||
caPool := x509.NewCertPool()
|
|
||||||
clientAuth := tls.NoClientCert
|
|
||||||
if c.CA != "" {
|
|
||||||
var ca []byte
|
|
||||||
if _, errCA := os.Stat(c.CA); errCA == nil {
|
|
||||||
ca, err = os.ReadFile(c.CA)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to read CA. %w", err)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
ca = []byte(c.CA)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !caPool.AppendCertsFromPEM(ca) {
|
|
||||||
return nil, fmt.Errorf("failed to parse CA")
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.CAOptional {
|
|
||||||
clientAuth = tls.VerifyClientCertIfGiven
|
|
||||||
} else {
|
|
||||||
clientAuth = tls.RequireAndVerifyClientCert
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
cert := tls.Certificate{}
|
|
||||||
_, errKeyIsFile := os.Stat(c.Key)
|
|
||||||
|
|
||||||
if !c.InsecureSkipVerify && (len(c.Cert) == 0 || len(c.Key) == 0) {
|
|
||||||
return nil, fmt.Errorf("TLS Certificate or Key file must be set when TLS configuration is created")
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(c.Cert) > 0 && len(c.Key) > 0 {
|
|
||||||
if _, errCertIsFile := os.Stat(c.Cert); errCertIsFile == nil {
|
|
||||||
if errKeyIsFile == nil {
|
|
||||||
cert, err = tls.LoadX509KeyPair(c.Cert, c.Key)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to load TLS keypair: %w", err)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return nil, fmt.Errorf("tls cert is a file, but tls key is not")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if errKeyIsFile != nil {
|
|
||||||
cert, err = tls.X509KeyPair([]byte(c.Cert), []byte(c.Key))
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to load TLS keypair: %w", err)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return nil, fmt.Errorf("TLS key is a file, but tls cert is not")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return &tls.Config{
|
|
||||||
Certificates: []tls.Certificate{cert},
|
|
||||||
RootCAs: caPool,
|
|
||||||
InsecureSkipVerify: c.InsecureSkipVerify,
|
|
||||||
ClientAuth: clientAuth,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
|
@ -124,22 +124,6 @@ func (in *CircuitBreaker) DeepCopy() *CircuitBreaker {
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
|
||||||
func (in *ClientTLS) DeepCopyInto(out *ClientTLS) {
|
|
||||||
*out = *in
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClientTLS.
|
|
||||||
func (in *ClientTLS) DeepCopy() *ClientTLS {
|
|
||||||
if in == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
out := new(ClientTLS)
|
|
||||||
in.DeepCopyInto(out)
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
func (in *Compress) DeepCopyInto(out *Compress) {
|
func (in *Compress) DeepCopyInto(out *Compress) {
|
||||||
*out = *in
|
*out = *in
|
||||||
|
@ -306,7 +290,7 @@ func (in *ForwardAuth) DeepCopyInto(out *ForwardAuth) {
|
||||||
*out = *in
|
*out = *in
|
||||||
if in.TLS != nil {
|
if in.TLS != nil {
|
||||||
in, out := &in.TLS, &out.TLS
|
in, out := &in.TLS, &out.TLS
|
||||||
*out = new(ClientTLS)
|
*out = new(types.ClientTLS)
|
||||||
**out = **in
|
**out = **in
|
||||||
}
|
}
|
||||||
if in.AuthResponseHeaders != nil {
|
if in.AuthResponseHeaders != nil {
|
||||||
|
@ -1535,49 +1519,17 @@ func (in *TCPWeightedRoundRobin) DeepCopy() *TCPWeightedRoundRobin {
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
|
||||||
func (in *TLSCLientCertificateIssuerDNInfo) DeepCopyInto(out *TLSCLientCertificateIssuerDNInfo) {
|
|
||||||
*out = *in
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TLSCLientCertificateIssuerDNInfo.
|
|
||||||
func (in *TLSCLientCertificateIssuerDNInfo) DeepCopy() *TLSCLientCertificateIssuerDNInfo {
|
|
||||||
if in == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
out := new(TLSCLientCertificateIssuerDNInfo)
|
|
||||||
in.DeepCopyInto(out)
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
|
||||||
func (in *TLSCLientCertificateSubjectDNInfo) DeepCopyInto(out *TLSCLientCertificateSubjectDNInfo) {
|
|
||||||
*out = *in
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TLSCLientCertificateSubjectDNInfo.
|
|
||||||
func (in *TLSCLientCertificateSubjectDNInfo) DeepCopy() *TLSCLientCertificateSubjectDNInfo {
|
|
||||||
if in == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
out := new(TLSCLientCertificateSubjectDNInfo)
|
|
||||||
in.DeepCopyInto(out)
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
func (in *TLSClientCertificateInfo) DeepCopyInto(out *TLSClientCertificateInfo) {
|
func (in *TLSClientCertificateInfo) DeepCopyInto(out *TLSClientCertificateInfo) {
|
||||||
*out = *in
|
*out = *in
|
||||||
if in.Subject != nil {
|
if in.Subject != nil {
|
||||||
in, out := &in.Subject, &out.Subject
|
in, out := &in.Subject, &out.Subject
|
||||||
*out = new(TLSCLientCertificateSubjectDNInfo)
|
*out = new(TLSClientCertificateSubjectDNInfo)
|
||||||
**out = **in
|
**out = **in
|
||||||
}
|
}
|
||||||
if in.Issuer != nil {
|
if in.Issuer != nil {
|
||||||
in, out := &in.Issuer, &out.Issuer
|
in, out := &in.Issuer, &out.Issuer
|
||||||
*out = new(TLSCLientCertificateIssuerDNInfo)
|
*out = new(TLSClientCertificateIssuerDNInfo)
|
||||||
**out = **in
|
**out = **in
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
|
@ -1593,6 +1545,38 @@ func (in *TLSClientCertificateInfo) DeepCopy() *TLSClientCertificateInfo {
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *TLSClientCertificateIssuerDNInfo) DeepCopyInto(out *TLSClientCertificateIssuerDNInfo) {
|
||||||
|
*out = *in
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TLSClientCertificateIssuerDNInfo.
|
||||||
|
func (in *TLSClientCertificateIssuerDNInfo) DeepCopy() *TLSClientCertificateIssuerDNInfo {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(TLSClientCertificateIssuerDNInfo)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *TLSClientCertificateSubjectDNInfo) DeepCopyInto(out *TLSClientCertificateSubjectDNInfo) {
|
||||||
|
*out = *in
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TLSClientCertificateSubjectDNInfo.
|
||||||
|
func (in *TLSClientCertificateSubjectDNInfo) DeepCopy() *TLSClientCertificateSubjectDNInfo {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(TLSClientCertificateSubjectDNInfo)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
func (in *TLSConfiguration) DeepCopyInto(out *TLSConfiguration) {
|
func (in *TLSConfiguration) DeepCopyInto(out *TLSConfiguration) {
|
||||||
*out = *in
|
*out = *in
|
||||||
|
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
ptypes "github.com/traefik/paerser/types"
|
ptypes "github.com/traefik/paerser/types"
|
||||||
"github.com/traefik/traefik/v2/pkg/config/dynamic"
|
"github.com/traefik/traefik/v2/pkg/config/dynamic"
|
||||||
|
"github.com/traefik/traefik/v2/pkg/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestDecodeConfiguration(t *testing.T) {
|
func TestDecodeConfiguration(t *testing.T) {
|
||||||
|
@ -367,7 +368,7 @@ func TestDecodeConfiguration(t *testing.T) {
|
||||||
NotAfter: true,
|
NotAfter: true,
|
||||||
NotBefore: true,
|
NotBefore: true,
|
||||||
SerialNumber: true,
|
SerialNumber: true,
|
||||||
Subject: &dynamic.TLSCLientCertificateSubjectDNInfo{
|
Subject: &dynamic.TLSClientCertificateSubjectDNInfo{
|
||||||
Country: true,
|
Country: true,
|
||||||
Province: true,
|
Province: true,
|
||||||
Locality: true,
|
Locality: true,
|
||||||
|
@ -377,7 +378,7 @@ func TestDecodeConfiguration(t *testing.T) {
|
||||||
SerialNumber: true,
|
SerialNumber: true,
|
||||||
DomainComponent: true,
|
DomainComponent: true,
|
||||||
},
|
},
|
||||||
Issuer: &dynamic.TLSCLientCertificateIssuerDNInfo{
|
Issuer: &dynamic.TLSClientCertificateIssuerDNInfo{
|
||||||
Country: true,
|
Country: true,
|
||||||
Province: true,
|
Province: true,
|
||||||
Locality: true,
|
Locality: true,
|
||||||
|
@ -505,7 +506,7 @@ func TestDecodeConfiguration(t *testing.T) {
|
||||||
"Middleware7": {
|
"Middleware7": {
|
||||||
ForwardAuth: &dynamic.ForwardAuth{
|
ForwardAuth: &dynamic.ForwardAuth{
|
||||||
Address: "foobar",
|
Address: "foobar",
|
||||||
TLS: &dynamic.ClientTLS{
|
TLS: &types.ClientTLS{
|
||||||
CA: "foobar",
|
CA: "foobar",
|
||||||
CAOptional: true,
|
CAOptional: true,
|
||||||
Cert: "foobar",
|
Cert: "foobar",
|
||||||
|
@ -848,7 +849,7 @@ func TestEncodeConfiguration(t *testing.T) {
|
||||||
NotAfter: true,
|
NotAfter: true,
|
||||||
NotBefore: true,
|
NotBefore: true,
|
||||||
SerialNumber: true,
|
SerialNumber: true,
|
||||||
Subject: &dynamic.TLSCLientCertificateSubjectDNInfo{
|
Subject: &dynamic.TLSClientCertificateSubjectDNInfo{
|
||||||
Country: true,
|
Country: true,
|
||||||
Province: true,
|
Province: true,
|
||||||
Locality: true,
|
Locality: true,
|
||||||
|
@ -858,7 +859,7 @@ func TestEncodeConfiguration(t *testing.T) {
|
||||||
SerialNumber: true,
|
SerialNumber: true,
|
||||||
DomainComponent: true,
|
DomainComponent: true,
|
||||||
},
|
},
|
||||||
Issuer: &dynamic.TLSCLientCertificateIssuerDNInfo{
|
Issuer: &dynamic.TLSClientCertificateIssuerDNInfo{
|
||||||
Country: true,
|
Country: true,
|
||||||
Province: true,
|
Province: true,
|
||||||
Locality: true,
|
Locality: true,
|
||||||
|
@ -993,7 +994,7 @@ func TestEncodeConfiguration(t *testing.T) {
|
||||||
"Middleware7": {
|
"Middleware7": {
|
||||||
ForwardAuth: &dynamic.ForwardAuth{
|
ForwardAuth: &dynamic.ForwardAuth{
|
||||||
Address: "foobar",
|
Address: "foobar",
|
||||||
TLS: &dynamic.ClientTLS{
|
TLS: &types.ClientTLS{
|
||||||
CA: "foobar",
|
CA: "foobar",
|
||||||
CAOptional: true,
|
CAOptional: true,
|
||||||
Cert: "foobar",
|
Cert: "foobar",
|
||||||
|
|
|
@ -2,7 +2,6 @@ package accesslog
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -84,7 +83,7 @@ func TestCommonLogFormatter_Format(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set timezone to Etc/GMT+9 to have a constant behavior
|
// Set timezone to Etc/GMT+9 to have a constant behavior
|
||||||
os.Setenv("TZ", "Etc/GMT+9")
|
t.Setenv("TZ", "Etc/GMT+9")
|
||||||
|
|
||||||
for _, test := range testCases {
|
for _, test := range testCases {
|
||||||
test := test
|
test := test
|
||||||
|
|
|
@ -72,9 +72,9 @@ func NewForward(ctx context.Context, next http.Handler, config dynamic.ForwardAu
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.TLS != nil {
|
if config.TLS != nil {
|
||||||
tlsConfig, err := config.TLS.CreateTLSConfig()
|
tlsConfig, err := config.TLS.CreateTLSConfig(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, fmt.Errorf("unable to create client TLS configuration: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
tr := http.DefaultTransport.(*http.Transport).Clone()
|
tr := http.DefaultTransport.(*http.Transport).Clone()
|
||||||
|
|
|
@ -48,7 +48,7 @@ type IssuerDistinguishedNameOptions struct {
|
||||||
StateOrProvinceName bool
|
StateOrProvinceName bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func newIssuerDistinguishedNameOptions(info *dynamic.TLSCLientCertificateIssuerDNInfo) *IssuerDistinguishedNameOptions {
|
func newIssuerDistinguishedNameOptions(info *dynamic.TLSClientCertificateIssuerDNInfo) *IssuerDistinguishedNameOptions {
|
||||||
if info == nil {
|
if info == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -78,7 +78,7 @@ type SubjectDistinguishedNameOptions struct {
|
||||||
StateOrProvinceName bool
|
StateOrProvinceName bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func newSubjectDistinguishedNameOptions(info *dynamic.TLSCLientCertificateSubjectDNInfo) *SubjectDistinguishedNameOptions {
|
func newSubjectDistinguishedNameOptions(info *dynamic.TLSClientCertificateSubjectDNInfo) *SubjectDistinguishedNameOptions {
|
||||||
if info == nil {
|
if info == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -376,7 +376,7 @@ func TestPassTLSClientCert_certInfo(t *testing.T) {
|
||||||
desc: "No TLS, with subject info",
|
desc: "No TLS, with subject info",
|
||||||
config: dynamic.PassTLSClientCert{
|
config: dynamic.PassTLSClientCert{
|
||||||
Info: &dynamic.TLSClientCertificateInfo{
|
Info: &dynamic.TLSClientCertificateInfo{
|
||||||
Subject: &dynamic.TLSCLientCertificateSubjectDNInfo{
|
Subject: &dynamic.TLSClientCertificateSubjectDNInfo{
|
||||||
CommonName: true,
|
CommonName: true,
|
||||||
Organization: true,
|
Organization: true,
|
||||||
OrganizationalUnit: true,
|
OrganizationalUnit: true,
|
||||||
|
@ -393,7 +393,7 @@ func TestPassTLSClientCert_certInfo(t *testing.T) {
|
||||||
config: dynamic.PassTLSClientCert{
|
config: dynamic.PassTLSClientCert{
|
||||||
PEM: false,
|
PEM: false,
|
||||||
Info: &dynamic.TLSClientCertificateInfo{
|
Info: &dynamic.TLSClientCertificateInfo{
|
||||||
Subject: &dynamic.TLSCLientCertificateSubjectDNInfo{},
|
Subject: &dynamic.TLSClientCertificateSubjectDNInfo{},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -406,7 +406,7 @@ func TestPassTLSClientCert_certInfo(t *testing.T) {
|
||||||
NotBefore: true,
|
NotBefore: true,
|
||||||
Sans: true,
|
Sans: true,
|
||||||
SerialNumber: true,
|
SerialNumber: true,
|
||||||
Subject: &dynamic.TLSCLientCertificateSubjectDNInfo{
|
Subject: &dynamic.TLSClientCertificateSubjectDNInfo{
|
||||||
CommonName: true,
|
CommonName: true,
|
||||||
Country: true,
|
Country: true,
|
||||||
DomainComponent: true,
|
DomainComponent: true,
|
||||||
|
@ -416,7 +416,7 @@ func TestPassTLSClientCert_certInfo(t *testing.T) {
|
||||||
Province: true,
|
Province: true,
|
||||||
SerialNumber: true,
|
SerialNumber: true,
|
||||||
},
|
},
|
||||||
Issuer: &dynamic.TLSCLientCertificateIssuerDNInfo{
|
Issuer: &dynamic.TLSClientCertificateIssuerDNInfo{
|
||||||
CommonName: true,
|
CommonName: true,
|
||||||
Country: true,
|
Country: true,
|
||||||
DomainComponent: true,
|
DomainComponent: true,
|
||||||
|
@ -436,10 +436,10 @@ func TestPassTLSClientCert_certInfo(t *testing.T) {
|
||||||
Info: &dynamic.TLSClientCertificateInfo{
|
Info: &dynamic.TLSClientCertificateInfo{
|
||||||
NotAfter: true,
|
NotAfter: true,
|
||||||
Sans: true,
|
Sans: true,
|
||||||
Subject: &dynamic.TLSCLientCertificateSubjectDNInfo{
|
Subject: &dynamic.TLSClientCertificateSubjectDNInfo{
|
||||||
Organization: true,
|
Organization: true,
|
||||||
},
|
},
|
||||||
Issuer: &dynamic.TLSCLientCertificateIssuerDNInfo{
|
Issuer: &dynamic.TLSClientCertificateIssuerDNInfo{
|
||||||
Country: true,
|
Country: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -453,13 +453,13 @@ func TestPassTLSClientCert_certInfo(t *testing.T) {
|
||||||
Info: &dynamic.TLSClientCertificateInfo{
|
Info: &dynamic.TLSClientCertificateInfo{
|
||||||
NotAfter: true,
|
NotAfter: true,
|
||||||
Sans: true,
|
Sans: true,
|
||||||
Subject: &dynamic.TLSCLientCertificateSubjectDNInfo{
|
Subject: &dynamic.TLSClientCertificateSubjectDNInfo{
|
||||||
Organization: true,
|
Organization: true,
|
||||||
// OrganizationalUnit is not set on this example certificate,
|
// OrganizationalUnit is not set on this example certificate,
|
||||||
// so even though it's requested, it will be absent.
|
// so even though it's requested, it will be absent.
|
||||||
OrganizationalUnit: true,
|
OrganizationalUnit: true,
|
||||||
},
|
},
|
||||||
Issuer: &dynamic.TLSCLientCertificateIssuerDNInfo{
|
Issuer: &dynamic.TLSClientCertificateIssuerDNInfo{
|
||||||
Country: true,
|
Country: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -475,7 +475,7 @@ func TestPassTLSClientCert_certInfo(t *testing.T) {
|
||||||
NotBefore: true,
|
NotBefore: true,
|
||||||
Sans: true,
|
Sans: true,
|
||||||
SerialNumber: true,
|
SerialNumber: true,
|
||||||
Subject: &dynamic.TLSCLientCertificateSubjectDNInfo{
|
Subject: &dynamic.TLSClientCertificateSubjectDNInfo{
|
||||||
Country: true,
|
Country: true,
|
||||||
Province: true,
|
Province: true,
|
||||||
Locality: true,
|
Locality: true,
|
||||||
|
@ -485,7 +485,7 @@ func TestPassTLSClientCert_certInfo(t *testing.T) {
|
||||||
SerialNumber: true,
|
SerialNumber: true,
|
||||||
DomainComponent: true,
|
DomainComponent: true,
|
||||||
},
|
},
|
||||||
Issuer: &dynamic.TLSCLientCertificateIssuerDNInfo{
|
Issuer: &dynamic.TLSClientCertificateIssuerDNInfo{
|
||||||
Country: true,
|
Country: true,
|
||||||
Province: true,
|
Province: true,
|
||||||
Locality: true,
|
Locality: true,
|
||||||
|
@ -507,7 +507,7 @@ func TestPassTLSClientCert_certInfo(t *testing.T) {
|
||||||
NotBefore: true,
|
NotBefore: true,
|
||||||
Sans: true,
|
Sans: true,
|
||||||
SerialNumber: true,
|
SerialNumber: true,
|
||||||
Subject: &dynamic.TLSCLientCertificateSubjectDNInfo{
|
Subject: &dynamic.TLSClientCertificateSubjectDNInfo{
|
||||||
Country: true,
|
Country: true,
|
||||||
Province: true,
|
Province: true,
|
||||||
Locality: true,
|
Locality: true,
|
||||||
|
@ -517,7 +517,7 @@ func TestPassTLSClientCert_certInfo(t *testing.T) {
|
||||||
SerialNumber: true,
|
SerialNumber: true,
|
||||||
DomainComponent: true,
|
DomainComponent: true,
|
||||||
},
|
},
|
||||||
Issuer: &dynamic.TLSCLientCertificateIssuerDNInfo{
|
Issuer: &dynamic.TLSClientCertificateIssuerDNInfo{
|
||||||
Country: true,
|
Country: true,
|
||||||
Province: true,
|
Province: true,
|
||||||
Locality: true,
|
Locality: true,
|
||||||
|
|
|
@ -41,12 +41,12 @@ func (r *replacePath) GetTracingInformation() (string, ext.SpanKindEnum) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *replacePath) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
func (r *replacePath) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||||
if req.URL.RawPath == "" {
|
currentPath := req.URL.RawPath
|
||||||
req.Header.Add(ReplacedPathHeader, req.URL.Path)
|
if currentPath == "" {
|
||||||
} else {
|
currentPath = req.URL.EscapedPath()
|
||||||
req.Header.Add(ReplacedPathHeader, req.URL.RawPath)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
req.Header.Add(ReplacedPathHeader, currentPath)
|
||||||
req.URL.RawPath = r.path
|
req.URL.RawPath = r.path
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
|
|
|
@ -60,6 +60,16 @@ func TestReplacePath(t *testing.T) {
|
||||||
expectedRawPath: "/foo%2Fbar",
|
expectedRawPath: "/foo%2Fbar",
|
||||||
expectedHeader: "/path",
|
expectedHeader: "/path",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
desc: "replacement with percent encoded backspace char",
|
||||||
|
path: "/path/%08bar",
|
||||||
|
config: dynamic.ReplacePath{
|
||||||
|
Path: "/path/%08bar",
|
||||||
|
},
|
||||||
|
expectedPath: "/path/\bbar",
|
||||||
|
expectedRawPath: "/path/%08bar",
|
||||||
|
expectedHeader: "/path/%08bar",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range testCases {
|
for _, test := range testCases {
|
||||||
|
|
|
@ -16,9 +16,7 @@ import (
|
||||||
"github.com/traefik/traefik/v2/pkg/tracing"
|
"github.com/traefik/traefik/v2/pkg/tracing"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const typeName = "ReplacePathRegex"
|
||||||
typeName = "ReplacePathRegex"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ReplacePathRegex is a middleware used to replace the path of a URL request with a regular expression.
|
// ReplacePathRegex is a middleware used to replace the path of a URL request with a regular expression.
|
||||||
type replacePathRegex struct {
|
type replacePathRegex struct {
|
||||||
|
@ -50,16 +48,13 @@ func (rp *replacePathRegex) GetTracingInformation() (string, ext.SpanKindEnum) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rp *replacePathRegex) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
func (rp *replacePathRegex) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||||
var currentPath string
|
currentPath := req.URL.RawPath
|
||||||
if req.URL.RawPath == "" {
|
if currentPath == "" {
|
||||||
currentPath = req.URL.Path
|
currentPath = req.URL.EscapedPath()
|
||||||
} else {
|
|
||||||
currentPath = req.URL.RawPath
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if rp.regexp != nil && len(rp.replacement) > 0 && rp.regexp.MatchString(currentPath) {
|
if rp.regexp != nil && len(rp.replacement) > 0 && rp.regexp.MatchString(currentPath) {
|
||||||
req.Header.Add(replacepath.ReplacedPathHeader, currentPath)
|
req.Header.Add(replacepath.ReplacedPathHeader, currentPath)
|
||||||
|
|
||||||
req.URL.RawPath = rp.regexp.ReplaceAllString(currentPath, rp.replacement)
|
req.URL.RawPath = rp.regexp.ReplaceAllString(currentPath, rp.replacement)
|
||||||
|
|
||||||
// as replacement can introduce escaped characters
|
// as replacement can introduce escaped characters
|
||||||
|
|
|
@ -106,6 +106,16 @@ func TestReplacePathRegex(t *testing.T) {
|
||||||
expectedPath: "/aaa/bbb",
|
expectedPath: "/aaa/bbb",
|
||||||
expectedRawPath: "/aaa%2Fbbb",
|
expectedRawPath: "/aaa%2Fbbb",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
desc: "path with percent encoded backspace char",
|
||||||
|
path: "/foo/%08bar",
|
||||||
|
config: dynamic.ReplacePathRegex{
|
||||||
|
Replacement: "/$1",
|
||||||
|
Regex: `^/foo/(.*)`,
|
||||||
|
},
|
||||||
|
expectedPath: "/\bbar",
|
||||||
|
expectedRawPath: "/%08bar",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range testCases {
|
for _, test := range testCases {
|
||||||
|
|
|
@ -165,7 +165,7 @@ func (p *Provider) getClientOpts() ([]client.Opt, error) {
|
||||||
|
|
||||||
conf, err := p.TLS.CreateTLSConfig(ctx)
|
conf, err := p.TLS.CreateTLSConfig(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, fmt.Errorf("unable to create client TLS configuration: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
hostURL, err := client.ParseHostURL(p.Endpoint)
|
hostURL, err := client.ParseHostURL(p.Endpoint)
|
||||||
|
|
|
@ -55,7 +55,7 @@ func (p *Provider) Init() error {
|
||||||
if p.TLS != nil {
|
if p.TLS != nil {
|
||||||
tlsConfig, err := p.TLS.CreateTLSConfig(context.Background())
|
tlsConfig, err := p.TLS.CreateTLSConfig(context.Background())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("unable to create TLS configuration: %w", err)
|
return fmt.Errorf("unable to create client TLS configuration: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
p.httpClient.Transport = &http.Transport{
|
p.httpClient.Transport = &http.Transport{
|
||||||
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
apiVersion: traefik.containo.us/v1alpha1
|
||||||
|
kind: MiddlewareTCP
|
||||||
|
metadata:
|
||||||
|
name: multiple---hyphens
|
||||||
|
namespace: default
|
||||||
|
spec:
|
||||||
|
ipWhiteList:
|
||||||
|
sourceRange:
|
||||||
|
- 127.0.0.1/32
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: traefik.containo.us/v1alpha1
|
||||||
|
kind: IngressRouteTCP
|
||||||
|
metadata:
|
||||||
|
name: test.route
|
||||||
|
namespace: default
|
||||||
|
|
||||||
|
spec:
|
||||||
|
entryPoints:
|
||||||
|
- foo
|
||||||
|
|
||||||
|
routes:
|
||||||
|
- match: HostSNI(`foo.com`)
|
||||||
|
services:
|
||||||
|
- name: whoamitcp
|
||||||
|
port: 8000
|
||||||
|
|
||||||
|
middlewares:
|
||||||
|
- name: multiple---hyphens
|
|
@ -0,0 +1,31 @@
|
||||||
|
apiVersion: traefik.containo.us/v1alpha1
|
||||||
|
kind: Middleware
|
||||||
|
metadata:
|
||||||
|
name: multiple---hyphens
|
||||||
|
namespace: default
|
||||||
|
|
||||||
|
spec:
|
||||||
|
stripPrefix:
|
||||||
|
prefixes:
|
||||||
|
- /tobestripped
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: traefik.containo.us/v1alpha1
|
||||||
|
kind: IngressRoute
|
||||||
|
metadata:
|
||||||
|
name: test2.route
|
||||||
|
namespace: default
|
||||||
|
|
||||||
|
spec:
|
||||||
|
entryPoints:
|
||||||
|
- web
|
||||||
|
|
||||||
|
routes:
|
||||||
|
- match: Host(`foo.com`) && PathPrefix(`/tobestripped`)
|
||||||
|
priority: 12
|
||||||
|
kind: Rule
|
||||||
|
services:
|
||||||
|
- name: whoami
|
||||||
|
port: 80
|
||||||
|
middlewares:
|
||||||
|
- name: multiple---hyphens
|
|
@ -25,6 +25,7 @@ import (
|
||||||
"github.com/traefik/traefik/v2/pkg/provider/kubernetes/crd/traefik/v1alpha1"
|
"github.com/traefik/traefik/v2/pkg/provider/kubernetes/crd/traefik/v1alpha1"
|
||||||
"github.com/traefik/traefik/v2/pkg/safe"
|
"github.com/traefik/traefik/v2/pkg/safe"
|
||||||
"github.com/traefik/traefik/v2/pkg/tls"
|
"github.com/traefik/traefik/v2/pkg/tls"
|
||||||
|
"github.com/traefik/traefik/v2/pkg/types"
|
||||||
corev1 "k8s.io/api/core/v1"
|
corev1 "k8s.io/api/core/v1"
|
||||||
apiextensionv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
apiextensionv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
||||||
"k8s.io/apimachinery/pkg/labels"
|
"k8s.io/apimachinery/pkg/labels"
|
||||||
|
@ -483,7 +484,7 @@ func createForwardAuthMiddleware(k8sClient Client, namespace string, auth *v1alp
|
||||||
return forwardAuth, nil
|
return forwardAuth, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
forwardAuth.TLS = &dynamic.ClientTLS{
|
forwardAuth.TLS = &types.ClientTLS{
|
||||||
CAOptional: auth.TLS.CAOptional,
|
CAOptional: auth.TLS.CAOptional,
|
||||||
InsecureSkipVerify: auth.TLS.InsecureSkipVerify,
|
InsecureSkipVerify: auth.TLS.InsecureSkipVerify,
|
||||||
}
|
}
|
||||||
|
|
|
@ -183,7 +183,7 @@ func (p *Provider) makeMiddlewareKeys(ctx context.Context, ingRouteNamespace str
|
||||||
ns = mi.Namespace
|
ns = mi.Namespace
|
||||||
}
|
}
|
||||||
|
|
||||||
mds = append(mds, makeID(ns, name))
|
mds = append(mds, provider.Normalize(makeID(ns, name)))
|
||||||
}
|
}
|
||||||
|
|
||||||
return mds, nil
|
return mds, nil
|
||||||
|
|
|
@ -10,6 +10,7 @@ import (
|
||||||
|
|
||||||
"github.com/traefik/traefik/v2/pkg/config/dynamic"
|
"github.com/traefik/traefik/v2/pkg/config/dynamic"
|
||||||
"github.com/traefik/traefik/v2/pkg/log"
|
"github.com/traefik/traefik/v2/pkg/log"
|
||||||
|
"github.com/traefik/traefik/v2/pkg/provider"
|
||||||
"github.com/traefik/traefik/v2/pkg/provider/kubernetes/crd/traefik/v1alpha1"
|
"github.com/traefik/traefik/v2/pkg/provider/kubernetes/crd/traefik/v1alpha1"
|
||||||
"github.com/traefik/traefik/v2/pkg/tls"
|
"github.com/traefik/traefik/v2/pkg/tls"
|
||||||
corev1 "k8s.io/api/core/v1"
|
corev1 "k8s.io/api/core/v1"
|
||||||
|
@ -162,7 +163,7 @@ func (p *Provider) makeMiddlewareTCPKeys(ctx context.Context, ingRouteTCPNamespa
|
||||||
ns = mi.Namespace
|
ns = mi.Namespace
|
||||||
}
|
}
|
||||||
|
|
||||||
mds = append(mds, makeID(ns, mi.Name))
|
mds = append(mds, provider.Normalize(makeID(ns, mi.Name)))
|
||||||
}
|
}
|
||||||
|
|
||||||
return mds, nil
|
return mds, nil
|
||||||
|
|
|
@ -11,13 +11,14 @@ import (
|
||||||
auth "github.com/abbot/go-http-auth"
|
auth "github.com/abbot/go-http-auth"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"github.com/traefik/paerser/types"
|
ptypes "github.com/traefik/paerser/types"
|
||||||
"github.com/traefik/traefik/v2/pkg/config/dynamic"
|
"github.com/traefik/traefik/v2/pkg/config/dynamic"
|
||||||
"github.com/traefik/traefik/v2/pkg/provider"
|
"github.com/traefik/traefik/v2/pkg/provider"
|
||||||
crdfake "github.com/traefik/traefik/v2/pkg/provider/kubernetes/crd/generated/clientset/versioned/fake"
|
crdfake "github.com/traefik/traefik/v2/pkg/provider/kubernetes/crd/generated/clientset/versioned/fake"
|
||||||
"github.com/traefik/traefik/v2/pkg/provider/kubernetes/crd/traefik/v1alpha1"
|
"github.com/traefik/traefik/v2/pkg/provider/kubernetes/crd/traefik/v1alpha1"
|
||||||
"github.com/traefik/traefik/v2/pkg/provider/kubernetes/k8s"
|
"github.com/traefik/traefik/v2/pkg/provider/kubernetes/k8s"
|
||||||
"github.com/traefik/traefik/v2/pkg/tls"
|
"github.com/traefik/traefik/v2/pkg/tls"
|
||||||
|
"github.com/traefik/traefik/v2/pkg/types"
|
||||||
corev1 "k8s.io/api/core/v1"
|
corev1 "k8s.io/api/core/v1"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
"k8s.io/apimachinery/pkg/util/intstr"
|
"k8s.io/apimachinery/pkg/util/intstr"
|
||||||
|
@ -151,6 +152,54 @@ func TestLoadIngressRouteTCPs(t *testing.T) {
|
||||||
TLS: &dynamic.TLSConfiguration{},
|
TLS: &dynamic.TLSConfiguration{},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
desc: "Middlewares in ingress route config are normalized",
|
||||||
|
paths: []string{"tcp/services.yml", "tcp/with_middleware_multiple_hyphens.yml"},
|
||||||
|
expected: &dynamic.Configuration{
|
||||||
|
UDP: &dynamic.UDPConfiguration{
|
||||||
|
Routers: map[string]*dynamic.UDPRouter{},
|
||||||
|
Services: map[string]*dynamic.UDPService{},
|
||||||
|
},
|
||||||
|
HTTP: &dynamic.HTTPConfiguration{
|
||||||
|
Routers: map[string]*dynamic.Router{},
|
||||||
|
Middlewares: map[string]*dynamic.Middleware{},
|
||||||
|
Services: map[string]*dynamic.Service{},
|
||||||
|
ServersTransports: map[string]*dynamic.ServersTransport{},
|
||||||
|
},
|
||||||
|
TCP: &dynamic.TCPConfiguration{
|
||||||
|
Routers: map[string]*dynamic.TCPRouter{
|
||||||
|
"default-test.route-fdd3e9338e47a45efefc": {
|
||||||
|
EntryPoints: []string{"foo"},
|
||||||
|
Service: "default-test.route-fdd3e9338e47a45efefc",
|
||||||
|
Middlewares: []string{"default-multiple-hyphens"},
|
||||||
|
Rule: "HostSNI(`foo.com`)",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Middlewares: map[string]*dynamic.TCPMiddleware{
|
||||||
|
"default-multiple-hyphens": {
|
||||||
|
IPWhiteList: &dynamic.TCPIPWhiteList{
|
||||||
|
SourceRange: []string{"127.0.0.1/32"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Services: map[string]*dynamic.TCPService{
|
||||||
|
"default-test.route-fdd3e9338e47a45efefc": {
|
||||||
|
LoadBalancer: &dynamic.TCPServersLoadBalancer{
|
||||||
|
Servers: []dynamic.TCPServer{
|
||||||
|
{
|
||||||
|
Address: "10.10.0.1:8000",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Address: "10.10.0.2:8000",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
TLS: &dynamic.TLSConfiguration{},
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
desc: "Simple Ingress Route, with foo entrypoint and crossprovider middleware",
|
desc: "Simple Ingress Route, with foo entrypoint and crossprovider middleware",
|
||||||
paths: []string{"tcp/services.yml", "tcp/with_middleware_crossprovider.yml"},
|
paths: []string{"tcp/services.yml", "tcp/with_middleware_crossprovider.yml"},
|
||||||
|
@ -1458,6 +1507,57 @@ func TestLoadIngressRoutes(t *testing.T) {
|
||||||
TLS: &dynamic.TLSConfiguration{},
|
TLS: &dynamic.TLSConfiguration{},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
desc: "Middlewares in ingress route config are normalized",
|
||||||
|
AllowCrossNamespace: true,
|
||||||
|
paths: []string{"services.yml", "with_middleware_multiple_hyphens.yml"},
|
||||||
|
expected: &dynamic.Configuration{
|
||||||
|
UDP: &dynamic.UDPConfiguration{
|
||||||
|
Routers: map[string]*dynamic.UDPRouter{},
|
||||||
|
Services: map[string]*dynamic.UDPService{},
|
||||||
|
},
|
||||||
|
TCP: &dynamic.TCPConfiguration{
|
||||||
|
Routers: map[string]*dynamic.TCPRouter{},
|
||||||
|
Middlewares: map[string]*dynamic.TCPMiddleware{},
|
||||||
|
Services: map[string]*dynamic.TCPService{},
|
||||||
|
},
|
||||||
|
HTTP: &dynamic.HTTPConfiguration{
|
||||||
|
Routers: map[string]*dynamic.Router{
|
||||||
|
"default-test2-route-23c7f4c450289ee29016": {
|
||||||
|
EntryPoints: []string{"web"},
|
||||||
|
Service: "default-test2-route-23c7f4c450289ee29016",
|
||||||
|
Rule: "Host(`foo.com`) && PathPrefix(`/tobestripped`)",
|
||||||
|
Priority: 12,
|
||||||
|
Middlewares: []string{"default-multiple-hyphens"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Middlewares: map[string]*dynamic.Middleware{
|
||||||
|
"default-multiple-hyphens": {
|
||||||
|
StripPrefix: &dynamic.StripPrefix{
|
||||||
|
Prefixes: []string{"/tobestripped"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Services: map[string]*dynamic.Service{
|
||||||
|
"default-test2-route-23c7f4c450289ee29016": {
|
||||||
|
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||||
|
Servers: []dynamic.Server{
|
||||||
|
{
|
||||||
|
URL: "http://10.10.0.1:80",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
URL: "http://10.10.0.2:80",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
PassHostHeader: Bool(true),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ServersTransports: map[string]*dynamic.ServersTransport{},
|
||||||
|
},
|
||||||
|
TLS: &dynamic.TLSConfiguration{},
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
desc: "Simple Ingress Route with middleware crossprovider",
|
desc: "Simple Ingress Route with middleware crossprovider",
|
||||||
AllowCrossNamespace: true,
|
AllowCrossNamespace: true,
|
||||||
|
@ -3146,7 +3246,7 @@ func TestLoadIngressRoutes(t *testing.T) {
|
||||||
"default-forwardauth": {
|
"default-forwardauth": {
|
||||||
ForwardAuth: &dynamic.ForwardAuth{
|
ForwardAuth: &dynamic.ForwardAuth{
|
||||||
Address: "test.com",
|
Address: "test.com",
|
||||||
TLS: &dynamic.ClientTLS{
|
TLS: &types.ClientTLS{
|
||||||
CA: "-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----",
|
CA: "-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----",
|
||||||
Cert: "-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----",
|
Cert: "-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----",
|
||||||
Key: "-----BEGIN PRIVATE KEY-----\n-----END PRIVATE KEY-----",
|
Key: "-----BEGIN PRIVATE KEY-----\n-----END PRIVATE KEY-----",
|
||||||
|
@ -3515,17 +3615,17 @@ func TestLoadIngressRoutes(t *testing.T) {
|
||||||
MaxIdleConnsPerHost: 42,
|
MaxIdleConnsPerHost: 42,
|
||||||
DisableHTTP2: true,
|
DisableHTTP2: true,
|
||||||
ForwardingTimeouts: &dynamic.ForwardingTimeouts{
|
ForwardingTimeouts: &dynamic.ForwardingTimeouts{
|
||||||
DialTimeout: types.Duration(42 * time.Second),
|
DialTimeout: ptypes.Duration(42 * time.Second),
|
||||||
ResponseHeaderTimeout: types.Duration(42 * time.Second),
|
ResponseHeaderTimeout: ptypes.Duration(42 * time.Second),
|
||||||
IdleConnTimeout: types.Duration(42 * time.Millisecond),
|
IdleConnTimeout: ptypes.Duration(42 * time.Millisecond),
|
||||||
},
|
},
|
||||||
PeerCertURI: "foo://bar",
|
PeerCertURI: "foo://bar",
|
||||||
},
|
},
|
||||||
"default-test": {
|
"default-test": {
|
||||||
ServerName: "test",
|
ServerName: "test",
|
||||||
ForwardingTimeouts: &dynamic.ForwardingTimeouts{
|
ForwardingTimeouts: &dynamic.ForwardingTimeouts{
|
||||||
DialTimeout: types.Duration(30 * time.Second),
|
DialTimeout: ptypes.Duration(30 * time.Second),
|
||||||
IdleConnTimeout: types.Duration(90 * time.Second),
|
IdleConnTimeout: ptypes.Duration(90 * time.Second),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -14,7 +14,7 @@ import (
|
||||||
func MustParseYaml(content []byte) []runtime.Object {
|
func MustParseYaml(content []byte) []runtime.Object {
|
||||||
acceptedK8sTypes := regexp.MustCompile(`^(Namespace|Deployment|Endpoints|Service|Ingress|IngressRoute|IngressRouteTCP|IngressRouteUDP|Middleware|MiddlewareTCP|Secret|TLSOption|TLSStore|TraefikService|IngressClass|ServersTransport|GatewayClass|Gateway|HTTPRoute|TCPRoute|TLSRoute)$`)
|
acceptedK8sTypes := regexp.MustCompile(`^(Namespace|Deployment|Endpoints|Service|Ingress|IngressRoute|IngressRouteTCP|IngressRouteUDP|Middleware|MiddlewareTCP|Secret|TLSOption|TLSStore|TraefikService|IngressClass|ServersTransport|GatewayClass|Gateway|HTTPRoute|TCPRoute|TLSRoute)$`)
|
||||||
|
|
||||||
files := strings.Split(string(content), "---")
|
files := strings.Split(string(content), "---\n")
|
||||||
retVal := make([]runtime.Object, 0, len(files))
|
retVal := make([]runtime.Object, 0, len(files))
|
||||||
for _, file := range files {
|
for _, file := range files {
|
||||||
if file == "\n" || file == "" {
|
if file == "\n" || file == "" {
|
||||||
|
|
|
@ -170,7 +170,7 @@ func (p *Provider) createKVClient(ctx context.Context) (store.Store, error) {
|
||||||
var err error
|
var err error
|
||||||
storeConfig.TLS, err = p.TLS.CreateTLSConfig(ctx)
|
storeConfig.TLS, err = p.TLS.CreateTLSConfig(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, fmt.Errorf("unable to create client TLS configuration: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -402,7 +402,7 @@ func Test_buildConfiguration(t *testing.T) {
|
||||||
"Middleware08": {
|
"Middleware08": {
|
||||||
ForwardAuth: &dynamic.ForwardAuth{
|
ForwardAuth: &dynamic.ForwardAuth{
|
||||||
Address: "foobar",
|
Address: "foobar",
|
||||||
TLS: &dynamic.ClientTLS{
|
TLS: &types.ClientTLS{
|
||||||
CA: "foobar",
|
CA: "foobar",
|
||||||
CAOptional: true,
|
CAOptional: true,
|
||||||
Cert: "foobar",
|
Cert: "foobar",
|
||||||
|
@ -481,7 +481,7 @@ func Test_buildConfiguration(t *testing.T) {
|
||||||
NotAfter: true,
|
NotAfter: true,
|
||||||
NotBefore: true,
|
NotBefore: true,
|
||||||
Sans: true,
|
Sans: true,
|
||||||
Subject: &dynamic.TLSCLientCertificateSubjectDNInfo{
|
Subject: &dynamic.TLSClientCertificateSubjectDNInfo{
|
||||||
Country: true,
|
Country: true,
|
||||||
Province: true,
|
Province: true,
|
||||||
Locality: true,
|
Locality: true,
|
||||||
|
@ -491,7 +491,7 @@ func Test_buildConfiguration(t *testing.T) {
|
||||||
SerialNumber: true,
|
SerialNumber: true,
|
||||||
DomainComponent: true,
|
DomainComponent: true,
|
||||||
},
|
},
|
||||||
Issuer: &dynamic.TLSCLientCertificateIssuerDNInfo{
|
Issuer: &dynamic.TLSClientCertificateIssuerDNInfo{
|
||||||
Country: true,
|
Country: true,
|
||||||
Province: true,
|
Province: true,
|
||||||
Locality: true,
|
Locality: true,
|
||||||
|
|
|
@ -134,7 +134,7 @@ func (p *Provider) Provide(configurationChan chan<- dynamic.Message, pool *safe.
|
||||||
}
|
}
|
||||||
TLSConfig, err := p.TLS.CreateTLSConfig(ctx)
|
TLSConfig, err := p.TLS.CreateTLSConfig(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return fmt.Errorf("unable to create client TLS configuration: %w", err)
|
||||||
}
|
}
|
||||||
confg.HTTPClient = &http.Client{
|
confg.HTTPClient = &http.Client{
|
||||||
Transport: &http.Transport{
|
Transport: &http.Transport{
|
||||||
|
|
|
@ -15,7 +15,7 @@ type server struct {
|
||||||
// WRRLoadBalancer is a naive RoundRobin load balancer for TCP services.
|
// WRRLoadBalancer is a naive RoundRobin load balancer for TCP services.
|
||||||
type WRRLoadBalancer struct {
|
type WRRLoadBalancer struct {
|
||||||
servers []server
|
servers []server
|
||||||
lock sync.RWMutex
|
lock sync.Mutex
|
||||||
currentWeight int
|
currentWeight int
|
||||||
index int
|
index int
|
||||||
}
|
}
|
||||||
|
@ -29,16 +29,16 @@ func NewWRRLoadBalancer() *WRRLoadBalancer {
|
||||||
|
|
||||||
// ServeTCP forwards the connection to the right service.
|
// ServeTCP forwards the connection to the right service.
|
||||||
func (b *WRRLoadBalancer) ServeTCP(conn WriteCloser) {
|
func (b *WRRLoadBalancer) ServeTCP(conn WriteCloser) {
|
||||||
if len(b.servers) == 0 {
|
b.lock.Lock()
|
||||||
log.WithoutContext().Error("no available server")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
next, err := b.next()
|
next, err := b.next()
|
||||||
|
b.lock.Unlock()
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.WithoutContext().Errorf("Error during load balancing: %v", err)
|
log.WithoutContext().Errorf("Error during load balancing: %v", err)
|
||||||
conn.Close()
|
conn.Close()
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
next.ServeTCP(conn)
|
next.ServeTCP(conn)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,6 +50,9 @@ func (b *WRRLoadBalancer) AddServer(serverHandler Handler) {
|
||||||
|
|
||||||
// AddWeightServer appends a server to the existing list with a weight.
|
// AddWeightServer appends a server to the existing list with a weight.
|
||||||
func (b *WRRLoadBalancer) AddWeightServer(serverHandler Handler, weight *int) {
|
func (b *WRRLoadBalancer) AddWeightServer(serverHandler Handler, weight *int) {
|
||||||
|
b.lock.Lock()
|
||||||
|
defer b.lock.Unlock()
|
||||||
|
|
||||||
w := 1
|
w := 1
|
||||||
if weight != nil {
|
if weight != nil {
|
||||||
w = *weight
|
w = *weight
|
||||||
|
@ -87,9 +90,6 @@ func gcd(a, b int) int {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *WRRLoadBalancer) next() (Handler, error) {
|
func (b *WRRLoadBalancer) next() (Handler, error) {
|
||||||
b.lock.Lock()
|
|
||||||
defer b.lock.Unlock()
|
|
||||||
|
|
||||||
if len(b.servers) == 0 {
|
if len(b.servers) == 0 {
|
||||||
return nil, fmt.Errorf("no servers in the pool")
|
return nil, fmt.Errorf("no servers in the pool")
|
||||||
}
|
}
|
||||||
|
@ -98,10 +98,14 @@ func (b *WRRLoadBalancer) next() (Handler, error) {
|
||||||
// it calculates the GCD and subtracts it on every iteration, what interleaves servers
|
// it calculates the GCD and subtracts it on every iteration, what interleaves servers
|
||||||
// and allows us not to build an iterator every time we readjust weights
|
// and allows us not to build an iterator every time we readjust weights
|
||||||
|
|
||||||
// GCD across all enabled servers
|
|
||||||
gcd := b.weightGcd()
|
|
||||||
// Maximum weight across all enabled servers
|
// Maximum weight across all enabled servers
|
||||||
max := b.maxWeight()
|
max := b.maxWeight()
|
||||||
|
if max == 0 {
|
||||||
|
return nil, fmt.Errorf("all servers have 0 weight")
|
||||||
|
}
|
||||||
|
|
||||||
|
// GCD across all enabled servers
|
||||||
|
gcd := b.weightGcd()
|
||||||
|
|
||||||
for {
|
for {
|
||||||
b.index = (b.index + 1) % len(b.servers)
|
b.index = (b.index + 1) % len(b.servers)
|
||||||
|
@ -109,9 +113,6 @@ func (b *WRRLoadBalancer) next() (Handler, error) {
|
||||||
b.currentWeight -= gcd
|
b.currentWeight -= gcd
|
||||||
if b.currentWeight <= 0 {
|
if b.currentWeight <= 0 {
|
||||||
b.currentWeight = max
|
b.currentWeight = max
|
||||||
if b.currentWeight == 0 {
|
|
||||||
return nil, fmt.Errorf("all servers have 0 weight")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
srv := b.servers[b.index]
|
srv := b.servers[b.index]
|
||||||
|
|
|
@ -10,7 +10,8 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type fakeConn struct {
|
type fakeConn struct {
|
||||||
call map[string]int
|
writeCall map[string]int
|
||||||
|
closeCall int
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *fakeConn) Read(b []byte) (n int, err error) {
|
func (f *fakeConn) Read(b []byte) (n int, err error) {
|
||||||
|
@ -18,12 +19,13 @@ func (f *fakeConn) Read(b []byte) (n int, err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *fakeConn) Write(b []byte) (n int, err error) {
|
func (f *fakeConn) Write(b []byte) (n int, err error) {
|
||||||
f.call[string(b)]++
|
f.writeCall[string(b)]++
|
||||||
return len(b), nil
|
return len(b), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *fakeConn) Close() error {
|
func (f *fakeConn) Close() error {
|
||||||
panic("implement me")
|
f.closeCall++
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *fakeConn) LocalAddr() net.Addr {
|
func (f *fakeConn) LocalAddr() net.Addr {
|
||||||
|
@ -55,7 +57,8 @@ func TestLoadBalancing(t *testing.T) {
|
||||||
desc string
|
desc string
|
||||||
serversWeight map[string]int
|
serversWeight map[string]int
|
||||||
totalCall int
|
totalCall int
|
||||||
expected map[string]int
|
expectedWrite map[string]int
|
||||||
|
expectedClose int
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
desc: "RoundRobin",
|
desc: "RoundRobin",
|
||||||
|
@ -64,7 +67,7 @@ func TestLoadBalancing(t *testing.T) {
|
||||||
"h2": 1,
|
"h2": 1,
|
||||||
},
|
},
|
||||||
totalCall: 4,
|
totalCall: 4,
|
||||||
expected: map[string]int{
|
expectedWrite: map[string]int{
|
||||||
"h1": 2,
|
"h1": 2,
|
||||||
"h2": 2,
|
"h2": 2,
|
||||||
},
|
},
|
||||||
|
@ -76,7 +79,7 @@ func TestLoadBalancing(t *testing.T) {
|
||||||
"h2": 1,
|
"h2": 1,
|
||||||
},
|
},
|
||||||
totalCall: 4,
|
totalCall: 4,
|
||||||
expected: map[string]int{
|
expectedWrite: map[string]int{
|
||||||
"h1": 3,
|
"h1": 3,
|
||||||
"h2": 1,
|
"h2": 1,
|
||||||
},
|
},
|
||||||
|
@ -88,22 +91,33 @@ func TestLoadBalancing(t *testing.T) {
|
||||||
"h2": 1,
|
"h2": 1,
|
||||||
},
|
},
|
||||||
totalCall: 16,
|
totalCall: 16,
|
||||||
expected: map[string]int{
|
expectedWrite: map[string]int{
|
||||||
"h1": 12,
|
"h1": 12,
|
||||||
"h2": 4,
|
"h2": 4,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "WeighedRoundRobin with 0 weight server",
|
desc: "WeighedRoundRobin with one 0 weight server",
|
||||||
serversWeight: map[string]int{
|
serversWeight: map[string]int{
|
||||||
"h1": 3,
|
"h1": 3,
|
||||||
"h2": 0,
|
"h2": 0,
|
||||||
},
|
},
|
||||||
totalCall: 16,
|
totalCall: 16,
|
||||||
expected: map[string]int{
|
expectedWrite: map[string]int{
|
||||||
"h1": 16,
|
"h1": 16,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
desc: "WeighedRoundRobin with all servers with 0 weight",
|
||||||
|
serversWeight: map[string]int{
|
||||||
|
"h1": 0,
|
||||||
|
"h2": 0,
|
||||||
|
"h3": 0,
|
||||||
|
},
|
||||||
|
totalCall: 10,
|
||||||
|
expectedWrite: map[string]int{},
|
||||||
|
expectedClose: 10,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, test := range testCases {
|
for _, test := range testCases {
|
||||||
|
@ -120,12 +134,13 @@ func TestLoadBalancing(t *testing.T) {
|
||||||
}), &weight)
|
}), &weight)
|
||||||
}
|
}
|
||||||
|
|
||||||
conn := &fakeConn{call: make(map[string]int)}
|
conn := &fakeConn{writeCall: make(map[string]int)}
|
||||||
for i := 0; i < test.totalCall; i++ {
|
for i := 0; i < test.totalCall; i++ {
|
||||||
balancer.ServeTCP(conn)
|
balancer.ServeTCP(conn)
|
||||||
}
|
}
|
||||||
|
|
||||||
assert.Equal(t, test.expected, conn.call)
|
assert.Equal(t, test.expectedWrite, conn.writeCall)
|
||||||
|
assert.Equal(t, test.expectedClose, conn.closeCall)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
13
pkg/types/fixtures/cert.pem
Normal file
13
pkg/types/fixtures/cert.pem
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIB9jCCAV+gAwIBAgIQI3edJckNbicw4WIHs5Ws9TANBgkqhkiG9w0BAQsFADAS
|
||||||
|
MRAwDgYDVQQKEwdBY21lIENvMCAXDTcwMDEwMTAwMDAwMFoYDzIwODQwMTI5MTYw
|
||||||
|
MDAwWjASMRAwDgYDVQQKEwdBY21lIENvMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCB
|
||||||
|
iQKBgQCb8oWyME1QRBoMLFei3M8TVKwfZfW74cVjtcugCBMTTOTCouEIgjjmiMv6
|
||||||
|
FdMio2uBcgeD9R3dOtjjnA7N+xjwZ4vIPqDlJRE3YbfpV9igVX3sXU7ssHTSH0vs
|
||||||
|
R0TuYJwGReIFUnu5QIjGwVorodF+CQ8dTnyXVLeQVU9kvjohHwIDAQABo0swSTAO
|
||||||
|
BgNVHQ8BAf8EBAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIw
|
||||||
|
ADAUBgNVHREEDTALgglsb2NhbGhvc3QwDQYJKoZIhvcNAQELBQADgYEADqylUQ/4
|
||||||
|
lrxh4h8UUQ2wKATQ2kG2YvMGlaIhr2vPZo2QDBlmL2xzai7YXX3+JZyM15TNCamn
|
||||||
|
WtFR7WQIOHzKA1GkR9WkaXKmFbJjhGMSZVCG6ghhTjzB+stBYZXhBsdjCJbkZWBu
|
||||||
|
OeI73oivo0MdI+4iCYCo7TnoY4PZGObwcgI=
|
||||||
|
-----END CERTIFICATE-----
|
16
pkg/types/fixtures/key.pem
Normal file
16
pkg/types/fixtures/key.pem
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
-----BEGIN PRIVATE KEY-----
|
||||||
|
MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAJvyhbIwTVBEGgws
|
||||||
|
V6LczxNUrB9l9bvhxWO1y6AIExNM5MKi4QiCOOaIy/oV0yKja4FyB4P1Hd062OOc
|
||||||
|
Ds37GPBni8g+oOUlETdht+lX2KBVfexdTuywdNIfS+xHRO5gnAZF4gVSe7lAiMbB
|
||||||
|
Wiuh0X4JDx1OfJdUt5BVT2S+OiEfAgMBAAECgYA9+PbghQl0aFvhko2RDybLi86K
|
||||||
|
+73X2DTVFx3AjvTlqp0OLCQ5eWabVqmYzKuHDGJgoqwR6Irhq80dRpsriCm0YNui
|
||||||
|
mMV35bbimOKz9FoCTKx0ZB6xsqrVoFhjVmX3DOD9Txe41H42ZxmccOKZndR/QaXz
|
||||||
|
VV+1W/Wbz2VawnkyYQJBAMvF6w2eOJRRoN8e7GM7b7uqkupJPp9axgFREoJZb16W
|
||||||
|
mqXUZnH4Cydzc5keG4yknQRHdgz6RrQxnvR7GyKHLfUCQQDD6qG9D5BX0+mNW6TG
|
||||||
|
PRwW/L2qWgnmg9lxtSSQat9ZOnBhw2OLPi0zTu4p70oSmU67/YJr50HEoJpRccZJ
|
||||||
|
mnJDAkBdBTtY2xpe8qhqUjZ80hweYi5wzwDMQ+bRoQ2+/U6usjdkbgJaEm4dE0H4
|
||||||
|
6tqOqHKZCnokUHfIOEKkvjHT4DulAkBAgiJNSTGi6aDOLa28pGR6YS/mRo1Z/HH9
|
||||||
|
kcJ/VuFB1Q8p8Zb2QzvI2CVtY2AFbbtSBPALrXKnVqZZSNgcZiFXAkEAvcLKaEXE
|
||||||
|
haGMGwq2BLADPHqAR3hdCJL3ikMJwWUsTkTjm973iEIEZfF5j57EzRI4bASm4Zq5
|
||||||
|
Zt3BcblLODQ//w==
|
||||||
|
-----END PRIVATE KEY-----
|
|
@ -4,12 +4,15 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/traefik/traefik/v2/pkg/log"
|
"github.com/traefik/traefik/v2/pkg/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// +k8s:deepcopy-gen=true
|
||||||
|
|
||||||
// ClientTLS holds TLS specific configurations as client
|
// ClientTLS holds TLS specific configurations as client
|
||||||
// CA, Cert and Key can be either path or file contents.
|
// CA, Cert and Key can be either path or file contents.
|
||||||
type ClientTLS struct {
|
type ClientTLS struct {
|
||||||
|
@ -27,7 +30,9 @@ func (clientTLS *ClientTLS) CreateTLSConfig(ctx context.Context) (*tls.Config, e
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
caPool := x509.NewCertPool()
|
// Not initialized, to rely on system bundle.
|
||||||
|
var caPool *x509.CertPool
|
||||||
|
|
||||||
clientAuth := tls.NoClientCert
|
clientAuth := tls.NoClientCert
|
||||||
if clientTLS.CA != "" {
|
if clientTLS.CA != "" {
|
||||||
var ca []byte
|
var ca []byte
|
||||||
|
@ -41,8 +46,9 @@ func (clientTLS *ClientTLS) CreateTLSConfig(ctx context.Context) (*tls.Config, e
|
||||||
ca = []byte(clientTLS.CA)
|
ca = []byte(clientTLS.CA)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
caPool = x509.NewCertPool()
|
||||||
if !caPool.AppendCertsFromPEM(ca) {
|
if !caPool.AppendCertsFromPEM(ca) {
|
||||||
return nil, fmt.Errorf("failed to parse CA")
|
return nil, errors.New("failed to parse CA")
|
||||||
}
|
}
|
||||||
|
|
||||||
if clientTLS.CAOptional {
|
if clientTLS.CAOptional {
|
||||||
|
@ -52,34 +58,24 @@ func (clientTLS *ClientTLS) CreateTLSConfig(ctx context.Context) (*tls.Config, e
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !clientTLS.InsecureSkipVerify && (len(clientTLS.Cert) == 0 || len(clientTLS.Key) == 0) {
|
hasCert := len(clientTLS.Cert) > 0
|
||||||
return nil, fmt.Errorf("TLS Certificate or Key file must be set when TLS configuration is created")
|
hasKey := len(clientTLS.Key) > 0
|
||||||
|
|
||||||
|
if hasCert != hasKey {
|
||||||
|
return nil, errors.New("both TLS cert and key must be defined")
|
||||||
}
|
}
|
||||||
|
|
||||||
cert := tls.Certificate{}
|
if !hasCert || !hasKey {
|
||||||
_, errKeyIsFile := os.Stat(clientTLS.Key)
|
return &tls.Config{
|
||||||
|
RootCAs: caPool,
|
||||||
|
InsecureSkipVerify: clientTLS.InsecureSkipVerify,
|
||||||
|
ClientAuth: clientAuth,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
if len(clientTLS.Cert) > 0 && len(clientTLS.Key) > 0 {
|
cert, err := loadKeyPair(clientTLS.Cert, clientTLS.Key)
|
||||||
var err error
|
if err != nil {
|
||||||
if _, errCertIsFile := os.Stat(clientTLS.Cert); errCertIsFile == nil {
|
return nil, err
|
||||||
if errKeyIsFile == nil {
|
|
||||||
cert, err = tls.LoadX509KeyPair(clientTLS.Cert, clientTLS.Key)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to load TLS keypair: %w", err)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return nil, fmt.Errorf("TLS cert is a file, but tls key is not")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if errKeyIsFile != nil {
|
|
||||||
cert, err = tls.X509KeyPair([]byte(clientTLS.Cert), []byte(clientTLS.Key))
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to load TLS keypair: %w", err)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return nil, fmt.Errorf("TLS key is a file, but tls cert is not")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return &tls.Config{
|
return &tls.Config{
|
||||||
|
@ -89,3 +85,27 @@ func (clientTLS *ClientTLS) CreateTLSConfig(ctx context.Context) (*tls.Config, e
|
||||||
ClientAuth: clientAuth,
|
ClientAuth: clientAuth,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func loadKeyPair(cert, key string) (tls.Certificate, error) {
|
||||||
|
keyPair, err := tls.X509KeyPair([]byte(cert), []byte(key))
|
||||||
|
if err == nil {
|
||||||
|
return keyPair, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = os.Stat(cert)
|
||||||
|
if err != nil {
|
||||||
|
return tls.Certificate{}, errors.New("cert file does not exist")
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = os.Stat(key)
|
||||||
|
if err != nil {
|
||||||
|
return tls.Certificate{}, errors.New("key file does not exist")
|
||||||
|
}
|
||||||
|
|
||||||
|
keyPair, err = tls.LoadX509KeyPair(cert, key)
|
||||||
|
if err != nil {
|
||||||
|
return tls.Certificate{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return keyPair, nil
|
||||||
|
}
|
||||||
|
|
129
pkg/types/tls_test.go
Normal file
129
pkg/types/tls_test.go
Normal file
|
@ -0,0 +1,129 @@
|
||||||
|
package types
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
// go run $GOROOT/src/crypto/tls/generate_cert.go --rsa-bits 1024 --host localhost --start-date "Jan 1 00:00:00 1970" --duration=1000000h
|
||||||
|
var cert = `-----BEGIN CERTIFICATE-----
|
||||||
|
MIIB9jCCAV+gAwIBAgIQI3edJckNbicw4WIHs5Ws9TANBgkqhkiG9w0BAQsFADAS
|
||||||
|
MRAwDgYDVQQKEwdBY21lIENvMCAXDTcwMDEwMTAwMDAwMFoYDzIwODQwMTI5MTYw
|
||||||
|
MDAwWjASMRAwDgYDVQQKEwdBY21lIENvMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCB
|
||||||
|
iQKBgQCb8oWyME1QRBoMLFei3M8TVKwfZfW74cVjtcugCBMTTOTCouEIgjjmiMv6
|
||||||
|
FdMio2uBcgeD9R3dOtjjnA7N+xjwZ4vIPqDlJRE3YbfpV9igVX3sXU7ssHTSH0vs
|
||||||
|
R0TuYJwGReIFUnu5QIjGwVorodF+CQ8dTnyXVLeQVU9kvjohHwIDAQABo0swSTAO
|
||||||
|
BgNVHQ8BAf8EBAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIw
|
||||||
|
ADAUBgNVHREEDTALgglsb2NhbGhvc3QwDQYJKoZIhvcNAQELBQADgYEADqylUQ/4
|
||||||
|
lrxh4h8UUQ2wKATQ2kG2YvMGlaIhr2vPZo2QDBlmL2xzai7YXX3+JZyM15TNCamn
|
||||||
|
WtFR7WQIOHzKA1GkR9WkaXKmFbJjhGMSZVCG6ghhTjzB+stBYZXhBsdjCJbkZWBu
|
||||||
|
OeI73oivo0MdI+4iCYCo7TnoY4PZGObwcgI=
|
||||||
|
-----END CERTIFICATE-----`
|
||||||
|
|
||||||
|
var key = `-----BEGIN PRIVATE KEY-----
|
||||||
|
MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAJvyhbIwTVBEGgws
|
||||||
|
V6LczxNUrB9l9bvhxWO1y6AIExNM5MKi4QiCOOaIy/oV0yKja4FyB4P1Hd062OOc
|
||||||
|
Ds37GPBni8g+oOUlETdht+lX2KBVfexdTuywdNIfS+xHRO5gnAZF4gVSe7lAiMbB
|
||||||
|
Wiuh0X4JDx1OfJdUt5BVT2S+OiEfAgMBAAECgYA9+PbghQl0aFvhko2RDybLi86K
|
||||||
|
+73X2DTVFx3AjvTlqp0OLCQ5eWabVqmYzKuHDGJgoqwR6Irhq80dRpsriCm0YNui
|
||||||
|
mMV35bbimOKz9FoCTKx0ZB6xsqrVoFhjVmX3DOD9Txe41H42ZxmccOKZndR/QaXz
|
||||||
|
VV+1W/Wbz2VawnkyYQJBAMvF6w2eOJRRoN8e7GM7b7uqkupJPp9axgFREoJZb16W
|
||||||
|
mqXUZnH4Cydzc5keG4yknQRHdgz6RrQxnvR7GyKHLfUCQQDD6qG9D5BX0+mNW6TG
|
||||||
|
PRwW/L2qWgnmg9lxtSSQat9ZOnBhw2OLPi0zTu4p70oSmU67/YJr50HEoJpRccZJ
|
||||||
|
mnJDAkBdBTtY2xpe8qhqUjZ80hweYi5wzwDMQ+bRoQ2+/U6usjdkbgJaEm4dE0H4
|
||||||
|
6tqOqHKZCnokUHfIOEKkvjHT4DulAkBAgiJNSTGi6aDOLa28pGR6YS/mRo1Z/HH9
|
||||||
|
kcJ/VuFB1Q8p8Zb2QzvI2CVtY2AFbbtSBPALrXKnVqZZSNgcZiFXAkEAvcLKaEXE
|
||||||
|
haGMGwq2BLADPHqAR3hdCJL3ikMJwWUsTkTjm973iEIEZfF5j57EzRI4bASm4Zq5
|
||||||
|
Zt3BcblLODQ//w==
|
||||||
|
-----END PRIVATE KEY-----`
|
||||||
|
|
||||||
|
func TestClientTLS_CreateTLSConfig(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
desc string
|
||||||
|
clientTLS ClientTLS
|
||||||
|
wantCertLen int
|
||||||
|
wantCALen int
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "Configure CA",
|
||||||
|
clientTLS: ClientTLS{CA: cert},
|
||||||
|
wantCALen: 1,
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "Configure the client keyPair from strings",
|
||||||
|
clientTLS: ClientTLS{Cert: cert, Key: key},
|
||||||
|
wantCertLen: 1,
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "Configure the client keyPair from files",
|
||||||
|
clientTLS: ClientTLS{Cert: "fixtures/cert.pem", Key: "fixtures/key.pem"},
|
||||||
|
wantCertLen: 1,
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "Configure InsecureSkipVerify",
|
||||||
|
clientTLS: ClientTLS{InsecureSkipVerify: true},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "Return an error if only the client cert is provided",
|
||||||
|
clientTLS: ClientTLS{Cert: cert},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "Return an error if only the client key is provided",
|
||||||
|
clientTLS: ClientTLS{Key: key},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "Return an error if only the client cert is of type file",
|
||||||
|
clientTLS: ClientTLS{Cert: "fixtures/cert.pem", Key: key},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "Return an error if only the client key is of type file",
|
||||||
|
clientTLS: ClientTLS{Cert: cert, Key: "fixtures/key.pem"},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "Return an error if the client cert does not exist",
|
||||||
|
clientTLS: ClientTLS{Cert: "fixtures/cert2.pem", Key: "fixtures/key.pem"},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "Return an error if the client key does not exist",
|
||||||
|
clientTLS: ClientTLS{Cert: "fixtures/cert.pem", Key: "fixtures/key2.pem"},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
test := test
|
||||||
|
|
||||||
|
t.Run(test.desc, func(t *testing.T) {
|
||||||
|
tlsConfig, err := test.clientTLS.CreateTLSConfig(context.Background())
|
||||||
|
if test.wantErr {
|
||||||
|
require.Error(t, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Len(t, tlsConfig.Certificates, test.wantCertLen)
|
||||||
|
assert.Equal(t, test.clientTLS.InsecureSkipVerify, tlsConfig.InsecureSkipVerify)
|
||||||
|
|
||||||
|
if test.wantCALen > 0 {
|
||||||
|
assert.Len(t, tlsConfig.RootCAs.Subjects(), test.wantCALen)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Nil(t, tlsConfig.RootCAs)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -29,6 +29,22 @@ THE SOFTWARE.
|
||||||
|
|
||||||
package types
|
package types
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *ClientTLS) DeepCopyInto(out *ClientTLS) {
|
||||||
|
*out = *in
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClientTLS.
|
||||||
|
func (in *ClientTLS) DeepCopy() *ClientTLS {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(ClientTLS)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
func (in *Domain) DeepCopyInto(out *Domain) {
|
func (in *Domain) DeepCopyInto(out *Domain) {
|
||||||
*out = *in
|
*out = *in
|
||||||
|
|
|
@ -15,7 +15,7 @@ type server struct {
|
||||||
// WRRLoadBalancer is a naive RoundRobin load balancer for UDP services.
|
// WRRLoadBalancer is a naive RoundRobin load balancer for UDP services.
|
||||||
type WRRLoadBalancer struct {
|
type WRRLoadBalancer struct {
|
||||||
servers []server
|
servers []server
|
||||||
lock sync.RWMutex
|
lock sync.Mutex
|
||||||
currentWeight int
|
currentWeight int
|
||||||
index int
|
index int
|
||||||
}
|
}
|
||||||
|
@ -29,16 +29,16 @@ func NewWRRLoadBalancer() *WRRLoadBalancer {
|
||||||
|
|
||||||
// ServeUDP forwards the connection to the right service.
|
// ServeUDP forwards the connection to the right service.
|
||||||
func (b *WRRLoadBalancer) ServeUDP(conn *Conn) {
|
func (b *WRRLoadBalancer) ServeUDP(conn *Conn) {
|
||||||
if len(b.servers) == 0 {
|
b.lock.Lock()
|
||||||
log.WithoutContext().Error("no available server")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
next, err := b.next()
|
next, err := b.next()
|
||||||
|
b.lock.Unlock()
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.WithoutContext().Errorf("Error during load balancing: %v", err)
|
log.WithoutContext().Errorf("Error during load balancing: %v", err)
|
||||||
conn.Close()
|
conn.Close()
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
next.ServeUDP(conn)
|
next.ServeUDP(conn)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,6 +50,9 @@ func (b *WRRLoadBalancer) AddServer(serverHandler Handler) {
|
||||||
|
|
||||||
// AddWeightedServer appends a handler to the existing list with a weight.
|
// AddWeightedServer appends a handler to the existing list with a weight.
|
||||||
func (b *WRRLoadBalancer) AddWeightedServer(serverHandler Handler, weight *int) {
|
func (b *WRRLoadBalancer) AddWeightedServer(serverHandler Handler, weight *int) {
|
||||||
|
b.lock.Lock()
|
||||||
|
defer b.lock.Unlock()
|
||||||
|
|
||||||
w := 1
|
w := 1
|
||||||
if weight != nil {
|
if weight != nil {
|
||||||
w = *weight
|
w = *weight
|
||||||
|
@ -87,9 +90,6 @@ func gcd(a, b int) int {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *WRRLoadBalancer) next() (Handler, error) {
|
func (b *WRRLoadBalancer) next() (Handler, error) {
|
||||||
b.lock.Lock()
|
|
||||||
defer b.lock.Unlock()
|
|
||||||
|
|
||||||
if len(b.servers) == 0 {
|
if len(b.servers) == 0 {
|
||||||
return nil, fmt.Errorf("no servers in the pool")
|
return nil, fmt.Errorf("no servers in the pool")
|
||||||
}
|
}
|
||||||
|
@ -98,10 +98,14 @@ func (b *WRRLoadBalancer) next() (Handler, error) {
|
||||||
// but is actually very simple it calculates the GCD and subtracts it on every iteration,
|
// but is actually very simple it calculates the GCD and subtracts it on every iteration,
|
||||||
// what interleaves servers and allows us not to build an iterator every time we readjust weights.
|
// what interleaves servers and allows us not to build an iterator every time we readjust weights.
|
||||||
|
|
||||||
// GCD across all enabled servers
|
|
||||||
gcd := b.weightGcd()
|
|
||||||
// Maximum weight across all enabled servers
|
// Maximum weight across all enabled servers
|
||||||
max := b.maxWeight()
|
max := b.maxWeight()
|
||||||
|
if max == 0 {
|
||||||
|
return nil, fmt.Errorf("all servers have 0 weight")
|
||||||
|
}
|
||||||
|
|
||||||
|
// GCD across all enabled servers
|
||||||
|
gcd := b.weightGcd()
|
||||||
|
|
||||||
for {
|
for {
|
||||||
b.index = (b.index + 1) % len(b.servers)
|
b.index = (b.index + 1) % len(b.servers)
|
||||||
|
@ -109,9 +113,6 @@ func (b *WRRLoadBalancer) next() (Handler, error) {
|
||||||
b.currentWeight -= gcd
|
b.currentWeight -= gcd
|
||||||
if b.currentWeight <= 0 {
|
if b.currentWeight <= 0 {
|
||||||
b.currentWeight = max
|
b.currentWeight = max
|
||||||
if b.currentWeight == 0 {
|
|
||||||
return nil, fmt.Errorf("all servers have 0 weight")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
srv := b.servers[b.index]
|
srv := b.servers[b.index]
|
||||||
|
|
|
@ -4,11 +4,11 @@ RepositoryName = "traefik"
|
||||||
OutputType = "file"
|
OutputType = "file"
|
||||||
FileName = "traefik_changelog.md"
|
FileName = "traefik_changelog.md"
|
||||||
|
|
||||||
# example new bugfix v2.5.3
|
# example new bugfix v2.5.4
|
||||||
CurrentRef = "v2.5"
|
CurrentRef = "v2.5"
|
||||||
PreviousRef = "v2.5.2"
|
PreviousRef = "v2.5.3"
|
||||||
BaseBranch = "v2.5"
|
BaseBranch = "v2.5"
|
||||||
FutureCurrentRefName = "v2.5.3"
|
FutureCurrentRefName = "v2.5.4"
|
||||||
|
|
||||||
ThresholdPreviousRef = 10
|
ThresholdPreviousRef = 10
|
||||||
ThresholdCurrentRef = 10
|
ThresholdCurrentRef = 10
|
||||||
|
|
|
@ -12,10 +12,10 @@ then
|
||||||
# The shellcheck command are run in background, to have an overview of the linter (instead of a fail at first issue)
|
# The shellcheck command are run in background, to have an overview of the linter (instead of a fail at first issue)
|
||||||
shellcheck "${script_to_check}" &
|
shellcheck "${script_to_check}" &
|
||||||
done < <( # Search all the repository for sh and bash shebangs, excluding .js and .md files
|
done < <( # Search all the repository for sh and bash shebangs, excluding .js and .md files
|
||||||
# the folders ".git" and "vendor" are also ignored
|
# the folders ".git", "vendor" and "node_modules" are also ignored
|
||||||
grep -rI '#!/' "${script_dir}"/.. \
|
grep -rI '#!/' "${script_dir}"/.. \
|
||||||
| grep 'sh' | grep -v '.js' | grep -v '.md' \
|
| grep 'sh' | grep -v '.js' | grep -v '.md' \
|
||||||
| grep -v '.git/' | grep -v 'vendor/' \
|
| grep -v '.git/' | grep -v 'vendor/' | grep -v 'node_modules/' \
|
||||||
| cut -d':' -f1
|
| cut -d':' -f1
|
||||||
)
|
)
|
||||||
wait # Wait for all background command to be completed
|
wait # Wait for all background command to be completed
|
||||||
|
|
|
@ -64,7 +64,7 @@ export default {
|
||||||
},
|
},
|
||||||
getProviderLogoPath (service) {
|
getProviderLogoPath (service) {
|
||||||
const provider = this.getProvider(service)
|
const provider = this.getProvider(service)
|
||||||
const name = provider.name.toLowerCase()
|
const name = provider.toLowerCase()
|
||||||
|
|
||||||
if (name.includes('plugin-')) {
|
if (name.includes('plugin-')) {
|
||||||
return 'statics/providers/plugin.svg'
|
return 'statics/providers/plugin.svg'
|
||||||
|
|
Loading…
Reference in a new issue