А так же о всякой фигне
Вообще-то уже есть готовый модуль testcookie-nginx. Он решит большинство Ваших проблем. Но он достаточно ограничен в функционале, а ещё я люблю писать велосипеды. Поэтому мы сделаем свой модуль, с блекджеком и шлюхами.
Создадим папку проекта рядом с nginx:
mkdir /opt/lua_filter cd /opt/lua_filter mkdir modules
В modules будем размещать какие-либо общие модули, а в корень проекта основные скрипты.
Ставим lua-resty-cookie:
luarocks install lua-resty-cookie
В корне проекта создаём info.lua - это будет скрипт для вывода информации о работе шлюза. Сейчас нам нужно только протестить работает ли lua, поэтому закидываем в него лишь пару строчек:
ngx.header['Content-Type'] = 'text/html; charset=UTF-8' ngx.say('<h1>TEST</h1>') ngx.exit(200)
Добавляем конфиг для тестового скрипта: /opt/nginx/conf/clients/lua_info.conf
server { listen localhost:8888; location / { content_by_lua_file '/opt/lua_filter/info.lua'; } }
Тестируем и перезагружаем nginx:
cd /opt/nginx/sbin/ ./nginx -t ./nginx -s reload
Пробрасываем туннель:
ssh -L 8888:localhost:8888 IP_АДРЕС_ШЛЮЗА -l root
Открываем в браузере адрес http://127.0.0.1:8888/ и видим:
Значит всё ок. Можно приступать. И так как мне дико лень расписывать что и почему я пишу в самом модуле, давайте пропустим этот этап, и перейдём сразу к далее.
А далее нам нужно вернуться к конфигам.
В клиентский (default.conf) нужно добавить директиву "rewrite_by_lua_file '/opt/lua_filter/main.lua';" и теперь этот конфиг будет выглядеть:
server { server_name default:80; location / { rewrite_by_lua_file '/opt/lua_filter/main.lua'; proxy_set_header HOST $host; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_pass http://ЛОКАЛЬНЫЙ_IP_ВЕБ_СЕРВЕРА:80; } }
И в главный конфиг nginx нужно добавить директиву по подгрузке всех наших модулей "lua_package_path '/opt/lua_filter/modules/?.lua;;';" и выглядит он сейчас так:
#user nobody; worker_processes 1; #error_log logs/error.log; #error_log logs/error.log notice; #error_log logs/error.log info; #pid logs/nginx.pid; events { worker_connections 1024; } http { gzip on; gzip_disable "msie6"; sendfile on; tcp_nopush on; tcp_nodelay on; keepalive_timeout 65; types_hash_max_size 2048; include /opt/nginx/conf/mime.types; default_type application/octet-stream; access_log /opt/nginx/logs/access.log; error_log /opt/nginx/logs/error.log; lua_package_path '/opt/lua_filter/modules/?.lua;;'; include /opt/nginx/conf/clients/*; }
Далее скачиваете готовый модуль и распаковываете в нашу папку "/opt/lua_filter". Рестартуете nginx и обновляете страницу. Ничего не должно измениться, но если откроете куки-инспектор, то увидите, что к документу прикреплена кука. Это значит что модуль работает.
У модуля есть 3 варианты работы, которые задаются в конфиге, в переменной _config.lvl. При нулевом значении модуль не работает, а сразу пропускает запрос. Этот вариант нужен на мирное время.
При lvl=1, каждому новому запросу будет присваиваться специальная кука и затем клент будет редиректиться на этот же адрес. После редиректа проверяется, есть ли кука, и если есть, то всё ок, пропускаем дальше
При lvl=2, запросу будет отдаваться страница с js кодом, который будет устанавливать куку и редиректить по тому же адресу. И после редиректа, кука опят же проверяется.
Таким образом в мирное время, модуль можно отключить и он не будет мешать. В случае атаки простеньких ботов, включаем lvl1, а если боты научатся редиректам и кукам, включаем lvl2.
А надо оно потому, что это по сути только заготовка под настоящий модуль. Lua очень простой и гибкий язык, в модуль можно встроить всё что угодно: статистику, поведенческий анализатор, отслеживание каждого конкретного коннекта и много чего ещё.
nginx.conf
#user nobody; worker_processes 1; #error_log logs/error.log; #error_log logs/error.log notice; #error_log logs/error.log info; #pid logs/nginx.pid; events { worker_connections 1024; } http { limit_req_zone $binary_remote_addr zone=content:10m rate=1r/s; limit_req_zone $binary_remote_addr zone=static:10m rate=50r/s; limit_req_status 429; gzip on; gzip_disable "msie6"; sendfile on; tcp_nopush on; tcp_nodelay on; keepalive_timeout 10s; keepalive_disable msie6; send_timeout 10s; types_hash_max_size 2048; server_tokens off; include /opt/nginx/conf/mime.types; default_type application/octet-stream; ssl_protocols TLSv1 TLSv1.1 TLSv1.2; ssl_prefer_server_ciphers on; access_log /opt/nginx/logs/access.log; error_log /opt/nginx/logs/error.log; lua_package_path '/opt/lua_filter/modules/?.lua;;'; proxy_limit_rate 1m; include /opt/nginx/conf/clients/*; }
И default.conf
server { server_name default:80; set $test_ip '10.129.28.157'; client_max_body_size 512k; if ($http_user_agent ~* (WordPress|MyIE|ApacheBench|foobar)) { return 444; } if ($http_user_agent = "") { return 444; } location ~ /. { deny all; access_log off; log_not_found off; } location = /xmlrpc.php { deny all; access_log off; log_not_found off; } location ~* .(jpg|jpeg|gif|png|ico|bmp|js|css)$ { limit_req zone=static burst=2; rewrite_by_lua_file '/opt/lua_filter/main.lua'; proxy_set_header HOST $host; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_pass http://$test_ip:80; } location ~* .(rar|zip|tar|gz)$ { limit_req zone=content burst=1; rewrite_by_lua_file '/opt/lua_filter/main.lua'; proxy_set_header HOST $host; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_pass http://$test_ip:80; } location / { limit_req zone=content burst=2; rewrite_by_lua_file '/opt/lua_filter/main.lua'; proxy_set_header HOST $host; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_pass http://$test_ip:80; } }
Даже если сайт умирает под ддосом, мы должны пропускать поисковые системы без ограничений. Для этого есть "whiteList.lua". После внесения изменений, нужно будет перезапустить nginx.
И обратная ситуация, самых ръяных ботов нужно будет банить ещё на фаерволе, чтобы до nginx запрос даже не доходил. Делается это так:
ufw insert НОМЕР_ПРАВИЛА deny from IP_бота
Правило должно стоять выше, чем правила:
Иначе сработает оно, и бот спокойно пройдёт дальше.
Дальше открывается огромная область для творчества: допиливание lua модуля, допиливание nginx конфигов, правил фаервола. Очередной поиск нового, более толстого канала связи, ресайз виртуалок...
Но есть пределы, за которые своими силами не перейти:
Канал связи. Самый толстый входящий канал, что мне удалось найти- у Linode, но и он всего 40Гбит/с, что в прочем ещё нужно проверить. Есть конечно амазон, с практически безграничными ресурсами, но и с совсем другим порядком цен.
Время. Атакующий всегда на шаг впереди. Он тратит пол часа, подправляет свои алгоритмы/методы и уходит бухать. А вы тратите часы/сутки, чтобы понять как это всё зафильтровать. Затем он возвращается, опять за пол часа меняет алгоритм и всё повторяется. Длительную атаку отбить в одиночку очень сложно.
Защита от DDoS Своими силами. Часть 4, настраиваем шлюз.
Мы приступаем к самому интересному, настраиваем anti ddos шлюз. Нашим главным инструментом будет nginx. Но в этом случае нельзя его просто взять и поставить из репозитория, придётся его собрать самим.
Правильные белые списки для google/yandex ботов
Типичное решение для этой задачи - посмотреть логи и взять от туда ip поисковых роботов, либо ещё хуже- найти список адресов в интернетах. Но это ошибочный путь, потому что адреса серверов меняются, и нам придётся постоянно следить за актуальностью списка.