Add maxResponseBodySize configuration on HTTP provider

This commit is contained in:
Gina A. 2026-03-11 14:24:05 +01:00 committed by GitHub
parent bb7bb49226
commit b460351f7e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 112 additions and 19 deletions

View File

@ -775,3 +775,13 @@ However, it is strongly recommended to set this option to a suitable value to av
such as DoS attacks and memory exhaustion.
Please check out the [ForwardAuth](../middlewares/http/forwardauth.md#maxresponsebodysize) middleware documentation for more details.
## v2.11.41
### `maxResponseBodySize` configuration on HTTP provider
In `v2.11.41`, a new `maxResponseBodySize` option has been added to the HTTP provider 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 issues such as memory exhaustion.
Please check out the [HTTP](../providers/http.md#maxresponsebodysize) provider documentation for more details.

View File

@ -178,3 +178,25 @@ providers:
```bash tab="CLI"
--providers.http.tls.insecureSkipVerify=true
```
### `maxResponseBodySize`
_Optional, Default=-1_
Defines the maximum size of the response body in bytes.
If left unset (or set to -1), the response body size is unrestricted which can have performance implications.
```yaml tab="File (YAML)"
providers:
http:
maxResponseBodySize: -1
```
```toml tab="File (TOML)"
[providers.http]
maxResponseBodySize = -1
```
```bash tab="CLI"
--providers.http.maxResponseBodySize=-1
```

View File

@ -663,6 +663,9 @@ Enable HTTP backend with default settings. (Default: ```false```)
`--providers.http.endpoint`:
Load configuration from this endpoint.
`--providers.http.maxresponsebodysize`:
Defines the maximum size of the response body in bytes. (Default: ```-1```)
`--providers.http.pollinterval`:
Polling interval for endpoint. (Default: ```5```)

View File

@ -663,6 +663,9 @@ Enable HTTP backend with default settings. (Default: ```false```)
`TRAEFIK_PROVIDERS_HTTP_ENDPOINT`:
Load configuration from this endpoint.
`TRAEFIK_PROVIDERS_HTTP_MAXRESPONSEBODYSIZE`:
Defines the maximum size of the response body in bytes. (Default: ```-1```)
`TRAEFIK_PROVIDERS_HTTP_POLLINTERVAL`:
Polling interval for endpoint. (Default: ```5```)

View File

@ -274,6 +274,7 @@
endpoint = "foobar"
pollInterval = "42s"
pollTimeout = "42s"
maxResponseBodySize = 42
[providers.http.tls]
ca = "foobar"
caOptional = true

View File

@ -310,6 +310,7 @@ providers:
cert: foobar
key: foobar
insecureSkipVerify: true
maxResponseBodySize: 42
plugin:
PluginConf0:
name0: foobar

View File

@ -23,6 +23,8 @@ import (
var _ provider.Provider = (*Provider)(nil)
const defaultMaxResponseBodySize = -1
// Provider is a provider.Provider implementation that queries an HTTP(s) endpoint for a configuration.
type Provider struct {
Endpoint string `description:"Load configuration from this endpoint." json:"endpoint" toml:"endpoint" yaml:"endpoint"`
@ -31,12 +33,14 @@ type Provider struct {
TLS *types.ClientTLS `description:"Enable TLS support." json:"tls,omitempty" toml:"tls,omitempty" yaml:"tls,omitempty" export:"true"`
httpClient *http.Client
lastConfigurationHash uint64
MaxResponseBodySize int64 `description:"Defines the maximum size of the response body in bytes." json:"maxResponseBodySize,omitempty" toml:"maxResponseBodySize,omitempty" yaml:"maxResponseBodySize,omitempty" export:"true"`
}
// SetDefaults sets the default values.
func (p *Provider) SetDefaults() {
p.PollInterval = ptypes.Duration(5 * time.Second)
p.PollTimeout = ptypes.Duration(5 * time.Second)
p.MaxResponseBodySize = defaultMaxResponseBodySize
}
// Init the provider.
@ -151,7 +155,19 @@ func (p *Provider) fetchConfigurationData() ([]byte, error) {
return nil, fmt.Errorf("received non-ok response code: %d", res.StatusCode)
}
return io.ReadAll(res.Body)
if p.MaxResponseBodySize < 0 {
return io.ReadAll(res.Body)
}
data, err := io.ReadAll(io.LimitReader(res.Body, p.MaxResponseBodySize+1))
if err != nil {
return nil, fmt.Errorf("reading response body: %w", err)
}
if int64(len(data)) > p.MaxResponseBodySize {
return nil, errors.New("response body too large")
}
return data, nil
}
// decodeConfiguration decodes and returns the dynamic configuration from the given data.

View File

@ -13,6 +13,7 @@ import (
"github.com/traefik/traefik/v2/pkg/config/dynamic"
"github.com/traefik/traefik/v2/pkg/safe"
"github.com/traefik/traefik/v2/pkg/tls"
"k8s.io/utils/ptr"
)
func TestProvider_Init(t *testing.T) {
@ -64,14 +65,16 @@ func TestProvider_SetDefaults(t *testing.T) {
assert.Equal(t, provider.PollInterval, ptypes.Duration(5*time.Second))
assert.Equal(t, provider.PollTimeout, ptypes.Duration(5*time.Second))
assert.Equal(t, int64(-1), provider.MaxResponseBodySize)
}
func TestProvider_fetchConfigurationData(t *testing.T) {
tests := []struct {
desc string
handler func(rw http.ResponseWriter, req *http.Request)
expData []byte
expErr bool
desc string
handler func(rw http.ResponseWriter, req *http.Request)
expData []byte
expErr bool
maxResponseBodySize *int64
}{
{
desc: "should return the fetched configuration data",
@ -88,6 +91,34 @@ func TestProvider_fetchConfigurationData(t *testing.T) {
rw.WriteHeader(http.StatusNoContent)
},
},
{
desc: "should return an error response body is too long when maxResponseBodySize is 0",
maxResponseBodySize: ptr.To(int64(0)),
expErr: true,
handler: func(rw http.ResponseWriter, req *http.Request) {
rw.WriteHeader(http.StatusOK)
_, _ = fmt.Fprintf(rw, "{}")
},
},
{
desc: "should return an error response body is too long when response is longer than maxResponseBodySize",
maxResponseBodySize: ptr.To(int64(1)),
expErr: true,
handler: func(rw http.ResponseWriter, req *http.Request) {
rw.WriteHeader(http.StatusOK)
_, _ = fmt.Fprintf(rw, "{}")
},
},
{
desc: "should return the fetched configuration data when response is the same length with maxResponseBodySize",
maxResponseBodySize: ptr.To(int64(2)),
expData: []byte("{}"),
expErr: false,
handler: func(rw http.ResponseWriter, req *http.Request) {
rw.WriteHeader(http.StatusOK)
_, _ = fmt.Fprintf(rw, "{}")
},
},
}
for _, test := range tests {
@ -95,10 +126,14 @@ func TestProvider_fetchConfigurationData(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(test.handler))
defer server.Close()
provider := Provider{
Endpoint: server.URL,
PollInterval: ptypes.Duration(1 * time.Second),
PollTimeout: ptypes.Duration(1 * time.Second),
var provider Provider
provider.SetDefaults()
provider.Endpoint = server.URL
provider.PollTimeout = ptypes.Duration(1 * time.Second)
provider.PollInterval = ptypes.Duration(100 * time.Millisecond)
if test.maxResponseBodySize != nil {
provider.MaxResponseBodySize = *test.maxResponseBodySize
}
err := provider.Init()
@ -179,11 +214,12 @@ func TestProvider_Provide(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(handler))
defer server.Close()
provider := Provider{
Endpoint: server.URL,
PollTimeout: ptypes.Duration(1 * time.Second),
PollInterval: ptypes.Duration(100 * time.Millisecond),
}
var provider Provider
provider.SetDefaults()
provider.Endpoint = server.URL
provider.PollTimeout = ptypes.Duration(1 * time.Second)
provider.PollInterval = ptypes.Duration(100 * time.Millisecond)
err := provider.Init()
require.NoError(t, err)
@ -234,11 +270,12 @@ func TestProvider_ProvideConfigurationOnlyOnceIfUnchanged(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(handler))
defer server.Close()
provider := Provider{
Endpoint: server.URL + "/endpoint",
PollTimeout: ptypes.Duration(1 * time.Second),
PollInterval: ptypes.Duration(100 * time.Millisecond),
}
var provider Provider
provider.SetDefaults()
provider.Endpoint = server.URL + "/endpoint"
provider.PollTimeout = ptypes.Duration(1 * time.Second)
provider.PollInterval = ptypes.Duration(100 * time.Millisecond)
err := provider.Init()
require.NoError(t, err)