Увидел у друга такую штуку, как Nginx proxy manager(далее — npm) и захотел поставить его в себе.
По сути, это тот же nginx, но с графической оболочкой, которая вполне приятная:


Здесь можно легко настроить реверс-прокси для всех своих контейнеров, выпустив Let’s Encrypt сертификаты, а также можно настраивать ACL для доступа к ресурсам. Например, к самому npm я настроил доступ только с двух адресов, к которым у меня всегда есть доступ. Также доступ есть по http через ip:port, но, на уровне iptables.
Собственно, развертывание самого контейнера с npm довольно простое. Собираем compose file:
services:
app:
container_name: nginx_proxy_manager
image: 'jc21/nginx-proxy-manager:2.15.1'
restart: unless-stopped
ports:
# These ports are in format <host-port>:<container-port>
- '80:80' # Public HTTP Port
- '443:443' # Public HTTPS Port
- '81:81' # Admin Web Port
environment:
TZ: "Europe/Moscow"
volumes:
- ./data:/data
- ./letsencrypt:/etc/letsencrypt
networks:
- proxy_network
networks:
proxy_network:
external: true
Здесь я по сути ничего и не менял, так как в этом нет необходимости, так как безопасность осуществляется иным образом.
Поднимаем контейнер:
docker compose up -d
Нас будет приветствовать экран создания учетной записи администратора. Ни в коем случае не вводите e-mail в верхнем регистре(у меня это никнейм), так как в базу оно так и запишется, а код приложения ищет в базе e-mail в нижем регистре. далее приведу кусок кода, который за это отвечает app/internal/token.js:
.where("email", data.identity.toLowerCase().trim())
в моем случае пришлось редактировать e-mail в базе и все заработало. понял я это после того, как несколько раз удалил всю data контейнера…
Не забываем настроить Two-Factor Auth здесь:

Далее npm заработал, но нужно было прикрутить к нему все свои сервисы, которые ранее проксировал nginx на машине.
Рассмотрим на примере моего affine. Про все параметры рассказывать не буду, так как есть официальная документация, но расскажу про то, ради чего я отказался от обычного пакетного nginx в пользу npm.
Начнём с добавления контейнера в одну сеть с npm.
Поскольку связи между контейнерами нет, то обращаться можно либо по ip самого контейнера, но это плохая практика, либо поместить affine и npm в одну сеть. Affine по умолчанию создает 3 контейнера: affine_server, affine_redis, affine_postgres. Нам необходимо добавить в сеть только affine_server.
Возьму сеть, которую я создал ранее: proxy_network и добавим ее в compose-файл affine:
services:
affine:
...
networks:
- proxy_network
...
networks:
proxy_network:
external: true
external: true — так как эта сеть была создана ранее. Таким образом контейнеры будут работать в одной сети и внутри npm можно будет обращаться к нему по hostname(имени контейнера).
Теперь про создание реверс-прокси.
Ранее у меня уже были выпущены сертификаты, но, поскольку npm хранит сертификаты в своем формате в папке ./letsencrypt, то проще не копировать вручную, хотя, это тоже возможно, а просто выпустить новые, так как это делается в пару кликов в GUI.
В дашборде заходим в Proxy hosts

Затем нажимаем на кнопку Add Proxy Host

и приступаем к созданию хоста/
В поле с адресом вводим домен и нажимаем Tab

SSL у меня на уровне npm, поэтому affine работает в http режиме, шифрования внутри docker сети нет.
В forward hostname вводим hostname контейнера(или его IP), так как он находится с npm в одной сети и порт, на котором работает сервис(не host port, а именно порт на котором работает сервис, т.е. внутренний, container port).

Из всех параметров я включил только поддержку веб-сокета, чтобы добавить заголовки Upgrade и Connection: upgrade, так как речь про приложение с real-time функциями.
Защиту от эксплойтов тоже можно включить, но это не обязательно.
Вкладку Custom Locations я пропустил, так как мне не нужно добавлять дополнительные location-блоки в конфиг(например, для какого-нибудь api).
Во вкладке SSL выбираем создание нового сертификата

В параметрах ниже выбираем:
Force SSL— редирект HTTP -> HTTPS;HTTP/2 Support— ускоряет загрузку ресурса за счет мультиплексирования протоколов, не обязательный параметр в моем случае;HSTS Enabled— благодаря этому пункту браузер запоминает, что сайт работает по протоколу HTTPS и дропает все попытки зайти на него через HTTP;HSTS Subdomains— распространяет HSTS на все поддомены, но если есть поддомены без SSL, то он может стать недоступен. Я не активировал;DNS Challenge— альтернативный способ выпуска сертификата через DNS записи вместо HTTP. Нужен для wildcard-сертификатов или если 80 порт закрыт. В моем случае не потребовалось.

Шестеренка сверху справа(Settings) отвечает за дополнительные заголовки, которые не настроить через GUI. В моем случае это:

client_body_buffer_size 128k;
client_max_body_size 100M;
sendfile on;
sendfile_max_chunk 1m;
Они мне были необходимы для корректной работы affine и были добавлены в конфиг еще пакетного nginx, а после переехали сюда.
Нажимаем Save и проксирование настроено.
Далее про ACL, который в npm настраивается максимально просто. Не для affine, но я добавил доступ к определенным ресурсам с определенных ip-адресов для безопасности.
Переходим во вкладку Access Lists и нажимаем Add Access List:

Далее появляется такое окно:

Во вкладке Details в поле Name вводим понятное для себя название списка.
Satisfy Any я не активировал, так как у меня нет какой-либо авторизации по паролю и, соответственно, вкладку Authorizations я тоже пропустил. Перешел сразу к Rules и вписал туда нужные мне IP-адреса:

Можно как разрешить доступ с определенного адреса, так и запретить.
Также, стоит не забывать про доступ к самому npm извне по http и, в моем случае, 81 порту.
Для ограничения доступа использовал стандартные iptables. Причем, стоит отметить, что docker перезаписывает цепочки правил при перезапуске контейнера и стандартное ограничение по INPUT не сработает, так как входящий пакет попадает в DNAT, созданный doker, и падает на FORWARD, а не на стандартный INPUT. В данном случае нужно написать правила для цепочки docker-user, которая специально создана для пользовательских правил:
iptables -I DOCKER-USER -p tcp --dport 81 -s <your_ip> -j ACCEPT
iptables -I DOCKER-USER -p tcp --dport 81 -j DROP
netfilter-persistent save
Причем, если захотите разрешить доступ с другого IP, то нужно будет заниматься либо перемещением правил по порядку, либо сначала удалить правило DROP, добавить новое, а затем создать снова правило DROP.
На этом я бы хотел закончить свою некую инструкцию по базовым аспектам npm. Реверс-прокси настроен, ACL настроен, доступ к npm извне запрещен.
Далее я столкнулся с некоторыми проблемами с SSO авторизацией через KeyCloak на WordPress сайте, которые напрямую связаны с npm, но об этом я напишу в следующем посте о настройки такой авторизации. 🙂
Основывался на официальной документации: Guide | Nginx Proxy Manager