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:
|
env:
|
||||||
GO_VERSION: '1.23'
|
GO_VERSION: '1.23'
|
||||||
GOLANGCI_LINT_VERSION: v1.60.3
|
GOLANGCI_LINT_VERSION: v1.60.3
|
||||||
MISSSPELL_VERSION: v0.6.0
|
MISSPELL_VERSION: v0.6.0
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
|
||||||
@ -29,8 +29,8 @@ jobs:
|
|||||||
- name: Install golangci-lint ${{ env.GOLANGCI_LINT_VERSION }}
|
- 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}
|
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 }}
|
- 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 ${MISSSPELL_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
|
- name: Avoid generating webui
|
||||||
run: touch webui/static/index.html
|
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)
|
## [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)
|
[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.
|
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.
|
This conversation beforehand avoids one-sided decisions.
|
||||||
|
|
||||||
The first message will be edited and marked as abuse.
|
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 {
|
if _, ok := resolverNames[rt.TLS.CertResolver]; !ok {
|
||||||
log.Error().Err(err).Str(logs.RouterName, rtName).Str("certificateResolver", rt.TLS.CertResolver).
|
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.
|
You can configure Traefik to use an ACME provider (like Let's Encrypt) for automatic certificate generation.
|
||||||
|
|
||||||
!!! warning "Let's Encrypt and Rate Limiting"
|
!!! 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.
|
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.
|
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.
|
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`.
|
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
|
!!! important
|
||||||
A `provider` is mandatory.
|
A `provider` is mandatory.
|
||||||
|
@ -278,7 +278,7 @@ spec:
|
|||||||
requestHost: true
|
requestHost: true
|
||||||
```
|
```
|
||||||
|
|
||||||
```yaml tab="Cosul Catalog"
|
```yaml tab="Consul Catalog"
|
||||||
- "traefik.http.middlewares.test-inflightreq.inflightreq.sourcecriterion.requesthost=true"
|
- "traefik.http.middlewares.test-inflightreq.inflightreq.sourcecriterion.requesthost=true"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -24,7 +24,7 @@ whoami:
|
|||||||
- "traefik.http.routers.router1.middlewares=foo-add-prefix@docker"
|
- "traefik.http.routers.router1.middlewares=foo-add-prefix@docker"
|
||||||
```
|
```
|
||||||
|
|
||||||
```yaml tab="Kubernetes IngressRoute"
|
```yaml tab="IngressRoute"
|
||||||
# As a Kubernetes Traefik IngressRoute
|
# As a Kubernetes Traefik IngressRoute
|
||||||
---
|
---
|
||||||
apiVersion: traefik.io/v1alpha1
|
apiVersion: traefik.io/v1alpha1
|
||||||
|
@ -35,7 +35,7 @@ whoami:
|
|||||||
- "traefik.http.routers.router1.middlewares=foo-add-prefix@docker"
|
- "traefik.http.routers.router1.middlewares=foo-add-prefix@docker"
|
||||||
```
|
```
|
||||||
|
|
||||||
```yaml tab="Kubernetes IngressRoute"
|
```yaml tab="IngressRoute"
|
||||||
---
|
---
|
||||||
apiVersion: traefik.io/v1alpha1
|
apiVersion: traefik.io/v1alpha1
|
||||||
kind: Middleware
|
kind: Middleware
|
||||||
|
@ -24,7 +24,7 @@ whoami:
|
|||||||
- "traefik.tcp.routers.router1.middlewares=foo-ip-allowlist@docker"
|
- "traefik.tcp.routers.router1.middlewares=foo-ip-allowlist@docker"
|
||||||
```
|
```
|
||||||
|
|
||||||
```yaml tab="Kubernetes IngressRoute"
|
```yaml tab="IngressRoute"
|
||||||
# As a Kubernetes Traefik IngressRoute
|
# As a Kubernetes Traefik IngressRoute
|
||||||
---
|
---
|
||||||
apiVersion: traefik.io/v1alpha1
|
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"
|
- "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
|
apiVersion: networking.k8s.io/v1beta1
|
||||||
kind: Ingress
|
kind: Ingress
|
||||||
metadata:
|
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"
|
- "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.
|
# The definitions below require the definitions for the Middleware and IngressRoute kinds.
|
||||||
# https://doc.traefik.io/traefik/reference/dynamic-configuration/kubernetes-crd/#definitions
|
# https://doc.traefik.io/traefik/reference/dynamic-configuration/kubernetes-crd/#definitions
|
||||||
apiVersion: traefik.io/v1alpha1
|
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.
|
# The definitions below require the definitions for the TLSOption and IngressRoute kinds.
|
||||||
# https://doc.traefik.io/traefik/reference/dynamic-configuration/kubernetes-crd/#definitions
|
# https://doc.traefik.io/traefik/reference/dynamic-configuration/kubernetes-crd/#definitions
|
||||||
apiVersion: traefik.io/v1alpha1
|
apiVersion: traefik.io/v1alpha1
|
||||||
@ -442,7 +442,7 @@ To apply a redirection:
|
|||||||
traefik.http.middlewares.https_redirect.redirectscheme.permanent: true
|
traefik.http.middlewares.https_redirect.redirectscheme.permanent: true
|
||||||
```
|
```
|
||||||
|
|
||||||
```yaml tab="K8s IngressRoute"
|
```yaml tab="IngressRoute"
|
||||||
apiVersion: traefik.io/v1alpha1
|
apiVersion: traefik.io/v1alpha1
|
||||||
kind: IngressRoute
|
kind: IngressRoute
|
||||||
metadata:
|
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"
|
- "traefik.frontend.rule=Host:example.org;PathPrefixStrip:/admin"
|
||||||
```
|
```
|
||||||
|
|
||||||
```yaml tab="Kubernetes Ingress"
|
```yaml tab="Ingress"
|
||||||
apiVersion: networking.k8s.io/v1beta1
|
apiVersion: networking.k8s.io/v1beta1
|
||||||
kind: Ingress
|
kind: Ingress
|
||||||
metadata:
|
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"
|
- "traefik.http.middlewares.admin-stripprefix.stripprefix.prefixes=/admin"
|
||||||
```
|
```
|
||||||
|
|
||||||
```yaml tab="Kubernetes IngressRoute"
|
```yaml tab="IngressRoute"
|
||||||
---
|
---
|
||||||
apiVersion: traefik.io/v1alpha1
|
apiVersion: traefik.io/v1alpha1
|
||||||
kind: IngressRoute
|
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),
|
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),
|
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
|
## 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).
|
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`
|
### `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"
|
- "traefik.http.routers.my-container.middlewares=add-foo-prefix@file"
|
||||||
```
|
```
|
||||||
|
|
||||||
```yaml tab="Kubernetes Ingress Route"
|
```yaml tab="IngressRoute"
|
||||||
apiVersion: traefik.io/v1alpha1
|
apiVersion: traefik.io/v1alpha1
|
||||||
kind: IngressRoute
|
kind: IngressRoute
|
||||||
metadata:
|
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.
|
# when the cross-provider syntax is used.
|
||||||
```
|
```
|
||||||
|
|
||||||
```yaml tab="Kubernetes Ingress"
|
```yaml tab="Ingress"
|
||||||
apiVersion: traefik.io/v1alpha1
|
apiVersion: traefik.io/v1alpha1
|
||||||
kind: Middleware
|
kind: Middleware
|
||||||
metadata:
|
metadata:
|
||||||
|
@ -8,7 +8,7 @@ description: "View the reference for performing dynamic configurations with Trae
|
|||||||
Dynamic configuration with Consul Catalog
|
Dynamic configuration with Consul Catalog
|
||||||
{: .subtitle }
|
{: .subtitle }
|
||||||
|
|
||||||
The labels are case insensitive.
|
The labels are case-insensitive.
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
--8<-- "content/reference/dynamic-configuration/consul-catalog.yml"
|
--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
|
Dynamic configuration with ECS provider
|
||||||
{: .subtitle }
|
{: .subtitle }
|
||||||
|
|
||||||
The labels are case insensitive.
|
The labels are case-insensitive.
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
--8<-- "content/reference/dynamic-configuration/ecs.yml"
|
--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
|
Dynamic configuration with Nomad Service Discovery
|
||||||
{: .subtitle }
|
{: .subtitle }
|
||||||
|
|
||||||
The labels are case insensitive.
|
The labels are case-insensitive.
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
--8<-- "content/reference/dynamic-configuration/nomad.yml"
|
--8<-- "content/reference/dynamic-configuration/nomad.yml"
|
||||||
|
@ -117,9 +117,15 @@ Entry points definition. (Default: ```false```)
|
|||||||
`--entrypoints.<name>.address`:
|
`--entrypoints.<name>.address`:
|
||||||
Entry point address.
|
Entry point address.
|
||||||
|
|
||||||
|
`--entrypoints.<name>.allowacmebypass`:
|
||||||
|
Enables handling of ACME TLS and HTTP challenges with custom routers. (Default: ```false```)
|
||||||
|
|
||||||
`--entrypoints.<name>.asdefault`:
|
`--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```)
|
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`:
|
`--entrypoints.<name>.forwardedheaders.insecure`:
|
||||||
Trust all forwarded headers. (Default: ```false```)
|
Trust all forwarded headers. (Default: ```false```)
|
||||||
|
|
||||||
|
@ -117,9 +117,15 @@ Entry points definition. (Default: ```false```)
|
|||||||
`TRAEFIK_ENTRYPOINTS_<NAME>_ADDRESS`:
|
`TRAEFIK_ENTRYPOINTS_<NAME>_ADDRESS`:
|
||||||
Entry point 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`:
|
`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```)
|
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`:
|
`TRAEFIK_ENTRYPOINTS_<NAME>_FORWARDEDHEADERS_INSECURE`:
|
||||||
Trust all forwarded headers. (Default: ```false```)
|
Trust all forwarded headers. (Default: ```false```)
|
||||||
|
|
||||||
|
@ -30,6 +30,7 @@
|
|||||||
[entryPoints]
|
[entryPoints]
|
||||||
[entryPoints.EntryPoint0]
|
[entryPoints.EntryPoint0]
|
||||||
address = "foobar"
|
address = "foobar"
|
||||||
|
allowACMEByPass = true
|
||||||
reusePort = true
|
reusePort = true
|
||||||
asDefault = true
|
asDefault = true
|
||||||
[entryPoints.EntryPoint0.transport]
|
[entryPoints.EntryPoint0.transport]
|
||||||
@ -48,6 +49,7 @@
|
|||||||
[entryPoints.EntryPoint0.forwardedHeaders]
|
[entryPoints.EntryPoint0.forwardedHeaders]
|
||||||
insecure = true
|
insecure = true
|
||||||
trustedIPs = ["foobar", "foobar"]
|
trustedIPs = ["foobar", "foobar"]
|
||||||
|
connection = ["foobar", "foobar"]
|
||||||
[entryPoints.EntryPoint0.http]
|
[entryPoints.EntryPoint0.http]
|
||||||
middlewares = ["foobar", "foobar"]
|
middlewares = ["foobar", "foobar"]
|
||||||
encodeQuerySemicolons = true
|
encodeQuerySemicolons = true
|
||||||
|
@ -35,6 +35,7 @@ tcpServersTransport:
|
|||||||
entryPoints:
|
entryPoints:
|
||||||
EntryPoint0:
|
EntryPoint0:
|
||||||
address: foobar
|
address: foobar
|
||||||
|
allowACMEByPass: true
|
||||||
reusePort: true
|
reusePort: true
|
||||||
asDefault: true
|
asDefault: true
|
||||||
transport:
|
transport:
|
||||||
@ -57,6 +58,9 @@ entryPoints:
|
|||||||
trustedIPs:
|
trustedIPs:
|
||||||
- foobar
|
- foobar
|
||||||
- foobar
|
- foobar
|
||||||
|
connection:
|
||||||
|
- foobar
|
||||||
|
- foobar
|
||||||
http:
|
http:
|
||||||
redirections:
|
redirections:
|
||||||
entryPoint:
|
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.
|
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
|
### ReusePort
|
||||||
|
|
||||||
_Optional, Default=false_
|
_Optional, Default=false_
|
||||||
@ -500,6 +529,40 @@ You can configure Traefik to trust the forwarded headers information (`X-Forward
|
|||||||
--entryPoints.web.forwardedHeaders.insecure
|
--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
|
### Transport
|
||||||
|
|
||||||
#### `respondingTimeouts`
|
#### `respondingTimeouts`
|
||||||
|
@ -24,7 +24,7 @@ With Consul Catalog, Traefik can leverage tags attached to a service to generate
|
|||||||
|
|
||||||
!!! info "tags"
|
!!! 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)
|
- The complete list of tags can be found [the reference page](../../reference/dynamic-configuration/consul-catalog.md)
|
||||||
|
|
||||||
### General
|
### General
|
||||||
|
@ -95,7 +95,7 @@ With Docker, Traefik can leverage labels attached to a container to generate rou
|
|||||||
|
|
||||||
!!! info "Labels"
|
!!! 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).
|
- The complete list of labels can be found in [the reference page](../../reference/dynamic-configuration/docker.md).
|
||||||
|
|
||||||
### General
|
### General
|
||||||
|
@ -22,7 +22,7 @@ With ECS, Traefik can leverage labels attached to a container to generate routin
|
|||||||
|
|
||||||
!!! info "labels"
|
!!! 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).
|
- The complete list of labels can be found in [the reference page](../../reference/dynamic-configuration/ecs.md).
|
||||||
|
|
||||||
### General
|
### General
|
||||||
|
@ -12,7 +12,7 @@ A Story of key & values
|
|||||||
|
|
||||||
!!! info "Keys"
|
!!! 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).
|
- The complete list of keys can be found in [the reference page](../../reference/dynamic-configuration/kv.md).
|
||||||
|
|
||||||
### Routers
|
### 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"
|
!!! 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)
|
- The complete list of tags can be found [the reference page](../../reference/dynamic-configuration/nomad.md)
|
||||||
|
|
||||||
### General
|
### General
|
||||||
|
@ -118,7 +118,7 @@ With Docker Swarm, Traefik can leverage labels attached to a service to generate
|
|||||||
|
|
||||||
!!! info "Labels"
|
!!! 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).
|
- The complete list of labels can be found in [the reference page](../../reference/dynamic-configuration/docker.md).
|
||||||
|
|
||||||
### General
|
### 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 |
|
| 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.
|
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)"
|
??? example "Setting priorities -- using the [File Provider](../../providers/file.md)"
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
title: "Traefik Docker DNS Challenge Documentation"
|
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
|
# 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) []:.
|
# Locality Name (eg, city) []:.
|
||||||
# Organization Name (eg, company) [Internet Widgits Pty Ltd]:.
|
# Organization Name (eg, company) [Internet Widgits Pty Ltd]:.
|
||||||
# Organizational Unit Name (eg, section) []:.
|
# 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 []:.
|
# Email Address []:.
|
||||||
#
|
#
|
||||||
# Please enter the following 'extra' attributes
|
# Please enter the following 'extra' attributes
|
||||||
@ -58,7 +58,7 @@ openssl genrsa -out client3.key 2048
|
|||||||
# Issuer
|
# Issuer
|
||||||
# CN = ca1.example.com
|
# CN = ca1.example.com
|
||||||
# Subject
|
# Subject
|
||||||
# CN = clien1.example.com
|
# CN = client1.example.com
|
||||||
openssl req -key client1.key -new -out client1.csr
|
openssl req -key client1.key -new -out client1.csr
|
||||||
|
|
||||||
# Country Name (2 letter code) [AU]:.
|
# Country Name (2 letter code) [AU]:.
|
||||||
|
@ -4,6 +4,7 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -20,6 +21,11 @@ func TestHeadersSuite(t *testing.T) {
|
|||||||
suite.Run(t, new(HeadersSuite))
|
suite.Run(t, new(HeadersSuite))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *HeadersSuite) TearDownTest() {
|
||||||
|
s.displayTraefikLogFile(traefikTestLogFile)
|
||||||
|
_ = os.Remove(traefikTestAccessLogFile)
|
||||||
|
}
|
||||||
|
|
||||||
func (s *HeadersSuite) TestSimpleConfiguration() {
|
func (s *HeadersSuite) TestSimpleConfiguration() {
|
||||||
s.traefikCmd(withConfigFile("fixtures/headers/basic.toml"))
|
s.traefikCmd(withConfigFile("fixtures/headers/basic.toml"))
|
||||||
|
|
||||||
@ -62,6 +68,53 @@ func (s *HeadersSuite) TestReverseProxyHeaderRemoved() {
|
|||||||
require.NoError(s.T(), err)
|
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() {
|
func (s *HeadersSuite) TestCorsResponses() {
|
||||||
file := s.adaptFile("fixtures/headers/cors.toml", struct{}{})
|
file := s.adaptFile("fixtures/headers/cors.toml", struct{}{})
|
||||||
s.traefikCmd(withConfigFile(file))
|
s.traefikCmd(withConfigFile(file))
|
||||||
|
@ -24,7 +24,7 @@ const traefikTestAccessLogFileRotated = traefikTestAccessLogFile + ".rotated"
|
|||||||
// Log rotation integration test suite.
|
// Log rotation integration test suite.
|
||||||
type LogRotationSuite struct{ BaseSuite }
|
type LogRotationSuite struct{ BaseSuite }
|
||||||
|
|
||||||
func TestLogRorationSuite(t *testing.T) {
|
func TestLogRotationSuite(t *testing.T) {
|
||||||
suite.Run(t, new(LogRotationSuite))
|
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)
|
req, err := http.NewRequest(http.MethodGet, "http://localhost:8000/whoami", http.NoBody)
|
||||||
require.NoError(s.T(), err)
|
require.NoError(s.T(), err)
|
||||||
req.Header.Set("Autorization", "Bearer ThisIsABearerToken")
|
req.Header.Set("Authorization", "Bearer ThisIsABearerToken")
|
||||||
|
|
||||||
response, err := http.DefaultClient.Do(req)
|
response, err := http.DefaultClient.Do(req)
|
||||||
require.NoError(s.T(), err)
|
require.NoError(s.T(), err)
|
||||||
|
@ -19,7 +19,7 @@ const (
|
|||||||
type timedAction func(timeout time.Duration, operation DoCondition) error
|
type timedAction func(timeout time.Duration, operation DoCondition) error
|
||||||
|
|
||||||
// Sleep pauses the current goroutine for at least the duration d.
|
// 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) {
|
func Sleep(d time.Duration) {
|
||||||
d = applyCIMultiplier(d)
|
d = applyCIMultiplier(d)
|
||||||
time.Sleep(d)
|
time.Sleep(d)
|
||||||
|
@ -70,8 +70,8 @@ func TestDecodeToNode(t *testing.T) {
|
|||||||
{
|
{
|
||||||
desc: "several entries, level 0",
|
desc: "several entries, level 0",
|
||||||
in: map[string]string{
|
in: map[string]string{
|
||||||
"traefik": "bar",
|
"traefik": "bar",
|
||||||
"traefic": "bur",
|
"traefik_": "bur",
|
||||||
},
|
},
|
||||||
expected: expected{error: true},
|
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{
|
in: map[string]string{
|
||||||
"traefik/foo/aaa": "bar",
|
"traefik/foo/aaa": "bar",
|
||||||
"traefik/Foo/bbb": "bur",
|
"traefik/Foo/bbb": "bur",
|
||||||
|
@ -12,6 +12,7 @@ import (
|
|||||||
// EntryPoint holds the entry point configuration.
|
// EntryPoint holds the entry point configuration.
|
||||||
type EntryPoint struct {
|
type EntryPoint struct {
|
||||||
Address string `description:"Entry point address." json:"address,omitempty" toml:"address,omitempty" yaml:"address,omitempty"`
|
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"`
|
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"`
|
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"`
|
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 {
|
type ForwardedHeaders struct {
|
||||||
Insecure bool `description:"Trust all forwarded headers." json:"insecure,omitempty" toml:"insecure,omitempty" yaml:"insecure,omitempty" export:"true"`
|
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"`
|
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.
|
// ProxyProtocol contains Proxy-Protocol configuration.
|
||||||
|
@ -46,7 +46,7 @@ func TestDepthStrategy_GetIP(t *testing.T) {
|
|||||||
expected: "10.0.0.3",
|
expected: "10.0.0.3",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "Use non existing depth in XForwardedFor",
|
desc: "Use nonexistent depth in XForwardedFor",
|
||||||
depth: 2,
|
depth: 2,
|
||||||
xForwardedFor: "",
|
xForwardedFor: "",
|
||||||
expected: "",
|
expected: "",
|
||||||
|
@ -106,15 +106,28 @@ func NewHandler(config *types.AccessLog) (*Handler, error) {
|
|||||||
Level: logrus.InfoLevel,
|
Level: logrus.InfoLevel,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Transform headers names in config to a canonical form, to be used as is without further transformations.
|
// Transform header names 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 {
|
// and transform field names to lower case, to enable case-insensitive lookup.
|
||||||
fields := map[string]string{}
|
if config.Fields != nil {
|
||||||
|
if len(config.Fields.Names) > 0 {
|
||||||
|
fields := map[string]string{}
|
||||||
|
|
||||||
for h, v := range config.Fields.Headers.Names {
|
for h, v := range config.Fields.Names {
|
||||||
fields[textproto.CanonicalMIMEHeaderKey(h)] = v
|
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{
|
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))
|
reqWithDataTable := req.WithContext(context.WithValue(req.Context(), DataTableKey, logDataTable))
|
||||||
|
|
||||||
core[RequestCount] = nextRequestCount()
|
core[RequestCount] = nextRequestCount()
|
||||||
@ -238,19 +241,30 @@ func (h *Handler) ServeHTTP(rw http.ResponseWriter, req *http.Request, next http
|
|||||||
return
|
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)
|
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).
|
// Close closes the Logger (i.e. the file, drain logHandlerChan, etc).
|
||||||
@ -334,7 +348,7 @@ func (h *Handler) logTheRoundTrip(logDataTable *LogData) {
|
|||||||
fields := logrus.Fields{}
|
fields := logrus.Fields{}
|
||||||
|
|
||||||
for k, v := range logDataTable.Core {
|
for k, v := range logDataTable.Core {
|
||||||
if h.config.Fields.Keep(k) {
|
if h.config.Fields.Keep(strings.ToLower(k)) {
|
||||||
fields[k] = v
|
fields[k] = v
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ package accesslog
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"crypto/x509/pkix"
|
"crypto/x509/pkix"
|
||||||
@ -24,6 +25,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/v3/pkg/middlewares/capture"
|
"github.com/traefik/traefik/v3/pkg/middlewares/capture"
|
||||||
|
"github.com/traefik/traefik/v3/pkg/middlewares/recovery"
|
||||||
"github.com/traefik/traefik/v3/pkg/types"
|
"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",
|
header: "User-Agent",
|
||||||
expected: types.AccessLogKeep,
|
expected: types.AccessLogKeep,
|
||||||
accessLogFields: types.AccessLogFields{
|
accessLogFields: types.AccessLogFields{
|
||||||
@ -465,6 +467,32 @@ func TestLoggerJSON(t *testing.T) {
|
|||||||
RequestRefererHeader: assertString(testReferer),
|
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 {
|
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) {
|
func TestNewLogHandlerOutputStdout(t *testing.T) {
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
desc string
|
desc string
|
||||||
@ -832,3 +918,89 @@ func logWriterTestHandlerFunc(rw http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
rw.WriteHeader(testStatus)
|
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 (
|
import (
|
||||||
"net/http"
|
"net/http"
|
@ -1,4 +1,4 @@
|
|||||||
package connectionheader
|
package auth
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
@ -13,7 +13,6 @@ import (
|
|||||||
|
|
||||||
"github.com/traefik/traefik/v3/pkg/config/dynamic"
|
"github.com/traefik/traefik/v3/pkg/config/dynamic"
|
||||||
"github.com/traefik/traefik/v3/pkg/middlewares"
|
"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/middlewares/observability"
|
||||||
"github.com/traefik/traefik/v3/pkg/tracing"
|
"github.com/traefik/traefik/v3/pkg/tracing"
|
||||||
"github.com/traefik/traefik/v3/pkg/types"
|
"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) {
|
func (fa *forwardAuth) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||||
logger := middlewares.GetLogger(req.Context(), fa.name, typeNameForward)
|
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)
|
forwardReq, err := http.NewRequestWithContext(req.Context(), http.MethodGet, fa.address, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -235,7 +235,7 @@ func (cc *codeCatcher) Flush() {
|
|||||||
// since we want to serve the ones from the error page,
|
// since we want to serve the ones from the error page,
|
||||||
// so we just don't flush.
|
// so we just don't flush.
|
||||||
// (e.g., To prevent superfluous WriteHeader on request with a
|
// (e.g., To prevent superfluous WriteHeader on request with a
|
||||||
// `Transfert-Encoding: chunked` header).
|
// `Transfer-Encoding: chunked` header).
|
||||||
if cc.caughtFilteredCode {
|
if cc.caughtFilteredCode {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -3,10 +3,13 @@ package forwardedheaders
|
|||||||
import (
|
import (
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/textproto"
|
||||||
"os"
|
"os"
|
||||||
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/traefik/traefik/v3/pkg/ip"
|
"github.com/traefik/traefik/v3/pkg/ip"
|
||||||
|
"golang.org/x/net/http/httpguts"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -42,19 +45,20 @@ var xHeaders = []string{
|
|||||||
// Unless insecure is set,
|
// 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.
|
// it first removes all the existing values for those headers if the remote address is not one of the trusted ones.
|
||||||
type XForwarded struct {
|
type XForwarded struct {
|
||||||
insecure bool
|
insecure bool
|
||||||
trustedIps []string
|
trustedIPs []string
|
||||||
ipChecker *ip.Checker
|
connectionHeaders []string
|
||||||
next http.Handler
|
ipChecker *ip.Checker
|
||||||
hostname string
|
next http.Handler
|
||||||
|
hostname string
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewXForwarded creates a new XForwarded.
|
// 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
|
var ipChecker *ip.Checker
|
||||||
if len(trustedIps) > 0 {
|
if len(trustedIPs) > 0 {
|
||||||
var err error
|
var err error
|
||||||
ipChecker, err = ip.NewChecker(trustedIps)
|
ipChecker, err = ip.NewChecker(trustedIPs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -66,11 +70,12 @@ func NewXForwarded(insecure bool, trustedIps []string, next http.Handler) (*XFor
|
|||||||
}
|
}
|
||||||
|
|
||||||
return &XForwarded{
|
return &XForwarded{
|
||||||
insecure: insecure,
|
insecure: insecure,
|
||||||
trustedIps: trustedIps,
|
trustedIPs: trustedIPs,
|
||||||
ipChecker: ipChecker,
|
connectionHeaders: connectionHeaders,
|
||||||
next: next,
|
ipChecker: ipChecker,
|
||||||
hostname: hostname,
|
next: next,
|
||||||
|
hostname: hostname,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -189,9 +194,53 @@ func (x *XForwarded) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
x.rewrite(r)
|
x.rewrite(r)
|
||||||
|
|
||||||
|
x.removeConnectionHeaders(r)
|
||||||
|
|
||||||
x.next.ServeHTTP(w, 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.
|
// unsafeHeader allows to manage Header values.
|
||||||
// Must be used only when the header name is already a canonical key.
|
// Must be used only when the header name is already a canonical key.
|
||||||
type unsafeHeader map[string][]string
|
type unsafeHeader map[string][]string
|
||||||
|
@ -12,15 +12,16 @@ import (
|
|||||||
|
|
||||||
func TestServeHTTP(t *testing.T) {
|
func TestServeHTTP(t *testing.T) {
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
desc string
|
desc string
|
||||||
insecure bool
|
insecure bool
|
||||||
trustedIps []string
|
trustedIps []string
|
||||||
incomingHeaders map[string][]string
|
connectionHeaders []string
|
||||||
remoteAddr string
|
incomingHeaders map[string][]string
|
||||||
expectedHeaders map[string]string
|
remoteAddr string
|
||||||
tls bool
|
expectedHeaders map[string]string
|
||||||
websocket bool
|
tls bool
|
||||||
host string
|
websocket bool
|
||||||
|
host string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
desc: "all Empty",
|
desc: "all Empty",
|
||||||
@ -269,6 +270,196 @@ func TestServeHTTP(t *testing.T) {
|
|||||||
xForwardedServer: "foo.com:8080",
|
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 {
|
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) {}))
|
http.HandlerFunc(func(_ http.ResponseWriter, _ *http.Request) {}))
|
||||||
require.NoError(t, err)
|
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/config/dynamic"
|
||||||
"github.com/traefik/traefik/v3/pkg/middlewares"
|
"github.com/traefik/traefik/v3/pkg/middlewares"
|
||||||
"github.com/traefik/traefik/v3/pkg/middlewares/connectionheader"
|
|
||||||
"go.opentelemetry.io/otel/trace"
|
"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 {
|
if hasCustomHeaders || hasCorsHeaders {
|
||||||
logger.Debug().Msgf("Setting up customHeaders/Cors from %v", cfg)
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
handler = connectionheader.Remover(h)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return &headers{
|
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,
|
// 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.
|
// 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 {
|
if err := rl.buckets.Set(source, bucket, rl.ttl); err != nil {
|
||||||
logger.Error().Err(err).Msg("Could not insert/update bucket")
|
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
|
// 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
|
var newDomainsToCheck []string
|
||||||
for _, domainProcessed := range domainToCheck.ToStrArray() {
|
for _, domainProcessed := range domainToCheck.ToStrArray() {
|
||||||
if idxDomain < idxDomainToCheck && isDomainAlreadyChecked(domainProcessed, domain.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
|
spec.Mode = swarm.ResolutionModeDNSRR
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3976,7 +3976,7 @@ func TestDynConfBuilder_getIPAddress_swarm(t *testing.T) {
|
|||||||
networks map[string]*network.Summary
|
networks map[string]*network.Summary
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
service: swarmService(withEndpointSpec(modeDNSSR)),
|
service: swarmService(withEndpointSpec(modeDNSRR)),
|
||||||
expected: "",
|
expected: "",
|
||||||
networks: map[string]*network.Summary{},
|
networks: map[string]*network.Summary{},
|
||||||
},
|
},
|
||||||
|
@ -114,7 +114,7 @@ func TestSwarmProvider_listServices(t *testing.T) {
|
|||||||
"traefik.docker.network": "barnet",
|
"traefik.docker.network": "barnet",
|
||||||
"traefik.docker.LBSwarm": "true",
|
"traefik.docker.LBSwarm": "true",
|
||||||
}),
|
}),
|
||||||
withEndpointSpec(modeDNSSR)),
|
withEndpointSpec(modeDNSRR)),
|
||||||
},
|
},
|
||||||
dockerVersion: "1.30",
|
dockerVersion: "1.30",
|
||||||
networks: []network.Summary{},
|
networks: []network.Summary{},
|
||||||
@ -140,7 +140,7 @@ func TestSwarmProvider_listServices(t *testing.T) {
|
|||||||
"traefik.docker.network": "barnet",
|
"traefik.docker.network": "barnet",
|
||||||
"traefik.docker.LBSwarm": "true",
|
"traefik.docker.LBSwarm": "true",
|
||||||
}),
|
}),
|
||||||
withEndpointSpec(modeDNSSR)),
|
withEndpointSpec(modeDNSRR)),
|
||||||
},
|
},
|
||||||
dockerVersion: "1.30",
|
dockerVersion: "1.30",
|
||||||
networks: []network.Summary{
|
networks: []network.Summary{
|
||||||
@ -185,7 +185,7 @@ func TestSwarmProvider_listServices(t *testing.T) {
|
|||||||
serviceLabels(map[string]string{
|
serviceLabels(map[string]string{
|
||||||
"traefik.docker.network": "barnet",
|
"traefik.docker.network": "barnet",
|
||||||
}),
|
}),
|
||||||
withEndpointSpec(modeDNSSR)),
|
withEndpointSpec(modeDNSRR)),
|
||||||
},
|
},
|
||||||
tasks: []swarm.Task{
|
tasks: []swarm.Task{
|
||||||
swarmTask("id1",
|
swarmTask("id1",
|
||||||
|
@ -86,7 +86,7 @@ func Test_getPort_swarm(t *testing.T) {
|
|||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
service: swarmService(
|
service: swarmService(
|
||||||
withEndpointSpec(modeDNSSR),
|
withEndpointSpec(modeDNSRR),
|
||||||
),
|
),
|
||||||
networks: map[string]*docker.NetworkResource{},
|
networks: map[string]*docker.NetworkResource{},
|
||||||
serverPort: "8080",
|
serverPort: "8080",
|
||||||
|
@ -91,15 +91,27 @@ func (i *Provider) createConfiguration(ctx context.Context) *dynamic.Configurati
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (i *Provider) acme(cfg *dynamic.Configuration) {
|
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{}{}
|
uniq := map[string]struct{}{}
|
||||||
for _, resolver := range i.staticCfg.CertificatesResolvers {
|
for _, resolver := range i.staticCfg.CertificatesResolvers {
|
||||||
if resolver.ACME != nil && resolver.ACME.HTTPChallenge != nil && resolver.ACME.HTTPChallenge.EntryPoint != "" {
|
if resolver.ACME != nil && resolver.ACME.HTTPChallenge != nil && resolver.ACME.HTTPChallenge.EntryPoint != "" {
|
||||||
if _, ok := uniq[resolver.ACME.HTTPChallenge.EntryPoint]; !ok {
|
if _, ok := uniq[resolver.ACME.HTTPChallenge.EntryPoint]; ok {
|
||||||
eps = append(eps, resolver.ACME.HTTPChallenge.EntryPoint)
|
continue
|
||||||
uniq[resolver.ACME.HTTPChallenge.EntryPoint] = struct{}{}
|
|
||||||
}
|
}
|
||||||
|
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.Routers["acme-http"] = rt
|
||||||
cfg.HTTP.Services["acme-http"] = &dynamic.Service{}
|
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) {
|
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{
|
config.Providers.HTTP = &http.Provider{
|
||||||
Endpoint: "Myenpoint",
|
Endpoint: "Myendpoint",
|
||||||
PollInterval: 42,
|
PollInterval: 42,
|
||||||
PollTimeout: 42,
|
PollTimeout: 42,
|
||||||
TLS: &types.ClientTLS{
|
TLS: &types.ClientTLS{
|
||||||
|
@ -858,7 +858,7 @@ func BenchmarkService(b *testing.B) {
|
|||||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||||
Servers: []dynamic.Server{
|
Servers: []dynamic.Server{
|
||||||
{
|
{
|
||||||
URL: "tchouck",
|
URL: "tchouk",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -21,6 +21,8 @@ const defaultBufSize = 4096
|
|||||||
|
|
||||||
// Router is a TCP router.
|
// Router is a TCP router.
|
||||||
type Router struct {
|
type Router struct {
|
||||||
|
acmeTLSPassthrough bool
|
||||||
|
|
||||||
// Contains TCP routes.
|
// Contains TCP routes.
|
||||||
muxerTCP tcpmuxer.Muxer
|
muxerTCP tcpmuxer.Muxer
|
||||||
// Contains TCP TLS routes.
|
// Contains TCP TLS routes.
|
||||||
@ -164,7 +166,7 @@ func (r *Router) ServeTCP(conn tcp.WriteCloser) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Handling ACME-TLS/1 challenges.
|
// 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))
|
r.acmeTLSALPNHandler().ServeTCP(r.GetConn(conn, hello.peeked))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -317,6 +319,10 @@ func (r *Router) SetHTTPSHandler(handler http.Handler, config *tls.Config) {
|
|||||||
r.httpsTLSConfig = config
|
r.httpsTLSConfig = config
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *Router) EnableACMETLSPassthrough() {
|
||||||
|
r.acmeTLSPassthrough = true
|
||||||
|
}
|
||||||
|
|
||||||
// Conn is a connection proxy that handles Peeked bytes.
|
// Conn is a connection proxy that handles Peeked bytes.
|
||||||
type Conn struct {
|
type Conn struct {
|
||||||
// Peeked are the bytes that have been read from Conn for the purposes of route matching,
|
// 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 {
|
testCases := []struct {
|
||||||
desc string
|
desc string
|
||||||
routers []applyRouter
|
routers []applyRouter
|
||||||
checks []checkCase
|
checks []checkCase
|
||||||
|
allowACMETLSPassthrough bool
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
desc: "No routers",
|
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",
|
desc: "Single TCP CatchAll router",
|
||||||
routers: []applyRouter{routerTCPCatchAll},
|
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)
|
router, err := manager.buildEntryPointHandler(context.Background(), dynConf.TCPRouters, dynConf.Routers, nil, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
if test.allowACMETLSPassthrough {
|
||||||
|
router.EnableACMETLSPassthrough()
|
||||||
|
}
|
||||||
|
|
||||||
epListener, err := net.Listen("tcp", "127.0.0.1:0")
|
epListener, err := net.Listen("tcp", "127.0.0.1:0")
|
||||||
require.NoError(t, err)
|
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) {
|
func routerTCPTLSCatchAllPassthrough(conf *runtime.Configuration) {
|
||||||
conf.TCPRouters["tcp-tls-catchall-passthrough"] = &runtime.TCPRouterInfo{
|
conf.TCPRouters["tcp-tls-catchall-passthrough"] = &runtime.TCPRouterInfo{
|
||||||
TCPRouter: &dynamic.TCPRouter{
|
TCPRouter: &dynamic.TCPRouter{
|
||||||
|
@ -21,8 +21,9 @@ import (
|
|||||||
|
|
||||||
// RouterFactory the factory of TCP/UDP routers.
|
// RouterFactory the factory of TCP/UDP routers.
|
||||||
type RouterFactory struct {
|
type RouterFactory struct {
|
||||||
entryPointsTCP []string
|
entryPointsTCP []string
|
||||||
entryPointsUDP []string
|
entryPointsUDP []string
|
||||||
|
allowACMEByPass map[string]bool
|
||||||
|
|
||||||
managerFactory *service.ManagerFactory
|
managerFactory *service.ManagerFactory
|
||||||
|
|
||||||
@ -40,9 +41,20 @@ type RouterFactory struct {
|
|||||||
func NewRouterFactory(staticConfiguration static.Configuration, managerFactory *service.ManagerFactory, tlsManager *tls.Manager,
|
func NewRouterFactory(staticConfiguration static.Configuration, managerFactory *service.ManagerFactory, tlsManager *tls.Manager,
|
||||||
observabilityMgr *middleware.ObservabilityMgr, pluginBuilder middleware.PluginsBuilder, dialerManager *tcp.DialerManager,
|
observabilityMgr *middleware.ObservabilityMgr, pluginBuilder middleware.PluginsBuilder, dialerManager *tcp.DialerManager,
|
||||||
) *RouterFactory {
|
) *RouterFactory {
|
||||||
|
handlesTLSChallenge := false
|
||||||
|
for _, resolver := range staticConfiguration.CertificatesResolvers {
|
||||||
|
if resolver.ACME.TLSChallenge != nil {
|
||||||
|
handlesTLSChallenge = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
allowACMEByPass := map[string]bool{}
|
||||||
var entryPointsTCP, entryPointsUDP []string
|
var entryPointsTCP, entryPointsUDP []string
|
||||||
for name, cfg := range staticConfiguration.EntryPoints {
|
for name, ep := range staticConfiguration.EntryPoints {
|
||||||
protocol, err := cfg.GetProtocol()
|
allowACMEByPass[name] = ep.AllowACMEByPass || !handlesTLSChallenge
|
||||||
|
|
||||||
|
protocol, err := ep.GetProtocol()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Should never happen because Traefik should not start if protocol is invalid.
|
// Should never happen because Traefik should not start if protocol is invalid.
|
||||||
log.Error().Err(err).Msg("Invalid protocol")
|
log.Error().Err(err).Msg("Invalid protocol")
|
||||||
@ -63,6 +75,7 @@ func NewRouterFactory(staticConfiguration static.Configuration, managerFactory *
|
|||||||
tlsManager: tlsManager,
|
tlsManager: tlsManager,
|
||||||
pluginBuilder: pluginBuilder,
|
pluginBuilder: pluginBuilder,
|
||||||
dialerManager: dialerManager,
|
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)
|
rtTCPManager := tcprouter.NewManager(rtConf, svcTCPManager, middlewaresTCPBuilder, handlersNonTLS, handlersTLS, f.tlsManager)
|
||||||
routersTCP := rtTCPManager.BuildHandlers(ctx, f.entryPointsTCP)
|
routersTCP := rtTCPManager.BuildHandlers(ctx, f.entryPointsTCP)
|
||||||
|
|
||||||
|
for ep, r := range routersTCP {
|
||||||
|
if allowACMEByPass, ok := f.allowACMEByPass[ep]; ok && allowACMEByPass {
|
||||||
|
r.EnableACMETLSPassthrough()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// UDP
|
// UDP
|
||||||
svcUDPManager := udpsvc.NewManager(rtConf)
|
svcUDPManager := udpsvc.NewManager(rtConf)
|
||||||
rtUDPManager := udprouter.NewManager(rtConf, svcUDPManager)
|
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)
|
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)
|
reqDecorator := requestdecorator.New(hostResolverConfig)
|
||||||
|
|
||||||
@ -607,6 +610,7 @@ func createHTTPServer(ctx context.Context, ln net.Listener, configuration *stati
|
|||||||
handler, err = forwardedheaders.NewXForwarded(
|
handler, err = forwardedheaders.NewXForwarded(
|
||||||
configuration.ForwardedHeaders.Insecure,
|
configuration.ForwardedHeaders.Insecure,
|
||||||
configuration.ForwardedHeaders.TrustedIPs,
|
configuration.ForwardedHeaders.TrustedIPs,
|
||||||
|
configuration.ForwardedHeaders.Connection,
|
||||||
next)
|
next)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -20,7 +20,9 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestShutdownHijacked(t *testing.T) {
|
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) {
|
router.SetHTTPHandler(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||||
conn, _, err := rw.(http.Hijacker).Hijack()
|
conn, _, err := rw.(http.Hijacker).Hijack()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@ -34,7 +36,9 @@ func TestShutdownHijacked(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestShutdownHTTP(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) {
|
router.SetHTTPHandler(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||||
rw.WriteHeader(http.StatusOK)
|
rw.WriteHeader(http.StatusOK)
|
||||||
time.Sleep(time.Second)
|
time.Sleep(time.Second)
|
||||||
@ -167,7 +171,9 @@ func TestReadTimeoutWithoutFirstByte(t *testing.T) {
|
|||||||
}, nil, nil)
|
}, nil, nil)
|
||||||
require.NoError(t, err)
|
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) {
|
router.SetHTTPHandler(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||||
rw.WriteHeader(http.StatusOK)
|
rw.WriteHeader(http.StatusOK)
|
||||||
}))
|
}))
|
||||||
@ -204,7 +210,9 @@ func TestReadTimeoutWithFirstByte(t *testing.T) {
|
|||||||
}, nil, nil)
|
}, nil, nil)
|
||||||
require.NoError(t, err)
|
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) {
|
router.SetHTTPHandler(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||||
rw.WriteHeader(http.StatusOK)
|
rw.WriteHeader(http.StatusOK)
|
||||||
}))
|
}))
|
||||||
@ -244,7 +252,9 @@ func TestKeepAliveMaxRequests(t *testing.T) {
|
|||||||
}, nil, nil)
|
}, nil, nil)
|
||||||
require.NoError(t, err)
|
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) {
|
router.SetHTTPHandler(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||||
rw.WriteHeader(http.StatusOK)
|
rw.WriteHeader(http.StatusOK)
|
||||||
}))
|
}))
|
||||||
@ -290,7 +300,9 @@ func TestKeepAliveMaxTime(t *testing.T) {
|
|||||||
}, nil, nil)
|
}, nil, nil)
|
||||||
require.NoError(t, err)
|
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) {
|
router.SetHTTPHandler(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||||
rw.WriteHeader(http.StatusOK)
|
rw.WriteHeader(http.StatusOK)
|
||||||
}))
|
}))
|
||||||
|
@ -47,7 +47,7 @@ func TestGetBestCertificate(t *testing.T) {
|
|||||||
expectedCert: "*.snitest.com",
|
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",
|
domainToCheck: "bar.www.snitest.com",
|
||||||
dynamicCert: "*.www.snitest.com",
|
dynamicCert: "*.www.snitest.com",
|
||||||
expectedCert: "*.www.snitest.com",
|
expectedCert: "*.www.snitest.com",
|
||||||
|
@ -137,7 +137,7 @@ func TestMatchDomain(t *testing.T) {
|
|||||||
expected: true,
|
expected: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "dot replaced by a cahr",
|
desc: "dot replaced by a char",
|
||||||
certDomain: "sub.sub.traefik.wtf",
|
certDomain: "sub.sub.traefik.wtf",
|
||||||
domain: "sub.sub.traefikiwtf",
|
domain: "sub.sub.traefikiwtf",
|
||||||
expected: false,
|
expected: false,
|
||||||
|
@ -129,7 +129,7 @@
|
|||||||
align="left"
|
align="left"
|
||||||
icon="eva-github-outline"
|
icon="eva-github-outline"
|
||||||
no-caps
|
no-caps
|
||||||
label="Github repository"
|
label="GitHub repository"
|
||||||
class="btn-submenu full-width"
|
class="btn-submenu full-width"
|
||||||
/>
|
/>
|
||||||
</q-item>
|
</q-item>
|
||||||
|
Loading…
Reference in New Issue
Block a user