mirror of
https://github.com/traefik/traefik.git
synced 2026-06-17 19:09:29 +03:00
Merge v2.11 into v3.6
This commit is contained in:
@@ -10,7 +10,6 @@ on:
|
||||
- 'script/gcg/**'
|
||||
|
||||
env:
|
||||
GO_VERSION: '1.25'
|
||||
CGO_ENABLED: 0
|
||||
|
||||
jobs:
|
||||
@@ -55,12 +54,12 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Go ${{ env.GO_VERSION }}
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0
|
||||
env:
|
||||
ImageOS: ${{ matrix.os }}-${{ matrix.arch }}-${{ matrix.goarm }}
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
go-version-file: '.go-version'
|
||||
check-latest: true
|
||||
|
||||
- name: Artifact webui
|
||||
|
||||
@@ -34,7 +34,8 @@ jobs:
|
||||
uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0
|
||||
if: ${{ matrix.language == 'go' }}
|
||||
with:
|
||||
go-version-file: 'go.mod'
|
||||
go-version-file: '.go-version'
|
||||
check-latest: true
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
|
||||
@@ -7,7 +7,6 @@ on:
|
||||
- v*
|
||||
|
||||
env:
|
||||
GO_VERSION: '1.25'
|
||||
CGO_ENABLED: 0
|
||||
|
||||
jobs:
|
||||
@@ -27,12 +26,12 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Go ${{ env.GO_VERSION }}
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0
|
||||
env:
|
||||
ImageOS: ${{ matrix.os }}-${{ matrix.arch }}-${{ matrix.goarm }}
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
go-version-file: '.go-version'
|
||||
check-latest: true
|
||||
|
||||
- name: Build
|
||||
|
||||
@@ -6,7 +6,6 @@ on:
|
||||
- 'v*.*.*'
|
||||
|
||||
env:
|
||||
GO_VERSION: '1.25'
|
||||
CGO_ENABLED: 0
|
||||
VERSION: ${{ github.ref_name }}
|
||||
TRAEFIKER_EMAIL: "traefiker@traefik.io"
|
||||
@@ -34,13 +33,13 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Go ${{ env.GO_VERSION }}
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0
|
||||
env:
|
||||
# Ensure cache consistency on Linux, see https://github.com/actions/setup-go/pull/383
|
||||
ImageOS: ${{ matrix.os }}
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
go-version-file: '.go-version'
|
||||
check-latest: true
|
||||
|
||||
- name: Artifact webui
|
||||
@@ -133,4 +132,3 @@ jobs:
|
||||
gh release create ${VERSION} ./dist/**/traefik*.{zip,tar.gz} ./dist/traefik*.{tar.gz,txt} --repo traefik/traefik --title ${VERSION} --notes ${VERSION} --latest=false
|
||||
|
||||
./script/deploy.sh
|
||||
|
||||
|
||||
@@ -12,7 +12,6 @@ on:
|
||||
- 'integration/integration_test.go'
|
||||
|
||||
env:
|
||||
GO_VERSION: '1.25'
|
||||
CGO_ENABLED: 0
|
||||
|
||||
jobs:
|
||||
@@ -26,10 +25,11 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Go ${{ env.GO_VERSION }}
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
go-version-file: '.go-version'
|
||||
check-latest: true
|
||||
|
||||
- name: Avoid generating webui
|
||||
run: |
|
||||
|
||||
@@ -10,7 +10,6 @@ on:
|
||||
- 'script/gcg/**'
|
||||
|
||||
env:
|
||||
GO_VERSION: '1.25'
|
||||
CGO_ENABLED: 0
|
||||
|
||||
jobs:
|
||||
@@ -24,10 +23,10 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Go ${{ env.GO_VERSION }}
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
go-version-file: '.go-version'
|
||||
check-latest: true
|
||||
|
||||
- name: Avoid generating webui
|
||||
@@ -42,7 +41,7 @@ jobs:
|
||||
with:
|
||||
path: |
|
||||
~/.cache/go-build
|
||||
key: ${{ runner.os }}-go-build-cache-${{ env.GO_VERSION }}-${{ hashFiles('**/go.sum') }}
|
||||
key: ${{ runner.os }}-go-build-cache-${{ hashFiles('**/go.mod', '**/go.sum') }}
|
||||
|
||||
- name: Artifact traefik binary
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
@@ -67,10 +66,10 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Go ${{ env.GO_VERSION }}
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
go-version-file: '.go-version'
|
||||
check-latest: true
|
||||
|
||||
- name: Download traefik binary
|
||||
@@ -87,7 +86,7 @@ jobs:
|
||||
with:
|
||||
path: |
|
||||
~/.cache/go-build
|
||||
key: ${{ runner.os }}-go-build-cache-${{ env.GO_VERSION }}-${{ hashFiles('**/go.sum') }}
|
||||
key: ${{ runner.os }}-go-build-cache-${{ hashFiles('**/go.mod', '**/go.sum') }}
|
||||
|
||||
- name: Generate go test Slice
|
||||
id: test_split
|
||||
|
||||
@@ -12,7 +12,6 @@ on:
|
||||
- 'integration/integration_test.go'
|
||||
|
||||
env:
|
||||
GO_VERSION: '1.25'
|
||||
CGO_ENABLED: 0
|
||||
|
||||
jobs:
|
||||
@@ -26,10 +25,11 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Go ${{ env.GO_VERSION }}
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
go-version-file: '.go-version'
|
||||
check-latest: true
|
||||
|
||||
- name: Set up KO
|
||||
uses: ko-build/setup-ko@ace48d793556083a76f1e3e6068850c1f4a369aa # v0.6
|
||||
|
||||
@@ -9,9 +9,6 @@ on:
|
||||
- '**.md'
|
||||
- 'script/gcg/**'
|
||||
|
||||
env:
|
||||
GO_VERSION: '1.25'
|
||||
|
||||
jobs:
|
||||
generate-packages:
|
||||
name: List Go Packages
|
||||
@@ -24,10 +21,10 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Go ${{ env.GO_VERSION }}
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
go-version-file: '.go-version'
|
||||
check-latest: true
|
||||
|
||||
- name: Generate matrix
|
||||
@@ -50,10 +47,10 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Go ${{ env.GO_VERSION }}
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
go-version-file: '.go-version'
|
||||
check-latest: true
|
||||
|
||||
- name: Tests
|
||||
|
||||
@@ -6,8 +6,7 @@ on:
|
||||
- '*'
|
||||
|
||||
env:
|
||||
GO_VERSION: '1.25'
|
||||
GOLANGCI_LINT_VERSION: v2.8.0
|
||||
GOLANGCI_LINT_VERSION: v2.10.1
|
||||
MISSPELL_VERSION: v0.7.0
|
||||
|
||||
jobs:
|
||||
@@ -21,10 +20,10 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Go ${{ env.GO_VERSION }}
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
go-version-file: '.go-version'
|
||||
check-latest: true
|
||||
|
||||
- name: golangci-lint
|
||||
@@ -41,10 +40,10 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Go ${{ env.GO_VERSION }}
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
go-version-file: '.go-version'
|
||||
check-latest: true
|
||||
|
||||
- name: Install misspell ${{ env.MISSPELL_VERSION }}
|
||||
@@ -62,10 +61,10 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Go ${{ env.GO_VERSION }}
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
go-version-file: '.go-version'
|
||||
check-latest: true
|
||||
|
||||
- name: go generate
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
1.25.7
|
||||
+1
-1
@@ -312,7 +312,7 @@ linters:
|
||||
text: 'var-naming: avoid meaningless package names'
|
||||
linters:
|
||||
- revive
|
||||
- path: (pkg/muxer/http/.+|pkg/provider/http/.+)\.go
|
||||
- path: ((cmd|pkg)/version/.*|pkg/config/runtime/.*|pkg/log/.*|pkg/(middlewares/)?metrics/.*|pkg/muxer/http/.+|pkg/provider/http/.+|pkg/tls/.+|pkg/proxy/httputil/.+|pkg/observability/metrics/.+)\.go
|
||||
text: 'var-naming: avoid package names that conflict with Go standard library package names'
|
||||
linters:
|
||||
- revive
|
||||
|
||||
@@ -1,3 +1,11 @@
|
||||
## [v2.11.38](https://github.com/traefik/traefik/tree/v2.11.38) (2026-02-23)
|
||||
[All Commits](https://github.com/traefik/traefik/compare/v2.11.37...v2.11.38)
|
||||
|
||||
**Bug fixes:**
|
||||
- **[middleware]** Fix case sensitivity on x-forwarded headers for Connection ([#12690](https://github.com/traefik/traefik/pull/12690) by [LBF38](https://github.com/LBF38))
|
||||
- **[middleware, authentication]** Add maxResponseBodySize configuration to forwardAuth middleware ([#12694](https://github.com/traefik/traefik/pull/12694) by [gndz07](https://github.com/gndz07))
|
||||
- **[server]** Fix TLS handshake error handling ([#12692](https://github.com/traefik/traefik/pull/12692) by [juliens](https://github.com/juliens))
|
||||
|
||||
## [v3.6.8](https://github.com/traefik/traefik/tree/v3.6.8) (2026-02-11)
|
||||
[All Commits](https://github.com/traefik/traefik/compare/v3.6.7...v3.6.8)
|
||||
|
||||
|
||||
@@ -158,7 +158,7 @@ func (c Centrifuge) run(sc *types.Scope, rootPkg string, pkgName string) map[str
|
||||
|
||||
func (c Centrifuge) writeStruct(name string, obj *types.Struct, rootPkg string, elt *File) string {
|
||||
b := strings.Builder{}
|
||||
b.WriteString(fmt.Sprintf("type %s struct {\n", name))
|
||||
fmt.Fprintf(&b, "type %s struct {\n", name)
|
||||
|
||||
for i := range obj.NumFields() {
|
||||
field := obj.Field(i)
|
||||
@@ -175,7 +175,7 @@ func (c Centrifuge) writeStruct(name string, obj *types.Struct, rootPkg string,
|
||||
fType := c.TypeCleaner(field.Type(), rootPkg)
|
||||
|
||||
if field.Embedded() {
|
||||
b.WriteString(fmt.Sprintf("\t%s\n", fType))
|
||||
fmt.Fprintf(&b, "\t%s\n", fType)
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -184,10 +184,10 @@ func (c Centrifuge) writeStruct(name string, obj *types.Struct, rootPkg string,
|
||||
continue
|
||||
}
|
||||
|
||||
b.WriteString(fmt.Sprintf("\t%s %s", field.Name(), fType))
|
||||
fmt.Fprintf(&b, "\t%s %s", field.Name(), fType)
|
||||
|
||||
if ok {
|
||||
b.WriteString(fmt.Sprintf(" `json:\"%s\"`", strings.Join(values, ",")))
|
||||
fmt.Fprintf(&b, " `json:\"%s\"`", strings.Join(values, ","))
|
||||
}
|
||||
|
||||
b.WriteString("\n")
|
||||
|
||||
@@ -785,4 +785,54 @@ http:
|
||||
preserveRequestMethod = true
|
||||
```
|
||||
|
||||
|
||||
### `maxResponseBodySize`
|
||||
|
||||
_Optional, Default=-1_
|
||||
|
||||
The `maxResponseBodySize` option defines the maximum allowed response body size in bytes from the authentication server.
|
||||
If the response body exceeds the configured limit, the request is rejected with a 401 (Unauthorized) status.
|
||||
If left unset, the request body size is unrestricted which can have performance or security implications.
|
||||
|
||||
```yaml tab="Docker"
|
||||
labels:
|
||||
- "traefik.http.middlewares.test-auth.forwardauth.maxResponseBodySize=10000"
|
||||
```
|
||||
|
||||
```yaml tab="Kubernetes"
|
||||
apiVersion: traefik.io/v1alpha1
|
||||
kind: Middleware
|
||||
metadata:
|
||||
name: test-auth
|
||||
spec:
|
||||
forwardAuth:
|
||||
address: https://example.com/auth
|
||||
maxResponseBodySize: 10000
|
||||
```
|
||||
|
||||
```yaml tab="Consul Catalog"
|
||||
- "traefik.http.middlewares.test-auth.forwardauth.maxResponseBodySize=10000"
|
||||
```
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
http:
|
||||
middlewares:
|
||||
test-auth:
|
||||
forwardAuth:
|
||||
address: "https://example.com/auth"
|
||||
maxResponseBodySize: 10000
|
||||
```
|
||||
|
||||
```toml tab="File (TOML)"
|
||||
[http.middlewares]
|
||||
[http.middlewares.test-auth.forwardAuth]
|
||||
address = "https://example.com/auth"
|
||||
maxResponseBodySize = 10000
|
||||
```
|
||||
|
||||
!!! warning
|
||||
|
||||
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.
|
||||
|
||||
{% include-markdown "includes/traefik-for-business-applications.md" %}
|
||||
|
||||
@@ -609,3 +609,24 @@ for more details.
|
||||
### Health Check Request Path
|
||||
|
||||
Since `v3.6.8`, the configured path for the health check request is now verified to be a relative URL, and the health check will fail if it is not.
|
||||
|
||||
## v3.6.9
|
||||
|
||||
### `maxResponseBodySize` configuration on ForwardAuth middleware
|
||||
|
||||
In `v3.6.9`, a new `maxResponseBodySize` option has been added to the ForwardAuth middleware configuration.
|
||||
The default value for this option is -1, which means there is no limit to the response body size.
|
||||
However, it is strongly recommended to set this option to a suitable value to avoid performance and security issues,
|
||||
such as DoS attacks and memory exhaustion.
|
||||
|
||||
Please check out the [ForwardAuth](../reference/routing-configuration/http/middlewares/forwardauth.md#maxresponsebodysize) middleware documentation for more details.
|
||||
|
||||
### Kubernetes CRD Provider
|
||||
|
||||
To use the new `maxResponseBodySize` option in the ForwardAuth middleware with the Kubernetes CRD provider, you need to update your CRDs.
|
||||
|
||||
**Apply Updated CRDs:**
|
||||
|
||||
```shell
|
||||
kubectl apply -f https://raw.githubusercontent.com/traefik/traefik/v3.6/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml
|
||||
```
|
||||
|
||||
@@ -43,6 +43,7 @@
|
||||
- "traefik.http.middlewares.middleware10.forwardauth.forwardbody=true"
|
||||
- "traefik.http.middlewares.middleware10.forwardauth.headerfield=foobar"
|
||||
- "traefik.http.middlewares.middleware10.forwardauth.maxbodysize=42"
|
||||
- "traefik.http.middlewares.middleware10.forwardauth.maxresponsebodysize=42"
|
||||
- "traefik.http.middlewares.middleware10.forwardauth.preservelocationheader=true"
|
||||
- "traefik.http.middlewares.middleware10.forwardauth.preserverequestmethod=true"
|
||||
- "traefik.http.middlewares.middleware10.forwardauth.tls.ca=foobar"
|
||||
|
||||
@@ -207,6 +207,7 @@
|
||||
headerField = "foobar"
|
||||
forwardBody = true
|
||||
maxBodySize = 42
|
||||
maxResponseBodySize = 42
|
||||
preserveLocationHeader = true
|
||||
preserveRequestMethod = true
|
||||
[http.middlewares.Middleware10.forwardAuth.tls]
|
||||
|
||||
@@ -231,6 +231,7 @@ http:
|
||||
headerField: foobar
|
||||
forwardBody: true
|
||||
maxBodySize: 42
|
||||
maxResponseBodySize: 42
|
||||
preserveLocationHeader: true
|
||||
preserveRequestMethod: true
|
||||
Middleware11:
|
||||
|
||||
@@ -1403,6 +1403,11 @@ spec:
|
||||
allowed to be forwarded to the authentication server.
|
||||
format: int64
|
||||
type: integer
|
||||
maxResponseBodySize:
|
||||
description: MaxResponseBodySize defines the maximum body size
|
||||
in bytes allowed in the response from the authentication server.
|
||||
format: int64
|
||||
type: integer
|
||||
preserveLocationHeader:
|
||||
description: PreserveLocationHeader defines whether to forward
|
||||
the Location header to the client as is or prefix it with the
|
||||
|
||||
@@ -56,6 +56,7 @@ THIS FILE MUST NOT BE EDITED BY HAND
|
||||
| <a id="opt-traefikhttpmiddlewaresMiddleware10forwardAuthforwardBody" href="#opt-traefikhttpmiddlewaresMiddleware10forwardAuthforwardBody" title="#opt-traefikhttpmiddlewaresMiddleware10forwardAuthforwardBody">`traefik/http/middlewares/Middleware10/forwardAuth/forwardBody`</a> | `true` |
|
||||
| <a id="opt-traefikhttpmiddlewaresMiddleware10forwardAuthheaderField" href="#opt-traefikhttpmiddlewaresMiddleware10forwardAuthheaderField" title="#opt-traefikhttpmiddlewaresMiddleware10forwardAuthheaderField">`traefik/http/middlewares/Middleware10/forwardAuth/headerField`</a> | `foobar` |
|
||||
| <a id="opt-traefikhttpmiddlewaresMiddleware10forwardAuthmaxBodySize" href="#opt-traefikhttpmiddlewaresMiddleware10forwardAuthmaxBodySize" title="#opt-traefikhttpmiddlewaresMiddleware10forwardAuthmaxBodySize">`traefik/http/middlewares/Middleware10/forwardAuth/maxBodySize`</a> | `42` |
|
||||
| <a id="opt-traefikhttpmiddlewaresMiddleware10forwardAuthmaxResponseBodySize" href="#opt-traefikhttpmiddlewaresMiddleware10forwardAuthmaxResponseBodySize" title="#opt-traefikhttpmiddlewaresMiddleware10forwardAuthmaxResponseBodySize">`traefik/http/middlewares/Middleware10/forwardAuth/maxResponseBodySize`</a> | `42` |
|
||||
| <a id="opt-traefikhttpmiddlewaresMiddleware10forwardAuthpreserveLocationHeader" href="#opt-traefikhttpmiddlewaresMiddleware10forwardAuthpreserveLocationHeader" title="#opt-traefikhttpmiddlewaresMiddleware10forwardAuthpreserveLocationHeader">`traefik/http/middlewares/Middleware10/forwardAuth/preserveLocationHeader`</a> | `true` |
|
||||
| <a id="opt-traefikhttpmiddlewaresMiddleware10forwardAuthpreserveRequestMethod" href="#opt-traefikhttpmiddlewaresMiddleware10forwardAuthpreserveRequestMethod" title="#opt-traefikhttpmiddlewaresMiddleware10forwardAuthpreserveRequestMethod">`traefik/http/middlewares/Middleware10/forwardAuth/preserveRequestMethod`</a> | `true` |
|
||||
| <a id="opt-traefikhttpmiddlewaresMiddleware10forwardAuthtlsca" href="#opt-traefikhttpmiddlewaresMiddleware10forwardAuthtlsca" title="#opt-traefikhttpmiddlewaresMiddleware10forwardAuthtlsca">`traefik/http/middlewares/Middleware10/forwardAuth/tls/ca`</a> | `foobar` |
|
||||
|
||||
@@ -570,6 +570,11 @@ spec:
|
||||
allowed to be forwarded to the authentication server.
|
||||
format: int64
|
||||
type: integer
|
||||
maxResponseBodySize:
|
||||
description: MaxResponseBodySize defines the maximum body size
|
||||
in bytes allowed in the response from the authentication server.
|
||||
format: int64
|
||||
type: integer
|
||||
preserveLocationHeader:
|
||||
description: PreserveLocationHeader defines whether to forward
|
||||
the Location header to the client as is or prefix it with the
|
||||
|
||||
@@ -63,6 +63,7 @@ spec:
|
||||
| <a id="opt-addAuthCookiesToResponse" href="#opt-addAuthCookiesToResponse" title="#opt-addAuthCookiesToResponse">`addAuthCookiesToResponse`</a> | List of cookies to copy from the authentication server to the response, replacing any existing conflicting cookie from the forwarded response.<br /> Please note that all backend cookies matching the configured list will not be added to the response. | [] | No |
|
||||
| <a id="opt-forwardBody" href="#opt-forwardBody" title="#opt-forwardBody">`forwardBody`</a> | Sets the `forwardBody` option to `true` to send the Body. As body is read inside Traefik before forwarding, this breaks streaming. | false | No |
|
||||
| <a id="opt-maxBodySize" href="#opt-maxBodySize" title="#opt-maxBodySize">`maxBodySize`</a> | Set the `maxBodySize` to limit the body size in bytes. If body is bigger than this, it returns a 401 (unauthorized). If left unset, the request body size is unrestricted which can have performance or security implications. < br/>More information [here](#maxbodysize).| -1 | No |
|
||||
| <a id="opt-maxResponseBodySize" href="#opt-maxResponseBodySize" title="#opt-maxResponseBodySize">`maxResponseBodySize`</a> | Set the `maxResponseBodySize` to limit the response body size from the authentication server in bytes. If the response body exceeds this limit, it returns a 401 (unauthorized). If left unset, the response body size is unrestricted which can have performance or security implications. <br/>More information [here](#maxresponsebodysize).| -1 | No |
|
||||
| <a id="opt-headerField" href="#opt-headerField" title="#opt-headerField">`headerField`</a> | Defines a header field to store the authenticated user. | "" | No |
|
||||
| <a id="opt-preserveLocationHeader" href="#opt-preserveLocationHeader" title="#opt-preserveLocationHeader">`preserveLocationHeader`</a> | Defines whether to forward the Location header to the client as is or prefix it with the domain name of the authentication server. | false | No |
|
||||
| <a id="opt-preserveRequestMethod" href="#opt-preserveRequestMethod" title="#opt-preserveRequestMethod">`preserveRequestMethod`</a> | Defines whether to preserve the original request method while forwarding the request to the authentication server. | false | No |
|
||||
@@ -115,6 +116,17 @@ maxBodySize: 104857600 # 100MB in bytes
|
||||
- **File Uploads**: Set based on your maximum expected file size
|
||||
- **High-Traffic Services**: Use smaller limits to prevent resource exhaustion
|
||||
|
||||
### maxResponseBodySize
|
||||
|
||||
The `maxResponseBodySize` option defines the maximum allowed response body size in bytes from the authentication server.
|
||||
If the response body exceeds the configured limit, the request is rejected with a 401 (Unauthorized) status.
|
||||
If left unset, the request body size is unrestricted which can have performance or security implications.
|
||||
|
||||
!!! warning
|
||||
|
||||
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.
|
||||
|
||||
## Forward-Request Headers
|
||||
|
||||
The following request properties are provided to the forward-auth target endpoint as `X-Forwarded-` headers.
|
||||
|
||||
@@ -205,6 +205,7 @@
|
||||
authResponseHeaders = ["foobar", "foobar"]
|
||||
authResponseHeadersRegex = "foobar"
|
||||
authRequestHeaders = ["foobar", "foobar"]
|
||||
maxResponseBodySize = 42
|
||||
addAuthCookiesToResponse = ["foobar", "foobar"]
|
||||
headerField = "foobar"
|
||||
forwardBody = true
|
||||
|
||||
@@ -231,6 +231,7 @@ http:
|
||||
authRequestHeaders:
|
||||
- foobar
|
||||
- foobar
|
||||
maxResponseBodySize: 42
|
||||
addAuthCookiesToResponse:
|
||||
- foobar
|
||||
- foobar
|
||||
|
||||
@@ -129,6 +129,9 @@ They can be defined by using a file (YAML or TOML) or CLI arguments.
|
||||
trustedIPs:
|
||||
- "127.0.0.1"
|
||||
- "192.168.0.1"
|
||||
connection:
|
||||
- X-Foo
|
||||
- foobar
|
||||
http:
|
||||
encodedCharacters:
|
||||
allowEncodedSlash: false
|
||||
@@ -165,6 +168,7 @@ They can be defined by using a file (YAML or TOML) or CLI arguments.
|
||||
[entryPoints.name.forwardedHeaders]
|
||||
insecure = true
|
||||
trustedIPs = ["127.0.0.1", "192.168.0.1"]
|
||||
connection = ["X-Foo", "foobar"]
|
||||
[entryPoints.name.http.encodedCharacters]
|
||||
allowEncodedSlash = false
|
||||
allowEncodedBackSlash = false
|
||||
@@ -191,6 +195,7 @@ They can be defined by using a file (YAML or TOML) or CLI arguments.
|
||||
--entryPoints.name.proxyProtocol.trustedIPs=127.0.0.1,192.168.0.1
|
||||
--entryPoints.name.forwardedHeaders.insecure=true
|
||||
--entryPoints.name.forwardedHeaders.trustedIPs=127.0.0.1,192.168.0.1
|
||||
--entryPoints.name.forwardedHeaders.connection=X-Foo,foobar
|
||||
--entryPoints.name.http.encodedCharacters.allowEncodedSlash=false
|
||||
--entryPoints.name.http.encodedCharacters.allowEncodedBackSlash=false
|
||||
--entryPoints.name.http.encodedCharacters.allowEncodedNullCharacter=false
|
||||
@@ -611,6 +616,7 @@ You can configure Traefik to trust the forwarded headers information (`X-Forward
|
||||
The removal happens as soon as the request is handled by Traefik,
|
||||
thus the removed headers are not available when the request passes through the middleware chain.
|
||||
The `connection` option lists the Connection headers allowed to passthrough the middleware chain before their removal.
|
||||
The headers defined by this option are not case-sensitive. The middleware will automatically canonicalize them.
|
||||
|
||||
```yaml tab="File (YAML)"
|
||||
## Static configuration
|
||||
@@ -619,6 +625,7 @@ You can configure Traefik to trust the forwarded headers information (`X-Forward
|
||||
address: ":80"
|
||||
forwardedHeaders:
|
||||
connection:
|
||||
- X-Foo
|
||||
- foobar
|
||||
```
|
||||
|
||||
@@ -629,13 +636,13 @@ You can configure Traefik to trust the forwarded headers information (`X-Forward
|
||||
address = ":80"
|
||||
|
||||
[entryPoints.web.forwardedHeaders]
|
||||
connection = ["foobar"]
|
||||
connection = ["X-Foo", "foobar"]
|
||||
```
|
||||
|
||||
```bash tab="CLI"
|
||||
## Static configuration
|
||||
--entryPoints.web.address=:80
|
||||
--entryPoints.web.forwardedHeaders.connection=foobar
|
||||
--entryPoints.web.forwardedHeaders.connection=X-Foo,foobar
|
||||
```
|
||||
|
||||
### Transport
|
||||
|
||||
@@ -1404,6 +1404,11 @@ spec:
|
||||
allowed to be forwarded to the authentication server.
|
||||
format: int64
|
||||
type: integer
|
||||
maxResponseBodySize:
|
||||
description: MaxResponseBodySize defines the maximum body size
|
||||
in bytes allowed in the response from the authentication server.
|
||||
format: int64
|
||||
type: integer
|
||||
preserveLocationHeader:
|
||||
description: PreserveLocationHeader defines whether to forward
|
||||
the Location header to the client as is or prefix it with the
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
[global]
|
||||
checkNewVersion = false
|
||||
sendAnonymousUsage = false
|
||||
|
||||
[api]
|
||||
insecure = true
|
||||
[log]
|
||||
level = "DEBUG"
|
||||
|
||||
[entryPoints]
|
||||
|
||||
[entryPoints.web]
|
||||
address = ":8000"
|
||||
[entryPoints.web.transport.respondingTimeouts]
|
||||
readTimeout="200ms"
|
||||
|
||||
|
||||
[entryPoints.tcp]
|
||||
address = ":8001"
|
||||
[entryPoints.tcp.transport.respondingTimeouts]
|
||||
readTimeout="200ms"
|
||||
|
||||
|
||||
[providers.file]
|
||||
filename = "{{ .SelfFilename }}"
|
||||
|
||||
|
||||
[tcp.routers.withtls]
|
||||
rule="HostSNI(`*`)"
|
||||
service="noop"
|
||||
[tcp.routers.withtls.tls]
|
||||
|
||||
[[tcp.services.noop.loadBalancer.servers]]
|
||||
address="127.0.0.1:8080"
|
||||
@@ -2173,3 +2173,57 @@ func (s *SimpleSuite) TestEncodedCharactersDifferentEntryPoints() {
|
||||
require.NoError(s.T(), err)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *SimpleSuite) TestDDOS() {
|
||||
s.createComposeProject("base")
|
||||
|
||||
s.composeUp()
|
||||
defer s.composeDown()
|
||||
|
||||
file := s.adaptFile("fixtures/simple_ddos.toml", struct{}{})
|
||||
|
||||
_, output := s.cmdTraefik(withConfigFile(file))
|
||||
|
||||
defer func() {
|
||||
if s.T().Failed() {
|
||||
s.T().Log("---- Traefik Logs ----")
|
||||
s.T().Log(output)
|
||||
}
|
||||
}()
|
||||
err := try.GetRequest("http://127.0.0.1:8080/api/rawdata", 1*time.Second, try.BodyContains("HostSNI(`*`)"))
|
||||
require.NoError(s.T(), err)
|
||||
|
||||
// Try with an http router.
|
||||
conn, err := net.Dial("tcp", "127.0.0.1:8000")
|
||||
require.NoError(s.T(), err)
|
||||
|
||||
waitForWritePartial(s.T(), conn)
|
||||
|
||||
// Try with a tcp router only.
|
||||
conn, err = net.Dial("tcp", "127.0.0.1:8001")
|
||||
require.NoError(s.T(), err)
|
||||
|
||||
waitForWritePartial(s.T(), conn)
|
||||
}
|
||||
|
||||
func waitForWritePartial(t *testing.T, conn net.Conn) {
|
||||
t.Helper()
|
||||
|
||||
end := make(chan struct{})
|
||||
go func() {
|
||||
if _, err := conn.Write([]byte{0x16, 0x03, 0x03, 0x00, 0x10}); err != nil {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
_, err := conn.Read(make([]byte, 1))
|
||||
require.ErrorIs(t, err, io.EOF)
|
||||
|
||||
close(end)
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-end:
|
||||
case <-time.After(500 * time.Millisecond):
|
||||
t.Fatalf("timeout waiting for connection timeout")
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -187,7 +187,7 @@ THIS FILE MUST NOT BE EDITED BY HAND
|
||||
}
|
||||
|
||||
if w.err != nil {
|
||||
logger.Fatal().Err(err).Send()
|
||||
logger.Fatal().Err(w.err).Send()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -258,6 +258,8 @@ type ForwardAuth struct {
|
||||
// AuthRequestHeaders defines the list of the headers to copy from the request to the authentication server.
|
||||
// If not set or empty then all request headers are passed.
|
||||
AuthRequestHeaders []string `json:"authRequestHeaders,omitempty" toml:"authRequestHeaders,omitempty" yaml:"authRequestHeaders,omitempty" export:"true"`
|
||||
// MaxResponseBodySize defines the maximum body size in bytes allowed in the response from the authentication server.
|
||||
MaxResponseBodySize *int64 `json:"maxResponseBodySize,omitempty" toml:"maxResponseBodySize,omitempty" yaml:"maxResponseBodySize,omitempty" export:"true"`
|
||||
// AddAuthCookiesToResponse defines the list of cookies to copy from the authentication server response to the response.
|
||||
AddAuthCookiesToResponse []string `json:"addAuthCookiesToResponse,omitempty" toml:"addAuthCookiesToResponse,omitempty" yaml:"addAuthCookiesToResponse,omitempty" export:"true"`
|
||||
// HeaderField defines a header field to store the authenticated user.
|
||||
|
||||
@@ -373,6 +373,11 @@ func (in *ForwardAuth) DeepCopyInto(out *ForwardAuth) {
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
if in.MaxResponseBodySize != nil {
|
||||
in, out := &in.MaxResponseBodySize, &out.MaxResponseBodySize
|
||||
*out = new(int64)
|
||||
**out = **in
|
||||
}
|
||||
if in.AddAuthCookiesToResponse != nil {
|
||||
in, out := &in.AddAuthCookiesToResponse, &out.AddAuthCookiesToResponse
|
||||
*out = make([]string, len(*in))
|
||||
|
||||
@@ -54,6 +54,7 @@ func TestDecodeConfiguration(t *testing.T) {
|
||||
"traefik.http.middlewares.Middleware7.forwardauth.forwardbody": "true",
|
||||
"traefik.http.middlewares.Middleware7.forwardauth.maxbodysize": "42",
|
||||
"traefik.http.middlewares.Middleware7.forwardauth.preserveRequestMethod": "true",
|
||||
"traefik.http.middlewares.Middleware7.forwardauth.maxresponsebodysize": "42",
|
||||
"traefik.http.middlewares.Middleware8.headers.accesscontrolallowcredentials": "true",
|
||||
"traefik.http.middlewares.Middleware8.headers.allowedhosts": "foobar, fiibar",
|
||||
"traefik.http.middlewares.Middleware8.headers.accesscontrolallowheaders": "X-foobar, X-fiibar",
|
||||
@@ -587,6 +588,7 @@ func TestDecodeConfiguration(t *testing.T) {
|
||||
ForwardBody: true,
|
||||
MaxBodySize: pointer(int64(42)),
|
||||
PreserveRequestMethod: true,
|
||||
MaxResponseBodySize: pointer[int64](42),
|
||||
},
|
||||
},
|
||||
"Middleware8": {
|
||||
@@ -1141,6 +1143,7 @@ func TestEncodeConfiguration(t *testing.T) {
|
||||
ForwardBody: true,
|
||||
MaxBodySize: pointer(int64(42)),
|
||||
PreserveRequestMethod: true,
|
||||
MaxResponseBodySize: pointer[int64](42),
|
||||
},
|
||||
},
|
||||
"Middleware8": {
|
||||
@@ -1361,6 +1364,7 @@ func TestEncodeConfiguration(t *testing.T) {
|
||||
"traefik.HTTP.Middlewares.Middleware7.ForwardAuth.TrustForwardHeader": "true",
|
||||
"traefik.HTTP.Middlewares.Middleware7.ForwardAuth.PreserveLocationHeader": "false",
|
||||
"traefik.HTTP.Middlewares.Middleware7.ForwardAuth.PreserveRequestMethod": "true",
|
||||
"traefik.HTTP.Middlewares.Middleware7.ForwardAuth.MaxResponseBodySize": "42",
|
||||
"traefik.HTTP.Middlewares.Middleware8.Headers.AccessControlAllowCredentials": "true",
|
||||
"traefik.HTTP.Middlewares.Middleware8.Headers.AccessControlAllowHeaders": "X-foobar, X-fiibar",
|
||||
"traefik.HTTP.Middlewares.Middleware8.Headers.AccessControlAllowMethods": "GET, PUT",
|
||||
|
||||
@@ -55,6 +55,7 @@ type forwardAuth struct {
|
||||
client http.Client
|
||||
trustForwardHeader bool
|
||||
authRequestHeaders []string
|
||||
maxResponseBodySize int64
|
||||
addAuthCookiesToResponse map[string]struct{}
|
||||
headerField string
|
||||
forwardBody bool
|
||||
@@ -94,6 +95,13 @@ func NewForward(ctx context.Context, next http.Handler, config dynamic.ForwardAu
|
||||
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.")
|
||||
}
|
||||
|
||||
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.")
|
||||
}
|
||||
|
||||
// Ensure our request client does not follow redirects
|
||||
fa.client = http.Client{
|
||||
CheckRedirect: func(r *http.Request, via []*http.Request) error {
|
||||
@@ -210,8 +218,15 @@ func (fa *forwardAuth) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
}
|
||||
defer forwardResponse.Body.Close()
|
||||
|
||||
body, readError := io.ReadAll(forwardResponse.Body)
|
||||
body, readError := fa.readResponseBodyBytes(forwardResponse)
|
||||
if readError != nil {
|
||||
if errors.Is(readError, errResponseBodyTooLarge) {
|
||||
logger.Debug().Msgf("Response body is too large, maxResponseBodySize: %d", fa.maxResponseBodySize)
|
||||
|
||||
observability.SetStatusErrorf(req.Context(), "Response body is too large, maxResponseBodySize: %d", fa.maxResponseBodySize)
|
||||
rw.WriteHeader(http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
logger.Debug().Err(readError).Msgf("Error reading body %s", fa.address)
|
||||
observability.SetStatusErrorf(req.Context(), "Error reading body %s. Cause: %s", fa.address, readError)
|
||||
|
||||
@@ -354,6 +369,27 @@ 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)
|
||||
}
|
||||
|
||||
body := make([]byte, fa.maxResponseBodySize+1)
|
||||
n, err := io.ReadFull(res.Body, body)
|
||||
if errors.Is(err, io.EOF) {
|
||||
return nil, nil
|
||||
}
|
||||
if err != nil && !errors.Is(err, io.ErrUnexpectedEOF) {
|
||||
return nil, fmt.Errorf("reading response body bytes: %w", err)
|
||||
}
|
||||
if errors.Is(err, io.ErrUnexpectedEOF) {
|
||||
return body[:n], nil
|
||||
}
|
||||
return nil, errResponseBodyTooLarge
|
||||
}
|
||||
|
||||
func writeHeader(req, forwardReq *http.Request, trustForwardHeader bool, allowedHeaders []string) {
|
||||
utils.CopyHeaders(forwardReq.Header, req.Header)
|
||||
|
||||
|
||||
@@ -936,6 +936,89 @@ func TestForwardAuthPreserveRequestMethod(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func Test_ForwardAuthMaxResponseBodySize(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
maxResponseBodySize int64
|
||||
status int
|
||||
body string
|
||||
expectedStatus int
|
||||
expectedBody string
|
||||
}{
|
||||
{
|
||||
name: "auth failure, unlimited response body",
|
||||
maxResponseBodySize: -1,
|
||||
status: http.StatusForbidden,
|
||||
body: "Forbidden",
|
||||
expectedStatus: http.StatusForbidden,
|
||||
expectedBody: "Forbidden",
|
||||
},
|
||||
{
|
||||
name: "auth failure, response body exceeds the limit",
|
||||
maxResponseBodySize: 1,
|
||||
status: http.StatusForbidden,
|
||||
body: "Forbidden",
|
||||
expectedStatus: http.StatusUnauthorized,
|
||||
expectedBody: "",
|
||||
},
|
||||
{
|
||||
name: "auth success within limit",
|
||||
maxResponseBodySize: 100,
|
||||
status: http.StatusOK,
|
||||
body: "ok",
|
||||
expectedStatus: http.StatusOK,
|
||||
expectedBody: "traefik\n",
|
||||
},
|
||||
{
|
||||
name: "auth success body exceeds limit",
|
||||
maxResponseBodySize: 1,
|
||||
status: http.StatusOK,
|
||||
body: "large auth response",
|
||||
expectedStatus: http.StatusUnauthorized,
|
||||
expectedBody: "",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
t.Run(test.name, 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)
|
||||
}))
|
||||
t.Cleanup(server.Close)
|
||||
|
||||
next := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprintln(w, "traefik")
|
||||
}))
|
||||
|
||||
maxResponseBodySize := test.maxResponseBodySize
|
||||
auth := dynamic.ForwardAuth{
|
||||
Address: server.URL,
|
||||
MaxResponseBodySize: &maxResponseBodySize,
|
||||
}
|
||||
|
||||
middleware, err := NewForward(t.Context(), next, auth, "maxResponseBodySizeTest")
|
||||
require.NoError(t, err)
|
||||
|
||||
ts := httptest.NewServer(middleware)
|
||||
t.Cleanup(ts.Close)
|
||||
|
||||
req := testhelpers.MustNewRequest(http.MethodGet, ts.URL, nil)
|
||||
res, err := http.DefaultClient.Do(req)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, test.expectedStatus, res.StatusCode)
|
||||
|
||||
body, err := io.ReadAll(res.Body)
|
||||
require.NoError(t, err)
|
||||
err = res.Body.Close()
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, test.expectedBody, string(body))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type mockTracer struct {
|
||||
embedded.Tracer
|
||||
|
||||
|
||||
@@ -71,10 +71,15 @@ func NewXForwarded(insecure bool, trustedIPs []string, connectionHeaders []strin
|
||||
hostname = "localhost"
|
||||
}
|
||||
|
||||
canonicalConnectionHeaders := make([]string, len(connectionHeaders))
|
||||
for i, header := range connectionHeaders {
|
||||
canonicalConnectionHeaders[i] = http.CanonicalHeaderKey(header)
|
||||
}
|
||||
|
||||
return &XForwarded{
|
||||
insecure: insecure,
|
||||
trustedIPs: trustedIPs,
|
||||
connectionHeaders: connectionHeaders,
|
||||
connectionHeaders: canonicalConnectionHeaders,
|
||||
ipChecker: ipChecker,
|
||||
next: next,
|
||||
hostname: hostname,
|
||||
@@ -209,22 +214,23 @@ func (x *XForwarded) removeConnectionHeaders(req *http.Request) {
|
||||
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, sf) {
|
||||
if slices.Contains(xHeaders, key) {
|
||||
continue
|
||||
}
|
||||
|
||||
// Keep headers allowed through the middleware chain.
|
||||
if slices.Contains(x.connectionHeaders, sf) {
|
||||
connectionHopByHopHeaders = append(connectionHopByHopHeaders, sf)
|
||||
if slices.Contains(x.connectionHeaders, key) {
|
||||
connectionHopByHopHeaders = append(connectionHopByHopHeaders, key)
|
||||
continue
|
||||
}
|
||||
|
||||
// Apply Connection header option.
|
||||
req.Header.Del(sf)
|
||||
delete(req.Header, key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"crypto/tls"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -470,6 +471,100 @@ func TestServeHTTP(t *testing.T) {
|
||||
connection: "",
|
||||
},
|
||||
},
|
||||
{
|
||||
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(xForwardedTLSClientCert),
|
||||
strings.ToLower(xForwardedTLSClientCertInfo),
|
||||
strings.ToLower(xForwardedPrefix),
|
||||
strings.ToLower(xRealIP),
|
||||
},
|
||||
incomingHeaders: map[string][]string{
|
||||
connection: {
|
||||
xForwardedProto,
|
||||
xForwardedFor,
|
||||
xForwardedURI,
|
||||
xForwardedMethod,
|
||||
xForwardedHost,
|
||||
xForwardedPort,
|
||||
xForwardedTLSClientCert,
|
||||
xForwardedTLSClientCertInfo,
|
||||
xForwardedPrefix,
|
||||
xRealIP,
|
||||
},
|
||||
xForwardedProto: {"foo"},
|
||||
xForwardedFor: {"foo"},
|
||||
xForwardedURI: {"foo"},
|
||||
xForwardedMethod: {"foo"},
|
||||
xForwardedHost: {"foo"},
|
||||
xForwardedPort: {"foo"},
|
||||
xForwardedTLSClientCert: {"foo"},
|
||||
xForwardedTLSClientCertInfo: {"foo"},
|
||||
xForwardedPrefix: {"foo"},
|
||||
xRealIP: {"foo"},
|
||||
},
|
||||
expectedHeaders: map[string]string{
|
||||
xForwardedProto: "foo",
|
||||
xForwardedFor: "foo",
|
||||
xForwardedURI: "foo",
|
||||
xForwardedMethod: "foo",
|
||||
xForwardedHost: "foo",
|
||||
xForwardedPort: "foo",
|
||||
xForwardedTLSClientCert: "foo",
|
||||
xForwardedTLSClientCertInfo: "foo",
|
||||
xForwardedPrefix: "foo",
|
||||
xRealIP: "foo",
|
||||
connection: "",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Trusted (insecure) and Connection: Testing case sensitivity on X- forwarded headers",
|
||||
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(xForwardedTLSClientCert),
|
||||
strings.ToLower(xForwardedTLSClientCertInfo),
|
||||
strings.ToLower(xForwardedPrefix),
|
||||
strings.ToLower(xRealIP),
|
||||
},
|
||||
xForwardedProto: {"foo"},
|
||||
xForwardedFor: {"foo"},
|
||||
xForwardedURI: {"foo"},
|
||||
xForwardedMethod: {"foo"},
|
||||
xForwardedHost: {"foo"},
|
||||
xForwardedPort: {"foo"},
|
||||
xForwardedTLSClientCert: {"foo"},
|
||||
xForwardedTLSClientCertInfo: {"foo"},
|
||||
xForwardedPrefix: {"foo"},
|
||||
xRealIP: {"foo"},
|
||||
},
|
||||
expectedHeaders: map[string]string{
|
||||
xForwardedProto: "foo",
|
||||
xForwardedFor: "foo",
|
||||
xForwardedURI: "foo",
|
||||
xForwardedMethod: "foo",
|
||||
xForwardedHost: "foo",
|
||||
xForwardedPort: "foo",
|
||||
xForwardedTLSClientCert: "foo",
|
||||
xForwardedTLSClientCertInfo: "foo",
|
||||
xForwardedPrefix: "foo",
|
||||
xRealIP: "foo",
|
||||
connection: "",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Connection: one remove, and one passthrough header",
|
||||
connectionHeaders: []string{
|
||||
@@ -478,12 +573,14 @@ func TestServeHTTP(t *testing.T) {
|
||||
incomingHeaders: map[string][]string{
|
||||
connection: {
|
||||
"foo",
|
||||
"bar",
|
||||
},
|
||||
"Foo": {"bar"},
|
||||
"Bar": {"foo"},
|
||||
},
|
||||
expectedHeaders: map[string]string{
|
||||
"Bar": "foo",
|
||||
"Bar": "",
|
||||
"Foo": "bar",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -492,8 +492,6 @@ func resourceAttributes(traces ptrace.Traces) map[string]string {
|
||||
}
|
||||
|
||||
// mainSpan gets the main span from traces (assumes single span for testing).
|
||||
//
|
||||
//nolint:unqueryvet // False positive: This is OTel trace iteration, not SQLBoiler.
|
||||
func mainSpan(traces ptrace.Traces) ptrace.Span {
|
||||
for _, resourceSpans := range traces.ResourceSpans().All() {
|
||||
for _, scopeSpans := range resourceSpans.ScopeSpans().All() {
|
||||
|
||||
@@ -3,7 +3,7 @@ package acme
|
||||
import "os"
|
||||
|
||||
// CheckFile checks file content size
|
||||
// Do not check file permissions on Windows right now
|
||||
// Do not check file permissions on Windows right now.
|
||||
func CheckFile(name string) (bool, error) {
|
||||
f, err := os.Open(name)
|
||||
if err != nil {
|
||||
|
||||
+9
@@ -35,6 +35,7 @@ type ForwardAuthApplyConfiguration struct {
|
||||
AuthResponseHeadersRegex *string `json:"authResponseHeadersRegex,omitempty"`
|
||||
AuthRequestHeaders []string `json:"authRequestHeaders,omitempty"`
|
||||
TLS *ClientTLSWithCAOptionalApplyConfiguration `json:"tls,omitempty"`
|
||||
MaxResponseBodySize *int64 `json:"maxResponseBodySize,omitempty"`
|
||||
AddAuthCookiesToResponse []string `json:"addAuthCookiesToResponse,omitempty"`
|
||||
HeaderField *string `json:"headerField,omitempty"`
|
||||
ForwardBody *bool `json:"forwardBody,omitempty"`
|
||||
@@ -101,6 +102,14 @@ func (b *ForwardAuthApplyConfiguration) WithTLS(value *ClientTLSWithCAOptionalAp
|
||||
return b
|
||||
}
|
||||
|
||||
// WithMaxResponseBodySize sets the MaxResponseBodySize field in the declarative configuration to the given value
|
||||
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
|
||||
// If called multiple times, the MaxResponseBodySize field is set to the value of the last call.
|
||||
func (b *ForwardAuthApplyConfiguration) WithMaxResponseBodySize(value int64) *ForwardAuthApplyConfiguration {
|
||||
b.MaxResponseBodySize = &value
|
||||
return b
|
||||
}
|
||||
|
||||
// WithAddAuthCookiesToResponse adds the given value to the AddAuthCookiesToResponse field in the declarative configuration
|
||||
// and returns the receiver, so that objects can be build by chaining "With" function invocations.
|
||||
// If called multiple times, values provided by each call will be appended to the AddAuthCookiesToResponse field.
|
||||
|
||||
@@ -967,6 +967,10 @@ func createForwardAuthMiddleware(k8sClient Client, namespace string, auth *traef
|
||||
}
|
||||
forwardAuth.SetDefaults()
|
||||
|
||||
if auth.MaxResponseBodySize != nil {
|
||||
forwardAuth.MaxResponseBodySize = auth.MaxResponseBodySize
|
||||
}
|
||||
|
||||
if auth.MaxBodySize != nil {
|
||||
forwardAuth.MaxBodySize = auth.MaxBodySize
|
||||
}
|
||||
|
||||
@@ -171,6 +171,8 @@ type ForwardAuth struct {
|
||||
AuthRequestHeaders []string `json:"authRequestHeaders,omitempty"`
|
||||
// TLS defines the configuration used to secure the connection to the authentication server.
|
||||
TLS *ClientTLSWithCAOptional `json:"tls,omitempty"`
|
||||
// MaxResponseBodySize defines the maximum body size in bytes allowed in the response from the authentication server.
|
||||
MaxResponseBodySize *int64 `json:"maxResponseBodySize,omitempty"`
|
||||
// AddAuthCookiesToResponse defines the list of cookies to copy from the authentication server response to the response.
|
||||
AddAuthCookiesToResponse []string `json:"addAuthCookiesToResponse,omitempty"`
|
||||
// HeaderField defines a header field to store the authenticated user.
|
||||
|
||||
@@ -285,6 +285,11 @@ func (in *ForwardAuth) DeepCopyInto(out *ForwardAuth) {
|
||||
*out = new(ClientTLSWithCAOptional)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.MaxResponseBodySize != nil {
|
||||
in, out := &in.MaxResponseBodySize, &out.MaxResponseBodySize
|
||||
*out = new(int64)
|
||||
**out = **in
|
||||
}
|
||||
if in.AddAuthCookiesToResponse != nil {
|
||||
in, out := &in.AddAuthCookiesToResponse, &out.AddAuthCookiesToResponse
|
||||
*out = make([]string, len(*in))
|
||||
|
||||
@@ -93,6 +93,7 @@ func Test_buildConfiguration(t *testing.T) {
|
||||
"traefik/http/middlewares/Middleware08/forwardAuth/maxBodySize": "42",
|
||||
"traefik/http/middlewares/Middleware08/forwardAuth/preserveLocationHeader": "true",
|
||||
"traefik/http/middlewares/Middleware08/forwardAuth/preserveRequestMethod": "true",
|
||||
"traefik/http/middlewares/Middleware08/forwardAuth/maxResponseBodySize": "42",
|
||||
"traefik/http/middlewares/Middleware15/redirectScheme/scheme": "foobar",
|
||||
"traefik/http/middlewares/Middleware15/redirectScheme/port": "foobar",
|
||||
"traefik/http/middlewares/Middleware15/redirectScheme/permanent": "true",
|
||||
@@ -445,6 +446,7 @@ func Test_buildConfiguration(t *testing.T) {
|
||||
"foobar",
|
||||
"foobar",
|
||||
},
|
||||
MaxResponseBodySize: pointer[int64](42),
|
||||
ForwardBody: true,
|
||||
MaxBodySize: pointer(int64(42)),
|
||||
PreserveLocationHeader: true,
|
||||
|
||||
@@ -279,6 +279,7 @@ func init() {
|
||||
AuthResponseHeaders: []string{"foo"},
|
||||
AuthResponseHeadersRegex: "foo",
|
||||
AuthRequestHeaders: []string{"foo"},
|
||||
MaxResponseBodySize: pointer[int64](42),
|
||||
},
|
||||
InFlightReq: &dynamic.InFlightReq{
|
||||
Amount: 42,
|
||||
|
||||
+2
-1
@@ -246,7 +246,8 @@
|
||||
"authResponseHeadersRegex": "foo",
|
||||
"authRequestHeaders": [
|
||||
"foo"
|
||||
]
|
||||
],
|
||||
"maxResponseBodySize": 42
|
||||
},
|
||||
"inFlightReq": {
|
||||
"amount": 42,
|
||||
|
||||
+2
-1
@@ -249,7 +249,8 @@
|
||||
"authResponseHeadersRegex": "foo",
|
||||
"authRequestHeaders": [
|
||||
"foo"
|
||||
]
|
||||
],
|
||||
"maxResponseBodySize": 42
|
||||
},
|
||||
"inFlightReq": {
|
||||
"amount": 42,
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
@@ -140,6 +141,11 @@ func (r *Router) ServeTCP(conn tcp.WriteCloser) {
|
||||
|
||||
hello, err := clientHelloInfo(br)
|
||||
if err != nil {
|
||||
var opErr *net.OpError
|
||||
if !errors.Is(err, io.EOF) && (!errors.As(err, &opErr) || !opErr.Timeout()) {
|
||||
log.Debug().Err(err).Msg("Error while reading client hello")
|
||||
}
|
||||
|
||||
conn.Close()
|
||||
return
|
||||
}
|
||||
@@ -378,11 +384,7 @@ type clientHello struct {
|
||||
func clientHelloInfo(br *bufio.Reader) (*clientHello, error) {
|
||||
hdr, err := br.Peek(1)
|
||||
if err != nil {
|
||||
var opErr *net.OpError
|
||||
if !errors.Is(err, io.EOF) && (!errors.As(err, &opErr) || !opErr.Timeout()) {
|
||||
log.Debug().Err(err).Msg("Error while peeking first byte")
|
||||
}
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("peeking first byte: %w", err)
|
||||
}
|
||||
|
||||
// No valid TLS record has a type of 0x80, however SSLv2 handshakes start with an uint16 length
|
||||
@@ -406,20 +408,13 @@ func clientHelloInfo(br *bufio.Reader) (*clientHello, error) {
|
||||
const recordHeaderLen = 5
|
||||
hdr, err = br.Peek(recordHeaderLen)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Error while peeking client hello header")
|
||||
return &clientHello{
|
||||
peeked: getPeeked(br),
|
||||
}, nil
|
||||
return nil, fmt.Errorf("peeking client hello headers: %w", err)
|
||||
}
|
||||
|
||||
recLen := int(hdr[3])<<8 | int(hdr[4]) // ignoring version in hdr[1:3]
|
||||
|
||||
if recLen > maxTLSRecordLen {
|
||||
log.Debug().Msgf("Error while peeking client hello bytes, oversized record: %d", recLen)
|
||||
return &clientHello{
|
||||
isTLS: true,
|
||||
peeked: getPeeked(br),
|
||||
}, nil
|
||||
return nil, fmt.Errorf("peeking client hello bytes, oversized record: %d", recLen)
|
||||
}
|
||||
|
||||
if recordHeaderLen+recLen > defaultBufSize {
|
||||
@@ -428,11 +423,7 @@ func clientHelloInfo(br *bufio.Reader) (*clientHello, error) {
|
||||
|
||||
helloBytes, err := br.Peek(recordHeaderLen + recLen)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Error while peeking client hello bytes")
|
||||
return &clientHello{
|
||||
isTLS: true,
|
||||
peeked: getPeeked(br),
|
||||
}, nil
|
||||
return nil, fmt.Errorf("peeking client hello bytes: %w", err)
|
||||
}
|
||||
|
||||
sni := ""
|
||||
|
||||
@@ -1143,9 +1143,7 @@ func Test_clientHelloInfo_oversizedRecordLength(t *testing.T) {
|
||||
// With the fix, it returns immediately.
|
||||
select {
|
||||
case r := <-resultCh:
|
||||
require.NoError(t, r.err)
|
||||
require.NotNil(t, r.hello)
|
||||
assert.True(t, r.hello.isTLS)
|
||||
require.Error(t, r.err)
|
||||
case <-time.After(5 * time.Second):
|
||||
t.Fatal("clientHelloInfo blocked on oversized TLS record length — recLen is not capped")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user