Blog
Effective api service ddos protection with cloudflare

Effective api service ddos protection with cloudflare

Daniel Yavorovych
November 9, 2020

Problem

Today, applications are often split into front-end applications and back-end API.

The front-end is usually stored in Object Storage (such as AWS S3 or Google Cloud Storage), and a CDN is configured in front of it (such as CloudFront or the same CloudFlare). This scheme has proven itself well, and with DDoS such a resource is ineffective. Even if DDoS is really powerful, CloudFlare does a great job at filtering out unwanted traffic, prompting users to recaptcha as a last resort.

But when it comes to API services, things are worse. Even after passing a js challenge in a browser, a valid user cannot always easily access the API service behind CloudFlare in the Under attack mode.

If we talk about APIs that are used by other services that do not have interactive work (various bots, custom services, etc.), then enabling the Under attack mode for an API may be tantamount to blocking access to it.

What to do in this case?

CloudFlare itself has an article on the support resource. It recommends simply lowering the Security Level to the API endpoints, turning off the Always online mode, turning off Caching, and turning off Browser Integrity Check.

Of course, this will help get rid of the problem of unwanted API blocking, but it will reduce the level of protection so much that your API backend may become overloaded and even denied service.

In this article, we will look at two ways that will allow you to filter 100% of unwanted traffic and completely eliminate false positives and the blocking of valid user requests.

Idea

As the title suggests, we transfer the primary validation of requests to the CloudFlare side, passing only the API keys registered in your application.

This method is only suitable for private APIs (which are the most popular). If you are using a public API where anyone can make a request to your service, then you should be prepared for an influx of unwanted traffic. This practice over recent years shows that even public APIs are switching to the access model by token, which can only be obtained after registration. This allows you to better control the process of provided services and, at the same time, configure protection against attacks.

Implementation

CloudFlare has Serverless support - Cloudflare Workers. This is a truly powerful and flexible tool that also has a very low price (from 0 to $5/mo, no traffic overrun). More recently, Cloudflare has an additional service - key-value storage, which allows you to access it directly from CloudFlare workers.

These two technologies will help us implement our primary filter:

Effective API service DDOS protection with CloudFlare

As we can see in the diagram above, we use Cloudflare Workers in order to check for the presence of an API key in the Key-Value database. If the key is not present in the request - or it is not in the KV-store –the client will receive an HTTP 403 Status. If the key is specified correctly, the request will be proxied to the backend, as usual.

Special attention should be paid to the API-keys sync script. This is a script that must keep the list of keys in Cloudflare KV up to date, meaning that as soon as a key is added in your application or blocked/removed, its state should be reflected in Cloudflare KV as soon as possible.

Ideally, this process takes place in realtime.

Step-by-step instructions

  • Log into the Cloudflare dashboard
  • Select the domain
  • Go to Workers
  • Click Manage Workers
  • Go to tab KV and create a new namespace (with name tokens, for example)
  • Back to the Worker tab and click Create a Worker
  • Enter the next code and click Save and Deploy

Code:


async function handleRequest(request) {
    const {searchParams} = new URL(request.url)
    const key = searchParams.get(API_KEY_NAME)
    const isKeyPresent = await TOKENS_KV.get(key)

if (!key || !isKeyPresent) {
        const data = {
            "status": "REQUEST_DENIED",
            "error_message": "Access denied."
        }
        const json = JSON.stringify(data, null, 2)
        
        return new Response(json, {
            status: 403,
            headers: {
                "content-type": "application/json;charset=UTF-8"
            }
        })
    }
    return await fetch(request)
}

addEventListener("fetch", event => {
        event.respondWith(handleRequest(event.request))
})
  • Go to the Settings tab of the new worker page
  • In the section Environment Variables, add env variable API_KEY_NAME, with your API key name as Value
  • In the section KV Namespace Binding, create env variable TOKENS_KV, and in Value, please select create KV namespace. Finally, please click Save to save changes.
  • Now it's time to sync your app's API keys with Cloudflare KV. This can be done using the Cloudflare API. I will provide a sample Python code that the official Cloudflare client uses.
  • CF_ACCOUNT_ID you can get using documentation
  • CF_API_TOKEN also described in doc
  • CF_KV_NAMESPACE_ID - this is ID of KV namespace tokens, available on KV settings page

Code:


import logging
import CloudFlare‍

class CF:
    CF_ACCOUNT_ID = ''
    CF_API_TOKEN = ''
    CF_KV_NAMESPACE_ID = ''‍
    
    def __init__(self):
        self.cf = CloudFlare.CloudFlare(token=self.CF_API_TOKEN)‍
    
    def get_active_tokens_list():
        """
        Need define
        """
        return []
    
    def get_tokens(self):
        return [i['name'] for i in
                self.cf.accounts.storage.kv.namespaces.keys.get(self.CF_ACCOUNT_ID, self.CF_KV_NAMESPACE_ID)]
    def create_tokens(self, tokens):
        data = [{"key": token, "value": "enabled"} for token in tokens]
        self.cf.accounts.storage.kv.namespaces.bulk.put(self.CF_ACCOUNT_ID, self.CF_KV_NAMESPACE_ID, data=data)‍
    
    def delete_tokens(self, tokens):
        self.cf.accounts.storage.kv.namespaces.bulk.delete(self.CF_ACCOUNT_ID, self.CF_KV_NAMESPACE_ID,
                                                            data=list(tokens))‍
    def update_tokens(self):
        old_tokens = set(self.get_tokens())
        new_tokens = set(self.get_active_tokens_list())
        to_delete_tokens = old_tokens - new_tokens‍
        
        self.delete_tokens(to_delete_tokens)
        self.create_tokens(new_tokens)‍
        
        logging.info('CF KV tokens update status: {old} old / {new} new / {deleted} deleted'.format(old=len(old_tokens),
                                                                                                    new=len(new_tokens),
                                                                                                    deleted=len(to_delete_tokens)))

if __name__ == '__main__':
    c = CF()
    c.update_tokens()
  • The final step is to return to the main menu of the Cloudflare domain in the Workers section and click on the Add route button. Specify the API endpoint to be filtered by our new worker and select the created worker from the list by clicking Save to save the routing rules.
  • If you have high traffic, it probably makes sense to immediately switch to the paid Cloudflare Workers plan so as not to have problems with the exhausted limit. More about pricing: https://developers.cloudflare.com/workers/platform/pricing
  • Of course, after implementation of this decision, the level of protection should be lowered and all other recommendations specified in https://support.cloudflare.com/hc/en-us/articles/200504045-Using-Cloudflare-with-your-API for API endoints. The bonus will be that now the resource will not only pass valid traffic, but also filter invalid traffic.

Questions & answers

Q: But how can I give Cloudflare access to all API keys of my service? It's not safe!

A: You already give Cloudflare access to all your traffic by configuring it in proxy mode. And not only you. According to research, Cloudflare is used by 80.9% of all the websites whose reverse proxy service we know. This is 14.7% of all websites.

Conclusion

The described method was applied by us in practice for an API service that succumbed to a ddos ​​attack in the amount of several billion requests per day (with a normal load of tens of thousands of requests per day).

This solution repulsed the attack from 100% and did not block real user requests. At the same time, the costs were ridiculous - up to $50/month (for over usage of requests to Cloudflare workers, which is included in the $5/ month fee at the time the attack was going on). From this, we can judge that such a solution is not only very effective, but also the most budgetary option for protecting the API). For comparison, specialized services to protect API from ddos ​​on this traffic would cost from $800 to $1500 for the same volumes. At the same time, we are aware of the potential of CloudFlare, and we can be sure that this solution will protect against more massive attacks.

Daniel Yavorovych
CTO and Co-founder at Dysnix
Brainpower and problem-solver, meditating and mountain hiking.
Copied to Clipboard
Paste it wherever you like