Контроль доступа: allow/deny, auth_basic, auth_request и mTLS

Рейтинг: 68.5% · 14 голосов
Самый подробный курс по Nginx - от первого конфига до production API-gateway. Архитектура и event loop, контексты и location, виртуальные хосты, статика, reverse proxy и upstream-балансировка, кэширование, HTTPS и TLS (post-quantum), HTTP/2 и HTTP/3 QUIC, безопасность и rate limiting, realip, JSON-логи и сквозная трассировка, метрики, тюнинг и траблшутинг. Плюс OpenResty (Lua, фазы, JWT, динамика) и Angie (нативные Prometheus, ACME, dynamic upstream). На основе эталонного gateway и лучших практик. Для новичков и SRE/DevOps. Актуально на 2026 (nginx 1.30/1.31, Angie, ingress-nginx EOL -> Gateway API).
Ответить
Аватара пользователя
Igor_NgINX
Сообщения: 44
Зарегистрирован: 11 май 2026, 05:31

Контроль доступа: allow/deny, auth_basic, auth_request и mTLS

Сообщение Igor_NgINX »

Оглавление курса (44)
  1. Что такое Nginx и почему он захватил веб
  2. Установка Nginx, Angie и OpenResty: пакеты, версии, Docker
  3. Первый конфиг с нуля: минимальный сервер за 5 минут
  4. Архитектура процессов: master, worker и event loop
  5. Анатомия конфигурации: контексты, наследование и модульность
  6. Виртуальные хосты: server и server_name
  7. Директива location: матчинг и приоритеты по шагам
  8. Раздача статики: root, alias, try_files, sendfile
  9. Переменные, map и блок if
  10. rewrite, return и канонизация URL
  11. Работа с заголовками: add_header и proxy_set_header
  12. Модель доверия прокси-заголовков: realip и защита от спуфинга
  13. Сжатие: gzip, brotli и zstd
  14. Reverse proxy: proxy_pass и проброс запроса
  15. upstream и балансировка нагрузки
  16. Таймауты, повторы и устойчивость прокси
  17. FastCGI и PHP: nginx + PHP-FPM (и uwsgi/scgi)
  18. WebSocket, gRPC и стриминг через Nginx
  19. Кэширование ответов: proxy_cache и микрокэш
  20. HTTPS и TLS: сертификаты, протоколы, шифры (2026)
  21. Let's Encrypt и ACME: certbot, acme.sh, нативный ACME Angie
  22. HTTP/2 и HTTP/3 (QUIC) в 2026
  23. Безопасность: заголовки, скрытие версии, ограничения
  24. Rate limiting и защита от перегрузки
  25. Контроль доступа: allow/deny, auth_basic, auth_request и mTLS (вы здесь)
  26. Логирование: access_log, форматы и структурированный JSON
  27. Сквозная трассировка запросов и OpenTelemetry
  28. Метрики и мониторинг Nginx
  29. Производительность и тюнинг под нагрузку
  30. Траблшутинг: 502, 504, 403, 404 и debug-лог
  31. OpenResty: Nginx как платформа на LuaJIT
  32. Фазы обработки запроса и Lua-хуки
  33. Lua API: ngx.*, shared dict, cosocket и lua-resty-core
  34. Практика OpenResty: авторизация, JWT, кэш, Redis
  35. Angie: современный форк Nginx 2026
  36. Динамическая конфигурация и service discovery
  37. Nginx как API-gateway и Kubernetes Gateway API
  38. Динамические модули, njs и WAF
  39. Stream-модуль: проксирование TCP и UDP
  40. Деплой, перезагрузка и конфиг как код
  41. Сценарий: статика, SPA и кэширование за CDN
  42. Сценарий: микросервисный gateway по образцу ngx-trace-gateway
  43. Капстоун: собираем production-gateway с нуля
  44. Чек-лист безопасности, CVE 2026 и аудит конфигурации
Рано или поздно ты упираешься в один и тот же вопрос: "кто вообще имеет право сюда зайти?". Админка Grafana, торчащая в интернет голой. Партнёрский API, которым пользуются три компании, а не вся планета. Внутренний дашборд, который "временно" открыли всем и забыли. Контроль доступа на уровне nginx - это первый рубеж, который срабатывает ДО того, как запрос дойдёт до приложения. И это огромное преимущество: бэкенд может вообще не знать про аутентификацию, а ты закрываешь дыру правкой одного конфига и reload без даунтайма.

