1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6
  7. 7

# теория · шаг 2 из 7

auth_basic и satisfy

Коротко

Второй рубеж после адреса - пароль. auth_basic даёт его одной строкой, без приложения, без сессий и без базы.

  • Клиент без пароля получает 401 и заголовок WWW-Authenticate - браузер показывает системное окно.
  • satisfy any превращает «адрес И пароль» в «адрес ИЛИ пароль»: из офиса без пароля, снаружи с паролем.
  • auth_basic off; во вложенном блоке открывает его целиком - замер даёт 200 без единой попытки авторизации.
  • auth_delay 3s замедляет неверные попытки: замер - 3,004 с против 0,0008.

Если знаешь про satisfy any и помнишь, что basic-пароль ездит открытым текстом, - листай до «Теперь сам».

Сначала ответь сам

/etc/nginx/conf.d/shop.local.confhttp server
auth_basic           "закрыто";
auth_basic_user_file /etc/nginx/htpasswd;

location /open/ { auth_basic off; }

Что вернёт /open/ без пароля?

$  команда
curl -s -o /dev/null -w '%{http_code}\n' http://shop.local/open/
↳  выводcurl без -u (флаг -u задаёт логин:пароль)
200

Двести, и это задумано: auth_basic off; - штатный способ открыть один подкаталог внутри закрытого сайта. Опасен он тем, что выглядит безобидно и пишется одной строкой - в чужом конфиге такую строку легко не заметить при ревью.

На что это похоже

Домофон на подъезде. Он не знает, кто ты, - он знает код. Код один на всех, меняется редко, и любой, кому его назвали, входит как свой.

Этого хватает, чтобы отсечь случайных прохожих, и не хватает ни для чего серьёзнее. auth_basic ровно такой: рубеж от сканеров и любопытных, а не система учётных записей.

Механизм: два ответа и один заголовок

браузер nginx GET /admin/ 401 + WWW-Authenticate: Basic realm="закрыто" GET /admin/ + Authorization: Basic dmFzeWE6c2VjcmV0 200 Basic - это base64, а не шифрование: без TLS пароль виден в сети открытым
Значение Authorization - обратимое кодирование пары «логин:пароль».

Правило

/etc/nginx/conf.d/shop.local.confhttp server location /admin/
auth_basic           "admin area";   # строка в кавычках - произвольный текст окна («realm»)
auth_basic_user_file /etc/nginx/htpasswd;
$  команда
htpasswd -c /etc/nginx/htpasswd vasya

htpasswd - утилита из пакета apache2-utils; ключ -c создаёт новый файл, без него пользователь добавляется в существующий. После запуска команда спросит пароль в терминале (ввод не виден) и запишет его хешем. Права на файл ставят 640 (chmod 640 /etc/nginx/htpasswd), владельцем - пользователя, под которым работают воркеры (www-data в пакете, nginx в образе), чтобы файл читал nginx, но не посторонние.

Замер поведения:

запрос ответ
без пароля 401
с верным паролем 200
в блоке с auth_basic off 200

Разбор: satisfy any

/etc/nginx/conf.d/shop.local.confhttp server location /admin/
satisfy any;

allow 192.168.1.0/24;
deny  all;

auth_basic           "admin";
auth_basic_user_file /etc/nginx/htpasswd;

По умолчанию действует satisfy all - выполнены должны быть оба условия: и адрес подошёл, и пароль верный. С satisfy any достаточно одного:

↳  выводзапрос с адреса из allow, без пароля
200

Из офиса пускает молча, из дома спрашивает пароль. Это самая ходовая схема для внутренних панелей, и она же объясняет, зачем в предыдущем уроке allow без пароля был назван первым рубежом, а не единственным.

Обратная ошибка тоже встречается: satisfy all (умолчание) вместе с allow из офиса и паролем означает, что из дома не пустят даже с верным паролем - адрес не подошёл.

Разбор: auth_delay против перебора

Пароль один, и его будут подбирать. limit_req из прошлой главы отсекает частоту, но у него есть неприятная сторона: он отвечает 503 или 429, и по этому ответу подбирающий понимает, что нашёл живую точку. auth_delay работает иначе - он просто задерживает неверные попытки:

/etc/nginx/conf.d/shop.local.confhttp server location /admin/
auth_delay 3s;
↳  выводневерный пароль
без auth_delay:  0,0008 с
с auth_delay 3s: 3,004 с

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

Что ломается без этого

Пароль ездит открытым текстом. Basic - это base64 от строки логин:пароль, обратимое кодирование. По http его читает любой на пути; поэтому auth_basic без TLS не имеет смысла вовсе, а с TLS - имеет ровно столько, сколько стоит один общий пароль.

Пароль общий и не отзывается. Уволился человек - меняешь пароль всем. Никакого «кто именно зашёл» в логах нет (точнее, есть $remote_user, но он показывает только имя из файла, а имён обычно одно-два). Для внутренней панели это приемлемо, для системы с ролями - нет.

Забытый auth_basic off. Строка в одном вложенном блоке открывает его целиком, и заметить её при беглом чтении конфига трудно. Проверять надо запросом, а не глазами:

$  команда
curl -s -o /dev/null -w '%{http_code}\n' https://shop.local/admin/static/app.js

Зачем это в работе

auth_basic уместен там, где нужно быстро закрыть что-то от интернета и не строить систему: stub_status, панель метрик, тестовый стенд, страница разработчика. Всё, где пользователей больше двух и важно знать, кто вошёл, делают приложением - или auth_request из следующего урока, который отдаёт решение внешнему сервису.

Одна практическая деталь: auth_basic действует и на подзапросы - внутренние запросы, которые nginx делает сам (например, для auth_request или error_page). Если внутри закрытого паролем location стоит proxy_pass на бэкенд, который сам ходит за чем-то через nginx, эти внутренние запросы тоже упрутся в пароль. Лечится auth_basic off; в служебном блоке - осознанно и с комментарием.

Теперь сам

Панель закрыли auth_basic и limit_req zone=login burst=3 nodelay;. Через месяц в логе видно тысячи 401 с одного адреса в час. Лимит работает, а перебор идёт. Как так?

limit_req с burst=3 nodelay пропускает пачку, а дальше отдаёт 429 или 503 - но подбирающему это не мешает: он просто держит темп чуть ниже порога и продолжает. Тысяча попыток в час укладывается в один запрос каждые четыре секунды. Добавь auth_delay 3s: он не отвечает отказом, а тратит время атакующего на каждой неверной попытке, и темп падает принудительно.

Главное

auth_basic - один общий пароль без сессий и учёта, рубеж от сканеров и любопытных. Без пароля клиент получает 401 с WWW-Authenticate, а сам пароль едет в base64, то есть без TLS не защищён вовсе. satisfy any превращает «адрес и пароль» в «адрес или пароль» - типовая схема для внутренних панелей. auth_basic off; в дочернем блоке открывает его целиком, и проверять это надо запросом. Перебор тормозят не лимитом, а auth_delay: замер даёт 3 секунды на неверную попытку против тысячной доли секунды.

Комментарии

Пока нет комментариев. Будь первым!

Оставить комментарий

Комментарий появится после проверки. Email не публикуется. Войти, чтобы не вводить имя каждый раз.