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

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

allow и deny: порядок и наследование

Коротко

Самый дешёвый способ закрыть админку - не пускать в неё из интернета. Модуль крошечный, а ошибок вокруг него много, и все из-за двух свойств.

  • Первое подошедшее правило выигрывает. deny all; первой строкой делает все allow ниже недостижимыми.
  • Не подошло ни одно правило - запрос проходит. Забыл deny all; в конце - список не ограничивает никого.
  • Наследование целиком или никак: своя строка в location выбрасывает весь внешний список. Замер: сайт с deny all на уровне server отдал 200.
  • return в том же блоке выполняется раньше проверки доступа.

Если помнишь про порядок правил и про наследование «всё или ничего» - листай до «Теперь сам».

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

/etc/nginx/conf.d/shop.local.confhttp
server {
    listen 80;
    deny all;                       # закрыли весь сайт

    location /health/ {
        allow 10.0.0.0/8;           # мониторингу можно
    }
}

Что получит запрос к /health/ с чужого адреса 203.0.113.5?

$  команда
curl -s -o /dev/null -w '%{http_code}\n' http://shop.local/health/
↳  выводcurl с адреса вне 10.0.0.0/8
200

Двести. Сайт открыт. Правило deny all для этого location не действует вовсе: список наследуется только тогда, когда на текущем уровне нет ни одной своей строки. Появилась своя - внешний список выброшен целиком, а собственный на этот адрес не сработал, и запрос прошёл по правилу «ни одно правило не подошло».

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

Инструкция охраннику: «пускать по списку». Списков два - общий на входе в здание и свой на этаже. Правило простое и опасное: если у этажа есть свой список, общий не смотрят вовсе.

Дописали на этаже одну фамилию - и общий запрет для этого этажа перестал существовать. Ровно это и происходит в конфиге, который выглядит закрытым.

Механизм: два свойства списка

порядок allow 192.168.1.0/24; deny all; закрыто всем, кроме офиса deny all; allow 192.168.1.0/24; закрыто всем: строка allow недостижима наследование своя строка в location выбрасывает весь внешний список дописать одно правило к унаследованным нельзя
Список читается сверху вниз до первого совпадения; уровни не складываются.

Правило

/etc/nginx/conf.d/shop.local.confhttp server location /admin/
allow 192.168.1.0/24;
deny  all;

Полный список на каждом уровне, где вообще есть ограничения. Никакого способа «дописать одно правило к унаследованным» в этом модуле нет.

Замер трёх вариантов на одном стенде:

что написано ответ с моего адреса
deny all; затем allow мой-адрес; 403 - allow недостижим
allow мой-адрес; затем deny all; 200
allow чужой-адрес; без deny all 200 - ни одно правило не подошло

Третья строка - самая коварная: конфиг выглядит как ограничение, а не ограничивает никого.

Разбор: что видит отвергнутый

Отказ - это 403 и строка в логе:

↳  выводerror.log
[error] access forbidden by rule, client: 203.0.113.5, request: "GET /admin/ HTTP/1.1"

Формулировка «by rule» отличает отказ по allow/deny от отказа по правам на файл (там будет Permission denied) и от отказа приложения. Если в логе этой строки нет, а клиент получает 403 - смотри не сюда.

Свой ответ вместо стандартного делают через error_page (403 = @silence - «код 403 обслужи именованным блоком @silence»; знак = берёт итоговый код из него):

/etc/nginx/conf.d/shop.local.confhttp server location /admin/
allow 192.168.1.0/24;
deny  all;
error_page 403 = @silence;
/etc/nginx/conf.d/shop.local.confhttp server
location @silence { return 404; }

Отдавать 404 вместо 403 - осмысленный приём для админки: сканеру интернета не за что зацепиться, 403 однозначно говорит «тут что-то есть».

Разбор: доступ проверяется не первым

Порядок фазРазбирается в разделе 13, глава «return против rewrite»: Фазы: кто и когда решает из тринадцатого раздела действует и здесь:

/etc/nginx/conf.d/shop.local.confhttp server location /admin/
deny all;
return 200 "ok\n";

Такой блок никого не ограничивает: return выполняется на фазе rewrite, а deny - на фазе access, то есть позже. Клиент получит 200.

Ловушка не теоретическая: так пишут заглушки для проверки правил, получают 200 и делают вывод, что модуль не работает. Проверять надо на том, что отдаёт файл или уходит на бэкенд.

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

Адрес за прокси. allow 192.168.1.0/24; смотрит на $remote_addr. За CDN это адрес CDN: правило либо не пускает никого, либо пускает всех, кто пришёл через тот же CDN, - то есть весь интернет. Сначала realip из седьмого раздела, потом allow.

IPv6, о котором забыли. allow 192.168.1.0/24; deny all; закрывает офис от самого себя, если тот пришёл по IPv6: адрес не подошёл ни под одно правило, сработал deny all. Диагностируется мгновенно - в логе будет адрес вида 2a02:....

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

allow/deny - первый рубеж, а не единственный. Он ничего не знает о том, кто человек: адрес не аутентификация. Домашний интернет сотрудника, командировка, мобильный - и правило начинает мешать работе, а не защищать.

Поэтому типовая схема на две ступени: адрес плюс пароль (auth_basicРазбирается в разделе 12, глава «Доступ: allow, deny, auth_basic, auth_request»: auth_basic и satisfy - запрос пароля, следующий урок), и при этом «или», а не «и»:

/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 any означает «достаточно одного условия из списка» (по умолчанию all - нужны все): из офиса пускает без пароля, снаружи спрашивает пароль. Про satisfy и auth_basic - следующий урок.

Теперь сам

В конфиге на уровне server стоит deny all;, а в location /static/ - expires 30d;. Статика открывается из интернета. Это ошибка наследования?

Нет. expires - не директива доступа, своих allow/deny в блоке нет, значит внешний список наследуется целиком и deny all действует. Если статика всё-таки открывается, ищи другой location, который её перехватывает - например, регулярку по расширению, объявленную выше и со своим allow.

Главное

Список allow/deny читается сверху вниз до первого совпадения: deny all первой строкой делает всё ниже недостижимым. Если не подошло ни одно правило, запрос проходит - поэтому список почти всегда заканчивается deny all. Наследование целиком или никак: своя строка в location выбрасывает внешний список, и замер это показывает - сайт с deny all на уровне server отдаёт 200 там, где в блоке написан один allow. Проверка доступа идёт позже return, а за прокси она смотрит на адрес прокси, пока не настроен realip.

Комментарии

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

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

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