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

Вернуться на верх