gateway icon indicating copy to clipboard operation
gateway copied to clipboard

Ratelimiting doesn't work with both clientSelectors (i.e. headers and sourceCIDR) enabled

Open yash-nisar opened this issue 1 year ago • 3 comments

Description:

What issue is being seen? Describe what should be happening instead of the bug, for example: Envoy should not crash, the expected value isn't returned, etc. Ratelimiting doesn't work with both clientSelectors (i.e. headers and sourceCIDR) enabled. I would expect it to work if the headers match and the request is from the IP in the sourceCIDR range.

Repro steps:

Include sample requests, environment, etc. All data and inputs required to reproduce the bug. Followed all steps from quickstart. This is my ratelimit config:

rateLimit:
    global:
      rules:
      - clientSelectors:
        - headers:
          - name: x-user-id
            value: one
          sourceCIDR:
            type: Exact
            value: 0.0.0.0/0
        limit:
          requests: 3
          unit: Hour
    type: Global

Note: If there are privacy concerns, sanitize the data prior to sharing.

Environment:

Include the environment like gateway version, envoy version and so on.

Logs:

Include the access logs and the Envoy logs.

These are the envoy-ratelimit logs, note this line descriptor does not match any limit, no limits applied.

time="2024-09-27T00:56:04Z" level=debug msg="CertProvider: waiting for runtime update"
time="2024-09-27T00:56:04Z" level=info msg="Starting xDS client connection for rate limit configurations"
time="2024-09-27T00:56:04Z" level=info msg="Dialing xDS Management Server: 'envoy-gateway:18001'"
time="2024-09-27T00:56:04Z" level=warning msg="connecting to redis on redis.redis-system.svc.cluster.local:6379 with pool size 10"
time="2024-09-27T00:56:04Z" level=debug msg="Implicit pipelining enabled: false"
time="2024-09-27T00:56:04Z" level=warning msg="Listening for debug on '0.0.0.0:6070'"
time="2024-09-27T00:56:04Z" level=warning msg="Listening for gRPC on '0.0.0.0:8081'"
time="2024-09-27T00:56:04Z" level=debug msg="Waiting for config update event"
time="2024-09-27T00:56:04Z" level=warning msg="Listening for HTTP on '0.0.0.0:8080'"
time="2024-09-27T00:56:04Z" level=info msg="Connection to xDS Management Server is successful"
time="2024-09-27T00:56:04Z" level=debug msg="loading domain: default/eg/http"
time="2024-09-27T00:56:04Z" level=debug msg="loading descriptor: key=default/eg/http.httproute/default/http-ratelimit/rule/0/match/0/ratelimit_example_httproute/default/http-ratelimit/rule/0/match/0/ratelimit_example"
time="2024-09-27T00:56:04Z" level=debug msg="Creating stats for key: 'default/eg/http.httproute/default/http-ratelimit/rule/0/match/0/ratelimit_example_httproute/default/http-ratelimit/rule/0/match/0/ratelimit_example.masked_remote_address_0.0.0.0/0'"
time="2024-09-27T00:56:04Z" level=debug msg="loading descriptor: key=default/eg/http.httproute/default/http-ratelimit/rule/0/match/0/ratelimit_example_httproute/default/http-ratelimit/rule/0/match/0/ratelimit_example.masked_remote_address_0.0.0.0/0 ratelimit={requests_per_unit=3, unit=HOUR, unlimited=false, shadow_mode=false}"
time="2024-09-27T00:56:04Z" level=debug msg="Setting config retrieved from config provider"
time="2024-09-27T00:56:04Z" level=info msg="Successfully loaded new configuration"
time="2024-09-27T00:56:04Z" level=debug msg="Waiting for config update event"
time="2024-09-27T00:56:39Z" level=debug msg="got descriptor: (httproute/default/http-ratelimit/rule/0/match/0/ratelimit_example=httproute/default/http-ratelimit/rule/0/match/0/ratelimit_example),(rule-0-match-0=rule-0-match-0),(masked_remote_address=0.0.0.0/0)"
time="2024-09-27T00:56:39Z" level=debug msg="starting get limit lookup"
time="2024-09-27T00:56:39Z" level=debug msg="looking up key: httproute/default/http-ratelimit/rule/0/match/0/ratelimit_example_httproute/default/http-ratelimit/rule/0/match/0/ratelimit_example"
time="2024-09-27T00:56:39Z" level=debug msg="iterating to next level"
time="2024-09-27T00:56:39Z" level=debug msg="looking up key: rule-0-match-0_rule-0-match-0"
time="2024-09-27T00:56:39Z" level=debug msg="looking up key: rule-0-match-0"
time="2024-09-27T00:56:39Z" level=debug msg="descriptor does not match any limit, no limits applied"
time="2024-09-27T00:56:39Z" level=debug msg="starting cache lookup"
time="2024-09-27T00:56:39Z" level=debug msg="returning normal response"