В этом уроке разберём четыре механизма от простого к сложному: фильтр по IP (allow/deny), пароль (auth_basic), внешнюю авторизацию через бэкенд (auth_request - это и есть современный forward-auth для SSO) и клиентские сертификаты (mTLS). И, что важнее, научимся их комбинировать через satisfy, потому что в реальности один механизм почти никогда не хватает.

nginx allow deny: фильтр по IP и подсетям

Самый дешёвый способ закрыть доступ - пускать только с известных адресов. За это отвечает модуль ngx_http_access_module с директивами allow и deny. Синтаксис простой: адрес, подсеть в CIDR, all или unix-сокет.

Код: Выделить всё

location /admin/ {
    allow 203.0.113.10;       # конкретный IP офиса
    allow 10.0.0.0/8;         # вся внутренняя сеть
    allow 2001:db8::/32;      # IPv6 тоже работает
    deny  all;                # все остальные - 403
    proxy_pass http://admin_backend;
}
Ключевой момент, на котором спотыкаются все новички - порядок правил. nginx проверяет директивы сверху вниз и останавливается на ПЕРВОМ совпадении. То есть deny all в конце - это "всё, что не разрешено выше, запрещено". Если поставить deny all первым, дальше уже ничего не проверится и доступ закроется для всех. Запомни как мантру: сначала перечисляем разрешённых, последней строкой deny all.

Директивы наследуются от http к server к location, но - внимание - если в location появилась хоть одна своя allow/deny, она ПОЛНОСТЬЮ перекрывает унаследованный набор, а не дополняет его. Это типичная грабля: ты добавил один allow в location, думая, что он плюсуется к глобальному, а на деле обнулил всю внешнюю политику.

Теперь самое важное и недооценённое. allow/deny смотрит на $remote_addr - адрес того, кто открыл TCP-соединение с nginx. Если перед nginx стоит балансировщик, CDN или ещё один прокси, то $remote_addr - это адрес ЭТОГО прокси, а не реального клиента. Твой allow по офисному IP не сработает никогда, потому что nginx видит только адрес фронтового LB. Лечится это модулем realip (отдельный большой урок), который подменяет $remote_addr на адрес из X-Forwarded-For. Но запомни связку прямо сейчас: без корректно настроенного realip любой контроль по IP за прокси - это иллюзия. И ещё опаснее обратное: если ты доверяешь X-Forwarded-For от кого попало (set_real_ip_from 0.0.0.0/0), клиент сам подставит себе нужный IP в заголовок и обойдёт твой allow. Доверяй realip только известным сетям своих прокси и CDN.

Изображение

nginx auth_basic: пароль за тридцать секунд

IP-фильтр хорош, но адреса меняются, а сотрудники работают из дома. Тогда добавляем пароль. nginx basic auth - это модуль ngx_http_auth_basic_module, ему нужны две вещи: директива auth_basic с текстом подсказки и файл с логинами-паролями.

Файл создаём утилитой htpasswd из пакета apache2-utils (или httpd-tools на RHEL):

Код: Выделить всё

# создать файл и первого пользователя (-c создаёт файл, -B = bcrypt)
htpasswd -B -c /etc/nginx/.htpasswd alice
# добавить ещё одного БЕЗ -c, иначе файл перезапишется
htpasswd -B /etc/nginx/.htpasswd bob
Важно про -c: флаг создания файла затирает существующий. Добавляешь второго юзера с -c - потеряешь первого. Это классическая ошибка "у меня пропали все пароли". И обязательно используй -B (bcrypt): дефолтный формат htpasswd - это устаревший MD5-crypt, который сегодня брутится быстро.

Конфиг nginx auth_basic выглядит так:

Код: Выделить всё

location /admin/ {
    auth_basic           "Restricted Admin Area";
    auth_basic_user_file /etc/nginx/.htpasswd;
    proxy_pass http://admin_backend;
}
Строка "Restricted Admin Area" - это realm, текст в окне ввода пароля браузера. Чтобы временно выключить пароль в каком-то вложенном location, ставим auth_basic off;. Браузер шлёт логин и пароль в заголовке Authorization в виде base64 (НЕ шифрование, просто кодировка), поэтому nginx auth_basic безопасен ТОЛЬКО поверх HTTPS - иначе пароль летит открытым текстом. Минусы метода: один общий файл, нет ролей, нет логаута, пароль кэшируется браузером. Для быстрого закрытия служебной страницы - идеально. Для пользовательского портала - нет.

