Skip to content

Commit

Permalink
Merge pull request #9067 from naemono/6074-allow-config-MaxHeaderBytes
Browse files Browse the repository at this point in the history
Adds option to configure HTTP Server's MaxHeaderBytes
  • Loading branch information
dnephin committed Jan 5, 2021
2 parents a30959d + ba3fe0c commit b0574c9
Show file tree
Hide file tree
Showing 8 changed files with 109 additions and 4 deletions.
3 changes: 3 additions & 0 deletions .changelog/9067.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:feature
agent: add config flag `MaxHeaderBytes` to control the maximum size of the http header per client request.
```
7 changes: 4 additions & 3 deletions agent/agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -795,9 +795,10 @@ func (a *Agent) listenHTTP() ([]apiServer, error) {
a.configReloaders = append(a.configReloaders, srv.ReloadConfig)
a.httpHandlers = srv
httpServer := &http.Server{
Addr: l.Addr().String(),
TLSConfig: tlscfg,
Handler: srv.handler(a.config.EnableDebug),
Addr: l.Addr().String(),
TLSConfig: tlscfg,
Handler: srv.handler(a.config.EnableDebug),
MaxHeaderBytes: a.config.HTTPMaxHeaderBytes,
}

// Load the connlimit helper into the server
Expand Down
85 changes: 85 additions & 0 deletions agent/agent_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,91 @@ func TestAgent_ReconnectConfigSettings(t *testing.T) {
}()
}

func TestAgent_HTTPMaxHeaderBytes(t *testing.T) {
tests := []struct {
name string
maxHeaderBytes int
expectedHTTPResponse int
}{
{
"max header bytes 1 returns 431 http response when too large headers are sent",
1,
431,
},
{
"max header bytes 0 returns 200 http response, as the http.DefaultMaxHeaderBytes size of 1MB is used",
0,
200,
},
{
"negative maxHeaderBytes returns 200 http response, as the http.DefaultMaxHeaderBytes size of 1MB is used",
-10,
200,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ports, err := freeport.Take(1)
require.NoError(t, err)
t.Cleanup(func() { freeport.Return(ports) })

caConfig := tlsutil.Config{}
tlsConf, err := tlsutil.NewConfigurator(caConfig, hclog.New(nil))
require.NoError(t, err)

bd := BaseDeps{
Deps: consul.Deps{
Logger: hclog.NewInterceptLogger(nil),
Tokens: new(token.Store),
TLSConfigurator: tlsConf,
},
RuntimeConfig: &config.RuntimeConfig{
HTTPAddrs: []net.Addr{
&net.TCPAddr{IP: net.ParseIP("127.0.0.1"), Port: ports[0]},
},
HTTPMaxHeaderBytes: tt.maxHeaderBytes,
},
Cache: cache.New(cache.Options{}),
}
a, err := New(bd)
require.NoError(t, err)

srvs, err := a.listenHTTP()
require.NoError(t, err)

require.Equal(t, tt.maxHeaderBytes, a.config.HTTPMaxHeaderBytes)

ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
t.Cleanup(cancel)

g := new(errgroup.Group)
for _, s := range srvs {
g.Go(s.Run)
}

require.Len(t, srvs, 1)

client := &http.Client{}
for _, s := range srvs {
u := url.URL{Scheme: s.Protocol, Host: s.Addr.String()}
req, err := http.NewRequest(http.MethodGet, u.String(), nil)
require.NoError(t, err)

// This is directly pulled from the testing of request limits in the net/http source
// https://github.com/golang/go/blob/go1.15.3/src/net/http/serve_test.go#L2897-L2900
var bytesPerHeader = len("header12345: val12345\r\n")
for i := 0; i < ((tt.maxHeaderBytes+4096)/bytesPerHeader)+1; i++ {
req.Header.Set(fmt.Sprintf("header%05d", i), fmt.Sprintf("val%05d", i))
}

resp, err := client.Do(req.WithContext(ctx))
require.NoError(t, err)
require.Equal(t, tt.expectedHTTPResponse, resp.StatusCode, "expected a '%d' http response, got '%d'", tt.expectedHTTPResponse, resp.StatusCode)
}
})
}
}

func TestAgent_ReconnectConfigWanDisabled(t *testing.T) {
if testing.Short() {
t.Skip("too slow for testing.Short")
Expand Down
1 change: 1 addition & 0 deletions agent/config/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -920,6 +920,7 @@ func (b *Builder) Build() (rt RuntimeConfig, err error) {
HTTPAddrs: httpAddrs,
HTTPSAddrs: httpsAddrs,
HTTPBlockEndpoints: c.HTTPConfig.BlockEndpoints,
HTTPMaxHeaderBytes: b.intVal(c.HTTPConfig.MaxHeaderBytes),
HTTPResponseHeaders: c.HTTPConfig.ResponseHeaders,
AllowWriteHTTPFrom: b.cidrsVal("allow_write_http_from", c.HTTPConfig.AllowWriteHTTPFrom),
HTTPUseCache: b.boolValWithDefault(c.HTTPConfig.UseCache, true),
Expand Down
1 change: 1 addition & 0 deletions agent/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -614,6 +614,7 @@ type HTTPConfig struct {
AllowWriteHTTPFrom []string `json:"allow_write_http_from,omitempty" hcl:"allow_write_http_from" mapstructure:"allow_write_http_from"`
ResponseHeaders map[string]string `json:"response_headers,omitempty" hcl:"response_headers" mapstructure:"response_headers"`
UseCache *bool `json:"use_cache,omitempty" hcl:"use_cache" mapstructure:"use_cache"`
MaxHeaderBytes *int `json:"max_header_bytes,omitempty" hcl:"max_header_bytes" mapstructure:"max_header_bytes"`
}

type Performance struct {
Expand Down
8 changes: 8 additions & 0 deletions agent/config/runtime.go
Original file line number Diff line number Diff line change
Expand Up @@ -779,6 +779,14 @@ type RuntimeConfig struct {
// hcl: limits{ http_max_conns_per_client = 200 }
HTTPMaxConnsPerClient int

// HTTPMaxHeaderBytes controls the maximum number of bytes the
// server will read parsing the request header's keys and
// values, including the request line. It does not limit the
// size of the request body.
//
// If zero, or negative, http.DefaultMaxHeaderBytes is used.
HTTPMaxHeaderBytes int

// HTTPSHandshakeTimeout is the time allowed for HTTPS client to complete the
// TLS handshake and send first bytes of the request.
//
Expand Down
6 changes: 5 additions & 1 deletion agent/config/runtime_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5270,7 +5270,8 @@ func TestFullConfig(t *testing.T) {
"M6TKa9NP": "xjuxjOzQ",
"JRCrHZed": "rl0mTx81"
},
"use_cache": false
"use_cache": false,
"max_header_bytes": 10
},
"key_file": "IEkkwgIA",
"leave_on_terminate": true,
Expand Down Expand Up @@ -5959,6 +5960,7 @@ func TestFullConfig(t *testing.T) {
"JRCrHZed" = "rl0mTx81"
}
use_cache = false
max_header_bytes = 10
}
key_file = "IEkkwgIA"
leave_on_terminate = true
Expand Down Expand Up @@ -6738,6 +6740,7 @@ func TestFullConfig(t *testing.T) {
HTTPResponseHeaders: map[string]string{"M6TKa9NP": "xjuxjOzQ", "JRCrHZed": "rl0mTx81"},
HTTPSAddrs: []net.Addr{tcpAddr("95.17.17.19:15127")},
HTTPMaxConnsPerClient: 100,
HTTPMaxHeaderBytes: 10,
HTTPSHandshakeTimeout: 2391 * time.Millisecond,
HTTPSPort: 15127,
HTTPUseCache: false,
Expand Down Expand Up @@ -7665,6 +7668,7 @@ func TestSanitize(t *testing.T) {
],
"HTTPBlockEndpoints": [],
"HTTPMaxConnsPerClient": 0,
"HTTPMaxHeaderBytes": 0,
"HTTPPort": 0,
"HTTPResponseHeaders": {},
"HTTPUseCache": false,
Expand Down
2 changes: 2 additions & 0 deletions website/content/docs/agent/options.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -1633,6 +1633,8 @@ bind_addr = "{{ GetPrivateInterfaces | include \"network\" \"10.0.0.0/8\" | attr

- `use_cache` ((#http_config_use_cache)) Defaults to true. If disabled, the agent won't be using [agent caching](/api/features/caching) to answer the request. Even when the url parameter is provided.
- `max_header_bytes` This setting controls the maximum number of bytes the consul http server will read parsing the request header's keys and values, including the request line. It does not limit the size of the request body. If zero, or negative, http.DefaultMaxHeaderBytes is used, which equates to 1 Megabyte.

- `leave_on_terminate` If enabled, when the agent receives a TERM signal, it will send a `Leave` message to the rest of the cluster and gracefully leave. The default behavior for this feature varies based on whether or not the agent is running as a client or a server (prior to Consul 0.7 the default value was unconditionally set to `false`). On agents in client-mode, this defaults to `true` and for agents in server-mode, this defaults to `false`.

- `limits` Available in Consul 0.9.3 and later, this is a nested
Expand Down

0 comments on commit b0574c9

Please sign in to comment.