Skip to content

Commit

Permalink
expose configuration for envoy's RateLimitedAsResourceExhausted
Browse files Browse the repository at this point in the history
The Rate Limit filter in Envoy translates a 429 HTTP response code
to UNAVAILABLE as specified in the gRPC mapping document, but Google recommends
translating it to RESOURCE_EXHAUSTED
(see https://github.com/grpc/grpc/blob/master/doc/http-grpc-status-mapping.md)

This commit introduces a new setting to allow contour to forward the same parameter
introduced in envoyproxy/envoy#4879

The default value is disabled to retain the original behaviour of returning UNAVAILABLE,
as changing it would be a breaking change.

Signed-off-by: Víctor Roldán Betancort <vroldanbet@authzed.com>
  • Loading branch information
vroldanbet committed Jan 10, 2023
1 parent 02ff5b4 commit 0a3c88d
Show file tree
Hide file tree
Showing 11 changed files with 127 additions and 61 deletions.
6 changes: 6 additions & 0 deletions apis/projectcontour/v1alpha1/contourconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
13 changes: 7 additions & 6 deletions cmd/contour/serve.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down
7 changes: 4 additions & 3 deletions cmd/contour/servecontext.go
Original file line number Diff line number Diff line change
Expand Up @@ -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),
}
}

Expand Down
3 changes: 3 additions & 0 deletions examples/contour/01-contour-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
3 changes: 3 additions & 0 deletions examples/render/contour.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
16 changes: 9 additions & 7 deletions internal/envoy/v3/ratelimit.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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,
}),
},
}
Expand Down
31 changes: 31 additions & 0 deletions internal/envoy/v3/ratelimit_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
26 changes: 14 additions & 12 deletions internal/xdscache/v3/listener.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
}
}

Expand Down
53 changes: 31 additions & 22 deletions internal/xdscache/v3/listener_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{}{
Expand Down Expand Up @@ -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()),
Expand All @@ -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{}{
Expand Down Expand Up @@ -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,
}),
},
}).
Expand Down Expand Up @@ -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,
}),
},
}).
Expand All @@ -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{}{
Expand Down Expand Up @@ -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,
}),
},
}).
Expand Down Expand Up @@ -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,
}),
},
}).
Expand Down Expand Up @@ -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,
}),
},
}).
Expand Down
4 changes: 4 additions & 0 deletions pkg/config/parameters.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
26 changes: 15 additions & 11 deletions site/content/docs/main/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 | <none> | This field identifies the extension service defining the rate limit service, formatted as <namespace>/<name>. |
| 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 | <none> | This field identifies the extension service defining the rate limit service, formatted as <namespace>/<name>. |
| 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

Expand Down Expand Up @@ -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:
Expand Down

0 comments on commit 0a3c88d

Please sign in to comment.