diff --git a/apis/projectcontour/v1/httpproxy.go b/apis/projectcontour/v1/httpproxy.go index ae087ea0e1f..c55e84f2adf 100644 --- a/apis/projectcontour/v1/httpproxy.go +++ b/apis/projectcontour/v1/httpproxy.go @@ -817,12 +817,17 @@ type LocalRateLimitPolicy struct { // GlobalRateLimitPolicy defines global rate limiting parameters. type GlobalRateLimitPolicy struct { + // Disabled configures the HTTPProxy to not use + // the default global rate limit policy defined by the Contour configuration. + // +optional + Disabled bool `json:"disabled,omitempty"` + // Descriptors defines the list of descriptors that will // be generated and sent to the rate limit service. Each // descriptor contains 1+ key-value pair entries. - // +required + // +optional // +kubebuilder:validation:MinItems=1 - Descriptors []RateLimitDescriptor `json:"descriptors,omitempty"` + Descriptors []RateLimitDescriptor `json:"descriptors,omitempty" yaml:"descriptors,omitempty"` } // RateLimitDescriptor defines a list of key-value pair generators. @@ -830,7 +835,7 @@ type RateLimitDescriptor struct { // Entries is the list of key-value pair generators. // +required // +kubebuilder:validation:MinItems=1 - Entries []RateLimitDescriptorEntry `json:"entries,omitempty"` + Entries []RateLimitDescriptorEntry `json:"entries,omitempty" yaml:"entries,omitempty"` } // RateLimitDescriptorEntry is a key-value pair generator. Exactly @@ -838,24 +843,24 @@ type RateLimitDescriptor struct { type RateLimitDescriptorEntry struct { // GenericKey defines a descriptor entry with a static key and value. // +optional - GenericKey *GenericKeyDescriptor `json:"genericKey,omitempty"` + GenericKey *GenericKeyDescriptor `json:"genericKey,omitempty" yaml:"genericKey,omitempty"` // RequestHeader defines a descriptor entry that's populated only if // a given header is present on the request. The descriptor key is static, // and the descriptor value is equal to the value of the header. // +optional - RequestHeader *RequestHeaderDescriptor `json:"requestHeader,omitempty"` + RequestHeader *RequestHeaderDescriptor `json:"requestHeader,omitempty" yaml:"requestHeader,omitempty"` // RequestHeaderValueMatch defines a descriptor entry that's populated // if the request's headers match a set of 1+ match criteria. The // descriptor key is "header_match", and the descriptor value is static. // +optional - RequestHeaderValueMatch *RequestHeaderValueMatchDescriptor `json:"requestHeaderValueMatch,omitempty"` + RequestHeaderValueMatch *RequestHeaderValueMatchDescriptor `json:"requestHeaderValueMatch,omitempty" yaml:"requestHeaderValueMatch,omitempty"` // RemoteAddress defines a descriptor entry with a key of "remote_address" // and a value equal to the client's IP address (from x-forwarded-for). // +optional - RemoteAddress *RemoteAddressDescriptor `json:"remoteAddress,omitempty"` + RemoteAddress *RemoteAddressDescriptor `json:"remoteAddress,omitempty" yaml:"remoteAddress,omitempty"` } // GenericKeyDescriptor defines a descriptor entry with a static key and @@ -864,12 +869,12 @@ type GenericKeyDescriptor struct { // Key defines the key of the descriptor entry. If not set, the // key is set to "generic_key". // +optional - Key string `json:"key,omitempty"` + Key string `json:"key,omitempty" yaml:"key,omitempty"` // Value defines the value of the descriptor entry. // +required // +kubebuilder:validation:MinLength=1 - Value string `json:"value,omitempty"` + Value string `json:"value,omitempty" yaml:"value,omitempty"` } // RequestHeaderDescriptor defines a descriptor entry that's populated only @@ -879,12 +884,12 @@ type RequestHeaderDescriptor struct { // HeaderName defines the name of the header to look for on the request. // +required // +kubebuilder:validation:MinLength=1 - HeaderName string `json:"headerName,omitempty"` + HeaderName string `json:"headerName,omitempty" yaml:"headerName,omitempty"` // DescriptorKey defines the key to use on the descriptor entry. // +required // +kubebuilder:validation:MinLength=1 - DescriptorKey string `json:"descriptorKey,omitempty"` + DescriptorKey string `json:"descriptorKey,omitempty" yaml:"descriptorKey,omitempty"` } // RequestHeaderValueMatchDescriptor defines a descriptor entry that's populated @@ -894,19 +899,19 @@ type RequestHeaderValueMatchDescriptor struct { // Headers is a list of 1+ match criteria to apply against the request // to determine whether to populate the descriptor entry or not. // +kubebuilder:validation:MinItems=1 - Headers []HeaderMatchCondition `json:"headers,omitempty"` + Headers []HeaderMatchCondition `json:"headers,omitempty" yaml:"headers,omitempty"` // ExpectMatch defines whether the request must positively match the match // criteria in order to generate a descriptor entry (i.e. true), or not // match the match criteria in order to generate a descriptor entry (i.e. false). // The default is true. // +kubebuilder:default=true - ExpectMatch bool `json:"expectMatch,omitempty"` + ExpectMatch bool `json:"expectMatch,omitempty" yaml:"expectMatch,omitempty"` // Value defines the value of the descriptor entry. // +required // +kubebuilder:validation:MinLength=1 - Value string `json:"value,omitempty"` + Value string `json:"value,omitempty" yaml:"value,omitempty"` } // RemoteAddressDescriptor defines a descriptor entry with a key of diff --git a/apis/projectcontour/v1alpha1/contourconfig.go b/apis/projectcontour/v1alpha1/contourconfig.go index 5e17a90d7ae..16c730d6970 100644 --- a/apis/projectcontour/v1alpha1/contourconfig.go +++ b/apis/projectcontour/v1alpha1/contourconfig.go @@ -685,6 +685,12 @@ type RateLimitServiceConfig struct { // // +optional EnableResourceExhaustedCode *bool `json:"enableResourceExhaustedCode,omitempty"` + + // DefaultGlobalRateLimitPolicy allows setting a default global rate limit policy for every HTTPProxy. + // HTTPProxy can overwrite this configuration. + // + // +optional + DefaultGlobalRateLimitPolicy *contour_api_v1.GlobalRateLimitPolicy `json:"defaultGlobalRateLimitPolicy,omitempty"` } // TracingConfig defines properties for exporting trace data to OpenTelemetry. diff --git a/apis/projectcontour/v1alpha1/zz_generated.deepcopy.go b/apis/projectcontour/v1alpha1/zz_generated.deepcopy.go index e159fd9b96e..c5ab8ea8880 100644 --- a/apis/projectcontour/v1alpha1/zz_generated.deepcopy.go +++ b/apis/projectcontour/v1alpha1/zz_generated.deepcopy.go @@ -1105,6 +1105,11 @@ func (in *RateLimitServiceConfig) DeepCopyInto(out *RateLimitServiceConfig) { *out = new(bool) **out = **in } + if in.DefaultGlobalRateLimitPolicy != nil { + in, out := &in.DefaultGlobalRateLimitPolicy, &out.DefaultGlobalRateLimitPolicy + *out = new(v1.GlobalRateLimitPolicy) + (*in).DeepCopyInto(*out) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RateLimitServiceConfig. diff --git a/changelogs/unreleased/5363-shadialtarsha-minor.md b/changelogs/unreleased/5363-shadialtarsha-minor.md new file mode 100644 index 00000000000..11819859d92 --- /dev/null +++ b/changelogs/unreleased/5363-shadialtarsha-minor.md @@ -0,0 +1,28 @@ +## Default Global RateLimit Policy + +This Change adds the ability to define a default global rate limit policy in the Contour configuration +to be used as a global rate limit policy by all HTTPProxy objects. +HTTPProxy object can decide to opt out and disable this feature using `disabled` config. + +### Sample Configurations +#### contour.yaml +```yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: contour + namespace: projectcontour +data: + contour.yaml: | + rateLimitService: + extensionService: projectcontour/ratelimit + domain: contour + failOpen: false + defaultGlobalRateLimitPolicy: + descriptors: + - entries: + - remoteAddress: {} + - entries: + - genericKey: + value: foo +``` diff --git a/cmd/contour/serve.go b/cmd/contour/serve.go index 41831018506..253ed055b98 100644 --- a/cmd/contour/serve.go +++ b/cmd/contour/serve.go @@ -516,6 +516,7 @@ func (s *Server) doServe() error { httpsAddress: contourConfiguration.Envoy.HTTPSListener.Address, httpsPort: contourConfiguration.Envoy.HTTPSListener.Port, globalExternalAuthorizationService: contourConfiguration.GlobalExternalAuthorization, + globalRateLimitService: contourConfiguration.RateLimitService, maxRequestsPerConnection: contourConfiguration.Envoy.Cluster.MaxRequestsPerConnection, perConnectionBufferLimitBytes: contourConfiguration.Envoy.Cluster.PerConnectionBufferLimitBytes, }) @@ -1050,6 +1051,7 @@ type dagBuilderConfig struct { globalExternalAuthorizationService *contour_api_v1.AuthorizationServer maxRequestsPerConnection *uint32 perConnectionBufferLimitBytes *uint32 + globalRateLimitService *contour_api_v1alpha1.RateLimitServiceConfig } func (s *Server) getDAGBuilder(dbc dagBuilderConfig) *dag.Builder { @@ -1138,6 +1140,7 @@ func (s *Server) getDAGBuilder(dbc dagBuilderConfig) *dag.Builder { ConnectTimeout: dbc.connectTimeout, GlobalExternalAuthorization: dbc.globalExternalAuthorizationService, MaxRequestsPerConnection: dbc.maxRequestsPerConnection, + GlobalRateLimitService: dbc.globalRateLimitService, PerConnectionBufferLimitBytes: dbc.perConnectionBufferLimitBytes, }, } diff --git a/cmd/contour/servecontext.go b/cmd/contour/servecontext.go index 9789f9dd5b1..057fd5e9102 100644 --- a/cmd/contour/servecontext.go +++ b/cmd/contour/servecontext.go @@ -414,10 +414,11 @@ 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), - EnableResourceExhaustedCode: ref.To(ctx.Config.RateLimitService.EnableResourceExhaustedCode), + 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), + DefaultGlobalRateLimitPolicy: ctx.Config.RateLimitService.DefaultGlobalRateLimitPolicy, } } diff --git a/cmd/contour/servecontext_test.go b/cmd/contour/servecontext_test.go index 034f345a622..806692feaad 100644 --- a/cmd/contour/servecontext_test.go +++ b/cmd/contour/servecontext_test.go @@ -629,6 +629,20 @@ func TestConvertServeContext(t *testing.T) { FailOpen: true, EnableXRateLimitHeaders: true, EnableResourceExhaustedCode: true, + DefaultGlobalRateLimitPolicy: &contour_api_v1.GlobalRateLimitPolicy{ + Descriptors: []contour_api_v1.RateLimitDescriptor{ + { + Entries: []contour_api_v1.RateLimitDescriptorEntry{ + { + GenericKey: &contour_api_v1.GenericKeyDescriptor{ + Key: "foo", + Value: "bar", + }, + }, + }, + }, + }, + }, } return ctx }, @@ -642,6 +656,20 @@ func TestConvertServeContext(t *testing.T) { FailOpen: ref.To(true), EnableXRateLimitHeaders: ref.To(true), EnableResourceExhaustedCode: ref.To(true), + DefaultGlobalRateLimitPolicy: &contour_api_v1.GlobalRateLimitPolicy{ + Descriptors: []contour_api_v1.RateLimitDescriptor{ + { + Entries: []contour_api_v1.RateLimitDescriptorEntry{ + { + GenericKey: &contour_api_v1.GenericKeyDescriptor{ + Key: "foo", + Value: "bar", + }, + }, + }, + }, + }, + }, } return cfg }, diff --git a/design/default-global-rate-limit-policy.md b/design/default-global-rate-limit-policy.md index f8424d4eb5e..d5daa79fead 100644 --- a/design/default-global-rate-limit-policy.md +++ b/design/default-global-rate-limit-policy.md @@ -1,6 +1,6 @@ # Default Global RateLimit Policy -Status: Reviewing +Status: Accepted ## Abstract Define a default global rate limit policy in the Contour configuration to be used as a global rate limit policy by all HTTPProxy objects. @@ -20,7 +20,7 @@ Currently, the global `rateLimitPolicy` for `VirtualHost` is set per HTTPProxy. A new field, `DefaultGlobalRateLimitPolicy` (optional), will be added to `RateLimitServiceConfig` which is part of `ContourConfigurationSpec`. This field will define rate limit descriptors that will be added to every `VirtualHost`. -HTTPProxy has to opt-out **explicitly** to not use the default global rate limit policy using `rateLimitPolicy.GlobalRateLimitPolicy.defaultGlobalRateLimitPolicyDisabled` flag and still can have its own global `rateLimitPolicy` which overrides the default one. +HTTPProxy has to opt-out **explicitly** to not use the default global rate limit policy using `rateLimitPolicy.GlobalRateLimitPolicy.disabled` flag and still can have its own global `rateLimitPolicy` which overrides the default one. ### Sample Configurations #### contour.yaml @@ -110,7 +110,7 @@ spec: fqdn: local.projectcontour.io rateLimitPolicy: global: - defaultGlobalRateLimitPolicyDisabled: true + disabled: true local: requests: 100 unit: hour @@ -139,15 +139,15 @@ type RateLimitService struct { ``` ### HTTPProxy Configuration Changes -A new field `DefaultGlobalRateLimitPolicyDisabled`, will be added to HTTPProxy `RateLimitPolicy.GlobalRateLimitPolicy`. `GlobalRateLimitPolicy` is part of Contour API v1 definition for HTTPProxy's global `rateLimitPolicy` +A new field `Disabled`, will be added to HTTPProxy `RateLimitPolicy.GlobalRateLimitPolicy`. `GlobalRateLimitPolicy` is part of Contour API v1 definition for HTTPProxy's global `rateLimitPolicy` ```go // GlobalRateLimitPolicy defines global rate limiting parameters. type GlobalRateLimitPolicy struct { - // DefaultGlobalRateLimitPolicyDisabled configures the HTTPProxy to not use + // Disabled configures the HTTPProxy to not use // the default global rate limit policy defined by the Contour configuration // as a global rate limit policy for its defined virtual hosts entries. // +optional - DefaultGlobalRateLimitPolicyDisabled bool `json:"defaultGlobalRateLimitPolicyDisabled,omitempty"` + Disabled bool `json:"disabled,omitempty"` // Descriptors defines the list of descriptors that will // be generated and sent to the rate limit service. Each diff --git a/examples/contour/01-crds.yaml b/examples/contour/01-crds.yaml index 486f99704b6..11a5f274482 100644 --- a/examples/contour/01-crds.yaml +++ b/examples/contour/01-crds.yaml @@ -634,6 +634,161 @@ spec: description: RateLimitService optionally holds properties of the Rate Limit Service to be used for global rate limiting. properties: + defaultGlobalRateLimitPolicy: + description: DefaultGlobalRateLimitPolicy allows setting a default + global rate limit policy for every HTTPProxy. HTTPProxy can + overwrite this configuration. + properties: + descriptors: + description: Descriptors defines the list of descriptors that + will be generated and sent to the rate limit service. Each + descriptor contains 1+ key-value pair entries. + items: + description: RateLimitDescriptor defines a list of key-value + pair generators. + properties: + entries: + description: Entries is the list of key-value pair generators. + items: + description: RateLimitDescriptorEntry is a key-value + pair generator. Exactly one field on this struct + must be non-nil. + properties: + genericKey: + description: GenericKey defines a descriptor entry + with a static key and value. + properties: + key: + description: Key defines the key of the descriptor + entry. If not set, the key is set to "generic_key". + type: string + value: + description: Value defines the value of the + descriptor entry. + minLength: 1 + type: string + type: object + remoteAddress: + description: RemoteAddress defines a descriptor + entry with a key of "remote_address" and a value + equal to the client's IP address (from x-forwarded-for). + type: object + requestHeader: + description: RequestHeader defines a descriptor + entry that's populated only if a given header + is present on the request. The descriptor key + is static, and the descriptor value is equal + to the value of the header. + properties: + descriptorKey: + description: DescriptorKey defines the key + to use on the descriptor entry. + minLength: 1 + type: string + headerName: + description: HeaderName defines the name of + the header to look for on the request. + minLength: 1 + type: string + type: object + requestHeaderValueMatch: + description: RequestHeaderValueMatch defines a + descriptor entry that's populated if the request's + headers match a set of 1+ match criteria. The + descriptor key is "header_match", and the descriptor + value is static. + properties: + expectMatch: + default: true + description: ExpectMatch defines whether the + request must positively match the match + criteria in order to generate a descriptor + entry (i.e. true), or not match the match + criteria in order to generate a descriptor + entry (i.e. false). The default is true. + type: boolean + headers: + description: Headers is a list of 1+ match + criteria to apply against the request to + determine whether to populate the descriptor + entry or not. + items: + description: HeaderMatchCondition specifies + how to conditionally match against HTTP + headers. The Name field is required, but + only one of the remaining fields should + be be provided. + properties: + contains: + description: Contains specifies a substring + that must be present in the header + value. + type: string + exact: + description: Exact specifies a string + that the header value must be equal + to. + type: string + name: + description: Name is the name of the + header to match against. Name is required. + Header names are case insensitive. + type: string + notcontains: + description: NotContains specifies a + substring that must not be present + in the header value. + type: string + notexact: + description: NoExact specifies a string + that the header value must not be + equal to. The condition is true if + the header has any other value. + type: string + notpresent: + description: NotPresent specifies that + condition is true when the named header + is not present. Note that setting + NotPresent to false does not make + the condition true if the named header + is present. + type: boolean + present: + description: Present specifies that + condition is true when the named header + is present, regardless of its value. + Note that setting Present to false + does not make the condition true if + the named header is absent. + type: boolean + regex: + description: Regex specifies a regular + expression pattern that must match + the header value. + type: string + required: + - name + type: object + minItems: 1 + type: array + value: + description: Value defines the value of the + descriptor entry. + minLength: 1 + type: string + type: object + type: object + minItems: 1 + type: array + type: object + minItems: 1 + type: array + disabled: + description: Disabled configures the HTTPProxy to not use + the default global rate limit policy defined by the Contour + configuration. + type: boolean + type: object domain: description: Domain is passed to the Rate Limit Service. type: string @@ -3852,6 +4007,168 @@ spec: description: RateLimitService optionally holds properties of the Rate Limit Service to be used for global rate limiting. properties: + defaultGlobalRateLimitPolicy: + description: DefaultGlobalRateLimitPolicy allows setting a + default global rate limit policy for every HTTPProxy. HTTPProxy + can overwrite this configuration. + properties: + descriptors: + description: Descriptors defines the list of descriptors + that will be generated and sent to the rate limit service. + Each descriptor contains 1+ key-value pair entries. + items: + description: RateLimitDescriptor defines a list of key-value + pair generators. + properties: + entries: + description: Entries is the list of key-value pair + generators. + items: + description: RateLimitDescriptorEntry is a key-value + pair generator. Exactly one field on this struct + must be non-nil. + properties: + genericKey: + description: GenericKey defines a descriptor + entry with a static key and value. + properties: + key: + description: Key defines the key of the + descriptor entry. If not set, the key + is set to "generic_key". + type: string + value: + description: Value defines the value of + the descriptor entry. + minLength: 1 + type: string + type: object + remoteAddress: + description: RemoteAddress defines a descriptor + entry with a key of "remote_address" and + a value equal to the client's IP address + (from x-forwarded-for). + type: object + requestHeader: + description: RequestHeader defines a descriptor + entry that's populated only if a given header + is present on the request. The descriptor + key is static, and the descriptor value + is equal to the value of the header. + properties: + descriptorKey: + description: DescriptorKey defines the + key to use on the descriptor entry. + minLength: 1 + type: string + headerName: + description: HeaderName defines the name + of the header to look for on the request. + minLength: 1 + type: string + type: object + requestHeaderValueMatch: + description: RequestHeaderValueMatch defines + a descriptor entry that's populated if the + request's headers match a set of 1+ match + criteria. The descriptor key is "header_match", + and the descriptor value is static. + properties: + expectMatch: + default: true + description: ExpectMatch defines whether + the request must positively match the + match criteria in order to generate + a descriptor entry (i.e. true), or not + match the match criteria in order to + generate a descriptor entry (i.e. false). + The default is true. + type: boolean + headers: + description: Headers is a list of 1+ match + criteria to apply against the request + to determine whether to populate the + descriptor entry or not. + items: + description: HeaderMatchCondition specifies + how to conditionally match against + HTTP headers. The Name field is required, + but only one of the remaining fields + should be be provided. + properties: + contains: + description: Contains specifies + a substring that must be present + in the header value. + type: string + exact: + description: Exact specifies a string + that the header value must be + equal to. + type: string + name: + description: Name is the name of + the header to match against. Name + is required. Header names are + case insensitive. + type: string + notcontains: + description: NotContains specifies + a substring that must not be present + in the header value. + type: string + notexact: + description: NoExact specifies a + string that the header value must + not be equal to. The condition + is true if the header has any + other value. + type: string + notpresent: + description: NotPresent specifies + that condition is true when the + named header is not present. Note + that setting NotPresent to false + does not make the condition true + if the named header is present. + type: boolean + present: + description: Present specifies that + condition is true when the named + header is present, regardless + of its value. Note that setting + Present to false does not make + the condition true if the named + header is absent. + type: boolean + regex: + description: Regex specifies a regular + expression pattern that must match + the header value. + type: string + required: + - name + type: object + minItems: 1 + type: array + value: + description: Value defines the value of + the descriptor entry. + minLength: 1 + type: string + type: object + type: object + minItems: 1 + type: array + type: object + minItems: 1 + type: array + disabled: + description: Disabled configures the HTTPProxy to not + use the default global rate limit policy defined by + the Contour configuration. + type: boolean + type: object domain: description: Domain is passed to the Rate Limit Service. type: string @@ -5378,6 +5695,11 @@ spec: type: object minItems: 1 type: array + disabled: + description: Disabled configures the HTTPProxy to not + use the default global rate limit policy defined by + the Contour configuration. + type: boolean type: object local: description: Local defines local rate limiting parameters, @@ -6731,6 +7053,11 @@ spec: type: object minItems: 1 type: array + disabled: + description: Disabled configures the HTTPProxy to not + use the default global rate limit policy defined by + the Contour configuration. + type: boolean type: object local: description: Local defines local rate limiting parameters, diff --git a/examples/ratelimit/04-default-global-ratelimit-contour-config.yaml b/examples/ratelimit/04-default-global-ratelimit-contour-config.yaml new file mode 100644 index 00000000000..030fd3132d8 --- /dev/null +++ b/examples/ratelimit/04-default-global-ratelimit-contour-config.yaml @@ -0,0 +1,17 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: contour + namespace: projectcontour +data: + contour.yaml: | + rateLimitService: + extensionService: projectcontour/ratelimit + domain: contour + failOpen: false + defaultGlobalRateLimitPolicy: + descriptors: + - entries: + - requestHeader: + headerName: X-Custom-Header + descriptorKey: CustomHeader diff --git a/examples/render/contour-deployment.yaml b/examples/render/contour-deployment.yaml index 745a9156f37..347ff0aa8d9 100644 --- a/examples/render/contour-deployment.yaml +++ b/examples/render/contour-deployment.yaml @@ -847,6 +847,161 @@ spec: description: RateLimitService optionally holds properties of the Rate Limit Service to be used for global rate limiting. properties: + defaultGlobalRateLimitPolicy: + description: DefaultGlobalRateLimitPolicy allows setting a default + global rate limit policy for every HTTPProxy. HTTPProxy can + overwrite this configuration. + properties: + descriptors: + description: Descriptors defines the list of descriptors that + will be generated and sent to the rate limit service. Each + descriptor contains 1+ key-value pair entries. + items: + description: RateLimitDescriptor defines a list of key-value + pair generators. + properties: + entries: + description: Entries is the list of key-value pair generators. + items: + description: RateLimitDescriptorEntry is a key-value + pair generator. Exactly one field on this struct + must be non-nil. + properties: + genericKey: + description: GenericKey defines a descriptor entry + with a static key and value. + properties: + key: + description: Key defines the key of the descriptor + entry. If not set, the key is set to "generic_key". + type: string + value: + description: Value defines the value of the + descriptor entry. + minLength: 1 + type: string + type: object + remoteAddress: + description: RemoteAddress defines a descriptor + entry with a key of "remote_address" and a value + equal to the client's IP address (from x-forwarded-for). + type: object + requestHeader: + description: RequestHeader defines a descriptor + entry that's populated only if a given header + is present on the request. The descriptor key + is static, and the descriptor value is equal + to the value of the header. + properties: + descriptorKey: + description: DescriptorKey defines the key + to use on the descriptor entry. + minLength: 1 + type: string + headerName: + description: HeaderName defines the name of + the header to look for on the request. + minLength: 1 + type: string + type: object + requestHeaderValueMatch: + description: RequestHeaderValueMatch defines a + descriptor entry that's populated if the request's + headers match a set of 1+ match criteria. The + descriptor key is "header_match", and the descriptor + value is static. + properties: + expectMatch: + default: true + description: ExpectMatch defines whether the + request must positively match the match + criteria in order to generate a descriptor + entry (i.e. true), or not match the match + criteria in order to generate a descriptor + entry (i.e. false). The default is true. + type: boolean + headers: + description: Headers is a list of 1+ match + criteria to apply against the request to + determine whether to populate the descriptor + entry or not. + items: + description: HeaderMatchCondition specifies + how to conditionally match against HTTP + headers. The Name field is required, but + only one of the remaining fields should + be be provided. + properties: + contains: + description: Contains specifies a substring + that must be present in the header + value. + type: string + exact: + description: Exact specifies a string + that the header value must be equal + to. + type: string + name: + description: Name is the name of the + header to match against. Name is required. + Header names are case insensitive. + type: string + notcontains: + description: NotContains specifies a + substring that must not be present + in the header value. + type: string + notexact: + description: NoExact specifies a string + that the header value must not be + equal to. The condition is true if + the header has any other value. + type: string + notpresent: + description: NotPresent specifies that + condition is true when the named header + is not present. Note that setting + NotPresent to false does not make + the condition true if the named header + is present. + type: boolean + present: + description: Present specifies that + condition is true when the named header + is present, regardless of its value. + Note that setting Present to false + does not make the condition true if + the named header is absent. + type: boolean + regex: + description: Regex specifies a regular + expression pattern that must match + the header value. + type: string + required: + - name + type: object + minItems: 1 + type: array + value: + description: Value defines the value of the + descriptor entry. + minLength: 1 + type: string + type: object + type: object + minItems: 1 + type: array + type: object + minItems: 1 + type: array + disabled: + description: Disabled configures the HTTPProxy to not use + the default global rate limit policy defined by the Contour + configuration. + type: boolean + type: object domain: description: Domain is passed to the Rate Limit Service. type: string @@ -4065,6 +4220,168 @@ spec: description: RateLimitService optionally holds properties of the Rate Limit Service to be used for global rate limiting. properties: + defaultGlobalRateLimitPolicy: + description: DefaultGlobalRateLimitPolicy allows setting a + default global rate limit policy for every HTTPProxy. HTTPProxy + can overwrite this configuration. + properties: + descriptors: + description: Descriptors defines the list of descriptors + that will be generated and sent to the rate limit service. + Each descriptor contains 1+ key-value pair entries. + items: + description: RateLimitDescriptor defines a list of key-value + pair generators. + properties: + entries: + description: Entries is the list of key-value pair + generators. + items: + description: RateLimitDescriptorEntry is a key-value + pair generator. Exactly one field on this struct + must be non-nil. + properties: + genericKey: + description: GenericKey defines a descriptor + entry with a static key and value. + properties: + key: + description: Key defines the key of the + descriptor entry. If not set, the key + is set to "generic_key". + type: string + value: + description: Value defines the value of + the descriptor entry. + minLength: 1 + type: string + type: object + remoteAddress: + description: RemoteAddress defines a descriptor + entry with a key of "remote_address" and + a value equal to the client's IP address + (from x-forwarded-for). + type: object + requestHeader: + description: RequestHeader defines a descriptor + entry that's populated only if a given header + is present on the request. The descriptor + key is static, and the descriptor value + is equal to the value of the header. + properties: + descriptorKey: + description: DescriptorKey defines the + key to use on the descriptor entry. + minLength: 1 + type: string + headerName: + description: HeaderName defines the name + of the header to look for on the request. + minLength: 1 + type: string + type: object + requestHeaderValueMatch: + description: RequestHeaderValueMatch defines + a descriptor entry that's populated if the + request's headers match a set of 1+ match + criteria. The descriptor key is "header_match", + and the descriptor value is static. + properties: + expectMatch: + default: true + description: ExpectMatch defines whether + the request must positively match the + match criteria in order to generate + a descriptor entry (i.e. true), or not + match the match criteria in order to + generate a descriptor entry (i.e. false). + The default is true. + type: boolean + headers: + description: Headers is a list of 1+ match + criteria to apply against the request + to determine whether to populate the + descriptor entry or not. + items: + description: HeaderMatchCondition specifies + how to conditionally match against + HTTP headers. The Name field is required, + but only one of the remaining fields + should be be provided. + properties: + contains: + description: Contains specifies + a substring that must be present + in the header value. + type: string + exact: + description: Exact specifies a string + that the header value must be + equal to. + type: string + name: + description: Name is the name of + the header to match against. Name + is required. Header names are + case insensitive. + type: string + notcontains: + description: NotContains specifies + a substring that must not be present + in the header value. + type: string + notexact: + description: NoExact specifies a + string that the header value must + not be equal to. The condition + is true if the header has any + other value. + type: string + notpresent: + description: NotPresent specifies + that condition is true when the + named header is not present. Note + that setting NotPresent to false + does not make the condition true + if the named header is present. + type: boolean + present: + description: Present specifies that + condition is true when the named + header is present, regardless + of its value. Note that setting + Present to false does not make + the condition true if the named + header is absent. + type: boolean + regex: + description: Regex specifies a regular + expression pattern that must match + the header value. + type: string + required: + - name + type: object + minItems: 1 + type: array + value: + description: Value defines the value of + the descriptor entry. + minLength: 1 + type: string + type: object + type: object + minItems: 1 + type: array + type: object + minItems: 1 + type: array + disabled: + description: Disabled configures the HTTPProxy to not + use the default global rate limit policy defined by + the Contour configuration. + type: boolean + type: object domain: description: Domain is passed to the Rate Limit Service. type: string @@ -5591,6 +5908,11 @@ spec: type: object minItems: 1 type: array + disabled: + description: Disabled configures the HTTPProxy to not + use the default global rate limit policy defined by + the Contour configuration. + type: boolean type: object local: description: Local defines local rate limiting parameters, @@ -6944,6 +7266,11 @@ spec: type: object minItems: 1 type: array + disabled: + description: Disabled configures the HTTPProxy to not + use the default global rate limit policy defined by + the Contour configuration. + type: boolean type: object local: description: Local defines local rate limiting parameters, diff --git a/examples/render/contour-gateway-provisioner.yaml b/examples/render/contour-gateway-provisioner.yaml index 7a97d6743fe..dedbcae3591 100644 --- a/examples/render/contour-gateway-provisioner.yaml +++ b/examples/render/contour-gateway-provisioner.yaml @@ -648,6 +648,161 @@ spec: description: RateLimitService optionally holds properties of the Rate Limit Service to be used for global rate limiting. properties: + defaultGlobalRateLimitPolicy: + description: DefaultGlobalRateLimitPolicy allows setting a default + global rate limit policy for every HTTPProxy. HTTPProxy can + overwrite this configuration. + properties: + descriptors: + description: Descriptors defines the list of descriptors that + will be generated and sent to the rate limit service. Each + descriptor contains 1+ key-value pair entries. + items: + description: RateLimitDescriptor defines a list of key-value + pair generators. + properties: + entries: + description: Entries is the list of key-value pair generators. + items: + description: RateLimitDescriptorEntry is a key-value + pair generator. Exactly one field on this struct + must be non-nil. + properties: + genericKey: + description: GenericKey defines a descriptor entry + with a static key and value. + properties: + key: + description: Key defines the key of the descriptor + entry. If not set, the key is set to "generic_key". + type: string + value: + description: Value defines the value of the + descriptor entry. + minLength: 1 + type: string + type: object + remoteAddress: + description: RemoteAddress defines a descriptor + entry with a key of "remote_address" and a value + equal to the client's IP address (from x-forwarded-for). + type: object + requestHeader: + description: RequestHeader defines a descriptor + entry that's populated only if a given header + is present on the request. The descriptor key + is static, and the descriptor value is equal + to the value of the header. + properties: + descriptorKey: + description: DescriptorKey defines the key + to use on the descriptor entry. + minLength: 1 + type: string + headerName: + description: HeaderName defines the name of + the header to look for on the request. + minLength: 1 + type: string + type: object + requestHeaderValueMatch: + description: RequestHeaderValueMatch defines a + descriptor entry that's populated if the request's + headers match a set of 1+ match criteria. The + descriptor key is "header_match", and the descriptor + value is static. + properties: + expectMatch: + default: true + description: ExpectMatch defines whether the + request must positively match the match + criteria in order to generate a descriptor + entry (i.e. true), or not match the match + criteria in order to generate a descriptor + entry (i.e. false). The default is true. + type: boolean + headers: + description: Headers is a list of 1+ match + criteria to apply against the request to + determine whether to populate the descriptor + entry or not. + items: + description: HeaderMatchCondition specifies + how to conditionally match against HTTP + headers. The Name field is required, but + only one of the remaining fields should + be be provided. + properties: + contains: + description: Contains specifies a substring + that must be present in the header + value. + type: string + exact: + description: Exact specifies a string + that the header value must be equal + to. + type: string + name: + description: Name is the name of the + header to match against. Name is required. + Header names are case insensitive. + type: string + notcontains: + description: NotContains specifies a + substring that must not be present + in the header value. + type: string + notexact: + description: NoExact specifies a string + that the header value must not be + equal to. The condition is true if + the header has any other value. + type: string + notpresent: + description: NotPresent specifies that + condition is true when the named header + is not present. Note that setting + NotPresent to false does not make + the condition true if the named header + is present. + type: boolean + present: + description: Present specifies that + condition is true when the named header + is present, regardless of its value. + Note that setting Present to false + does not make the condition true if + the named header is absent. + type: boolean + regex: + description: Regex specifies a regular + expression pattern that must match + the header value. + type: string + required: + - name + type: object + minItems: 1 + type: array + value: + description: Value defines the value of the + descriptor entry. + minLength: 1 + type: string + type: object + type: object + minItems: 1 + type: array + type: object + minItems: 1 + type: array + disabled: + description: Disabled configures the HTTPProxy to not use + the default global rate limit policy defined by the Contour + configuration. + type: boolean + type: object domain: description: Domain is passed to the Rate Limit Service. type: string @@ -3866,6 +4021,168 @@ spec: description: RateLimitService optionally holds properties of the Rate Limit Service to be used for global rate limiting. properties: + defaultGlobalRateLimitPolicy: + description: DefaultGlobalRateLimitPolicy allows setting a + default global rate limit policy for every HTTPProxy. HTTPProxy + can overwrite this configuration. + properties: + descriptors: + description: Descriptors defines the list of descriptors + that will be generated and sent to the rate limit service. + Each descriptor contains 1+ key-value pair entries. + items: + description: RateLimitDescriptor defines a list of key-value + pair generators. + properties: + entries: + description: Entries is the list of key-value pair + generators. + items: + description: RateLimitDescriptorEntry is a key-value + pair generator. Exactly one field on this struct + must be non-nil. + properties: + genericKey: + description: GenericKey defines a descriptor + entry with a static key and value. + properties: + key: + description: Key defines the key of the + descriptor entry. If not set, the key + is set to "generic_key". + type: string + value: + description: Value defines the value of + the descriptor entry. + minLength: 1 + type: string + type: object + remoteAddress: + description: RemoteAddress defines a descriptor + entry with a key of "remote_address" and + a value equal to the client's IP address + (from x-forwarded-for). + type: object + requestHeader: + description: RequestHeader defines a descriptor + entry that's populated only if a given header + is present on the request. The descriptor + key is static, and the descriptor value + is equal to the value of the header. + properties: + descriptorKey: + description: DescriptorKey defines the + key to use on the descriptor entry. + minLength: 1 + type: string + headerName: + description: HeaderName defines the name + of the header to look for on the request. + minLength: 1 + type: string + type: object + requestHeaderValueMatch: + description: RequestHeaderValueMatch defines + a descriptor entry that's populated if the + request's headers match a set of 1+ match + criteria. The descriptor key is "header_match", + and the descriptor value is static. + properties: + expectMatch: + default: true + description: ExpectMatch defines whether + the request must positively match the + match criteria in order to generate + a descriptor entry (i.e. true), or not + match the match criteria in order to + generate a descriptor entry (i.e. false). + The default is true. + type: boolean + headers: + description: Headers is a list of 1+ match + criteria to apply against the request + to determine whether to populate the + descriptor entry or not. + items: + description: HeaderMatchCondition specifies + how to conditionally match against + HTTP headers. The Name field is required, + but only one of the remaining fields + should be be provided. + properties: + contains: + description: Contains specifies + a substring that must be present + in the header value. + type: string + exact: + description: Exact specifies a string + that the header value must be + equal to. + type: string + name: + description: Name is the name of + the header to match against. Name + is required. Header names are + case insensitive. + type: string + notcontains: + description: NotContains specifies + a substring that must not be present + in the header value. + type: string + notexact: + description: NoExact specifies a + string that the header value must + not be equal to. The condition + is true if the header has any + other value. + type: string + notpresent: + description: NotPresent specifies + that condition is true when the + named header is not present. Note + that setting NotPresent to false + does not make the condition true + if the named header is present. + type: boolean + present: + description: Present specifies that + condition is true when the named + header is present, regardless + of its value. Note that setting + Present to false does not make + the condition true if the named + header is absent. + type: boolean + regex: + description: Regex specifies a regular + expression pattern that must match + the header value. + type: string + required: + - name + type: object + minItems: 1 + type: array + value: + description: Value defines the value of + the descriptor entry. + minLength: 1 + type: string + type: object + type: object + minItems: 1 + type: array + type: object + minItems: 1 + type: array + disabled: + description: Disabled configures the HTTPProxy to not + use the default global rate limit policy defined by + the Contour configuration. + type: boolean + type: object domain: description: Domain is passed to the Rate Limit Service. type: string @@ -5392,6 +5709,11 @@ spec: type: object minItems: 1 type: array + disabled: + description: Disabled configures the HTTPProxy to not + use the default global rate limit policy defined by + the Contour configuration. + type: boolean type: object local: description: Local defines local rate limiting parameters, @@ -6745,6 +7067,11 @@ spec: type: object minItems: 1 type: array + disabled: + description: Disabled configures the HTTPProxy to not + use the default global rate limit policy defined by + the Contour configuration. + type: boolean type: object local: description: Local defines local rate limiting parameters, diff --git a/examples/render/contour-gateway.yaml b/examples/render/contour-gateway.yaml index 2827d2738f0..d265b9a778d 100644 --- a/examples/render/contour-gateway.yaml +++ b/examples/render/contour-gateway.yaml @@ -853,6 +853,161 @@ spec: description: RateLimitService optionally holds properties of the Rate Limit Service to be used for global rate limiting. properties: + defaultGlobalRateLimitPolicy: + description: DefaultGlobalRateLimitPolicy allows setting a default + global rate limit policy for every HTTPProxy. HTTPProxy can + overwrite this configuration. + properties: + descriptors: + description: Descriptors defines the list of descriptors that + will be generated and sent to the rate limit service. Each + descriptor contains 1+ key-value pair entries. + items: + description: RateLimitDescriptor defines a list of key-value + pair generators. + properties: + entries: + description: Entries is the list of key-value pair generators. + items: + description: RateLimitDescriptorEntry is a key-value + pair generator. Exactly one field on this struct + must be non-nil. + properties: + genericKey: + description: GenericKey defines a descriptor entry + with a static key and value. + properties: + key: + description: Key defines the key of the descriptor + entry. If not set, the key is set to "generic_key". + type: string + value: + description: Value defines the value of the + descriptor entry. + minLength: 1 + type: string + type: object + remoteAddress: + description: RemoteAddress defines a descriptor + entry with a key of "remote_address" and a value + equal to the client's IP address (from x-forwarded-for). + type: object + requestHeader: + description: RequestHeader defines a descriptor + entry that's populated only if a given header + is present on the request. The descriptor key + is static, and the descriptor value is equal + to the value of the header. + properties: + descriptorKey: + description: DescriptorKey defines the key + to use on the descriptor entry. + minLength: 1 + type: string + headerName: + description: HeaderName defines the name of + the header to look for on the request. + minLength: 1 + type: string + type: object + requestHeaderValueMatch: + description: RequestHeaderValueMatch defines a + descriptor entry that's populated if the request's + headers match a set of 1+ match criteria. The + descriptor key is "header_match", and the descriptor + value is static. + properties: + expectMatch: + default: true + description: ExpectMatch defines whether the + request must positively match the match + criteria in order to generate a descriptor + entry (i.e. true), or not match the match + criteria in order to generate a descriptor + entry (i.e. false). The default is true. + type: boolean + headers: + description: Headers is a list of 1+ match + criteria to apply against the request to + determine whether to populate the descriptor + entry or not. + items: + description: HeaderMatchCondition specifies + how to conditionally match against HTTP + headers. The Name field is required, but + only one of the remaining fields should + be be provided. + properties: + contains: + description: Contains specifies a substring + that must be present in the header + value. + type: string + exact: + description: Exact specifies a string + that the header value must be equal + to. + type: string + name: + description: Name is the name of the + header to match against. Name is required. + Header names are case insensitive. + type: string + notcontains: + description: NotContains specifies a + substring that must not be present + in the header value. + type: string + notexact: + description: NoExact specifies a string + that the header value must not be + equal to. The condition is true if + the header has any other value. + type: string + notpresent: + description: NotPresent specifies that + condition is true when the named header + is not present. Note that setting + NotPresent to false does not make + the condition true if the named header + is present. + type: boolean + present: + description: Present specifies that + condition is true when the named header + is present, regardless of its value. + Note that setting Present to false + does not make the condition true if + the named header is absent. + type: boolean + regex: + description: Regex specifies a regular + expression pattern that must match + the header value. + type: string + required: + - name + type: object + minItems: 1 + type: array + value: + description: Value defines the value of the + descriptor entry. + minLength: 1 + type: string + type: object + type: object + minItems: 1 + type: array + type: object + minItems: 1 + type: array + disabled: + description: Disabled configures the HTTPProxy to not use + the default global rate limit policy defined by the Contour + configuration. + type: boolean + type: object domain: description: Domain is passed to the Rate Limit Service. type: string @@ -4071,6 +4226,168 @@ spec: description: RateLimitService optionally holds properties of the Rate Limit Service to be used for global rate limiting. properties: + defaultGlobalRateLimitPolicy: + description: DefaultGlobalRateLimitPolicy allows setting a + default global rate limit policy for every HTTPProxy. HTTPProxy + can overwrite this configuration. + properties: + descriptors: + description: Descriptors defines the list of descriptors + that will be generated and sent to the rate limit service. + Each descriptor contains 1+ key-value pair entries. + items: + description: RateLimitDescriptor defines a list of key-value + pair generators. + properties: + entries: + description: Entries is the list of key-value pair + generators. + items: + description: RateLimitDescriptorEntry is a key-value + pair generator. Exactly one field on this struct + must be non-nil. + properties: + genericKey: + description: GenericKey defines a descriptor + entry with a static key and value. + properties: + key: + description: Key defines the key of the + descriptor entry. If not set, the key + is set to "generic_key". + type: string + value: + description: Value defines the value of + the descriptor entry. + minLength: 1 + type: string + type: object + remoteAddress: + description: RemoteAddress defines a descriptor + entry with a key of "remote_address" and + a value equal to the client's IP address + (from x-forwarded-for). + type: object + requestHeader: + description: RequestHeader defines a descriptor + entry that's populated only if a given header + is present on the request. The descriptor + key is static, and the descriptor value + is equal to the value of the header. + properties: + descriptorKey: + description: DescriptorKey defines the + key to use on the descriptor entry. + minLength: 1 + type: string + headerName: + description: HeaderName defines the name + of the header to look for on the request. + minLength: 1 + type: string + type: object + requestHeaderValueMatch: + description: RequestHeaderValueMatch defines + a descriptor entry that's populated if the + request's headers match a set of 1+ match + criteria. The descriptor key is "header_match", + and the descriptor value is static. + properties: + expectMatch: + default: true + description: ExpectMatch defines whether + the request must positively match the + match criteria in order to generate + a descriptor entry (i.e. true), or not + match the match criteria in order to + generate a descriptor entry (i.e. false). + The default is true. + type: boolean + headers: + description: Headers is a list of 1+ match + criteria to apply against the request + to determine whether to populate the + descriptor entry or not. + items: + description: HeaderMatchCondition specifies + how to conditionally match against + HTTP headers. The Name field is required, + but only one of the remaining fields + should be be provided. + properties: + contains: + description: Contains specifies + a substring that must be present + in the header value. + type: string + exact: + description: Exact specifies a string + that the header value must be + equal to. + type: string + name: + description: Name is the name of + the header to match against. Name + is required. Header names are + case insensitive. + type: string + notcontains: + description: NotContains specifies + a substring that must not be present + in the header value. + type: string + notexact: + description: NoExact specifies a + string that the header value must + not be equal to. The condition + is true if the header has any + other value. + type: string + notpresent: + description: NotPresent specifies + that condition is true when the + named header is not present. Note + that setting NotPresent to false + does not make the condition true + if the named header is present. + type: boolean + present: + description: Present specifies that + condition is true when the named + header is present, regardless + of its value. Note that setting + Present to false does not make + the condition true if the named + header is absent. + type: boolean + regex: + description: Regex specifies a regular + expression pattern that must match + the header value. + type: string + required: + - name + type: object + minItems: 1 + type: array + value: + description: Value defines the value of + the descriptor entry. + minLength: 1 + type: string + type: object + type: object + minItems: 1 + type: array + type: object + minItems: 1 + type: array + disabled: + description: Disabled configures the HTTPProxy to not + use the default global rate limit policy defined by + the Contour configuration. + type: boolean + type: object domain: description: Domain is passed to the Rate Limit Service. type: string @@ -5597,6 +5914,11 @@ spec: type: object minItems: 1 type: array + disabled: + description: Disabled configures the HTTPProxy to not + use the default global rate limit policy defined by + the Contour configuration. + type: boolean type: object local: description: Local defines local rate limiting parameters, @@ -6950,6 +7272,11 @@ spec: type: object minItems: 1 type: array + disabled: + description: Disabled configures the HTTPProxy to not + use the default global rate limit policy defined by + the Contour configuration. + type: boolean type: object local: description: Local defines local rate limiting parameters, diff --git a/examples/render/contour.yaml b/examples/render/contour.yaml index cf5829792c1..d9dd04d11c8 100644 --- a/examples/render/contour.yaml +++ b/examples/render/contour.yaml @@ -847,6 +847,161 @@ spec: description: RateLimitService optionally holds properties of the Rate Limit Service to be used for global rate limiting. properties: + defaultGlobalRateLimitPolicy: + description: DefaultGlobalRateLimitPolicy allows setting a default + global rate limit policy for every HTTPProxy. HTTPProxy can + overwrite this configuration. + properties: + descriptors: + description: Descriptors defines the list of descriptors that + will be generated and sent to the rate limit service. Each + descriptor contains 1+ key-value pair entries. + items: + description: RateLimitDescriptor defines a list of key-value + pair generators. + properties: + entries: + description: Entries is the list of key-value pair generators. + items: + description: RateLimitDescriptorEntry is a key-value + pair generator. Exactly one field on this struct + must be non-nil. + properties: + genericKey: + description: GenericKey defines a descriptor entry + with a static key and value. + properties: + key: + description: Key defines the key of the descriptor + entry. If not set, the key is set to "generic_key". + type: string + value: + description: Value defines the value of the + descriptor entry. + minLength: 1 + type: string + type: object + remoteAddress: + description: RemoteAddress defines a descriptor + entry with a key of "remote_address" and a value + equal to the client's IP address (from x-forwarded-for). + type: object + requestHeader: + description: RequestHeader defines a descriptor + entry that's populated only if a given header + is present on the request. The descriptor key + is static, and the descriptor value is equal + to the value of the header. + properties: + descriptorKey: + description: DescriptorKey defines the key + to use on the descriptor entry. + minLength: 1 + type: string + headerName: + description: HeaderName defines the name of + the header to look for on the request. + minLength: 1 + type: string + type: object + requestHeaderValueMatch: + description: RequestHeaderValueMatch defines a + descriptor entry that's populated if the request's + headers match a set of 1+ match criteria. The + descriptor key is "header_match", and the descriptor + value is static. + properties: + expectMatch: + default: true + description: ExpectMatch defines whether the + request must positively match the match + criteria in order to generate a descriptor + entry (i.e. true), or not match the match + criteria in order to generate a descriptor + entry (i.e. false). The default is true. + type: boolean + headers: + description: Headers is a list of 1+ match + criteria to apply against the request to + determine whether to populate the descriptor + entry or not. + items: + description: HeaderMatchCondition specifies + how to conditionally match against HTTP + headers. The Name field is required, but + only one of the remaining fields should + be be provided. + properties: + contains: + description: Contains specifies a substring + that must be present in the header + value. + type: string + exact: + description: Exact specifies a string + that the header value must be equal + to. + type: string + name: + description: Name is the name of the + header to match against. Name is required. + Header names are case insensitive. + type: string + notcontains: + description: NotContains specifies a + substring that must not be present + in the header value. + type: string + notexact: + description: NoExact specifies a string + that the header value must not be + equal to. The condition is true if + the header has any other value. + type: string + notpresent: + description: NotPresent specifies that + condition is true when the named header + is not present. Note that setting + NotPresent to false does not make + the condition true if the named header + is present. + type: boolean + present: + description: Present specifies that + condition is true when the named header + is present, regardless of its value. + Note that setting Present to false + does not make the condition true if + the named header is absent. + type: boolean + regex: + description: Regex specifies a regular + expression pattern that must match + the header value. + type: string + required: + - name + type: object + minItems: 1 + type: array + value: + description: Value defines the value of the + descriptor entry. + minLength: 1 + type: string + type: object + type: object + minItems: 1 + type: array + type: object + minItems: 1 + type: array + disabled: + description: Disabled configures the HTTPProxy to not use + the default global rate limit policy defined by the Contour + configuration. + type: boolean + type: object domain: description: Domain is passed to the Rate Limit Service. type: string @@ -4065,6 +4220,168 @@ spec: description: RateLimitService optionally holds properties of the Rate Limit Service to be used for global rate limiting. properties: + defaultGlobalRateLimitPolicy: + description: DefaultGlobalRateLimitPolicy allows setting a + default global rate limit policy for every HTTPProxy. HTTPProxy + can overwrite this configuration. + properties: + descriptors: + description: Descriptors defines the list of descriptors + that will be generated and sent to the rate limit service. + Each descriptor contains 1+ key-value pair entries. + items: + description: RateLimitDescriptor defines a list of key-value + pair generators. + properties: + entries: + description: Entries is the list of key-value pair + generators. + items: + description: RateLimitDescriptorEntry is a key-value + pair generator. Exactly one field on this struct + must be non-nil. + properties: + genericKey: + description: GenericKey defines a descriptor + entry with a static key and value. + properties: + key: + description: Key defines the key of the + descriptor entry. If not set, the key + is set to "generic_key". + type: string + value: + description: Value defines the value of + the descriptor entry. + minLength: 1 + type: string + type: object + remoteAddress: + description: RemoteAddress defines a descriptor + entry with a key of "remote_address" and + a value equal to the client's IP address + (from x-forwarded-for). + type: object + requestHeader: + description: RequestHeader defines a descriptor + entry that's populated only if a given header + is present on the request. The descriptor + key is static, and the descriptor value + is equal to the value of the header. + properties: + descriptorKey: + description: DescriptorKey defines the + key to use on the descriptor entry. + minLength: 1 + type: string + headerName: + description: HeaderName defines the name + of the header to look for on the request. + minLength: 1 + type: string + type: object + requestHeaderValueMatch: + description: RequestHeaderValueMatch defines + a descriptor entry that's populated if the + request's headers match a set of 1+ match + criteria. The descriptor key is "header_match", + and the descriptor value is static. + properties: + expectMatch: + default: true + description: ExpectMatch defines whether + the request must positively match the + match criteria in order to generate + a descriptor entry (i.e. true), or not + match the match criteria in order to + generate a descriptor entry (i.e. false). + The default is true. + type: boolean + headers: + description: Headers is a list of 1+ match + criteria to apply against the request + to determine whether to populate the + descriptor entry or not. + items: + description: HeaderMatchCondition specifies + how to conditionally match against + HTTP headers. The Name field is required, + but only one of the remaining fields + should be be provided. + properties: + contains: + description: Contains specifies + a substring that must be present + in the header value. + type: string + exact: + description: Exact specifies a string + that the header value must be + equal to. + type: string + name: + description: Name is the name of + the header to match against. Name + is required. Header names are + case insensitive. + type: string + notcontains: + description: NotContains specifies + a substring that must not be present + in the header value. + type: string + notexact: + description: NoExact specifies a + string that the header value must + not be equal to. The condition + is true if the header has any + other value. + type: string + notpresent: + description: NotPresent specifies + that condition is true when the + named header is not present. Note + that setting NotPresent to false + does not make the condition true + if the named header is present. + type: boolean + present: + description: Present specifies that + condition is true when the named + header is present, regardless + of its value. Note that setting + Present to false does not make + the condition true if the named + header is absent. + type: boolean + regex: + description: Regex specifies a regular + expression pattern that must match + the header value. + type: string + required: + - name + type: object + minItems: 1 + type: array + value: + description: Value defines the value of + the descriptor entry. + minLength: 1 + type: string + type: object + type: object + minItems: 1 + type: array + type: object + minItems: 1 + type: array + disabled: + description: Disabled configures the HTTPProxy to not + use the default global rate limit policy defined by + the Contour configuration. + type: boolean + type: object domain: description: Domain is passed to the Rate Limit Service. type: string @@ -5591,6 +5908,11 @@ spec: type: object minItems: 1 type: array + disabled: + description: Disabled configures the HTTPProxy to not + use the default global rate limit policy defined by + the Contour configuration. + type: boolean type: object local: description: Local defines local rate limiting parameters, @@ -6944,6 +7266,11 @@ spec: type: object minItems: 1 type: array + disabled: + description: Disabled configures the HTTPProxy to not + use the default global rate limit policy defined by + the Contour configuration. + type: boolean type: object local: description: Local defines local rate limiting parameters, diff --git a/internal/contourconfig/contourconfiguration_test.go b/internal/contourconfig/contourconfiguration_test.go index 53f3585e793..27c121eb4c8 100644 --- a/internal/contourconfig/contourconfiguration_test.go +++ b/internal/contourconfig/contourconfiguration_test.go @@ -17,6 +17,7 @@ import ( "testing" "time" + contour_api_v1 "github.com/projectcontour/contour/apis/projectcontour/v1" contour_api_v1alpha1 "github.com/projectcontour/contour/apis/projectcontour/v1alpha1" "github.com/projectcontour/contour/internal/contourconfig" "github.com/projectcontour/contour/internal/ref" @@ -148,6 +149,20 @@ func TestOverlayOnDefaults(t *testing.T) { Domain: "ratelimitservicedomain", FailOpen: ref.To(true), EnableXRateLimitHeaders: ref.To(true), + DefaultGlobalRateLimitPolicy: &contour_api_v1.GlobalRateLimitPolicy{ + Descriptors: []contour_api_v1.RateLimitDescriptor{ + { + Entries: []contour_api_v1.RateLimitDescriptorEntry{ + { + GenericKey: &contour_api_v1.GenericKeyDescriptor{ + Key: "foo", + Value: "bar", + }, + }, + }, + }, + }, + }, }, Policy: &contour_api_v1alpha1.PolicyConfig{ RequestHeadersPolicy: &contour_api_v1alpha1.HeadersPolicy{ diff --git a/internal/dag/httpproxy_processor.go b/internal/dag/httpproxy_processor.go index 2a681890eaf..22e73ddb095 100644 --- a/internal/dag/httpproxy_processor.go +++ b/internal/dag/httpproxy_processor.go @@ -103,6 +103,9 @@ type HTTPProxyProcessor struct { // PerConnectionBufferLimitBytes defines the soft limit on size of the listener’s new connection read and write buffers. PerConnectionBufferLimitBytes *uint32 + + // GlobalRateLimitService defines Envoy's Global RateLimit Service configuration. + GlobalRateLimitService *contour_api_v1alpha1.RateLimitServiceConfig } // Run translates HTTPProxies into DAG objects and @@ -443,7 +446,7 @@ func (p *HTTPProxyProcessor) computeHTTPProxy(proxy *contour_api_v1.HTTPProxy) { } // Get the DNS lookup family if specified, otherwise - // default to to the Contour-wide setting. + // default to the Contour-wide setting. dnsLookupFamily := "" switch jwtProvider.RemoteJWKS.DNSLookupFamily { case "auto", "v4", "v6", "all": @@ -507,13 +510,11 @@ func (p *HTTPProxyProcessor) computeHTTPProxy(proxy *contour_api_v1.HTTPProxy) { } insecure.CORSPolicy = cp - rlp, err := rateLimitPolicy(proxy.Spec.VirtualHost.RateLimitPolicy) - if err != nil { - validCond.AddErrorf(contour_api_v1.ConditionTypeRouteError, "RateLimitPolicyNotValid", - "Spec.VirtualHost.RateLimitPolicy is invalid: %s", err) + var isValidRLP bool + insecure.RateLimitPolicy, isValidRLP = computeVirtualHostRateLimitPolicy(proxy, p.GlobalRateLimitService, validCond) + if !isValidRLP { return } - insecure.RateLimitPolicy = rlp if p.GlobalExternalAuthorization != nil && !proxy.Spec.VirtualHost.DisableAuthorization() { p.computeVirtualHostAuthorization(p.GlobalExternalAuthorization, validCond, proxy) @@ -540,13 +541,10 @@ func (p *HTTPProxyProcessor) computeHTTPProxy(proxy *contour_api_v1.HTTPProxy) { secure := p.dag.EnsureSecureVirtualHost(listener.Name, host) secure.CORSPolicy = cp - rlp, err := rateLimitPolicy(proxy.Spec.VirtualHost.RateLimitPolicy) - if err != nil { - validCond.AddErrorf(contour_api_v1.ConditionTypeRouteError, "RateLimitPolicyNotValid", - "Spec.VirtualHost.RateLimitPolicy is invalid: %s", err) + secure.RateLimitPolicy, isValidRLP = computeVirtualHostRateLimitPolicy(proxy, p.GlobalRateLimitService, validCond) + if !isValidRLP { return } - secure.RateLimitPolicy = rlp secure.IPFilterAllow, secure.IPFilterRules, err = toIPFilterRules(proxy.Spec.VirtualHost.IPAllowFilterPolicy, proxy.Spec.VirtualHost.IPDenyFilterPolicy, validCond) if err != nil { @@ -1360,18 +1358,18 @@ func validateExternalAuthExtensionService(ref contour_api_v1.ExtensionServiceRef } func determineExternalAuthTimeout(responseTimeout string, validCond *contour_api_v1.DetailedCondition, ext *ExtensionCluster) (bool, *timeout.Setting) { - timeout, err := timeout.Parse(responseTimeout) + tout, err := timeout.Parse(responseTimeout) if err != nil { validCond.AddErrorf(contour_api_v1.ConditionTypeAuthError, "AuthResponseTimeoutInvalid", "Spec.Virtualhost.Authorization.ResponseTimeout is invalid: %s", err) return false, nil } - if timeout.UseDefault() { + if tout.UseDefault() { return true, &ext.RouteTimeoutPolicy.ResponseTimeout } - return true, &timeout + return true, &tout } func (p *HTTPProxyProcessor) computeSecureVirtualHostAuthorization(validCond *contour_api_v1.DetailedCondition, httpproxy *contour_api_v1.HTTPProxy, svhost *SecureVirtualHost) bool { @@ -1394,11 +1392,46 @@ func (p *HTTPProxyProcessor) computeSecureVirtualHostAuthorization(validCond *co return true } +func computeVirtualHostRateLimitPolicy(proxy *contour_api_v1.HTTPProxy, rls *contour_api_v1alpha1.RateLimitServiceConfig, validCond *contour_api_v1.DetailedCondition) (*RateLimitPolicy, bool) { + rlp, err := rateLimitPolicy(proxy.Spec.VirtualHost.RateLimitPolicy) + if err != nil { + validCond.AddErrorf(contour_api_v1.ConditionTypeVirtualHostError, "RateLimitPolicyNotValid", + "Spec.VirtualHost.RateLimitPolicy is invalid: %s", err) + return nil, false + } + + // If HTTPProxy defines its own Global RateLimit Policy, no need to check the default rate limit policy. + if rlp != nil && rlp.Global != nil { + return rlp, true + } + + // Set the global rateLimit policy from the default global rateLimit policy + // if HTTPProxy is not opted out explicitly, or it defines its own global rate limit policy. + optedOut := proxy.Spec.VirtualHost.RateLimitPolicy != nil && + proxy.Spec.VirtualHost.RateLimitPolicy.Global != nil && + proxy.Spec.VirtualHost.RateLimitPolicy.Global.Disabled + + // Attach the default global rate limit policy if the rate limit service defines one. + if rls != nil && rls.DefaultGlobalRateLimitPolicy != nil && !optedOut { + if rlp == nil { + rlp = &RateLimitPolicy{} + } + rlp.Global, err = globalRateLimitPolicy(rls.DefaultGlobalRateLimitPolicy) + if err != nil { + validCond.AddErrorf(contour_api_v1.ConditionTypeVirtualHostError, "RateLimitPolicyNotValid", + "Default Global RateLimit Policy is invalid: %s", err) + return nil, false + } + } + + return rlp, true +} + func (p *HTTPProxyProcessor) GlobalAuthorizationConfigured() bool { return p.GlobalExternalAuthorization != nil } -// AuthorizationContext returns the authorization policy context (if present). +// GlobalAuthorizationContext returns the authorization policy context (if present). func (p *HTTPProxyProcessor) GlobalAuthorizationContext() map[string]string { if p.GlobalAuthorizationConfigured() && p.GlobalExternalAuthorization.AuthPolicy != nil { return p.GlobalExternalAuthorization.AuthPolicy.Context diff --git a/internal/dag/httpproxy_processor_test.go b/internal/dag/httpproxy_processor_test.go index 404a8096a4c..52f984ba17b 100644 --- a/internal/dag/httpproxy_processor_test.go +++ b/internal/dag/httpproxy_processor_test.go @@ -19,6 +19,7 @@ import ( "time" contour_api_v1 "github.com/projectcontour/contour/apis/projectcontour/v1" + contour_api_v1alpha1 "github.com/projectcontour/contour/apis/projectcontour/v1alpha1" "github.com/projectcontour/contour/internal/ref" "github.com/projectcontour/contour/internal/timeout" "github.com/stretchr/testify/assert" @@ -989,3 +990,399 @@ func TestToIPFilterRule(t *testing.T) { }) } } + +func TestValidateVirtualHostRateLimitPolicy(t *testing.T) { + tests := map[string]struct { + rateLimitServiceConfig *contour_api_v1alpha1.RateLimitServiceConfig + wantValidCond *contour_api_v1.DetailedCondition + httpproxy *contour_api_v1.HTTPProxy + want *RateLimitPolicy + isValidCond bool + wantConditionErrs []contour_api_v1.SubCondition + }{ + "no rate limit policy is set anywhere": { + rateLimitServiceConfig: &contour_api_v1alpha1.RateLimitServiceConfig{ + Domain: "test-domain", + FailOpen: ref.To(true), + }, + wantValidCond: &contour_api_v1.DetailedCondition{}, + httpproxy: &contour_api_v1.HTTPProxy{ + ObjectMeta: v1.ObjectMeta{ + Namespace: "ns", + }, + Spec: contour_api_v1.HTTPProxySpec{ + VirtualHost: &contour_api_v1.VirtualHost{}, + }, + }, + want: nil, + isValidCond: true, + }, + "default global rate limit Policy is not set": { + rateLimitServiceConfig: &contour_api_v1alpha1.RateLimitServiceConfig{ + Domain: "test-domain", + FailOpen: ref.To(true), + }, + wantValidCond: &contour_api_v1.DetailedCondition{}, + httpproxy: &contour_api_v1.HTTPProxy{ + ObjectMeta: v1.ObjectMeta{ + Namespace: "ns", + }, + Spec: contour_api_v1.HTTPProxySpec{ + VirtualHost: &contour_api_v1.VirtualHost{ + RateLimitPolicy: &contour_api_v1.RateLimitPolicy{ + Global: &contour_api_v1.GlobalRateLimitPolicy{ + Descriptors: []contour_api_v1.RateLimitDescriptor{ + { + Entries: []contour_api_v1.RateLimitDescriptorEntry{ + { + GenericKey: &contour_api_v1.GenericKeyDescriptor{ + Key: "foo", + Value: "bar", + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + want: &RateLimitPolicy{ + Global: &GlobalRateLimitPolicy{ + Descriptors: []*RateLimitDescriptor{ + { + Entries: []RateLimitDescriptorEntry{ + { + GenericKey: &GenericKeyDescriptorEntry{ + Key: "foo", + Value: "bar", + }, + }, + }, + }, + }, + }, + }, + isValidCond: true, + }, + "default global rate limit policy is set but HTTPProxy is opted out": { + rateLimitServiceConfig: &contour_api_v1alpha1.RateLimitServiceConfig{ + Domain: "test-domain", + FailOpen: ref.To(true), + DefaultGlobalRateLimitPolicy: &contour_api_v1.GlobalRateLimitPolicy{ + Descriptors: []contour_api_v1.RateLimitDescriptor{ + { + Entries: []contour_api_v1.RateLimitDescriptorEntry{ + { + GenericKey: &contour_api_v1.GenericKeyDescriptor{ + Key: "A general policy key", + Value: "A general policy value", + }, + }, + }, + }, + }, + }, + }, + wantValidCond: &contour_api_v1.DetailedCondition{}, + httpproxy: &contour_api_v1.HTTPProxy{ + ObjectMeta: v1.ObjectMeta{ + Namespace: "ns", + }, + Spec: contour_api_v1.HTTPProxySpec{ + VirtualHost: &contour_api_v1.VirtualHost{ + RateLimitPolicy: &contour_api_v1.RateLimitPolicy{ + Global: &contour_api_v1.GlobalRateLimitPolicy{ + Disabled: true, + }, + }, + }, + }, + }, + want: nil, + isValidCond: true, + }, + "default global rate limit policy is set but HTTPProxy defines its own global RateLimit policy": { + rateLimitServiceConfig: &contour_api_v1alpha1.RateLimitServiceConfig{ + Domain: "test-domain", + FailOpen: ref.To(true), + DefaultGlobalRateLimitPolicy: &contour_api_v1.GlobalRateLimitPolicy{ + Descriptors: []contour_api_v1.RateLimitDescriptor{ + { + Entries: []contour_api_v1.RateLimitDescriptorEntry{ + { + GenericKey: &contour_api_v1.GenericKeyDescriptor{ + Key: "A general policy key", + Value: "A general policy value", + }, + }, + }, + }, + }, + }, + }, + wantValidCond: &contour_api_v1.DetailedCondition{}, + httpproxy: &contour_api_v1.HTTPProxy{ + ObjectMeta: v1.ObjectMeta{ + Namespace: "ns", + }, + Spec: contour_api_v1.HTTPProxySpec{ + VirtualHost: &contour_api_v1.VirtualHost{ + RateLimitPolicy: &contour_api_v1.RateLimitPolicy{ + Global: &contour_api_v1.GlobalRateLimitPolicy{ + Descriptors: []contour_api_v1.RateLimitDescriptor{ + { + Entries: []contour_api_v1.RateLimitDescriptorEntry{ + { + GenericKey: &contour_api_v1.GenericKeyDescriptor{ + Key: "foo", + Value: "bar", + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + want: &RateLimitPolicy{ + Global: &GlobalRateLimitPolicy{ + Descriptors: []*RateLimitDescriptor{ + { + Entries: []RateLimitDescriptorEntry{ + { + GenericKey: &GenericKeyDescriptorEntry{ + Key: "foo", + Value: "bar", + }, + }, + }, + }, + }, + }, + }, + isValidCond: true, + }, + "default rate limit policy is set": { + rateLimitServiceConfig: &contour_api_v1alpha1.RateLimitServiceConfig{ + Domain: "test-domain", + FailOpen: ref.To(true), + DefaultGlobalRateLimitPolicy: &contour_api_v1.GlobalRateLimitPolicy{ + Descriptors: []contour_api_v1.RateLimitDescriptor{ + { + Entries: []contour_api_v1.RateLimitDescriptorEntry{ + { + GenericKey: &contour_api_v1.GenericKeyDescriptor{ + Key: "A general policy key", + Value: "A general policy value", + }, + }, + }, + }, + }, + }, + }, + wantValidCond: &contour_api_v1.DetailedCondition{}, + httpproxy: &contour_api_v1.HTTPProxy{ + ObjectMeta: v1.ObjectMeta{ + Namespace: "ns", + }, + Spec: contour_api_v1.HTTPProxySpec{ + VirtualHost: &contour_api_v1.VirtualHost{}, + }, + }, + want: &RateLimitPolicy{ + Global: &GlobalRateLimitPolicy{ + Descriptors: []*RateLimitDescriptor{ + { + Entries: []RateLimitDescriptorEntry{ + { + GenericKey: &GenericKeyDescriptorEntry{ + Key: "A general policy key", + Value: "A general policy value", + }, + }, + }, + }, + }, + }, + }, + isValidCond: true, + }, + "default rate limit policy is set and HTTPProxy's local rate limit should not change": { + rateLimitServiceConfig: &contour_api_v1alpha1.RateLimitServiceConfig{ + Domain: "test-domain", + FailOpen: ref.To(true), + DefaultGlobalRateLimitPolicy: &contour_api_v1.GlobalRateLimitPolicy{ + Descriptors: []contour_api_v1.RateLimitDescriptor{ + { + Entries: []contour_api_v1.RateLimitDescriptorEntry{ + { + GenericKey: &contour_api_v1.GenericKeyDescriptor{ + Key: "A general policy key", + Value: "A general policy value", + }, + }, + }, + }, + }, + }, + }, + wantValidCond: &contour_api_v1.DetailedCondition{}, + httpproxy: &contour_api_v1.HTTPProxy{ + ObjectMeta: v1.ObjectMeta{ + Namespace: "ns", + }, + Spec: contour_api_v1.HTTPProxySpec{ + VirtualHost: &contour_api_v1.VirtualHost{ + RateLimitPolicy: &contour_api_v1.RateLimitPolicy{ + Local: &contour_api_v1.LocalRateLimitPolicy{ + Requests: 10, + Unit: "second", + }, + }, + }, + }, + }, + want: &RateLimitPolicy{ + Global: &GlobalRateLimitPolicy{ + Descriptors: []*RateLimitDescriptor{ + { + Entries: []RateLimitDescriptorEntry{ + { + GenericKey: &GenericKeyDescriptorEntry{ + Key: "A general policy key", + Value: "A general policy value", + }, + }, + }, + }, + }, + }, + Local: &LocalRateLimitPolicy{ + MaxTokens: 10, + TokensPerFill: 10, + FillInterval: time.Second, + }, + }, + isValidCond: true, + }, + "default rate limit policy is set but it is invalid": { + rateLimitServiceConfig: &contour_api_v1alpha1.RateLimitServiceConfig{ + Domain: "test-domain", + FailOpen: ref.To(true), + DefaultGlobalRateLimitPolicy: &contour_api_v1.GlobalRateLimitPolicy{ + Descriptors: []contour_api_v1.RateLimitDescriptor{ + { + Entries: []contour_api_v1.RateLimitDescriptorEntry{ + {}, + }, + }, + }, + }, + }, + wantValidCond: &contour_api_v1.DetailedCondition{ + Condition: v1.Condition{ + Status: contour_api_v1.ConditionTrue, + Reason: "ErrorPresent", + Message: "At least one error present, see Errors for details", + }, + }, + httpproxy: &contour_api_v1.HTTPProxy{ + ObjectMeta: v1.ObjectMeta{ + Namespace: "ns", + }, + Spec: contour_api_v1.HTTPProxySpec{ + VirtualHost: &contour_api_v1.VirtualHost{ + RateLimitPolicy: &contour_api_v1.RateLimitPolicy{ + Local: &contour_api_v1.LocalRateLimitPolicy{ + Requests: 10, + Unit: "second", + }, + }, + }, + }, + }, + want: nil, + isValidCond: false, + wantConditionErrs: []contour_api_v1.SubCondition{ + { + Type: "VirtualHostError", + Status: "True", + Reason: "RateLimitPolicyNotValid", + Message: "Default Global RateLimit Policy is invalid: rate limit descriptor entry must have exactly one field set", + }, + }, + }, + "global rate limit policy on HTTPProxy is invalid": { + rateLimitServiceConfig: &contour_api_v1alpha1.RateLimitServiceConfig{ + Domain: "test-domain", + FailOpen: ref.To(true), + DefaultGlobalRateLimitPolicy: &contour_api_v1.GlobalRateLimitPolicy{ + Descriptors: []contour_api_v1.RateLimitDescriptor{ + { + Entries: []contour_api_v1.RateLimitDescriptorEntry{ + { + GenericKey: &contour_api_v1.GenericKeyDescriptor{ + Key: "A general policy key", + Value: "A general policy value", + }, + }, + }, + }, + }, + }, + }, + wantValidCond: &contour_api_v1.DetailedCondition{ + Condition: v1.Condition{ + Status: contour_api_v1.ConditionTrue, + Reason: "ErrorPresent", + Message: "At least one error present, see Errors for details", + }, + }, + httpproxy: &contour_api_v1.HTTPProxy{ + ObjectMeta: v1.ObjectMeta{ + Namespace: "ns", + }, + Spec: contour_api_v1.HTTPProxySpec{ + VirtualHost: &contour_api_v1.VirtualHost{ + RateLimitPolicy: &contour_api_v1.RateLimitPolicy{ + Global: &contour_api_v1.GlobalRateLimitPolicy{ + Descriptors: []contour_api_v1.RateLimitDescriptor{ + { + Entries: []contour_api_v1.RateLimitDescriptorEntry{ + {}, + }, + }, + }, + }, + }, + }, + }, + }, + want: nil, + isValidCond: false, + wantConditionErrs: []contour_api_v1.SubCondition{ + { + Type: "VirtualHostError", + Status: "True", + Reason: "RateLimitPolicyNotValid", + Message: "Spec.VirtualHost.RateLimitPolicy is invalid: rate limit descriptor entry must have exactly one field set", + }, + }, + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + validCond := &contour_api_v1.DetailedCondition{} + got, isValid := computeVirtualHostRateLimitPolicy(tc.httpproxy, tc.rateLimitServiceConfig, validCond) + require.Equal(t, tc.isValidCond, isValid) + require.Equal(t, tc.want, got) + require.Equal(t, tc.wantConditionErrs, validCond.Errors) + }) + } +} diff --git a/internal/dag/policy.go b/internal/dag/policy.go index 33590506045..8f6966ccbbf 100644 --- a/internal/dag/policy.go +++ b/internal/dag/policy.go @@ -539,7 +539,7 @@ func prefixReplacementsAreValid(replacements []contour_api_v1.ReplacePrefix) (st } func rateLimitPolicy(in *contour_api_v1.RateLimitPolicy) (*RateLimitPolicy, error) { - if in == nil || (in.Local == nil && in.Global == nil) { + if in == nil || (in.Local == nil && (in.Global == nil || len(in.Global.Descriptors) == 0)) { return nil, nil } @@ -608,7 +608,7 @@ func localRateLimitPolicy(in *contour_api_v1.LocalRateLimitPolicy) (*LocalRateLi } func globalRateLimitPolicy(in *contour_api_v1.GlobalRateLimitPolicy) (*GlobalRateLimitPolicy, error) { - if in == nil { + if in == nil || in.Disabled { return nil, nil } diff --git a/internal/featuretests/v3/globalratelimit_test.go b/internal/featuretests/v3/globalratelimit_test.go index 7be0de28dd6..7cefb8c7ba0 100644 --- a/internal/featuretests/v3/globalratelimit_test.go +++ b/internal/featuretests/v3/globalratelimit_test.go @@ -111,6 +111,11 @@ func globalRateLimitNoRateLimitsDefined(t *testing.T, rh ResourceEventHandlerWra Spec: contour_api_v1.HTTPProxySpec{ VirtualHost: &contour_api_v1.VirtualHost{ Fqdn: "foo.com", + RateLimitPolicy: &contour_api_v1.RateLimitPolicy{ + Global: &contour_api_v1.GlobalRateLimitPolicy{ + Disabled: true, + }, + }, }, Routes: []contour_api_v1.Route{ { @@ -287,6 +292,11 @@ func globalRateLimitRouteRateLimitDefined(t *testing.T, rh ResourceEventHandlerW Spec: contour_api_v1.HTTPProxySpec{ VirtualHost: &contour_api_v1.VirtualHost{ Fqdn: "foo.com", + RateLimitPolicy: &contour_api_v1.RateLimitPolicy{ + Global: &contour_api_v1.GlobalRateLimitPolicy{ + Disabled: true, + }, + }, }, Routes: []contour_api_v1.Route{ { @@ -496,6 +506,113 @@ func globalRateLimitVhostAndRouteRateLimitDefined(t *testing.T, rh ResourceEvent } } +func defaultGlobalRateLimitVhostRateLimitDefined(t *testing.T, rh ResourceEventHandlerWrapper, c *Contour, tls tlsConfig) { + p := &contour_api_v1.HTTPProxy{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "default", + Name: "proxy1", + }, + Spec: contour_api_v1.HTTPProxySpec{ + VirtualHost: &contour_api_v1.VirtualHost{ + Fqdn: "foo.com", + }, + Routes: []contour_api_v1.Route{ + { + Services: []contour_api_v1.Service{ + { + Name: "s1", + Port: 80, + }, + }, + RateLimitPolicy: &contour_api_v1.RateLimitPolicy{ + Global: &contour_api_v1.GlobalRateLimitPolicy{ + Descriptors: []contour_api_v1.RateLimitDescriptor{ + { + Entries: []contour_api_v1.RateLimitDescriptorEntry{ + { + RemoteAddress: &contour_api_v1.RemoteAddressDescriptor{}, + }, + { + GenericKey: &contour_api_v1.GenericKeyDescriptor{Value: "generic-key-value"}, + }, + }, + }, + }, + }, + }, + }, + }, + }, + } + + if tls.enabled { + p.Spec.VirtualHost.TLS = &contour_api_v1.TLS{ + SecretName: "tls-cert", + EnableFallbackCertificate: tls.fallbackEnabled, + } + } + + rh.OnAdd(p) + c.Status(p).IsValid() + + route := &envoy_route_v3.Route{ + Match: routePrefix("/"), + Action: routeCluster("default/s1/80/da39a3ee5e", func(r *envoy_route_v3.Route_Route) { + r.Route.RateLimits = []*envoy_route_v3.RateLimit{ + { + Actions: []*envoy_route_v3.RateLimit_Action{ + { + ActionSpecifier: &envoy_route_v3.RateLimit_Action_RemoteAddress_{ + RemoteAddress: &envoy_route_v3.RateLimit_Action_RemoteAddress{}, + }, + }, + { + ActionSpecifier: &envoy_route_v3.RateLimit_Action_GenericKey_{ + GenericKey: &envoy_route_v3.RateLimit_Action_GenericKey{DescriptorValue: "generic-key-value"}, + }, + }, + }, + }, + } + }), + } + + vhost := envoy_v3.VirtualHost("foo.com", route) + vhost.RateLimits = []*envoy_route_v3.RateLimit{ + { + Actions: []*envoy_route_v3.RateLimit_Action{ + { + ActionSpecifier: &envoy_route_v3.RateLimit_Action_GenericKey_{ + GenericKey: &envoy_route_v3.RateLimit_Action_GenericKey{ + DescriptorKey: "generic-key-vhost", + DescriptorValue: "generic-key-vhost", + }, + }, + }, + }, + }, + } + + switch tls.enabled { + case true: + c.Request(routeType, "https/foo.com").Equals(&envoy_discovery_v3.DiscoveryResponse{ + TypeUrl: routeType, + Resources: resources(t, envoy_v3.RouteConfiguration("https/foo.com", vhost)), + }) + if tls.fallbackEnabled { + c.Request(routeType, "ingress_fallbackcert").Equals(&envoy_discovery_v3.DiscoveryResponse{ + TypeUrl: routeType, + Resources: resources(t, envoy_v3.RouteConfiguration("ingress_fallbackcert", vhost)), + }) + } + default: + c.Request(routeType).Equals(&envoy_discovery_v3.DiscoveryResponse{ + TypeUrl: routeType, + Resources: resources(t, envoy_v3.RouteConfiguration("ingress_http", vhost)), + }) + } +} + func globalRateLimitMultipleDescriptorsAndEntries(t *testing.T, rh ResourceEventHandlerWrapper, c *Contour) { p := &contour_api_v1.HTTPProxy{ ObjectMeta: metav1.ObjectMeta{ @@ -505,6 +622,11 @@ func globalRateLimitMultipleDescriptorsAndEntries(t *testing.T, rh ResourceEvent Spec: contour_api_v1.HTTPProxySpec{ VirtualHost: &contour_api_v1.VirtualHost{ Fqdn: "foo.com", + RateLimitPolicy: &contour_api_v1.RateLimitPolicy{ + Global: &contour_api_v1.GlobalRateLimitPolicy{ + Disabled: true, + }, + }, }, Routes: []contour_api_v1.Route{ { @@ -626,6 +748,9 @@ func TestGlobalRateLimiting(t *testing.T) { "VirtualHostAndRouteRateLimitsDefined": func(t *testing.T, rh ResourceEventHandlerWrapper, c *Contour) { globalRateLimitVhostAndRouteRateLimitDefined(t, rh, c, tlsDisabled) }, + "VirtualHostDefaultGlobalRateLimitDefined": func(t *testing.T, rh ResourceEventHandlerWrapper, c *Contour) { + defaultGlobalRateLimitVhostRateLimitDefined(t, rh, c, tlsDisabled) + }, // test cases for secure/TLS vhosts "TLSNoRateLimitsDefined": func(t *testing.T, rh ResourceEventHandlerWrapper, c *Contour) { @@ -640,6 +765,9 @@ func TestGlobalRateLimiting(t *testing.T) { "TLSVirtualHostAndRouteRateLimitsDefined": func(t *testing.T, rh ResourceEventHandlerWrapper, c *Contour) { globalRateLimitVhostAndRouteRateLimitDefined(t, rh, c, tlsEnabled) }, + "TLSVirtualHostDefaultGlobalRateLimitDefined": func(t *testing.T, rh ResourceEventHandlerWrapper, c *Contour) { + defaultGlobalRateLimitVhostRateLimitDefined(t, rh, c, tlsEnabled) + }, // test cases for secure/TLS vhosts with fallback cert enabled "FallbackNoRateLimitsDefined": func(t *testing.T, rh ResourceEventHandlerWrapper, c *Contour) { @@ -654,6 +782,9 @@ func TestGlobalRateLimiting(t *testing.T) { "FallbackVirtualHostAndRouteRateLimitsDefined": func(t *testing.T, rh ResourceEventHandlerWrapper, c *Contour) { globalRateLimitVhostAndRouteRateLimitDefined(t, rh, c, fallbackEnabled) }, + "FallbackVirtualHostDefaultGlobalRateLimitDefined": func(t *testing.T, rh ResourceEventHandlerWrapper, c *Contour) { + defaultGlobalRateLimitVhostRateLimitDefined(t, rh, c, fallbackEnabled) + }, "MultipleDescriptorsAndEntriesDefined": globalRateLimitMultipleDescriptorsAndEntries, } @@ -677,6 +808,28 @@ func TestGlobalRateLimiting(t *testing.T) { Name: "fallback-cert", Namespace: "default", } + + httpProxyProcessor.GlobalRateLimitService = &contour_api_v1alpha1.RateLimitServiceConfig{ + ExtensionService: contour_api_v1alpha1.NamespacedName{ + Name: "extension", + Namespace: "ratelimit", + }, + Domain: "contour", + DefaultGlobalRateLimitPolicy: &contour_api_v1.GlobalRateLimitPolicy{ + Descriptors: []contour_api_v1.RateLimitDescriptor{ + { + Entries: []contour_api_v1.RateLimitDescriptorEntry{ + { + GenericKey: &contour_api_v1.GenericKeyDescriptor{ + Key: "generic-key-vhost", + Value: "generic-key-vhost", + }, + }, + }, + }, + }, + }, + } } } }, diff --git a/pkg/config/parameters.go b/pkg/config/parameters.go index 2bc253e9e23..5edf26b2131 100644 --- a/pkg/config/parameters.go +++ b/pkg/config/parameters.go @@ -22,6 +22,7 @@ import ( "strings" "time" + contour_api_v1 "github.com/projectcontour/contour/apis/projectcontour/v1" contour_api_v1alpha1 "github.com/projectcontour/contour/apis/projectcontour/v1alpha1" "gopkg.in/yaml.v3" "k8s.io/apimachinery/pkg/util/validation" @@ -736,6 +737,10 @@ type RateLimitService struct { // EnableResourceExhaustedCode enables translating error code 429 to // grpc code RESOURCE_EXHAUSTED. When disabled it's translated to UNAVAILABLE EnableResourceExhaustedCode bool `yaml:"enableResourceExhaustedCode,omitempty"` + + // DefaultGlobalRateLimitPolicy allows setting a default global rate limit policy for all HTTPProxy + // HTTPProxy can overwrite this configuration. + DefaultGlobalRateLimitPolicy *contour_api_v1.GlobalRateLimitPolicy `yaml:"defaultGlobalRateLimitPolicy,omitempty"` } // MetricsParameters defines configuration for metrics server endpoints in both diff --git a/site/content/docs/main/config/api-reference.html b/site/content/docs/main/config/api-reference.html index 18f981a205c..4bd249d37ab 100644 --- a/site/content/docs/main/config/api-reference.html +++ b/site/content/docs/main/config/api-reference.html @@ -1226,7 +1226,8 @@

GlobalRateLimitPolicy

(Appears on: -RateLimitPolicy) +RateLimitPolicy, +RateLimitServiceConfig)

