Django + Postgres: OperationalError FATAL: извините, уже слишком много клиентов (в результате преднамеренной атаки)
На Stackoverflow есть много похожих вопросов, но они не касаются моего конкретного случая. Проблема заключается в том, что на сайт совершаются преднамеренные атаки с большим количеством одновременных запросов. Я имитировал эти атаки с помощью a/b тестирования:
ab -k -c 150 -n 90000 'https://www.mywebsite.com/'
Веб-сайт не работает со следующей ошибкой:
OperationalError at /
FATAL: sorry, too many clients already
На моем экземпляре Postgres максимальное количество разрешенных соединений равно 100. Повышать его, как рекомендуется в большинстве ответов на Stackoverflow, бессмысленно, так как во время преднамеренных атак, которым мы подвергаемся в последнее время, количество запросов может быть значительно выше.
Я попытался решить проблему, создав специальное промежуточное ПО, кэширующее количество запросов:
from ipware import get_client_ip
class IPMiddleware:
REQUEST_PERIOD_IN_SECONDS = 10
MAX_REQUESTS_IN_PERIOD = 100
TOO_MANY_REQUESTS_BAN_PERIOD_IN_SECONDS = 60 * 2
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
try:
ip, is_routable = get_client_ip(request)
if ip:
if cache.get(f'ip_is_banned_{ip}', False):
raise PermissionDenied()
cache_key = f'requests_made_{ip}'
try:
cache.incr(cache_key)
except ValueError:
cache.set(cache_key, 1, self.REQUEST_PERIOD_IN_SECONDS)
requests_made = cache.get(cache_key)
if requests_made > self.MAX_REQUESTS_IN_PERIOD:
cache.set(f'ip_is_banned_{ip}', True, self.TOO_MANY_REQUESTS_BAN_PERIOD_IN_SECONDS)
except:
pass
response = self.get_response(request)
return response
Порядок моих промежуточных продуктов в файле settings.py следующий:
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'mywebsite.middlewares.CustomLocaleMiddleware',
'mywebsite.middlewares.IPMiddleware',
'maintenancemode.middleware.MaintenanceModeMiddleware',
]
Идея заключается в том, чтобы запретить IP, который сделал больше определенного количества запросов за определенный период времени (например, 100 запросов за 10 секунд). Затем, если IP-адрес забанен, возникает PermissionDenied().
Логика кэширования работает, и IP становится запрещенным. Однако это не предотвращает попадание одновременных запросов в базу данных. Я проверил это, изменяя количество запросов в a/b тестировании: это работает нормально при 50 запросах, но ломает сайт при 150 запросах.
Есть ли какой-нибудь эффективный способ противостоять таким атакам, кроме использования Cloudflare?
Используете ли вы nginx? Тогда вам лучше ограничить количество запросов с одного ip с помощью nginx. Django слишком медленный для этого, имхо. https://www.nginx.com/blog/rate-limiting-nginx/
Я просто думаю, что лучше ограничить его от самого главного сервера, на который попал запрос, как предложил @Timur. Таким образом, django не будет знать о запросе, как и Postgres