VK Cloud

Как ограничить частоту запросов в HAProxy: пошаговая инструкция

10 июня 2020 г.
_blog_head_152.png

Узнавайте о выходе новых статей в блоге первыми!

Будем держать в курсе новостей и облачных трендов

section-subscribe_2x.png
    section-subscribe_2x.png

    Ограничение частоты запросов в HAProxy — один из самых популярных методов защиты сервера от DoS-атак, но в интернете трудно найти понятную инструкцию, как конкретно его настроить.

    Перевели статью, в которой рассказывается, как реализовать в HAProxy ограничение скорости обработки запросов (rate limiting) по списку IP-адресов, который обновляется в реальном времени. Надежный способ автор нашел методом проб и ошибок. Надеемся, благодаря статье вам не придется потратить столько времени и усилий, сколько пришлось потратить автору.

    Для настройки HAProxy не требуется никаких предварительных знаний, поскольку ниже излагаются все необходимые шаги.

    Опенсорсный и бесплатный HAProxy — высокодоступный балансировщик нагрузки и прокси-сервер. В последние годы он стал очень популярным, поскольку обеспечивает высокую производительность с минимумом ресурсов. В отличие от альтернативных программ, некоммерческая версия HAProxy Community Edition предлагает достаточное количество функций для надежной балансировки нагрузки.

    В этой программе поначалу довольно сложно разобраться. Однако у нее очень скрупулезная и подробная техническая документация. Автор говорит, что это самая подробная документация среди всех опенсорсных программ, какие он когда-либо использовал.

    Итак, вот пошаговая инструкция.

    Настройка балансировщика нагрузки

    Чтобы сэкономить время и не отвлекаться на настройку инфраструктуры, возьмем образы Docker и Docker Compose — и быстро запустим основные компоненты.

    Первая задача — поднять рабочий инстанс балансировщика нагрузки HAProxy с несколькими бэкенд-серверами Apache.

    Клонируем репозиторий

    $ git clone git@github.com:stargazer/haproxy-ratelimiter.git $ cd haproxy-ratelimiter

    Можете посмотреть на Dockerfile и docker-compose.yml с параметрами установки. Их обсуждение выходит за рамки данной статьи, поэтому остановимся на том, что они создали рабочий инстанс HAProxy под названием loadbalancer с двумя бэкенд-серверами api01 и api02. Для конфигурации HAProxy изначально будем использовать файл haproxy-basic.cfg, а затем переключимся на haproxy-ratelimiting.cfg.

    Для простоты конфигурационный файл haproxy-basic.cfg сокращен до самого необходимого минимума и очищен от лишнего. Посмотрим на него:

    defaults mode http timeout connect 5000ms timeout client 50000ms timeout server 50000ms frontend proxy bind *:80 use_backend api backend api balance roundrobin server api01 api01:80 server api02 api02:80

    Раздел frontend proxy устанавливает HAProxy прослушивать порт 80 и пересылать все запросы в пул серверов api на бэкенде.

    Раздел backend api определяет пул бэкенда api с двумя бэкенд-серверами api01 и api02> и соответствующими адресами. Сервер для обслуживания каждого входящего запроса выбирается алгоритмом балансировки нагрузки roundrobin, то есть, по сути, два доступных сервера используются по очереди.

    Давайте запустим все три наши контейнера

    $ sudo docker-compose up

    Теперь у нас есть контейнер loadbalancer, который перенаправляет запросы на два сервера бэкенда api01 и api02. Мы получим ответ от одного из них, если введем в адресной строке http://localhost/.

    Интересно несколько раз обновить страницу и посмотреть логи docker-compose.

    api01_1 | 192.168.192.3 - - [08/Jan/2019:11:38:09 +0000] "GET / HTTP/1.1" 200 45 api02_1 | 192.168.192.3 - - [08/Jan/2019:11:38:10 +0000] "GET / HTTP/1.1" 304 - api01_1 | 192.168.192.3 - - [08/Jan/2019:11:38:10 +0000] "GET / HTTP/1.1" 304 - api02_1 | 192.168.192.3 - - [08/Jan/2019:11:38:11 +0000] "GET / HTTP/1.1" 304 - api01_1 | 192.168.192.3 - - [08/Jan/2019:11:38:11 +0000] "GET / HTTP/1.1" 304 - api02_1 | 192.168.192.3 - - [08/Jan/2019:11:38:11 +0000] "GET / HTTP/1.1" 304 -

    Как видим, два сервера api обрабатывают запросы по очереди.

    Теперь у нас есть инстанс HAProxy с очень простой конфигурацией балансировки нагрузки и имеется некоторое представление о том, как он устроен.

    Добавляем лимит на количество запросов

    Чтобы поставить лимит на количество запросов в балансировщик нагрузки, нужно изменить файл конфигурации в инстансе HAProxy. Следует убедиться, что контейнер loadbalancer использует конфигурационный файл haproxy-ratelimiter.cfg.

    Просто измените Dockerfile, чтобы заместить файл конфигурации.

    FROM haproxy:1.7 COPY haproxy-ratelimiter.cfg /usr/local/etc/haproxy/haproxy.cfg

    Установка лимитов

    Все настройки прописываются в конфигурационном файле haproxy-ratelimiter.cfg. Давайте внимательно его изучим.

    defaults mode http timeout connect 5000ms timeout client 50000ms timeout server 50000ms frontend proxy bind *:80 # ACL function declarations acl is_abuse src_http_req_rate(Abuse) ge 10 acl inc_abuse_cnt src_inc_gpc0(Abuse) gt 0 acl abuse_cnt src_get_gpc0(Abuse) gt 0 # Rules tcp-request connection track-sc0 src table Abuse tcp-request connection reject if abuse_cnt http-request deny if abuse_cnt http-request deny if is_abuse inc_abuse_cnt use_backend api backend api balance roundrobin server api01 api01:80 server api02 api02:80 backend Abuse stick-table type ip size 100K expire 30m store gpc0,http_req_rate(10s)

    HAProxy предлагает набор низкоуровневых примитивов, которые обеспечивают большую гибкость и подходят для различных вариантов использования. Его счетчики часто напоминают мне накапливающий регистр (сумматор) в CPU. Они хранят промежуточные результаты, принимают на вход различную семантику, но в итоге — это просто цифры. Чтобы хорошо во всем разобраться, есть смысл начать с самого конца конфигурационного файла.

    Таблица Abuse

    backend Abuse stick-table type ip size 100K expire 30m store gpc0,http_req_rate(10s)

    Здесь мы устанавливаем фиктивный бэкенд под названием Abuse («злоупотребления»). Фиктивный, потому что он используется только для таблицы stick-table, к которой остальная конфигурация может ссылаться по имени Abuse. Stick-table — это таблица, хранящаяся в памяти процесса, где для каждой записи можно определить время жизни.

    У нашей таблицы следующие характеристики:

    • type ip: запросы сохраняются в таблице по IP-адресу в качестве ключа. Таким образом, запросы с одного и того же IP-адреса будут ссылаться на одну и ту же запись. По сути, это означает, что мы отслеживаем IP-адреса и связанные с ними данные.
    • size 100K: таблица содержит максимум 100 тыс. записей.
    • expire 30m: срок хранения записей составляет 30 минут бездействия.
    • store gpc0,http_req_rate(10s): с записями хранится счетчик gpc0 и количество запросов IP-адреса за последние 10 секунд. С помощью gpc0 мы будем отслеживать, сколько раз IP-адрес замечен в злоупотреблениях. По сути, положительное значение счетчика означает, что IP-адрес уже помечен как подозрительный. Назовем этот счетчик abuse indicator.

    В целом, таблица Abuse позволяет отслеживать, помечен ли IP-адрес как подозрительный, а также текущую частоту запросов с этого адреса. Поэтому у нас появляется история записей, а также информация в реальном времени.

    Теперь перейдем в раздел frontend proxy и посмотрим, что там нового.

    Функции и правила ACL

    frontend proxy bind *:80 # ACL function declarations acl is_abuse src_http_req_rate(Abuse) ge 10 acl inc_abuse_cnt src_inc_gpc0(Abuse) gt 0 acl abuse_cnt src_get_gpc0(Abuse) gt 0 # Rules tcp-request connection track-sc0 src table Abuse tcp-request connection reject if abuse_cnt http-request deny if abuse_cnt http-request deny if is_abuse inc_abuse_cnt use_backend api

    Список управления доступом ACL (Access Control List) — это объявления функций, которые вызываются только при соответствии установленному правилу.

    Давайте подробно рассмотрим все три записи ACL. Имейте в виду, что все они явно ссылаются на таблицу Abuse, которая в качестве ключа использует IP-адреса, поэтому каждая функция применяется к IP-адресу запроса.

    • acl is_abuse src_http_req_rate(Abuse) ge 10: функция is_abuse возвращает True, если текущая частота поступления запросов больше или равна десяти.
    • acl inc_abuse_cnt src_inc_gpc0(Abuse) gt 0: функция inc_abuse_cnt возвращает True, если инкрементируемое значение gpc0 больше нуля. Поскольку начальное значение gpc0 равно нулю, эта функция всегда возвращает True. Другими словами, она увеличивает значение abuse indicator, по сути, сигнализируя о злоупотреблениях с этого IP-адреса.
    • acl abuse_cnt src_get_gpc0(Abuse) gt 0: функция abuse_cnt возвращает True, если значение gpc0 больше нуля. Другими словами, он говорит, был ли этот IP-адрес уже замечен в злоупотреблениях.

    Как упоминалось ранее, ACL — это просто декларации, то есть объявления функций. Они не применяются к входящим запросам, пока не срабатывает какое-то правило.

    Имеет смысл взглянуть на правила, определенные в том же разделе frontend. Правила поочередно применяются к каждому входящему запросу — и запускают функции из списка ACL, которые мы только что определили.

    Давайте посмотрим, что делает каждое правило:

    • tcp-request connection track-sc0 src table Abuse: добавляет запрос в таблицу Abuse. Поскольку в таблице ключом является IP-адрес, это правило пополняет список IP-адресов.
    • tcp-request connection reject if abuse_cnt: отклоняет новые TCP-соединения, если IP-адрес уже замечен в злоупотреблениях, то есть помечен как abuse. По сути, запрещает новые TCP-соединения с такими IP-адресами.
    • http-request deny if abuse_cnt: запрещает доступ, если IP-адрес уже замечен в злоупотреблениях. Это относится к уже установленным соединениям с IP-адресами, которые только что помечены как abuse.
    • http-request deny if is_abuse inc_abuse_cnt: запрещает доступ, если is_abuse и inc_abuse_cnt оба возвращают True. Другими словами, будет отказ в доступе, если с этого IP-адреса в настоящее время запросы поступают с высокой частотой, а затем этот IP-адрес вносится в черный список.

    По существу, мы вводим два типа проверок: в реальном времени и по черному списку из истории запросов. Второе правило отвергает все новые TCP-соединения, если IP-адрес был замечен в злоупотреблениях. Третье правило запрещает обслуживание HTTP-запросов для IP-адреса из черного списка, независимо от текущей частоты запросов с этого адреса. Четвертое правило гарантирует, что HTTP-запросы с IP-адреса будут отклонены в тот самый момент, как только преодолен порог частоты запросов.

    Таким образом, второе правило в основном работает на новых TCP-соединениях, третье и четвертое — на уже установленных соединениях, причем первое — это историческая проверка, а второе — проверка в реальном времени.

    Попробуем фильтр в деле!

    Теперь можем снова собрать и запустить наши контейнеры.

    $ sudo docker-compose down $ sudo docker-compose build $ sudo docker-compose up

    Балансировщик нагрузки должен запуститься перед двумя серверами бэкенда.

    Давайте направим наш браузер на http://localhost/. Если быстро обновить страничку с десяток раз, мы превысим порог в десять запросов за десятисекундный интервал — и наши запросы будут отклонены. Если мы продолжим обновлять страницу, новые запросы будут отклоняться сразу — еще до того, как установлено TCP-соединение.

    Вопросы

    Почему лимит составляет десять запросов на десять секунд?

    Таблица Abuse определяет http_req_rate(10s), то есть частота запросов измеряется в окне в десять секунд. Функция is_abuse из ACL возвращает True при частоте запросов ≥10 в течение указанного интервала. Таким образом, злоупотреблением считается частота запросов в десять и более запросов за десять секунд.

    В этой статье для примера мы решили установить низкий лимит, чтобы проще было проверить работоспособность ограничителя.

    В чем разница между правилами http-request и tcp-request connection?

    Из документации:

    http-request: оператор http-request определяет набор правил, которые применяются на сетевом уровне 7 [по модели OSI].
    tcp-request connection: выполнение действия над входящим соединением в зависимости от условия на сетевом уровне 4.

    Зачем отбрасывать HTTP-запросы, если мы в любом случае полностью отбрасываем TCP-запросы?

    Представьте, что HTTP-запросы на сервер отправляют несколько TCP-соединений с одного IP-адреса. Частота HTTP-запросов быстро превысит пороговые значения. Именно тогда вступает в действие четвертое правило, которое отбрасывает запросы и вносит IP-адрес в черный список.

    Теперь вполне возможно, что HTTP-соединения с того же IP-адреса остаются открытыми (см. постоянное HTTP-соединение), а частота HTTP-запросов упала ниже порогового значения. Третье правило гарантирует продолжение блокировки HTTP-запросов, поскольку abuse indicator срабатывает на этот IP.

    А теперь предположим, что через несколько минут тот же IP пытается установить TCP-соединения. Они отбрасываются немедленно, так как действует второе правило: оно видит помеченный IP-адрес — и сразу отбрасывает соединения.

    Вывод

    Поначалу может быть сложно разобраться с ограничением скорости обработки запросов с помощью HAProxy. Чтобы все сделать правильно, требуется довольно «низкоуровневое» мышление и ряд неинтуитивных действий. Наверное, документация в этой части слишком техническая и страдает от отсутствия каких-нибудь базовых примеров. Надеемся, что данное руководство восполнит недостаток и покажет направление каждому, кто хочет пойти по этому пути.

    Оригинал статьи на Habr.com

    Group_2087326420_1_c76b79cb60.png

    Читайте больше статей про информационную безопасность и оставляйте заявку на консультацию

    Оставьте заявку, чтобы получить консультацию

    Наши специалисты свяжутся с вами в ближайшее время и ответят на все вопросы.

    section-subscribe_2x.png
              Теги: Разработка, devops, cерверное администрирование, DoS-атаки, информационная безопасность, прокси, HAProxy
              Ссылка скопирована
              Поделиться

              Почитать по теме

              _blog_head_42.png
              30 июня

              SQL-инъекции и XSS-атаки: как защитить веб-приложение от самых частых угроз

              _blog_head_45.png
              24 июня

              OWASP Top 10: главные уязвимости веб-приложений и как от них защититься

              _blog_head_126.png
              4 июня

              DDoS-атаки в 2026 году: какие бывают, чем опасны и как от них защититься

              40+ готовых сервисов