Merge v2.11 into v3.6

This commit is contained in:
mmatur
2026-02-23 16:41:44 +01:00
48 changed files with 526 additions and 81 deletions
+2 -3
View File
@@ -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
+2 -1
View File
@@ -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
+2 -3
View File
@@ -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
+2 -4
View File
@@ -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: |
+6 -7
View File
@@ -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
+4 -7
View File
@@ -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
+7 -8
View File
@@ -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
+1
View File
@@ -0,0 +1 @@
1.25.7
+1 -1
View File
@@ -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
+8
View File
@@ -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)
+4 -4
View File
@@ -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" %}
+21
View File
@@ -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
+9 -2
View File
@@ -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
+34
View File
@@ -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"
+54
View File
@@ -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
View File
@@ -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()
}
}
+2
View File
@@ -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))
+4
View File
@@ -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",
+37 -1
View File
@@ -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)
+83
View File
@@ -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() {
+1 -1
View File
@@ -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 {
@@ -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))
+2
View File
@@ -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,
+1
View File
@@ -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
View File
@@ -246,7 +246,8 @@
"authResponseHeadersRegex": "foo",
"authRequestHeaders": [
"foo"
]
],
"maxResponseBodySize": 42
},
"inFlightReq": {
"amount": 42,
+2 -1
View File
@@ -249,7 +249,8 @@
"authResponseHeadersRegex": "foo",
"authRequestHeaders": [
"foo"
]
],
"maxResponseBodySize": 42
},
"inFlightReq": {
"amount": 42,
+10 -19
View File
@@ -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 := ""
+1 -3
View File
@@ -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")
}