Блокировка IP на MITIGATOR с Nginx и fail2ban

Описана настройка следующей схемы защиты web-сервера:

  • Nginx модулем ngx_http_limit_req выявляет превышение лимита запросов;
  • fail2ban анализирует error.log, куда Nginx пишет о превышениях;
  • IP добавляется в список заблокированных через MITIGATOR API.

Клиент MITIGATOR API

Имеется скрипт mitigator.py (скачать) для управления MITIGATOR, в частности, для временной блокировки IP-адреса через MITIGATOR API. При необходимости скрипт можно доработать самостоятельно для совершения любых других действий на MITIGATOR. Скрипт использует только стандартные модули, работает с Python 2.7+ и Python 3.

Для запуска скрипта нужна учетная запись MITIGATOR и ID политики защиты (например, 42 в URL .../policies/42 при заходе на MITIGATOR). Также скрипт принимает IP и время блокировки в секундах. Описание всех параметров печатается с ключом --help (-h).

Настройка Nginx

Модуль ngx_http_limit_req встроен в Nginx и позволяет ограничить количество запросов в секунду (RPS) через конфигурацию Nginx.

Лимит на запросы относится к зоне (zone). Обычно зоной является IP клиента (то есть ограничиваются запросы от него) или URI на сайте (ограничиваются запросы к нему), но возможны и более сложные комбинации параметров запроса.

Зоны описываются в контексте http. Опишем зону perip (per IP), способную отслеживать превышение лимита в 10 RPS для любого из 10 млн. IP-адресов. Для этого в добавим в /etc/nginx.conf через промежуточный файл строку:

mkdir -p /etc/nginx/nginx.conf.d

cat > /etc/nginx/conf.d/limit-req.conf <<'END'
limit_req_zone $binary_remote_addr zone=perip:10m rate=10r/s;
END

Чтобы применить этот лимит к части сайта (контекст location), то есть не позволять обращаться с одного IP к странице более 10 раз в секунду, используется следующая директива в /etc/nginx/sites-available/default (если ограничиваются запросы для сайта по умолчанию):

server {
    ...
    location / {
        ...
        limit_req zone=perip burst=20 nodelay;
    }
}

Здесь burst=20 позволяет всплески активности до 20 RPS (но в среднем не более 10 RPS, как описано для зоны), а nodelay означает, что запросы сверх лимита сбрасываются, а не ожидают своей очереди (актуально при DDoS).

Обновим конфигурацию Nginx:

nginx -s reload

Когда лимит превышается, в error.log появляются строки такого вида:

2019/01/11 09:27:12 [error] 155#155: *142 limiting requests, excess: 20.200 by zone "perip", client: 10.0.1.254, server: _, request: "GET / HTTP/1.1", host: "10.0.2.254"

Настройка fail2ban

Утилита fail2ban анализирует логи и при обнаружении в них заданных признаков выполняет действия по блокировке. Также fail2ban позволяет управлять списками заблокированных адресов.

Разместим скрипт блокировки:

install mitigator.py /usr/local/bin

Создадим новое действие fail2ban, которое будет вызывать скрипт:

cat >/etc/fail2ban/action.d/mitigator.conf <<'END'
[Definition]
actionban = \
        /usr/local/bin/mitigator.py \
            --server "<server>" \
            --user "<user>" --password "<password>" \
            --no-verify \
            --policy <policy> \
            tbl block --ip <ip> --time <bantime>
actionunban = \
        /usr/local/bin/mitigator.py \
            --server "<server>" \
            --user "<user>" --password "<password>" \
            --no-verify \
            --policy <policy> \
            tbl unblock --ip <ip>
END

Метки <server>, <user>, <password>, <policy>, <ip> и <bantime> нужно писать как есть — это параметры, которые при выполнении действия будут автоматически заменены на правильные значения. Ключ --no-verify нужен, если MITIGATOR работает с самоподписанным сертификатом, и необходимо отключить его проверку.

Создадим ограничение (jail), которое будет блокировать IP по записям в error.log:

cat >>/etc/fail2ban/jail.local <<END

[nginx-limit-req]
enabled = true
logpath = %(nginx_error_log)s
banaction = mitigator[server=mitigator.local, user=admin, password=admin, policy=42, bantime=600]
END

Модуль nginx-limit-req поставляется с fail2ban и находит нужные строки. В строке banaction=... указываются актуальные параметры действия: сервер, имя пользователя, пароль, ID политики. Для примера время блокировки 10 минут.

Перезагрузим правила fail2ban:

fail2ban-client reload

Проверка работы

Трафик должен идти через MITIGATOR, должна быть включена общая защита и защита выбранной политики.

Имитировать атаку можно утилитой httperf:

httperf --server 192.0.2.20 --num-conn 100

При успешной настройке через несколько секунд после запуска httperf на сервере можно видеть, что fail2ban отработал:

$ fail2ban-client status nginx-limit-req
Status for the jail: nginx-limit-req
|- Filter
|  |- Currently failed:	1
|  |- Total failed:	65
|  `- File list:	/var/log/nginx/error.log
`- Actions
   |- Currently banned:	1
   |- Total banned:	1
   `- Banned IP list:	192.0.2.10

На MITIGATOR можно в карточке TBL политики проверить, что IP атакующего (192.0.2.10 в примере) находится в списке временно заблокированных.

Разблокировать IP можно как через MITIGATOR, так и через fail2ban:

fail2ban-client set nginx-limit-req unbanip 192.0.2.10

Если какой-то IP блокировать через fail2ban не нужно, а добавлять его в «белый список» (WL или TWL) на MITIGATOR нежелательно, можно игнорировать IP на уровне fail2ban:

fail2ban-client set nginx-limit-req addignoreip 192.0.2.10

Если fail2ban не отработал, ошибки можно наблюдать в его журнале:

tail -f /var/log/fail2ban/fail2ban.log