nginx auth_request: внешняя авторизация и forward-auth для SSO

А вот это - самый мощный и современный механизм. Идея: перед тем как проксировать запрос на бэкенд, nginx делает внутренний субзапрос к отдельному сервису авторизации и спрашивает "этому пользователю можно?". Если сервис ответил 2xx - запрос идёт дальше. Если 401 или 403 - nginx сразу отдаёт отказ, бэкенд даже не трогается. Это паттерн forward-auth, на нём построены oauth2-proxy, Authelia, Authentik, Vouch - то есть весь современный self-hosted SSO.

Работает это через модуль ngx_http_auth_request_module (его нужно собрать в nginx, в стандартных пакетах он есть). Минимальная схема:

Код: Выделить всё

location / {
    auth_request /_auth;                       # спросить разрешение
    auth_request_set $user $upstream_http_x_user;  # забрать заголовок из ответа
    proxy_set_header X-Authenticated-User $user;   # пробросить на бэкенд
    proxy_pass http://app_backend;
}

# внутренний эндпойнт, наружу не доступен
location = /_auth {
    internal;
    proxy_pass http://127.0.0.1:4180/oauth2/auth;
    proxy_pass_request_body off;               # тело субзапросу не нужно
    proxy_set_header Content-Length "";
    proxy_set_header X-Original-URI $request_uri;
}
Разберём по косточкам. Директива auth_request /_auth говорит: перед обработкой сделай субзапрос к location /_auth. Этот location помечен internal - его нельзя дёрнуть снаружи напрямую, только субзапросом. Внутри он проксируется на сервис авторизации (тут oauth2-proxy на 4180).

Теперь грабли, на которых горят все:
  • Тело и метод субзапроса. auth_request всегда шлёт субзапрос методом GET (наследует метод исходного запроса для тела, но семантически это проверка) и по умолчанию тащит тело оригинального запроса. Для аутентификации тело не нужно и только мешает - поэтому proxy_pass_request_body off и обнуление Content-Length. Иначе большой POST-аплоад будет целиком гоняться ещё и в auth-сервис.
  • Возврат заголовков. Сам auth_request НЕ пробрасывает заголовки из ответа авторизатора на бэкенд автоматически. Чтобы достать, например, имя пользователя, которое сервис вернул в заголовке X-User, используешь auth_request_set $user $upstream_http_x_user - префикс $upstream_http_ плюс имя заголовка в нижнем регистре с подчёркиваниями. Потом этот $user пробрасываешь через proxy_set_header.
  • Только код ответа. auth_request смотрит исключительно на HTTP-статус субзапроса: 2xx - пускаем, 401/403 - блок, всё остальное (500, таймаут) - 500 клиенту. Тело ответа авторизатора игнорируется полностью.
Чтобы при отказе не отдавать сухой 401, а редиректить на страницу логина, используют связку error_page и named location:

Код: Выделить всё

error_page 401 = @signin;
location @signin {
    return 302 https://auth.example.com/oauth2/start?rd=$scheme://$host$request_uri;
}
Так получается полноценный nginx авторизация-флоу: неавторизованного гонят на SSO, после логина возвращают обратно. Это и есть тот "forward-auth", который ты видишь в каждом гайде по Authelia. Главное преимущество - приложение за прокси вообще ничего не знает про OAuth, оно просто читает заголовок X-Authenticated-User, который ему уже проверенным положил nginx.

satisfy: комбинируем IP и пароль

Теперь склеим механизмы. Директива satisfy управляет логикой, когда на location навешано несколько проверок доступа (allow/deny + auth_basic или auth_request). Значений два:
  • satisfy all (по умолчанию) - доступ только если пройдены ВСЕ проверки. И правильный IP, и правильный пароль. Логическое И.
  • satisfy any - достаточно ОДНОЙ пройденной проверки. Или из доверенной сети, или ввёл пароль. Логическое ИЛИ.
Классический сценарий "закрыть админку": из офиса пускаем без пароля, снаружи - требуем пароль.

Код: Выделить всё

