mirror of
https://github.com/traefik/traefik.git
synced 2026-05-05 20:36:20 +02:00
Add maxResponseBodySize configuration on HTTP provider
This commit is contained in:
parent
bb7bb49226
commit
b460351f7e
@ -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.
|
||||
|
||||
@ -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
|
||||
```
|
||||
|
||||
@ -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```)
|
||||
|
||||
|
||||
@ -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```)
|
||||
|
||||
|
||||
@ -274,6 +274,7 @@
|
||||
endpoint = "foobar"
|
||||
pollInterval = "42s"
|
||||
pollTimeout = "42s"
|
||||
maxResponseBodySize = 42
|
||||
[providers.http.tls]
|
||||
ca = "foobar"
|
||||
caOptional = true
|
||||
|
||||
@ -310,6 +310,7 @@ providers:
|
||||
cert: foobar
|
||||
key: foobar
|
||||
insecureSkipVerify: true
|
||||
maxResponseBodySize: 42
|
||||
plugin:
|
||||
PluginConf0:
|
||||
name0: foobar
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user