{{announcement.body}}
{{announcement.title}}

Getting Started With Advanced Rate-Limiting on Enroute Universal Gateway Built on Envoy Proxy

DZone 's Guide to

Getting Started With Advanced Rate-Limiting on Enroute Universal Gateway Built on Envoy Proxy

This article shows how Enroute can be deployed in two different topologies to add rate-limiting to APIs.

· Microservices Zone ·
Free Resource

stoplight

Why Rate-Limit?

APIs drive the digital economy. They are fundamental to today’s commerce. Rate limiting of APIs is used to ensure availability and prevent abuse.

While ensuring high availability, APIs need to meet SLAs defined for users. To meet these SLAs, checks are necessary so that one user does not overwhelm the system.

Every system or API has different uses and throughput. Also, every API may provide separate limits for authenticated and unauthenticated users.

The rate limit is very fundamental to APIs. Every API needs some form of rate-limiting.

What Is Enroute Universal Gateway

Enroute Universal API gateway is a polymorphic gateway that allows flexible policy enforcement for APIs. It can work as a Standalone Gateway for traditional brownfield use-cases, at Kubernetes ingress or can be run alongside a service for mesh-like deployments. Such flexibility is critical for organizations adopting cloud or rewriting services or parts of it to run under Kubernetes.

What This Article Covers

This article shows how Enroute can be deployed in two different topologies to add rate-limiting to APIs.

One topology is running it at Kubernetes Ingress. Enroute Universal Gateway deployed as Kubernetes ingress controller can be configured for advanced rate-limiting.

Saaras kubernetes

Another topology is running it as a Standalone Gateway that does not depend on Kubernetes. It can be configured in any environment or cloud to run independently of Kubernetes. We program the Enroute Universal Gateway in Standalone mode for advanced rate-limiting.

saaras standalone

Enroute also supports several other topologies including a Gateway Mesh topology. One Enroute control plane can program multiple stateless Enroute data planes. Every Enroute data plane instance runs an instance of Envoy along with it. This topology is not covered in this article.

Enroute Universal Gateway uses filters to attach additional functionality either at the global level or per-route level. We show how the same filter configuration can be applied across all topologies in which Enroute is run.

Enroute Quick Start — Get Running in Less Than a Minute

This section demonstrates how to quickly set up and run advanced real-life rate-limiting on Enroute. The rest of the article describes in more detail how this is achieved.

Quick Start: Standalone

The following steps cover how you can run Enroute in standalone mode for advanced rate-limiting. 

Start Enroute Standalone API Gateway

Java
 






 

Setup Listener, Rate-Limit Filter, Upstream

The quick start is a golang script that invokes Standalone Gateway APIs to program it. The quick-start is written using golang and needs golang installed. The script was tested on golang version 1.13.9

Download quick start script:

Java
 




xxxxxxxxxx
1


 
1
curl -O https://raw.githubusercontent.com/saarasio/api-ratelimit/master/api-rate-limit.go



Run quick start script to create configuration:

Java
 







To check configuration, use the following command:

Java
 




xxxxxxxxxx
1


1
go run api-rate-limit.go --op=show



To clear configuration, use the following command:

Java
 




xxxxxxxxxx
1


1
go run api-rate-limit.go --op=delete



Run Traffic

The Lua script in quick-start extracts the API-key either from the header x-app-key or API parameter api-key. If none of them are present, the request gets rate-limited.

To run traffic, use any of the following curl commands:

API Key passed as a query param:

Java
 




xxxxxxxxxx
1


1
curl -vvv http://localhost:8080/?api-key=query-param-saaras



The API key passed as a header:

Java
 




xxxxxxxxxx
1


1
curl -vvv -H "x-app-key: hdr-app-saaras" http://localhost:8080/



The following curl command results in a 429

Java
 




xxxxxxxxxx
1


1
curl -vvv http://localhost:8080/



When the API key is not found, it is set to x-app-notfound in the Lua script. This results in descriptor match where the rate limit of requests_per_unit is set to zero

JSON
 




xxxxxxxxxx
1
14


 
1
...
2
{
3
     "key": "x-app-key",
4
     "value" : "x-app-notfound",
5
 
          
6
     "descriptors": [
7
         {
8
             "key" : "remote_address",
9
             "rate_limit": {
10
                 "unit": "second",
11
                 "requests_per_unit": 0
12
             }
13
         }
14
     ]
15
 }
16
...



Quick Start: Kubernetes Ingress

Follow the steps below to run Enroute Universal Gateway at Kubernetes Ingress

Start Enroute Kubernetes Ingress

This example assumes that you have a working Kubernetes cluster that supports service of type LoadBalancer

The steps can also be run using the following command:

Java
 




xxxxxxxxxx
1


1
bash -c 'kubectl apply -f https://getenroute.io/ic/v0.3.0/qs/ic-all-rl.yaml'



This sets up a LoadBalancer type of service that provides a public DNS name to access it on AWS. Note that to make the example work, we set a CNAME record to point to the AWS domain. This was done to match the programmed Fqdn.