location /admin/ {
    satisfy any;              # достаточно одного условия

    allow 203.0.113.0/24;     # офисная сеть - без вопросов
    deny  all;                # остальным IP-проверка не пройдена

    auth_basic           "Admin";
    auth_basic_user_file /etc/nginx/.htpasswd;

    proxy_pass http://admin_backend;
}
Логика satisfy any здесь: если клиент из 203.0.113.0/24 - allow сработал, доступ дан, пароль не спросят. Если клиент снаружи - deny all "провалил" IP-проверку, но раз satisfy any, nginx даёт второй шанс через пароль. Введёшь правильный - зайдёшь. А если поставить satisfy all - то даже из офиса попросят пароль (нужны оба условия). Выбирай осознанно. Для строгой админки лучше all: и из доверенной сети, и с паролем.

nginx mtls: клиентские сертификаты для B2B и zero-trust

Пароль может утечь, IP можно подделать. Для партнёрского API, межсервисного трафика и zero-trust-архитектуры применяют mTLS (mutual TLS) - когда не только клиент проверяет сертификат сервера, но и сервер требует валидный сертификат от клиента. Без правильного клиентского сертификата TLS-рукопожатие просто не завершится. Это сильнейшая из рассмотренных проверок: подделать приватный ключ нельзя, а отозвать доступ можно, перевыпустив CA или добавив сертификат в CRL.

Настройка nginx mtls идёт в server-блоке поверх обычного TLS:

Код: Выделить всё

server {
    listen 443 ssl;
    http2 on;
    server_name api.example.com;

    ssl_certificate     /etc/nginx/tls/server.crt;
    ssl_certificate_key /etc/nginx/tls/server.key;

    # CA, которым подписаны клиентские сертификаты
    ssl_client_certificate /etc/nginx/tls/partners-ca.crt;
    ssl_verify_client      on;       # требовать клиентский сертификат
    ssl_verify_depth       2;        # глубина цепочки до доверенного CA

    location /partner-api/ {
        # передаём бэкенду, КТО пришёл
        proxy_set_header X-Client-DN     $ssl_client_s_dn;
        proxy_set_header X-Client-Verify $ssl_client_verify;
        proxy_pass http://partner_backend;
    }
}
Что здесь происходит:
  • ssl_client_certificate - файл с сертификатом(ами) CA, которым ты доверяешь. Любой клиентский сертификат, подписанный этим CA, будет принят. Фактически ты сам становишься удостоверяющим центром для своих партнёров и выдаёшь им сертификаты.
  • ssl_verify_client on - жёстко требовать валидный клиентский сертификат. Нет сертификата или он не прошёл проверку - рукопожатие обрывается с ошибкой TLS, до location дело не доходит. Есть ещё значение optional - сертификат запрашивается, но соединение не рвётся, а результат кладётся в переменную (полезно, когда mTLS нужен только на части путей).
  • ssl_verify_depth 2 - максимальная длина цепочки от клиентского сертификата до доверенного CA. Если у партнёра промежуточный CA, глубины 1 не хватит.
Главная рабочая переменная - $ssl_client_verify. Она принимает значения SUCCESS, FAILED:причина или NONE. При ssl_verify_client optional проверять её надо руками:

Код: Выделить всё

location /partner-api/ {
    if ($ssl_client_verify != SUCCESS) {
        return 403;
    }
    proxy_pass http://partner_backend;
}
Полезно пробросить на бэкенд $ssl_client_s_dn (subject DN сертификата - по сути "имя" клиента) - так приложение узнаёт, какой именно партнёр пришёл, и применяет свои правила. Это и есть основа zero-trust: каждый запрос несёт криптографически проверенную личность, а не анонимный IP.

Типичные грабли
  • deny all первой строкой - закрывает доступ всем мгновенно, остальные allow не проверяются. Разрешённых пишем сверху.
  • allow/deny за прокси без realip - фильтруешь адрес LB, а не клиента. И наоборот, доверять X-Forwarded-For всем подряд - дыра.
  • htpasswd -c поверх существующего файла - затирает всех пользователей. -c только при создании.
  • Basic Auth по HTTP - пароль уходит открытым текстом в base64. Только поверх TLS.
  • auth_request с телом запроса - забыл proxy_pass_request_body off, и каждый POST дублируется в auth-сервис.
  • Перепутал $upstream_http_ префикс в auth_request_set - заголовок из ответа авторизатора не подхватится, переменная будет пустой.
  • mTLS optional без проверки $ssl_client_verify - думаешь, что закрыл, а пускаешь всех, потому что optional не рвёт соединение.
  • Забыл ssl_verify_depth при промежуточном CA - валидные клиенты получают отказ.
