на соединения
limit_conn_zone $binary_remote_addr zone=perip:10m; limit_conn perip 100;
и скорость запросов на динамику (fastcgi_pass на php-fpm)
limit_req_zone $binary_remote_addr zone=dynamic:10m rate=2r/s; limit_req zone=dynamic burst=10 nodelay;
Сильно полегчало, по логам видно, что в первую зону никто не попадает, зато вторая отрабатывает по полной.
Но плохиши продолжали долбить, и захотелось их отбрасывать раньше — на уровне фаервола, и надолго.
Сначала сам парсил логи, и особо настырных добавлял через iptables в баню. Потом парсил уже по крону каждые 5 минут. Пробовал fail2ban. Когда понял, что плохишей стало очень много, перенёс их в ipset ip hash.
Почти всё хорошо стало, но есть неприятные моменты:
— парсинг/сортировка логов тоже приличное (процессорное) время отнимает
— сервер тупит, если началась новая волна между соседними разборками (логов)
Нужно было придумать как быстро добавлять нарушителей в черный список. Сначала была идея написать/дописать модуль к Nginx + демон, который будет ipset-ы обновлять. Можно и без демона, но тогда придётся запускать Nginx от рута, что не есть красиво. Написать это реально, но понял, что нет столько времени. Ничего похожего не нашёл (может плохо искал?), и придумал вот такой алгоритм.
При привышении лимита, Nginx выбрасывает 503-юю ошибку Service Temporarily Unavailable. Вот я решил на неё и прицепиться!
Для каждого location создаём свою страничку с ошибкой
error_page 503 =429 @blacklist;
И соответствующий именованный location
location @blacklist { fastcgi_pass localhost:1234; fastcgi_param SCRIPT_FILENAME /data/web/cgi/blacklist.sh; include fastcgi_params; }
Дальше интересней.
Нам нужна поддержка CGI-скриптов. Ставим, настраиваем, запускаем spawn-fcgi и fcgiwrap. У меня уже было готовое для collectd.
#!/bin/bash BAN_TIME=5 DB_NAME="web_black_list" SQLITE_DB="/data/web/cgi/${DB_NAME}.sqlite3" CREATE_TABLE_SQL="\ CREATE TABLE $DB_NAME (\ ip varchar(16) NOT NULL PRIMARY KEY,\ added DATETIME NOT NULL DEFAULT (DATETIME()),\ updated DATETIME NOT NULL DEFAULT (DATETIME()),\ counter INTEGER NOT NULL DEFAULT 0 )" ADD_ENTRY_SQL="INSERT OR IGNORE INTO $DB_NAME (ip) VALUES (\"$REMOTE_ADDR\")" UPD_ENTRY_SQL="UPDATE $DB_NAME SET updated=DATETIME(), counter=(counter+1) WHERE ip=\"$REMOTE_ADDR\"" SQLITE_CMD="/usr/bin/sqlite3 $SQLITE_DB" IPSET_CMD="/usr/sbin/ipset" $IPSET_CMD add $DB_NAME $REMOTE_ADDR > /dev/null 2>&1 if [ ! -f $SQLITE_DB ]; then $SQLITE_CMD "$CREATE_TABLE_SQL" fi $SQLITE_CMD "$ADD_ENTRY_SQL" $SQLITE_CMD "$UPD_ENTRY_SQL" echo "Content-type: text/html" echo "" echo "<html>" echo "<head><title>429 Too Many Requests</title></head>" echo "<body bgcolor=\"white\">" echo "<center><h1>429 Too Many Requests</h1></center>" echo "<center><small><p>Your address ($REMOTE_ADDR) is blacklisted for $BAN_TIME minutes</p></small></center>" echo "<hr><center>$SERVER_SOFTWARE</center>" echo "</body>" echo "</html>"
Собственно всё очевидно, кроме, разве что, SQLite. Я его добавил пока просто для статистики, но в принципе можно использовать и для удаления устаревших плохишей из черного списка. Время 5 минут пока тоже не используется.
Черный список создавался вот так
ipset create web_black_list hash:ip
Правило в iptables у каждого может быть своё, в зависимости от конфигурации и фантазии.
З.Ы.: Улыбнуло сообщение одного «хакера» на форуме, как быстро он положил сервер. Он и не догадывался, что это сервер положил на него.
ссылка на оригинал статьи http://habrahabr.ru/post/176119/
Добавить комментарий