diff --git a/apis/projectcontour/v1alpha1/contourconfig.go b/apis/projectcontour/v1alpha1/contourconfig.go index ce9cbf7d374..b2bbdfaa580 100644 --- a/apis/projectcontour/v1alpha1/contourconfig.go +++ b/apis/projectcontour/v1alpha1/contourconfig.go @@ -619,6 +619,12 @@ type RateLimitServiceConfig struct { // ref. https://tools.ietf.org/id/draft-polli-ratelimit-headers-03.html // +optional EnableXRateLimitHeaders *bool `json:"enableXRateLimitHeaders,omitempty"` + + // EnableResourceExhaustedCode enables translating error code 429 to + // grpc code RESOURCE_EXHAUSTED. When disabled it's translated to UNAVAILABLE + // + // +optional + EnableResourceExhaustedCode *bool `json:"enableResourceExhaustedCode,omitempty"` } // PolicyConfig holds default policy used if not explicitly set by the user diff --git a/cmd/contour/serve.go b/cmd/contour/serve.go index 7b1c728ebf5..15ee63a1eb2 100644 --- a/cmd/contour/serve.go +++ b/cmd/contour/serve.go @@ -610,12 +610,13 @@ func (s *Server) setupRateLimitService(contourConfiguration contour_api_v1alpha1 } return &xdscache_v3.RateLimitConfig{ - ExtensionService: key, - SNI: sni, - Domain: contourConfiguration.RateLimitService.Domain, - Timeout: responseTimeout, - FailOpen: ref.Val(contourConfiguration.RateLimitService.FailOpen, false), - EnableXRateLimitHeaders: ref.Val(contourConfiguration.RateLimitService.EnableXRateLimitHeaders, false), + ExtensionService: key, + SNI: sni, + Domain: contourConfiguration.RateLimitService.Domain, + Timeout: responseTimeout, + FailOpen: ref.Val(contourConfiguration.RateLimitService.FailOpen, false), + EnableXRateLimitHeaders: ref.Val(contourConfiguration.RateLimitService.EnableXRateLimitHeaders, false), + EnableResourceExhaustedCode: ref.Val(contourConfiguration.RateLimitService.EnableResourceExhaustedCode, false), }, nil } diff --git a/cmd/contour/servecontext.go b/cmd/contour/servecontext.go index e685e532315..1857575ae64 100644 --- a/cmd/contour/servecontext.go +++ b/cmd/contour/servecontext.go @@ -371,9 +371,10 @@ func (ctx *serveContext) convertToContourConfigurationSpec() contour_api_v1alpha Name: nsedName.Name, Namespace: nsedName.Namespace, }, - Domain: ctx.Config.RateLimitService.Domain, - FailOpen: ref.To(ctx.Config.RateLimitService.FailOpen), - EnableXRateLimitHeaders: ref.To(ctx.Config.RateLimitService.EnableXRateLimitHeaders), + Domain: ctx.Config.RateLimitService.Domain, + FailOpen: ref.To(ctx.Config.RateLimitService.FailOpen), + EnableXRateLimitHeaders: ref.To(ctx.Config.RateLimitService.EnableXRateLimitHeaders), + EnableResourceExhaustedCode: ref.To(ctx.Config.RateLimitService.EnableResourceExhaustedCode), } } diff --git a/examples/contour/01-contour-config.yaml b/examples/contour/01-contour-config.yaml index 4b87eaeef5f..d1372c8fd21 100644 --- a/examples/contour/01-contour-config.yaml +++ b/examples/contour/01-contour-config.yaml @@ -145,6 +145,9 @@ data: # Limit Service is consulted for a request. # ref. https://tools.ietf.org/id/draft-polli-ratelimit-headers-03.html # enableXRateLimitHeaders: false + # Defines whether to translate status code 429 to grpc code RESOURCE_EXHAUSTED + # instead of the default UNAVAILABLE + # enableResourceExhaustedCode: false # # Global Policy settings. # policy: diff --git a/examples/render/contour.yaml b/examples/render/contour.yaml index edf4895f909..374f897b78a 100644 --- a/examples/render/contour.yaml +++ b/examples/render/contour.yaml @@ -178,6 +178,9 @@ data: # Limit Service is consulted for a request. # ref. https://tools.ietf.org/id/draft-polli-ratelimit-headers-03.html # enableXRateLimitHeaders: false + # Defines whether to translate status code 429 to grpc code RESOURCE_EXHAUSTED + # instead of the default UNAVAILABLE + # enableResourceExhaustedCode: false # # Global Policy settings. # policy: diff --git a/internal/envoy/v3/ratelimit.go b/internal/envoy/v3/ratelimit.go index 3a201bc052b..1aebd499570 100644 --- a/internal/envoy/v3/ratelimit.go +++ b/internal/envoy/v3/ratelimit.go @@ -123,12 +123,13 @@ func GlobalRateLimits(descriptors []*dag.RateLimitDescriptor) []*envoy_route_v3. // GlobalRateLimitConfig stores configuration for // an HTTP global rate limiting filter. type GlobalRateLimitConfig struct { - ExtensionService types.NamespacedName - SNI string - FailOpen bool - Timeout timeout.Setting - Domain string - EnableXRateLimitHeaders bool + ExtensionService types.NamespacedName + SNI string + FailOpen bool + Timeout timeout.Setting + Domain string + EnableXRateLimitHeaders bool + EnableResourceExhaustedCode bool } // GlobalRateLimitFilter returns a configured HTTP global rate limit filter, @@ -149,7 +150,8 @@ func GlobalRateLimitFilter(config *GlobalRateLimitConfig) *http.HttpFilter { GrpcService: GrpcService(dag.ExtensionClusterName(config.ExtensionService), config.SNI, timeout.DefaultSetting()), TransportApiVersion: envoy_core_v3.ApiVersion_V3, }, - EnableXRatelimitHeaders: enableXRateLimitHeaders(config.EnableXRateLimitHeaders), + EnableXRatelimitHeaders: enableXRateLimitHeaders(config.EnableXRateLimitHeaders), + RateLimitedAsResourceExhausted: config.EnableResourceExhaustedCode, }), }, } diff --git a/internal/envoy/v3/ratelimit_test.go b/internal/envoy/v3/ratelimit_test.go index 969931fb876..6010b72808d 100644 --- a/internal/envoy/v3/ratelimit_test.go +++ b/internal/envoy/v3/ratelimit_test.go @@ -372,6 +372,37 @@ func TestGlobalRateLimitFilter(t *testing.T) { }, }, }, + "EnableResourceExhaustedCode=true is configured correctly": { + cfg: &GlobalRateLimitConfig{ + ExtensionService: k8s.NamespacedNameFrom("projectcontour/ratelimit"), + Timeout: timeout.DurationSetting(7 * time.Second), + Domain: "domain", + FailOpen: true, + EnableResourceExhaustedCode: true, + }, + want: &http.HttpFilter{ + Name: wellknown.HTTPRateLimit, + ConfigType: &http.HttpFilter_TypedConfig{ + TypedConfig: protobuf.MustMarshalAny(&ratelimit_filter_v3.RateLimit{ + Domain: "domain", + Timeout: durationpb.New(7 * time.Second), + FailureModeDeny: false, + RateLimitService: &ratelimit_config_v3.RateLimitServiceConfig{ + GrpcService: &envoy_core_v3.GrpcService{ + TargetSpecifier: &envoy_core_v3.GrpcService_EnvoyGrpc_{ + EnvoyGrpc: &envoy_core_v3.GrpcService_EnvoyGrpc{ + ClusterName: "extension/projectcontour/ratelimit", + Authority: "extension.projectcontour.ratelimit", + }, + }, + }, + TransportApiVersion: envoy_core_v3.ApiVersion_V3, + }, + RateLimitedAsResourceExhausted: true, + }), + }, + }, + }, } for name, tc := range tests { diff --git a/internal/xdscache/v3/listener.go b/internal/xdscache/v3/listener.go index 74a7e8559dd..5e6e31836ff 100644 --- a/internal/xdscache/v3/listener.go +++ b/internal/xdscache/v3/listener.go @@ -140,12 +140,13 @@ type ListenerConfig struct { } type RateLimitConfig struct { - ExtensionService types.NamespacedName - SNI string - Domain string - Timeout timeout.Setting - FailOpen bool - EnableXRateLimitHeaders bool + ExtensionService types.NamespacedName + SNI string + Domain string + Timeout timeout.Setting + FailOpen bool + EnableXRateLimitHeaders bool + EnableResourceExhaustedCode bool } // DefaultListeners returns the configured Listeners or a single @@ -558,12 +559,13 @@ func envoyGlobalRateLimitConfig(config *RateLimitConfig) *envoy_v3.GlobalRateLim } return &envoy_v3.GlobalRateLimitConfig{ - ExtensionService: config.ExtensionService, - SNI: config.SNI, - FailOpen: config.FailOpen, - Timeout: config.Timeout, - Domain: config.Domain, - EnableXRateLimitHeaders: config.EnableXRateLimitHeaders, + ExtensionService: config.ExtensionService, + SNI: config.SNI, + FailOpen: config.FailOpen, + Timeout: config.Timeout, + Domain: config.Domain, + EnableXRateLimitHeaders: config.EnableXRateLimitHeaders, + EnableResourceExhaustedCode: config.EnableResourceExhaustedCode, } } diff --git a/internal/xdscache/v3/listener_test.go b/internal/xdscache/v3/listener_test.go index 474ef77dbf8..ab9f8b28959 100644 --- a/internal/xdscache/v3/listener_test.go +++ b/internal/xdscache/v3/listener_test.go @@ -3024,11 +3024,12 @@ func TestListenerVisit(t *testing.T) { "insecure httpproxy with rate limit config": { ListenerConfig: ListenerConfig{ RateLimitConfig: &RateLimitConfig{ - ExtensionService: types.NamespacedName{Namespace: "projectcontour", Name: "ratelimit"}, - Domain: "contour", - Timeout: timeout.DurationSetting(7 * time.Second), - FailOpen: false, - EnableXRateLimitHeaders: true, + ExtensionService: types.NamespacedName{Namespace: "projectcontour", Name: "ratelimit"}, + Domain: "contour", + Timeout: timeout.DurationSetting(7 * time.Second), + FailOpen: false, + EnableXRateLimitHeaders: true, + EnableResourceExhaustedCode: true, }, }, objs: []interface{}{ @@ -3092,7 +3093,8 @@ func TestListenerVisit(t *testing.T) { }, TransportApiVersion: envoy_core_v3.ApiVersion_V3, }, - EnableXRatelimitHeaders: ratelimit_filter_v3.RateLimit_DRAFT_VERSION_03, + EnableXRatelimitHeaders: ratelimit_filter_v3.RateLimit_DRAFT_VERSION_03, + RateLimitedAsResourceExhausted: true, }), }, }).Get()), @@ -3102,12 +3104,13 @@ func TestListenerVisit(t *testing.T) { "secure httpproxy with rate limit config": { ListenerConfig: ListenerConfig{ RateLimitConfig: &RateLimitConfig{ - ExtensionService: types.NamespacedName{Namespace: "projectcontour", Name: "ratelimit"}, - SNI: "ratelimit-example.com", - Domain: "contour", - Timeout: timeout.DurationSetting(7 * time.Second), - FailOpen: false, - EnableXRateLimitHeaders: true, + ExtensionService: types.NamespacedName{Namespace: "projectcontour", Name: "ratelimit"}, + SNI: "ratelimit-example.com", + Domain: "contour", + Timeout: timeout.DurationSetting(7 * time.Second), + FailOpen: false, + EnableXRateLimitHeaders: true, + EnableResourceExhaustedCode: true, }, }, objs: []interface{}{ @@ -3179,7 +3182,8 @@ func TestListenerVisit(t *testing.T) { }, TransportApiVersion: envoy_core_v3.ApiVersion_V3, }, - EnableXRatelimitHeaders: ratelimit_filter_v3.RateLimit_DRAFT_VERSION_03, + EnableXRatelimitHeaders: ratelimit_filter_v3.RateLimit_DRAFT_VERSION_03, + RateLimitedAsResourceExhausted: true, }), }, }). @@ -3218,7 +3222,8 @@ func TestListenerVisit(t *testing.T) { }, TransportApiVersion: envoy_core_v3.ApiVersion_V3, }, - EnableXRatelimitHeaders: ratelimit_filter_v3.RateLimit_DRAFT_VERSION_03, + EnableXRatelimitHeaders: ratelimit_filter_v3.RateLimit_DRAFT_VERSION_03, + RateLimitedAsResourceExhausted: true, }), }, }). @@ -3237,11 +3242,12 @@ func TestListenerVisit(t *testing.T) { }, ListenerConfig: ListenerConfig{ RateLimitConfig: &RateLimitConfig{ - ExtensionService: types.NamespacedName{Namespace: "projectcontour", Name: "ratelimit"}, - Domain: "contour", - Timeout: timeout.DurationSetting(7 * time.Second), - FailOpen: false, - EnableXRateLimitHeaders: true, + ExtensionService: types.NamespacedName{Namespace: "projectcontour", Name: "ratelimit"}, + Domain: "contour", + Timeout: timeout.DurationSetting(7 * time.Second), + FailOpen: false, + EnableXRateLimitHeaders: true, + EnableResourceExhaustedCode: true, }, }, objs: []interface{}{ @@ -3326,7 +3332,8 @@ func TestListenerVisit(t *testing.T) { }, TransportApiVersion: envoy_core_v3.ApiVersion_V3, }, - EnableXRatelimitHeaders: ratelimit_filter_v3.RateLimit_DRAFT_VERSION_03, + EnableXRatelimitHeaders: ratelimit_filter_v3.RateLimit_DRAFT_VERSION_03, + RateLimitedAsResourceExhausted: true, }), }, }). @@ -3365,7 +3372,8 @@ func TestListenerVisit(t *testing.T) { }, TransportApiVersion: envoy_core_v3.ApiVersion_V3, }, - EnableXRatelimitHeaders: ratelimit_filter_v3.RateLimit_DRAFT_VERSION_03, + EnableXRatelimitHeaders: ratelimit_filter_v3.RateLimit_DRAFT_VERSION_03, + RateLimitedAsResourceExhausted: true, }), }, }). @@ -3399,7 +3407,8 @@ func TestListenerVisit(t *testing.T) { }, TransportApiVersion: envoy_core_v3.ApiVersion_V3, }, - EnableXRatelimitHeaders: ratelimit_filter_v3.RateLimit_DRAFT_VERSION_03, + EnableXRatelimitHeaders: ratelimit_filter_v3.RateLimit_DRAFT_VERSION_03, + RateLimitedAsResourceExhausted: true, }), }, }). diff --git a/pkg/config/parameters.go b/pkg/config/parameters.go index b74e33aa6f5..db064e3dbe3 100644 --- a/pkg/config/parameters.go +++ b/pkg/config/parameters.go @@ -554,6 +554,10 @@ type RateLimitService struct { // // ref. https://tools.ietf.org/id/draft-polli-ratelimit-headers-03.html EnableXRateLimitHeaders bool `yaml:"enableXRateLimitHeaders,omitempty"` + + // EnableResourceExhaustedCode enables translating error code 429 to + // grpc code RESOURCE_EXHAUSTED. When disabled it's translated to UNAVAILABLE + EnableResourceExhaustedCode bool `yaml:"enableResourceExhaustedCode,omitempty"` } // MetricsParameters defines configuration for metrics server endpoints in both diff --git a/site/content/docs/main/configuration.md b/site/content/docs/main/configuration.md index 51df6a005db..58dc4db91f8 100644 --- a/site/content/docs/main/configuration.md +++ b/site/content/docs/main/configuration.md @@ -236,12 +236,13 @@ Note: the values of entries in the `set` and `remove` fields can be overridden i The rate limit service configuration block is used to configure an optional global rate limit service: -| Field Name | Type | Default | Description | -| ----------------------- | ------ | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| extensionService | string | | This field identifies the extension service defining the rate limit service, formatted as /. | -| domain | string | contour | This field defines the rate limit domain value to pass to the rate limit service. Acts as a container for a set of rate limit definitions within the RLS. | -| failOpen | bool | false | This field defines whether to allow requests to proceed when the rate limit service fails to respond with a valid rate limit decision within the timeout defined on the extension service. | -| enableXRateLimitHeaders | bool | false | This field defines whether to include the X-RateLimit headers X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset (as defined by the IETF Internet-Draft https://tools.ietf.org/id/draft-polli-ratelimit-headers-03.html), on responses to clients when the Rate Limit Service is consulted for a request. | +| Field Name | Type | Default | Description | +|-----------------------------| ------ | ------- |------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| extensionService | string | | This field identifies the extension service defining the rate limit service, formatted as /. | +| domain | string | contour | This field defines the rate limit domain value to pass to the rate limit service. Acts as a container for a set of rate limit definitions within the RLS. | +| failOpen | bool | false | This field defines whether to allow requests to proceed when the rate limit service fails to respond with a valid rate limit decision within the timeout defined on the extension service. | +| enableXRateLimitHeaders | bool | false | This field defines whether to include the X-RateLimit headers X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset (as defined by the IETF Internet-Draft https://tools.ietf.org/id/draft-polli-ratelimit-headers-03.html), on responses to clients when the Rate Limit Service is consulted for a request. | +| enableResourceExhaustedCode | bool | false | This field defines whether to translate status code 429 to gRPC RESOURCE_EXHAUSTED instead of UNAVAILABLE. | ### Metrics Configuration @@ -390,12 +391,15 @@ data: # service fails to respond with a valid rate limit decision within # the timeout defined on the extension service. # failOpen: false - # Defines whether to include the X-RateLimit headers X-RateLimit-Limit, - # X-RateLimit-Remaining, and X-RateLimit-Reset (as defined by the IETF - # Internet-Draft linked below), on responses to clients when the Rate - # Limit Service is consulted for a request. - # ref. https://tools.ietf.org/id/draft-polli-ratelimit-headers-03.html + # Defines whether to include the X-RateLimit headers X-RateLimit-Limit, + # X-RateLimit-Remaining, and X-RateLimit-Reset (as defined by the IETF + # Internet-Draft linked below), on responses to clients when the Rate + # Limit Service is consulted for a request. + # ref. https://tools.ietf.org/id/draft-polli-ratelimit-headers-03.html # enableXRateLimitHeaders: false + # Defines whether to translate status code 429 to grpc code RESOURCE_EXHAUSTED + # instead of the default UNAVAILABLE + # enableResourceExhaustedCode: false # # Global Policy settings. # policy: