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.
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.
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:
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.
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))
})
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()
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.
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.