Resolves #6074. Adds new option to configure HTTP Server's MaxHeaderBytes with option `-http-max-header-bytes`
Adds tests for behavior
This commit is contained in:
parent
aabd0130a9
commit
1c0a46849a
|
@ -775,9 +775,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
|
||||
|
@ -802,6 +803,7 @@ func (a *Agent) listenHTTP() ([]apiServer, error) {
|
|||
}
|
||||
return fmt.Errorf("%s server %s failed: %w", proto, l.Addr(), err)
|
||||
},
|
||||
MaxHeaderBytes: a.config.HTTPMaxHeaderBytes,
|
||||
})
|
||||
}
|
||||
return nil
|
||||
|
|
|
@ -239,6 +239,63 @@ func TestAgent_ReconnectConfigSettings(t *testing.T) {
|
|||
}()
|
||||
}
|
||||
|
||||
func TestAgent_HTTPMaxHeaderBytes(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
maxHeaderBytes int
|
||||
expectError bool
|
||||
expectedHTTPResponse int
|
||||
}{
|
||||
{
|
||||
"max header bytes 1 returns 431 http response when too large headers are sent",
|
||||
1,
|
||||
false,
|
||||
431,
|
||||
},
|
||||
{
|
||||
"max header bytes 0 returns 200 http response, as the http.DefaultMaxHeaderBytes size of 1MB is used",
|
||||
0,
|
||||
false,
|
||||
200,
|
||||
},
|
||||
{
|
||||
"negative maxHeaderBytes returns 200 http response, as the http.DefaultMaxHeaderBytes size of 1MB is used",
|
||||
-10,
|
||||
false,
|
||||
200,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
a := NewTestAgent(t, fmt.Sprintf(`
|
||||
http_config {
|
||||
max_header_bytes = %d
|
||||
}
|
||||
`, tt.maxHeaderBytes))
|
||||
defer a.Shutdown()
|
||||
|
||||
require.Equal(t, tt.maxHeaderBytes, a.Agent.config.HTTPMaxHeaderBytes)
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, "http://"+a.HTTPAddr()+"/v1/health/state/passing", nil)
|
||||
require.NoError(t, err, "unexpected error creating new http request")
|
||||
|
||||
// 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")
|
||||
t.Logf("bytesPerHeader: %d", bytesPerHeader)
|
||||
for i := 0; i < ((tt.maxHeaderBytes+4096)/bytesPerHeader)+1; i++ {
|
||||
req.Header.Set(fmt.Sprintf("header%05d", i), fmt.Sprintf("val%05d", i))
|
||||
}
|
||||
|
||||
var res *http.Response
|
||||
res, err = http.DefaultClient.Do(req)
|
||||
require.True(t, (tt.expectError && (err != nil)) || !tt.expectError && (err == nil))
|
||||
require.Equal(t, tt.expectedHTTPResponse, res.StatusCode, "expected a '%d' http response, got '%d'", tt.expectedHTTPResponse, res.StatusCode)
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestAgent_ReconnectConfigWanDisabled(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
|
|
@ -37,6 +37,8 @@ type apiServer struct {
|
|||
Run func() error
|
||||
// Shutdown function used to stop the server
|
||||
Shutdown func(context.Context) error
|
||||
|
||||
MaxHeaderBytes int
|
||||
}
|
||||
|
||||
// NewAPIServers returns an empty apiServers that is ready to Start servers.
|
||||
|
|
|
@ -918,6 +918,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),
|
||||
|
|
|
@ -613,6 +613,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 {
|
||||
|
|
|
@ -75,6 +75,7 @@ func AddFlags(fs *flag.FlagSet, f *BuilderOpts) {
|
|||
add(&f.Config.HTTPConfig.AllowWriteHTTPFrom, "allow-write-http-from", "Only allow write endpoint calls from given network. CIDR format, can be specified multiple times.")
|
||||
add(&f.Config.EncryptKey, "encrypt", "Provides the gossip encryption key.")
|
||||
add(&f.Config.Ports.GRPC, "grpc-port", "Sets the gRPC API port to listen on (currently needed for Envoy xDS only).")
|
||||
add(&f.Config.HTTPConfig.MaxHeaderBytes, "http-max-header-bytes", "Sets the HTTP Server's Max Header Bytes.")
|
||||
add(&f.Config.Ports.HTTP, "http-port", "Sets the HTTP API port to listen on.")
|
||||
add(&f.Config.Ports.HTTPS, "https-port", "Sets the HTTPS API port to listen on.")
|
||||
add(&f.Config.StartJoinAddrsLAN, "join", "Address of an agent to join at start time. Can be specified multiple times.")
|
||||
|
|
|
@ -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.
|
||||
//
|
||||
|
|
|
@ -509,6 +509,17 @@ func TestBuilder_BuildAndValidate_ConfigFlagsAndEdgecases(t *testing.T) {
|
|||
rt.DataDir = dataDir
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "-http-max-header-bytes",
|
||||
args: []string{
|
||||
`-http-max-header-bytes=1`,
|
||||
`-data-dir=` + dataDir,
|
||||
},
|
||||
patch: func(rt *RuntimeConfig) {
|
||||
rt.HTTPMaxHeaderBytes = 1
|
||||
rt.DataDir = dataDir
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "-join",
|
||||
args: []string{
|
||||
|
@ -5074,7 +5085,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,
|
||||
|
@ -5761,6 +5773,7 @@ func TestFullConfig(t *testing.T) {
|
|||
"JRCrHZed" = "rl0mTx81"
|
||||
}
|
||||
use_cache = false
|
||||
max_header_bytes = 10
|
||||
}
|
||||
key_file = "IEkkwgIA"
|
||||
leave_on_terminate = true
|
||||
|
@ -6538,6 +6551,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,
|
||||
|
|
Loading…
Reference in New Issue