diff --git a/apis/projectcontour/v1alpha1/contourconfig.go b/apis/projectcontour/v1alpha1/contourconfig.go index 5b6eb1ad8bd..e0ec26c597b 100644 --- a/apis/projectcontour/v1alpha1/contourconfig.go +++ b/apis/projectcontour/v1alpha1/contourconfig.go @@ -361,6 +361,16 @@ type EnvoyListenerConfig struct { // TLS holds various configurable Envoy TLS listener values. // +optional TLS *EnvoyTLS `json:"tls,omitempty"` + + // Defines the limit on number of HTTP requests that Envoy will process from a single + // connection in a single I/O cycle. Requests over this limit are processed in subsequent + // I/O cycles. Can be used as a mitigation for CVE-2023-44487 when abusive traffic is + // detected. Configures the http.max_requests_per_io_cycle Envoy runtime setting. The default + // value when this is not set is no limit. + // + // +kubebuilder:validation:Minimum=1 + // +optional + MaxRequestsPerIOCycle *uint32 `json:"maxRequestsPerIOCycle"` } // EnvoyTLS describes tls parameters for Envoy listneners. diff --git a/apis/projectcontour/v1alpha1/zz_generated.deepcopy.go b/apis/projectcontour/v1alpha1/zz_generated.deepcopy.go index 7ee5df65a55..2cd0cc98331 100644 --- a/apis/projectcontour/v1alpha1/zz_generated.deepcopy.go +++ b/apis/projectcontour/v1alpha1/zz_generated.deepcopy.go @@ -526,6 +526,11 @@ func (in *EnvoyListenerConfig) DeepCopyInto(out *EnvoyListenerConfig) { *out = new(EnvoyTLS) (*in).DeepCopyInto(*out) } + if in.MaxRequestsPerIOCycle != nil { + in, out := &in.MaxRequestsPerIOCycle, &out.MaxRequestsPerIOCycle + *out = new(uint32) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EnvoyListenerConfig. diff --git a/changelogs/unreleased/5827-sunjayBhatia-minor.md b/changelogs/unreleased/5827-sunjayBhatia-minor.md new file mode 100644 index 00000000000..93010ffb61a --- /dev/null +++ b/changelogs/unreleased/5827-sunjayBhatia-minor.md @@ -0,0 +1,16 @@ +## Max HTTP requests per IO cycle is configurable as an additional mitigation for HTTP/2 CVE-2023-44487 + +Envoy v1.27.1 mitigates CVE-2023-44487 with some default runtime settings, however the `http.max_requests_per_io_cycle` does not have a default value. +This change allows configuring this runtime setting via Contour configuration to allow administrators of Contour to prevent abusive connections from starving resources from other valid connections. +The default is left as the existing behavior (no limit) so as not to impact existing valid traffic. + +The Contour ConfigMap can be modified similar to the following (and Contour restarted) to set this value: + +``` +listener: + max-requests-per-io-cycle: 10 +``` + +(Note this can be used in addition to the existing Listener configuration field `listener.max-requests-per-connection` which is used primarily for HTTP/1.1 connections and is an approximate limit for HTTP/2) + +See the [Envoy release notes](https://www.envoyproxy.io/docs/envoy/v1.27.1/version_history/v1.27/v1.27.1) for more details. diff --git a/cmd/contour/serve.go b/cmd/contour/serve.go index 179719f358b..7dd02c3529b 100644 --- a/cmd/contour/serve.go +++ b/cmd/contour/serve.go @@ -383,7 +383,9 @@ func (s *Server) doServe() error { &xdscache_v3.RouteCache{}, &xdscache_v3.ClusterCache{}, endpointHandler, - &xdscache_v3.RuntimeCache{}, + xdscache_v3.NewRuntimeCache(xdscache_v3.ConfigurableRuntimeSettings{ + MaxRequestsPerIOCycle: contourConfiguration.Envoy.Listener.MaxRequestsPerIOCycle, + }), } // snapshotHandler is used to produce new snapshots when the internal state changes for any xDS resource. diff --git a/cmd/contour/servecontext.go b/cmd/contour/servecontext.go index dab3bcd267e..f9576c58469 100644 --- a/cmd/contour/servecontext.go +++ b/cmd/contour/servecontext.go @@ -454,6 +454,7 @@ func (ctx *serveContext) convertToContourConfigurationSpec() contour_api_v1alpha DisableMergeSlashes: &ctx.Config.DisableMergeSlashes, ServerHeaderTransformation: serverHeaderTransformation, ConnectionBalancer: ctx.Config.Listener.ConnectionBalancer, + MaxRequestsPerIOCycle: ctx.Config.Listener.MaxRequestsPerIOCycle, TLS: &contour_api_v1alpha1.EnvoyTLS{ MinimumProtocolVersion: ctx.Config.TLS.MinimumProtocolVersion, CipherSuites: cipherSuites, diff --git a/cmd/contour/servecontext_test.go b/cmd/contour/servecontext_test.go index 4d5583caa2b..3aaf2458538 100644 --- a/cmd/contour/servecontext_test.go +++ b/cmd/contour/servecontext_test.go @@ -699,6 +699,16 @@ func TestConvertServeContext(t *testing.T) { return cfg }, }, + "envoy listener settings": { + getServeContext: func(ctx *serveContext) *serveContext { + ctx.Config.Listener.MaxRequestsPerIOCycle = ref.To(uint32(10)) + return ctx + }, + getContourConfiguration: func(cfg contour_api_v1alpha1.ContourConfigurationSpec) contour_api_v1alpha1.ContourConfigurationSpec { + cfg.Envoy.Listener.MaxRequestsPerIOCycle = ref.To(uint32(10)) + return cfg + }, + }, } for name, tc := range cases { diff --git a/examples/contour/01-crds.yaml b/examples/contour/01-crds.yaml index e497e155a22..33da15a8e19 100644 --- a/examples/contour/01-crds.yaml +++ b/examples/contour/01-crds.yaml @@ -180,6 +180,17 @@ spec: slashes from request URL paths. \n Contour's default is false." type: boolean + maxRequestsPerIOCycle: + description: Defines the limit on number of HTTP requests + that Envoy will process from a single connection in a single + I/O cycle. Requests over this limit are processed in subsequent + I/O cycles. Can be used as a mitigation for CVE-2023-44487 + when abusive traffic is detected. Configures the http.max_requests_per_io_cycle + Envoy runtime setting. The default value when this is not + set is no limit. + format: int32 + minimum: 1 + type: integer serverHeaderTransformation: description: "Defines the action to be applied to the Server header on the response path. When configured as overwrite, @@ -3197,6 +3208,18 @@ spec: duplicate slashes from request URL paths. \n Contour's default is false." type: boolean + maxRequestsPerIOCycle: + description: Defines the limit on number of HTTP requests + that Envoy will process from a single connection in + a single I/O cycle. Requests over this limit are processed + in subsequent I/O cycles. Can be used as a mitigation + for CVE-2023-44487 when abusive traffic is detected. + Configures the http.max_requests_per_io_cycle Envoy + runtime setting. The default value when this is not + set is no limit. + format: int32 + minimum: 1 + type: integer serverHeaderTransformation: description: "Defines the action to be applied to the Server header on the response path. When configured diff --git a/examples/render/contour-deployment.yaml b/examples/render/contour-deployment.yaml index f15fb261eb2..b39fe9466ca 100644 --- a/examples/render/contour-deployment.yaml +++ b/examples/render/contour-deployment.yaml @@ -393,6 +393,17 @@ spec: slashes from request URL paths. \n Contour's default is false." type: boolean + maxRequestsPerIOCycle: + description: Defines the limit on number of HTTP requests + that Envoy will process from a single connection in a single + I/O cycle. Requests over this limit are processed in subsequent + I/O cycles. Can be used as a mitigation for CVE-2023-44487 + when abusive traffic is detected. Configures the http.max_requests_per_io_cycle + Envoy runtime setting. The default value when this is not + set is no limit. + format: int32 + minimum: 1 + type: integer serverHeaderTransformation: description: "Defines the action to be applied to the Server header on the response path. When configured as overwrite, @@ -3410,6 +3421,18 @@ spec: duplicate slashes from request URL paths. \n Contour's default is false." type: boolean + maxRequestsPerIOCycle: + description: Defines the limit on number of HTTP requests + that Envoy will process from a single connection in + a single I/O cycle. Requests over this limit are processed + in subsequent I/O cycles. Can be used as a mitigation + for CVE-2023-44487 when abusive traffic is detected. + Configures the http.max_requests_per_io_cycle Envoy + runtime setting. The default value when this is not + set is no limit. + format: int32 + minimum: 1 + type: integer serverHeaderTransformation: description: "Defines the action to be applied to the Server header on the response path. When configured diff --git a/examples/render/contour-gateway-provisioner.yaml b/examples/render/contour-gateway-provisioner.yaml index 756411676a1..e3390a39334 100644 --- a/examples/render/contour-gateway-provisioner.yaml +++ b/examples/render/contour-gateway-provisioner.yaml @@ -194,6 +194,17 @@ spec: slashes from request URL paths. \n Contour's default is false." type: boolean + maxRequestsPerIOCycle: + description: Defines the limit on number of HTTP requests + that Envoy will process from a single connection in a single + I/O cycle. Requests over this limit are processed in subsequent + I/O cycles. Can be used as a mitigation for CVE-2023-44487 + when abusive traffic is detected. Configures the http.max_requests_per_io_cycle + Envoy runtime setting. The default value when this is not + set is no limit. + format: int32 + minimum: 1 + type: integer serverHeaderTransformation: description: "Defines the action to be applied to the Server header on the response path. When configured as overwrite, @@ -3211,6 +3222,18 @@ spec: duplicate slashes from request URL paths. \n Contour's default is false." type: boolean + maxRequestsPerIOCycle: + description: Defines the limit on number of HTTP requests + that Envoy will process from a single connection in + a single I/O cycle. Requests over this limit are processed + in subsequent I/O cycles. Can be used as a mitigation + for CVE-2023-44487 when abusive traffic is detected. + Configures the http.max_requests_per_io_cycle Envoy + runtime setting. The default value when this is not + set is no limit. + format: int32 + minimum: 1 + type: integer serverHeaderTransformation: description: "Defines the action to be applied to the Server header on the response path. When configured diff --git a/examples/render/contour-gateway.yaml b/examples/render/contour-gateway.yaml index eefdec6b033..47feb9d51a0 100644 --- a/examples/render/contour-gateway.yaml +++ b/examples/render/contour-gateway.yaml @@ -399,6 +399,17 @@ spec: slashes from request URL paths. \n Contour's default is false." type: boolean + maxRequestsPerIOCycle: + description: Defines the limit on number of HTTP requests + that Envoy will process from a single connection in a single + I/O cycle. Requests over this limit are processed in subsequent + I/O cycles. Can be used as a mitigation for CVE-2023-44487 + when abusive traffic is detected. Configures the http.max_requests_per_io_cycle + Envoy runtime setting. The default value when this is not + set is no limit. + format: int32 + minimum: 1 + type: integer serverHeaderTransformation: description: "Defines the action to be applied to the Server header on the response path. When configured as overwrite, @@ -3416,6 +3427,18 @@ spec: duplicate slashes from request URL paths. \n Contour's default is false." type: boolean + maxRequestsPerIOCycle: + description: Defines the limit on number of HTTP requests + that Envoy will process from a single connection in + a single I/O cycle. Requests over this limit are processed + in subsequent I/O cycles. Can be used as a mitigation + for CVE-2023-44487 when abusive traffic is detected. + Configures the http.max_requests_per_io_cycle Envoy + runtime setting. The default value when this is not + set is no limit. + format: int32 + minimum: 1 + type: integer serverHeaderTransformation: description: "Defines the action to be applied to the Server header on the response path. When configured diff --git a/examples/render/contour.yaml b/examples/render/contour.yaml index 7b256cdc5f1..9ff7295f938 100644 --- a/examples/render/contour.yaml +++ b/examples/render/contour.yaml @@ -393,6 +393,17 @@ spec: slashes from request URL paths. \n Contour's default is false." type: boolean + maxRequestsPerIOCycle: + description: Defines the limit on number of HTTP requests + that Envoy will process from a single connection in a single + I/O cycle. Requests over this limit are processed in subsequent + I/O cycles. Can be used as a mitigation for CVE-2023-44487 + when abusive traffic is detected. Configures the http.max_requests_per_io_cycle + Envoy runtime setting. The default value when this is not + set is no limit. + format: int32 + minimum: 1 + type: integer serverHeaderTransformation: description: "Defines the action to be applied to the Server header on the response path. When configured as overwrite, @@ -3410,6 +3421,18 @@ spec: duplicate slashes from request URL paths. \n Contour's default is false." type: boolean + maxRequestsPerIOCycle: + description: Defines the limit on number of HTTP requests + that Envoy will process from a single connection in + a single I/O cycle. Requests over this limit are processed + in subsequent I/O cycles. Can be used as a mitigation + for CVE-2023-44487 when abusive traffic is detected. + Configures the http.max_requests_per_io_cycle Envoy + runtime setting. The default value when this is not + set is no limit. + format: int32 + minimum: 1 + type: integer serverHeaderTransformation: description: "Defines the action to be applied to the Server header on the response path. When configured diff --git a/internal/envoy/v3/runtime.go b/internal/envoy/v3/runtime.go index 8e2a121e06b..135eddab56b 100644 --- a/internal/envoy/v3/runtime.go +++ b/internal/envoy/v3/runtime.go @@ -24,11 +24,15 @@ const ( maxRegexProgramSizeWarn = 1000 ) -func RuntimeLayers() []*envoy_service_runtime_v3.Runtime { +func RuntimeLayers(configurableRuntimeFields map[string]*structpb.Value) []*envoy_service_runtime_v3.Runtime { + baseLayer := baseRuntimeLayer() + for k, v := range configurableRuntimeFields { + baseLayer.Fields[k] = v + } return []*envoy_service_runtime_v3.Runtime{ { Name: DynamicRuntimeLayerName, - Layer: baseRuntimeLayer(), + Layer: baseLayer, }, } } @@ -36,8 +40,8 @@ func RuntimeLayers() []*envoy_service_runtime_v3.Runtime { func baseRuntimeLayer() *structpb.Struct { return &structpb.Struct{ Fields: map[string]*structpb.Value{ - "re2.max_program_size.error_level": {Kind: &structpb.Value_NumberValue{NumberValue: maxRegexProgramSizeError}}, - "re2.max_program_size.warn_level": {Kind: &structpb.Value_NumberValue{NumberValue: maxRegexProgramSizeWarn}}, + "re2.max_program_size.error_level": structpb.NewNumberValue(maxRegexProgramSizeError), + "re2.max_program_size.warn_level": structpb.NewNumberValue(maxRegexProgramSizeWarn), }, } } diff --git a/internal/envoy/v3/runtime_test.go b/internal/envoy/v3/runtime_test.go index a0ca20d3cf0..9e84d136b65 100644 --- a/internal/envoy/v3/runtime_test.go +++ b/internal/envoy/v3/runtime_test.go @@ -22,15 +22,38 @@ import ( ) func TestRuntimeLayers(t *testing.T) { - require.Equal(t, []*envoy_service_runtime_v3.Runtime{ - { - Name: "dynamic", - Layer: &structpb.Struct{ - Fields: map[string]*structpb.Value{ - "re2.max_program_size.error_level": {Kind: &structpb.Value_NumberValue{NumberValue: 1 << 20}}, - "re2.max_program_size.warn_level": {Kind: &structpb.Value_NumberValue{NumberValue: 1000}}, - }, + testCases := map[string]struct { + configurableFields map[string]*structpb.Value + }{ + "nil configurable fields": {}, + "empty configurable fields": { + configurableFields: map[string]*structpb.Value{}, + }, + "some configurable fields": { + configurableFields: map[string]*structpb.Value{ + "some.value1": structpb.NewBoolValue(true), + "some.value2": structpb.NewNumberValue(1000), }, }, - }, RuntimeLayers()) + } + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + expectedFields := map[string]*structpb.Value{ + "re2.max_program_size.error_level": structpb.NewNumberValue(1 << 20), + "re2.max_program_size.warn_level": structpb.NewNumberValue(1000), + } + for k, v := range tc.configurableFields { + expectedFields[k] = v + } + layers := RuntimeLayers(tc.configurableFields) + require.Equal(t, []*envoy_service_runtime_v3.Runtime{ + { + Name: "dynamic", + Layer: &structpb.Struct{ + Fields: expectedFields, + }, + }, + }, layers) + }) + } } diff --git a/internal/xdscache/v3/runtime.go b/internal/xdscache/v3/runtime.go index a163350804f..b4124bfa217 100644 --- a/internal/xdscache/v3/runtime.go +++ b/internal/xdscache/v3/runtime.go @@ -20,30 +20,46 @@ import ( envoy_v3 "github.com/projectcontour/contour/internal/envoy/v3" "github.com/projectcontour/contour/internal/protobuf" "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/types/known/structpb" ) +type ConfigurableRuntimeSettings struct { + MaxRequestsPerIOCycle *uint32 +} + // RuntimeCache manages the contents of the gRPC RTDS cache. -type RuntimeCache struct { +type runtimeCache struct { contour.Cond + runtimeKV map[string]*structpb.Value +} + +// NewRuntimeCache builds a RuntimeCache with the provided runtime +// settings that will be set in the runtime layer configured by Contour. +func NewRuntimeCache(runtimeSettings ConfigurableRuntimeSettings) *runtimeCache { + runtimeKV := make(map[string]*structpb.Value) + if runtimeSettings.MaxRequestsPerIOCycle != nil && *runtimeSettings.MaxRequestsPerIOCycle > 0 { + runtimeKV["http.max_requests_per_io_cycle"] = structpb.NewNumberValue(float64(*runtimeSettings.MaxRequestsPerIOCycle)) + } + return &runtimeCache{runtimeKV: runtimeKV} } // Contents returns all Runtime layers. -func (c *RuntimeCache) Contents() []proto.Message { - return protobuf.AsMessages(envoy_v3.RuntimeLayers()) +func (c *runtimeCache) Contents() []proto.Message { + return protobuf.AsMessages(envoy_v3.RuntimeLayers(c.runtimeKV)) } // Query returns only the "dynamic" layer if requested, otherwise empty. -func (c *RuntimeCache) Query(names []string) []proto.Message { +func (c *runtimeCache) Query(names []string) []proto.Message { for _, name := range names { if name == envoy_v3.DynamicRuntimeLayerName { - return protobuf.AsMessages(envoy_v3.RuntimeLayers()) + return protobuf.AsMessages(envoy_v3.RuntimeLayers(c.runtimeKV)) } } return []proto.Message{} } -func (*RuntimeCache) TypeURL() string { return resource.RuntimeType } +func (*runtimeCache) TypeURL() string { return resource.RuntimeType } -func (c *RuntimeCache) OnChange(root *dag.DAG) { +func (c *runtimeCache) OnChange(root *dag.DAG) { // DAG changes do not affect runtime layers at the moment. } diff --git a/internal/xdscache/v3/runtime_test.go b/internal/xdscache/v3/runtime_test.go index 71793f0c40d..b7aa25815d2 100644 --- a/internal/xdscache/v3/runtime_test.go +++ b/internal/xdscache/v3/runtime_test.go @@ -18,16 +18,72 @@ import ( envoy_service_runtime_v3 "github.com/envoyproxy/go-control-plane/envoy/service/runtime/v3" "github.com/projectcontour/contour/internal/protobuf" + "github.com/projectcontour/contour/internal/ref" "google.golang.org/protobuf/proto" "google.golang.org/protobuf/types/known/structpb" ) func TestRuntimeCacheContents(t *testing.T) { - rc := &RuntimeCache{} - protobuf.ExpectEqual(t, runtimeLayers(), rc.Contents()) + testCases := map[string]struct { + runtimeSettings ConfigurableRuntimeSettings + additionalFields map[string]*structpb.Value + }{ + "no values set": { + runtimeSettings: ConfigurableRuntimeSettings{}, + }, + "http max requests per io cycle set": { + runtimeSettings: ConfigurableRuntimeSettings{ + MaxRequestsPerIOCycle: ref.To(uint32(1)), + }, + additionalFields: map[string]*structpb.Value{ + "http.max_requests_per_io_cycle": structpb.NewNumberValue(1), + }, + }, + "http max requests per io cycle set invalid": { + runtimeSettings: ConfigurableRuntimeSettings{ + MaxRequestsPerIOCycle: ref.To(uint32(0)), + }, + }, + "http max requests per io cycle set nil": { + runtimeSettings: ConfigurableRuntimeSettings{ + MaxRequestsPerIOCycle: nil, + }, + }, + } + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + rc := NewRuntimeCache(tc.runtimeSettings) + fields := map[string]*structpb.Value{ + "re2.max_program_size.error_level": structpb.NewNumberValue(1 << 20), + "re2.max_program_size.warn_level": structpb.NewNumberValue(1000), + } + for k, v := range tc.additionalFields { + fields[k] = v + } + protobuf.ExpectEqual(t, []proto.Message{ + &envoy_service_runtime_v3.Runtime{ + Name: "dynamic", + Layer: &structpb.Struct{ + Fields: fields, + }, + }, + }, rc.Contents()) + }) + } } func TestRuntimeCacheQuery(t *testing.T) { + baseRuntimeLayers := []proto.Message{ + &envoy_service_runtime_v3.Runtime{ + Name: "dynamic", + Layer: &structpb.Struct{ + Fields: map[string]*structpb.Value{ + "re2.max_program_size.error_level": structpb.NewNumberValue(1 << 20), + "re2.max_program_size.warn_level": structpb.NewNumberValue(1000), + }, + }, + }, + } testCases := map[string]struct { names []string expected []proto.Message @@ -38,7 +94,7 @@ func TestRuntimeCacheQuery(t *testing.T) { }, "names include dynamic": { names: []string{"foo", "dynamic", "bar"}, - expected: runtimeLayers(), + expected: baseRuntimeLayers, }, "names excludes dynamic": { names: []string{"foo", "bar", "baz"}, @@ -47,22 +103,8 @@ func TestRuntimeCacheQuery(t *testing.T) { } for name, tc := range testCases { t.Run(name, func(t *testing.T) { - rc := &RuntimeCache{} + rc := NewRuntimeCache(ConfigurableRuntimeSettings{}) protobuf.ExpectEqual(t, tc.expected, rc.Query(tc.names)) }) } } - -func runtimeLayers() []proto.Message { - return []proto.Message{ - &envoy_service_runtime_v3.Runtime{ - Name: "dynamic", - Layer: &structpb.Struct{ - Fields: map[string]*structpb.Value{ - "re2.max_program_size.error_level": {Kind: &structpb.Value_NumberValue{NumberValue: 1 << 20}}, - "re2.max_program_size.warn_level": {Kind: &structpb.Value_NumberValue{NumberValue: 1000}}, - }, - }, - }, - } -} diff --git a/internal/xdscache/v3/server_test.go b/internal/xdscache/v3/server_test.go index 0acc20e90f2..22437a86573 100644 --- a/internal/xdscache/v3/server_test.go +++ b/internal/xdscache/v3/server_test.go @@ -210,7 +210,7 @@ func TestGRPC(t *testing.T) { &RouteCache{}, &ClusterCache{}, et, - &RuntimeCache{}, + NewRuntimeCache(ConfigurableRuntimeSettings{}), } eh = contour.NewEventHandler(contour.EventHandlerConfig{ diff --git a/pkg/config/parameters.go b/pkg/config/parameters.go index ed9026803dd..a04a2b4c941 100644 --- a/pkg/config/parameters.go +++ b/pkg/config/parameters.go @@ -436,6 +436,13 @@ type ListenerParameters struct { // See https://www.envoyproxy.io/docs/envoy/latest/api-v2/api/v2/listener.proto#envoy-api-msg-listener-connectionbalanceconfig // for more information. ConnectionBalancer string `yaml:"connection-balancer"` + + // Defines the limit on number of HTTP requests that Envoy will process from a single + // connection in a single I/O cycle. Requests over this limit are processed in subsequent + // I/O cycles. Can be used as a mitigation for CVE-2023-44487 when abusive traffic is + // detected. Configures the http.max_requests_per_io_cycle Envoy runtime setting. The default + // value when this is not set is no limit. + MaxRequestsPerIOCycle *uint32 `yaml:"max-requests-per-io-cycle"` } func (p *ListenerParameters) Validate() error { @@ -446,6 +453,10 @@ func (p *ListenerParameters) Validate() error { if p.ConnectionBalancer != "" && p.ConnectionBalancer != "exact" { return fmt.Errorf("invalid listener connection balancer value %q, only 'exact' connection balancing is supported for now", p.ConnectionBalancer) } + + if p.MaxRequestsPerIOCycle != nil && *p.MaxRequestsPerIOCycle < 1 { + return fmt.Errorf("invalid max connections per IO cycle value %q set on listener, minimum value is 1", *p.MaxRequestsPerIOCycle) + } return nil } diff --git a/pkg/config/parameters_test.go b/pkg/config/parameters_test.go index d136eea3467..871207e7804 100644 --- a/pkg/config/parameters_test.go +++ b/pkg/config/parameters_test.go @@ -18,6 +18,7 @@ import ( "strings" "testing" + "github.com/projectcontour/contour/internal/ref" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "gopkg.in/yaml.v3" @@ -417,6 +418,13 @@ network: num-trusted-hops: 1 admin-port: 9001 `) + + check(func(t *testing.T, conf *Parameters) { + assert.Equal(t, ref.To(uint32(1)), conf.Listener.MaxRequestsPerIOCycle) + }, ` +listener: + max-requests-per-io-cycle: 1 +`) } func TestMetricsParametersValidation(t *testing.T) { @@ -491,4 +499,12 @@ func TestListenerValidation(t *testing.T) { ConnectionBalancer: "invalid", } require.Error(t, l.Validate()) + l = &ListenerParameters{ + MaxRequestsPerIOCycle: ref.To(uint32(1)), + } + require.NoError(t, l.Validate()) + l = &ListenerParameters{ + MaxRequestsPerIOCycle: ref.To(uint32(0)), + } + require.Error(t, l.Validate()) } diff --git a/site/content/docs/main/config/api-reference.html b/site/content/docs/main/config/api-reference.html index be449e43201..edbd5c79505 100644 --- a/site/content/docs/main/config/api-reference.html +++ b/site/content/docs/main/config/api-reference.html @@ -5979,6 +5979,23 @@

EnvoyListenerConfig

TLS holds various configurable Envoy TLS listener values.

+ + +maxRequestsPerIOCycle +
+ +uint32 + + + +(Optional) +

Defines the limit on number of HTTP requests that Envoy will process from a single +connection in a single I/O cycle. Requests over this limit are processed in subsequent +I/O cycles. Can be used as a mitigation for CVE-2023-44487 when abusive traffic is +detected. Configures the http.max_requests_per_io_cycle Envoy runtime setting. The default +value when this is not set is no limit.

+ +

EnvoyLogging diff --git a/site/content/docs/main/configuration.md b/site/content/docs/main/configuration.md index 9fd9c77ff3d..8660031462b 100644 --- a/site/content/docs/main/configuration.md +++ b/site/content/docs/main/configuration.md @@ -185,6 +185,7 @@ The listener configuration block can be used to configure various parameters for | Field Name | Type | Default | Description | | ------------------- | ------ | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | connection-balancer | string | `""` | This field specifies the listener connection balancer. If the value is `exact`, the listener will use the exact connection balancer to balance connections between threads in a single Envoy process. See [the Envoy documentation][14] for more information. | +| max-requests-per-io-cycle | int | none | Defines the limit on number of HTTP requests that Envoy will process from a single connection in a single I/O cycle. Requests over this limit are processed in subsequent I/O cycles. Can be used as a mitigation for CVE-2023-44487 when abusive traffic is detected. Configures the `http.max_requests_per_io_cycle` Envoy runtime setting. The default value when this is not set is no limit. | ### Server Configuration