Refer to Enroute Universal API Gateway for Kubernetes ingress to get a step-by-step introduction on running Enroute Kubernetes Ingress API gateway

Run Traffic

Get the LoadBalancer IP

Shell
 







Note that we have a CNAME mapping from demo.saaras.io to

Java
 




xxxxxxxxxx
1


 
1
curl -vvv -k https://demo.saaras.io/get



Common Rate-Limiting Configuration

A lot of APIs use some form of rate-limiting to prevent abuse and provide fairness. This needs to identify the user who is calling the API. One way to identify a user is through a unique IP address. A better way to identify a user is a token that the user receives after they have authenticated.

This user information is either received in a header or as a URL parameter. Rate-limiting then uses this information to count the number of API requests made and limits the user according to the policy configured.

We delve into some of the examples of how rate-limiting is used today and how Enroute Universal Gateway can be used to recreate such configuration. This how-to only covers one configuration for rate-limiting. Also, check why every API needs rate-limiting to go through other real-world examples.

Mapping Rate-Limiting Requirements to Enroute Directives

We look at real-world examples specified in why every API needs rate-limiting and use it as an example.

The following steps are involved in recreating the real-world example:

  • Extract user-token from the request, send it as a header value for rate-limit engine to read
  • Add route action to send request state to rate-limit engine when a request matches that route
  • Add rate-limit engine descriptors to specify limits on user

Lua

The real-world examples use the token received in the request to rate-limit and use different rate limits — one for authenticated users and another set of limits for un-authenticated users.

The Lua global level filter gets invoked first. We use it to fish out the token for an authenticated user. We then use this token for our rate-limiting calculations.

A route level filter lets us specify what state needs to be sent to the rate-limit engine. When the request matches this route, the corresponding state is sent to the rate-limit engine.

The configuration on the rate-limit engine specified in terms of descriptors describes the rate-limits for authenticated and unauthenticated users. We provide configuration to the rate-limit engine using Enroute’s GlobalConfig.

The next two sections show the individual filter configuration and walk through the configuration.

Lua Filter Configuration

The global Lua filter tries to extract the token or API-key sent by the user. This is either sent as a query parameter or as a header. The Lua filter checks both these locations.

If the Lua filter finds the token in any of the locations, it sets it on a header that is used by the rate-limit engine. If it cannot find this token, it sets it to a pre-determined value that can be interpreted accordingly by the rate-limit engine.

Note that the same Lua code is attached as Lua filter configuration when running Enroute as Kubernetes Ingress or running it as a Standalone Gateway.

YAML
 




xxxxxxxxxx
1
76


1
---
2
 apiVersion: enroute.saaras.io/v1beta1
3
 kind: HttpFilter
4
 metadata:
5
   labels:
6
     app: httpbin
7
   name: luatestfilter
8
   namespace: enroute-gw-k8s
9
 spec:
10
   name: luatestfilter
11
   type: http_filter_lua
12
   httpFilterConfig:
13
     config: |
14
         function get_api_key(path, q_param_name)
15
             -- path = "/?api-key=valid-key"
16
             s, e = string.find(path, "?")
17
             if s ~= nil then
18
               for pre, q_params in string.gmatch(path, "(%S+)?(%S+)") do
19
                 -- print(pre, q_params, path, s, e)
20
                 for k, v in string.gmatch(q_params, "(%S+)=(%S+)") do
21
                   print(k, v)
22
                   if k == q_param_name then
23
                     return v
24
                   end
25
                 end
26
               end
27
             end
28
 
          
29
             return nil
30
         end
31
 
          
32
         function envoy_on_request(request_handle)
33
            request_handle:logInfo("Begin: envoy_on_request()");
34
 
          
35
            hdr_x_app_key = "x-app-key"
36
            hdr_x_app_not_found = "x-app-notfound"
37
            q_param_name = "api-key"
38
 
          
39
            -- extract API key from header "x-app-key"
40
            headers = request_handle:headers()
41
            header_value = headers:get(hdr_x_app_key)
42
 
          
43
            if header_value ~= nil then
44
              request_handle:logInfo("envoy_on_request() API Key from header "..header_value);
45
            else
46
              request_handle:logInfo("envoy_on_request() API Key in header is nil");
47
            end
48
 
          
49
            -- extract API key from query param "api-key"
50
            path_in = headers:get(":path")
51
            api_key = get_api_key(path_in, q_param_name)
52
 
          
53
            if api_key ~= nil then
54
              request_handle:logInfo("envoy_on_request() API Key from query param "..api_key);
55
            else
56
              request_handle:logInfo("envoy_on_request() API Key from query param is nil");
57
            end
58
 
          
59
            -- If API key found, do nothing
60
            -- else set header x-app-key:x-app-notfound
61
            if header_value == nil then
62
                if api_key == nil then
63
                  headers:add(hdr_x_app_key, hdr_x_app_not_found)
64
                else
65
                  headers:add(hdr_x_app_key, api_key)
66
                end
67
            end
68
 
          
69
            request_handle:logInfo("End: envoy_on_request()");
70
 
          
71
         end
72
 
          
73
         function envoy_on_response(response_handle)
