diff --git a/api/v1alpha1/clienttrafficpolicy_types.go b/api/v1alpha1/clienttrafficpolicy_types.go index f3b69520fd6..929b7388bf5 100644 --- a/api/v1alpha1/clienttrafficpolicy_types.go +++ b/api/v1alpha1/clienttrafficpolicy_types.go @@ -104,6 +104,11 @@ type HeaderSettings struct { // +optional EnableEnvoyHeaders *bool `json:"enableEnvoyHeaders,omitempty"` + // DisableRateLimitHeaders configures Envoy Proxy to omit the "X-RateLimit-" response headers + // when rate limiting is enabled. + // +optional + DisableRateLimitHeaders *bool `json:"disableRateLimitHeaders,omitempty"` + // XForwardedClientCert configures how Envoy Proxy handle the x-forwarded-client-cert (XFCC) HTTP header. // // x-forwarded-client-cert (XFCC) is an HTTP header used to forward the certificate diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 8720b70a549..44ed508c724 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -2552,6 +2552,11 @@ func (in *HeaderSettings) DeepCopyInto(out *HeaderSettings) { *out = new(bool) **out = **in } + if in.DisableRateLimitHeaders != nil { + in, out := &in.DisableRateLimitHeaders, &out.DisableRateLimitHeaders + *out = new(bool) + **out = **in + } if in.XForwardedClientCert != nil { in, out := &in.XForwardedClientCert, &out.XForwardedClientCert *out = new(XForwardedClientCert) diff --git a/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_clienttrafficpolicies.yaml b/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_clienttrafficpolicies.yaml index f0ce9e09977..bf0f80fab0f 100644 --- a/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_clienttrafficpolicies.yaml +++ b/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_clienttrafficpolicies.yaml @@ -142,6 +142,11 @@ spec: headers: description: HeaderSettings provides configuration for header management. properties: + disableRateLimitHeaders: + description: |- + DisableRateLimitHeaders configures Envoy Proxy to omit the "X-RateLimit-" response headers + when rate limiting is enabled. + type: boolean enableEnvoyHeaders: description: |- EnableEnvoyHeaders configures Envoy Proxy to add the "X-Envoy-" headers to requests diff --git a/internal/gatewayapi/clienttrafficpolicy.go b/internal/gatewayapi/clienttrafficpolicy.go index 18ed64edcea..95e3c3fa2f6 100644 --- a/internal/gatewayapi/clienttrafficpolicy.go +++ b/internal/gatewayapi/clienttrafficpolicy.go @@ -614,9 +614,10 @@ func translateListenerHeaderSettings(headerSettings *egv1a1.HeaderSettings, http return } httpIR.Headers = &ir.HeaderSettings{ - EnableEnvoyHeaders: ptr.Deref(headerSettings.EnableEnvoyHeaders, false), - WithUnderscoresAction: ir.WithUnderscoresAction(ptr.Deref(headerSettings.WithUnderscoresAction, egv1a1.WithUnderscoresActionRejectRequest)), - PreserveXRequestID: ptr.Deref(headerSettings.PreserveXRequestID, false), + EnableEnvoyHeaders: ptr.Deref(headerSettings.EnableEnvoyHeaders, false), + DisableRateLimitHeaders: ptr.Deref(headerSettings.DisableRateLimitHeaders, false), + WithUnderscoresAction: ir.WithUnderscoresAction(ptr.Deref(headerSettings.WithUnderscoresAction, egv1a1.WithUnderscoresActionRejectRequest)), + PreserveXRequestID: ptr.Deref(headerSettings.PreserveXRequestID, false), } if headerSettings.XForwardedClientCert != nil { diff --git a/internal/gatewayapi/testdata/clienttrafficpolicy-ratelimitheaders.in.yaml b/internal/gatewayapi/testdata/clienttrafficpolicy-ratelimitheaders.in.yaml new file mode 100644 index 00000000000..8dcf7385dcc --- /dev/null +++ b/internal/gatewayapi/testdata/clienttrafficpolicy-ratelimitheaders.in.yaml @@ -0,0 +1,35 @@ +clientTrafficPolicies: +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: ClientTrafficPolicy + metadata: + namespace: envoy-gateway + name: target-gateway-1 + spec: + headers: + enableEnvoyHeaders: true + disableRateLimitHeaders: true + targetRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-1 +gateways: +- apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + namespace: envoy-gateway + name: gateway-1 + spec: + gatewayClassName: envoy-gateway-class + listeners: + - name: http-1 + protocol: HTTP + port: 80 + allowedRoutes: + namespaces: + from: Same + - name: http-2 + protocol: HTTP + port: 8080 + allowedRoutes: + namespaces: + from: Same diff --git a/internal/gatewayapi/testdata/clienttrafficpolicy-ratelimitheaders.out.yaml b/internal/gatewayapi/testdata/clienttrafficpolicy-ratelimitheaders.out.yaml new file mode 100644 index 00000000000..4ad38ff0187 --- /dev/null +++ b/internal/gatewayapi/testdata/clienttrafficpolicy-ratelimitheaders.out.yaml @@ -0,0 +1,154 @@ +clientTrafficPolicies: +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: ClientTrafficPolicy + metadata: + creationTimestamp: null + name: target-gateway-1 + namespace: envoy-gateway + spec: + headers: + disableRateLimitHeaders: true + enableEnvoyHeaders: true + targetRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-1 + status: + ancestors: + - ancestorRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-1 + namespace: envoy-gateway + conditions: + - lastTransitionTime: null + message: Policy has been accepted. + reason: Accepted + status: "True" + type: Accepted + controllerName: gateway.envoyproxy.io/gatewayclass-controller +gateways: +- apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + creationTimestamp: null + name: gateway-1 + namespace: envoy-gateway + spec: + gatewayClassName: envoy-gateway-class + listeners: + - allowedRoutes: + namespaces: + from: Same + name: http-1 + port: 80 + protocol: HTTP + - allowedRoutes: + namespaces: + from: Same + name: http-2 + port: 8080 + protocol: HTTP + status: + listeners: + - attachedRoutes: 0 + conditions: + - lastTransitionTime: null + message: Sending translated listener configuration to the data plane + reason: Programmed + status: "True" + type: Programmed + - lastTransitionTime: null + message: Listener has been successfully translated + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Listener references have been resolved + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + name: http-1 + supportedKinds: + - group: gateway.networking.k8s.io + kind: HTTPRoute + - group: gateway.networking.k8s.io + kind: GRPCRoute + - attachedRoutes: 0 + conditions: + - lastTransitionTime: null + message: Sending translated listener configuration to the data plane + reason: Programmed + status: "True" + type: Programmed + - lastTransitionTime: null + message: Listener has been successfully translated + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Listener references have been resolved + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + name: http-2 + supportedKinds: + - group: gateway.networking.k8s.io + kind: HTTPRoute + - group: gateway.networking.k8s.io + kind: GRPCRoute +infraIR: + envoy-gateway/gateway-1: + proxy: + listeners: + - address: null + name: envoy-gateway/gateway-1/http-1 + ports: + - containerPort: 10080 + name: http-80 + protocol: HTTP + servicePort: 80 + - address: null + name: envoy-gateway/gateway-1/http-2 + ports: + - containerPort: 8080 + name: http-8080 + protocol: HTTP + servicePort: 8080 + metadata: + labels: + gateway.envoyproxy.io/owning-gateway-name: gateway-1 + gateway.envoyproxy.io/owning-gateway-namespace: envoy-gateway + name: envoy-gateway/gateway-1 +xdsIR: + envoy-gateway/gateway-1: + accessLog: + text: + - path: /dev/stdout + http: + - address: 0.0.0.0 + headers: + disableRateLimitHeaders: true + enableEnvoyHeaders: true + withUnderscoresAction: RejectRequest + hostnames: + - '*' + isHTTP2: false + name: envoy-gateway/gateway-1/http-1 + path: + escapedSlashesAction: UnescapeAndRedirect + mergeSlashes: true + port: 10080 + - address: 0.0.0.0 + headers: + disableRateLimitHeaders: true + enableEnvoyHeaders: true + withUnderscoresAction: RejectRequest + hostnames: + - '*' + isHTTP2: false + name: envoy-gateway/gateway-1/http-2 + path: + escapedSlashesAction: UnescapeAndRedirect + mergeSlashes: true + port: 8080 diff --git a/internal/ir/xds.go b/internal/ir/xds.go index 078f70b7246..25e7fd943b5 100644 --- a/internal/ir/xds.go +++ b/internal/ir/xds.go @@ -430,6 +430,11 @@ type HeaderSettings struct { // Refer to https://www.envoyproxy.io/docs/envoy/latest/api-v3/extensions/filters/http/router/v3/router.proto#extensions-filters-http-router-v3-router EnableEnvoyHeaders bool `json:"enableEnvoyHeaders,omitempty" yaml:"enableEnvoyHeaders,omitempty"` + // DisableRateLimitHeaders controls if "x-ratelimit-" headers are added by the HTTP Router filter. + // The default is to emit these headers. + // https://www.envoyproxy.io/docs/envoy/latest/api-v3/extensions/filters/http/ratelimit/v3/rate_limit.proto#extensions-filters-http-ratelimit-v3-ratelimit + DisableRateLimitHeaders bool `json:"disableRateLimitHeaders,omitempty" yaml:"disableRateLimitHeaders,omitempty"` + // Configure Envoy proxy how to handle the x-forwarded-client-cert (XFCC) HTTP header. // refer to https://www.envoyproxy.io/docs/envoy/latest/api-v3/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto#envoy-v3-api-enum-extensions-filters-network-http-connection-manager-v3-httpconnectionmanager-forwardclientcertdetails XForwardedClientCert *XForwardedClientCert `json:"xForwardedClientCert,omitempty" yaml:"xForwardedClientCert,omitempty"` diff --git a/internal/xds/translator/ratelimit.go b/internal/xds/translator/ratelimit.go index 86a558b291d..8e3e661f9d7 100644 --- a/internal/xds/translator/ratelimit.go +++ b/internal/xds/translator/ratelimit.go @@ -40,19 +40,6 @@ const ( rateLimitClientTLSCACertFilename = "/certs/ca.crt" ) -const ( - // Use `draft RFC Version 03 ` by default, - // where 3 headers will be added: - // * ``X-RateLimit-Limit`` - indicates the request-quota associated to the - // client in the current time-window followed by the description of the - // quota policy. The value is returned by the maximum tokens of the token bucket. - // * ``X-RateLimit-Remaining`` - indicates the remaining requests in the - // current time-window. The value is returned by the remaining tokens in the token bucket. - // * ``X-RateLimit-Reset`` - indicates the number of seconds until reset of - // the current time-window. The value is returned by the remaining fill interval of the token bucket. - xRateLimitHeadersRfcVersion = 1 -) - // patchHCMWithRateLimit builds and appends the Rate Limit Filter to the HTTP connection manager // if applicable and it does not already exist. func (t *Translator) patchHCMWithRateLimit(mgr *hcmv3.HttpConnectionManager, irListener *ir.HTTPListener) { @@ -111,12 +98,17 @@ func (t *Translator) buildRateLimitFilter(irListener *ir.HTTPListener) *hcmv3.Ht }, TransportApiVersion: corev3.ApiVersion_V3, }, - EnableXRatelimitHeaders: ratelimitfilterv3.RateLimit_XRateLimitHeadersRFCVersion(xRateLimitHeadersRfcVersion), } if t.GlobalRateLimit.Timeout > 0 { rateLimitFilterProto.Timeout = durationpb.New(t.GlobalRateLimit.Timeout) } + if irListener.Headers != nil && irListener.Headers.DisableRateLimitHeaders { + rateLimitFilterProto.EnableXRatelimitHeaders = ratelimitfilterv3.RateLimit_OFF + } else { + rateLimitFilterProto.EnableXRatelimitHeaders = ratelimitfilterv3.RateLimit_DRAFT_VERSION_03 + } + if t.GlobalRateLimit.FailClosed { rateLimitFilterProto.FailureModeDeny = t.GlobalRateLimit.FailClosed } diff --git a/internal/xds/translator/testdata/in/xds-ir/ratelimit-disable-headers.yaml b/internal/xds/translator/testdata/in/xds-ir/ratelimit-disable-headers.yaml new file mode 100644 index 00000000000..7c48e227ecc --- /dev/null +++ b/internal/xds/translator/testdata/in/xds-ir/ratelimit-disable-headers.yaml @@ -0,0 +1,69 @@ +http: + - name: "first-listener" + address: "0.0.0.0" + port: 10080 + hostnames: + - "*" + path: + mergeSlashes: true + escapedSlashesAction: UnescapeAndRedirect + headers: + disableRateLimitHeaders: true + routes: + - name: "first-route" + hostname: "*" + traffic: + rateLimit: + global: + rules: + - headerMatches: + - name: "x-user-id" + exact: "one" + limit: + requests: 5 + unit: second + pathMatch: + exact: "foo/bar" + destination: + name: "first-route-dest" + settings: + - endpoints: + - host: "1.2.3.4" + port: 50000 + - name: "second-route" + hostname: "*" + traffic: + rateLimit: + global: + rules: + - headerMatches: + - name: "x-user-id" + distinct: true + limit: + requests: 5 + unit: second + pathMatch: + exact: "example" + destination: + name: "second-route-dest" + settings: + - endpoints: + - host: "1.2.3.4" + port: 50000 + - name: "third-route" + hostname: "*" + traffic: + rateLimit: + global: + rules: + - limit: + requests: 5 + unit: second + pathMatch: + exact: "test" + destination: + name: "third-route-dest" + settings: + - endpoints: + - host: "1.2.3.4" + port: 50000 diff --git a/internal/xds/translator/testdata/out/xds-ir/ratelimit-disable-headers.clusters.yaml b/internal/xds/translator/testdata/out/xds-ir/ratelimit-disable-headers.clusters.yaml new file mode 100644 index 00000000000..9f9bc9f1dff --- /dev/null +++ b/internal/xds/translator/testdata/out/xds-ir/ratelimit-disable-headers.clusters.yaml @@ -0,0 +1,96 @@ +- circuitBreakers: + thresholds: + - maxRetries: 1024 + commonLbConfig: + localityWeightedLbConfig: {} + connectTimeout: 10s + dnsLookupFamily: V4_ONLY + edsClusterConfig: + edsConfig: + ads: {} + resourceApiVersion: V3 + serviceName: first-route-dest + lbPolicy: LEAST_REQUEST + name: first-route-dest + outlierDetection: {} + perConnectionBufferLimitBytes: 32768 + type: EDS +- circuitBreakers: + thresholds: + - maxRetries: 1024 + commonLbConfig: + localityWeightedLbConfig: {} + connectTimeout: 10s + dnsLookupFamily: V4_ONLY + edsClusterConfig: + edsConfig: + ads: {} + resourceApiVersion: V3 + serviceName: second-route-dest + lbPolicy: LEAST_REQUEST + name: second-route-dest + outlierDetection: {} + perConnectionBufferLimitBytes: 32768 + type: EDS +- circuitBreakers: + thresholds: + - maxRetries: 1024 + commonLbConfig: + localityWeightedLbConfig: {} + connectTimeout: 10s + dnsLookupFamily: V4_ONLY + edsClusterConfig: + edsConfig: + ads: {} + resourceApiVersion: V3 + serviceName: third-route-dest + lbPolicy: LEAST_REQUEST + name: third-route-dest + outlierDetection: {} + perConnectionBufferLimitBytes: 32768 + type: EDS +- circuitBreakers: + thresholds: + - maxRetries: 1024 + commonLbConfig: + localityWeightedLbConfig: {} + connectTimeout: 10s + dnsLookupFamily: V4_ONLY + dnsRefreshRate: 30s + lbPolicy: LEAST_REQUEST + loadAssignment: + clusterName: ratelimit_cluster + endpoints: + - lbEndpoints: + - endpoint: + address: + socketAddress: + address: envoy-ratelimit.envoy-gateway-system.svc.cluster.local + portValue: 8081 + loadBalancingWeight: 1 + loadBalancingWeight: 1 + locality: + region: ratelimit_cluster/backend/0 + name: ratelimit_cluster + outlierDetection: {} + perConnectionBufferLimitBytes: 32768 + respectDnsTtl: true + transportSocket: + name: envoy.transport_sockets.tls + typedConfig: + '@type': type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext + commonTlsContext: + tlsCertificates: + - certificateChain: + filename: /certs/tls.crt + privateKey: + filename: /certs/tls.key + validationContext: + trustedCa: + filename: /certs/ca.crt + type: STRICT_DNS + typedExtensionProtocolOptions: + envoy.extensions.upstreams.http.v3.HttpProtocolOptions: + '@type': type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions + explicitHttpConfig: + http2ProtocolOptions: {} diff --git a/internal/xds/translator/testdata/out/xds-ir/ratelimit-disable-headers.endpoints.yaml b/internal/xds/translator/testdata/out/xds-ir/ratelimit-disable-headers.endpoints.yaml new file mode 100644 index 00000000000..475b89a087c --- /dev/null +++ b/internal/xds/translator/testdata/out/xds-ir/ratelimit-disable-headers.endpoints.yaml @@ -0,0 +1,36 @@ +- clusterName: first-route-dest + endpoints: + - lbEndpoints: + - endpoint: + address: + socketAddress: + address: 1.2.3.4 + portValue: 50000 + loadBalancingWeight: 1 + loadBalancingWeight: 1 + locality: + region: first-route-dest/backend/0 +- clusterName: second-route-dest + endpoints: + - lbEndpoints: + - endpoint: + address: + socketAddress: + address: 1.2.3.4 + portValue: 50000 + loadBalancingWeight: 1 + loadBalancingWeight: 1 + locality: + region: second-route-dest/backend/0 +- clusterName: third-route-dest + endpoints: + - lbEndpoints: + - endpoint: + address: + socketAddress: + address: 1.2.3.4 + portValue: 50000 + loadBalancingWeight: 1 + loadBalancingWeight: 1 + locality: + region: third-route-dest/backend/0 diff --git a/internal/xds/translator/testdata/out/xds-ir/ratelimit-disable-headers.listeners.yaml b/internal/xds/translator/testdata/out/xds-ir/ratelimit-disable-headers.listeners.yaml new file mode 100644 index 00000000000..17684087bc9 --- /dev/null +++ b/internal/xds/translator/testdata/out/xds-ir/ratelimit-disable-headers.listeners.yaml @@ -0,0 +1,43 @@ +- address: + socketAddress: + address: 0.0.0.0 + portValue: 10080 + defaultFilterChain: + filters: + - name: envoy.filters.network.http_connection_manager + typedConfig: + '@type': type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager + commonHttpProtocolOptions: + headersWithUnderscoresAction: REJECT_REQUEST + http2ProtocolOptions: + initialConnectionWindowSize: 1048576 + initialStreamWindowSize: 65536 + maxConcurrentStreams: 100 + httpFilters: + - name: envoy.filters.http.ratelimit + typedConfig: + '@type': type.googleapis.com/envoy.extensions.filters.http.ratelimit.v3.RateLimit + domain: first-listener + rateLimitService: + grpcService: + envoyGrpc: + clusterName: ratelimit_cluster + transportApiVersion: V3 + - name: envoy.filters.http.router + typedConfig: + '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router + suppressEnvoyHeaders: true + mergeSlashes: true + normalizePath: true + pathWithEscapedSlashesAction: UNESCAPE_AND_REDIRECT + rds: + configSource: + ads: {} + resourceApiVersion: V3 + routeConfigName: first-listener + serverHeaderTransformation: PASS_THROUGH + statPrefix: http + useRemoteAddress: true + drainType: MODIFY_ONLY + name: first-listener + perConnectionBufferLimitBytes: 32768 diff --git a/internal/xds/translator/testdata/out/xds-ir/ratelimit-disable-headers.routes.yaml b/internal/xds/translator/testdata/out/xds-ir/ratelimit-disable-headers.routes.yaml new file mode 100644 index 00000000000..479c2cd143c --- /dev/null +++ b/internal/xds/translator/testdata/out/xds-ir/ratelimit-disable-headers.routes.yaml @@ -0,0 +1,57 @@ +- ignorePortInHostMatching: true + name: first-listener + virtualHosts: + - domains: + - '*' + name: first-listener/* + routes: + - match: + path: foo/bar + name: first-route + route: + cluster: first-route-dest + rateLimits: + - actions: + - genericKey: + descriptorKey: first-route + descriptorValue: first-route + - headerValueMatch: + descriptorKey: rule-0-match-0 + descriptorValue: rule-0-match-0 + expectMatch: true + headers: + - name: x-user-id + stringMatch: + exact: one + upgradeConfigs: + - upgradeType: websocket + - match: + path: example + name: second-route + route: + cluster: second-route-dest + rateLimits: + - actions: + - genericKey: + descriptorKey: second-route + descriptorValue: second-route + - requestHeaders: + descriptorKey: rule-0-match-0 + headerName: x-user-id + upgradeConfigs: + - upgradeType: websocket + - match: + path: test + name: third-route + route: + cluster: third-route-dest + rateLimits: + - actions: + - genericKey: + descriptorKey: third-route + descriptorValue: third-route + - genericKey: + descriptorKey: rule-0-match--1 + descriptorValue: rule-0-match--1 + upgradeConfigs: + - upgradeType: websocket diff --git a/site/content/en/latest/api/extension_types.md b/site/content/en/latest/api/extension_types.md index 0ba1f5677c6..3afc65348da 100644 --- a/site/content/en/latest/api/extension_types.md +++ b/site/content/en/latest/api/extension_types.md @@ -1900,6 +1900,7 @@ _Appears in:_ | Field | Type | Required | Description | | --- | --- | --- | --- | | `enableEnvoyHeaders` | _boolean_ | false | EnableEnvoyHeaders configures Envoy Proxy to add the "X-Envoy-" headers to requests
and responses. | +| `disableRateLimitHeaders` | _boolean_ | false | DisableRateLimitHeaders configures Envoy Proxy to omit the "X-RateLimit-" response headers
when rate limiting is enabled. | | `xForwardedClientCert` | _[XForwardedClientCert](#xforwardedclientcert)_ | false | XForwardedClientCert configures how Envoy Proxy handle the x-forwarded-client-cert (XFCC) HTTP header.

x-forwarded-client-cert (XFCC) is an HTTP header used to forward the certificate
information of part or all of the clients or proxies that a request has flowed through,
on its way from the client to the server.

Envoy proxy may choose to sanitize/append/forward the XFCC header before proxying the request.

If not set, the default behavior is sanitizing the XFCC header. | | `withUnderscoresAction` | _[WithUnderscoresAction](#withunderscoresaction)_ | false | WithUnderscoresAction configures the action to take when an HTTP header with underscores
is encountered. The default action is to reject the request. | | `preserveXRequestID` | _boolean_ | false | PreserveXRequestID configures Envoy to keep the X-Request-ID header if passed for a request that is edge
(Edge request is the request from external clients to front Envoy) and not reset it, which is the current Envoy behaviour.
It defaults to false. | diff --git a/test/e2e/testdata/ratelimit-headers-disabled.yaml b/test/e2e/testdata/ratelimit-headers-disabled.yaml new file mode 100644 index 00000000000..6f1e4e44774 --- /dev/null +++ b/test/e2e/testdata/ratelimit-headers-disabled.yaml @@ -0,0 +1,56 @@ +apiVersion: gateway.envoyproxy.io/v1alpha1 +kind: ClientTrafficPolicy +metadata: + name: ratelimit-headers-disabled-ctp + namespace: gateway-conformance-infra +spec: + headers: + disableRateLimitHeaders: true + targetRef: + group: gateway.networking.k8s.io + kind: Gateway + name: same-namespace +--- +apiVersion: gateway.envoyproxy.io/v1alpha1 +kind: BackendTrafficPolicy +metadata: + name: ratelimit-headers-disabled-btp + namespace: gateway-conformance-infra +spec: + targetRef: + group: gateway.networking.k8s.io + kind: HTTPRoute + name: ratelimit-headers-disabled + namespace: gateway-conformance-infra + rateLimit: + type: Global + global: + rules: + - clientSelectors: + - headers: + - name: x-user-id + type: Exact + value: one + - name: x-user-org + type: Exact + value: acme + limit: + requests: 3 + unit: Hour +--- +apiVersion: gateway.networking.k8s.io/v1 +kind: HTTPRoute +metadata: + name: ratelimit-headers-disabled + namespace: gateway-conformance-infra +spec: + parentRefs: + - name: same-namespace + rules: + - matches: + - path: + type: PathPrefix + value: /get + backendRefs: + - name: infra-backend-v1 + port: 8080 diff --git a/test/e2e/tests/ratelimit.go b/test/e2e/tests/ratelimit.go index 0115dbe0089..058c466171b 100644 --- a/test/e2e/tests/ratelimit.go +++ b/test/e2e/tests/ratelimit.go @@ -24,6 +24,7 @@ import ( func init() { ConformanceTests = append(ConformanceTests, RateLimitCIDRMatchTest) ConformanceTests = append(ConformanceTests, RateLimitHeaderMatchTest) + ConformanceTests = append(ConformanceTests, RateLimitHeadersDisabled) ConformanceTests = append(ConformanceTests, RateLimitBasedJwtClaimsTest) ConformanceTests = append(ConformanceTests, RateLimitMultipleListenersTest) } @@ -167,6 +168,94 @@ var RateLimitHeaderMatchTest = suite.ConformanceTest{ }, } +var RateLimitHeadersDisabled = suite.ConformanceTest{ + ShortName: "RateLimitHeadersDisabled", + Description: "Disable rate limit headers", + Manifests: []string{"testdata/ratelimit-headers-disabled.yaml"}, + Test: func(t *testing.T, suite *suite.ConformanceTestSuite) { + ns := "gateway-conformance-infra" + routeNN := types.NamespacedName{Name: "ratelimit-headers-disabled", Namespace: ns} + gwNN := types.NamespacedName{Name: "same-namespace", Namespace: ns} + gwAddr := kubernetes.GatewayAndHTTPRoutesMustBeAccepted(t, suite.Client, suite.TimeoutConfig, suite.ControllerName, kubernetes.NewGatewayRef(gwNN), routeNN) + + t.Run("all matched headers can get limited", func(t *testing.T) { + requestHeaders := map[string]string{ + "x-user-id": "one", + "x-user-org": "acme", + } + + ratelimitHeader := make(map[string]string) + expectOkResp := http.ExpectedResponse{ + Request: http.Request{ + Path: "/get", + Headers: requestHeaders, + }, + Response: http.Response{ + StatusCode: 200, + Headers: ratelimitHeader, + }, + Namespace: ns, + } + // expectOkResp.Response.Headers["X-Ratelimit-Limit"] is not defined because we disabled it. + expectOkReq := http.MakeRequest(t, &expectOkResp, gwAddr, "HTTP", "http") + + expectLimitResp := http.ExpectedResponse{ + Request: http.Request{ + Path: "/get", + Headers: requestHeaders, + }, + Response: http.Response{ + StatusCode: 429, + }, + Namespace: ns, + } + expectLimitReq := http.MakeRequest(t, &expectLimitResp, gwAddr, "HTTP", "http") + + // should just send exactly 4 requests, and expect 429 + + // keep sending requests till get 200 first, that will cost one 200 + http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, gwAddr, expectOkResp) + + // fire the rest of the requests + if err := GotExactExpectedResponse(t, 2, suite.RoundTripper, expectOkReq, expectOkResp); err != nil { + t.Errorf("failed to get expected response for the first three requests: %v", err) + } + if err := GotExactExpectedResponse(t, 1, suite.RoundTripper, expectLimitReq, expectLimitResp); err != nil { + t.Errorf("failed to get expected response for the last (fourth) request: %v", err) + } + }) + + t.Run("only one matched header cannot got limited", func(t *testing.T) { + requestHeaders := map[string]string{ + "x-user-id": "one", + } + + // it does not require any rate limit header, since this request never be rate limited. + expectOkResp := http.ExpectedResponse{ + Request: http.Request{ + Path: "/get", + Headers: requestHeaders, + }, + Response: http.Response{ + StatusCode: 200, + }, + Namespace: ns, + } + expectOkReq := http.MakeRequest(t, &expectOkResp, gwAddr, "HTTP", "http") + + // send exactly 4 requests, and still expect 200 + + // keep sending requests till get 200 first, that will cost one 200 + http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, gwAddr, expectOkResp) + + // fire the rest of the requests + if err := GotExactExpectedResponse(t, 3, suite.RoundTripper, expectOkReq, expectOkResp); err != nil { + t.Errorf("failed to get expected responses for the request: %v", err) + } + }) + }, +} + var RateLimitBasedJwtClaimsTest = suite.ConformanceTest{ ShortName: "RateLimitBasedJwtClaims", Description: "Limit based jwt claims",