Мини-лаба: закрываем админку IP плюс паролем

Повтори руками по шагам на тестовом nginx.
  • Создай файл паролей: htpasswd -B -c /etc/nginx/.htpasswd testuser (введи пароль).
  • Добавь location в свой server-блок:

Код: Выделить всё

location /secret/ {
    satisfy any;
    allow 127.0.0.1;          # localhost - без пароля
    deny  all;
    auth_basic           "Lab Area";
    auth_basic_user_file /etc/nginx/.htpasswd;
    return 200 "you are in\n";
}
  • Проверь конфиг и перезагрузи: nginx -t и nginx -s reload.
  • Запрос с localhost: curl http://127.0.0.1/secret/ - должен пустить без пароля (сработал allow при satisfy any).
  • Запрос с другого IP без пароля - получишь 401. С паролем: curl -u testuser:ПАРОЛЬ http://СЕРВЕР/secret/ - пустит.
  • Поменяй satisfy any на satisfy all, reload. Теперь даже с localhost попросят пароль - оба условия обязательны. Прочувствуй разницу.
Контрольные вопросы
  • Почему deny all должна стоять ПОСЛЕ всех allow, а не до них? Что проверяет nginx, дойдя до первого совпадения?
  • Чем satisfy any отличается от satisfy all, если на location висят и allow/deny, и auth_basic? Какой вариант для строгой админки?
  • Зачем в auth_request ставят proxy_pass_request_body off, и как достать из ответа авторизатора заголовок X-User на бэкенд?
  • Что лежит в $ssl_client_verify и почему при ssl_verify_client optional эту переменную обязательно проверять самому?
Итог

У тебя теперь четыре уровня контроля доступа на шлюзе. allow/deny - быстрый IP-фильтр, но помни про realip за прокси. auth_basic - пароль за тридцать секунд для служебных страниц, только поверх HTTPS. auth_request - современный forward-auth, фундамент SSO через oauth2-proxy и Authelia, где nginx спрашивает разрешение у внешнего сервиса до проксирования. mTLS - криптографическая личность клиента для B2B и zero-trust. И главное - satisfy, который позволяет комбинировать их в одну осмысленную политику: из офиса свободно, снаружи по паролю, а к партнёрскому API - только с сертификатом.
👍 ❤️2 🔥5 😄 🤔3
Аватара пользователя
tor_kun
Сообщения: 1
Зарегистрирован: 12 май 2026, 04:41

Re: Контроль доступа: allow/deny, auth_basic, auth_request и mTLS

Сообщение tor_kun »

Долго не мог понять почему allow по офисному IP не срабатывал на проде, а оно за балансером было и nginx видел адрес LB. Realip всё чинил, спасибо что прямо в этом уроке предупредили.
👍 ❤️ 🔥 😄 🤔
Аватара пользователя
asyncoverflow
Сообщения: 1
Зарегистрирован: 24 май 2026, 10:48

Re: Контроль доступа: allow/deny, auth_basic, auth_request и mTLS

Сообщение asyncoverflow »

Связка satisfy any с allow офис + auth_basic снаружи - прям то что искал для grafana. А для auth_request не сразу дошло что префикс upstream_http_ это имя заголовка в нижнем регистре, потерял на этом полчаса.
👍1 ❤️2 🔥 😄 🤔
Ответить
← Предыдущая глава
Rate limiting и защита от перегрузки
Следующая глава →
Логирование: access_log, форматы и структурированный JSON

Все главы курса «Nginx профессионально: от первого конфига до API-gateway на OpenResty и Angie»

Поделиться темой: ✈ Telegram VK
Похожие запросы: Безопасность nginx: заголовки и hardeningRate limiting в nginx: защита от перегрузкиКонтроль доступа в nginx: auth и allow/denyОшибки nginx 403, 404, 504: диагностикаHTTPS и TLS в nginx: сертификаты и протоколыLet's Encrypt для nginx: бесплатный сертификат

Вернуться в «Nginx профессионально: от первого конфига до API-gateway на OpenResty и Angie»

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и 2 гостя