I do see that the config is reflected in xds-ir (in gateway logs):

2024-09-27T00:54:27.139Z        INFO    gateway-api     runner/runner.go:186    {
        "accessLog": {
                "text": [
                        {
                                "path": "/dev/stdout"
                        }
                ]
        },
        "http": [
                {
                        "name": "default/eg/http",
                        "address": "0.0.0.0",
                        "port": 10080,
                        "metadata": {
                                "kind": "Gateway",
                                "name": "eg",
                                "namespace": "default",
                                "sectionName": "http"
                        },
                        "hostnames": [
                                "*"
                        ],
                        "routes": [
                                {
                                        "name": "httproute/default/backend/rule/0/match/0/www_example_com",
                                        "hostname": "www.example.com",
                                        "isHTTP2": false,
                                        "pathMatch": {
                                                "name": "",
                                                "prefix": "/",
                                                "distinct": false
                                        },
                                        "destination": {
                                                "name": "httproute/default/backend/rule/0",
                                                "settings": [
                                                        {
                                                                "weight": 1,
                                                                "protocol": "HTTP",
                                                                "endpoints": [
                                                                        {
                                                                                "host": "10.244.0.10",
                                                                                "port": 3000
                                                                        },
                                                                        {
                                                                                "host": "10.244.0.10",
                                                                                "port": 3000
                                                                        }
                                                                ],
                                                                "addressType": "IP"
                                                        }
                                                ]
                                        },
                                        "metadata": {
                                                "kind": "HTTPRoute",
                                                "name": "backend",
                                                "namespace": "default"
                                        }
                                },
                                {
                                        "name": "httproute/default/http-ratelimit/rule/0/match/0/ratelimit_example",
                                        "hostname": "ratelimit.example",
                                        "isHTTP2": false,
                                        "pathMatch": {
                                                "name": "",
                                                "prefix": "/",
                                                "distinct": false
                                        },
                                        "destination": {
                                                "name": "httproute/default/http-ratelimit/rule/0",
                                                "settings": [
                                                        {
                                                                "weight": 1,
                                                                "protocol": "HTTP",
                                                                "endpoints": [
                                                                        {
                                                                                "host": "10.244.0.10",
                                                                                "port": 3000
                                                                        },
                                                                        {
                                                                                "host": "10.244.0.10",
                                                                                "port": 3000
                                                                        }
                                                                ],
                                                                "addressType": "IP"
                                                        }
                                                ]
                                        },
                                        "traffic": {
                                                "rateLimit": {
                                                        "global": {
                                                                "rules": [
                                                                        {
                                                                                "headerMatches": [
                                                                                        {
                                                                                                "name": "x-user-id",
                                                                                                "exact": "one",
                                                                                                "distinct": false
                                                                                        }
                                                                                ],
                                                                                "cidrMatch": {
                                                                                        "cidr": "0.0.0.0/0",
                                                                                        "ip": "0.0.0.0",
                                                                                        "maskLen": 0,
                                                                                        "isIPv6": false,
                                                                                        "distinct": false
                                                                                },
                                                                                "limit": {
                                                                                        "requests": 3,
                                                                                        "unit": "Hour"
                                                                                }
                                                                        }
                                                                ]
                                                        }
                                                }
                                        },
                                        "metadata": {
                                                "kind": "HTTPRoute",
                                                "name": "http-ratelimit",
                                                "namespace": "default"
                                        }
                                }
                        ],
                        "isHTTP2": false,
                        "path": {
                                "mergeSlashes": true,
                                "escapedSlashesAction": "UnescapeAndRedirect"
                        }
                }
        ]
}       {"runner": "gateway-api", "xds-ir": "default/eg"}

