Цели этого поста:
- Эскизная реализация описанной в статье схемы на Nginx + немного бэкэнда в виде php;
- Поиск решения «покрасивее»
Если интересно, прошу под хабракат.
Повторим на всякий случай цели борьбы:
- Ограничить доступ к админкам CMS, чтобы не дать возможности массовым ботам брутить пароли
- Без неудобств для любимых
живых людейпользователей интернета - Решение должно быть универсальным и массовым
Логика:
0. Nginx работает как самый обычный front. Апач и всё остальное сзади.
1. При превышении некоторого лимита показываем пользователю формочку, где нужно что-либо сделать (в моём случае — просто нажать на кнопку), тем самым доказав, что он человек.
2. В дальнейшем пропускать человека без проволочек.
Решение оказалось несложным, все комментарии по ходу.
# описание т.н зоны limit_req_zone $binary_remote_addr zone=one:10m rate=5r/m; server { # ... root /var/wl-web; recursive_error_pages on; #мы два раз проходим по error_page в случае @limit -> @wlgui; location / { #... тут самое обычное проксирование на бэкэнд } # именованый локейшн для нашего ифейса авторизатора. location @wlgui { # у меня fpm, но можно проксировать и на апач. internal; fastcgi_pass 127.0.0.1:9000; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root/wlgui.php; fastcgi_intercept_errors on; include fastcgi_params; } # защищаемые файлы. именно тут можно перечислить "сигнатуры" админок движков location ~* /(i|i2)\.html$ { # Срабатыване лимита реализуется в "возврате" 503 (c 1.3.15 этот код можно поменять строчкой ниже) #limit_req_status 516; error_page 503 = @limit; # включаем ограничитель. При срабатывании запрос уйдет на @limit limit_req zone=one nodelay; # Это сработает, если в лимит не упёрлись. # Важно. Нам нужен именно изначальный URI, поэтому собираем руками. proxy_pass http://127.0.0.1:8080$request_uri; proxy_set_header Host $host; #и другие директивы проксирования на бэкэнд } # Если сработал лимит location @limit { internal; #проверяем что в куке нет ничего, что может плохо сработать при подстановке в путь файловой системы. if ($cookie_wlsid ~* [^a-f\d]) { return 503; } # так не получилось, но было бы очень красиво =( #error_page 516 @backend; #try_files /wl/$cookie_wlsid.cookie /wl/$remote_addr.ip /wl/$remote_addr-$host.iph @wlgui; #if ($uri ~* /wl/[a-z\d]+\.cookie ) {return 516;} # именованый локейшн для заворота запроса на страничку подтверждения хм.. "человечности". # Если кука не авторизована(т.е нет файла с её значением), заворачиваем туда. If is Evil, i know.... error_page 516 = @wlgui; if ( !-f $document_root/wl/$cookie_wlsid.cookie) {return 516;} # Кука авторизована! На бэкэнд. proxy_pass http://127.0.0.1:8080$request_uri; proxy_set_header Host $host; #и другие директивы проксирования на бэкэнд } }
и очень простой wlgui.php, который производит «авторизацию» куки путём создания пустого файла с именем, равным значению куки.
(важно не забыть создать папку wl в /var/wl-web и поставить на неё соответствующий права)
<?php if (!empty($_POST['wlsec'])){ # если форма сабминится, отдаём куку в браузер и авторизовываем её. $cookie=md5(uniqid()); setcookie('wlsid',$cookie,time()+3600*24*90/*90d*/); touch ('/var/wl-web/wl/'.$cookie.'.cookie'); echo "Done! Please, refresh the page! (setting {$cookie})"; } else { ?> <form method="POST"> <input name="wlsec" value="GetAccess" type="submit"> </form> <?php echo "<br> Your cookie:".(isset($_COOKIE['wlsid'])?htmlspecialchars($_COOKIE['wlsid']):'(not set)'); }
Возможные улучшения:
- Решение кривое, уверен, есть куда еще пилить
- Не применять эти ограничения на GET (спорно)
- По крону чистить папку со старыми авторизованными куками
- Папка с авторизованными куками будет содержать очень много файлов, для ФС сервера это не очень хорошо. Решение от rozhik,
- У меня авторизатор очень простой, по желанию в него можно(нужно) добавить капчу, смс, email и т.д
- ifIsEvil, надо постараться без него (решения у меня пока нет)
- Бан на игнорирование странички-авторизатора. (самое простое — писать в логи и убивать ботов по ним, адепты cut | uniq | sort будут очень рады)
Ну и как обычно: опечатки в личку, вопросы/дополнения/улучшения в комментарии.
Ссылки: Nginx limit_req_module, базовые вещи по ядру и прокси.
Спасибо, что дочитали до конца!
PS
По роду деятельности я не очень плотно занимаюсь настройкой серверного ПО и прочим сисадминством, поэтому я считаю это решение эскизным, хотя оно и отработало в продакшене какое-то время(затем у меня просто дошли руки переименовать админки).