74
            response_handle:logInfo("Begin: envoy_on_response()");
75
            response_handle:logInfo("End: envoy_on_response()");
76
         end



The same Lua script can also be programmed on the standalone gateway as demonstrated in the quick-start script here

Java
 




xxxxxxxxxx
1


 
1
https://raw.githubusercontent.com/saarasio/api-ratelimit/master/api-rate-limit.go



For every request, the function envoy_on_request(request_handler) gets invoked. This is the entry point. We look for the user token or API key first in a header and then in the query parameter. If we cannot find it, we set it to x-app-notfound to indicate it wasn’t found. This information helps the rate-limit engine decide how it should treat the request.

Rate-Limit Filter Configuration

To rate-limit requests, the rate-limit engine needs state from the request to identify the request/user. This information is specified at the route level.

Per-Route Filter Config

YAML
 




xxxxxxxxxx
1
26


1
---
2
apiVersion: enroute.saaras.io/v1beta1
3
kind: RouteFilter
4
metadata:
5
  labels:
6
    app: httpbin
7
  name: rl2
8
  namespace: enroute-gw-k8s
9
spec:
10
  name: rl2
11
  type: route_filter_ratelimit
12
  routeFilterConfig:
13
    config: |
14
        {
15
            "descriptors": [
16
              {
17
                "request_headers": {
18
                  "header_name": "x-app-key",
19
                  "descriptor_key": "x-app-key"
20
                }
21
              },
22
              {
23
                "remote_address": "{}"
24
              }
25
            ]
26
        }



The above is configuration is for k8s ingress attached to a route. It sends the value of x-app-key to the rate-limit engine.

The same configuration can also be used for a filter as specified in the quick-start script. Alternatively, it can also be specified using a curl command —

Shell
 




xxxxxxxxxx
1
26


 
1
# Create rate-limit filter for route
2
curl -0 -v http://localhost:1323/filter \
3
-H "Host: localhost:1323" \
4
-H "Content-Type: application/json" \
5
-H "Accept-Encoding: gzip" \
6
-H "Expect:" \
7
-H 'Content-Type: application/json; charset=utf-8' \
8
-d @- <<'EOF'
9
    {
10
       "Filter_name" : "test_filter_rl",
11
       "Filter_type" : "route_filter_ratelimit",
12
       "Filter_config" : "
13
   {
14
     \"descriptors\" :
15
     [
16
       {
17
         \"generic_key\":
18
         {
19
           \"descriptor_value\":\"default\"
20
         }
21
       }
22
     ]
23
   }
24
"
25
}
26
EOF



Once the rate-limit engine receives this information, it does the counting to allow/disallow the request. It uses Redis in the backend to do the counting. The configuration to the rate-limit engine can be specified using globalconfig.

Rate-Limit Engine Config

YAML
 




xxxxxxxxxx
1
43


1
---
2
apiVersion: enroute.saaras.io/v1beta1
3
kind: GlobalConfig
4
metadata:
5
  labels:
6
    app: httpbin
7
  name: rl-global-config
8
  namespace: enroute-gw-k8s
9
spec:
10
  name: rl-global-config
11
  type: globalconfig_ratelimit
12
  config: |
13
        {
14
            "domain": "enroute",
15
            "descriptors": [
16
                {
17
                    "key": "x-app-key",
18
                    "value" : "x-app-notfound",
19
 
          
20
                    "descriptors": [
21
                        {
22
                            "key" : "remote_address",
23
                            "rate_limit": {
24
                                "unit": "second",
25
                                "requests_per_unit": 0
26
                            }
27
                        }
28
                    ]
29
                },
30
                {
31
                    "key": "x-app-key",
32
                    "descriptors": [
33
                        {
34
                            "key" : "remote_address",
35
                            "rate_limit": {
36
                                "unit": "second",
37
                                "requests_per_unit": 100000
38
                            }
39
                        }
40
                    ]
41
                }
42
            ]
43
        }



The rate-limit engine uses the descriptors to build a token to count the request. In the above case, it’ll use a rate-limit "requests_per_unit": 0 for requests when a token isn’t found.

When a token is found, it uses the "requests_per_unit": 100000 for every unique token.

The same configuration can also be found in the quick start script.

Conclusion

We show how API rate-limiting is critical for APIs today and how they can be programmed on the Enroute Universal Gateway. Depending on where the API is running, the standalone gateway or the Kubernetes Ingress API gateway can be used.

Advanced rate-limiting is a free feature and runs without any special licensing on Enroute Universal API gateway. Rate-limiting is fundamental to running APIs and is provided completely free in the community edition (while other vendors charge for it).

Enroute provides a complete rate-limiting solution for APIs with centralized control for all aspects of rate-limiting. With Enroute’s flexibility, they need for rate-limiting for APIs can be achieved in any environment — private cloud, public cloud, Kubernetes ingress or gateway mesh.

You can use the contact form to reach us if you have any feedback.

Topics:
api gateway, apis, envoy proxy, infrastructure, ingress controller, kubernetes, mesh, microservices, rate limiting, scale out

Published at DZone with permission of Chintan Thakker . See the original article here.

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}