Rate-limiting in a Dockerized backend (Spring-boot and NGINX)

aminbe
3 min readApr 20, 2021

Context

The objective of this article is to implement a simplified rate-limiter to protect an API exposed to the public from a DDoS or brute force attack, etc…

Overview

We have a springboot application dockerized and deployed with Docker Swarm. If we want to implement a rate-limiter, we realize that the remote IP that the container “sees” is the one provided by the Docker load balancer to this container in Docker network. This issue was documented here.

To overcome this limitation, we propose hereby a way that implements NGINX as a reverse proxy and the Bucked4j Java library as a rate limiter.

Docker layer

We define a hostname of our API so that it could be easily reached out by our reverse proxy. Briefly, our docker-compose file will look like :

version: "3.1"services:
api:
hostname: api # hostname of our API
container_name: api
build: ./api/. # Path to Dockerfile of our API projet
ports:
- "9999:9999"
proxy:
container_name: proxy
build: ./proxy/. # Path to Dockerfile of our reverse proxy projet
ports:
- "8888:8888"

NGINX layer

We will use NGINX as a reverse proxy, however, if we catch X-Forwarded-For as the header on which our ratelimiting checks will be done, this can easily be bypassed, since we can change X-Forwarded-For this way:

curl -k -H "X-Forwarded-For: 1.1.1.1" MY_API_URL

Therefore, we will use a REGEX to select the last IP address of the X-Forwarded-For string :

if ($proxy_add_x_forwarded_for ~* "\\, ([^,]+)$" ) {
set $remote_ip $1;
}

The NGINX configuration will therefore be as follows :

# Adding remote ip to headers
server {
listen 8888;
location / {
proxy_pass <http://api:9999>; # Our API mentioned in docker-compose.yml
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
set $remote_ip $proxy_add_x_forwarded_for ; if ($proxy_add_x_forwarded_for ~* "\\, ([^,]+)$" ) {
set $remote_ip $1;
# X-Forwarded-For looks like IP1, IP2, IP3 ... so we select the last IP address
}
proxy_set_header My-Custom-Header $remote_ip;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 90;
}
}

Spring-boot layer

We can directly use a spring-boot starter for bucket4j

Bucket4j is a Java commonly used library to limit access rate to an API.

Getting started

In order to integrate this library, we need to perform some actions :

pom.xml dependencies

  <!-- a starter for bucket4j -->
<dependency>
<groupId>com.giffing.bucket4j.spring.boot.starter</groupId>
<artifactId>bucket4j-spring-boot-starter</artifactId>
<version>{TAG}</version>
</dependency>
<!-- API caching -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
<version>{TAG}</version>
</dependency>
<dependency>
<groupId>org.ehcache</groupId>
<artifactId>ehcache</artifactId>
<version>{TAG}</version>
</dependency>

Enabling caching

We need to add @EnableCaching annotation to any of the configuration classes. We have chosen here CorsConfig class :

import org.springframework.cache.annotation.EnableCaching;@Configuration
@EnableCaching
@Order(Ordered.HIGHEST_PRECEDENCE)
public class CorsConfig {
/* ... */}

Application.yml configuration

We can then configure our rate-limiter without any line of code.

In this example, we restrict access based on the IP address.

spring:
cache:
cache-names:
- buckets_auth
- buckets_upload
caffeine:
spec: maximumSize=1000000,expireAfterAccess=3600s
bandwidths: &myBandwidths
- capacity: 10
time: 1
unit: hours
rate_limits: &myRateLimits
- expression: "getHeader('My-Custom-Header')"
bandwidths: *myBandwidths
bucket4j:
enabled: true
filter-method: servlet
filters:
- cache-name: buckets_login
url: ".*/(auth_route_1|auth_route_2|auth_route_3)"
rate-limits: *myRateLimits
- cache-name: buckets_files
url: ".*/(upload_route_1|upload_route_2)"
rate-limits: *myRateLimits

Conclusion

One could argue that we may use traefik to do something similar, however, since treafik proposes multiple other features (integration with kubernetes, etc…), we prefer minimalist aproaches.

Extra-link

An implementation : https://github.com/AmineBenaddiGitHub/rt-spbt-nginx-docker

--

--