Thanks !

yash-nisar avatar Sep 27 '24 15:09 yash-nisar

Looks weird, can you share the output of egctl config rl -n envoy-gateway-system ?

shawnh2 avatar Sep 28 '24 02:09 shawnh2

Looks weird, can you share the output of egctl config rl -n envoy-gateway-system ?

@shawnh2 Sure, here's the output:

default/eg/http.httproute/default/http-ratelimit/rule/0/match/0/ratelimit_example_httproute/default/http-ratelimit/rule/0/match/0/ratelimit_example.masked_remote_address_0.0.0.0/0: unit=HOUR requests_per_unit=3, shadow_mode: false

I think there is a bug in this method: buildRateLimitServiceDescriptors https://github.com/envoyproxy/gateway/blob/cbd92e2a423de036d5c8b9db530f0a66d5ce47f9/internal/xds/translator/ratelimit.go#L329

It does not consider the case where both the clientSelectors are present.

I made this change to make it work for the time being:

if rule.CIDRMatch != nil {
	// MaskedRemoteAddress case
	pbDesc := new(rlsconfv3.RateLimitDescriptor)
	pbDesc.Key = "masked_remote_address"
	pbDesc.Value = rule.CIDRMatch.CIDR
	rateLimit := rlsconfv3.RateLimitPolicy{
		RequestsPerUnit: uint32(rule.Limit.Requests),
		Unit:            rlsconfv3.RateLimitUnit(rlsconfv3.RateLimitUnit_value[strings.ToUpper(string(rule.Limit.Unit))]),
	}

	if rule.CIDRMatch.Distinct {
		pbDesc.Descriptors = []*rlsconfv3.RateLimitDescriptor{
			{
				Key:       "remote_address",
				RateLimit: &rateLimit,
			},
		}
	} else {
		pbDesc.RateLimit = &rateLimit
	}

	if len(rule.HeaderMatches) != 0 {
		cur.Descriptors = []*rlsconfv3.RateLimitDescriptor{pbDesc}
	} else {
		head = pbDesc
		cur = head
	}
}
for mIdx, match := range rule.HeaderMatches {
	pbDesc := new(rlsconfv3.RateLimitDescriptor)
	// Case for distinct match
	if match.Distinct {
		// RequestHeader case
		pbDesc.Key = getRouteRuleDescriptor(rIdx, mIdx)
	} else {
		// HeaderValueMatch case
		pbDesc.Key = getRouteRuleDescriptor(rIdx, mIdx)
		pbDesc.Value = getRouteRuleDescriptor(rIdx, mIdx)
	}

	// Add the ratelimit values to the last descriptor
	if mIdx == len(rule.HeaderMatches)-1 && rule.CIDRMatch == nil {
		rateLimit := rlsconfv3.RateLimitPolicy{
			RequestsPerUnit: uint32(rule.Limit.Requests),
			Unit:            rlsconfv3.RateLimitUnit(rlsconfv3.RateLimitUnit_value[strings.ToUpper(string(rule.Limit.Unit))]),
		}
		pbDesc.RateLimit = &rateLimit
	}

	if mIdx == 0 {
		head = pbDesc
	} else {
		cur.Descriptors = []*rlsconfv3.RateLimitDescriptor{pbDesc}
	}

	cur = pbDesc
}

yash-nisar avatar Sep 29 '24 01:09 yash-nisar

Thanks for reporting this, it is indeed a bug. Though we have created RL actions for both Headers & CIDR correctly,

https://github.com/envoyproxy/gateway/blob/cbd92e2a423de036d5c8b9db530f0a66d5ce47f9/internal/xds/translator/ratelimit.go#L142

but doing wrong when building RL Descriptors.

shawnh2 avatar Sep 30 '24 13:09 shawnh2