mirror of
https://github.com/traefik/traefik.git
synced 2026-05-04 20:06:21 +02:00
Merge branch v3.6 into v3.7
This commit is contained in:
commit
da808bda43
5
.github/workflows/validate.yaml
vendored
5
.github/workflows/validate.yaml
vendored
@ -75,11 +75,6 @@ jobs:
|
||||
make generate
|
||||
git diff --exit-code
|
||||
|
||||
- name: go mod tidy
|
||||
run: |
|
||||
go mod tidy
|
||||
git diff --exit-code
|
||||
|
||||
- name: make generate-crd
|
||||
run: |
|
||||
make generate-crd
|
||||
|
||||
34
CHANGELOG.md
34
CHANGELOG.md
@ -1,3 +1,37 @@
|
||||
## [v3.6.14](https://github.com/traefik/traefik/tree/v3.6.14) (2026-04-22)
|
||||
[All Commits](https://github.com/traefik/traefik/compare/v3.6.13...v3.6.14)
|
||||
|
||||
**Bug fixes:**
|
||||
- **[acme]** Bump github.com/go-acme/lego/v4 to v4.34.0 ([#12993](https://github.com/traefik/traefik/pull/12993) @ldez)
|
||||
- **[docker]** Downgrade log level for missing container on inspect ([#12900](https://github.com/traefik/traefik/pull/12900) @Otoru)
|
||||
- **[sticky-session, k8s/crd]** Make SameSite cookie value case-insensitive ([#12922](https://github.com/traefik/traefik/pull/12922) @murataslan1)
|
||||
- **[k8s/crd, k8s]** Honor allowCrossNamespace with chain middleware CRD ([#12976](https://github.com/traefik/traefik/pull/12976) @rtribotte)
|
||||
- **[middleware]** Remove untrusted X headers with underscores ([#12961](https://github.com/traefik/traefik/pull/12961) @rtribotte)
|
||||
- **[middleware]** Sanitize the request URL after stripping the prefix ([#12990](https://github.com/traefik/traefik/pull/12990) @kevinpollet)
|
||||
- **[middleware]** Deprecate ForwardAuth.TrustForwardHeader option ([#13012](https://github.com/traefik/traefik/pull/13012) @kevinpollet)
|
||||
- **[middleware, authentication]** Remove map lookup making the basic auth notFoundSecret empty ([#12960](https://github.com/traefik/traefik/pull/12960) @rtribotte)
|
||||
- **[middleware, authentication]** Fix trustForwardHeader on forward auth middleware ([#12994](https://github.com/traefik/traefik/pull/12994) @juliens)
|
||||
- **[middleware, authentication]** Cleanup and make ForwardAuth logs consistent ([#13013](https://github.com/traefik/traefik/pull/13013) @kevinpollet)
|
||||
- **[webui]** Upgrade form-data to 2.5.4, 3.0.4, 4.0.4 ([#12958](https://github.com/traefik/traefik/pull/12958) @orbisai0security)
|
||||
|
||||
**Documentation:**
|
||||
- **[k8s]** Fix yaml indentation ([#12957](https://github.com/traefik/traefik/pull/12957) @isayme)
|
||||
- **[k8s]** Clarify install config watchNamespace watches only one namespace ([#12962](https://github.com/traefik/traefik/pull/12962) @parkerfath)
|
||||
- **[k8s/crd]** Update ingressroute.md ([#12916](https://github.com/traefik/traefik/pull/12916) @Rajakavitha1)
|
||||
- Reverse versions order in migration guide ([#12959](https://github.com/traefik/traefik/pull/12959) @nmengin)
|
||||
- Update vulnerability submission guidelines ([#12968](https://github.com/traefik/traefik/pull/12968) @emilevauge)
|
||||
|
||||
## [v2.11.43](https://github.com/traefik/traefik/tree/v2.11.43) (2026-04-22)
|
||||
[All Commits](https://github.com/traefik/traefik/compare/v2.11.42...v2.11.43)
|
||||
|
||||
**Bug fixes:**
|
||||
- **[middleware, authentication]** Remove map lookup making the basic auth notFoundSecret empty ([#12960](https://github.com/traefik/traefik/pull/12960) @rtribotte)
|
||||
- **[middleware, authentication]** Fix trustForwardHeader on forward auth middleware ([#12994](https://github.com/traefik/traefik/pull/12994) @juliens)
|
||||
- **[middleware, authentication]** Cleanup and make ForwardAuth logs consistent ([#13013](https://github.com/traefik/traefik/pull/13013) @kevinpollet)
|
||||
- **[middleware]** Remove untrusted X headers with underscores ([#12961](https://github.com/traefik/traefik/pull/12961) @rtribotte)
|
||||
- **[middleware]** Sanitize the request URL after stripping the prefix ([#12990](https://github.com/traefik/traefik/pull/12990) @kevinpollet)
|
||||
- **[k8s/crd, k8s]** Honor allowCrossNamespace with chain middleware CRD ([#12976](https://github.com/traefik/traefik/pull/12976) @rtribotte)
|
||||
|
||||
## [v3.7.0-rc.1](https://github.com/traefik/traefik/tree/v3.7.0-rc.1) (2026-04-07)
|
||||
[All Commits](https://github.com/traefik/traefik/compare/v3.7.0-ea.3...v3.7.0-rc.1)
|
||||
|
||||
|
||||
@ -15,6 +15,10 @@ You can subscribe by sending an email to security+subscribe@traefik.io or on [th
|
||||
Reported vulnerabilities can be found on
|
||||
[cve.mitre.org](https://cve.mitre.org/cgi-bin/cvekey.cgi?keyword=traefik).
|
||||
|
||||
CVEs are only created for vulnerabilities affecting **Generally Available (GA) versions** of Traefik.
|
||||
Vulnerabilities discovered in non-GA versions (release candidates, betas, early access, or development branches)
|
||||
will be fixed without creating a CVE.
|
||||
|
||||
## Report a Vulnerability
|
||||
|
||||
We want to keep Traefik safe for everyone.
|
||||
@ -22,6 +26,28 @@ If you've discovered a security vulnerability in Traefik,
|
||||
we appreciate your help in disclosing it to us in a responsible manner,
|
||||
by creating a [security advisory](https://github.com/traefik/traefik/security/advisories).
|
||||
|
||||
## Code of Conduct for Vulnerability Submissions
|
||||
|
||||
We are committed to handling every legitimate report responsibly,
|
||||
and we expect submitters to engage with our security team in a respectful and collaborative manner.
|
||||
|
||||
The following behaviors are **not acceptable** and will not be tolerated:
|
||||
|
||||
- **Threats** to publicly disclose the vulnerability if it is not fixed within a timeframe you set unilaterally.
|
||||
- **Ultimatums** or pressure tactics intended to force a faster response than our normal triage and remediation process allows.
|
||||
- **Demands** for payment, bug bounties, or any form of compensation in exchange for not disclosing the issue
|
||||
(Traefik does not operate a paid bug bounty program).
|
||||
- **Aggressive, abusive, or disrespectful communication** with our security team.
|
||||
|
||||
Submitters who engage in any of the above may face the following consequences:
|
||||
|
||||
- The submitter **will not be credited** in the security advisory or any subsequent communication.
|
||||
- The submitter's GitHub profile may be **reported to GitHub** for violation of platform terms of service.
|
||||
- We may **decline to engage further** on the report, while still addressing the underlying issue if it is legitimate.
|
||||
|
||||
We take security seriously and act on legitimate reports as quickly as our resources allow.
|
||||
Patience and constructive dialogue help us protect users effectively.
|
||||
|
||||
## Submission Quality Guidelines
|
||||
|
||||
We have been receiving an increasing number of low-quality vulnerability reports that are not actual security issues.
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -362,6 +362,9 @@ spec:
|
||||
- none
|
||||
- lax
|
||||
- strict
|
||||
- None
|
||||
- Lax
|
||||
- Strict
|
||||
type: string
|
||||
secure:
|
||||
description: Secure defines whether the cookie
|
||||
@ -1390,6 +1393,9 @@ spec:
|
||||
- none
|
||||
- lax
|
||||
- strict
|
||||
- None
|
||||
- Lax
|
||||
- Strict
|
||||
type: string
|
||||
secure:
|
||||
description: Secure defines whether the cookie can
|
||||
@ -1529,8 +1535,10 @@ spec:
|
||||
type: boolean
|
||||
type: object
|
||||
trustForwardHeader:
|
||||
description: 'TrustForwardHeader defines whether to trust (ie:
|
||||
forward) all X-Forwarded-* headers.'
|
||||
description: |-
|
||||
TrustForwardHeader defines whether to trust (ie: forward) all X-Forwarded-* headers.
|
||||
|
||||
Deprecated: Use forwardedHeaders.trustedIPs at the EntryPoint level instead, and set trustForwardHeader to true on this middleware.
|
||||
type: boolean
|
||||
type: object
|
||||
grpcWeb:
|
||||
@ -3202,6 +3210,9 @@ spec:
|
||||
- none
|
||||
- lax
|
||||
- strict
|
||||
- None
|
||||
- Lax
|
||||
- Strict
|
||||
type: string
|
||||
secure:
|
||||
description: Secure defines whether the cookie can
|
||||
@ -3449,6 +3460,9 @@ spec:
|
||||
- none
|
||||
- lax
|
||||
- strict
|
||||
- None
|
||||
- Lax
|
||||
- Strict
|
||||
type: string
|
||||
secure:
|
||||
description: Secure defines whether the cookie can
|
||||
@ -3709,6 +3723,9 @@ spec:
|
||||
- none
|
||||
- lax
|
||||
- strict
|
||||
- None
|
||||
- Lax
|
||||
- Strict
|
||||
type: string
|
||||
secure:
|
||||
description: Secure defines whether the cookie can
|
||||
@ -4075,6 +4092,9 @@ spec:
|
||||
- none
|
||||
- lax
|
||||
- strict
|
||||
- None
|
||||
- Lax
|
||||
- Strict
|
||||
type: string
|
||||
secure:
|
||||
description: Secure defines whether the cookie can
|
||||
@ -4224,6 +4244,9 @@ spec:
|
||||
- none
|
||||
- lax
|
||||
- strict
|
||||
- None
|
||||
- Lax
|
||||
- Strict
|
||||
type: string
|
||||
secure:
|
||||
description: Secure defines whether the cookie can only
|
||||
@ -4477,6 +4500,9 @@ spec:
|
||||
- none
|
||||
- lax
|
||||
- strict
|
||||
- None
|
||||
- Lax
|
||||
- Strict
|
||||
type: string
|
||||
secure:
|
||||
description: Secure defines whether the cookie can
|
||||
@ -4547,6 +4573,9 @@ spec:
|
||||
- none
|
||||
- lax
|
||||
- strict
|
||||
- None
|
||||
- Lax
|
||||
- Strict
|
||||
type: string
|
||||
secure:
|
||||
description: Secure defines whether the cookie can only
|
||||
|
||||
@ -363,6 +363,9 @@ spec:
|
||||
- none
|
||||
- lax
|
||||
- strict
|
||||
- None
|
||||
- Lax
|
||||
- Strict
|
||||
type: string
|
||||
secure:
|
||||
description: Secure defines whether the cookie
|
||||
|
||||
@ -526,6 +526,9 @@ spec:
|
||||
- none
|
||||
- lax
|
||||
- strict
|
||||
- None
|
||||
- Lax
|
||||
- Strict
|
||||
type: string
|
||||
secure:
|
||||
description: Secure defines whether the cookie can
|
||||
@ -665,8 +668,10 @@ spec:
|
||||
type: boolean
|
||||
type: object
|
||||
trustForwardHeader:
|
||||
description: 'TrustForwardHeader defines whether to trust (ie:
|
||||
forward) all X-Forwarded-* headers.'
|
||||
description: |-
|
||||
TrustForwardHeader defines whether to trust (ie: forward) all X-Forwarded-* headers.
|
||||
|
||||
Deprecated: Use forwardedHeaders.trustedIPs at the EntryPoint level instead, and set trustForwardHeader to true on this middleware.
|
||||
type: boolean
|
||||
type: object
|
||||
grpcWeb:
|
||||
|
||||
@ -283,6 +283,9 @@ spec:
|
||||
- none
|
||||
- lax
|
||||
- strict
|
||||
- None
|
||||
- Lax
|
||||
- Strict
|
||||
type: string
|
||||
secure:
|
||||
description: Secure defines whether the cookie can
|
||||
@ -530,6 +533,9 @@ spec:
|
||||
- none
|
||||
- lax
|
||||
- strict
|
||||
- None
|
||||
- Lax
|
||||
- Strict
|
||||
type: string
|
||||
secure:
|
||||
description: Secure defines whether the cookie can
|
||||
@ -790,6 +796,9 @@ spec:
|
||||
- none
|
||||
- lax
|
||||
- strict
|
||||
- None
|
||||
- Lax
|
||||
- Strict
|
||||
type: string
|
||||
secure:
|
||||
description: Secure defines whether the cookie can
|
||||
@ -1156,6 +1165,9 @@ spec:
|
||||
- none
|
||||
- lax
|
||||
- strict
|
||||
- None
|
||||
- Lax
|
||||
- Strict
|
||||
type: string
|
||||
secure:
|
||||
description: Secure defines whether the cookie can
|
||||
@ -1305,6 +1317,9 @@ spec:
|
||||
- none
|
||||
- lax
|
||||
- strict
|
||||
- None
|
||||
- Lax
|
||||
- Strict
|
||||
type: string
|
||||
secure:
|
||||
description: Secure defines whether the cookie can only
|
||||
@ -1558,6 +1573,9 @@ spec:
|
||||
- none
|
||||
- lax
|
||||
- strict
|
||||
- None
|
||||
- Lax
|
||||
- Strict
|
||||
type: string
|
||||
secure:
|
||||
description: Secure defines whether the cookie can
|
||||
@ -1628,6 +1646,9 @@ spec:
|
||||
- none
|
||||
- lax
|
||||
- strict
|
||||
- None
|
||||
- Lax
|
||||
- Strict
|
||||
type: string
|
||||
secure:
|
||||
description: Secure defines whether the cookie can only
|
||||
|
||||
@ -39,7 +39,7 @@ This provider discovers all Ingresses in the cluster by default, which may lead
|
||||
**Best Practices:**
|
||||
|
||||
- Use IngressClass to specify which Ingresses should be handled by this provider
|
||||
- Configure `watchNamespace` to limit discovery to specific namespaces
|
||||
- Configure `watchNamespace` to limit discovery to a single namespace
|
||||
- Use `watchNamespaceSelector` to target Ingresses based on namespace labels
|
||||
|
||||
### IngressClass Selection Logic
|
||||
|
||||
@ -336,7 +336,7 @@ When set to a negative number, the cookie expires immediately.
|
||||
By default, the affinity cookie is created without those flags.
|
||||
One however can change that through configuration.
|
||||
|
||||
`SameSite` can be `none`, `lax`, `strict` or empty.
|
||||
`SameSite` can be `none`, `lax`, `strict` or empty. Values are case-insensitive.
|
||||
|
||||
#### Domain
|
||||
|
||||
|
||||
@ -54,9 +54,9 @@ spec:
|
||||
## Configuration Options
|
||||
|
||||
| Field | Description | Default | Required |
|
||||
|:-----------------------------------------------------------------------------------------------------------------------------------------------|:----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:--------|:---------|
|
||||
|:-----------------------------------------------------------------------------------------------------------------------------------------------|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:--------|:---------|
|
||||
| <a id="opt-address" href="#opt-address" title="#opt-address">`address`</a> | Authentication server address. | "" | Yes |
|
||||
| <a id="opt-trustForwardHeader" href="#opt-trustForwardHeader" title="#opt-trustForwardHeader">`trustForwardHeader`</a> | Trust all `X-Forwarded-*` headers. | false | No |
|
||||
| <a id="opt-trustForwardHeader" href="#opt-trustForwardHeader" title="#opt-trustForwardHeader">`trustForwardHeader`</a> | Trust all `X-Forwarded-*` headers. <br/>The trustForwardHeader option is deprecated and will be removed in the next major version. <br/>More information [here](#trustforwardheader)| false | No |
|
||||
| <a id="opt-authResponseHeaders" href="#opt-authResponseHeaders" title="#opt-authResponseHeaders">`authResponseHeaders`</a> | List of headers to copy from the authentication server response and set on forwarded request, replacing any existing conflicting headers. | [] | No |
|
||||
| <a id="opt-authResponseHeadersRegex" href="#opt-authResponseHeadersRegex" title="#opt-authResponseHeadersRegex">`authResponseHeadersRegex`</a> | Regex to match by the headers to copy from the authentication server response and set on forwarded request, after stripping all headers that match the regex.<br /> More information [here](#authresponseheadersregex). | "" | No |
|
||||
| <a id="opt-authRequestHeaders" href="#opt-authRequestHeaders" title="#opt-authRequestHeaders">`authRequestHeaders`</a> | List of the headers to copy from the request to the authentication server. <br /> It allows filtering headers that should not be passed to the authentication server. <br /> If not set or empty, then all request headers are passed. | [] | No |
|
||||
@ -128,6 +128,25 @@ If left unset, the request body size is unrestricted which can have performance
|
||||
It is strongly recommended to set this option to a suitable value.
|
||||
Not setting it (or setting it to `-1`) allows unlimited response body sizes which can lead to DoS attacks and memory exhaustion.
|
||||
|
||||
### trustForwardHeader
|
||||
|
||||
!!! warning
|
||||
|
||||
`trustForwardHeader` option is deprecated and will be removed in the next major version.
|
||||
|
||||
Configure the trusted IPs at the [EntryPoint level](../../../install-configuration/entrypoints.md#forwarded-headers) using `forwardedHeaders.trustedIPs`,
|
||||
and set `trustForwardHeader` to `true` on this middleware.
|
||||
|
||||
With this setup, the EntryPoint is responsible for sanitizing incoming `X-Forwarded-*` headers:
|
||||
it strips any such headers sent by untrusted clients and only preserves those coming from trusted upstream proxies.
|
||||
By the time the ForwardAuth middleware processes the request, all `X-Forwarded-*` headers are guaranteed to be trustworthy,
|
||||
including those intentionally added by other middlewares in the chain — for example, the `X-Forwarded-Prefix` header set by the [StripPrefix](stripprefix.md) middleware.
|
||||
|
||||
If `trustForwardHeader` is not explicitly set, Traefik will log a warning at startup and use a legacy behavior where some `X-Forwarded-*` headers (e.g. `X-Forwarded-For`, `X-Forwarded-Proto`) are removed but others (e.g. `X-Forwarded-Prefix`) are forwarded untouched.
|
||||
To silence this warning, explicitly set `trustForwardHeader` to `true` or `false`.
|
||||
|
||||
Set the `trustForwardHeader` option to `true` to trust all `X-Forwarded-*` headers.
|
||||
|
||||
## Forward-Request Headers
|
||||
|
||||
The following request properties are provided to the forward-auth target endpoint as `X-Forwarded-` headers.
|
||||
|
||||
@ -5,7 +5,7 @@ description: "An IngressRoute is a Traefik CRD is in charge of connecting incomi
|
||||
|
||||
`IngressRoute` is the CRD implementation of a [Traefik HTTP router](../../../http/routing/rules-and-priority.md).
|
||||
|
||||
Before creating `IngressRoute` objects, you need to apply the [Traefik Kubernetes CRDs](https://doc.traefik.io/traefik/reference/dynamic-configuration/kubernetes-crd/#definitions) to your Kubernetes cluster.
|
||||
Before creating `IngressRoute` objects, you need to apply the Traefik Kubernetes CRDs such as [Definitions](/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml) and [RBAC](/docs/content/reference/dynamic-configuration/kubernetes-crd-rbac.yml) to your Kubernetes cluster.
|
||||
|
||||
This registers the `IngressRoute` kind and other Traefik-specific resources.
|
||||
|
||||
|
||||
@ -157,7 +157,7 @@ data:
|
||||
| <a id="opt-sticky-cookie-name" href="#opt-sticky-cookie-name" title="#opt-sticky-cookie-name">`sticky.`<br />`cookie.name`</a> | Name of the cookie used for the stickiness at the WRR service level.<br />When sticky sessions are enabled, a `Set-Cookie` header is set on the initial response to let the client know which server handles the first response.<br />On subsequent requests, to keep the session alive with the same server, the client should send the cookie with the value set.<br />If the server pecified in the cookie becomes unhealthy, the request will be forwarded to a new server (and the cookie will keep track of the new server).<br />More information about WRR stickiness [here](#stickiness-on-multiple-levels) | Abbreviation of a sha1<br />(ex: `_1d52e`). | No |
|
||||
| <a id="opt-sticky-cookie-httpOnly" href="#opt-sticky-cookie-httpOnly" title="#opt-sticky-cookie-httpOnly">`sticky.`<br />`cookie.httpOnly`</a> | Allow the cookie used for the stickiness at the WRR service level to be accessed by client-side APIs, such as JavaScript.<br />More information about WRR stickiness [here](#stickiness-on-multiple-levels) | false | No |
|
||||
| <a id="opt-sticky-cookie-secure" href="#opt-sticky-cookie-secure" title="#opt-sticky-cookie-secure">`sticky.`<br />`cookie.secure`</a> | Allow the cookie used for the stickiness at the WRR service level to be only transmitted over an encrypted connection (i.e. HTTPS).<br />More information about WRR stickiness [here](#stickiness-on-multiple-levels) | false | No |
|
||||
| <a id="opt-sticky-cookie-sameSite" href="#opt-sticky-cookie-sameSite" title="#opt-sticky-cookie-sameSite">`sticky.`<br />`cookie.sameSite`</a> | [SameSite](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite) policy for the cookie used for the stickiness at the WRR service level.<br />Allowed values:<br />-`none`<br />-`lax`<br />`strict`<br />More information about WRR stickiness [here](#stickiness-on-multiple-levels) | "" | No |
|
||||
| <a id="opt-sticky-cookie-sameSite" href="#opt-sticky-cookie-sameSite" title="#opt-sticky-cookie-sameSite">`sticky.`<br />`cookie.sameSite`</a> | [SameSite](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite) policy for the cookie used for the stickiness at the WRR service level.<br />Allowed values:<br />-`none`<br />-`lax`<br />-`strict`<br />-`None`<br />-`Lax`<br />-`Strict`<br />More information about WRR stickiness [here](#stickiness-on-multiple-levels) | "" | No |
|
||||
| <a id="opt-sticky-cookie-maxAge" href="#opt-sticky-cookie-maxAge" title="#opt-sticky-cookie-maxAge">`sticky.`<br />`cookie.maxAge`</a> | Number of seconds until the cookie used for the stickiness at the WRR service level expires.<br />Negative number, the cookie expires immediately.<br />0, the cookie never expires. | 0 | No |
|
||||
|
||||
#### Stickiness on multiple levels
|
||||
@ -345,7 +345,7 @@ spec:
|
||||
| <a id="opt-servicesm-sticky-cookie-name" href="#opt-servicesm-sticky-cookie-name" title="#opt-servicesm-sticky-cookie-name">`services[m].`<br />`sticky.`<br />`cookie.name`</a> | Name of the cookie used for the stickiness.<br />Evaluated only if the kind is **Service**. | Abbreviation of a sha1<br />(ex: `_1d52e`). | No |
|
||||
| <a id="opt-servicesm-sticky-cookie-httpOnly" href="#opt-servicesm-sticky-cookie-httpOnly" title="#opt-servicesm-sticky-cookie-httpOnly">`services[m].`<br />`sticky.`<br />`cookie.httpOnly`</a> | Allow the cookie can be accessed by client-side APIs, such as JavaScript.<br />Evaluated only if the kind is **Service**. | false | No |
|
||||
| <a id="opt-servicesm-sticky-cookie-secure" href="#opt-servicesm-sticky-cookie-secure" title="#opt-servicesm-sticky-cookie-secure">`services[m].`<br />`sticky.`<br />`cookie.secure`</a> | Allow the cookie can only be transmitted over an encrypted connection (i.e. HTTPS).<br />Evaluated only if the kind is **Service**. | false | No |
|
||||
| <a id="opt-servicesm-sticky-cookie-sameSite" href="#opt-servicesm-sticky-cookie-sameSite" title="#opt-servicesm-sticky-cookie-sameSite">`services[m].`<br />`sticky.`<br />`cookie.sameSite`</a> | [SameSite](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite) policy.<br />Allowed values:<br />-`none`<br />-`lax`<br />`strict`<br />Evaluated only if the kind is **Service**. | "" | No |
|
||||
| <a id="opt-servicesm-sticky-cookie-sameSite" href="#opt-servicesm-sticky-cookie-sameSite" title="#opt-servicesm-sticky-cookie-sameSite">`services[m].`<br />`sticky.`<br />`cookie.sameSite`</a> | [SameSite](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite) policy.<br />Allowed values (case-insensitive):<br />-`none`<br />-`lax`<br />-`strict`<br />-`None`<br />-`Lax`<br />-`Strict`<br />Evaluated only if the kind is **Service**. | "" | No |
|
||||
| <a id="opt-servicesm-sticky-cookie-maxAge" href="#opt-servicesm-sticky-cookie-maxAge" title="#opt-servicesm-sticky-cookie-maxAge">`services[m].`<br />`sticky.`<br />`cookie.maxAge`</a> | Number of seconds until the cookie expires.<br />Negative number, the cookie expires immediately.<br />0, the cookie never expires.<br />Evaluated only if the kind is **Service**. | 0 | No |
|
||||
| <a id="opt-servicesm-strategy" href="#opt-servicesm-strategy" title="#opt-servicesm-strategy">`services[m].`<br />`strategy`</a> | Load balancing strategy between the servers.<br />RoundRobin is the only supported value yet.<br />Evaluated only if the kind is **Service**. | "RoundRobin" | No |
|
||||
| <a id="opt-servicesm-nativeLB" href="#opt-servicesm-nativeLB" title="#opt-servicesm-nativeLB">`services[m].`<br />`nativeLB`</a> | Allow using the Kubernetes Service load balancing between the pods instead of the one provided by Traefik.<br />Evaluated only if the kind is **Service**. | false | No |
|
||||
|
||||
@ -153,9 +153,9 @@ ingressClass:
|
||||
# Providers tell Traefik where to find routing configuration.
|
||||
providers:
|
||||
kubernetesIngress:
|
||||
enabled: false
|
||||
enabled: false
|
||||
kubernetesGateway:
|
||||
enabled: true
|
||||
enabled: true
|
||||
|
||||
## Gateway Listeners
|
||||
gateway:
|
||||
|
||||
155
go.mod
155
go.mod
@ -8,14 +8,15 @@ require (
|
||||
github.com/abbot/go-http-auth v0.0.0-00010101000000-000000000000 // No tag on the repo.
|
||||
github.com/andybalholm/brotli v1.2.0
|
||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5
|
||||
github.com/aws/aws-sdk-go-v2 v1.41.1
|
||||
github.com/aws/aws-sdk-go-v2/config v1.32.8
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.19.8
|
||||
github.com/aws/aws-sdk-go-v2 v1.41.5
|
||||
github.com/aws/aws-sdk-go-v2/config v1.32.14
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.19.14
|
||||
github.com/aws/aws-sdk-go-v2/service/ec2 v1.203.1
|
||||
github.com/aws/aws-sdk-go-v2/service/ecs v1.53.15
|
||||
github.com/aws/aws-sdk-go-v2/service/ssm v1.56.13
|
||||
github.com/aws/smithy-go v1.24.0
|
||||
github.com/aws/smithy-go v1.24.2
|
||||
github.com/cenkalti/backoff/v4 v4.3.0
|
||||
github.com/containerd/errdefs v1.0.0
|
||||
github.com/containous/alice v0.0.0-20181107144136-d83ebdd94cbd // No tag on the repo.
|
||||
github.com/coreos/go-systemd/v22 v22.5.0
|
||||
github.com/docker/cli v29.2.1+incompatible
|
||||
@ -23,7 +24,7 @@ require (
|
||||
github.com/docker/go-connections v0.6.0
|
||||
github.com/fatih/structs v1.1.0
|
||||
github.com/fsnotify/fsnotify v1.9.0
|
||||
github.com/go-acme/lego/v4 v4.33.0
|
||||
github.com/go-acme/lego/v4 v4.34.0
|
||||
github.com/go-kit/kit v0.13.0
|
||||
github.com/go-kit/log v0.2.1
|
||||
github.com/golang/protobuf v1.5.4
|
||||
@ -33,7 +34,7 @@ require (
|
||||
github.com/hashicorp/consul/api v1.26.1
|
||||
github.com/hashicorp/go-hclog v1.6.3
|
||||
github.com/hashicorp/go-retryablehttp v0.7.8
|
||||
github.com/hashicorp/go-version v1.8.0
|
||||
github.com/hashicorp/go-version v1.9.0
|
||||
github.com/hashicorp/nomad/api v0.0.0-20231213195942-64e3dca9274b // No tag on the repo.
|
||||
github.com/http-wasm/http-wasm-host-go v0.7.0
|
||||
github.com/huandu/xstrings v1.5.0
|
||||
@ -83,7 +84,7 @@ require (
|
||||
go.opentelemetry.io/collector/pdata v1.41.0
|
||||
go.opentelemetry.io/contrib/bridges/otellogrus v0.13.0
|
||||
go.opentelemetry.io/contrib/propagators/autoprop v0.63.0
|
||||
go.opentelemetry.io/otel v1.41.0
|
||||
go.opentelemetry.io/otel v1.43.0
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.17.0
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.17.0
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.41.0
|
||||
@ -92,20 +93,20 @@ require (
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.41.0
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.41.0
|
||||
go.opentelemetry.io/otel/log v0.17.0
|
||||
go.opentelemetry.io/otel/metric v1.41.0
|
||||
go.opentelemetry.io/otel/sdk v1.41.0
|
||||
go.opentelemetry.io/otel/metric v1.43.0
|
||||
go.opentelemetry.io/otel/sdk v1.43.0
|
||||
go.opentelemetry.io/otel/sdk/log v0.17.0
|
||||
go.opentelemetry.io/otel/sdk/metric v1.41.0
|
||||
go.opentelemetry.io/otel/trace v1.41.0
|
||||
golang.org/x/crypto v0.48.0
|
||||
golang.org/x/mod v0.32.0
|
||||
golang.org/x/net v0.51.0
|
||||
golang.org/x/sync v0.19.0
|
||||
golang.org/x/sys v0.41.0
|
||||
golang.org/x/text v0.34.0
|
||||
golang.org/x/time v0.14.0
|
||||
golang.org/x/tools v0.41.0
|
||||
google.golang.org/grpc v1.79.3
|
||||
go.opentelemetry.io/otel/sdk/metric v1.43.0
|
||||
go.opentelemetry.io/otel/trace v1.43.0
|
||||
golang.org/x/crypto v0.50.0
|
||||
golang.org/x/mod v0.35.0
|
||||
golang.org/x/net v0.53.0
|
||||
golang.org/x/sync v0.20.0
|
||||
golang.org/x/sys v0.43.0
|
||||
golang.org/x/text v0.36.0
|
||||
golang.org/x/time v0.15.0
|
||||
golang.org/x/tools v0.44.0
|
||||
google.golang.org/grpc v1.80.0
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
k8s.io/api v0.35.2
|
||||
@ -124,7 +125,7 @@ require (
|
||||
)
|
||||
|
||||
require (
|
||||
cloud.google.com/go/auth v0.18.1 // indirect
|
||||
cloud.google.com/go/auth v0.20.0 // indirect
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
|
||||
cloud.google.com/go/compute/metadata v0.9.0 // indirect
|
||||
dario.cat/mergo v1.0.2 // indirect
|
||||
@ -152,37 +153,38 @@ require (
|
||||
github.com/Masterminds/semver/v3 v3.4.0 // indirect
|
||||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||
github.com/VividCortex/gohistogram v1.0.0 // indirect
|
||||
github.com/akamai/AkamaiOPEN-edgegrid-golang/v11 v11.1.0 // indirect
|
||||
github.com/akamai/AkamaiOPEN-edgegrid-golang/v13 v13.1.0 // indirect
|
||||
github.com/alexbrainman/sspi v0.0.0-20180613141037-e580b900e9f5 // indirect
|
||||
github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.5 // indirect
|
||||
github.com/alibabacloud-go/darabonba-openapi/v2 v2.1.15 // indirect
|
||||
github.com/alibabacloud-go/darabonba-openapi/v2 v2.1.16 // indirect
|
||||
github.com/alibabacloud-go/debug v1.0.1 // indirect
|
||||
github.com/alibabacloud-go/tea v1.4.0 // indirect
|
||||
github.com/alibabacloud-go/tea-utils/v2 v2.0.7 // indirect
|
||||
github.com/alibabacloud-go/tea-utils/v2 v2.0.9 // indirect
|
||||
github.com/aliyun/credentials-go v1.4.7 // indirect
|
||||
github.com/armon/go-metrics v0.4.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.17 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.17 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.17 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.17 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/lightsail v1.50.11 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/route53 v1.62.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/signin v1.0.5 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.30.9 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.14 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.41.6 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.21 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.21 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.21 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.6 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.7 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.21 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/lightsail v1.53.0 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/route53 v1.62.5 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/signin v1.0.9 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.30.15 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.19 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.41.10 // indirect
|
||||
github.com/aziontech/azionapi-go-sdk v0.144.0 // indirect
|
||||
github.com/baidubce/bce-sdk-go v0.9.260 // indirect
|
||||
github.com/baidubce/bce-sdk-go v0.9.264 // indirect
|
||||
github.com/benbjohnson/clock v1.3.5 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/blendle/zapdriver v1.3.1 // indirect
|
||||
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect
|
||||
github.com/bodgit/tsig v1.2.2 // indirect
|
||||
github.com/boombuler/barcode v1.0.1 // indirect
|
||||
github.com/bytedance/sonic v1.12.0 // indirect
|
||||
github.com/cenkalti/backoff/v5 v5.0.3 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/clbanning/mxj/v2 v2.7.0 // indirect
|
||||
github.com/containerd/errdefs v1.0.0 // indirect
|
||||
github.com/containerd/errdefs/pkg v0.3.0 // indirect
|
||||
github.com/containerd/log v0.1.0 // indirect
|
||||
github.com/containerd/platforms v1.0.0-rc.1 // indirect
|
||||
@ -199,11 +201,11 @@ require (
|
||||
github.com/ebitengine/purego v0.8.4 // indirect
|
||||
github.com/emicklei/go-restful/v3 v3.13.0 // indirect
|
||||
github.com/evanphx/json-patch/v5 v5.9.11 // indirect
|
||||
github.com/exoscale/egoscale/v3 v3.1.33 // indirect
|
||||
github.com/exoscale/egoscale/v3 v3.1.34 // indirect
|
||||
github.com/fatih/color v1.18.0 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/fxamacker/cbor/v2 v2.9.0 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.13 // indirect
|
||||
github.com/ghodss/yaml v1.0.0 // indirect
|
||||
github.com/gin-gonic/gin v1.9.1 // indirect
|
||||
github.com/go-acme/alidns-20150109/v4 v4.7.0 // indirect
|
||||
@ -212,7 +214,7 @@ require (
|
||||
github.com/go-acme/tencentclouddnspod v1.3.24 // indirect
|
||||
github.com/go-acme/tencentedgdeone v1.3.38 // indirect
|
||||
github.com/go-errors/errors v1.0.1 // indirect
|
||||
github.com/go-jose/go-jose/v4 v4.1.3 // indirect
|
||||
github.com/go-jose/go-jose/v4 v4.1.4 // indirect
|
||||
github.com/go-logfmt/logfmt v0.5.1 // indirect
|
||||
github.com/go-logr/logr v1.4.3 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
@ -225,22 +227,22 @@ require (
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/go-playground/validator/v10 v10.23.0 // indirect
|
||||
github.com/go-resty/resty/v2 v2.17.1 // indirect
|
||||
github.com/go-resty/resty/v2 v2.17.2 // indirect
|
||||
github.com/go-viper/mapstructure/v2 v2.5.0 // indirect
|
||||
github.com/go-zookeeper/zk v1.0.3 // indirect
|
||||
github.com/goccy/go-yaml v1.18.0 // indirect
|
||||
github.com/goccy/go-yaml v1.19.2 // indirect
|
||||
github.com/gofrs/flock v0.13.0 // indirect
|
||||
github.com/gofrs/uuid v4.4.0+incompatible // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang-jwt/jwt/v4 v4.5.2 // indirect
|
||||
github.com/golang-jwt/jwt/v5 v5.3.0 // indirect
|
||||
github.com/golang-jwt/jwt/v5 v5.3.1 // indirect
|
||||
github.com/google/gnostic-models v0.7.0 // indirect
|
||||
github.com/google/go-cmp v0.7.0 // indirect
|
||||
github.com/google/go-querystring v1.2.0 // indirect
|
||||
github.com/google/s2a-go v0.1.9 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.11 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.17.0 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.14 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.21.0 // indirect
|
||||
github.com/gophercloud/gophercloud v1.14.1 // indirect
|
||||
github.com/gophercloud/utils v0.0.0-20231010081019-80377eca5d56 // indirect
|
||||
github.com/gravitational/trace v1.5.0 // indirect
|
||||
@ -256,11 +258,18 @@ require (
|
||||
github.com/hashicorp/golang-lru v1.0.2 // indirect
|
||||
github.com/hashicorp/hcl v1.0.1-vault-5 // indirect
|
||||
github.com/hashicorp/serf v0.10.1 // indirect
|
||||
github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.187 // indirect
|
||||
github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.192 // indirect
|
||||
github.com/iij/doapi v0.0.0-20190504054126-0bbf12d6d7df // indirect
|
||||
github.com/imdario/mergo v0.3.16 // indirect
|
||||
github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839 // indirect
|
||||
github.com/infobloxopen/infoblox-go-client/v2 v2.10.0 // indirect
|
||||
github.com/infobloxopen/infoblox-go-client/v2 v2.11.0 // indirect
|
||||
github.com/jcmturner/aescts/v2 v2.0.0 // indirect
|
||||
github.com/jcmturner/dnsutils/v2 v2.0.0 // indirect
|
||||
github.com/jcmturner/gofork v1.7.6 // indirect
|
||||
github.com/jcmturner/goidentity/v6 v6.0.1 // indirect
|
||||
github.com/jcmturner/gokrb5/v8 v8.4.4 // indirect
|
||||
github.com/jcmturner/rpc/v2 v2.0.3 // indirect
|
||||
github.com/jinzhu/copier v0.4.0 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/json-iterator/go v1.1.13-0.20220915233716-71ac16282d12 // indirect
|
||||
github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213 // indirect
|
||||
@ -270,8 +279,8 @@ require (
|
||||
github.com/labbsr0x/bindman-dns-webhook v1.0.2 // indirect
|
||||
github.com/labbsr0x/goh v1.0.1 // indirect
|
||||
github.com/leodido/go-urn v1.4.0 // indirect
|
||||
github.com/linode/linodego v1.65.0 // indirect
|
||||
github.com/liquidweb/liquidweb-cli v0.6.9 // indirect
|
||||
github.com/linode/linodego v1.67.0 // indirect
|
||||
github.com/liquidweb/liquidweb-cli v0.7.0 // indirect
|
||||
github.com/liquidweb/liquidweb-go v1.6.4 // indirect
|
||||
github.com/lufia/plan9stats v0.0.0-20250317134145-8bc96cf8fc35 // indirect
|
||||
github.com/magiconair/properties v1.8.10 // indirect
|
||||
@ -280,7 +289,7 @@ require (
|
||||
github.com/mailgun/timetools v0.0.0-20141028012446-7e6055773c51 // indirect
|
||||
github.com/mailru/easyjson v0.9.1 // indirect
|
||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-isatty v0.0.21 // indirect
|
||||
github.com/mimuret/golang-iij-dpf v0.9.1 // indirect
|
||||
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||
github.com/mitchellh/go-ps v1.0.0 // indirect
|
||||
@ -309,14 +318,15 @@ require (
|
||||
github.com/nrdcg/mailinabox v0.3.0 // indirect
|
||||
github.com/nrdcg/namesilo v0.5.0 // indirect
|
||||
github.com/nrdcg/nodion v0.1.0 // indirect
|
||||
github.com/nrdcg/oci-go-sdk/common/v1065 v1065.108.2 // indirect
|
||||
github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.108.2 // indirect
|
||||
github.com/nrdcg/oci-go-sdk/common/v1065 v1065.111.0 // indirect
|
||||
github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.111.0 // indirect
|
||||
github.com/nrdcg/porkbun v0.4.0 // indirect
|
||||
github.com/nrdcg/vegadns v0.3.0 // indirect
|
||||
github.com/nzdjb/go-metaname v1.0.0 // indirect
|
||||
github.com/onsi/ginkgo v1.16.5 // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/opencontainers/image-spec v1.1.1 // indirect
|
||||
github.com/openshift/gssapi v0.0.0-20161010215902-5fb4217df13b // indirect
|
||||
github.com/ovh/go-ovh v1.9.0 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
||||
github.com/peterhellberg/link v1.2.0 // indirect
|
||||
@ -330,7 +340,7 @@ require (
|
||||
github.com/regfish/regfish-dnsapi-go v0.1.1 // indirect
|
||||
github.com/rs/cors v1.7.0 // indirect
|
||||
github.com/rs/dnscache v0.0.0-20230804202142-fc85eb664529 // indirect
|
||||
github.com/sacloud/api-client-go v0.3.3 // indirect
|
||||
github.com/sacloud/api-client-go v0.3.5 // indirect
|
||||
github.com/sacloud/go-http v0.1.9 // indirect
|
||||
github.com/sacloud/iaas-api-go v1.23.1 // indirect
|
||||
github.com/sacloud/packages-go v0.0.12 // indirect
|
||||
@ -338,7 +348,7 @@ require (
|
||||
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
|
||||
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.36 // indirect
|
||||
github.com/selectel/domains-go v1.1.0 // indirect
|
||||
github.com/selectel/go-selvpcclient/v4 v4.1.0 // indirect
|
||||
github.com/selectel/go-selvpcclient/v4 v4.2.0 // indirect
|
||||
github.com/shirou/gopsutil/v4 v4.25.6 // indirect
|
||||
github.com/shopspring/decimal v1.4.0 // indirect
|
||||
github.com/softlayer/softlayer-go v1.2.1 // indirect
|
||||
@ -349,33 +359,34 @@ require (
|
||||
github.com/spf13/cast v1.7.0 // indirect
|
||||
github.com/spf13/pflag v1.0.10 // indirect
|
||||
github.com/spf13/viper v1.18.2 // indirect
|
||||
github.com/stretchr/objx v0.5.2 // indirect
|
||||
github.com/stretchr/objx v0.5.3 // indirect
|
||||
github.com/subosito/gotenv v1.6.0 // indirect
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.48 // indirect
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.77 // indirect
|
||||
github.com/tidwall/match v1.1.1 // indirect
|
||||
github.com/tidwall/pretty v1.2.1 // indirect
|
||||
github.com/tjfoc/gmsm v1.4.1 // indirect
|
||||
github.com/tklauser/go-sysconf v0.3.15 // indirect
|
||||
github.com/tklauser/numcpus v0.10.0 // indirect
|
||||
github.com/transip/gotransip/v6 v6.26.1 // indirect
|
||||
github.com/transip/gotransip/v6 v6.26.2 // indirect
|
||||
github.com/ucloud/ucloud-sdk-go v0.22.61 // indirect
|
||||
github.com/ultradns/ultradns-go-sdk v1.8.1-20250722213956-faef419 // indirect
|
||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||
github.com/vinyldns/go-vinyldns v0.9.17 // indirect
|
||||
github.com/volcengine/volc-sdk-golang v1.0.237 // indirect
|
||||
github.com/vultr/govultr/v3 v3.27.0 // indirect
|
||||
github.com/volcengine/volc-sdk-golang v1.0.241 // indirect
|
||||
github.com/vultr/govultr/v3 v3.30.0 // indirect
|
||||
github.com/x448/float16 v0.8.4 // indirect
|
||||
github.com/yandex-cloud/go-genproto v0.54.0 // indirect
|
||||
github.com/yandex-cloud/go-sdk/services/dns v0.0.36 // indirect
|
||||
github.com/yandex-cloud/go-sdk/v2 v2.56.0 // indirect
|
||||
github.com/yandex-cloud/go-genproto v0.71.0 // indirect
|
||||
github.com/yandex-cloud/go-sdk/services/dns v0.0.52 // indirect
|
||||
github.com/yandex-cloud/go-sdk/v2 v2.88.0 // indirect
|
||||
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
||||
go.etcd.io/etcd/api/v3 v3.6.5 // indirect
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.6.5 // indirect
|
||||
go.etcd.io/etcd/client/v3 v3.6.5 // indirect
|
||||
go.mongodb.org/mongo-driver v1.13.1 // indirect
|
||||
go.mongodb.org/mongo-driver v1.17.9 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
|
||||
go.opentelemetry.io/collector/featuregate v1.41.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 // indirect
|
||||
go.opentelemetry.io/contrib/propagators/aws v1.38.0 // indirect
|
||||
go.opentelemetry.io/contrib/propagators/b3 v1.38.0 // indirect
|
||||
go.opentelemetry.io/contrib/propagators/jaeger v1.38.0 // indirect
|
||||
@ -388,13 +399,13 @@ require (
|
||||
go.yaml.in/yaml/v2 v2.4.3 // indirect
|
||||
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
||||
golang.org/x/arch v0.4.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 // indirect
|
||||
golang.org/x/oauth2 v0.35.0 // indirect
|
||||
golang.org/x/term v0.40.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f // indirect
|
||||
golang.org/x/oauth2 v0.36.0 // indirect
|
||||
golang.org/x/term v0.42.0 // indirect
|
||||
gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect
|
||||
google.golang.org/api v0.267.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 // indirect
|
||||
google.golang.org/api v0.275.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260319201613-d00831a3d3e7 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9 // indirect
|
||||
google.golang.org/protobuf v1.36.11 // indirect
|
||||
gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
|
||||
@ -363,6 +363,9 @@ spec:
|
||||
- none
|
||||
- lax
|
||||
- strict
|
||||
- None
|
||||
- Lax
|
||||
- Strict
|
||||
type: string
|
||||
secure:
|
||||
description: Secure defines whether the cookie
|
||||
@ -1391,6 +1394,9 @@ spec:
|
||||
- none
|
||||
- lax
|
||||
- strict
|
||||
- None
|
||||
- Lax
|
||||
- Strict
|
||||
type: string
|
||||
secure:
|
||||
description: Secure defines whether the cookie can
|
||||
@ -1530,8 +1536,10 @@ spec:
|
||||
type: boolean
|
||||
type: object
|
||||
trustForwardHeader:
|
||||
description: 'TrustForwardHeader defines whether to trust (ie:
|
||||
forward) all X-Forwarded-* headers.'
|
||||
description: |-
|
||||
TrustForwardHeader defines whether to trust (ie: forward) all X-Forwarded-* headers.
|
||||
|
||||
Deprecated: Use forwardedHeaders.trustedIPs at the EntryPoint level instead, and set trustForwardHeader to true on this middleware.
|
||||
type: boolean
|
||||
type: object
|
||||
grpcWeb:
|
||||
@ -3203,6 +3211,9 @@ spec:
|
||||
- none
|
||||
- lax
|
||||
- strict
|
||||
- None
|
||||
- Lax
|
||||
- Strict
|
||||
type: string
|
||||
secure:
|
||||
description: Secure defines whether the cookie can
|
||||
@ -3450,6 +3461,9 @@ spec:
|
||||
- none
|
||||
- lax
|
||||
- strict
|
||||
- None
|
||||
- Lax
|
||||
- Strict
|
||||
type: string
|
||||
secure:
|
||||
description: Secure defines whether the cookie can
|
||||
@ -3710,6 +3724,9 @@ spec:
|
||||
- none
|
||||
- lax
|
||||
- strict
|
||||
- None
|
||||
- Lax
|
||||
- Strict
|
||||
type: string
|
||||
secure:
|
||||
description: Secure defines whether the cookie can
|
||||
@ -4076,6 +4093,9 @@ spec:
|
||||
- none
|
||||
- lax
|
||||
- strict
|
||||
- None
|
||||
- Lax
|
||||
- Strict
|
||||
type: string
|
||||
secure:
|
||||
description: Secure defines whether the cookie can
|
||||
@ -4225,6 +4245,9 @@ spec:
|
||||
- none
|
||||
- lax
|
||||
- strict
|
||||
- None
|
||||
- Lax
|
||||
- Strict
|
||||
type: string
|
||||
secure:
|
||||
description: Secure defines whether the cookie can only
|
||||
@ -4478,6 +4501,9 @@ spec:
|
||||
- none
|
||||
- lax
|
||||
- strict
|
||||
- None
|
||||
- Lax
|
||||
- Strict
|
||||
type: string
|
||||
secure:
|
||||
description: Secure defines whether the cookie can
|
||||
@ -4548,6 +4574,9 @@ spec:
|
||||
- none
|
||||
- lax
|
||||
- strict
|
||||
- None
|
||||
- Lax
|
||||
- Strict
|
||||
type: string
|
||||
secure:
|
||||
description: Secure defines whether the cookie can only
|
||||
|
||||
@ -339,7 +339,7 @@ type Cookie struct {
|
||||
HTTPOnly bool `json:"httpOnly,omitempty" toml:"httpOnly,omitempty" yaml:"httpOnly,omitempty" export:"true"`
|
||||
// SameSite defines the same site policy.
|
||||
// More info: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite
|
||||
// +kubebuilder:validation:Enum=none;lax;strict
|
||||
// +kubebuilder:validation:Enum=none;lax;strict;None;Lax;Strict
|
||||
SameSite string `json:"sameSite,omitempty" toml:"sameSite,omitempty" yaml:"sameSite,omitempty" export:"true"`
|
||||
// MaxAge defines the number of seconds until the cookie expires.
|
||||
// When set to a negative number, the cookie expires immediately.
|
||||
|
||||
@ -288,7 +288,9 @@ type ForwardAuth struct {
|
||||
// TLS defines the configuration used to secure the connection to the authentication server.
|
||||
TLS *ClientTLS `json:"tls,omitempty" toml:"tls,omitempty" yaml:"tls,omitempty" export:"true"`
|
||||
// TrustForwardHeader defines whether to trust (ie: forward) all X-Forwarded-* headers.
|
||||
TrustForwardHeader bool `json:"trustForwardHeader,omitempty" toml:"trustForwardHeader,omitempty" yaml:"trustForwardHeader,omitempty" export:"true"`
|
||||
//
|
||||
// Deprecated: Use forwardedHeaders.trustedIPs at the EntryPoint level instead, and set trustForwardHeader to true on this middleware.
|
||||
TrustForwardHeader *bool `json:"trustForwardHeader,omitempty" toml:"trustForwardHeader,omitempty" yaml:"trustForwardHeader,omitempty" export:"true"`
|
||||
// AuthResponseHeaders defines the list of headers to copy from the authentication server response and set on forwarded request, replacing any existing conflicting headers.
|
||||
AuthResponseHeaders []string `json:"authResponseHeaders,omitempty" toml:"authResponseHeaders,omitempty" yaml:"authResponseHeaders,omitempty" export:"true"`
|
||||
// AuthResponseHeadersRegex defines the regex to match headers to copy from the authentication server response and set on forwarded request, after stripping all headers that match the regex.
|
||||
|
||||
@ -473,6 +473,11 @@ func (in *ForwardAuth) DeepCopyInto(out *ForwardAuth) {
|
||||
*out = new(ClientTLS)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.TrustForwardHeader != nil {
|
||||
in, out := &in.TrustForwardHeader, &out.TrustForwardHeader
|
||||
*out = new(bool)
|
||||
**out = **in
|
||||
}
|
||||
if in.AuthResponseHeaders != nil {
|
||||
in, out := &in.AuthResponseHeaders, &out.AuthResponseHeaders
|
||||
*out = make([]string, len(*in))
|
||||
|
||||
@ -586,7 +586,7 @@ func TestDecodeConfiguration(t *testing.T) {
|
||||
InsecureSkipVerify: true,
|
||||
CAOptional: pointer(true),
|
||||
},
|
||||
TrustForwardHeader: true,
|
||||
TrustForwardHeader: pointer(true),
|
||||
AuthResponseHeaders: []string{
|
||||
"foobar",
|
||||
"fiibar",
|
||||
@ -1146,7 +1146,7 @@ func TestEncodeConfiguration(t *testing.T) {
|
||||
InsecureSkipVerify: true,
|
||||
CAOptional: pointer(true),
|
||||
},
|
||||
TrustForwardHeader: true,
|
||||
TrustForwardHeader: pointer(true),
|
||||
AuthResponseHeaders: []string{
|
||||
"foobar",
|
||||
"fiibar",
|
||||
|
||||
@ -469,8 +469,8 @@ func usernameIfPresent(theURL *url.URL) string {
|
||||
return "-"
|
||||
}
|
||||
|
||||
var requestCounter uint64 // Request ID
|
||||
var requestCounter atomic.Uint64 // Request ID
|
||||
|
||||
func nextRequestCount() uint64 {
|
||||
return atomic.AddUint64(&requestCounter, 1)
|
||||
return requestCounter.Add(1)
|
||||
}
|
||||
|
||||
@ -46,7 +46,7 @@ func NewBasic(ctx context.Context, next http.Handler, authConfig dynamic.BasicAu
|
||||
// To prevent timing attacks, we need to compute a hash even if the user is not found.
|
||||
// We assume it to be safe only when the users hashes are all from the same algorithm,
|
||||
// so we can pick the first one as a random hash to compute.
|
||||
notFoundSecret := users[slices.Collect(maps.Values(users))[0]]
|
||||
notFoundSecret := slices.Collect(maps.Values(users))[0]
|
||||
|
||||
ba := &basicAuth{
|
||||
next: next,
|
||||
|
||||
@ -16,6 +16,17 @@ import (
|
||||
"github.com/traefik/traefik/v3/pkg/testhelpers"
|
||||
)
|
||||
|
||||
func TestNewBasicNotFoundSecretIsSet(t *testing.T) {
|
||||
auth := dynamic.BasicAuth{
|
||||
Users: []string{"test:$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/"},
|
||||
}
|
||||
middleware, err := NewBasic(t.Context(), nil, auth, "authName")
|
||||
require.NoError(t, err)
|
||||
|
||||
ba := middleware.(*basicAuth)
|
||||
assert.Equal(t, "$apr1$H6uskkkW$IgXLP6ewTrSuBkTrqE8wj/", ba.notFoundSecret)
|
||||
}
|
||||
|
||||
func TestBasicAuthFail(t *testing.T) {
|
||||
next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprintln(w, "traefik")
|
||||
|
||||
@ -6,16 +6,19 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"maps"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/traefik/traefik/v3/pkg/config/dynamic"
|
||||
"github.com/traefik/traefik/v3/pkg/middlewares"
|
||||
"github.com/traefik/traefik/v3/pkg/middlewares/accesslog"
|
||||
"github.com/traefik/traefik/v3/pkg/middlewares/forwardedheaders"
|
||||
"github.com/traefik/traefik/v3/pkg/middlewares/observability"
|
||||
"github.com/traefik/traefik/v3/pkg/observability/tracing"
|
||||
"github.com/traefik/traefik/v3/pkg/proxy/httputil"
|
||||
@ -27,10 +30,7 @@ import (
|
||||
|
||||
const typeNameForward = "ForwardAuth"
|
||||
|
||||
const (
|
||||
xForwardedURI = "X-Forwarded-Uri"
|
||||
xForwardedMethod = "X-Forwarded-Method"
|
||||
)
|
||||
var errResponseBodyTooLarge = errors.New("response body too large")
|
||||
|
||||
// hopHeaders Hop-by-hop headers to be removed in the authentication request.
|
||||
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html
|
||||
@ -53,7 +53,7 @@ type forwardAuth struct {
|
||||
next http.Handler
|
||||
name string
|
||||
client http.Client
|
||||
trustForwardHeader bool
|
||||
trustForwardHeader *bool
|
||||
authRequestHeaders []string
|
||||
maxResponseBodySize int64
|
||||
addAuthCookiesToResponse map[string]struct{}
|
||||
@ -94,14 +94,14 @@ func NewForward(ctx context.Context, next http.Handler, config dynamic.ForwardAu
|
||||
if config.MaxBodySize != nil {
|
||||
fa.maxBodySize = *config.MaxBodySize
|
||||
} else if fa.forwardBody {
|
||||
logger.Warn().Msgf("ForwardAuth 'maxBodySize' is not configured with 'forwardBody: true', allowing unlimited request body size which can lead to DoS attacks and memory exhaustion. Please set an appropriate limit.")
|
||||
logger.Warn().Msgf("maxBodySize is not configured with 'forwardBody: true', allowing unlimited request body size which can lead to DoS attacks and memory exhaustion. Please set an appropriate limit.")
|
||||
}
|
||||
|
||||
if config.MaxResponseBodySize != nil {
|
||||
fa.maxResponseBodySize = *config.MaxResponseBodySize
|
||||
} else {
|
||||
fa.maxResponseBodySize = -1
|
||||
logger.Warn().Msg("ForwardAuth 'maxResponseBodySize' is not configured, allowing unlimited response body size which can lead to DoS attacks and memory exhaustion. Please set an appropriate limit.")
|
||||
logger.Warn().Msg("maxResponseBodySize is not configured, allowing unlimited response body size which can lead to DoS attacks and memory exhaustion. Please set an appropriate limit.")
|
||||
}
|
||||
|
||||
// Ensure our request client does not follow redirects
|
||||
@ -114,7 +114,7 @@ func NewForward(ctx context.Context, next http.Handler, config dynamic.ForwardAu
|
||||
|
||||
if config.TLS != nil {
|
||||
if config.TLS.CAOptional != nil {
|
||||
logger.Warn().Msg("CAOptional option is deprecated, TLS client authentication is a server side option, please remove any usage of this option.")
|
||||
logger.Warn().Msg("tls.CAOptional option is deprecated, TLS client authentication is a server side option, please remove any usage of this option.")
|
||||
}
|
||||
|
||||
clientTLS := &types.ClientTLS{
|
||||
@ -142,6 +142,12 @@ func NewForward(ctx context.Context, next http.Handler, config dynamic.ForwardAu
|
||||
fa.authResponseHeadersRegex = re
|
||||
}
|
||||
|
||||
if config.TrustForwardHeader == nil {
|
||||
logger.Warn().Msg("trustForwardHeader is not configured: this creates an inconsistent security behavior where some X-Forwarded headers (e.g. X-Forwarded-For, X-Forwarded-Proto) are removed but others (e.g. X-Forwarded-Prefix) are forwarded untouched. Please set it to false to remove all X-Forwarded headers, or true to trust them all.")
|
||||
} else if *config.TrustForwardHeader && len(fa.authRequestHeaders) > 0 {
|
||||
fa.authRequestHeaders = append(fa.authRequestHeaders, slices.Collect(maps.Keys(forwardedheaders.XHeadersSet))...)
|
||||
}
|
||||
|
||||
return fa, nil
|
||||
}
|
||||
|
||||
@ -190,7 +196,11 @@ func (fa *forwardAuth) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
writeHeader(req, forwardReq, fa.trustForwardHeader, fa.authRequestHeaders)
|
||||
if fa.trustForwardHeader != nil {
|
||||
writeHeader(req, forwardReq, *fa.trustForwardHeader, fa.authRequestHeaders)
|
||||
} else {
|
||||
oldWriteHeader(req, forwardReq, fa.authRequestHeaders)
|
||||
}
|
||||
|
||||
var forwardSpan trace.Span
|
||||
var tracer *tracing.Tracer
|
||||
@ -380,8 +390,6 @@ func (fa *forwardAuth) readBodyBytes(req *http.Request) ([]byte, error) {
|
||||
return nil, errBodyTooLarge
|
||||
}
|
||||
|
||||
var errResponseBodyTooLarge = errors.New("response body too large")
|
||||
|
||||
func (fa *forwardAuth) readResponseBodyBytes(res *http.Response) ([]byte, error) {
|
||||
if fa.maxResponseBodySize < 0 {
|
||||
return io.ReadAll(res.Body)
|
||||
@ -407,6 +415,10 @@ func writeHeader(req, forwardReq *http.Request, trustForwardHeader bool, allowed
|
||||
RemoveConnectionHeaders(forwardReq)
|
||||
utils.RemoveHeaders(forwardReq.Header, hopHeaders...)
|
||||
|
||||
if !trustForwardHeader {
|
||||
forwardedheaders.DeleteXForwardedHeaders(forwardReq.Header)
|
||||
}
|
||||
|
||||
if _, ok := req.Header[userAgentHeader]; !ok {
|
||||
// If the incoming request doesn't have a User-Agent header set,
|
||||
// don't send the default Go HTTP client User-Agent for the forwarded request.
|
||||
@ -416,59 +428,61 @@ func writeHeader(req, forwardReq *http.Request, trustForwardHeader bool, allowed
|
||||
forwardReq.Header = filterForwardRequestHeaders(forwardReq.Header, allowedHeaders)
|
||||
|
||||
if clientIP, _, err := net.SplitHostPort(req.RemoteAddr); err == nil {
|
||||
if trustForwardHeader {
|
||||
if prior, ok := req.Header[forward.XForwardedFor]; ok {
|
||||
clientIP = strings.Join(prior, ", ") + ", " + clientIP
|
||||
}
|
||||
if prior, ok := forwardReq.Header[forwardedheaders.XForwardedFor]; ok {
|
||||
clientIP = strings.Join(prior, ", ") + ", " + clientIP
|
||||
}
|
||||
forwardReq.Header.Set(forward.XForwardedFor, clientIP)
|
||||
forwardReq.Header.Set(forwardedheaders.XForwardedFor, clientIP)
|
||||
}
|
||||
|
||||
xMethod := req.Header.Get(xForwardedMethod)
|
||||
switch {
|
||||
case xMethod != "" && trustForwardHeader:
|
||||
forwardReq.Header.Set(xForwardedMethod, xMethod)
|
||||
case req.Method != "":
|
||||
forwardReq.Header.Set(xForwardedMethod, req.Method)
|
||||
default:
|
||||
forwardReq.Header.Del(xForwardedMethod)
|
||||
if _, ok := forwardReq.Header[forwardedheaders.XForwardedMethod]; !ok {
|
||||
forwardReq.Header.Set(forwardedheaders.XForwardedMethod, req.Method)
|
||||
}
|
||||
|
||||
xfp := req.Header.Get(forward.XForwardedProto)
|
||||
switch {
|
||||
case xfp != "" && trustForwardHeader:
|
||||
forwardReq.Header.Set(forward.XForwardedProto, xfp)
|
||||
case req.TLS != nil:
|
||||
forwardReq.Header.Set(forward.XForwardedProto, "https")
|
||||
default:
|
||||
forwardReq.Header.Set(forward.XForwardedProto, "http")
|
||||
if _, ok := forwardReq.Header[forwardedheaders.XForwardedProto]; !ok {
|
||||
forwardReq.Header.Set(forwardedheaders.XForwardedProto, "http")
|
||||
if req.TLS != nil {
|
||||
forwardReq.Header.Set(forwardedheaders.XForwardedProto, "https")
|
||||
}
|
||||
}
|
||||
|
||||
if xfp := req.Header.Get(forward.XForwardedPort); xfp != "" && trustForwardHeader {
|
||||
forwardReq.Header.Set(forward.XForwardedPort, xfp)
|
||||
if _, ok := forwardReq.Header[forwardedheaders.XForwardedPort]; !ok {
|
||||
forwardReq.Header.Set(forwardedheaders.XForwardedPort, forwardedPort(req))
|
||||
}
|
||||
|
||||
xfh := req.Header.Get(forward.XForwardedHost)
|
||||
switch {
|
||||
case xfh != "" && trustForwardHeader:
|
||||
forwardReq.Header.Set(forward.XForwardedHost, xfh)
|
||||
case req.Host != "":
|
||||
forwardReq.Header.Set(forward.XForwardedHost, req.Host)
|
||||
default:
|
||||
forwardReq.Header.Del(forward.XForwardedHost)
|
||||
if _, ok := forwardReq.Header[forwardedheaders.XForwardedHost]; !ok {
|
||||
forwardReq.Header.Set(forwardedheaders.XForwardedHost, req.Host)
|
||||
}
|
||||
|
||||
xfURI := req.Header.Get(xForwardedURI)
|
||||
switch {
|
||||
case xfURI != "" && trustForwardHeader:
|
||||
forwardReq.Header.Set(xForwardedURI, xfURI)
|
||||
case req.URL.RequestURI() != "":
|
||||
forwardReq.Header.Set(xForwardedURI, req.URL.RequestURI())
|
||||
default:
|
||||
forwardReq.Header.Del(xForwardedURI)
|
||||
if _, ok := forwardReq.Header[forwardedheaders.XForwardedURI]; !ok {
|
||||
forwardReq.Header.Set(forwardedheaders.XForwardedURI, req.URL.RequestURI())
|
||||
}
|
||||
}
|
||||
|
||||
// oldWriteHeader is the legacy implementation of writeHeader, which is used when TrustForwardHeader is not set (old false behavior).
|
||||
// It is kept to avoid breaking existing configurations that rely on the previous behavior.
|
||||
func oldWriteHeader(req, forwardReq *http.Request, allowedHeaders []string) {
|
||||
utils.CopyHeaders(forwardReq.Header, req.Header)
|
||||
|
||||
RemoveConnectionHeaders(forwardReq)
|
||||
utils.RemoveHeaders(forwardReq.Header, hopHeaders...)
|
||||
|
||||
forwardReq.Header = filterForwardRequestHeaders(forwardReq.Header, allowedHeaders)
|
||||
|
||||
if clientIP, _, err := net.SplitHostPort(req.RemoteAddr); err == nil {
|
||||
forwardReq.Header.Set(forwardedheaders.XForwardedFor, clientIP)
|
||||
}
|
||||
|
||||
proto := "http"
|
||||
if req.TLS != nil {
|
||||
proto = "https"
|
||||
}
|
||||
forwardReq.Header.Set(forwardedheaders.XForwardedProto, proto)
|
||||
|
||||
forwardReq.Header.Set(forwardedheaders.XForwardedMethod, req.Method)
|
||||
forwardReq.Header.Set(forwardedheaders.XForwardedHost, req.Host)
|
||||
forwardReq.Header.Set(forwardedheaders.XForwardedURI, req.URL.RequestURI())
|
||||
}
|
||||
|
||||
func filterForwardRequestHeaders(forwardRequestHeaders http.Header, allowedHeaders []string) http.Header {
|
||||
if len(allowedHeaders) == 0 {
|
||||
return forwardRequestHeaders
|
||||
@ -476,11 +490,30 @@ func filterForwardRequestHeaders(forwardRequestHeaders http.Header, allowedHeade
|
||||
|
||||
filteredHeaders := http.Header{}
|
||||
for _, headerName := range allowedHeaders {
|
||||
values := forwardRequestHeaders.Values(headerName)
|
||||
if len(values) > 0 {
|
||||
filteredHeaders[http.CanonicalHeaderKey(headerName)] = append([]string(nil), values...)
|
||||
if values := forwardRequestHeaders.Values(headerName); len(values) > 0 {
|
||||
filteredHeaders[http.CanonicalHeaderKey(headerName)] = values
|
||||
}
|
||||
}
|
||||
|
||||
return filteredHeaders
|
||||
}
|
||||
|
||||
func forwardedPort(req *http.Request) string {
|
||||
if req == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
if _, port, err := net.SplitHostPort(req.Host); err == nil && port != "" {
|
||||
return port
|
||||
}
|
||||
|
||||
if req.Header.Get(forwardedheaders.XForwardedProto) == "https" || req.Header.Get(forwardedheaders.XForwardedProto) == "wss" {
|
||||
return "443"
|
||||
}
|
||||
|
||||
if req.TLS != nil {
|
||||
return "443"
|
||||
}
|
||||
|
||||
return "80"
|
||||
}
|
||||
|
||||
@ -480,9 +480,9 @@ func TestForwardAuthForwardError(t *testing.T) {
|
||||
assert.Equal(t, http.StatusInternalServerError, recorder.Result().StatusCode)
|
||||
}
|
||||
|
||||
func Test_writeHeader(t *testing.T) {
|
||||
func TestForwardAuth_writeHeader(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
desc string
|
||||
headers map[string]string
|
||||
authRequestHeaders []string
|
||||
trustForwardHeader bool
|
||||
@ -491,7 +491,7 @@ func Test_writeHeader(t *testing.T) {
|
||||
checkForUnexpectedHeaders bool
|
||||
}{
|
||||
{
|
||||
name: "trust Forward Header",
|
||||
desc: "trust forward header",
|
||||
headers: map[string]string{
|
||||
"Accept": "application/json",
|
||||
"X-Forwarded-Host": "fii.bir",
|
||||
@ -503,7 +503,7 @@ func Test_writeHeader(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "not trust Forward Header",
|
||||
desc: "not trust forward header",
|
||||
headers: map[string]string{
|
||||
"Accept": "application/json",
|
||||
"X-Forwarded-Host": "fii.bir",
|
||||
@ -515,7 +515,7 @@ func Test_writeHeader(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "trust Forward Header with empty Host",
|
||||
desc: "trust forward header with empty Host",
|
||||
headers: map[string]string{
|
||||
"Accept": "application/json",
|
||||
"X-Forwarded-Host": "fii.bir",
|
||||
@ -528,7 +528,7 @@ func Test_writeHeader(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "not trust Forward Header with empty Host",
|
||||
desc: "not trust forward header with empty Host",
|
||||
headers: map[string]string{
|
||||
"Accept": "application/json",
|
||||
"X-Forwarded-Host": "fii.bir",
|
||||
@ -540,7 +540,7 @@ func Test_writeHeader(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "trust Forward Header with forwarded URI",
|
||||
desc: "trust forward header with forwarded URI",
|
||||
headers: map[string]string{
|
||||
"Accept": "application/json",
|
||||
"X-Forwarded-Host": "fii.bir",
|
||||
@ -554,7 +554,7 @@ func Test_writeHeader(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "not trust Forward Header with forward requested URI",
|
||||
desc: "not trust forward header with forward requested URI",
|
||||
headers: map[string]string{
|
||||
"Accept": "application/json",
|
||||
"X-Forwarded-Host": "fii.bir",
|
||||
@ -568,7 +568,7 @@ func Test_writeHeader(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "trust Forward Header with forwarded request Method",
|
||||
desc: "trust forward header with forwarded request Method",
|
||||
headers: map[string]string{
|
||||
"X-Forwarded-Method": "OPTIONS",
|
||||
},
|
||||
@ -578,7 +578,7 @@ func Test_writeHeader(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "not trust Forward Header with forward request Method",
|
||||
desc: "not trust forward header with forward request Method",
|
||||
headers: map[string]string{
|
||||
"X-Forwarded-Method": "OPTIONS",
|
||||
},
|
||||
@ -588,7 +588,7 @@ func Test_writeHeader(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "remove hop-by-hop headers",
|
||||
desc: "remove hop-by-hop headers",
|
||||
headers: map[string]string{
|
||||
forward.Connection: "Connection",
|
||||
forward.KeepAlive: "KeepAlive",
|
||||
@ -607,6 +607,7 @@ func Test_writeHeader(t *testing.T) {
|
||||
"X-Forwarded-Host": "foo.bar",
|
||||
"X-Forwarded-Uri": "/path?q=1",
|
||||
"X-Forwarded-Method": "GET",
|
||||
"X-Forwarded-Port": "80",
|
||||
forward.ProxyAuthenticate: "ProxyAuthenticate",
|
||||
forward.ProxyAuthorization: "ProxyAuthorization",
|
||||
"User-Agent": "",
|
||||
@ -614,7 +615,7 @@ func Test_writeHeader(t *testing.T) {
|
||||
checkForUnexpectedHeaders: true,
|
||||
},
|
||||
{
|
||||
name: "filter forward request headers",
|
||||
desc: "filter forward request headers",
|
||||
headers: map[string]string{
|
||||
"X-CustomHeader": "CustomHeader",
|
||||
"Content-Type": "multipart/form-data; boundary=---123456",
|
||||
@ -629,11 +630,12 @@ func Test_writeHeader(t *testing.T) {
|
||||
"X-Forwarded-Host": "foo.bar",
|
||||
"X-Forwarded-Uri": "/path?q=1",
|
||||
"X-Forwarded-Method": "GET",
|
||||
"X-Forwarded-Port": "80",
|
||||
},
|
||||
checkForUnexpectedHeaders: true,
|
||||
},
|
||||
{
|
||||
name: "filter forward request headers doesn't add new headers",
|
||||
desc: "filter forward request headers doesn't add new headers",
|
||||
headers: map[string]string{
|
||||
"X-CustomHeader": "CustomHeader",
|
||||
"Content-Type": "multipart/form-data; boundary=---123456",
|
||||
@ -649,11 +651,12 @@ func Test_writeHeader(t *testing.T) {
|
||||
"X-Forwarded-Host": "foo.bar",
|
||||
"X-Forwarded-Uri": "/path?q=1",
|
||||
"X-Forwarded-Method": "GET",
|
||||
"X-Forwarded-Port": "80",
|
||||
},
|
||||
checkForUnexpectedHeaders: true,
|
||||
},
|
||||
{
|
||||
name: "set empty User-Agent header if header is allowed but missing",
|
||||
desc: "set empty User-Agent header if header is allowed but missing",
|
||||
headers: map[string]string{
|
||||
"X-CustomHeader": "CustomHeader",
|
||||
"Accept": "application/json",
|
||||
@ -670,12 +673,13 @@ func Test_writeHeader(t *testing.T) {
|
||||
"X-Forwarded-Host": "foo.bar",
|
||||
"X-Forwarded-Uri": "/path?q=1",
|
||||
"X-Forwarded-Method": "GET",
|
||||
"X-Forwarded-Port": "80",
|
||||
"User-Agent": "",
|
||||
},
|
||||
checkForUnexpectedHeaders: true,
|
||||
},
|
||||
{
|
||||
name: "ignore User-Agent header if header is not allowed and missing",
|
||||
desc: "ignore User-Agent header if header is not allowed and missing",
|
||||
headers: map[string]string{
|
||||
"X-CustomHeader": "CustomHeader",
|
||||
"Accept": "application/json",
|
||||
@ -691,11 +695,12 @@ func Test_writeHeader(t *testing.T) {
|
||||
"X-Forwarded-Host": "foo.bar",
|
||||
"X-Forwarded-Uri": "/path?q=1",
|
||||
"X-Forwarded-Method": "GET",
|
||||
"X-Forwarded-Port": "80",
|
||||
},
|
||||
checkForUnexpectedHeaders: true,
|
||||
},
|
||||
{
|
||||
name: "set empty User-Agent header if header is missing",
|
||||
desc: "set empty User-Agent header if header is missing",
|
||||
headers: map[string]string{
|
||||
"X-CustomHeader": "CustomHeader",
|
||||
"Accept": "application/json",
|
||||
@ -707,14 +712,64 @@ func Test_writeHeader(t *testing.T) {
|
||||
"X-Forwarded-Host": "foo.bar",
|
||||
"X-Forwarded-Uri": "/path?q=1",
|
||||
"X-Forwarded-Method": "GET",
|
||||
"X-Forwarded-Port": "80",
|
||||
"User-Agent": "",
|
||||
},
|
||||
checkForUnexpectedHeaders: true,
|
||||
},
|
||||
{
|
||||
desc: "authRequestHeaders and XForwarded are kept if trusted",
|
||||
headers: map[string]string{
|
||||
"X-CustomHeader": "CustomHeader",
|
||||
"X-Forwarded-Uri": "/path?q=2",
|
||||
},
|
||||
authRequestHeaders: []string{
|
||||
"X-CustomHeader",
|
||||
},
|
||||
trustForwardHeader: true,
|
||||
expectedHeaders: map[string]string{
|
||||
"X-CustomHeader": "CustomHeader",
|
||||
"X-Forwarded-Proto": "http",
|
||||
"X-Forwarded-Host": "foo.bar",
|
||||
"X-Forwarded-Uri": "/path?q=2",
|
||||
"X-Forwarded-Method": "GET",
|
||||
"X-Forwarded-Port": "80",
|
||||
},
|
||||
checkForUnexpectedHeaders: true,
|
||||
},
|
||||
{
|
||||
desc: "X-Forwarded and X_forwarded headers are removed when not trusted",
|
||||
headers: map[string]string{
|
||||
"X-CustomHeader": "CustomHeader",
|
||||
"X_forwarded_for": "127.0.0.1",
|
||||
"X-Forwarded-Proto": "xxx",
|
||||
},
|
||||
trustForwardHeader: false,
|
||||
expectedHeaders: map[string]string{
|
||||
"X-CustomHeader": "CustomHeader",
|
||||
"X-Forwarded-Proto": "http",
|
||||
"X-Forwarded-Host": "foo.bar",
|
||||
"X-Forwarded-Uri": "/path?q=1",
|
||||
"X-Forwarded-Method": "GET",
|
||||
"X-Forwarded-Port": "80",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cfg := dynamic.ForwardAuth{
|
||||
TrustForwardHeader: &test.trustForwardHeader,
|
||||
AuthRequestHeaders: test.authRequestHeaders,
|
||||
}
|
||||
hdl, err := NewForward(t.Context(), nil, cfg, "test")
|
||||
require.NoError(t, err)
|
||||
|
||||
fwdAuth, ok := hdl.(*forwardAuth)
|
||||
require.True(t, ok)
|
||||
|
||||
req := testhelpers.MustNewRequest(http.MethodGet, "http://foo.bar/path?q=1", nil)
|
||||
for key, value := range test.headers {
|
||||
req.Header.Set(key, value)
|
||||
@ -726,7 +781,7 @@ func Test_writeHeader(t *testing.T) {
|
||||
|
||||
forwardReq := testhelpers.MustNewRequest(http.MethodGet, "http://foo.bar/path?q=1", nil)
|
||||
|
||||
writeHeader(req, forwardReq, test.trustForwardHeader, test.authRequestHeaders)
|
||||
writeHeader(req, forwardReq, *fwdAuth.trustForwardHeader, fwdAuth.authRequestHeaders)
|
||||
|
||||
actualHeaders := forwardReq.Header
|
||||
|
||||
@ -735,13 +790,184 @@ func Test_writeHeader(t *testing.T) {
|
||||
_, headerExists := actualHeaders[http.CanonicalHeaderKey(key)]
|
||||
|
||||
assert.True(t, headerExists, "Expected header %s not found", key)
|
||||
assert.Equal(t, value, actualHeaders.Get(key))
|
||||
assert.Equal(t, value, forwardReq.Header.Get(key))
|
||||
|
||||
actualHeaders.Del(key)
|
||||
forwardReq.Header.Del(key)
|
||||
}
|
||||
|
||||
if test.checkForUnexpectedHeaders {
|
||||
for key := range actualHeaders {
|
||||
for key := range forwardReq.Header {
|
||||
assert.Fail(t, "Unexpected header found", key)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestForwardAuth_oldWriteHeader(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
headers map[string]string
|
||||
authRequestHeaders []string
|
||||
emptyHost bool
|
||||
expectedHeaders map[string]string
|
||||
checkForUnexpectedHeaders bool
|
||||
}{
|
||||
{
|
||||
desc: "not trust forward header",
|
||||
headers: map[string]string{
|
||||
"Accept": "application/json",
|
||||
"X-Forwarded-Host": "fii.bir",
|
||||
},
|
||||
expectedHeaders: map[string]string{
|
||||
"Accept": "application/json",
|
||||
"X-Forwarded-Host": "foo.bar",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "not trust forward header with empty Host",
|
||||
headers: map[string]string{
|
||||
"Accept": "application/json",
|
||||
"X-Forwarded-Host": "fii.bir",
|
||||
},
|
||||
emptyHost: true,
|
||||
expectedHeaders: map[string]string{
|
||||
"Accept": "application/json",
|
||||
"X-Forwarded-Host": "",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "not trust forward header with forward requested URI",
|
||||
headers: map[string]string{
|
||||
"Accept": "application/json",
|
||||
"X-Forwarded-Host": "fii.bir",
|
||||
"X-Forwarded-Uri": "/forward?q=1",
|
||||
},
|
||||
expectedHeaders: map[string]string{
|
||||
"Accept": "application/json",
|
||||
"X-Forwarded-Host": "foo.bar",
|
||||
"X-Forwarded-Uri": "/path?q=1",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "not trust forward header with forward request Method",
|
||||
headers: map[string]string{
|
||||
"X-Forwarded-Method": "OPTIONS",
|
||||
},
|
||||
expectedHeaders: map[string]string{
|
||||
"X-Forwarded-Method": "GET",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "remove hop-by-hop headers",
|
||||
headers: map[string]string{
|
||||
forward.Connection: "Connection",
|
||||
forward.KeepAlive: "KeepAlive",
|
||||
forward.ProxyAuthenticate: "ProxyAuthenticate",
|
||||
forward.ProxyAuthorization: "ProxyAuthorization",
|
||||
forward.Te: "Te",
|
||||
forward.Trailers: "Trailers",
|
||||
forward.TransferEncoding: "TransferEncoding",
|
||||
forward.Upgrade: "Upgrade",
|
||||
"X-CustomHeader": "CustomHeader",
|
||||
},
|
||||
expectedHeaders: map[string]string{
|
||||
"X-CustomHeader": "CustomHeader",
|
||||
"X-Forwarded-Proto": "http",
|
||||
"X-Forwarded-Host": "foo.bar",
|
||||
"X-Forwarded-Uri": "/path?q=1",
|
||||
"X-Forwarded-Method": "GET",
|
||||
forward.ProxyAuthenticate: "ProxyAuthenticate",
|
||||
forward.ProxyAuthorization: "ProxyAuthorization",
|
||||
},
|
||||
checkForUnexpectedHeaders: true,
|
||||
},
|
||||
{
|
||||
desc: "filter forward request headers",
|
||||
headers: map[string]string{
|
||||
"X-CustomHeader": "CustomHeader",
|
||||
"Content-Type": "multipart/form-data; boundary=---123456",
|
||||
},
|
||||
authRequestHeaders: []string{
|
||||
"X-CustomHeader",
|
||||
},
|
||||
expectedHeaders: map[string]string{
|
||||
"x-customHeader": "CustomHeader",
|
||||
"X-Forwarded-Proto": "http",
|
||||
"X-Forwarded-Host": "foo.bar",
|
||||
"X-Forwarded-Uri": "/path?q=1",
|
||||
"X-Forwarded-Method": "GET",
|
||||
},
|
||||
checkForUnexpectedHeaders: true,
|
||||
},
|
||||
{
|
||||
desc: "filter forward request headers doesn't add new headers",
|
||||
headers: map[string]string{
|
||||
"X-CustomHeader": "CustomHeader",
|
||||
"Content-Type": "multipart/form-data; boundary=---123456",
|
||||
},
|
||||
authRequestHeaders: []string{
|
||||
"X-CustomHeader",
|
||||
"X-Non-Exists-Header",
|
||||
},
|
||||
expectedHeaders: map[string]string{
|
||||
"X-CustomHeader": "CustomHeader",
|
||||
"X-Forwarded-Proto": "http",
|
||||
"X-Forwarded-Host": "foo.bar",
|
||||
"X-Forwarded-Uri": "/path?q=1",
|
||||
"X-Forwarded-Method": "GET",
|
||||
},
|
||||
checkForUnexpectedHeaders: true,
|
||||
},
|
||||
|
||||
{
|
||||
desc: "X-Forwarded-Prefix is kept for non-breaking behavior",
|
||||
headers: map[string]string{
|
||||
"X-CustomHeader": "CustomHeader",
|
||||
"X-Forwarded-Prefix": "foo.bar",
|
||||
},
|
||||
expectedHeaders: map[string]string{
|
||||
"X-CustomHeader": "CustomHeader",
|
||||
"X-Forwarded-Proto": "http",
|
||||
"X-Forwarded-Host": "foo.bar",
|
||||
"X-Forwarded-Uri": "/path?q=1",
|
||||
"X-Forwarded-Method": "GET",
|
||||
"X-Forwarded-Prefix": "foo.bar",
|
||||
},
|
||||
checkForUnexpectedHeaders: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
hdl, err := NewForward(t.Context(), nil, dynamic.ForwardAuth{AuthRequestHeaders: test.authRequestHeaders}, "test")
|
||||
require.NoError(t, err)
|
||||
|
||||
fwdAuth, ok := hdl.(*forwardAuth)
|
||||
require.True(t, ok)
|
||||
|
||||
req := testhelpers.MustNewRequest(http.MethodGet, "http://foo.bar/path?q=1", nil)
|
||||
for key, value := range test.headers {
|
||||
req.Header.Set(key, value)
|
||||
}
|
||||
|
||||
if test.emptyHost {
|
||||
req.Host = ""
|
||||
}
|
||||
|
||||
forwardReq := testhelpers.MustNewRequest(http.MethodGet, "http://foo.bar/path?q=1", nil)
|
||||
|
||||
oldWriteHeader(req, forwardReq, fwdAuth.authRequestHeaders)
|
||||
|
||||
expectedHeaders := test.expectedHeaders
|
||||
for key, value := range expectedHeaders {
|
||||
assert.Equal(t, value, forwardReq.Header.Get(key))
|
||||
forwardReq.Header.Del(key)
|
||||
}
|
||||
if test.checkForUnexpectedHeaders {
|
||||
for key := range forwardReq.Header {
|
||||
assert.Fail(t, "Unexpected header found", key)
|
||||
}
|
||||
}
|
||||
@ -936,9 +1162,9 @@ func TestForwardAuthPreserveRequestMethod(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func Test_ForwardAuthMaxResponseBodySize(t *testing.T) {
|
||||
func TestForwardAuthMaxResponseBodySize(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
desc string
|
||||
maxResponseBodySize int64
|
||||
status int
|
||||
body string
|
||||
@ -946,7 +1172,7 @@ func Test_ForwardAuthMaxResponseBodySize(t *testing.T) {
|
||||
expectedBody string
|
||||
}{
|
||||
{
|
||||
name: "auth failure, unlimited response body",
|
||||
desc: "auth failure, unlimited response body",
|
||||
maxResponseBodySize: -1,
|
||||
status: http.StatusForbidden,
|
||||
body: "Forbidden",
|
||||
@ -954,7 +1180,7 @@ func Test_ForwardAuthMaxResponseBodySize(t *testing.T) {
|
||||
expectedBody: "Forbidden",
|
||||
},
|
||||
{
|
||||
name: "auth failure, response body exceeds the limit",
|
||||
desc: "auth failure, response body exceeds the limit",
|
||||
maxResponseBodySize: 1,
|
||||
status: http.StatusForbidden,
|
||||
body: "Forbidden",
|
||||
@ -962,7 +1188,7 @@ func Test_ForwardAuthMaxResponseBodySize(t *testing.T) {
|
||||
expectedBody: "",
|
||||
},
|
||||
{
|
||||
name: "auth success within limit",
|
||||
desc: "auth success within limit",
|
||||
maxResponseBodySize: 100,
|
||||
status: http.StatusOK,
|
||||
body: "ok",
|
||||
@ -970,7 +1196,7 @@ func Test_ForwardAuthMaxResponseBodySize(t *testing.T) {
|
||||
expectedBody: "traefik\n",
|
||||
},
|
||||
{
|
||||
name: "auth success body exceeds limit",
|
||||
desc: "auth success body exceeds limit",
|
||||
maxResponseBodySize: 1,
|
||||
status: http.StatusOK,
|
||||
body: "large auth response",
|
||||
@ -980,7 +1206,7 @@ func Test_ForwardAuthMaxResponseBodySize(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(test.status)
|
||||
fmt.Fprint(w, test.body)
|
||||
|
||||
@ -14,14 +14,14 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
xForwardedProto = "X-Forwarded-Proto"
|
||||
xForwardedFor = "X-Forwarded-For"
|
||||
xForwardedHost = "X-Forwarded-Host"
|
||||
xForwardedPort = "X-Forwarded-Port"
|
||||
XForwardedProto = "X-Forwarded-Proto"
|
||||
XForwardedFor = "X-Forwarded-For"
|
||||
XForwardedHost = "X-Forwarded-Host"
|
||||
XForwardedPort = "X-Forwarded-Port"
|
||||
xForwardedServer = "X-Forwarded-Server"
|
||||
xForwardedURI = "X-Forwarded-Uri"
|
||||
xForwardedMethod = "X-Forwarded-Method"
|
||||
xForwardedPrefix = "X-Forwarded-Prefix"
|
||||
XForwardedURI = "X-Forwarded-Uri"
|
||||
XForwardedMethod = "X-Forwarded-Method"
|
||||
XForwardedPrefix = "X-Forwarded-Prefix"
|
||||
xForwardedTLSClientCert = "X-Forwarded-Tls-Client-Cert"
|
||||
xForwardedTLSClientCertInfo = "X-Forwarded-Tls-Client-Cert-Info"
|
||||
xRealIP = "X-Real-Ip"
|
||||
@ -29,18 +29,40 @@ const (
|
||||
upgrade = "Upgrade"
|
||||
)
|
||||
|
||||
var xHeaders = []string{
|
||||
xForwardedProto,
|
||||
xForwardedFor,
|
||||
xForwardedHost,
|
||||
xForwardedPort,
|
||||
xForwardedServer,
|
||||
xForwardedURI,
|
||||
xForwardedMethod,
|
||||
xForwardedPrefix,
|
||||
xForwardedTLSClientCert,
|
||||
xForwardedTLSClientCertInfo,
|
||||
xRealIP,
|
||||
// XHeadersSet contains the canonical X-headers managed by Traefik. Used by
|
||||
// isManagedXHeader to detect both the canonical form and underscore variants
|
||||
// that Go's HTTP server preserves (e.g. X_Forwarded_Proto).
|
||||
var XHeadersSet = map[string]struct{}{
|
||||
XForwardedProto: {},
|
||||
XForwardedFor: {},
|
||||
XForwardedHost: {},
|
||||
XForwardedPort: {},
|
||||
xForwardedServer: {},
|
||||
XForwardedURI: {},
|
||||
XForwardedMethod: {},
|
||||
XForwardedPrefix: {},
|
||||
xForwardedTLSClientCert: {},
|
||||
xForwardedTLSClientCertInfo: {},
|
||||
xRealIP: {},
|
||||
}
|
||||
|
||||
// isManagedXHeader reports whether key matches one of Traefik's X-headers,
|
||||
// treating '_' as '-'. Every managed header starts with 'X', so a byte check
|
||||
// skips most headers without any map work; the underscore branch is only
|
||||
// reached for the rare attacker-injected variants.
|
||||
func isManagedXHeader(key string) bool {
|
||||
if len(key) == 0 || key[0] != 'X' {
|
||||
return false
|
||||
}
|
||||
if _, ok := XHeadersSet[key]; ok {
|
||||
return true
|
||||
}
|
||||
if strings.IndexByte(key, '_') < 0 {
|
||||
return false
|
||||
}
|
||||
canonical := http.CanonicalHeaderKey(strings.ReplaceAll(key, "_", "-"))
|
||||
_, ok := XHeadersSet[canonical]
|
||||
return ok
|
||||
}
|
||||
|
||||
// XForwarded is an HTTP handler wrapper that sets the X-Forwarded headers,
|
||||
@ -89,6 +111,132 @@ func NewXForwarded(insecure bool, trustedIPs []string, connectionHeaders []strin
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ServeHTTP implements http.Handler.
|
||||
func (x *XForwarded) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
if !x.insecure && !x.isTrustedIP(r.RemoteAddr) {
|
||||
DeleteXForwardedHeaders(r.Header)
|
||||
}
|
||||
|
||||
x.rewrite(r)
|
||||
|
||||
x.removeConnectionHeaders(r)
|
||||
|
||||
if x.notAppendXForwardedFor {
|
||||
r = r.WithContext(httputil.SetNotAppendXFF(r.Context()))
|
||||
}
|
||||
|
||||
x.next.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
func (x *XForwarded) isTrustedIP(ip string) bool {
|
||||
if x.ipChecker == nil {
|
||||
return false
|
||||
}
|
||||
return x.ipChecker.IsAuthorized(ip) == nil
|
||||
}
|
||||
|
||||
func (x *XForwarded) rewrite(outreq *http.Request) {
|
||||
if clientIP, _, err := net.SplitHostPort(outreq.RemoteAddr); err == nil {
|
||||
clientIP = removeIPv6Zone(clientIP)
|
||||
|
||||
if unsafeHeader(outreq.Header).Get(xRealIP) == "" {
|
||||
unsafeHeader(outreq.Header).Set(xRealIP, clientIP)
|
||||
}
|
||||
}
|
||||
|
||||
xfProto := unsafeHeader(outreq.Header).Get(XForwardedProto)
|
||||
if xfProto == "" {
|
||||
// TODO: is this expected to set the X-Forwarded-Proto header value to
|
||||
// ws(s) as the underlying request used to upgrade the connection is
|
||||
// made over HTTP(S)?
|
||||
if isWebsocketRequest(outreq) {
|
||||
if outreq.TLS != nil {
|
||||
unsafeHeader(outreq.Header).Set(XForwardedProto, "wss")
|
||||
} else {
|
||||
unsafeHeader(outreq.Header).Set(XForwardedProto, "ws")
|
||||
}
|
||||
} else {
|
||||
if outreq.TLS != nil {
|
||||
unsafeHeader(outreq.Header).Set(XForwardedProto, "https")
|
||||
} else {
|
||||
unsafeHeader(outreq.Header).Set(XForwardedProto, "http")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if xfPort := unsafeHeader(outreq.Header).Get(XForwardedPort); xfPort == "" {
|
||||
unsafeHeader(outreq.Header).Set(XForwardedPort, forwardedPort(outreq))
|
||||
}
|
||||
|
||||
if xfHost := unsafeHeader(outreq.Header).Get(XForwardedHost); xfHost == "" && outreq.Host != "" {
|
||||
unsafeHeader(outreq.Header).Set(XForwardedHost, outreq.Host)
|
||||
}
|
||||
|
||||
// Per https://www.rfc-editor.org/rfc/rfc2616#section-4.2, the Forwarded IPs list is in
|
||||
// the same order as the values in the X-Forwarded-For header(s).
|
||||
if xffs := unsafeHeader(outreq.Header).Values(XForwardedFor); len(xffs) > 0 {
|
||||
unsafeHeader(outreq.Header).Set(XForwardedFor, strings.Join(xffs, ", "))
|
||||
}
|
||||
|
||||
if x.hostname != "" {
|
||||
unsafeHeader(outreq.Header).Set(xForwardedServer, x.hostname)
|
||||
}
|
||||
}
|
||||
|
||||
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.SplitSeq(f, ",") {
|
||||
if sf = textproto.TrimString(sf); sf != "" {
|
||||
key := http.CanonicalHeaderKey(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 isManagedXHeader(key) {
|
||||
continue
|
||||
}
|
||||
|
||||
// Keep headers allowed through the middleware chain.
|
||||
if slices.Contains(x.connectionHeaders, key) {
|
||||
connectionHopByHopHeaders = append(connectionHopByHopHeaders, key)
|
||||
continue
|
||||
}
|
||||
|
||||
// Apply Connection header option.
|
||||
delete(req.Header, key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
// DeleteXForwardedHeaders Strip X-Forwarded headers and their underscore variants
|
||||
// (e.g. X_Forwarded_Proto), which Go's HTTP server preserves
|
||||
// alongside the canonical dash form.
|
||||
func DeleteXForwardedHeaders(headers http.Header) {
|
||||
for key := range headers {
|
||||
if isManagedXHeader(key) {
|
||||
delete(headers, key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// removeIPv6Zone removes the zone if the given IP is an ipv6 address and it has {zone} information in it,
|
||||
// like "[fe80::d806:a55d:eb1b:49cc%vEthernet (vmxnet3 Ethernet Adapter - Virtual Switch)]:64692".
|
||||
func removeIPv6Zone(clientIP string) string {
|
||||
@ -126,7 +274,7 @@ func forwardedPort(req *http.Request) string {
|
||||
return port
|
||||
}
|
||||
|
||||
if unsafeHeader(req.Header).Get(xForwardedProto) == "https" || unsafeHeader(req.Header).Get(xForwardedProto) == "wss" {
|
||||
if unsafeHeader(req.Header).Get(XForwardedProto) == "https" || unsafeHeader(req.Header).Get(XForwardedProto) == "wss" {
|
||||
return "443"
|
||||
}
|
||||
|
||||
@ -137,123 +285,6 @@ func forwardedPort(req *http.Request) string {
|
||||
return "80"
|
||||
}
|
||||
|
||||
// ServeHTTP implements http.Handler.
|
||||
func (x *XForwarded) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
if !x.insecure && !x.isTrustedIP(r.RemoteAddr) {
|
||||
for _, h := range xHeaders {
|
||||
unsafeHeader(r.Header).Del(h)
|
||||
}
|
||||
}
|
||||
|
||||
x.rewrite(r)
|
||||
|
||||
x.removeConnectionHeaders(r)
|
||||
|
||||
if x.notAppendXForwardedFor {
|
||||
r = r.WithContext(httputil.SetNotAppendXFF(r.Context()))
|
||||
}
|
||||
|
||||
x.next.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
func (x *XForwarded) isTrustedIP(ip string) bool {
|
||||
if x.ipChecker == nil {
|
||||
return false
|
||||
}
|
||||
return x.ipChecker.IsAuthorized(ip) == nil
|
||||
}
|
||||
|
||||
func (x *XForwarded) rewrite(outreq *http.Request) {
|
||||
if clientIP, _, err := net.SplitHostPort(outreq.RemoteAddr); err == nil {
|
||||
clientIP = removeIPv6Zone(clientIP)
|
||||
|
||||
if unsafeHeader(outreq.Header).Get(xRealIP) == "" {
|
||||
unsafeHeader(outreq.Header).Set(xRealIP, clientIP)
|
||||
}
|
||||
}
|
||||
|
||||
xfProto := unsafeHeader(outreq.Header).Get(xForwardedProto)
|
||||
if xfProto == "" {
|
||||
// TODO: is this expected to set the X-Forwarded-Proto header value to
|
||||
// ws(s) as the underlying request used to upgrade the connection is
|
||||
// made over HTTP(S)?
|
||||
if isWebsocketRequest(outreq) {
|
||||
if outreq.TLS != nil {
|
||||
unsafeHeader(outreq.Header).Set(xForwardedProto, "wss")
|
||||
} else {
|
||||
unsafeHeader(outreq.Header).Set(xForwardedProto, "ws")
|
||||
}
|
||||
} else {
|
||||
if outreq.TLS != nil {
|
||||
unsafeHeader(outreq.Header).Set(xForwardedProto, "https")
|
||||
} else {
|
||||
unsafeHeader(outreq.Header).Set(xForwardedProto, "http")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if xfPort := unsafeHeader(outreq.Header).Get(xForwardedPort); xfPort == "" {
|
||||
unsafeHeader(outreq.Header).Set(xForwardedPort, forwardedPort(outreq))
|
||||
}
|
||||
|
||||
if xfHost := unsafeHeader(outreq.Header).Get(xForwardedHost); xfHost == "" && outreq.Host != "" {
|
||||
unsafeHeader(outreq.Header).Set(xForwardedHost, outreq.Host)
|
||||
}
|
||||
|
||||
// Per https://www.rfc-editor.org/rfc/rfc2616#section-4.2, the Forwarded IPs list is in
|
||||
// the same order as the values in the X-Forwarded-For header(s).
|
||||
if xffs := unsafeHeader(outreq.Header).Values(xForwardedFor); len(xffs) > 0 {
|
||||
unsafeHeader(outreq.Header).Set(xForwardedFor, strings.Join(xffs, ", "))
|
||||
}
|
||||
|
||||
if x.hostname != "" {
|
||||
unsafeHeader(outreq.Header).Set(xForwardedServer, x.hostname)
|
||||
}
|
||||
}
|
||||
|
||||
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.SplitSeq(f, ",") {
|
||||
if sf = textproto.TrimString(sf); sf != "" {
|
||||
key := http.CanonicalHeaderKey(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, key) {
|
||||
continue
|
||||
}
|
||||
|
||||
// Keep headers allowed through the middleware chain.
|
||||
if slices.Contains(x.connectionHeaders, key) {
|
||||
connectionHopByHopHeaders = append(connectionHopByHopHeaders, key)
|
||||
continue
|
||||
}
|
||||
|
||||
// Apply Connection header option.
|
||||
delete(req.Header, key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if reqUpType != "" {
|
||||
connectionHopByHopHeaders = append(connectionHopByHopHeaders, upgrade)
|
||||
unsafeHeader(req.Header).Set(upgrade, reqUpType)
|
||||
}
|
||||
if len(connectionHopByHopHeaders) > 0 {
|
||||
unsafeHeader(req.Header).Set(connection, strings.Join(connectionHopByHopHeaders, ","))
|
||||
return
|
||||
}
|
||||
|
||||
unsafeHeader(req.Header).Del(connection)
|
||||
}
|
||||
|
||||
// unsafeHeader allows to manage Header values.
|
||||
// Must be used only when the header name is already a canonical key.
|
||||
type unsafeHeader map[string][]string
|
||||
|
||||
@ -31,9 +31,9 @@ func TestServeHTTP(t *testing.T) {
|
||||
remoteAddr: "",
|
||||
incomingHeaders: map[string][]string{},
|
||||
expectedHeaders: map[string]string{
|
||||
xForwardedFor: "",
|
||||
xForwardedURI: "",
|
||||
xForwardedMethod: "",
|
||||
XForwardedFor: "",
|
||||
XForwardedURI: "",
|
||||
XForwardedMethod: "",
|
||||
xForwardedTLSClientCert: "",
|
||||
xForwardedTLSClientCertInfo: "",
|
||||
},
|
||||
@ -44,20 +44,24 @@ func TestServeHTTP(t *testing.T) {
|
||||
trustedIps: nil,
|
||||
remoteAddr: "",
|
||||
incomingHeaders: map[string][]string{
|
||||
xForwardedFor: {"10.0.1.0, 10.0.1.12"},
|
||||
xForwardedURI: {"/bar"},
|
||||
xForwardedMethod: {"GET"},
|
||||
XForwardedFor: {"10.0.1.0, 10.0.1.12"},
|
||||
XForwardedURI: {"/bar"},
|
||||
XForwardedMethod: {"GET"},
|
||||
xForwardedTLSClientCert: {"Cert"},
|
||||
xForwardedTLSClientCertInfo: {"CertInfo"},
|
||||
xForwardedPrefix: {"/prefix"},
|
||||
XForwardedPrefix: {"/prefix"},
|
||||
"X_forwarded_proto": {"https"},
|
||||
"X_forwarded_for": {"10.0.0.1"},
|
||||
},
|
||||
expectedHeaders: map[string]string{
|
||||
xForwardedFor: "10.0.1.0, 10.0.1.12",
|
||||
xForwardedURI: "/bar",
|
||||
xForwardedMethod: "GET",
|
||||
XForwardedFor: "10.0.1.0, 10.0.1.12",
|
||||
XForwardedURI: "/bar",
|
||||
XForwardedMethod: "GET",
|
||||
xForwardedTLSClientCert: "Cert",
|
||||
xForwardedTLSClientCertInfo: "CertInfo",
|
||||
xForwardedPrefix: "/prefix",
|
||||
XForwardedPrefix: "/prefix",
|
||||
"X_forwarded_proto": "https",
|
||||
"X_forwarded_for": "10.0.0.1",
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -66,20 +70,26 @@ func TestServeHTTP(t *testing.T) {
|
||||
trustedIps: nil,
|
||||
remoteAddr: "",
|
||||
incomingHeaders: map[string][]string{
|
||||
xForwardedFor: {"10.0.1.0, 10.0.1.12"},
|
||||
xForwardedURI: {"/bar"},
|
||||
xForwardedMethod: {"GET"},
|
||||
XForwardedFor: {"10.0.1.0, 10.0.1.12"},
|
||||
XForwardedURI: {"/bar"},
|
||||
XForwardedMethod: {"GET"},
|
||||
xForwardedTLSClientCert: {"Cert"},
|
||||
xForwardedTLSClientCertInfo: {"CertInfo"},
|
||||
xForwardedPrefix: {"/prefix"},
|
||||
XForwardedPrefix: {"/prefix"},
|
||||
"X_forwarded_proto": {"https"},
|
||||
"X_forwarded_for": {"10.0.0.1"},
|
||||
"X_forwarded_host": {"evil.example"},
|
||||
},
|
||||
expectedHeaders: map[string]string{
|
||||
xForwardedFor: "",
|
||||
xForwardedURI: "",
|
||||
xForwardedMethod: "",
|
||||
XForwardedFor: "",
|
||||
XForwardedURI: "",
|
||||
XForwardedMethod: "",
|
||||
xForwardedTLSClientCert: "",
|
||||
xForwardedTLSClientCertInfo: "",
|
||||
xForwardedPrefix: "",
|
||||
XForwardedPrefix: "",
|
||||
"X_forwarded_proto": "",
|
||||
"X_forwarded_for": "",
|
||||
"X_forwarded_host": "",
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -88,20 +98,24 @@ func TestServeHTTP(t *testing.T) {
|
||||
trustedIps: []string{"10.0.1.100"},
|
||||
remoteAddr: "10.0.1.100:80",
|
||||
incomingHeaders: map[string][]string{
|
||||
xForwardedFor: {"10.0.1.0, 10.0.1.12"},
|
||||
xForwardedURI: {"/bar"},
|
||||
xForwardedMethod: {"GET"},
|
||||
XForwardedFor: {"10.0.1.0, 10.0.1.12"},
|
||||
XForwardedURI: {"/bar"},
|
||||
XForwardedMethod: {"GET"},
|
||||
xForwardedTLSClientCert: {"Cert"},
|
||||
xForwardedTLSClientCertInfo: {"CertInfo"},
|
||||
xForwardedPrefix: {"/prefix"},
|
||||
XForwardedPrefix: {"/prefix"},
|
||||
"X_forwarded_proto": {"https"},
|
||||
"X_forwarded_for": {"10.0.0.1"},
|
||||
},
|
||||
expectedHeaders: map[string]string{
|
||||
xForwardedFor: "10.0.1.0, 10.0.1.12",
|
||||
xForwardedURI: "/bar",
|
||||
xForwardedMethod: "GET",
|
||||
XForwardedFor: "10.0.1.0, 10.0.1.12",
|
||||
XForwardedURI: "/bar",
|
||||
XForwardedMethod: "GET",
|
||||
xForwardedTLSClientCert: "Cert",
|
||||
xForwardedTLSClientCertInfo: "CertInfo",
|
||||
xForwardedPrefix: "/prefix",
|
||||
XForwardedPrefix: "/prefix",
|
||||
"X_forwarded_proto": "https",
|
||||
"X_forwarded_for": "10.0.0.1",
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -110,20 +124,20 @@ func TestServeHTTP(t *testing.T) {
|
||||
trustedIps: []string{"10.0.1.100"},
|
||||
remoteAddr: "10.0.1.101:80",
|
||||
incomingHeaders: map[string][]string{
|
||||
xForwardedFor: {"10.0.1.0, 10.0.1.12"},
|
||||
xForwardedURI: {"/bar"},
|
||||
xForwardedMethod: {"GET"},
|
||||
XForwardedFor: {"10.0.1.0, 10.0.1.12"},
|
||||
XForwardedURI: {"/bar"},
|
||||
XForwardedMethod: {"GET"},
|
||||
xForwardedTLSClientCert: {"Cert"},
|
||||
xForwardedTLSClientCertInfo: {"CertInfo"},
|
||||
xForwardedPrefix: {"/prefix"},
|
||||
XForwardedPrefix: {"/prefix"},
|
||||
},
|
||||
expectedHeaders: map[string]string{
|
||||
xForwardedFor: "",
|
||||
xForwardedURI: "",
|
||||
xForwardedMethod: "",
|
||||
XForwardedFor: "",
|
||||
XForwardedURI: "",
|
||||
XForwardedMethod: "",
|
||||
xForwardedTLSClientCert: "",
|
||||
xForwardedTLSClientCertInfo: "",
|
||||
xForwardedPrefix: "",
|
||||
XForwardedPrefix: "",
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -132,20 +146,20 @@ func TestServeHTTP(t *testing.T) {
|
||||
trustedIps: []string{"1.2.3.4/24"},
|
||||
remoteAddr: "1.2.3.156:80",
|
||||
incomingHeaders: map[string][]string{
|
||||
xForwardedFor: {"10.0.1.0, 10.0.1.12"},
|
||||
xForwardedURI: {"/bar"},
|
||||
xForwardedMethod: {"GET"},
|
||||
XForwardedFor: {"10.0.1.0, 10.0.1.12"},
|
||||
XForwardedURI: {"/bar"},
|
||||
XForwardedMethod: {"GET"},
|
||||
xForwardedTLSClientCert: {"Cert"},
|
||||
xForwardedTLSClientCertInfo: {"CertInfo"},
|
||||
xForwardedPrefix: {"/prefix"},
|
||||
XForwardedPrefix: {"/prefix"},
|
||||
},
|
||||
expectedHeaders: map[string]string{
|
||||
xForwardedFor: "10.0.1.0, 10.0.1.12",
|
||||
xForwardedURI: "/bar",
|
||||
xForwardedMethod: "GET",
|
||||
XForwardedFor: "10.0.1.0, 10.0.1.12",
|
||||
XForwardedURI: "/bar",
|
||||
XForwardedMethod: "GET",
|
||||
xForwardedTLSClientCert: "Cert",
|
||||
xForwardedTLSClientCertInfo: "CertInfo",
|
||||
xForwardedPrefix: "/prefix",
|
||||
XForwardedPrefix: "/prefix",
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -154,34 +168,34 @@ func TestServeHTTP(t *testing.T) {
|
||||
trustedIps: []string{"1.2.3.4/24"},
|
||||
remoteAddr: "10.0.1.101:80",
|
||||
incomingHeaders: map[string][]string{
|
||||
xForwardedFor: {"10.0.1.0, 10.0.1.12"},
|
||||
xForwardedURI: {"/bar"},
|
||||
xForwardedMethod: {"GET"},
|
||||
XForwardedFor: {"10.0.1.0, 10.0.1.12"},
|
||||
XForwardedURI: {"/bar"},
|
||||
XForwardedMethod: {"GET"},
|
||||
xForwardedTLSClientCert: {"Cert"},
|
||||
xForwardedTLSClientCertInfo: {"CertInfo"},
|
||||
xForwardedPrefix: {"/prefix"},
|
||||
XForwardedPrefix: {"/prefix"},
|
||||
},
|
||||
expectedHeaders: map[string]string{
|
||||
xForwardedFor: "",
|
||||
xForwardedURI: "",
|
||||
xForwardedMethod: "",
|
||||
XForwardedFor: "",
|
||||
XForwardedURI: "",
|
||||
XForwardedMethod: "",
|
||||
xForwardedTLSClientCert: "",
|
||||
xForwardedTLSClientCertInfo: "",
|
||||
xForwardedPrefix: "",
|
||||
XForwardedPrefix: "",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "xForwardedFor with multiple header(s) values",
|
||||
insecure: true,
|
||||
incomingHeaders: map[string][]string{
|
||||
xForwardedFor: {
|
||||
XForwardedFor: {
|
||||
"10.0.0.4, 10.0.0.3",
|
||||
"10.0.0.2, 10.0.0.1",
|
||||
"10.0.0.0",
|
||||
},
|
||||
},
|
||||
expectedHeaders: map[string]string{
|
||||
xForwardedFor: "10.0.0.4, 10.0.0.3, 10.0.0.2, 10.0.0.1, 10.0.0.0",
|
||||
XForwardedFor: "10.0.0.4, 10.0.0.3, 10.0.0.2, 10.0.0.1, 10.0.0.0",
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -206,14 +220,14 @@ func TestServeHTTP(t *testing.T) {
|
||||
desc: "xForwardedProto with no tls",
|
||||
tls: false,
|
||||
expectedHeaders: map[string]string{
|
||||
xForwardedProto: "http",
|
||||
XForwardedProto: "http",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "xForwardedProto with tls",
|
||||
tls: true,
|
||||
expectedHeaders: map[string]string{
|
||||
xForwardedProto: "https",
|
||||
XForwardedProto: "https",
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -221,7 +235,7 @@ func TestServeHTTP(t *testing.T) {
|
||||
tls: false,
|
||||
websocket: true,
|
||||
expectedHeaders: map[string]string{
|
||||
xForwardedProto: "ws",
|
||||
XForwardedProto: "ws",
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -229,7 +243,7 @@ func TestServeHTTP(t *testing.T) {
|
||||
tls: true,
|
||||
websocket: true,
|
||||
expectedHeaders: map[string]string{
|
||||
xForwardedProto: "wss",
|
||||
XForwardedProto: "wss",
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -237,17 +251,17 @@ func TestServeHTTP(t *testing.T) {
|
||||
tls: true,
|
||||
websocket: true,
|
||||
incomingHeaders: map[string][]string{
|
||||
xForwardedProto: {"wss"},
|
||||
XForwardedProto: {"wss"},
|
||||
},
|
||||
expectedHeaders: map[string]string{
|
||||
xForwardedProto: "wss",
|
||||
XForwardedProto: "wss",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "xForwardedPort with explicit port",
|
||||
host: "foo.com:8080",
|
||||
expectedHeaders: map[string]string{
|
||||
xForwardedPort: "8080",
|
||||
XForwardedPort: "8080",
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -255,25 +269,25 @@ func TestServeHTTP(t *testing.T) {
|
||||
// setting insecure just so our initial xForwardedProto does not get cleaned
|
||||
insecure: true,
|
||||
incomingHeaders: map[string][]string{
|
||||
xForwardedProto: {"https"},
|
||||
XForwardedProto: {"https"},
|
||||
},
|
||||
expectedHeaders: map[string]string{
|
||||
xForwardedProto: "https",
|
||||
xForwardedPort: "443",
|
||||
XForwardedProto: "https",
|
||||
XForwardedPort: "443",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "xForwardedPort with implicit tls port from TLS in req",
|
||||
tls: true,
|
||||
expectedHeaders: map[string]string{
|
||||
xForwardedPort: "443",
|
||||
XForwardedPort: "443",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "xForwardedHost from req host",
|
||||
host: "foo.com:8080",
|
||||
expectedHeaders: map[string]string{
|
||||
xForwardedHost: "foo.com:8080",
|
||||
XForwardedHost: "foo.com:8080",
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -288,39 +302,42 @@ func TestServeHTTP(t *testing.T) {
|
||||
insecure: false,
|
||||
incomingHeaders: map[string][]string{
|
||||
connection: {
|
||||
xForwardedProto,
|
||||
xForwardedFor,
|
||||
xForwardedURI,
|
||||
xForwardedMethod,
|
||||
xForwardedHost,
|
||||
xForwardedPort,
|
||||
XForwardedProto,
|
||||
XForwardedFor,
|
||||
XForwardedURI,
|
||||
XForwardedMethod,
|
||||
XForwardedHost,
|
||||
XForwardedPort,
|
||||
xForwardedTLSClientCert,
|
||||
xForwardedTLSClientCertInfo,
|
||||
xForwardedPrefix,
|
||||
XForwardedPrefix,
|
||||
xRealIP,
|
||||
"X_forwarded_proto",
|
||||
},
|
||||
xForwardedProto: {"foo"},
|
||||
xForwardedFor: {"foo"},
|
||||
xForwardedURI: {"foo"},
|
||||
xForwardedMethod: {"foo"},
|
||||
xForwardedHost: {"foo"},
|
||||
xForwardedPort: {"foo"},
|
||||
XForwardedProto: {"foo"},
|
||||
XForwardedFor: {"foo"},
|
||||
XForwardedURI: {"foo"},
|
||||
XForwardedMethod: {"foo"},
|
||||
XForwardedHost: {"foo"},
|
||||
XForwardedPort: {"foo"},
|
||||
xForwardedTLSClientCert: {"foo"},
|
||||
xForwardedTLSClientCertInfo: {"foo"},
|
||||
xForwardedPrefix: {"foo"},
|
||||
XForwardedPrefix: {"foo"},
|
||||
xRealIP: {"foo"},
|
||||
"X_forwarded_proto": {"spoofed"},
|
||||
},
|
||||
expectedHeaders: map[string]string{
|
||||
xForwardedProto: "http",
|
||||
xForwardedFor: "",
|
||||
xForwardedURI: "",
|
||||
xForwardedMethod: "",
|
||||
xForwardedHost: "",
|
||||
xForwardedPort: "80",
|
||||
XForwardedProto: "http",
|
||||
XForwardedFor: "",
|
||||
XForwardedURI: "",
|
||||
XForwardedMethod: "",
|
||||
XForwardedHost: "",
|
||||
XForwardedPort: "80",
|
||||
xForwardedTLSClientCert: "",
|
||||
xForwardedTLSClientCertInfo: "",
|
||||
xForwardedPrefix: "",
|
||||
XForwardedPrefix: "",
|
||||
xRealIP: "",
|
||||
"X_forwarded_proto": "",
|
||||
connection: "",
|
||||
},
|
||||
},
|
||||
@ -329,38 +346,38 @@ func TestServeHTTP(t *testing.T) {
|
||||
insecure: true,
|
||||
incomingHeaders: map[string][]string{
|
||||
connection: {
|
||||
xForwardedProto,
|
||||
xForwardedFor,
|
||||
xForwardedURI,
|
||||
xForwardedMethod,
|
||||
xForwardedHost,
|
||||
xForwardedPort,
|
||||
XForwardedProto,
|
||||
XForwardedFor,
|
||||
XForwardedURI,
|
||||
XForwardedMethod,
|
||||
XForwardedHost,
|
||||
XForwardedPort,
|
||||
xForwardedTLSClientCert,
|
||||
xForwardedTLSClientCertInfo,
|
||||
xForwardedPrefix,
|
||||
XForwardedPrefix,
|
||||
xRealIP,
|
||||
},
|
||||
xForwardedProto: {"foo"},
|
||||
xForwardedFor: {"foo"},
|
||||
xForwardedURI: {"foo"},
|
||||
xForwardedMethod: {"foo"},
|
||||
xForwardedHost: {"foo"},
|
||||
xForwardedPort: {"foo"},
|
||||
XForwardedProto: {"foo"},
|
||||
XForwardedFor: {"foo"},
|
||||
XForwardedURI: {"foo"},
|
||||
XForwardedMethod: {"foo"},
|
||||
XForwardedHost: {"foo"},
|
||||
XForwardedPort: {"foo"},
|
||||
xForwardedTLSClientCert: {"foo"},
|
||||
xForwardedTLSClientCertInfo: {"foo"},
|
||||
xForwardedPrefix: {"foo"},
|
||||
XForwardedPrefix: {"foo"},
|
||||
xRealIP: {"foo"},
|
||||
},
|
||||
expectedHeaders: map[string]string{
|
||||
xForwardedProto: "foo",
|
||||
xForwardedFor: "foo",
|
||||
xForwardedURI: "foo",
|
||||
xForwardedMethod: "foo",
|
||||
xForwardedHost: "foo",
|
||||
xForwardedPort: "foo",
|
||||
XForwardedProto: "foo",
|
||||
XForwardedFor: "foo",
|
||||
XForwardedURI: "foo",
|
||||
XForwardedMethod: "foo",
|
||||
XForwardedHost: "foo",
|
||||
XForwardedPort: "foo",
|
||||
xForwardedTLSClientCert: "foo",
|
||||
xForwardedTLSClientCertInfo: "foo",
|
||||
xForwardedPrefix: "foo",
|
||||
XForwardedPrefix: "foo",
|
||||
xRealIP: "foo",
|
||||
connection: "",
|
||||
},
|
||||
@ -369,51 +386,51 @@ func TestServeHTTP(t *testing.T) {
|
||||
desc: "Untrusted and Connection: Connection header has no effect on X- forwarded headers",
|
||||
insecure: false,
|
||||
connectionHeaders: []string{
|
||||
xForwardedProto,
|
||||
xForwardedFor,
|
||||
xForwardedURI,
|
||||
xForwardedMethod,
|
||||
xForwardedHost,
|
||||
xForwardedPort,
|
||||
XForwardedProto,
|
||||
XForwardedFor,
|
||||
XForwardedURI,
|
||||
XForwardedMethod,
|
||||
XForwardedHost,
|
||||
XForwardedPort,
|
||||
xForwardedTLSClientCert,
|
||||
xForwardedTLSClientCertInfo,
|
||||
xForwardedPrefix,
|
||||
XForwardedPrefix,
|
||||
xRealIP,
|
||||
},
|
||||
incomingHeaders: map[string][]string{
|
||||
connection: {
|
||||
xForwardedProto,
|
||||
xForwardedFor,
|
||||
xForwardedURI,
|
||||
xForwardedMethod,
|
||||
xForwardedHost,
|
||||
xForwardedPort,
|
||||
XForwardedProto,
|
||||
XForwardedFor,
|
||||
XForwardedURI,
|
||||
XForwardedMethod,
|
||||
XForwardedHost,
|
||||
XForwardedPort,
|
||||
xForwardedTLSClientCert,
|
||||
xForwardedTLSClientCertInfo,
|
||||
xForwardedPrefix,
|
||||
XForwardedPrefix,
|
||||
xRealIP,
|
||||
},
|
||||
xForwardedProto: {"foo"},
|
||||
xForwardedFor: {"foo"},
|
||||
xForwardedURI: {"foo"},
|
||||
xForwardedMethod: {"foo"},
|
||||
xForwardedHost: {"foo"},
|
||||
xForwardedPort: {"foo"},
|
||||
XForwardedProto: {"foo"},
|
||||
XForwardedFor: {"foo"},
|
||||
XForwardedURI: {"foo"},
|
||||
XForwardedMethod: {"foo"},
|
||||
XForwardedHost: {"foo"},
|
||||
XForwardedPort: {"foo"},
|
||||
xForwardedTLSClientCert: {"foo"},
|
||||
xForwardedTLSClientCertInfo: {"foo"},
|
||||
xForwardedPrefix: {"foo"},
|
||||
XForwardedPrefix: {"foo"},
|
||||
xRealIP: {"foo"},
|
||||
},
|
||||
expectedHeaders: map[string]string{
|
||||
xForwardedProto: "http",
|
||||
xForwardedFor: "",
|
||||
xForwardedURI: "",
|
||||
xForwardedMethod: "",
|
||||
xForwardedHost: "",
|
||||
xForwardedPort: "80",
|
||||
XForwardedProto: "http",
|
||||
XForwardedFor: "",
|
||||
XForwardedURI: "",
|
||||
XForwardedMethod: "",
|
||||
XForwardedHost: "",
|
||||
XForwardedPort: "80",
|
||||
xForwardedTLSClientCert: "",
|
||||
xForwardedTLSClientCertInfo: "",
|
||||
xForwardedPrefix: "",
|
||||
XForwardedPrefix: "",
|
||||
xRealIP: "",
|
||||
connection: "",
|
||||
},
|
||||
@ -422,51 +439,51 @@ func TestServeHTTP(t *testing.T) {
|
||||
desc: "Trusted (insecure) and Connection: Connection header has no effect on X- forwarded headers",
|
||||
insecure: true,
|
||||
connectionHeaders: []string{
|
||||
xForwardedProto,
|
||||
xForwardedFor,
|
||||
xForwardedURI,
|
||||
xForwardedMethod,
|
||||
xForwardedHost,
|
||||
xForwardedPort,
|
||||
XForwardedProto,
|
||||
XForwardedFor,
|
||||
XForwardedURI,
|
||||
XForwardedMethod,
|
||||
XForwardedHost,
|
||||
XForwardedPort,
|
||||
xForwardedTLSClientCert,
|
||||
xForwardedTLSClientCertInfo,
|
||||
xForwardedPrefix,
|
||||
XForwardedPrefix,
|
||||
xRealIP,
|
||||
},
|
||||
incomingHeaders: map[string][]string{
|
||||
connection: {
|
||||
xForwardedProto,
|
||||
xForwardedFor,
|
||||
xForwardedURI,
|
||||
xForwardedMethod,
|
||||
xForwardedHost,
|
||||
xForwardedPort,
|
||||
XForwardedProto,
|
||||
XForwardedFor,
|
||||
XForwardedURI,
|
||||
XForwardedMethod,
|
||||
XForwardedHost,
|
||||
XForwardedPort,
|
||||
xForwardedTLSClientCert,
|
||||
xForwardedTLSClientCertInfo,
|
||||
xForwardedPrefix,
|
||||
XForwardedPrefix,
|
||||
xRealIP,
|
||||
},
|
||||
xForwardedProto: {"foo"},
|
||||
xForwardedFor: {"foo"},
|
||||
xForwardedURI: {"foo"},
|
||||
xForwardedMethod: {"foo"},
|
||||
xForwardedHost: {"foo"},
|
||||
xForwardedPort: {"foo"},
|
||||
XForwardedProto: {"foo"},
|
||||
XForwardedFor: {"foo"},
|
||||
XForwardedURI: {"foo"},
|
||||
XForwardedMethod: {"foo"},
|
||||
XForwardedHost: {"foo"},
|
||||
XForwardedPort: {"foo"},
|
||||
xForwardedTLSClientCert: {"foo"},
|
||||
xForwardedTLSClientCertInfo: {"foo"},
|
||||
xForwardedPrefix: {"foo"},
|
||||
XForwardedPrefix: {"foo"},
|
||||
xRealIP: {"foo"},
|
||||
},
|
||||
expectedHeaders: map[string]string{
|
||||
xForwardedProto: "foo",
|
||||
xForwardedFor: "foo",
|
||||
xForwardedURI: "foo",
|
||||
xForwardedMethod: "foo",
|
||||
xForwardedHost: "foo",
|
||||
xForwardedPort: "foo",
|
||||
XForwardedProto: "foo",
|
||||
XForwardedFor: "foo",
|
||||
XForwardedURI: "foo",
|
||||
XForwardedMethod: "foo",
|
||||
XForwardedHost: "foo",
|
||||
XForwardedPort: "foo",
|
||||
xForwardedTLSClientCert: "foo",
|
||||
xForwardedTLSClientCertInfo: "foo",
|
||||
xForwardedPrefix: "foo",
|
||||
XForwardedPrefix: "foo",
|
||||
xRealIP: "foo",
|
||||
connection: "",
|
||||
},
|
||||
@ -475,51 +492,51 @@ func TestServeHTTP(t *testing.T) {
|
||||
desc: "Trusted (insecure) and Connection: Testing case sensitivity on connection Headers param",
|
||||
insecure: true,
|
||||
connectionHeaders: []string{
|
||||
strings.ToLower(xForwardedProto),
|
||||
strings.ToLower(xForwardedFor),
|
||||
strings.ToLower(xForwardedURI),
|
||||
strings.ToLower(xForwardedMethod),
|
||||
strings.ToLower(xForwardedHost),
|
||||
strings.ToLower(xForwardedPort),
|
||||
strings.ToLower(XForwardedProto),
|
||||
strings.ToLower(XForwardedFor),
|
||||
strings.ToLower(XForwardedURI),
|
||||
strings.ToLower(XForwardedMethod),
|
||||
strings.ToLower(XForwardedHost),
|
||||
strings.ToLower(XForwardedPort),
|
||||
strings.ToLower(xForwardedTLSClientCert),
|
||||
strings.ToLower(xForwardedTLSClientCertInfo),
|
||||
strings.ToLower(xForwardedPrefix),
|
||||
strings.ToLower(XForwardedPrefix),
|
||||
strings.ToLower(xRealIP),
|
||||
},
|
||||
incomingHeaders: map[string][]string{
|
||||
connection: {
|
||||
xForwardedProto,
|
||||
xForwardedFor,
|
||||
xForwardedURI,
|
||||
xForwardedMethod,
|
||||
xForwardedHost,
|
||||
xForwardedPort,
|
||||
XForwardedProto,
|
||||
XForwardedFor,
|
||||
XForwardedURI,
|
||||
XForwardedMethod,
|
||||
XForwardedHost,
|
||||
XForwardedPort,
|
||||
xForwardedTLSClientCert,
|
||||
xForwardedTLSClientCertInfo,
|
||||
xForwardedPrefix,
|
||||
XForwardedPrefix,
|
||||
xRealIP,
|
||||
},
|
||||
xForwardedProto: {"foo"},
|
||||
xForwardedFor: {"foo"},
|
||||
xForwardedURI: {"foo"},
|
||||
xForwardedMethod: {"foo"},
|
||||
xForwardedHost: {"foo"},
|
||||
xForwardedPort: {"foo"},
|
||||
XForwardedProto: {"foo"},
|
||||
XForwardedFor: {"foo"},
|
||||
XForwardedURI: {"foo"},
|
||||
XForwardedMethod: {"foo"},
|
||||
XForwardedHost: {"foo"},
|
||||
XForwardedPort: {"foo"},
|
||||
xForwardedTLSClientCert: {"foo"},
|
||||
xForwardedTLSClientCertInfo: {"foo"},
|
||||
xForwardedPrefix: {"foo"},
|
||||
XForwardedPrefix: {"foo"},
|
||||
xRealIP: {"foo"},
|
||||
},
|
||||
expectedHeaders: map[string]string{
|
||||
xForwardedProto: "foo",
|
||||
xForwardedFor: "foo",
|
||||
xForwardedURI: "foo",
|
||||
xForwardedMethod: "foo",
|
||||
xForwardedHost: "foo",
|
||||
xForwardedPort: "foo",
|
||||
XForwardedProto: "foo",
|
||||
XForwardedFor: "foo",
|
||||
XForwardedURI: "foo",
|
||||
XForwardedMethod: "foo",
|
||||
XForwardedHost: "foo",
|
||||
XForwardedPort: "foo",
|
||||
xForwardedTLSClientCert: "foo",
|
||||
xForwardedTLSClientCertInfo: "foo",
|
||||
xForwardedPrefix: "foo",
|
||||
XForwardedPrefix: "foo",
|
||||
xRealIP: "foo",
|
||||
connection: "",
|
||||
},
|
||||
@ -529,38 +546,38 @@ func TestServeHTTP(t *testing.T) {
|
||||
insecure: true,
|
||||
incomingHeaders: map[string][]string{
|
||||
connection: {
|
||||
strings.ToLower(xForwardedProto),
|
||||
strings.ToLower(xForwardedFor),
|
||||
strings.ToLower(xForwardedURI),
|
||||
strings.ToLower(xForwardedMethod),
|
||||
strings.ToLower(xForwardedHost),
|
||||
strings.ToLower(xForwardedPort),
|
||||
strings.ToLower(XForwardedProto),
|
||||
strings.ToLower(XForwardedFor),
|
||||
strings.ToLower(XForwardedURI),
|
||||
strings.ToLower(XForwardedMethod),
|
||||
strings.ToLower(XForwardedHost),
|
||||
strings.ToLower(XForwardedPort),
|
||||
strings.ToLower(xForwardedTLSClientCert),
|
||||
strings.ToLower(xForwardedTLSClientCertInfo),
|
||||
strings.ToLower(xForwardedPrefix),
|
||||
strings.ToLower(XForwardedPrefix),
|
||||
strings.ToLower(xRealIP),
|
||||
},
|
||||
xForwardedProto: {"foo"},
|
||||
xForwardedFor: {"foo"},
|
||||
xForwardedURI: {"foo"},
|
||||
xForwardedMethod: {"foo"},
|
||||
xForwardedHost: {"foo"},
|
||||
xForwardedPort: {"foo"},
|
||||
XForwardedProto: {"foo"},
|
||||
XForwardedFor: {"foo"},
|
||||
XForwardedURI: {"foo"},
|
||||
XForwardedMethod: {"foo"},
|
||||
XForwardedHost: {"foo"},
|
||||
XForwardedPort: {"foo"},
|
||||
xForwardedTLSClientCert: {"foo"},
|
||||
xForwardedTLSClientCertInfo: {"foo"},
|
||||
xForwardedPrefix: {"foo"},
|
||||
XForwardedPrefix: {"foo"},
|
||||
xRealIP: {"foo"},
|
||||
},
|
||||
expectedHeaders: map[string]string{
|
||||
xForwardedProto: "foo",
|
||||
xForwardedFor: "foo",
|
||||
xForwardedURI: "foo",
|
||||
xForwardedMethod: "foo",
|
||||
xForwardedHost: "foo",
|
||||
xForwardedPort: "foo",
|
||||
XForwardedProto: "foo",
|
||||
XForwardedFor: "foo",
|
||||
XForwardedURI: "foo",
|
||||
XForwardedMethod: "foo",
|
||||
XForwardedHost: "foo",
|
||||
XForwardedPort: "foo",
|
||||
xForwardedTLSClientCert: "foo",
|
||||
xForwardedTLSClientCertInfo: "foo",
|
||||
xForwardedPrefix: "foo",
|
||||
XForwardedPrefix: "foo",
|
||||
xRealIP: "foo",
|
||||
connection: "",
|
||||
},
|
||||
@ -583,6 +600,19 @@ func TestServeHTTP(t *testing.T) {
|
||||
"Foo": "bar",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "insecure false preserves non-matching underscore headers",
|
||||
insecure: false,
|
||||
remoteAddr: "10.0.1.101:80",
|
||||
incomingHeaders: map[string][]string{
|
||||
"X_custom_header": {"value"},
|
||||
"X_forwarded_proto": {"spoofed"},
|
||||
},
|
||||
expectedHeaders: map[string]string{
|
||||
"X_custom_header": "value",
|
||||
"X_forwarded_proto": "",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
|
||||
@ -36,8 +36,13 @@ func New(ctx context.Context, next http.Handler, config dynamic.StripPrefix, nam
|
||||
// Handle default value (here because of deprecation and the removal of setDefault).
|
||||
forceSlash := config.ForceSlash != nil && *config.ForceSlash
|
||||
|
||||
prefixes := make([]string, len(config.Prefixes))
|
||||
for i, p := range config.Prefixes {
|
||||
prefixes[i] = strings.TrimSpace(p)
|
||||
}
|
||||
|
||||
return &stripPrefix{
|
||||
prefixes: config.Prefixes,
|
||||
prefixes: prefixes,
|
||||
next: next,
|
||||
name: name,
|
||||
forceSlash: forceSlash,
|
||||
@ -55,19 +60,22 @@ func (s *stripPrefix) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
if req.URL.RawPath != "" {
|
||||
req.URL.RawPath = s.getRawPathStripped(req.URL.RawPath, prefix)
|
||||
}
|
||||
s.serveRequest(rw, req, strings.TrimSpace(prefix))
|
||||
return
|
||||
|
||||
// Here we are sanitizing the URL when the path is not empty,
|
||||
// as the JoinPath method is adding a leading slash if the path is empty
|
||||
// to be aligned with ensureLeadingSlash behavior.
|
||||
if req.URL.Path != "" {
|
||||
req.URL = req.URL.JoinPath()
|
||||
}
|
||||
|
||||
req.Header.Add(ForwardedPrefixHeader, prefix)
|
||||
req.RequestURI = req.URL.RequestURI()
|
||||
break
|
||||
}
|
||||
}
|
||||
s.next.ServeHTTP(rw, req)
|
||||
}
|
||||
|
||||
func (s *stripPrefix) serveRequest(rw http.ResponseWriter, req *http.Request, prefix string) {
|
||||
req.Header.Add(ForwardedPrefixHeader, prefix)
|
||||
req.RequestURI = req.URL.RequestURI()
|
||||
s.next.ServeHTTP(rw, req)
|
||||
}
|
||||
|
||||
func (s *stripPrefix) getPathStripped(urlPath, prefix string) string {
|
||||
if s.forceSlash {
|
||||
// Only for compatibility reason with the previous behavior,
|
||||
|
||||
@ -9,6 +9,7 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/traefik/traefik/v3/pkg/config/dynamic"
|
||||
"github.com/traefik/traefik/v3/pkg/testhelpers"
|
||||
"k8s.io/utils/ptr"
|
||||
)
|
||||
|
||||
func TestStripPrefix(t *testing.T) {
|
||||
@ -141,6 +142,40 @@ func TestStripPrefix(t *testing.T) {
|
||||
expectedRawPath: "/a%2Fb",
|
||||
expectedHeader: "/api/",
|
||||
},
|
||||
{
|
||||
desc: "dot in the path not stripped by the prefix",
|
||||
config: dynamic.StripPrefix{
|
||||
Prefixes: []string{"/api"},
|
||||
},
|
||||
path: "/api./foo",
|
||||
expectedStatusCode: http.StatusOK,
|
||||
expectedPath: "/foo",
|
||||
expectedRawPath: "",
|
||||
expectedHeader: "/api",
|
||||
},
|
||||
{
|
||||
desc: "multiple dots in the path not stripped by the prefix",
|
||||
config: dynamic.StripPrefix{
|
||||
Prefixes: []string{"/api"},
|
||||
},
|
||||
path: "/api../foo",
|
||||
expectedStatusCode: http.StatusOK,
|
||||
expectedPath: "/foo",
|
||||
expectedRawPath: "",
|
||||
expectedHeader: "/api",
|
||||
},
|
||||
{
|
||||
desc: "multiple dots in the path not stripped by the prefix with forceSlash",
|
||||
config: dynamic.StripPrefix{
|
||||
Prefixes: []string{"/api"},
|
||||
ForceSlash: ptr.To(true),
|
||||
},
|
||||
path: "/api../foo",
|
||||
expectedStatusCode: http.StatusOK,
|
||||
expectedPath: "/foo",
|
||||
expectedRawPath: "",
|
||||
expectedHeader: "/api",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
|
||||
@ -62,9 +62,15 @@ func (s *stripPrefixRegex) ServeHTTP(rw http.ResponseWriter, req *http.Request)
|
||||
req.URL.RawPath = ensureLeadingSlash(req.URL.RawPath[encodedPrefixLen(req.URL.RawPath, prefix):])
|
||||
}
|
||||
|
||||
// Here we are sanitizing the URL when the path is not empty,
|
||||
// as the JoinPath method is adding a leading slash if the path is empty
|
||||
// to be aligned with ensureLeadingSlash behavior.
|
||||
if req.URL.Path != "" {
|
||||
req.URL = req.URL.JoinPath()
|
||||
}
|
||||
|
||||
req.RequestURI = req.URL.RequestURI()
|
||||
s.next.ServeHTTP(rw, req)
|
||||
return
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -153,7 +153,7 @@ func TestStripPrefixRegex(t *testing.T) {
|
||||
path: "/b/ap%69/test",
|
||||
expectedStatusCode: http.StatusOK,
|
||||
expectedPath: "/test",
|
||||
expectedRawPath: "/test",
|
||||
expectedRawPath: "",
|
||||
expectedRequestURI: "/test",
|
||||
expectedHeader: "/b/api/",
|
||||
},
|
||||
@ -197,6 +197,26 @@ func TestStripPrefixRegex(t *testing.T) {
|
||||
expectedRequestURI: "/a%2Fb",
|
||||
expectedHeader: "/t /test",
|
||||
},
|
||||
{
|
||||
desc: "/api./foo",
|
||||
config: dynamic.StripPrefixRegex{Regex: []string{"/api"}},
|
||||
path: "/api./foo",
|
||||
expectedStatusCode: http.StatusOK,
|
||||
expectedPath: "/foo",
|
||||
expectedRawPath: "",
|
||||
expectedRequestURI: "/foo",
|
||||
expectedHeader: "/api",
|
||||
},
|
||||
{
|
||||
desc: "/api../foo",
|
||||
config: dynamic.StripPrefixRegex{Regex: []string{"/api"}},
|
||||
path: "/api../foo",
|
||||
expectedStatusCode: http.StatusOK,
|
||||
expectedPath: "/foo",
|
||||
expectedRawPath: "",
|
||||
expectedRequestURI: "/foo",
|
||||
expectedHeader: "/api",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
|
||||
@ -121,7 +121,7 @@ func (c *ChallengeHTTP) getTokenValue(ctx context.Context, token, domain string)
|
||||
}
|
||||
|
||||
func getPathParam(uri *url.URL) (string, error) {
|
||||
exp := regexp.MustCompile(fmt.Sprintf(`^%s([^/]+)/?$`, http01.ChallengePath("")))
|
||||
exp := regexp.MustCompile(fmt.Sprintf(`^%s([^/]+)/?$`, http01.PathPrefix))
|
||||
parts := exp.FindStringSubmatch(uri.Path)
|
||||
|
||||
if len(parts) != 2 {
|
||||
|
||||
@ -8,6 +8,7 @@ import (
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
"github.com/containerd/errdefs"
|
||||
"github.com/docker/cli/cli/connhelper"
|
||||
containertypes "github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/client"
|
||||
@ -39,7 +40,11 @@ type Shared struct {
|
||||
func inspectContainers(ctx context.Context, dockerClient client.ContainerAPIClient, containerID string) dockerData {
|
||||
containerInspected, err := dockerClient.ContainerInspect(ctx, containerID)
|
||||
if err != nil {
|
||||
log.Ctx(ctx).Warn().Err(err).Msgf("Failed to inspect container %s", containerID)
|
||||
if errdefs.IsNotFound(err) {
|
||||
log.Ctx(ctx).Debug().Err(err).Msgf("Failed to inspect container %s", containerID)
|
||||
} else {
|
||||
log.Ctx(ctx).Warn().Err(err).Msgf("Failed to inspect container %s", containerID)
|
||||
}
|
||||
return dockerData{}
|
||||
}
|
||||
|
||||
|
||||
@ -37,6 +37,16 @@ spec:
|
||||
port: 80
|
||||
middlewares:
|
||||
- name: cross-ns-stripprefix@kubernetescrd
|
||||
- match: Host(`foo.com`) && PathPrefix(`/chain`)
|
||||
kind: Rule
|
||||
priority: 12
|
||||
services:
|
||||
- name: whoami
|
||||
namespace: default
|
||||
port: 80
|
||||
middlewares:
|
||||
- name: test-chain
|
||||
- name: test-chain-cross-provider
|
||||
|
||||
---
|
||||
apiVersion: traefik.io/v1alpha1
|
||||
@ -50,6 +60,31 @@ spec:
|
||||
prefixes:
|
||||
- /stripit
|
||||
|
||||
---
|
||||
apiVersion: traefik.io/v1alpha1
|
||||
kind: Middleware
|
||||
metadata:
|
||||
name: test-chain
|
||||
namespace: default
|
||||
|
||||
spec:
|
||||
chain:
|
||||
middlewares:
|
||||
- name: stripprefix
|
||||
namespace: cross-ns
|
||||
|
||||
---
|
||||
apiVersion: traefik.io/v1alpha1
|
||||
kind: Middleware
|
||||
metadata:
|
||||
name: test-chain-cross-provider
|
||||
namespace: default
|
||||
|
||||
spec:
|
||||
chain:
|
||||
middlewares:
|
||||
- name: other-middleware@kubernetescrd
|
||||
|
||||
---
|
||||
apiVersion: traefik.io/v1alpha1
|
||||
kind: Middleware
|
||||
|
||||
@ -36,6 +36,8 @@ type ForwardAuthApplyConfiguration struct {
|
||||
// Address defines the authentication server address.
|
||||
Address *string `json:"address,omitempty"`
|
||||
// TrustForwardHeader defines whether to trust (ie: forward) all X-Forwarded-* headers.
|
||||
//
|
||||
// Deprecated: Use forwardedHeaders.trustedIPs at the EntryPoint level instead, and set trustForwardHeader to true on this middleware.
|
||||
TrustForwardHeader *bool `json:"trustForwardHeader,omitempty"`
|
||||
// AuthResponseHeaders defines the list of headers to copy from the authentication server response and set on forwarded request, replacing any existing conflicting headers.
|
||||
AuthResponseHeaders []string `json:"authResponseHeaders,omitempty"`
|
||||
|
||||
@ -303,13 +303,19 @@ func (p *Provider) loadConfigurationFromCRD(ctx context.Context, client Client)
|
||||
continue
|
||||
}
|
||||
|
||||
chain, err := createChainMiddleware(ctxMid, middleware.Namespace, middleware.Spec.Chain, p.AllowCrossNamespace)
|
||||
if err != nil {
|
||||
logger.Error().Err(err).Msg("Error while reading chain middleware")
|
||||
continue
|
||||
}
|
||||
|
||||
conf.HTTP.Middlewares[id] = &dynamic.Middleware{
|
||||
AddPrefix: middleware.Spec.AddPrefix,
|
||||
StripPrefix: middleware.Spec.StripPrefix,
|
||||
StripPrefixRegex: middleware.Spec.StripPrefixRegex,
|
||||
ReplacePath: middleware.Spec.ReplacePath,
|
||||
ReplacePathRegex: middleware.Spec.ReplacePathRegex,
|
||||
Chain: createChainMiddleware(ctxMid, middleware.Namespace, middleware.Spec.Chain),
|
||||
Chain: chain,
|
||||
IPWhiteList: middleware.Spec.IPWhiteList,
|
||||
IPAllowList: middleware.Spec.IPAllowList,
|
||||
Headers: middleware.Spec.Headers,
|
||||
@ -1272,13 +1278,20 @@ func loadAuthCredentials(secret *corev1.Secret) ([]string, error) {
|
||||
return credentials, nil
|
||||
}
|
||||
|
||||
func createChainMiddleware(ctx context.Context, namespace string, chain *traefikv1alpha1.Chain) *dynamic.Chain {
|
||||
func createChainMiddleware(ctx context.Context, parentNamespace string, chain *traefikv1alpha1.Chain, allowCrossNamespace bool) (*dynamic.Chain, error) {
|
||||
if chain == nil {
|
||||
return nil
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var mds []string
|
||||
for _, mi := range chain.Middlewares {
|
||||
if !allowCrossNamespace && strings.HasSuffix(mi.Name, providerNamespaceSeparator+ProviderName) {
|
||||
// Since we are not able to know if another namespace is in the name (namespace-name@kubernetescrd),
|
||||
// if the provider namespace kubernetescrd is used,
|
||||
// we don't allow this format to avoid cross-namespace references.
|
||||
return nil, fmt.Errorf("invalid reference to middleware %s: when allowCrossNamespace is disabled @kubernetescrd provider references are disallowed", mi.Name)
|
||||
}
|
||||
|
||||
if strings.Contains(mi.Name, providerNamespaceSeparator) {
|
||||
if len(mi.Namespace) > 0 {
|
||||
log.Ctx(ctx).Warn().Msgf("namespace %q is ignored in cross-provider context", mi.Namespace)
|
||||
@ -1287,13 +1300,19 @@ func createChainMiddleware(ctx context.Context, namespace string, chain *traefik
|
||||
continue
|
||||
}
|
||||
|
||||
ns := mi.Namespace
|
||||
if len(ns) == 0 {
|
||||
ns = namespace
|
||||
ns := parentNamespace
|
||||
if len(mi.Namespace) > 0 {
|
||||
if !isNamespaceAllowed(allowCrossNamespace, parentNamespace, mi.Namespace) {
|
||||
return nil, fmt.Errorf("middleware %s/%s is not in the chain namespace %s", mi.Namespace, mi.Name, parentNamespace)
|
||||
}
|
||||
|
||||
ns = mi.Namespace
|
||||
}
|
||||
|
||||
mds = append(mds, makeID(ns, mi.Name))
|
||||
}
|
||||
return &dynamic.Chain{Middlewares: mds}
|
||||
|
||||
return &dynamic.Chain{Middlewares: mds}, nil
|
||||
}
|
||||
|
||||
func buildTLSOptions(ctx context.Context, client Client) map[string]tls.Options {
|
||||
|
||||
@ -189,8 +189,8 @@ func makeMiddlewareKeys(ctx context.Context, namespace string, middlewares []tra
|
||||
if !allowCrossNamespace && strings.HasSuffix(mi.Name, providerNamespaceSeparator+ProviderName) {
|
||||
// Since we are not able to know if another namespace is in the name (namespace-name@kubernetescrd),
|
||||
// if the provider namespace kubernetescrd is used,
|
||||
// we don't allow this format to avoid cross namespace references.
|
||||
return nil, fmt.Errorf("invalid reference to middleware %s: with crossnamespace disallowed, the namespace field needs to be explicitly specified", mi.Name)
|
||||
// we don't allow this format to avoid cross-namespace references.
|
||||
return nil, fmt.Errorf("invalid reference to middleware %s: when allowCrossNamespace is disabled @kubernetescrd provider references are disallowed", mi.Name)
|
||||
}
|
||||
|
||||
if strings.Contains(name, providerNamespaceSeparator) {
|
||||
|
||||
@ -7405,6 +7405,16 @@ func TestCrossNamespace(t *testing.T) {
|
||||
Priority: 12,
|
||||
Middlewares: []string{"default-test-errorpage"},
|
||||
},
|
||||
"default-test-crossnamespace-route-4932ffbbcd99474df323": {
|
||||
EntryPoints: []string{"foo"},
|
||||
Service: "default-test-crossnamespace-route-4932ffbbcd99474df323",
|
||||
Rule: "Host(`foo.com`) && PathPrefix(`/chain`)",
|
||||
Priority: 12,
|
||||
Middlewares: []string{
|
||||
"default-test-chain",
|
||||
"default-test-chain-cross-provider",
|
||||
},
|
||||
},
|
||||
},
|
||||
Middlewares: map[string]*dynamic.Middleware{
|
||||
"cross-ns-stripprefix": {
|
||||
@ -7431,6 +7441,23 @@ func TestCrossNamespace(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
"default-test-crossnamespace-route-4932ffbbcd99474df323": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
Strategy: dynamic.BalancerStrategyWRR,
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://10.10.0.1:80",
|
||||
},
|
||||
{
|
||||
URL: "http://10.10.0.2:80",
|
||||
},
|
||||
},
|
||||
PassHostHeader: pointer(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
ServersTransports: map[string]*dynamic.ServersTransport{},
|
||||
},
|
||||
@ -7477,6 +7504,16 @@ func TestCrossNamespace(t *testing.T) {
|
||||
Priority: 12,
|
||||
Middlewares: []string{"cross-ns-stripprefix@kubernetescrd"},
|
||||
},
|
||||
"default-test-crossnamespace-route-4932ffbbcd99474df323": {
|
||||
EntryPoints: []string{"foo"},
|
||||
Service: "default-test-crossnamespace-route-4932ffbbcd99474df323",
|
||||
Rule: "Host(`foo.com`) && PathPrefix(`/chain`)",
|
||||
Priority: 12,
|
||||
Middlewares: []string{
|
||||
"default-test-chain",
|
||||
"default-test-chain-cross-provider",
|
||||
},
|
||||
},
|
||||
},
|
||||
Middlewares: map[string]*dynamic.Middleware{
|
||||
"cross-ns-stripprefix": {
|
||||
@ -7491,6 +7528,16 @@ func TestCrossNamespace(t *testing.T) {
|
||||
Query: "/{status}.html",
|
||||
},
|
||||
},
|
||||
"default-test-chain": {
|
||||
Chain: &dynamic.Chain{
|
||||
Middlewares: []string{"cross-ns-stripprefix"},
|
||||
},
|
||||
},
|
||||
"default-test-chain-cross-provider": {
|
||||
Chain: &dynamic.Chain{
|
||||
Middlewares: []string{"other-middleware@kubernetescrd"},
|
||||
},
|
||||
},
|
||||
},
|
||||
Services: map[string]*dynamic.Service{
|
||||
"default-test-crossnamespace-route-6b204d94623b3df4370c": {
|
||||
@ -7561,6 +7608,23 @@ func TestCrossNamespace(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
"default-test-crossnamespace-route-4932ffbbcd99474df323": {
|
||||
LoadBalancer: &dynamic.ServersLoadBalancer{
|
||||
Strategy: dynamic.BalancerStrategyWRR,
|
||||
Servers: []dynamic.Server{
|
||||
{
|
||||
URL: "http://10.10.0.1:80",
|
||||
},
|
||||
{
|
||||
URL: "http://10.10.0.2:80",
|
||||
},
|
||||
},
|
||||
PassHostHeader: pointer(true),
|
||||
ResponseForwarding: &dynamic.ResponseForwarding{
|
||||
FlushInterval: ptypes.Duration(100 * time.Millisecond),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
ServersTransports: map[string]*dynamic.ServersTransport{},
|
||||
},
|
||||
|
||||
@ -187,7 +187,9 @@ type ForwardAuth struct {
|
||||
// Address defines the authentication server address.
|
||||
Address string `json:"address,omitempty"`
|
||||
// TrustForwardHeader defines whether to trust (ie: forward) all X-Forwarded-* headers.
|
||||
TrustForwardHeader bool `json:"trustForwardHeader,omitempty"`
|
||||
//
|
||||
// Deprecated: Use forwardedHeaders.trustedIPs at the EntryPoint level instead, and set trustForwardHeader to true on this middleware.
|
||||
TrustForwardHeader *bool `json:"trustForwardHeader,omitempty"`
|
||||
// AuthResponseHeaders defines the list of headers to copy from the authentication server response and set on forwarded request, replacing any existing conflicting headers.
|
||||
AuthResponseHeaders []string `json:"authResponseHeaders,omitempty"`
|
||||
// AuthResponseHeadersRegex defines the regex to match headers to copy from the authentication server response and set on forwarded request, after stripping all headers that match the regex.
|
||||
|
||||
@ -331,6 +331,11 @@ func (in *FailoverError) DeepCopy() *FailoverError {
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ForwardAuth) DeepCopyInto(out *ForwardAuth) {
|
||||
*out = *in
|
||||
if in.TrustForwardHeader != nil {
|
||||
in, out := &in.TrustForwardHeader, &out.TrustForwardHeader
|
||||
*out = new(bool)
|
||||
**out = **in
|
||||
}
|
||||
if in.AuthResponseHeaders != nil {
|
||||
in, out := &in.AuthResponseHeaders, &out.AuthResponseHeaders
|
||||
*out = make([]string, len(*in))
|
||||
|
||||
@ -443,7 +443,7 @@ func Test_buildConfiguration(t *testing.T) {
|
||||
InsecureSkipVerify: true,
|
||||
CAOptional: pointer(true),
|
||||
},
|
||||
TrustForwardHeader: true,
|
||||
TrustForwardHeader: pointer(true),
|
||||
AuthResponseHeaders: []string{
|
||||
"foobar",
|
||||
"foobar",
|
||||
|
||||
@ -275,7 +275,7 @@ func init() {
|
||||
Key: "cert.pem",
|
||||
InsecureSkipVerify: true,
|
||||
},
|
||||
TrustForwardHeader: true,
|
||||
TrustForwardHeader: pointer(true),
|
||||
AuthResponseHeaders: []string{"foo"},
|
||||
AuthResponseHeadersRegex: "foo",
|
||||
AuthRequestHeaders: []string{"foo"},
|
||||
|
||||
@ -139,7 +139,7 @@ func TestMirroringWithBody(t *testing.T) {
|
||||
const numMirrors = 10
|
||||
|
||||
var (
|
||||
countMirror int32
|
||||
countMirror atomic.Int32
|
||||
body = []byte(`body`)
|
||||
)
|
||||
|
||||
@ -161,7 +161,7 @@ func TestMirroringWithBody(t *testing.T) {
|
||||
bb, err := io.ReadAll(r.Body)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, body, bb)
|
||||
atomic.AddInt32(&countMirror, 1)
|
||||
countMirror.Add(1)
|
||||
}), 100)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
@ -172,7 +172,7 @@ func TestMirroringWithBody(t *testing.T) {
|
||||
|
||||
pool.Stop()
|
||||
|
||||
val := atomic.LoadInt32(&countMirror)
|
||||
val := countMirror.Load()
|
||||
assert.Equal(t, numMirrors, int(val))
|
||||
}
|
||||
|
||||
@ -180,7 +180,7 @@ func TestMirroringWithIgnoredBody(t *testing.T) {
|
||||
const numMirrors = 10
|
||||
|
||||
var (
|
||||
countMirror int32
|
||||
countMirror atomic.Int32
|
||||
body = []byte(`body`)
|
||||
emptyBody = []byte(``)
|
||||
)
|
||||
@ -203,7 +203,7 @@ func TestMirroringWithIgnoredBody(t *testing.T) {
|
||||
bb, err := io.ReadAll(r.Body)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, emptyBody, bb)
|
||||
atomic.AddInt32(&countMirror, 1)
|
||||
countMirror.Add(1)
|
||||
}), 100)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
@ -214,7 +214,7 @@ func TestMirroringWithIgnoredBody(t *testing.T) {
|
||||
|
||||
pool.Stop()
|
||||
|
||||
val := atomic.LoadInt32(&countMirror)
|
||||
val := countMirror.Load()
|
||||
assert.Equal(t, numMirrors, int(val))
|
||||
}
|
||||
|
||||
|
||||
@ -8,6 +8,7 @@ import (
|
||||
"hash/fnv"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@ -150,7 +151,7 @@ func (s *Sticky) WriteStickyCookie(rw http.ResponseWriter, name string) error {
|
||||
}
|
||||
|
||||
func convertSameSite(sameSite string) http.SameSite {
|
||||
switch sameSite {
|
||||
switch strings.ToLower(sameSite) {
|
||||
case "none":
|
||||
return http.SameSiteNoneMode
|
||||
case "lax":
|
||||
|
||||
@ -140,3 +140,27 @@ func TestSticky_WriteStickyCookie(t *testing.T) {
|
||||
assert.Equal(t, "/foo", cookie.Path)
|
||||
assert.Equal(t, "foo.com", cookie.Domain)
|
||||
}
|
||||
|
||||
func TestConvertSameSite_CaseInsensitive(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string
|
||||
expected http.SameSite
|
||||
}{
|
||||
{"none", http.SameSiteNoneMode},
|
||||
{"None", http.SameSiteNoneMode},
|
||||
{"NONE", http.SameSiteNoneMode},
|
||||
{"lax", http.SameSiteLaxMode},
|
||||
{"Lax", http.SameSiteLaxMode},
|
||||
{"LAX", http.SameSiteLaxMode},
|
||||
{"strict", http.SameSiteStrictMode},
|
||||
{"Strict", http.SameSiteStrictMode},
|
||||
{"STRICT", http.SameSiteStrictMode},
|
||||
{"", http.SameSiteDefaultMode},
|
||||
{"invalid", http.SameSiteDefaultMode},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.input, func(t *testing.T) {
|
||||
assert.Equal(t, tt.expected, convertSameSite(tt.input))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,11 +4,11 @@ RepositoryName = "traefik"
|
||||
OutputType = "file"
|
||||
FileName = "traefik_changelog.md"
|
||||
|
||||
# example new bugfix v3.6.13
|
||||
# example new bugfix v3.6.14
|
||||
CurrentRef = "v3.6"
|
||||
PreviousRef = "v3.6.12"
|
||||
PreviousRef = "v3.6.13"
|
||||
BaseBranch = "v3.6"
|
||||
FutureCurrentRefName = "v3.6.13"
|
||||
FutureCurrentRefName = "v3.6.14"
|
||||
|
||||
ThresholdPreviousRef = 10000
|
||||
ThresholdCurrentRef = 10000
|
||||
|
||||
@ -103,6 +103,7 @@
|
||||
},
|
||||
"packageManager": "yarn@4.13.0",
|
||||
"resolutions": {
|
||||
"form-data": "4.0.4",
|
||||
"lodash": "4.18.1",
|
||||
"lodash-es": "4.18.1"
|
||||
}
|
||||
|
||||
@ -10247,15 +10247,16 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"form-data@npm:^4.0.0":
|
||||
version: 4.0.2
|
||||
resolution: "form-data@npm:4.0.2"
|
||||
"form-data@npm:4.0.4":
|
||||
version: 4.0.4
|
||||
resolution: "form-data@npm:4.0.4"
|
||||
dependencies:
|
||||
asynckit: "npm:^0.4.0"
|
||||
combined-stream: "npm:^1.0.8"
|
||||
es-set-tostringtag: "npm:^2.1.0"
|
||||
hasown: "npm:^2.0.2"
|
||||
mime-types: "npm:^2.1.12"
|
||||
checksum: 10c0/e534b0cf025c831a0929bf4b9bbe1a9a6b03e273a8161f9947286b9b13bf8fb279c6944aae0070c4c311100c6d6dbb815cd955dc217728caf73fad8dc5b8ee9c
|
||||
checksum: 10c0/373525a9a034b9d57073e55eab79e501a714ffac02e7a9b01be1c820780652b16e4101819785e1e18f8d98f0aee866cc654d660a435c378e16a72f2e7cac9695
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user