GlobalRateLimitPolicy defines global rate limiting parameters.

@@ -1241,6 +1242,20 @@

GlobalRateLimitPolicy +disabled +
+ +bool + + + +(Optional) +

Disabled configures the HTTPProxy to not use +the default global rate limit policy defined by the Contour configuration.

+ + + + descriptors
@@ -1250,6 +1265,7 @@

GlobalRateLimitPolicy +(Optional)

Descriptors defines the list of descriptors that will be generated and sent to the rate limit service. Each descriptor contains 1+ key-value pair entries.

@@ -8163,6 +8179,22 @@

RateLimitServiceConfi grpc code RESOURCE_EXHAUSTED. When disabled it’s translated to UNAVAILABLE

+ + +defaultGlobalRateLimitPolicy +
+ + +GlobalRateLimitPolicy + + + + +(Optional) +

DefaultGlobalRateLimitPolicy allows setting a default global rate limit policy for every HTTPProxy. +HTTPProxy can overwrite this configuration.

+ +

ServerHeaderTransformationType diff --git a/site/content/docs/main/guides/global-rate-limiting.md b/site/content/docs/main/guides/global-rate-limiting.md index 5a9b38f062e..c2ff23edd27 100644 --- a/site/content/docs/main/guides/global-rate-limiting.md +++ b/site/content/docs/main/guides/global-rate-limiting.md @@ -291,7 +291,7 @@ $ curl -k http://local.projectcontour.io/test/$((RANDOM)) ## Add global rate limit policies -Now that we have a working application exposed by a `HTTPProxy` resource, we can add add global rate limiting to it. +Now that we have a working application exposed by a `HTTPProxy` resource, we can add global rate limiting to it. Edit the `HTTPProxy` that we created in the previous step to add rate limit policies to both routes: @@ -329,6 +329,75 @@ spec: value: foo ``` +## Default Global rate limit policy + +Contour supports defining a default global rate limit policy in the `rateLimitService` configuration +which is applied to all virtual hosts unless the host is opted-out by +explicitly setting `disabled` to `true`. This is useful for a single-tenant +setup use-case. This means you don't have to edit all HTTPProxy objects with the same rate limit policies, instead you can +define the policies in the `rateLimitService` configuration like this: +```yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: contour + namespace: projectcontour +data: + contour.yaml: | + rateLimitService: + extensionService: projectcontour/ratelimit + domain: contour + failOpen: false + defaultGlobalRateLimitPolicy: + descriptors: + - entries: + - requestHeader: + headerName: X-Custom-Header + descriptorKey: CustomHeader +``` + +Virtual host can opt out by setting `disabled` to `true`. +```yaml +apiVersion: projectcontour.io/v1 +kind: HTTPProxy +metadata: + name: echo +spec: + virtualhost: + fqdn: local.projectcontour.io + rateLimitPolicy: + global: + disabled: true + routes: + - conditions: + - prefix: / + services: + - name: ingress-conformance-echo + port: 80 +``` + +Also, the default global rate limit policy is not applied in case the virtual host defines its own global rate limit policy. +```yaml +apiVersion: projectcontour.io/v1 +kind: HTTPProxy +metadata: + name: echo +spec: + virtualhost: + fqdn: local.projectcontour.io + rateLimitPolicy: + global: + descriptors: + - entries: + - remoteAddress: {} + routes: + - conditions: + - prefix: / + services: + - name: ingress-conformance-echo + port: 80 +``` + ## Make requests Before making requests to our `HTTPProxy`, let's quickly revisit the `ratelimit-config` config map. diff --git a/test/e2e/httpproxy/default_global_rate_limiting_test.go b/test/e2e/httpproxy/default_global_rate_limiting_test.go new file mode 100644 index 00000000000..b9f00a643eb --- /dev/null +++ b/test/e2e/httpproxy/default_global_rate_limiting_test.go @@ -0,0 +1,364 @@ +// Copyright Project Contour Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//go:build e2e + +package httpproxy + +import ( + "net/http" + + . "github.com/onsi/ginkgo/v2" + contourv1 "github.com/projectcontour/contour/apis/projectcontour/v1" + "github.com/projectcontour/contour/test/e2e" + "github.com/stretchr/testify/require" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func testDefaultGlobalRateLimitingVirtualHostNonTLS(namespace string) { + Specify("default global rate limit policy is applied on non-TLS virtualhost", func() { + t := f.T() + + f.Fixtures.Echo.Deploy(namespace, "echo") + + p := &contourv1.HTTPProxy{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: namespace, + Name: "defaultglobalratelimitvhostnontls", + }, + Spec: contourv1.HTTPProxySpec{ + VirtualHost: &contourv1.VirtualHost{ + Fqdn: "defaultglobalratelimitvhostnontls.projectcontour.io", + }, + Routes: []contourv1.Route{ + { + Services: []contourv1.Service{ + { + Name: "echo", + Port: 80, + }, + }, + }, + }, + }, + } + p, _ = f.CreateHTTPProxyAndWaitFor(p, e2e.HTTPProxyValid) + + // Wait until we get a 429 from the proxy confirming + // that we've exceeded the rate limit. + res, ok := f.HTTP.RequestUntil(&e2e.HTTPRequestOpts{ + Host: p.Spec.VirtualHost.Fqdn, + Condition: e2e.HasStatusCode(429), + RequestOpts: []func(*http.Request){ + e2e.OptSetHeaders(map[string]string{ + "X-Default-Header": "test_value_1", + }), + }, + }) + require.NotNil(t, res, "request never succeeded") + require.Truef(t, ok, "expected 429 response code, got %d", res.StatusCode) + }) + + Specify("default global rate limit policy is set but HTTPProxy is opted-out", func() { + t := f.T() + + f.Fixtures.Echo.Deploy(namespace, "echo") + + p := &contourv1.HTTPProxy{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: namespace, + Name: "defaultglobalratelimitvhostnontls", + }, + Spec: contourv1.HTTPProxySpec{ + VirtualHost: &contourv1.VirtualHost{ + Fqdn: "defaultglobalratelimitvhostnontls.projectcontour.io", + RateLimitPolicy: &contourv1.RateLimitPolicy{ + Global: &contourv1.GlobalRateLimitPolicy{ + Disabled: true, + }, + }, + }, + Routes: []contourv1.Route{ + { + Services: []contourv1.Service{ + { + Name: "echo", + Port: 80, + }, + }, + }, + }, + }, + } + p, _ = f.CreateHTTPProxyAndWaitFor(p, e2e.HTTPProxyValid) + + // Wait until we get a 200 from the proxy confirming + // the pods are up and serving traffic. + res, ok := f.HTTP.RequestUntil(&e2e.HTTPRequestOpts{ + Host: p.Spec.VirtualHost.Fqdn, + Condition: e2e.HasStatusCode(200), + RequestOpts: []func(*http.Request){ + e2e.OptSetHeaders(map[string]string{ + "X-Default-Header": "test_value_2", + }), + }, + }) + require.NotNil(t, res, "request never succeeded") + require.Truef(t, ok, "expected 200 response code, got %d", res.StatusCode) + + // Make another request against the proxy to confirm a 200 response + // which indicates that HTTPProxy has disabled the default global rate limiting. + res, ok = f.HTTP.RequestUntil(&e2e.HTTPRequestOpts{ + Host: p.Spec.VirtualHost.Fqdn, + Condition: e2e.HasStatusCode(200), + RequestOpts: []func(*http.Request){ + e2e.OptSetHeaders(map[string]string{ + "X-Default-Header": "test_value_2", + }), + }, + }) + require.NotNil(t, res, "request never succeeded") + require.Truef(t, ok, "expected 200 response code, got %d", res.StatusCode) + }) + + Specify("default global rate limit policy is set but HTTPProxy has its own global rate limit policy", func() { + t := f.T() + + f.Fixtures.Echo.Deploy(namespace, "echo") + + p := &contourv1.HTTPProxy{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: namespace, + Name: "defaultglobalratelimitvhostnontls", + }, + Spec: contourv1.HTTPProxySpec{ + VirtualHost: &contourv1.VirtualHost{ + Fqdn: "defaultglobalratelimitvhostnontls.projectcontour.io", + RateLimitPolicy: &contourv1.RateLimitPolicy{ + Global: &contourv1.GlobalRateLimitPolicy{ + Descriptors: []contourv1.RateLimitDescriptor{ + { + Entries: []contourv1.RateLimitDescriptorEntry{ + { + GenericKey: &contourv1.GenericKeyDescriptor{ + Value: "foo", + }, + }, + }, + }, + }, + }, + }, + }, + Routes: []contourv1.Route{ + { + Services: []contourv1.Service{ + { + Name: "echo", + Port: 80, + }, + }, + }, + }, + }, + } + p, _ = f.CreateHTTPProxyAndWaitFor(p, e2e.HTTPProxyValid) + + // Make requests against the proxy, confirm a 429 response + // is now gotten since we've exceeded the rate limit. + res, ok := f.HTTP.RequestUntil(&e2e.HTTPRequestOpts{ + Host: p.Spec.VirtualHost.Fqdn, + Condition: e2e.HasStatusCode(429), + }) + require.NotNil(t, res, "request never succeeded") + require.Truef(t, ok, "expected 429 response code, got %d", res.StatusCode) + }) +} + +func testDefaultGlobalRateLimitingVirtualHostTLS(namespace string) { + Specify("default global rate limit policy is applied on TLS virtualhost", func() { + t := f.T() + + f.Fixtures.Echo.Deploy(namespace, "echo") + f.Certs.CreateSelfSignedCert(namespace, "echo-cert", "echo", "globalratelimitvhosttls.projectcontour.io") + + p := &contourv1.HTTPProxy{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: namespace, + Name: "defaultglobalratelimitvhostnontls", + }, + Spec: contourv1.HTTPProxySpec{ + VirtualHost: &contourv1.VirtualHost{ + Fqdn: "defaultglobalratelimitvhostnontls.projectcontour.io", + TLS: &contourv1.TLS{ + SecretName: "echo", + }, + }, + Routes: []contourv1.Route{ + { + Services: []contourv1.Service{ + { + Name: "echo", + Port: 80, + }, + }, + }, + }, + }, + } + p, _ = f.CreateHTTPProxyAndWaitFor(p, e2e.HTTPProxyValid) + + // Wait until we get a 429 from the proxy confirming + // that we've exceeded the rate limit. + res, ok := f.HTTP.SecureRequestUntil(&e2e.HTTPSRequestOpts{ + Host: p.Spec.VirtualHost.Fqdn, + Condition: e2e.HasStatusCode(429), + RequestOpts: []func(*http.Request){ + e2e.OptSetHeaders(map[string]string{ + "X-Default-Header": "test_value_3", + }), + }, + }) + require.NotNil(t, res, "request never succeeded") + require.Truef(t, ok, "expected 429 response code, got %d", res.StatusCode) + }) + + Specify("default global rate limit policy is set but HTTPProxy opts out", func() { + t := f.T() + + f.Fixtures.Echo.Deploy(namespace, "echo") + f.Certs.CreateSelfSignedCert(namespace, "echo-cert", "echo", "globalratelimitroutetls.projectcontour.io") + + p := &contourv1.HTTPProxy{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: namespace, + Name: "defaultglobalratelimitvhostnontls", + }, + Spec: contourv1.HTTPProxySpec{ + VirtualHost: &contourv1.VirtualHost{ + Fqdn: "defaultglobalratelimitvhostnontls.projectcontour.io", + TLS: &contourv1.TLS{ + SecretName: "echo", + }, + RateLimitPolicy: &contourv1.RateLimitPolicy{ + Global: &contourv1.GlobalRateLimitPolicy{ + Disabled: true, + }, + }, + }, + Routes: []contourv1.Route{ + { + Services: []contourv1.Service{ + { + Name: "echo", + Port: 80, + }, + }, + }, + }, + }, + } + p, _ = f.CreateHTTPProxyAndWaitFor(p, e2e.HTTPProxyValid) + + // Wait until we get a 200 from the proxy confirming + // the pods are up and serving traffic. + res, ok := f.HTTP.SecureRequestUntil(&e2e.HTTPSRequestOpts{ + Host: p.Spec.VirtualHost.Fqdn, + Condition: e2e.HasStatusCode(200), + RequestOpts: []func(*http.Request){ + e2e.OptSetHeaders(map[string]string{ + "X-Default-Header": "test_value_4", + }), + }, + }) + require.NotNil(t, res, "request never succeeded") + require.Truef(t, ok, "expected 200 response code, got %d", res.StatusCode) + + // Make another request against the proxy to confirm a 200 response + // which indicates that HTTPProxy has disabled the default global rate limiting. + res, ok = f.HTTP.SecureRequestUntil(&e2e.HTTPSRequestOpts{ + Host: p.Spec.VirtualHost.Fqdn, + Condition: e2e.HasStatusCode(200), + RequestOpts: []func(*http.Request){ + e2e.OptSetHeaders(map[string]string{ + "X-Default-Header": "test_value_4", + }), + }, + }) + require.NotNil(t, res, "request never succeeded") + require.Truef(t, ok, "expected 200 response code, got %d", res.StatusCode) + }) + + Specify("default global rate limit policy is set but HTTPProxy has its own global rate limit policy", func() { + t := f.T() + + f.Fixtures.Echo.Deploy(namespace, "echo") + f.Certs.CreateSelfSignedCert(namespace, "echo-cert", "echo", "globalratelimitroutetls.projectcontour.io") + + p := &contourv1.HTTPProxy{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: namespace, + Name: "defaultglobalratelimitvhostnontls", + }, + Spec: contourv1.HTTPProxySpec{ + VirtualHost: &contourv1.VirtualHost{ + Fqdn: "defaultglobalratelimitvhostnontls.projectcontour.io", + TLS: &contourv1.TLS{ + SecretName: "echo", + }, + RateLimitPolicy: &contourv1.RateLimitPolicy{ + Global: &contourv1.GlobalRateLimitPolicy{ + Descriptors: []contourv1.RateLimitDescriptor{ + { + Entries: []contourv1.RateLimitDescriptorEntry{ + { + RequestHeader: &contourv1.RequestHeaderDescriptor{ + HeaderName: "X-HTTPProxy-Descriptor", + DescriptorKey: "customHeader", + }, + }, + }, + }, + }, + }, + }, + }, + Routes: []contourv1.Route{ + { + Services: []contourv1.Service{ + { + Name: "echo", + Port: 80, + }, + }, + }, + }, + }, + } + p, _ = f.CreateHTTPProxyAndWaitFor(p, e2e.HTTPProxyValid) + + // Make requests against the proxy, confirm a 429 response + // is now gotten since we've exceeded the rate limit. + res, ok := f.HTTP.SecureRequestUntil(&e2e.HTTPSRequestOpts{ + Host: p.Spec.VirtualHost.Fqdn, + Condition: e2e.HasStatusCode(429), + RequestOpts: []func(*http.Request){ + e2e.OptSetHeaders(map[string]string{ + "X-HTTPProxy-Descriptor": "test_value", + }), + }, + }) + require.NotNil(t, res, "request never succeeded") + require.Truef(t, ok, "expected 429 response code, got %d", res.StatusCode) + }) +} diff --git a/test/e2e/httpproxy/httpproxy_test.go b/test/e2e/httpproxy/httpproxy_test.go index e27dd09fc77..fa327366305 100644 --- a/test/e2e/httpproxy/httpproxy_test.go +++ b/test/e2e/httpproxy/httpproxy_test.go @@ -423,6 +423,83 @@ descriptors: f.NamespacedTest("httpproxy-global-rate-limiting-route-tls", withRateLimitService(testGlobalRateLimitingRouteTLS)) }) + Context("default global rate limiting", func() { + withRateLimitService := func(body e2e.NamespacedTestBody) e2e.NamespacedTestBody { + return func(namespace string) { + Context("with rate limit service", func() { + BeforeEach(func() { + contourConfig.RateLimitService = config.RateLimitService{ + ExtensionService: fmt.Sprintf("%s/%s", namespace, f.Deployment.RateLimitExtensionService.Name), + Domain: "contour-default-global-rate-limit", + FailOpen: false, + DefaultGlobalRateLimitPolicy: &contour_api_v1.GlobalRateLimitPolicy{ + Descriptors: []contour_api_v1.RateLimitDescriptor{ + { + Entries: []contour_api_v1.RateLimitDescriptorEntry{ + { + RequestHeader: &contour_api_v1.RequestHeaderDescriptor{ + HeaderName: "X-Default-Header", + DescriptorKey: "defaultHeader", + }, + }, + }, + }, + }, + }, + } + contourConfiguration.Spec.RateLimitService = &contour_api_v1alpha1.RateLimitServiceConfig{ + ExtensionService: contour_api_v1alpha1.NamespacedName{ + Name: f.Deployment.RateLimitExtensionService.Name, + Namespace: namespace, + }, + Domain: "contour-default-global-rate-limit", + FailOpen: ref.To(false), + EnableXRateLimitHeaders: ref.To(false), + DefaultGlobalRateLimitPolicy: &contour_api_v1.GlobalRateLimitPolicy{ + Descriptors: []contour_api_v1.RateLimitDescriptor{ + { + Entries: []contour_api_v1.RateLimitDescriptorEntry{ + { + RequestHeader: &contour_api_v1.RequestHeaderDescriptor{ + HeaderName: "X-Default-Header", + DescriptorKey: "defaultHeader", + }, + }, + }, + }, + }, + }, + } + require.NoError(f.T(), + f.Deployment.EnsureRateLimitResources( + namespace, + ` +domain: contour-default-global-rate-limit +descriptors: + - key: generic_key + value: foo + rate_limit: + unit: hour + requests_per_unit: 1 + - key: defaultHeader + rate_limit: + unit: hour + requests_per_unit: 1 + - key: customHeader + rate_limit: + unit: hour + requests_per_unit: 1`)) + }) + + body(namespace) + }) + } + } + + f.NamespacedTest("httpproxy-default-global-rate-limiting-vhost-non-tls", withRateLimitService(testDefaultGlobalRateLimitingVirtualHostNonTLS)) + f.NamespacedTest("httpproxy-default-global-rate-limiting-vhost-tls", withRateLimitService(testDefaultGlobalRateLimitingVirtualHostTLS)) + }) + Context("cookie-rewriting", func() { f.NamespacedTest("app-cookie-rewrite", testAppCookieRewrite)