mirror of https://github.com/hashicorp/consul
Resolves #6074. Adds new option to configure HTTP Server's MaxHeaderBytes with option `-http-max-header-bytes`
Adds tests for behaviorpull/9067/head
parent
37cfa479d8
commit
5b6ac035ff
|
@ -775,9 +775,10 @@ func (a *Agent) listenHTTP() ([]apiServer, error) {
|
||||||
a.configReloaders = append(a.configReloaders, srv.ReloadConfig)
|
a.configReloaders = append(a.configReloaders, srv.ReloadConfig)
|
||||||
a.httpHandlers = srv
|
a.httpHandlers = srv
|
||||||
httpServer := &http.Server{
|
httpServer := &http.Server{
|
||||||
Addr: l.Addr().String(),
|
Addr: l.Addr().String(),
|
||||||
TLSConfig: tlscfg,
|
TLSConfig: tlscfg,
|
||||||
Handler: srv.handler(a.config.EnableDebug),
|
Handler: srv.handler(a.config.EnableDebug),
|
||||||
|
MaxHeaderBytes: a.config.HTTPMaxHeaderBytes,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load the connlimit helper into the server
|
// 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)
|
return fmt.Errorf("%s server %s failed: %w", proto, l.Addr(), err)
|
||||||
},
|
},
|
||||||
|
MaxHeaderBytes: a.config.HTTPMaxHeaderBytes,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return nil
|
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) {
|
func TestAgent_ReconnectConfigWanDisabled(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
|
|
|
@ -37,6 +37,8 @@ type apiServer struct {
|
||||||
Run func() error
|
Run func() error
|
||||||
// Shutdown function used to stop the server
|
// Shutdown function used to stop the server
|
||||||
Shutdown func(context.Context) error
|
Shutdown func(context.Context) error
|
||||||
|
|
||||||
|
MaxHeaderBytes int
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewAPIServers returns an empty apiServers that is ready to Start servers.
|
// 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,
|
HTTPAddrs: httpAddrs,
|
||||||
HTTPSAddrs: httpsAddrs,
|
HTTPSAddrs: httpsAddrs,
|
||||||
HTTPBlockEndpoints: c.HTTPConfig.BlockEndpoints,
|
HTTPBlockEndpoints: c.HTTPConfig.BlockEndpoints,
|
||||||
|
HTTPMaxHeaderBytes: b.intVal(c.HTTPConfig.MaxHeaderBytes),
|
||||||
HTTPResponseHeaders: c.HTTPConfig.ResponseHeaders,
|
HTTPResponseHeaders: c.HTTPConfig.ResponseHeaders,
|
||||||
AllowWriteHTTPFrom: b.cidrsVal("allow_write_http_from", c.HTTPConfig.AllowWriteHTTPFrom),
|
AllowWriteHTTPFrom: b.cidrsVal("allow_write_http_from", c.HTTPConfig.AllowWriteHTTPFrom),
|
||||||
HTTPUseCache: b.boolValWithDefault(c.HTTPConfig.UseCache, true),
|
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"`
|
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"`
|
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"`
|
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 {
|
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.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.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.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.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.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.")
|
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 }
|
// hcl: limits{ http_max_conns_per_client = 200 }
|
||||||
HTTPMaxConnsPerClient int
|
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
|
// HTTPSHandshakeTimeout is the time allowed for HTTPS client to complete the
|
||||||
// TLS handshake and send first bytes of the request.
|
// TLS handshake and send first bytes of the request.
|
||||||
//
|
//
|
||||||
|
|
|
@ -509,6 +509,17 @@ func TestBuilder_BuildAndValidate_ConfigFlagsAndEdgecases(t *testing.T) {
|
||||||
rt.DataDir = dataDir
|
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",
|
desc: "-join",
|
||||||
args: []string{
|
args: []string{
|
||||||
|
@ -5074,7 +5085,8 @@ func TestFullConfig(t *testing.T) {
|
||||||
"M6TKa9NP": "xjuxjOzQ",
|
"M6TKa9NP": "xjuxjOzQ",
|
||||||
"JRCrHZed": "rl0mTx81"
|
"JRCrHZed": "rl0mTx81"
|
||||||
},
|
},
|
||||||
"use_cache": false
|
"use_cache": false,
|
||||||
|
"max_header_bytes": 10
|
||||||
},
|
},
|
||||||
"key_file": "IEkkwgIA",
|
"key_file": "IEkkwgIA",
|
||||||
"leave_on_terminate": true,
|
"leave_on_terminate": true,
|
||||||
|
@ -5761,6 +5773,7 @@ func TestFullConfig(t *testing.T) {
|
||||||
"JRCrHZed" = "rl0mTx81"
|
"JRCrHZed" = "rl0mTx81"
|
||||||
}
|
}
|
||||||
use_cache = false
|
use_cache = false
|
||||||
|
max_header_bytes = 10
|
||||||
}
|
}
|
||||||
key_file = "IEkkwgIA"
|
key_file = "IEkkwgIA"
|
||||||
leave_on_terminate = true
|
leave_on_terminate = true
|
||||||
|
@ -6538,6 +6551,7 @@ func TestFullConfig(t *testing.T) {
|
||||||
HTTPResponseHeaders: map[string]string{"M6TKa9NP": "xjuxjOzQ", "JRCrHZed": "rl0mTx81"},
|
HTTPResponseHeaders: map[string]string{"M6TKa9NP": "xjuxjOzQ", "JRCrHZed": "rl0mTx81"},
|
||||||
HTTPSAddrs: []net.Addr{tcpAddr("95.17.17.19:15127")},
|
HTTPSAddrs: []net.Addr{tcpAddr("95.17.17.19:15127")},
|
||||||
HTTPMaxConnsPerClient: 100,
|
HTTPMaxConnsPerClient: 100,
|
||||||
|
HTTPMaxHeaderBytes: 10,
|
||||||
HTTPSHandshakeTimeout: 2391 * time.Millisecond,
|
HTTPSHandshakeTimeout: 2391 * time.Millisecond,
|
||||||
HTTPSPort: 15127,
|
HTTPSPort: 15127,
|
||||||
HTTPUseCache: false,
|
HTTPUseCache: false,
|
||||||
|
|
Loading…
Reference in New Issue