Merge branch v2.11 into v3.6

This commit is contained in:
mmatur 2026-04-29 12:37:42 +02:00
commit 59357eeaf8
No known key found for this signature in database
GPG Key ID: 2FFE42FC256CFF8E
13 changed files with 116 additions and 2 deletions

View File

@ -1,3 +1,13 @@
## [v2.11.44](https://github.com/traefik/traefik/tree/v2.11.44) (2026-04-29)
[All Commits](https://github.com/traefik/traefik/compare/v2.11.43...v2.11.44)
**Bug fixes:**
- **[middleware]** Add errorRequestHeaders option to Errors middleware ([#13034](https://github.com/traefik/traefik/pull/13034) @gndz07)
- **[acme]** Bump github.com/go-acme/lego to v4.35.2 ([#13052](https://github.com/traefik/traefik/pull/13052) @mmatur)
**Misc:**
- Make FLAGS Make variable usable ([#13009](https://github.com/traefik/traefik/pull/13009) @twz123)
## [v3.6.14](https://github.com/traefik/traefik/tree/v3.6.14) (2026-04-22)
[All Commits](https://github.com/traefik/traefik/compare/v3.6.13...v3.6.14)

View File

@ -58,7 +58,7 @@ generate:
#? binary: Build the binary
binary: generate-webui dist
@echo SHA: $(VERSION) $(CODENAME) $(DATE)
CGO_ENABLED=0 GOGC=${GOGC} GOOS=${GOOS} GOARCH=${GOARCH} go build ${FLAGS[*]} -ldflags "-s -w \
CGO_ENABLED=0 GOGC=${GOGC} GOOS=${GOOS} GOARCH=${GOARCH} go build ${FLAGS} -ldflags "-s -w \
-X github.com/traefik/traefik/v3/pkg/version.Version=$(VERSION) \
-X github.com/traefik/traefik/v3/pkg/version.Codename=$(CODENAME) \
-X github.com/traefik/traefik/v3/pkg/version.BuildDate=$(DATE)" \

View File

@ -9,6 +9,15 @@ This guide provides detailed migration steps for upgrading between different Tra
---
## v3.6.15
In `v3.6.15`, a new `errorRequestHeaders` option has been added to the Errors middleware.
By default, the behavior is unchanged: all original request headers are forwarded to the error page service.
If the error page service is in a separate trust domain, consider using `errorRequestHeaders` to restrict which headers are forwarded.
Please check out the [Error Pages](../reference/routing-configuration/http/middlewares/errorpages.md#errorRequestHeaders) middleware documentation for more details.
## v3.6.14
### Kubernetes CRD: Chain middleware and `allowCrossNamespace`

View File

@ -30,6 +30,7 @@
- "traefik.http.middlewares.middleware08.digestauth.removeheader=true"
- "traefik.http.middlewares.middleware08.digestauth.users=foobar, foobar"
- "traefik.http.middlewares.middleware08.digestauth.usersfile=foobar"
- "traefik.http.middlewares.middleware09.errors.errorrequestheaders=foobar, foobar"
- "traefik.http.middlewares.middleware09.errors.query=foobar"
- "traefik.http.middlewares.middleware09.errors.service=foobar"
- "traefik.http.middlewares.middleware09.errors.status=foobar, foobar"

View File

@ -193,6 +193,7 @@
status = ["foobar", "foobar"]
service = "foobar"
query = "foobar"
errorRequestHeaders = ["foobar", "foobar"]
[http.middlewares.Middleware09.errors.statusRewrites]
name0 = 42
name1 = 42

View File

@ -208,6 +208,9 @@ http:
name1: 42
service: foobar
query: foobar
errorRequestHeaders:
- foobar
- foobar
Middleware10:
forwardAuth:
address: foobar

View File

@ -129,3 +129,12 @@ The table below lists all the available variables and their associated values.
| <a id="opt-status-2" href="#opt-status-2" title="#opt-status-2">`{status}`</a> | The response status code. |
| <a id="opt-originalStatus" href="#opt-originalStatus" title="#opt-originalStatus">`{originalStatus}`</a> | The original response status code, if it has been modified by the `statusRewrites` option. |
| <a id="opt-url" href="#opt-url" title="#opt-url">`{url}`</a> | The [escaped](https://pkg.go.dev/net/url#QueryEscape) request URL.|
### `errorRequestHeaders`
Defines the list of original request headers forwarded to the error page service.
By default (`errorRequestHeaders` not set), all request headers — including authentication material such as `Authorization` and `Cookie` — are forwarded.
If the error page service is in a separate trust domain, use this option to restrict which headers cross the service boundary.
Set to an explicit list to forward only those headers, or set to an empty list (`errorRequestHeaders: []`) to forward no headers.

View File

@ -195,6 +195,7 @@
status = ["foobar", "foobar"]
service = "foobar"
query = "foobar"
errorRequestHeaders = ["foobar", "foobar"]
[http.middlewares.Middleware09.errors.statusRewrites]
name0 = 42
name1 = 42

View File

@ -214,6 +214,9 @@ http:
name1: 42
service: foobar
query: foobar
errorRequestHeaders:
- foobar
- foobar
Middleware10:
forwardAuth:
address: foobar

View File

@ -236,6 +236,10 @@ type ErrorPage struct {
// The {originalStatus} variable can be used in order to insert the upstream status code in the URL.
// The {url} variable can be used in order to insert the escaped request URL.
Query string `json:"query,omitempty" toml:"query,omitempty" yaml:"query,omitempty" export:"true"`
// ErrorRequestHeaders defines the list of request headers forwarded to the error page service.
// When nil (not set), all original request headers are forwarded.
// Set to an empty list to forward no headers, or list specific headers to forward only those.
ErrorRequestHeaders []string `json:"errorRequestHeaders,omitempty" toml:"errorRequestHeaders,omitempty" yaml:"errorRequestHeaders,omitempty" export:"true"`
}
// +k8s:deepcopy-gen=true

View File

@ -321,6 +321,11 @@ func (in *ErrorPage) DeepCopyInto(out *ErrorPage) {
(*out)[key] = val
}
}
if in.ErrorRequestHeaders != nil {
in, out := &in.ErrorRequestHeaders, &out.ErrorRequestHeaders
*out = make([]string, len(*in))
copy(*out, *in)
}
return
}

View File

@ -37,6 +37,7 @@ type customErrors struct {
backendHandler http.Handler
httpCodeRanges types.HTTPCodeRanges
backendQuery string
requestHeaders []string
statusRewrites []statusRewrite
}
@ -79,6 +80,7 @@ func New(ctx context.Context, next http.Handler, config dynamic.ErrorPage, servi
backendHandler: backend,
httpCodeRanges: httpCodeRanges,
backendQuery: config.Query,
requestHeaders: config.ErrorRequestHeaders,
statusRewrites: statusRewrites,
}, nil
}
@ -145,7 +147,15 @@ func (c *customErrors) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
return
}
utils.CopyHeaders(pageReq.Header, req.Header)
if c.requestHeaders != nil {
for _, header := range c.requestHeaders {
if values := req.Header.Values(header); len(values) > 0 {
pageReq.Header[http.CanonicalHeaderKey(header)] = values
}
}
} else {
utils.CopyHeaders(pageReq.Header, req.Header)
}
c.backendHandler.ServeHTTP(newCodeModifier(rw, code),
pageReq.WithContext(req.Context()))
}

View File

@ -23,6 +23,7 @@ func TestHandler(t *testing.T) {
backendCode int
backendErrorHandler http.HandlerFunc
validate func(t *testing.T, recorder *httptest.ResponseRecorder)
requestHeaders map[string]string
}{
{
desc: "no error",
@ -154,6 +155,60 @@ func TestHandler(t *testing.T) {
assert.Contains(t, recorder.Body.String(), "My 503 page.")
},
},
{
desc: "forward all headers by default",
errorPage: &dynamic.ErrorPage{Service: "error", Query: "/test", Status: []string{"503"}},
requestHeaders: map[string]string{
"X-Request-Id": "trace-abc",
"Authorization": "Bearer secret",
},
backendCode: http.StatusServiceUnavailable,
backendErrorHandler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, r.Header.Get("X-Request-Id"))
fmt.Fprintln(w, r.Header.Get("Authorization"))
}),
validate: func(t *testing.T, recorder *httptest.ResponseRecorder) {
t.Helper()
assert.Contains(t, recorder.Body.String(), "trace-abc")
assert.Contains(t, recorder.Body.String(), "Bearer secret")
},
},
{
desc: "forward only allowlisted headers",
errorPage: &dynamic.ErrorPage{Service: "error", Query: "/test", Status: []string{"503"}, ErrorRequestHeaders: []string{"X-Request-Id"}},
requestHeaders: map[string]string{
"X-Request-Id": "trace-abc",
"Authorization": "Bearer secret",
},
backendCode: http.StatusServiceUnavailable,
backendErrorHandler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, r.Header.Get("X-Request-Id"))
fmt.Fprintln(w, r.Header.Get("Authorization"))
}),
validate: func(t *testing.T, recorder *httptest.ResponseRecorder) {
t.Helper()
assert.Contains(t, recorder.Body.String(), "trace-abc")
assert.NotContains(t, recorder.Body.String(), "Bearer secret")
},
},
{
desc: "forward no headers",
errorPage: &dynamic.ErrorPage{Service: "error", Query: "/test", Status: []string{"503"}, ErrorRequestHeaders: []string{}},
requestHeaders: map[string]string{
"X-Request-Id": "trace-abc",
"Authorization": "Bearer secret",
},
backendCode: http.StatusServiceUnavailable,
backendErrorHandler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, r.Header.Get("X-Request-Id"))
fmt.Fprintln(w, r.Header.Get("Authorization"))
}),
validate: func(t *testing.T, recorder *httptest.ResponseRecorder) {
t.Helper()
assert.NotContains(t, recorder.Body.String(), "trace-abc")
assert.NotContains(t, recorder.Body.String(), "Bearer secret")
},
},
}
for _, test := range testCases {
@ -174,6 +229,9 @@ func TestHandler(t *testing.T) {
require.NoError(t, err)
req := testhelpers.MustNewRequest(http.MethodGet, "http://localhost/test?foo=bar&baz=buz", nil)
for k, v := range test.requestHeaders {
req.Header.Set(k, v)
}
// Client like browser and curl will issue a relative HTTP request, which not have a host and scheme in the URL. But the http.NewRequest will set them automatically.
req.URL.Host = ""