mirror of
https://github.com/containous/traefik.git
synced 2024-12-22 13:34:03 +03:00
Merge branch v2.11 into v3.1
This commit is contained in:
commit
093989fc14
6
.github/workflows/validate.yaml
vendored
6
.github/workflows/validate.yaml
vendored
@ -8,7 +8,7 @@ on:
|
||||
env:
|
||||
GO_VERSION: '1.23'
|
||||
GOLANGCI_LINT_VERSION: v1.60.3
|
||||
MISSSPELL_VERSION: v0.6.0
|
||||
MISSPELL_VERSION: v0.6.0
|
||||
|
||||
jobs:
|
||||
|
||||
@ -29,8 +29,8 @@ jobs:
|
||||
- name: Install golangci-lint ${{ env.GOLANGCI_LINT_VERSION }}
|
||||
run: curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin ${GOLANGCI_LINT_VERSION}
|
||||
|
||||
- name: Install missspell ${{ env.MISSSPELL_VERSION }}
|
||||
run: curl -sfL https://raw.githubusercontent.com/golangci/misspell/master/install-misspell.sh | sh -s -- -b $(go env GOPATH)/bin ${MISSSPELL_VERSION}
|
||||
- name: Install misspell ${{ env.MISSPELL_VERSION }}
|
||||
run: curl -sfL https://raw.githubusercontent.com/golangci/misspell/master/install-misspell.sh | sh -s -- -b $(go env GOPATH)/bin ${MISSPELL_VERSION}
|
||||
|
||||
- name: Avoid generating webui
|
||||
run: touch webui/static/index.html
|
||||
|
20
CHANGELOG.md
20
CHANGELOG.md
@ -1,3 +1,23 @@
|
||||
## [v2.11.9](https://github.com/traefik/traefik/tree/v2.11.9) (2024-09-16)
|
||||
[All Commits](https://github.com/traefik/traefik/compare/v2.11.8...v2.11.9)
|
||||
|
||||
**Bug fixes:**
|
||||
- **[acme]** Update go-acme/lego to v4.18.0 ([#11060](https://github.com/traefik/traefik/pull/11060) by [ldez](https://github.com/ldez))
|
||||
- **[acme]** Allow handling ACME challenges with custom routers ([#10981](https://github.com/traefik/traefik/pull/10981) by [rtribotte](https://github.com/rtribotte))
|
||||
- **[logs,middleware]** Make the keys of the accessLog.fields.names map case-insensitive ([#11040](https://github.com/traefik/traefik/pull/11040) by [SpecLad](https://github.com/SpecLad))
|
||||
- **[logs,middleware]** Ensure proper logs for aborted streaming responses ([#10819](https://github.com/traefik/traefik/pull/10819) by [hood](https://github.com/hood))
|
||||
- **[middleware,server]** Cleanup Connection headers before passing the middleware chain ([#11077](https://github.com/traefik/traefik/pull/11077) by [kevinpollet](https://github.com/kevinpollet))
|
||||
- **[plugins]** Upgrade paerser to v0.2.1 ([#11048](https://github.com/traefik/traefik/pull/11048) by [mmatur](https://github.com/mmatur))
|
||||
- **[server,tcp]** Prevent error logging when TCP WRR pool is empty ([#10989](https://github.com/traefik/traefik/pull/10989) by [kevinpollet](https://github.com/kevinpollet))
|
||||
- **[webui]** Upgrade webui dependencies ([#11031](https://github.com/traefik/traefik/pull/11031) by [mloiseleur](https://github.com/mloiseleur))
|
||||
|
||||
**Documentation:**
|
||||
- **[acme]** Fix typo in multiple DNS challenge provider warning ([#11001](https://github.com/traefik/traefik/pull/11001) by [tired-engineer](https://github.com/tired-engineer))
|
||||
- **[k8s]** Update k8s quickstart permissions ([#11049](https://github.com/traefik/traefik/pull/11049) by [mmatur](https://github.com/mmatur))
|
||||
- **[metrics]** Remove documentation for unimplemented service retries metric ([#10983](https://github.com/traefik/traefik/pull/10983) by [rtribotte](https://github.com/rtribotte))
|
||||
- **[middleware]** Unify tab titles ([#11072](https://github.com/traefik/traefik/pull/11072) by [jsoref](https://github.com/jsoref))
|
||||
- Give valid examples for exposing dashboard with default Helm values ([#11015](https://github.com/traefik/traefik/pull/11015) by [holysoles](https://github.com/holysoles))
|
||||
|
||||
## [v3.1.2](https://github.com/traefik/traefik/tree/v3.1.2) (2024-08-06)
|
||||
[All Commits](https://github.com/traefik/traefik/compare/v3.1.1...v3.1.2)
|
||||
|
||||
|
@ -47,7 +47,7 @@ Further details of specific enforcement policies may be posted separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
|
||||
|
||||
When an inapropriate behavior is reported, maintainers will discuss on the Maintainer's Discord before marking the message as "abuse".
|
||||
When an inappropriate behavior is reported, maintainers will discuss on the Maintainer's Discord before marking the message as "abuse".
|
||||
This conversation beforehand avoids one-sided decisions.
|
||||
|
||||
The first message will be edited and marked as abuse.
|
||||
|
@ -364,7 +364,7 @@ func setupServer(staticConfiguration *static.Configuration) (*server.Server, err
|
||||
|
||||
if _, ok := resolverNames[rt.TLS.CertResolver]; !ok {
|
||||
log.Error().Err(err).Str(logs.RouterName, rtName).Str("certificateResolver", rt.TLS.CertResolver).
|
||||
Msg("Router uses a non-existent certificate resolver")
|
||||
Msg("Router uses a nonexistent certificate resolver")
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -11,7 +11,7 @@ Automatic HTTPS
|
||||
You can configure Traefik to use an ACME provider (like Let's Encrypt) for automatic certificate generation.
|
||||
|
||||
!!! warning "Let's Encrypt and Rate Limiting"
|
||||
Note that Let's Encrypt API has [rate limiting](https://letsencrypt.org/docs/rate-limits). These last up to **one week**, and can not be overridden.
|
||||
Note that Let's Encrypt API has [rate limiting](https://letsencrypt.org/docs/rate-limits). These last up to **one week**, and cannot be overridden.
|
||||
|
||||
When running Traefik in a container this file should be persisted across restarts.
|
||||
If Traefik requests new certificates each time it starts up, a crash-looping container can quickly reach Let's Encrypt's ratelimits.
|
||||
@ -298,7 +298,7 @@ Use the `DNS-01` challenge to generate and renew ACME certificates by provisioni
|
||||
|
||||
Multiple DNS challenge provider are not supported with Traefik, but you can use `CNAME` to handle that.
|
||||
For example, if you have `example.org` (account foo) and `example.com` (account bar) you can create a CNAME on `example.org` called `_acme-challenge.example.org` pointing to `challenge.example.com`.
|
||||
This way, you can obtain certificates for `example.com` with the `foo` account.
|
||||
This way, you can obtain certificates for `example.org` with the `bar` account.
|
||||
|
||||
!!! important
|
||||
A `provider` is mandatory.
|
||||
|
@ -278,7 +278,7 @@ spec:
|
||||
requestHost: true
|
||||
```
|
||||
|
||||
```yaml tab="Cosul Catalog"
|
||||
```yaml tab="Consul Catalog"
|
||||
- "traefik.http.middlewares.test-inflightreq.inflightreq.sourcecriterion.requesthost=true"
|
||||
```
|
||||
|
||||
|
@ -24,7 +24,7 @@ whoami:
|
||||
- "traefik.http.routers.router1.middlewares=foo-add-prefix@docker"
|
||||
```
|
||||
|
||||
```yaml tab="Kubernetes IngressRoute"
|
||||
```yaml tab="IngressRoute"
|
||||
# As a Kubernetes Traefik IngressRoute
|
||||
---
|
||||
apiVersion: traefik.io/v1alpha1
|
||||
|
@ -35,7 +35,7 @@ whoami:
|
||||
- "traefik.http.routers.router1.middlewares=foo-add-prefix@docker"
|
||||
```
|
||||
|
||||
```yaml tab="Kubernetes IngressRoute"
|
||||
```yaml tab="IngressRoute"
|
||||
---
|
||||
apiVersion: traefik.io/v1alpha1
|
||||
kind: Middleware
|
||||
|
@ -24,7 +24,7 @@ whoami:
|
||||
- "traefik.tcp.routers.router1.middlewares=foo-ip-allowlist@docker"
|
||||
```
|
||||
|
||||
```yaml tab="Kubernetes IngressRoute"
|
||||
```yaml tab="IngressRoute"
|
||||
# As a Kubernetes Traefik IngressRoute
|
||||
---
|
||||
apiVersion: traefik.io/v1alpha1
|
||||
|
@ -44,7 +44,7 @@ Then any router can refer to an instance of the wanted middleware.
|
||||
- "traefik.frontend.auth.basic.users=test:$$apr1$$H6uskkkW$$IgXLP6ewTrSuBkTrqE8wj/,test2:$$apr1$$d9hr9HBB$$4HxwgUir3HP4EsggP/QNo0"
|
||||
```
|
||||
|
||||
```yaml tab="K8s Ingress"
|
||||
```yaml tab="Ingress"
|
||||
apiVersion: networking.k8s.io/v1beta1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
@ -107,7 +107,7 @@ Then any router can refer to an instance of the wanted middleware.
|
||||
- "traefik.http.middlewares.auth.basicauth.users=test:$$apr1$$H6uskkkW$$IgXLP6ewTrSuBkTrqE8wj/,test2:$$apr1$$d9hr9HBB$$4HxwgUir3HP4EsggP/QNo0"
|
||||
```
|
||||
|
||||
```yaml tab="K8s IngressRoute"
|
||||
```yaml tab="IngressRoute"
|
||||
# The definitions below require the definitions for the Middleware and IngressRoute kinds.
|
||||
# https://doc.traefik.io/traefik/reference/dynamic-configuration/kubernetes-crd/#definitions
|
||||
apiVersion: traefik.io/v1alpha1
|
||||
@ -278,7 +278,7 @@ Then, a [router's TLS field](../routing/routers/index.md#tls) can refer to one o
|
||||
]
|
||||
```
|
||||
|
||||
```yaml tab="K8s IngressRoute"
|
||||
```yaml tab="IngressRoute"
|
||||
# The definitions below require the definitions for the TLSOption and IngressRoute kinds.
|
||||
# https://doc.traefik.io/traefik/reference/dynamic-configuration/kubernetes-crd/#definitions
|
||||
apiVersion: traefik.io/v1alpha1
|
||||
@ -442,7 +442,7 @@ To apply a redirection:
|
||||
traefik.http.middlewares.https_redirect.redirectscheme.permanent: true
|
||||
```
|
||||
|
||||
```yaml tab="K8s IngressRoute"
|
||||
```yaml tab="IngressRoute"
|
||||
apiVersion: traefik.io/v1alpha1
|
||||
kind: IngressRoute
|
||||
metadata:
|
||||
@ -561,7 +561,7 @@ with the path `/admin` stripped, e.g. to `http://<IP>:<port>/`. In this case, yo
|
||||
- "traefik.frontend.rule=Host:example.org;PathPrefixStrip:/admin"
|
||||
```
|
||||
|
||||
```yaml tab="Kubernetes Ingress"
|
||||
```yaml tab="Ingress"
|
||||
apiVersion: networking.k8s.io/v1beta1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
@ -595,7 +595,7 @@ with the path `/admin` stripped, e.g. to `http://<IP>:<port>/`. In this case, yo
|
||||
- "traefik.http.middlewares.admin-stripprefix.stripprefix.prefixes=/admin"
|
||||
```
|
||||
|
||||
```yaml tab="Kubernetes IngressRoute"
|
||||
```yaml tab="IngressRoute"
|
||||
---
|
||||
apiVersion: traefik.io/v1alpha1
|
||||
kind: IngressRoute
|
||||
|
@ -432,7 +432,7 @@ For more advanced use cases, you can use either the [RedirectScheme middleware](
|
||||
|
||||
Following up on the deprecation started [previously](#x509-commonname-deprecation),
|
||||
as the `x509ignoreCN=0` value for the `GODEBUG` is [deprecated in Go 1.17](https://tip.golang.org/doc/go1.17#crypto/x509),
|
||||
the legacy behavior related to the CommonName field can not be enabled at all anymore.
|
||||
the legacy behavior related to the CommonName field cannot be enabled at all anymore.
|
||||
|
||||
## v2.5.3 to v2.5.4
|
||||
|
||||
|
@ -33,7 +33,7 @@ traefik [--flag[=true|false| ]] [-f [true|false| ]]
|
||||
|
||||
All flags are documented in the [(static configuration) CLI reference](../reference/static-configuration/cli.md).
|
||||
|
||||
!!! info "Flags are case insensitive."
|
||||
!!! info "Flags are case-insensitive."
|
||||
|
||||
### `healthcheck`
|
||||
|
||||
|
@ -81,7 +81,7 @@ For the list of the providers names, see the [supported providers](#supported-pr
|
||||
- "traefik.http.routers.my-container.middlewares=add-foo-prefix@file"
|
||||
```
|
||||
|
||||
```yaml tab="Kubernetes Ingress Route"
|
||||
```yaml tab="IngressRoute"
|
||||
apiVersion: traefik.io/v1alpha1
|
||||
kind: IngressRoute
|
||||
metadata:
|
||||
@ -103,7 +103,7 @@ For the list of the providers names, see the [supported providers](#supported-pr
|
||||
# when the cross-provider syntax is used.
|
||||
```
|
||||
|
||||
```yaml tab="Kubernetes Ingress"
|
||||
```yaml tab="Ingress"
|
||||
apiVersion: traefik.io/v1alpha1
|
||||
kind: Middleware
|
||||
metadata:
|
||||
|
@ -8,7 +8,7 @@ description: "View the reference for performing dynamic configurations with Trae
|
||||
Dynamic configuration with Consul Catalog
|
||||
{: .subtitle }
|
||||
|
||||
The labels are case insensitive.
|
||||
The labels are case-insensitive.
|
||||
|
||||
```yaml
|
||||
--8<-- "content/reference/dynamic-configuration/consul-catalog.yml"
|
||||
|
@ -8,7 +8,7 @@ description: "Learn how to do dynamic configuration in Traefik Proxy with AWS EC
|
||||
Dynamic configuration with ECS provider
|
||||
{: .subtitle }
|
||||
|
||||
The labels are case insensitive.
|
||||
The labels are case-insensitive.
|
||||
|
||||
```yaml
|
||||
--8<-- "content/reference/dynamic-configuration/ecs.yml"
|
||||
|
@ -8,7 +8,7 @@ description: "View the reference for performing dynamic configurations with Trae
|
||||
Dynamic configuration with Nomad Service Discovery
|
||||
{: .subtitle }
|
||||
|
||||
The labels are case insensitive.
|
||||
The labels are case-insensitive.
|
||||
|
||||
```yaml
|
||||
--8<-- "content/reference/dynamic-configuration/nomad.yml"
|
||||
|
@ -117,9 +117,15 @@ Entry points definition. (Default: ```false```)
|
||||
`--entrypoints.<name>.address`:
|
||||
Entry point address.
|
||||
|
||||
`--entrypoints.<name>.allowacmebypass`:
|
||||
Enables handling of ACME TLS and HTTP challenges with custom routers. (Default: ```false```)
|
||||
|
||||
`--entrypoints.<name>.asdefault`:
|
||||
Adds this EntryPoint to the list of default EntryPoints to be used on routers that don't have any Entrypoint defined. (Default: ```false```)
|
||||
|
||||
`--entrypoints.<name>.forwardedheaders.connection`:
|
||||
List of Connection headers that are allowed to pass through the middleware chain before being removed.
|
||||
|
||||
`--entrypoints.<name>.forwardedheaders.insecure`:
|
||||
Trust all forwarded headers. (Default: ```false```)
|
||||
|
||||
|
@ -117,9 +117,15 @@ Entry points definition. (Default: ```false```)
|
||||
`TRAEFIK_ENTRYPOINTS_<NAME>_ADDRESS`:
|
||||
Entry point address.
|
||||
|
||||
`TRAEFIK_ENTRYPOINTS_<NAME>_ALLOWACMEBYPASS`:
|
||||
Enables handling of ACME TLS and HTTP challenges with custom routers. (Default: ```false```)
|
||||
|
||||
`TRAEFIK_ENTRYPOINTS_<NAME>_ASDEFAULT`:
|
||||
Adds this EntryPoint to the list of default EntryPoints to be used on routers that don't have any Entrypoint defined. (Default: ```false```)
|
||||
|
||||
`TRAEFIK_ENTRYPOINTS_<NAME>_FORWARDEDHEADERS_CONNECTION`:
|
||||
List of Connection headers that are allowed to pass through the middleware chain before being removed.
|
||||
|
||||
`TRAEFIK_ENTRYPOINTS_<NAME>_FORWARDEDHEADERS_INSECURE`:
|
||||
Trust all forwarded headers. (Default: ```false```)
|
||||
|
||||
|
@ -30,6 +30,7 @@
|
||||
[entryPoints]
|
||||
[entryPoints.EntryPoint0]
|
||||
address = "foobar"
|
||||
allowACMEByPass = true
|
||||
reusePort = true
|
||||
asDefault = true
|
||||
[entryPoints.EntryPoint0.transport]
|
||||
@ -48,6 +49,7 @@
|
||||
[entryPoints.EntryPoint0.forwardedHeaders]
|
||||
insecure = true
|
||||
trustedIPs = ["foobar", "foobar"]
|
||||
connection = ["foobar", "foobar"]
|
||||
[entryPoints.EntryPoint0.http]
|
||||
middlewares = ["foobar", "foobar"]
|
||||
encodeQuerySemicolons = true
|
||||
|
@ -35,6 +35,7 @@ tcpServersTransport:
|
||||
entryPoints:
|
||||
EntryPoint0:
|
||||
address: foobar
|
||||
allowACMEByPass: true
|
||||
reusePort: true
|
||||
asDefault: true
|
||||
transport:
|
||||
@ -57,6 +58,9 @@ entryPoints:
|
||||
trustedIPs:
|
||||
- foobar
|
||||
- foobar
|
||||
connection:
|
||||
- foobar
|
||||
- foobar
|
||||
http:
|
||||
redirections:
|
||||
entryPoint:
|
||||
|
@ -233,6 +233,35 @@ If both TCP and UDP are wanted for the same port, two entryPoints definitions ar
|
||||
|
||||
Full details for how to specify `address` can be found in [net.Listen](https://golang.org/pkg/net/#Listen) (and [net.Dial](https://golang.org/pkg/net/#Dial)) of the doc for go.
|
||||
|
||||
### AllowACMEByPass
|
||||
|
||||
_Optional, Default=false_
|
||||
|
||||
`allowACMEByPass` determines whether a user defined router can handle ACME TLS or HTTP challenges instead of the Traefik dedicated one.
|
||||
This option can be used when a Traefik instance has one or more certificate resolvers configured,
|
||||
but is also used to route challenges connections/requests to services that could also initiate their own ACME challenges.
|
||||
|
||||
??? info "No Certificate Resolvers configured"
|
||||
|
||||
It is not necessary to use the `allowACMEByPass' option certificate option if no certificate resolver is defined.
|
||||
In fact, Traefik will automatically allow ACME TLS or HTTP requests to be handled by custom routers in this case, since there can be no concurrency with its own challenge handlers.
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
entryPoints:
|
||||
foo:
|
||||
allowACMEByPass: true
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[entryPoints.foo]
|
||||
[entryPoints.foo.allowACMEByPass]
|
||||
allowACMEByPass = true
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
--entryPoints.name.allowACMEByPass=true
|
||||
```
|
||||
|
||||
### ReusePort
|
||||
|
||||
_Optional, Default=false_
|
||||
@ -500,6 +529,40 @@ You can configure Traefik to trust the forwarded headers information (`X-Forward
|
||||
--entryPoints.web.forwardedHeaders.insecure
|
||||
```
|
||||
|
||||
??? info "`forwardedHeaders.connection`"
|
||||
|
||||
As per RFC7230, Traefik respects the Connection options from the client request.
|
||||
By doing so, it removes any header field(s) listed in the request Connection header and the Connection header field itself when empty.
|
||||
The removal happens as soon as the request is handled by Traefik,
|
||||
thus the removed headers are not available when the request passes through the middleware chain.
|
||||
The `connection` option lists the Connection headers allowed to passthrough the middleware chain before their removal.
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
## Static configuration
|
||||
entryPoints:
|
||||
web:
|
||||
address: ":80"
|
||||
forwardedHeaders:
|
||||
connection:
|
||||
- foobar
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
## Static configuration
|
||||
[entryPoints]
|
||||
[entryPoints.web]
|
||||
address = ":80"
|
||||
|
||||
[entryPoints.web.forwardedHeaders]
|
||||
connection = ["foobar"]
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
## Static configuration
|
||||
--entryPoints.web.address=:80
|
||||
--entryPoints.web.forwardedHeaders.connection=foobar
|
||||
```
|
||||
|
||||
### Transport
|
||||
|
||||
#### `respondingTimeouts`
|
||||
|
@ -24,7 +24,7 @@ With Consul Catalog, Traefik can leverage tags attached to a service to generate
|
||||
|
||||
!!! info "tags"
|
||||
|
||||
- tags are case insensitive.
|
||||
- tags are case-insensitive.
|
||||
- The complete list of tags can be found [the reference page](../../reference/dynamic-configuration/consul-catalog.md)
|
||||
|
||||
### General
|
||||
|
@ -95,7 +95,7 @@ With Docker, Traefik can leverage labels attached to a container to generate rou
|
||||
|
||||
!!! info "Labels"
|
||||
|
||||
- Labels are case insensitive.
|
||||
- Labels are case-insensitive.
|
||||
- The complete list of labels can be found in [the reference page](../../reference/dynamic-configuration/docker.md).
|
||||
|
||||
### General
|
||||
|
@ -22,7 +22,7 @@ With ECS, Traefik can leverage labels attached to a container to generate routin
|
||||
|
||||
!!! info "labels"
|
||||
|
||||
- labels are case insensitive.
|
||||
- labels are case-insensitive.
|
||||
- The complete list of labels can be found in [the reference page](../../reference/dynamic-configuration/ecs.md).
|
||||
|
||||
### General
|
||||
|
@ -12,7 +12,7 @@ A Story of key & values
|
||||
|
||||
!!! info "Keys"
|
||||
|
||||
- Keys are case insensitive.
|
||||
- Keys are case-insensitive.
|
||||
- The complete list of keys can be found in [the reference page](../../reference/dynamic-configuration/kv.md).
|
||||
|
||||
### Routers
|
||||
|
0
docs/content/routing/providers/marathon.md
Normal file
0
docs/content/routing/providers/marathon.md
Normal file
@ -24,7 +24,7 @@ With Nomad, Traefik can leverage tags attached to a service to generate routing
|
||||
|
||||
!!! info "tags"
|
||||
|
||||
- tags are case insensitive.
|
||||
- tags are case-insensitive.
|
||||
- The complete list of tags can be found [the reference page](../../reference/dynamic-configuration/nomad.md)
|
||||
|
||||
### General
|
||||
|
@ -118,7 +118,7 @@ With Docker Swarm, Traefik can leverage labels attached to a service to generate
|
||||
|
||||
!!! info "Labels"
|
||||
|
||||
- Labels are case insensitive.
|
||||
- Labels are case-insensitive.
|
||||
- The complete list of labels can be found in [the reference page](../../reference/dynamic-configuration/docker.md).
|
||||
|
||||
### General
|
||||
|
@ -1197,7 +1197,7 @@ A value of `0` for the priority is ignored: `priority = 0` means that the defaul
|
||||
| Router-2 | ```ClientIP(`192.168.0.0/24`)``` | 26 |
|
||||
|
||||
Which means that requests from `192.168.0.12` would go to Router-2 even though Router-1 is intended to specifically handle them.
|
||||
To achieve this intention, a priority (higher than 26) should be set on Router-1.
|
||||
To achieve this intention, a priority (greater than 26) should be set on Router-1.
|
||||
|
||||
??? example "Setting priorities -- using the [File Provider](../../providers/file.md)"
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
---
|
||||
title: "Traefik Docker DNS Challenge Documentation"
|
||||
description: "Learn how to create a certificate with the Let's Encrypt DNS challenge to use HTTPS on a Service exposed with Traefik Proxy. Read the tehnical documentation."
|
||||
description: "Learn how to create a certificate with the Let's Encrypt DNS challenge to use HTTPS on a Service exposed with Traefik Proxy. Read the technical documentation."
|
||||
---
|
||||
|
||||
# Docker-compose with Let's Encrypt: DNS Challenge
|
||||
|
@ -0,0 +1,37 @@
|
||||
[global]
|
||||
checkNewVersion = false
|
||||
sendAnonymousUsage = false
|
||||
|
||||
[log]
|
||||
level = "DEBUG"
|
||||
|
||||
# Limiting the Logs to Specific Fields
|
||||
[accessLog]
|
||||
format = "json"
|
||||
filePath = "access.log"
|
||||
|
||||
[accessLog.fields.headers.names]
|
||||
"Foo" = "keep"
|
||||
"Bar" = "keep"
|
||||
|
||||
[entryPoints]
|
||||
[entryPoints.web]
|
||||
address = ":8000"
|
||||
[entryPoints.web.forwardedHeaders]
|
||||
insecure = true
|
||||
connection = ["Foo"]
|
||||
|
||||
[providers.file]
|
||||
filename = "{{ .SelfFilename }}"
|
||||
|
||||
## dynamic configuration ##
|
||||
|
||||
[http.routers]
|
||||
[http.routers.router1]
|
||||
rule = "Host(`test.localhost`)"
|
||||
service = "service1"
|
||||
|
||||
[http.services]
|
||||
[http.services.service1.loadBalancer]
|
||||
[[http.services.service1.loadBalancer.servers]]
|
||||
url = "http://127.0.0.1:9000"
|
@ -48,7 +48,7 @@ openssl genrsa -out client3.key 2048
|
||||
# Locality Name (eg, city) []:.
|
||||
# Organization Name (eg, company) [Internet Widgits Pty Ltd]:.
|
||||
# Organizational Unit Name (eg, section) []:.
|
||||
# Common Name (e.g. server FQDN or YOUR name) []:clien1.example.com
|
||||
# Common Name (e.g. server FQDN or YOUR name) []:client1.example.com
|
||||
# Email Address []:.
|
||||
#
|
||||
# Please enter the following 'extra' attributes
|
||||
@ -58,7 +58,7 @@ openssl genrsa -out client3.key 2048
|
||||
# Issuer
|
||||
# CN = ca1.example.com
|
||||
# Subject
|
||||
# CN = clien1.example.com
|
||||
# CN = client1.example.com
|
||||
openssl req -key client1.key -new -out client1.csr
|
||||
|
||||
# Country Name (2 letter code) [AU]:.
|
||||
|
@ -4,6 +4,7 @@ import (
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@ -20,6 +21,11 @@ func TestHeadersSuite(t *testing.T) {
|
||||
suite.Run(t, new(HeadersSuite))
|
||||
}
|
||||
|
||||
func (s *HeadersSuite) TearDownTest() {
|
||||
s.displayTraefikLogFile(traefikTestLogFile)
|
||||
_ = os.Remove(traefikTestAccessLogFile)
|
||||
}
|
||||
|
||||
func (s *HeadersSuite) TestSimpleConfiguration() {
|
||||
s.traefikCmd(withConfigFile("fixtures/headers/basic.toml"))
|
||||
|
||||
@ -62,6 +68,53 @@ func (s *HeadersSuite) TestReverseProxyHeaderRemoved() {
|
||||
require.NoError(s.T(), err)
|
||||
}
|
||||
|
||||
func (s *HeadersSuite) TestConnectionHopByHop() {
|
||||
file := s.adaptFile("fixtures/headers/connection_hop_by_hop_headers.toml", struct{}{})
|
||||
s.traefikCmd(withConfigFile(file))
|
||||
|
||||
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
_, found := r.Header["X-Forwarded-For"]
|
||||
assert.True(s.T(), found)
|
||||
xHost, found := r.Header["X-Forwarded-Host"]
|
||||
assert.True(s.T(), found)
|
||||
assert.Equal(s.T(), "localhost", xHost[0])
|
||||
|
||||
_, found = r.Header["Foo"]
|
||||
assert.False(s.T(), found)
|
||||
_, found = r.Header["Bar"]
|
||||
assert.False(s.T(), found)
|
||||
})
|
||||
|
||||
listener, err := net.Listen("tcp", "127.0.0.1:9000")
|
||||
require.NoError(s.T(), err)
|
||||
|
||||
ts := &httptest.Server{
|
||||
Listener: listener,
|
||||
Config: &http.Server{Handler: handler},
|
||||
}
|
||||
ts.Start()
|
||||
defer ts.Close()
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1:8000/", nil)
|
||||
require.NoError(s.T(), err)
|
||||
req.Host = "test.localhost"
|
||||
req.Header = http.Header{
|
||||
"Connection": {"Foo,Bar,X-Forwarded-For,X-Forwarded-Host"},
|
||||
"Foo": {"bar"},
|
||||
"Bar": {"foo"},
|
||||
"X-Forwarded-Host": {"localhost"},
|
||||
}
|
||||
|
||||
err = try.Request(req, time.Second, try.StatusCodeIs(http.StatusOK))
|
||||
require.NoError(s.T(), err)
|
||||
|
||||
accessLog, err := os.ReadFile(traefikTestAccessLogFile)
|
||||
require.NoError(s.T(), err)
|
||||
|
||||
assert.Contains(s.T(), string(accessLog), "\"request_Foo\":\"bar\"")
|
||||
assert.NotContains(s.T(), string(accessLog), "\"request_Bar\":\"\"")
|
||||
}
|
||||
|
||||
func (s *HeadersSuite) TestCorsResponses() {
|
||||
file := s.adaptFile("fixtures/headers/cors.toml", struct{}{})
|
||||
s.traefikCmd(withConfigFile(file))
|
||||
|
@ -24,7 +24,7 @@ const traefikTestAccessLogFileRotated = traefikTestAccessLogFile + ".rotated"
|
||||
// Log rotation integration test suite.
|
||||
type LogRotationSuite struct{ BaseSuite }
|
||||
|
||||
func TestLogRorationSuite(t *testing.T) {
|
||||
func TestLogRotationSuite(t *testing.T) {
|
||||
suite.Run(t, new(LogRotationSuite))
|
||||
}
|
||||
|
||||
|
@ -1382,7 +1382,7 @@ func (s *SimpleSuite) TestDebugLog() {
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, "http://localhost:8000/whoami", http.NoBody)
|
||||
require.NoError(s.T(), err)
|
||||
req.Header.Set("Autorization", "Bearer ThisIsABearerToken")
|
||||
req.Header.Set("Authorization", "Bearer ThisIsABearerToken")
|
||||
|
||||
response, err := http.DefaultClient.Do(req)
|
||||
require.NoError(s.T(), err)
|
||||
|
@ -19,7 +19,7 @@ const (
|
||||
type timedAction func(timeout time.Duration, operation DoCondition) error
|
||||
|
||||
// Sleep pauses the current goroutine for at least the duration d.
|
||||
// Deprecated: Use only when use an other Try[...] functions is not possible.
|
||||
// Deprecated: Use only when use another Try[...] functions is not possible.
|
||||
func Sleep(d time.Duration) {
|
||||
d = applyCIMultiplier(d)
|
||||
time.Sleep(d)
|
||||
|
@ -70,8 +70,8 @@ func TestDecodeToNode(t *testing.T) {
|
||||
{
|
||||
desc: "several entries, level 0",
|
||||
in: map[string]string{
|
||||
"traefik": "bar",
|
||||
"traefic": "bur",
|
||||
"traefik": "bar",
|
||||
"traefik_": "bur",
|
||||
},
|
||||
expected: expected{error: true},
|
||||
},
|
||||
@ -120,7 +120,7 @@ func TestDecodeToNode(t *testing.T) {
|
||||
}},
|
||||
},
|
||||
{
|
||||
desc: "several entries, level 2, case insensitive",
|
||||
desc: "several entries, level 2, case-insensitive",
|
||||
in: map[string]string{
|
||||
"traefik/foo/aaa": "bar",
|
||||
"traefik/Foo/bbb": "bur",
|
||||
|
@ -12,6 +12,7 @@ import (
|
||||
// EntryPoint holds the entry point configuration.
|
||||
type EntryPoint struct {
|
||||
Address string `description:"Entry point address." json:"address,omitempty" toml:"address,omitempty" yaml:"address,omitempty"`
|
||||
AllowACMEByPass bool `description:"Enables handling of ACME TLS and HTTP challenges with custom routers." json:"allowACMEByPass,omitempty" toml:"allowACMEByPass,omitempty" yaml:"allowACMEByPass,omitempty"`
|
||||
ReusePort bool `description:"Enables EntryPoints from the same or different processes listening on the same TCP/UDP port." json:"reusePort,omitempty" toml:"reusePort,omitempty" yaml:"reusePort,omitempty"`
|
||||
AsDefault bool `description:"Adds this EntryPoint to the list of default EntryPoints to be used on routers that don't have any Entrypoint defined." json:"asDefault,omitempty" toml:"asDefault,omitempty" yaml:"asDefault,omitempty"`
|
||||
Transport *EntryPointsTransport `description:"Configures communication between clients and Traefik." json:"transport,omitempty" toml:"transport,omitempty" yaml:"transport,omitempty" export:"true"`
|
||||
@ -111,6 +112,7 @@ type TLSConfig struct {
|
||||
type ForwardedHeaders struct {
|
||||
Insecure bool `description:"Trust all forwarded headers." json:"insecure,omitempty" toml:"insecure,omitempty" yaml:"insecure,omitempty" export:"true"`
|
||||
TrustedIPs []string `description:"Trust only forwarded headers from selected IPs." json:"trustedIPs,omitempty" toml:"trustedIPs,omitempty" yaml:"trustedIPs,omitempty"`
|
||||
Connection []string `description:"List of Connection headers that are allowed to pass through the middleware chain before being removed." json:"connection,omitempty" toml:"connection,omitempty" yaml:"connection,omitempty"`
|
||||
}
|
||||
|
||||
// ProxyProtocol contains Proxy-Protocol configuration.
|
||||
|
@ -46,7 +46,7 @@ func TestDepthStrategy_GetIP(t *testing.T) {
|
||||
expected: "10.0.0.3",
|
||||
},
|
||||
{
|
||||
desc: "Use non existing depth in XForwardedFor",
|
||||
desc: "Use nonexistent depth in XForwardedFor",
|
||||
depth: 2,
|
||||
xForwardedFor: "",
|
||||
expected: "",
|
||||
|
@ -106,15 +106,28 @@ func NewHandler(config *types.AccessLog) (*Handler, error) {
|
||||
Level: logrus.InfoLevel,
|
||||
}
|
||||
|
||||
// Transform headers names in config to a canonical form, to be used as is without further transformations.
|
||||
if config.Fields != nil && config.Fields.Headers != nil && len(config.Fields.Headers.Names) > 0 {
|
||||
fields := map[string]string{}
|
||||
// Transform header names to a canonical form, to be used as is without further transformations,
|
||||
// and transform field names to lower case, to enable case-insensitive lookup.
|
||||
if config.Fields != nil {
|
||||
if len(config.Fields.Names) > 0 {
|
||||
fields := map[string]string{}
|
||||
|
||||
for h, v := range config.Fields.Headers.Names {
|
||||
fields[textproto.CanonicalMIMEHeaderKey(h)] = v
|
||||
for h, v := range config.Fields.Names {
|
||||
fields[strings.ToLower(h)] = v
|
||||
}
|
||||
|
||||
config.Fields.Names = fields
|
||||
}
|
||||
|
||||
config.Fields.Headers.Names = fields
|
||||
if config.Fields.Headers != nil && len(config.Fields.Headers.Names) > 0 {
|
||||
fields := map[string]string{}
|
||||
|
||||
for h, v := range config.Fields.Headers.Names {
|
||||
fields[textproto.CanonicalMIMEHeaderKey(h)] = v
|
||||
}
|
||||
|
||||
config.Fields.Headers.Names = fields
|
||||
}
|
||||
}
|
||||
|
||||
logHandler := &Handler{
|
||||
@ -184,16 +197,6 @@ func (h *Handler) ServeHTTP(rw http.ResponseWriter, req *http.Request, next http
|
||||
},
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if h.config.BufferingSize > 0 {
|
||||
h.logHandlerChan <- handlerParams{
|
||||
logDataTable: logDataTable,
|
||||
}
|
||||
return
|
||||
}
|
||||
h.logTheRoundTrip(logDataTable)
|
||||
}()
|
||||
|
||||
reqWithDataTable := req.WithContext(context.WithValue(req.Context(), DataTableKey, logDataTable))
|
||||
|
||||
core[RequestCount] = nextRequestCount()
|
||||
@ -238,19 +241,30 @@ func (h *Handler) ServeHTTP(rw http.ResponseWriter, req *http.Request, next http
|
||||
return
|
||||
}
|
||||
|
||||
defer func() {
|
||||
logDataTable.DownstreamResponse = downstreamResponse{
|
||||
headers: rw.Header().Clone(),
|
||||
}
|
||||
|
||||
logDataTable.DownstreamResponse.status = capt.StatusCode()
|
||||
logDataTable.DownstreamResponse.size = capt.ResponseSize()
|
||||
logDataTable.Request.size = capt.RequestSize()
|
||||
|
||||
if _, ok := core[ClientUsername]; !ok {
|
||||
core[ClientUsername] = usernameIfPresent(reqWithDataTable.URL)
|
||||
}
|
||||
|
||||
if h.config.BufferingSize > 0 {
|
||||
h.logHandlerChan <- handlerParams{
|
||||
logDataTable: logDataTable,
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
h.logTheRoundTrip(logDataTable)
|
||||
}()
|
||||
|
||||
next.ServeHTTP(rw, reqWithDataTable)
|
||||
|
||||
if _, ok := core[ClientUsername]; !ok {
|
||||
core[ClientUsername] = usernameIfPresent(reqWithDataTable.URL)
|
||||
}
|
||||
|
||||
logDataTable.DownstreamResponse = downstreamResponse{
|
||||
headers: rw.Header().Clone(),
|
||||
}
|
||||
|
||||
logDataTable.DownstreamResponse.status = capt.StatusCode()
|
||||
logDataTable.DownstreamResponse.size = capt.ResponseSize()
|
||||
logDataTable.Request.size = capt.RequestSize()
|
||||
}
|
||||
|
||||
// Close closes the Logger (i.e. the file, drain logHandlerChan, etc).
|
||||
@ -334,7 +348,7 @@ func (h *Handler) logTheRoundTrip(logDataTable *LogData) {
|
||||
fields := logrus.Fields{}
|
||||
|
||||
for k, v := range logDataTable.Core {
|
||||
if h.config.Fields.Keep(k) {
|
||||
if h.config.Fields.Keep(strings.ToLower(k)) {
|
||||
fields[k] = v
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ package accesslog
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
@ -24,6 +25,7 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
ptypes "github.com/traefik/paerser/types"
|
||||
"github.com/traefik/traefik/v3/pkg/middlewares/capture"
|
||||
"github.com/traefik/traefik/v3/pkg/middlewares/recovery"
|
||||
"github.com/traefik/traefik/v3/pkg/types"
|
||||
)
|
||||
|
||||
@ -164,7 +166,7 @@ func TestLoggerHeaderFields(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "with case insensitive match on header name",
|
||||
desc: "with case-insensitive match on header name",
|
||||
header: "User-Agent",
|
||||
expected: types.AccessLogKeep,
|
||||
accessLogFields: types.AccessLogFields{
|
||||
@ -465,6 +467,32 @@ func TestLoggerJSON(t *testing.T) {
|
||||
RequestRefererHeader: assertString(testReferer),
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "fields and headers with unconventional letter case",
|
||||
config: &types.AccessLog{
|
||||
FilePath: "",
|
||||
Format: JSONFormat,
|
||||
Fields: &types.AccessLogFields{
|
||||
DefaultMode: "drop",
|
||||
Names: map[string]string{
|
||||
"rEqUeStHoSt": "keep",
|
||||
},
|
||||
Headers: &types.FieldHeaders{
|
||||
DefaultMode: "drop",
|
||||
Names: map[string]string{
|
||||
"ReFeReR": "keep",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: map[string]func(t *testing.T, value interface{}){
|
||||
RequestHost: assertString(testHostname),
|
||||
"level": assertString("info"),
|
||||
"msg": assertString(""),
|
||||
"time": assertNotEmpty(),
|
||||
RequestRefererHeader: assertString(testReferer),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
@ -496,6 +524,64 @@ func TestLoggerJSON(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestLogger_AbortedRequest(t *testing.T) {
|
||||
expected := map[string]func(t *testing.T, value interface{}){
|
||||
RequestContentSize: assertFloat64(0),
|
||||
RequestHost: assertString(testHostname),
|
||||
RequestAddr: assertString(testHostname),
|
||||
RequestMethod: assertString(testMethod),
|
||||
RequestPath: assertString(""),
|
||||
RequestProtocol: assertString(testProto),
|
||||
RequestScheme: assertString(testScheme),
|
||||
RequestPort: assertString("-"),
|
||||
DownstreamStatus: assertFloat64(float64(200)),
|
||||
DownstreamContentSize: assertFloat64(float64(40)),
|
||||
RequestRefererHeader: assertString(testReferer),
|
||||
RequestUserAgentHeader: assertString(testUserAgent),
|
||||
ServiceURL: assertString("http://stream"),
|
||||
ServiceAddr: assertString("127.0.0.1"),
|
||||
ServiceName: assertString("stream"),
|
||||
ClientUsername: assertString(testUsername),
|
||||
ClientHost: assertString(testHostname),
|
||||
ClientPort: assertString(strconv.Itoa(testPort)),
|
||||
ClientAddr: assertString(fmt.Sprintf("%s:%d", testHostname, testPort)),
|
||||
"level": assertString("info"),
|
||||
"msg": assertString(""),
|
||||
RequestCount: assertFloat64NotZero(),
|
||||
Duration: assertFloat64NotZero(),
|
||||
Overhead: assertFloat64NotZero(),
|
||||
RetryAttempts: assertFloat64(float64(0)),
|
||||
"time": assertNotEmpty(),
|
||||
StartLocal: assertNotEmpty(),
|
||||
StartUTC: assertNotEmpty(),
|
||||
"downstream_Content-Type": assertString("text/plain"),
|
||||
"downstream_Transfer-Encoding": assertString("chunked"),
|
||||
"downstream_Cache-Control": assertString("no-cache"),
|
||||
}
|
||||
|
||||
config := &types.AccessLog{
|
||||
FilePath: filepath.Join(t.TempDir(), logFileNameSuffix),
|
||||
Format: JSONFormat,
|
||||
}
|
||||
doLoggingWithAbortedStream(t, config)
|
||||
|
||||
logData, err := os.ReadFile(config.FilePath)
|
||||
require.NoError(t, err)
|
||||
|
||||
jsonData := make(map[string]interface{})
|
||||
err = json.Unmarshal(logData, &jsonData)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, len(expected), len(jsonData))
|
||||
|
||||
for field, assertion := range expected {
|
||||
assertion(t, jsonData[field])
|
||||
if t.Failed() {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewLogHandlerOutputStdout(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
@ -832,3 +918,89 @@ func logWriterTestHandlerFunc(rw http.ResponseWriter, r *http.Request) {
|
||||
|
||||
rw.WriteHeader(testStatus)
|
||||
}
|
||||
|
||||
func doLoggingWithAbortedStream(t *testing.T, config *types.AccessLog) {
|
||||
t.Helper()
|
||||
|
||||
logger, err := NewHandler(config)
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
err := logger.Close()
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
if config.FilePath != "" {
|
||||
_, err = os.Stat(config.FilePath)
|
||||
require.NoError(t, err, "logger should create "+config.FilePath)
|
||||
}
|
||||
|
||||
reqContext, cancelRequest := context.WithCancel(context.Background())
|
||||
|
||||
req := &http.Request{
|
||||
Header: map[string][]string{
|
||||
"User-Agent": {testUserAgent},
|
||||
"Referer": {testReferer},
|
||||
},
|
||||
Proto: testProto,
|
||||
Host: testHostname,
|
||||
Method: testMethod,
|
||||
RemoteAddr: fmt.Sprintf("%s:%d", testHostname, testPort),
|
||||
URL: &url.URL{
|
||||
User: url.UserPassword(testUsername, ""),
|
||||
},
|
||||
Body: nil,
|
||||
}
|
||||
|
||||
req = req.WithContext(reqContext)
|
||||
|
||||
chain := alice.New()
|
||||
chain = chain.Append(func(next http.Handler) (http.Handler, error) {
|
||||
return recovery.New(context.Background(), next)
|
||||
})
|
||||
chain = chain.Append(capture.Wrap)
|
||||
chain = chain.Append(WrapHandler(logger))
|
||||
|
||||
service := NewFieldHandler(http.HandlerFunc(streamBackend), ServiceURL, "http://stream", nil)
|
||||
service = NewFieldHandler(service, ServiceAddr, "127.0.0.1", nil)
|
||||
service = NewFieldHandler(service, ServiceName, "stream", AddServiceFields)
|
||||
|
||||
handler, err := chain.Then(service)
|
||||
require.NoError(t, err)
|
||||
|
||||
go func() {
|
||||
time.Sleep(499 * time.Millisecond)
|
||||
cancelRequest()
|
||||
}()
|
||||
|
||||
handler.ServeHTTP(httptest.NewRecorder(), req)
|
||||
}
|
||||
|
||||
func streamBackend(rw http.ResponseWriter, r *http.Request) {
|
||||
// Get the Flusher to flush the response to the client
|
||||
flusher, ok := rw.(http.Flusher)
|
||||
if !ok {
|
||||
http.Error(rw, "Streaming unsupported!", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// Set the headers for streaming
|
||||
rw.Header().Set("Content-Type", "text/plain")
|
||||
rw.Header().Set("Transfer-Encoding", "chunked")
|
||||
rw.Header().Set("Cache-Control", "no-cache")
|
||||
|
||||
for {
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
select {
|
||||
case <-r.Context().Done():
|
||||
panic(http.ErrAbortHandler)
|
||||
|
||||
default:
|
||||
if _, err := fmt.Fprint(rw, "FOOBAR!!!!"); err != nil {
|
||||
http.Error(rw, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
flusher.Flush()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
package connectionheader
|
||||
package auth
|
||||
|
||||
import (
|
||||
"net/http"
|
@ -1,4 +1,4 @@
|
||||
package connectionheader
|
||||
package auth
|
||||
|
||||
import (
|
||||
"net/http"
|
@ -13,7 +13,6 @@ import (
|
||||
|
||||
"github.com/traefik/traefik/v3/pkg/config/dynamic"
|
||||
"github.com/traefik/traefik/v3/pkg/middlewares"
|
||||
"github.com/traefik/traefik/v3/pkg/middlewares/connectionheader"
|
||||
"github.com/traefik/traefik/v3/pkg/middlewares/observability"
|
||||
"github.com/traefik/traefik/v3/pkg/tracing"
|
||||
"github.com/traefik/traefik/v3/pkg/types"
|
||||
@ -121,7 +120,7 @@ func (fa *forwardAuth) GetTracingInformation() (string, string, trace.SpanKind)
|
||||
func (fa *forwardAuth) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
logger := middlewares.GetLogger(req.Context(), fa.name, typeNameForward)
|
||||
|
||||
req = connectionheader.Remove(req)
|
||||
req = Remove(req)
|
||||
|
||||
forwardReq, err := http.NewRequestWithContext(req.Context(), http.MethodGet, fa.address, nil)
|
||||
if err != nil {
|
||||
|
@ -235,7 +235,7 @@ func (cc *codeCatcher) Flush() {
|
||||
// since we want to serve the ones from the error page,
|
||||
// so we just don't flush.
|
||||
// (e.g., To prevent superfluous WriteHeader on request with a
|
||||
// `Transfert-Encoding: chunked` header).
|
||||
// `Transfer-Encoding: chunked` header).
|
||||
if cc.caughtFilteredCode {
|
||||
return
|
||||
}
|
||||
|
@ -3,10 +3,13 @@ package forwardedheaders
|
||||
import (
|
||||
"net"
|
||||
"net/http"
|
||||
"net/textproto"
|
||||
"os"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/traefik/traefik/v3/pkg/ip"
|
||||
"golang.org/x/net/http/httpguts"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -42,19 +45,20 @@ var xHeaders = []string{
|
||||
// Unless insecure is set,
|
||||
// it first removes all the existing values for those headers if the remote address is not one of the trusted ones.
|
||||
type XForwarded struct {
|
||||
insecure bool
|
||||
trustedIps []string
|
||||
ipChecker *ip.Checker
|
||||
next http.Handler
|
||||
hostname string
|
||||
insecure bool
|
||||
trustedIPs []string
|
||||
connectionHeaders []string
|
||||
ipChecker *ip.Checker
|
||||
next http.Handler
|
||||
hostname string
|
||||
}
|
||||
|
||||
// NewXForwarded creates a new XForwarded.
|
||||
func NewXForwarded(insecure bool, trustedIps []string, next http.Handler) (*XForwarded, error) {
|
||||
func NewXForwarded(insecure bool, trustedIPs []string, connectionHeaders []string, next http.Handler) (*XForwarded, error) {
|
||||
var ipChecker *ip.Checker
|
||||
if len(trustedIps) > 0 {
|
||||
if len(trustedIPs) > 0 {
|
||||
var err error
|
||||
ipChecker, err = ip.NewChecker(trustedIps)
|
||||
ipChecker, err = ip.NewChecker(trustedIPs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -66,11 +70,12 @@ func NewXForwarded(insecure bool, trustedIps []string, next http.Handler) (*XFor
|
||||
}
|
||||
|
||||
return &XForwarded{
|
||||
insecure: insecure,
|
||||
trustedIps: trustedIps,
|
||||
ipChecker: ipChecker,
|
||||
next: next,
|
||||
hostname: hostname,
|
||||
insecure: insecure,
|
||||
trustedIPs: trustedIPs,
|
||||
connectionHeaders: connectionHeaders,
|
||||
ipChecker: ipChecker,
|
||||
next: next,
|
||||
hostname: hostname,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@ -189,9 +194,53 @@ func (x *XForwarded) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
x.rewrite(r)
|
||||
|
||||
x.removeConnectionHeaders(r)
|
||||
|
||||
x.next.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
func (x *XForwarded) removeConnectionHeaders(req *http.Request) {
|
||||
var reqUpType string
|
||||
if httpguts.HeaderValuesContainsToken(req.Header[connection], upgrade) {
|
||||
reqUpType = unsafeHeader(req.Header).Get(upgrade)
|
||||
}
|
||||
|
||||
var connectionHopByHopHeaders []string
|
||||
for _, f := range req.Header[connection] {
|
||||
for _, sf := range strings.Split(f, ",") {
|
||||
if sf = textproto.TrimString(sf); sf != "" {
|
||||
// Connection header cannot dictate to remove X- headers managed by Traefik,
|
||||
// as per rfc7230 https://datatracker.ietf.org/doc/html/rfc7230#section-6.1,
|
||||
// A proxy or gateway MUST ... and then remove the Connection header field itself
|
||||
// (or replace it with the intermediary's own connection options for the forwarded message).
|
||||
if slices.Contains(xHeaders, sf) {
|
||||
continue
|
||||
}
|
||||
|
||||
// Keep headers allowed through the middleware chain.
|
||||
if slices.Contains(x.connectionHeaders, sf) {
|
||||
connectionHopByHopHeaders = append(connectionHopByHopHeaders, sf)
|
||||
continue
|
||||
}
|
||||
|
||||
// Apply Connection header option.
|
||||
req.Header.Del(sf)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if reqUpType != "" {
|
||||
connectionHopByHopHeaders = append(connectionHopByHopHeaders, upgrade)
|
||||
unsafeHeader(req.Header).Set(upgrade, reqUpType)
|
||||
}
|
||||
if len(connectionHopByHopHeaders) > 0 {
|
||||
unsafeHeader(req.Header).Set(connection, strings.Join(connectionHopByHopHeaders, ","))
|
||||
return
|
||||
}
|
||||
|
||||
unsafeHeader(req.Header).Del(connection)
|
||||
}
|
||||
|
||||
// unsafeHeader allows to manage Header values.
|
||||
// Must be used only when the header name is already a canonical key.
|
||||
type unsafeHeader map[string][]string
|
||||
|
@ -12,15 +12,16 @@ import (
|
||||
|
||||
func TestServeHTTP(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
insecure bool
|
||||
trustedIps []string
|
||||
incomingHeaders map[string][]string
|
||||
remoteAddr string
|
||||
expectedHeaders map[string]string
|
||||
tls bool
|
||||
websocket bool
|
||||
host string
|
||||
desc string
|
||||
insecure bool
|
||||
trustedIps []string
|
||||
connectionHeaders []string
|
||||
incomingHeaders map[string][]string
|
||||
remoteAddr string
|
||||
expectedHeaders map[string]string
|
||||
tls bool
|
||||
websocket bool
|
||||
host string
|
||||
}{
|
||||
{
|
||||
desc: "all Empty",
|
||||
@ -269,6 +270,196 @@ func TestServeHTTP(t *testing.T) {
|
||||
xForwardedServer: "foo.com:8080",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Untrusted: Connection header has no effect on X- forwarded headers",
|
||||
insecure: false,
|
||||
incomingHeaders: map[string][]string{
|
||||
connection: {
|
||||
xForwardedProto,
|
||||
xForwardedFor,
|
||||
xForwardedURI,
|
||||
xForwardedMethod,
|
||||
xForwardedHost,
|
||||
xForwardedPort,
|
||||
xForwardedTLSClientCert,
|
||||
xForwardedTLSClientCertInfo,
|
||||
xRealIP,
|
||||
},
|
||||
xForwardedProto: {"foo"},
|
||||
xForwardedFor: {"foo"},
|
||||
xForwardedURI: {"foo"},
|
||||
xForwardedMethod: {"foo"},
|
||||
xForwardedHost: {"foo"},
|
||||
xForwardedPort: {"foo"},
|
||||
xForwardedTLSClientCert: {"foo"},
|
||||
xForwardedTLSClientCertInfo: {"foo"},
|
||||
xRealIP: {"foo"},
|
||||
},
|
||||
expectedHeaders: map[string]string{
|
||||
xForwardedProto: "http",
|
||||
xForwardedFor: "",
|
||||
xForwardedURI: "",
|
||||
xForwardedMethod: "",
|
||||
xForwardedHost: "",
|
||||
xForwardedPort: "80",
|
||||
xForwardedTLSClientCert: "",
|
||||
xForwardedTLSClientCertInfo: "",
|
||||
xRealIP: "",
|
||||
connection: "",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Trusted (insecure): Connection header has no effect on X- forwarded headers",
|
||||
insecure: true,
|
||||
incomingHeaders: map[string][]string{
|
||||
connection: {
|
||||
xForwardedProto,
|
||||
xForwardedFor,
|
||||
xForwardedURI,
|
||||
xForwardedMethod,
|
||||
xForwardedHost,
|
||||
xForwardedPort,
|
||||
xForwardedTLSClientCert,
|
||||
xForwardedTLSClientCertInfo,
|
||||
xRealIP,
|
||||
},
|
||||
xForwardedProto: {"foo"},
|
||||
xForwardedFor: {"foo"},
|
||||
xForwardedURI: {"foo"},
|
||||
xForwardedMethod: {"foo"},
|
||||
xForwardedHost: {"foo"},
|
||||
xForwardedPort: {"foo"},
|
||||
xForwardedTLSClientCert: {"foo"},
|
||||
xForwardedTLSClientCertInfo: {"foo"},
|
||||
xRealIP: {"foo"},
|
||||
},
|
||||
expectedHeaders: map[string]string{
|
||||
xForwardedProto: "foo",
|
||||
xForwardedFor: "foo",
|
||||
xForwardedURI: "foo",
|
||||
xForwardedMethod: "foo",
|
||||
xForwardedHost: "foo",
|
||||
xForwardedPort: "foo",
|
||||
xForwardedTLSClientCert: "foo",
|
||||
xForwardedTLSClientCertInfo: "foo",
|
||||
xRealIP: "foo",
|
||||
connection: "",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Untrusted and Connection: Connection header has no effect on X- forwarded headers",
|
||||
insecure: false,
|
||||
connectionHeaders: []string{
|
||||
xForwardedProto,
|
||||
xForwardedFor,
|
||||
xForwardedURI,
|
||||
xForwardedMethod,
|
||||
xForwardedHost,
|
||||
xForwardedPort,
|
||||
xForwardedTLSClientCert,
|
||||
xForwardedTLSClientCertInfo,
|
||||
xRealIP,
|
||||
},
|
||||
incomingHeaders: map[string][]string{
|
||||
connection: {
|
||||
xForwardedProto,
|
||||
xForwardedFor,
|
||||
xForwardedURI,
|
||||
xForwardedMethod,
|
||||
xForwardedHost,
|
||||
xForwardedPort,
|
||||
xForwardedTLSClientCert,
|
||||
xForwardedTLSClientCertInfo,
|
||||
xRealIP,
|
||||
},
|
||||
xForwardedProto: {"foo"},
|
||||
xForwardedFor: {"foo"},
|
||||
xForwardedURI: {"foo"},
|
||||
xForwardedMethod: {"foo"},
|
||||
xForwardedHost: {"foo"},
|
||||
xForwardedPort: {"foo"},
|
||||
xForwardedTLSClientCert: {"foo"},
|
||||
xForwardedTLSClientCertInfo: {"foo"},
|
||||
xRealIP: {"foo"},
|
||||
},
|
||||
expectedHeaders: map[string]string{
|
||||
xForwardedProto: "http",
|
||||
xForwardedFor: "",
|
||||
xForwardedURI: "",
|
||||
xForwardedMethod: "",
|
||||
xForwardedHost: "",
|
||||
xForwardedPort: "80",
|
||||
xForwardedTLSClientCert: "",
|
||||
xForwardedTLSClientCertInfo: "",
|
||||
xRealIP: "",
|
||||
connection: "",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Trusted (insecure) and Connection: Connection header has no effect on X- forwarded headers",
|
||||
insecure: true,
|
||||
connectionHeaders: []string{
|
||||
xForwardedProto,
|
||||
xForwardedFor,
|
||||
xForwardedURI,
|
||||
xForwardedMethod,
|
||||
xForwardedHost,
|
||||
xForwardedPort,
|
||||
xForwardedTLSClientCert,
|
||||
xForwardedTLSClientCertInfo,
|
||||
xRealIP,
|
||||
},
|
||||
incomingHeaders: map[string][]string{
|
||||
connection: {
|
||||
xForwardedProto,
|
||||
xForwardedFor,
|
||||
xForwardedURI,
|
||||
xForwardedMethod,
|
||||
xForwardedHost,
|
||||
xForwardedPort,
|
||||
xForwardedTLSClientCert,
|
||||
xForwardedTLSClientCertInfo,
|
||||
xRealIP,
|
||||
},
|
||||
xForwardedProto: {"foo"},
|
||||
xForwardedFor: {"foo"},
|
||||
xForwardedURI: {"foo"},
|
||||
xForwardedMethod: {"foo"},
|
||||
xForwardedHost: {"foo"},
|
||||
xForwardedPort: {"foo"},
|
||||
xForwardedTLSClientCert: {"foo"},
|
||||
xForwardedTLSClientCertInfo: {"foo"},
|
||||
xRealIP: {"foo"},
|
||||
},
|
||||
expectedHeaders: map[string]string{
|
||||
xForwardedProto: "foo",
|
||||
xForwardedFor: "foo",
|
||||
xForwardedURI: "foo",
|
||||
xForwardedMethod: "foo",
|
||||
xForwardedHost: "foo",
|
||||
xForwardedPort: "foo",
|
||||
xForwardedTLSClientCert: "foo",
|
||||
xForwardedTLSClientCertInfo: "foo",
|
||||
xRealIP: "foo",
|
||||
connection: "",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Connection: one remove, and one passthrough header",
|
||||
connectionHeaders: []string{
|
||||
"foo",
|
||||
},
|
||||
incomingHeaders: map[string][]string{
|
||||
connection: {
|
||||
"foo",
|
||||
},
|
||||
"Foo": {"bar"},
|
||||
"Bar": {"foo"},
|
||||
},
|
||||
expectedHeaders: map[string]string{
|
||||
"Bar": "foo",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
@ -299,7 +490,7 @@ func TestServeHTTP(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
m, err := NewXForwarded(test.insecure, test.trustedIps,
|
||||
m, err := NewXForwarded(test.insecure, test.trustedIps, test.connectionHeaders,
|
||||
http.HandlerFunc(func(_ http.ResponseWriter, _ *http.Request) {}))
|
||||
require.NoError(t, err)
|
||||
|
||||
@ -382,3 +573,74 @@ func Test_isWebsocketRequest(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestConnection(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
reqHeaders map[string]string
|
||||
connectionHeaders []string
|
||||
expected http.Header
|
||||
}{
|
||||
{
|
||||
desc: "simple remove",
|
||||
reqHeaders: map[string]string{
|
||||
"Foo": "bar",
|
||||
connection: "foo",
|
||||
},
|
||||
expected: http.Header{},
|
||||
},
|
||||
{
|
||||
desc: "remove and upgrade",
|
||||
reqHeaders: map[string]string{
|
||||
upgrade: "test",
|
||||
"Foo": "bar",
|
||||
connection: "upgrade,foo",
|
||||
},
|
||||
expected: http.Header{
|
||||
upgrade: []string{"test"},
|
||||
connection: []string{"Upgrade"},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "no remove",
|
||||
reqHeaders: map[string]string{
|
||||
"Foo": "bar",
|
||||
connection: "fii",
|
||||
},
|
||||
expected: http.Header{
|
||||
"Foo": []string{"bar"},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "no remove because connection header pass through",
|
||||
reqHeaders: map[string]string{
|
||||
"Foo": "bar",
|
||||
connection: "Foo",
|
||||
},
|
||||
connectionHeaders: []string{"Foo"},
|
||||
expected: http.Header{
|
||||
"Foo": []string{"bar"},
|
||||
connection: []string{"Foo"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
forwarded, err := NewXForwarded(true, nil, test.connectionHeaders, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "https://localhost", nil)
|
||||
|
||||
for k, v := range test.reqHeaders {
|
||||
req.Header.Set(k, v)
|
||||
}
|
||||
|
||||
forwarded.removeConnectionHeaders(req)
|
||||
|
||||
assert.Equal(t, test.expected, req.Header)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -8,7 +8,6 @@ import (
|
||||
|
||||
"github.com/traefik/traefik/v3/pkg/config/dynamic"
|
||||
"github.com/traefik/traefik/v3/pkg/middlewares"
|
||||
"github.com/traefik/traefik/v3/pkg/middlewares/connectionheader"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
)
|
||||
|
||||
@ -46,12 +45,11 @@ func New(ctx context.Context, next http.Handler, cfg dynamic.Headers, name strin
|
||||
|
||||
if hasCustomHeaders || hasCorsHeaders {
|
||||
logger.Debug().Msgf("Setting up customHeaders/Cors from %v", cfg)
|
||||
h, err := NewHeader(nextHandler, cfg)
|
||||
var err error
|
||||
handler, err = NewHeader(nextHandler, cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
handler = connectionheader.Remover(h)
|
||||
}
|
||||
|
||||
return &headers{
|
||||
|
@ -149,7 +149,7 @@ func (rl *rateLimiter) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
}
|
||||
|
||||
// We Set even in the case where the source already exists,
|
||||
// because we want to update the expiryTime everytime we get the source,
|
||||
// because we want to update the expiryTime every time we get the source,
|
||||
// as the expiryTime is supposed to reflect the activity (or lack thereof) on that source.
|
||||
if err := rl.buckets.Set(source, bucket, rl.ttl); err != nil {
|
||||
logger.Error().Err(err).Msg("Could not insert/update bucket")
|
||||
|
@ -730,7 +730,7 @@ func deleteUnnecessaryDomains(ctx context.Context, domains []types.Domain) []typ
|
||||
}
|
||||
|
||||
// Check if CN or SANS to check already exists
|
||||
// or can not be checked by a wildcard
|
||||
// or cannot be checked by a wildcard
|
||||
var newDomainsToCheck []string
|
||||
for _, domainProcessed := range domainToCheck.ToStrArray() {
|
||||
if idxDomain < idxDomainToCheck && isDomainAlreadyChecked(domainProcessed, domain.ToStrArray()) {
|
||||
|
@ -205,7 +205,7 @@ func withEndpointSpec(ops ...func(*swarm.EndpointSpec)) func(*swarm.Service) {
|
||||
}
|
||||
}
|
||||
|
||||
func modeDNSSR(spec *swarm.EndpointSpec) {
|
||||
func modeDNSRR(spec *swarm.EndpointSpec) {
|
||||
spec.Mode = swarm.ResolutionModeDNSRR
|
||||
}
|
||||
|
||||
|
@ -3976,7 +3976,7 @@ func TestDynConfBuilder_getIPAddress_swarm(t *testing.T) {
|
||||
networks map[string]*network.Summary
|
||||
}{
|
||||
{
|
||||
service: swarmService(withEndpointSpec(modeDNSSR)),
|
||||
service: swarmService(withEndpointSpec(modeDNSRR)),
|
||||
expected: "",
|
||||
networks: map[string]*network.Summary{},
|
||||
},
|
||||
|
@ -114,7 +114,7 @@ func TestSwarmProvider_listServices(t *testing.T) {
|
||||
"traefik.docker.network": "barnet",
|
||||
"traefik.docker.LBSwarm": "true",
|
||||
}),
|
||||
withEndpointSpec(modeDNSSR)),
|
||||
withEndpointSpec(modeDNSRR)),
|
||||
},
|
||||
dockerVersion: "1.30",
|
||||
networks: []network.Summary{},
|
||||
@ -140,7 +140,7 @@ func TestSwarmProvider_listServices(t *testing.T) {
|
||||
"traefik.docker.network": "barnet",
|
||||
"traefik.docker.LBSwarm": "true",
|
||||
}),
|
||||
withEndpointSpec(modeDNSSR)),
|
||||
withEndpointSpec(modeDNSRR)),
|
||||
},
|
||||
dockerVersion: "1.30",
|
||||
networks: []network.Summary{
|
||||
@ -185,7 +185,7 @@ func TestSwarmProvider_listServices(t *testing.T) {
|
||||
serviceLabels(map[string]string{
|
||||
"traefik.docker.network": "barnet",
|
||||
}),
|
||||
withEndpointSpec(modeDNSSR)),
|
||||
withEndpointSpec(modeDNSRR)),
|
||||
},
|
||||
tasks: []swarm.Task{
|
||||
swarmTask("id1",
|
||||
|
@ -86,7 +86,7 @@ func Test_getPort_swarm(t *testing.T) {
|
||||
}{
|
||||
{
|
||||
service: swarmService(
|
||||
withEndpointSpec(modeDNSSR),
|
||||
withEndpointSpec(modeDNSRR),
|
||||
),
|
||||
networks: map[string]*docker.NetworkResource{},
|
||||
serverPort: "8080",
|
||||
|
@ -91,15 +91,27 @@ func (i *Provider) createConfiguration(ctx context.Context) *dynamic.Configurati
|
||||
}
|
||||
|
||||
func (i *Provider) acme(cfg *dynamic.Configuration) {
|
||||
var eps []string
|
||||
allowACMEByPass := map[string]bool{}
|
||||
for name, ep := range i.staticCfg.EntryPoints {
|
||||
allowACMEByPass[name] = ep.AllowACMEByPass
|
||||
}
|
||||
|
||||
var eps []string
|
||||
var epsByPass []string
|
||||
uniq := map[string]struct{}{}
|
||||
for _, resolver := range i.staticCfg.CertificatesResolvers {
|
||||
if resolver.ACME != nil && resolver.ACME.HTTPChallenge != nil && resolver.ACME.HTTPChallenge.EntryPoint != "" {
|
||||
if _, ok := uniq[resolver.ACME.HTTPChallenge.EntryPoint]; !ok {
|
||||
eps = append(eps, resolver.ACME.HTTPChallenge.EntryPoint)
|
||||
uniq[resolver.ACME.HTTPChallenge.EntryPoint] = struct{}{}
|
||||
if _, ok := uniq[resolver.ACME.HTTPChallenge.EntryPoint]; ok {
|
||||
continue
|
||||
}
|
||||
uniq[resolver.ACME.HTTPChallenge.EntryPoint] = struct{}{}
|
||||
|
||||
if allowByPass, ok := allowACMEByPass[resolver.ACME.HTTPChallenge.EntryPoint]; ok && allowByPass {
|
||||
epsByPass = append(epsByPass, resolver.ACME.HTTPChallenge.EntryPoint)
|
||||
continue
|
||||
}
|
||||
|
||||
eps = append(eps, resolver.ACME.HTTPChallenge.EntryPoint)
|
||||
}
|
||||
}
|
||||
|
||||
@ -115,6 +127,17 @@ func (i *Provider) acme(cfg *dynamic.Configuration) {
|
||||
cfg.HTTP.Routers["acme-http"] = rt
|
||||
cfg.HTTP.Services["acme-http"] = &dynamic.Service{}
|
||||
}
|
||||
|
||||
if len(epsByPass) > 0 {
|
||||
rt := &dynamic.Router{
|
||||
Rule: "PathPrefix(`/.well-known/acme-challenge/`)",
|
||||
EntryPoints: epsByPass,
|
||||
Service: "acme-http@internal",
|
||||
}
|
||||
|
||||
cfg.HTTP.Routers["acme-http-bypass"] = rt
|
||||
cfg.HTTP.Services["acme-http"] = &dynamic.Service{}
|
||||
}
|
||||
}
|
||||
|
||||
func (i *Provider) redirection(ctx context.Context, cfg *dynamic.Configuration) {
|
||||
|
@ -777,7 +777,7 @@ func TestDo_staticConfiguration(t *testing.T) {
|
||||
}
|
||||
|
||||
config.Providers.HTTP = &http.Provider{
|
||||
Endpoint: "Myenpoint",
|
||||
Endpoint: "Myendpoint",
|
||||
PollInterval: 42,
|
||||
PollTimeout: 42,
|
||||
TLS: &types.ClientTLS{
|
||||
|
@ -858,7 +858,7 @@ func BenchmarkService(b *testing.B) {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "tchouck",
|
||||
URL: "tchouk",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -21,6 +21,8 @@ const defaultBufSize = 4096
|
||||
|
||||
// Router is a TCP router.
|
||||
type Router struct {
|
||||
acmeTLSPassthrough bool
|
||||
|
||||
// Contains TCP routes.
|
||||
muxerTCP tcpmuxer.Muxer
|
||||
// Contains TCP TLS routes.
|
||||
@ -164,7 +166,7 @@ func (r *Router) ServeTCP(conn tcp.WriteCloser) {
|
||||
}
|
||||
|
||||
// Handling ACME-TLS/1 challenges.
|
||||
if slices.Contains(hello.protos, tlsalpn01.ACMETLS1Protocol) {
|
||||
if !r.acmeTLSPassthrough && slices.Contains(hello.protos, tlsalpn01.ACMETLS1Protocol) {
|
||||
r.acmeTLSALPNHandler().ServeTCP(r.GetConn(conn, hello.peeked))
|
||||
return
|
||||
}
|
||||
@ -317,6 +319,10 @@ func (r *Router) SetHTTPSHandler(handler http.Handler, config *tls.Config) {
|
||||
r.httpsTLSConfig = config
|
||||
}
|
||||
|
||||
func (r *Router) EnableACMETLSPassthrough() {
|
||||
r.acmeTLSPassthrough = true
|
||||
}
|
||||
|
||||
// Conn is a connection proxy that handles Peeked bytes.
|
||||
type Conn struct {
|
||||
// Peeked are the bytes that have been read from Conn for the purposes of route matching,
|
||||
|
@ -212,9 +212,10 @@ func Test_Routing(t *testing.T) {
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
desc string
|
||||
routers []applyRouter
|
||||
checks []checkCase
|
||||
desc string
|
||||
routers []applyRouter
|
||||
checks []checkCase
|
||||
allowACMETLSPassthrough bool
|
||||
}{
|
||||
{
|
||||
desc: "No routers",
|
||||
@ -271,6 +272,18 @@ func Test_Routing(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "TCP TLS passthrough catches ACME TLS",
|
||||
allowACMETLSPassthrough: true,
|
||||
routers: []applyRouter{routerTCPTLSCatchAllPassthrough},
|
||||
checks: []checkCase{
|
||||
{
|
||||
desc: "ACME TLS Challenge",
|
||||
checkRouter: checkACMETLS,
|
||||
expectedError: "tls: first record does not look like a TLS handshake",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Single TCP CatchAll router",
|
||||
routers: []applyRouter{routerTCPCatchAll},
|
||||
@ -596,6 +609,10 @@ func Test_Routing(t *testing.T) {
|
||||
router, err := manager.buildEntryPointHandler(context.Background(), dynConf.TCPRouters, dynConf.Routers, nil, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
if test.allowACMETLSPassthrough {
|
||||
router.EnableACMETLSPassthrough()
|
||||
}
|
||||
|
||||
epListener, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
require.NoError(t, err)
|
||||
|
||||
@ -717,7 +734,7 @@ func routerTCPTLSCatchAll(conf *runtime.Configuration) {
|
||||
}
|
||||
}
|
||||
|
||||
// routerTCPTLSCatchAllPassthrough a TCP TLS CatchAll Passthrough - HostSNI(`*`) router with TLS 1.0 config.
|
||||
// routerTCPTLSCatchAllPassthrough a TCP TLS CatchAll Passthrough - HostSNI(`*`) router with TLS 1.2 config.
|
||||
func routerTCPTLSCatchAllPassthrough(conf *runtime.Configuration) {
|
||||
conf.TCPRouters["tcp-tls-catchall-passthrough"] = &runtime.TCPRouterInfo{
|
||||
TCPRouter: &dynamic.TCPRouter{
|
||||
|
@ -21,8 +21,9 @@ import (
|
||||
|
||||
// RouterFactory the factory of TCP/UDP routers.
|
||||
type RouterFactory struct {
|
||||
entryPointsTCP []string
|
||||
entryPointsUDP []string
|
||||
entryPointsTCP []string
|
||||
entryPointsUDP []string
|
||||
allowACMEByPass map[string]bool
|
||||
|
||||
managerFactory *service.ManagerFactory
|
||||
|
||||
@ -40,9 +41,20 @@ type RouterFactory struct {
|
||||
func NewRouterFactory(staticConfiguration static.Configuration, managerFactory *service.ManagerFactory, tlsManager *tls.Manager,
|
||||
observabilityMgr *middleware.ObservabilityMgr, pluginBuilder middleware.PluginsBuilder, dialerManager *tcp.DialerManager,
|
||||
) *RouterFactory {
|
||||
handlesTLSChallenge := false
|
||||
for _, resolver := range staticConfiguration.CertificatesResolvers {
|
||||
if resolver.ACME.TLSChallenge != nil {
|
||||
handlesTLSChallenge = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
allowACMEByPass := map[string]bool{}
|
||||
var entryPointsTCP, entryPointsUDP []string
|
||||
for name, cfg := range staticConfiguration.EntryPoints {
|
||||
protocol, err := cfg.GetProtocol()
|
||||
for name, ep := range staticConfiguration.EntryPoints {
|
||||
allowACMEByPass[name] = ep.AllowACMEByPass || !handlesTLSChallenge
|
||||
|
||||
protocol, err := ep.GetProtocol()
|
||||
if err != nil {
|
||||
// Should never happen because Traefik should not start if protocol is invalid.
|
||||
log.Error().Err(err).Msg("Invalid protocol")
|
||||
@ -63,6 +75,7 @@ func NewRouterFactory(staticConfiguration static.Configuration, managerFactory *
|
||||
tlsManager: tlsManager,
|
||||
pluginBuilder: pluginBuilder,
|
||||
dialerManager: dialerManager,
|
||||
allowACMEByPass: allowACMEByPass,
|
||||
}
|
||||
}
|
||||
|
||||
@ -95,6 +108,12 @@ func (f *RouterFactory) CreateRouters(rtConf *runtime.Configuration) (map[string
|
||||
rtTCPManager := tcprouter.NewManager(rtConf, svcTCPManager, middlewaresTCPBuilder, handlersNonTLS, handlersTLS, f.tlsManager)
|
||||
routersTCP := rtTCPManager.BuildHandlers(ctx, f.entryPointsTCP)
|
||||
|
||||
for ep, r := range routersTCP {
|
||||
if allowACMEByPass, ok := f.allowACMEByPass[ep]; ok && allowACMEByPass {
|
||||
r.EnableACMETLSPassthrough()
|
||||
}
|
||||
}
|
||||
|
||||
// UDP
|
||||
svcUDPManager := udpsvc.NewManager(rtConf)
|
||||
rtUDPManager := udprouter.NewManager(rtConf, svcUDPManager)
|
||||
|
@ -185,7 +185,10 @@ func NewTCPEntryPoint(ctx context.Context, name string, config *static.EntryPoin
|
||||
return nil, fmt.Errorf("error preparing server: %w", err)
|
||||
}
|
||||
|
||||
rt := &tcprouter.Router{}
|
||||
rt, err := tcprouter.NewRouter()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error preparing tcp router: %w", err)
|
||||
}
|
||||
|
||||
reqDecorator := requestdecorator.New(hostResolverConfig)
|
||||
|
||||
@ -607,6 +610,7 @@ func createHTTPServer(ctx context.Context, ln net.Listener, configuration *stati
|
||||
handler, err = forwardedheaders.NewXForwarded(
|
||||
configuration.ForwardedHeaders.Insecure,
|
||||
configuration.ForwardedHeaders.TrustedIPs,
|
||||
configuration.ForwardedHeaders.Connection,
|
||||
next)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -20,7 +20,9 @@ import (
|
||||
)
|
||||
|
||||
func TestShutdownHijacked(t *testing.T) {
|
||||
router := &tcprouter.Router{}
|
||||
router, err := tcprouter.NewRouter()
|
||||
require.NoError(t, err)
|
||||
|
||||
router.SetHTTPHandler(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
conn, _, err := rw.(http.Hijacker).Hijack()
|
||||
require.NoError(t, err)
|
||||
@ -34,7 +36,9 @@ func TestShutdownHijacked(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestShutdownHTTP(t *testing.T) {
|
||||
router := &tcprouter.Router{}
|
||||
router, err := tcprouter.NewRouter()
|
||||
require.NoError(t, err)
|
||||
|
||||
router.SetHTTPHandler(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
rw.WriteHeader(http.StatusOK)
|
||||
time.Sleep(time.Second)
|
||||
@ -167,7 +171,9 @@ func TestReadTimeoutWithoutFirstByte(t *testing.T) {
|
||||
}, nil, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
router := &tcprouter.Router{}
|
||||
router, err := tcprouter.NewRouter()
|
||||
require.NoError(t, err)
|
||||
|
||||
router.SetHTTPHandler(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
rw.WriteHeader(http.StatusOK)
|
||||
}))
|
||||
@ -204,7 +210,9 @@ func TestReadTimeoutWithFirstByte(t *testing.T) {
|
||||
}, nil, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
router := &tcprouter.Router{}
|
||||
router, err := tcprouter.NewRouter()
|
||||
require.NoError(t, err)
|
||||
|
||||
router.SetHTTPHandler(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
rw.WriteHeader(http.StatusOK)
|
||||
}))
|
||||
@ -244,7 +252,9 @@ func TestKeepAliveMaxRequests(t *testing.T) {
|
||||
}, nil, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
router := &tcprouter.Router{}
|
||||
router, err := tcprouter.NewRouter()
|
||||
require.NoError(t, err)
|
||||
|
||||
router.SetHTTPHandler(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
rw.WriteHeader(http.StatusOK)
|
||||
}))
|
||||
@ -290,7 +300,9 @@ func TestKeepAliveMaxTime(t *testing.T) {
|
||||
}, nil, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
router := &tcprouter.Router{}
|
||||
router, err := tcprouter.NewRouter()
|
||||
require.NoError(t, err)
|
||||
|
||||
router.SetHTTPHandler(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
rw.WriteHeader(http.StatusOK)
|
||||
}))
|
||||
|
@ -47,7 +47,7 @@ func TestGetBestCertificate(t *testing.T) {
|
||||
expectedCert: "*.snitest.com",
|
||||
},
|
||||
{
|
||||
desc: "Best Match with dynamic wildcard only, case insensitive",
|
||||
desc: "Best Match with dynamic wildcard only, case-insensitive",
|
||||
domainToCheck: "bar.www.snitest.com",
|
||||
dynamicCert: "*.www.snitest.com",
|
||||
expectedCert: "*.www.snitest.com",
|
||||
|
@ -137,7 +137,7 @@ func TestMatchDomain(t *testing.T) {
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
desc: "dot replaced by a cahr",
|
||||
desc: "dot replaced by a char",
|
||||
certDomain: "sub.sub.traefik.wtf",
|
||||
domain: "sub.sub.traefikiwtf",
|
||||
expected: false,
|
||||
|
@ -129,7 +129,7 @@
|
||||
align="left"
|
||||
icon="eva-github-outline"
|
||||
no-caps
|
||||
label="Github repository"
|
||||
label="GitHub repository"
|
||||
class="btn-submenu full-width"
|
||||
/>
|
||||
</q-item>
|
||||
|
Loading…
Reference in New Issue
Block a user