Предисловие
Добро пожаловать, уважаемые читатели!
В одном из своих циклов статей, я реализовывал возможность обращаться к различным сервисам через поддомены.
В последней статье про Plex один из читателей поинтересовался, а почему не завести все запросы с http(80) и https(443) на единый nginx?
Уважаемый автор, статья отличная, но как новичку мне не совсем понятен вопрос с веб-сервером nginx: его назначение, необходимость установки в каждый контейнер – неужели нельзя его сделать единым на все контейнеры с Transmission и Plex’ом? Напишите об этом поподробнее здесь как дополнение или же вообще отдельной статьёй с упором на практическое применение в домашней сети!
Вот тот самый цикл статей:
- Домашний Сервер: Часть 1 – Предисловие, аппаратная и софтовая начинка
- Домашний Сервер: Часть 2 – Установка системы виртуализации Proxmox
- Домашний Сервер: Часть 3 – Внутренний DNS сервис на BIND9 или свои доменные имена в локальной сети
- Домашний Сервер: Часть 4 – Настройка Transmission daemon в контейнере LXC Proxmox-VE
- Домашний Сервер: Часть 5 – Установка и настройка Plex Media Server в контейнере LXC Proxmox-VE
Подготовка
Для того, чтобы все завести на один Nginx, я решил запустить отдельный LXC контейнер.
Ставим необходимое в контейнере. (Как поставить LXC контейнер, можно найти в цикле статей)
apt install nginx git -y
Клонируем файлы клиента Certbot
git clone https://github.com/certbot/certbot /opt/letsencrypt
ln -s /opt/letsencrypt/certbot-auto /usr/bin/certbot
Тут я хочу остановится подробнее.
Я поставил себе задачу, чтобы можно было через поддомены открывать свои сервисы, как в локальной сети, так и из интернета.
Для этого необходимо будет перенаправить 80 и 443 порты на данный контейнер. Но об этом чуть позже.
Let’s Encrypt обычно требует, чтобы ваш IP адрес на который назначен домен или поддомен был доступен из вне, но т.к. у меня вся сеть закрыта Firewall-ом роутера возникают сложности. Можно было бы найти IP адреса сервисов Let’s Encrypt, но это не благодарное занятие.
Помочь в этой ситуации может Wildcard сертификат.
Wildcard-сертификат — сертификат открытого ключа, который может использоваться с несколькими подобластями домена.
Поддерживается только один уровень поддоменов! Т.е. *.example.com позволит поддерживать поддомены first.example.com, second.example.com и т.д., а вот для once.first.example.com данный сертфикат уже работать не будет.
При этом можно создать сертификат для поддомена второго уровня: *.first.example.com
В этом случае сертификат будет работать для любых поддоменов второго уровня: one.first.example.com, two.first.example.com и .т.д.
Я буду получать сертификат для поддомена первого уровня, а все сервисы повешу на поддомены второго уровня и мне не придется получать сертификаты для каждого сервиса по отдельности.
У меня есть хостинг и управление доменом я веду на нем. Чтобы была возможность заходить на поддомены сервисов, вам необходим основной домен доступный из интернета.
Для домена на своем хостинге нужно создать основной поддомен, для примера я возьму такой:
home.gregory-gost.ru
Запускаем certbot для получения сертификата.
certbot certonly --agree-tos -d home.gregory-gost.ru -d *.home.gregory-gost.ru --preferred-challenges dns --manual --server https://acme-v02.api.letsencrypt.org/directory --manual-public-ip-logging-ok
Please deploy a DNS TXT record under the name
_acme-challenge.home.gregory-gost.ru with the following value:
fgfgDFDFuReJBUTYbYO71os_BKdfJDJFdfhfHDHFDFJHFD
Before continuing, verify the record is deployed.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Press Enter to Continue
Certbot будет выдавать такие сообщения. У меня было два таких.
Нужно занести обе TXT записи в DNS настройки основного домена. Также добавить/заменить А запись, своим статическим IP роутера, в поддоменах.
Сертификаты будут лежать по пути
/etc/letsencrypt/live/home.gregory-gost.ru/
Далее создаем поддомены второго уровня для сервисов: plex.home.gregory-gost.ru и т.д.
В Cron появится файл автоматической проверки и продления сертификата.
Проверим это правило:
cat /etc/cron.d/certbot
# /etc/cron.d/certbot: crontab entries for the certbot package
#
# Upstream recommends attempting renewal twice a day
#
# Eventually, this will be an opportunity to validate certificates
# haven't been revoked, etc. Renewal will only occur if expiration
# is within 30 days.
#
# Important Note! This cronjob will NOT be executed if you are
# running systemd as your init system. If you are running systemd,
# the cronjob.timer function takes precedence over this cronjob. For
# more details, see the systemd.timer manpage, or use systemctl show
# certbot.timer.
SHELL=/bin/sh
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
0 */12 * * * root test -x /usr/bin/certbot -a \! -d /run/systemd/system && perl -e 'sleep int(rand(43200))' && certbot -q renew
Все вроде бы хорошо, за одним исключением. Можно попроще и после обновления сертификатов хорошо бы автоматически перезагрузить Nginx.
И это можно сделать отредактировав конфигурационный файл Certbot для нашего домена.
Файл конфигурации лежит по пути: /etc/letsencrypt/renewal/home.gregory-gost.ru.conf
Для того, чтобы выполнить какую-либо команду ДО или ПОСЛЕ обновления сертификатов у Certbot есть специальные ключи
- —pre-hook — выполнять команду перед запуском certboot
- —post-hook — выполнять команду после запуска certboot
- —renew-hook — выполнять команду только после успешного продления сертификата
Самым удобным в этом случае выглядит ключ —renew-hook, он позволяет перезапускать службы только после успешного продления сертификата, а не просто дважды в день, как у ключа —post-hook
Добавим команду перезапуска в файл конфигурации, в раздел [renewalparams]:
nano /etc/letsencrypt/renewal/home.gregory-gost.ru.conf
# renew_before_expiry = 30 days
version = 0.28.0
archive_dir = /etc/letsencrypt/archive/home.gregory-gost.ru
cert = /etc/letsencrypt/live/home.gregory-gost.ru/cert.pem
privkey = /etc/letsencrypt/live/home.gregory-gost.ru/privkey.pem
chain = /etc/letsencrypt/live/home.gregory-gost.ru/chain.pem
fullchain = /etc/letsencrypt/live/home.gregory-gost.ru/fullchain.pem
# Options used in the renewal process
[renewalparams]
authenticator = manual
account = 9471dfdhjdhghg1a8c1a2a07fde01
pref_challs = dns-01,
manual_public_ip_logging_ok = True
server = https://acme-v02.api.letsencrypt.org/directory
renew-hook = service nginx restart
Делаем проверку на продление сертификата проще.
Удаляем или комментируем строки в файле /etc/cron.d/certbot и копируем файл в /etc/cron.weekly
cp /etc/cron.d/certbot /etc/cron.weekly/
chmod +x /etc/cron.weekly/certbot
nano /etc/cron.weekly/certbot
#!/bin/bash
/usr/bin/certbot -q renew
certbot certonly --agree-tos -d home.gregory-gost.ru -d *.home.gregory-gost.ru --preferred-challenges dns --manual --server https://acme-v02.api.letsencrypt.org/directory --manual-public-ip-logging-ok
Далее настраиваем nginx.
Файлы конфигурации nginx
Другие сервисы по аналогии.
Перенастройка локальных доменных зон
Само собой необходимо изменить основной IP адрес для доменных зон в bind9
Открываю зону прямого просмотра и редактирую
На примере роутера MikroTik приведу правила перенаправления 80 и 443 порта:
/ip firewall nat
add action=dst-nat chain=dstnat comment="HTTP(S) to LXC" dst-port=80 in-interface-list=Internet protocol=tcp src-address-list="Access IP" to-addresses=192.168.88.14 to-ports=80
add action=dst-nat chain=dstnat dst-port=443 in-interface-list=Internet protocol=tcp src-address-list="Access IP" to-addresses=192.168.88.14 to-ports=443
Соответственно src-address-list=»Access IP» это список разрешенных адресов прошедших PortKnocking.
На этом можно закончить, все должно работать.
У меня точно работает 🙂
Как это работает?
Немного расскажу, как это работает с точки зрения обычного пользователя. Т.е. тут не будет каких-то объемных технических подробностей по работе DNS или Nginx.
Опишу два примера, один это запрос с устройства из локальной сети, а второй это запрос с устройства откуда-то из интернета.
Запрашивать буду доменное имя:
— plex.home.gregory-gost.ru
Давайте посмотрим, что происходит в первом случае:
- Мы на локальном устройстве с IP адресом 192.168.88.40 пытаемся открыть в браузере WEB интерфейс Plex Media Server
- Т.к. мы запросили доменное имя, то необходимо распознать это имя в IP адрес. В локальной сети всем устройствам выдаются DNS адреса 192.168.88.7 и 192.168.88.1
Это означает, что первый DNS запрос попадет на IP 192.168.88.7 на котором у нас в свою очередь работает DNS сервер BIND9 - BIND9 получив такой запрос проводит сверку по спискам доступа ACL, далее проверяет есть ли такая запись в его базе.
Если записи нет, отправляет запрос дальше. Например может отправить на роутер с IP адресом 192.168.88.1 согласно спискам ACL, а роутер отправит его провайдеру(если роутер получает от провайдера DNS) и т.д. пока доменное имя не преобразуется в IP адрес
Нам повезло, имя home.gregory-gost.ru есть в базе BIND9 и вот, что там написано:cat /etc/bind/zone/db.gregory-gost.ru plex.home IN A 192.168.88.14
cat /etc/bind/zone/db.gregory-gost.ru.inverse 14 IN PTR plex.home.gregory-gost.ru.
- Как мы видим DNS сервер преобразует имя и направляет нас на IP адрес: 192.168.88.14
- IP 192.168.88.14 в свою очередь привязан к LXC контейнеру на котором установлен Nginx.
Nginx слушает стандартные WEB порты 80 и 443. Наш запрос приходит на 80 порт т.к. мы указали http: и не указывали принудительно https: - Создаваемые для Nginx файлы конфигураций работают на одном порту, но отвечают за различные сервисы.
Nginx решает, какой из файлов «серверов» должен обработать запрос с помощью сравнения заголовка Host в запросе, оно же «доменное имя», которое мы запросили.
Пример запроса браузера Opera. Вы можете найти заголовок Host самостоятельно:GET / HTTP/1.1 Host: plex.home.gregory-gost.ru Connection: keep-alive Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36 OPR/65.0.3467.78 Sec-Fetch-User: ?1 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3 Sec-Fetch-Site: none Sec-Fetch-Mode: navigate Accept-Encoding: gzip, deflate, br Accept-Language: ru-RU,ru;q=0.9,en-US;q=0.8,en;q=0.7
Заголовки(Headers): https://ru.wikipedia.org/wiki/Список_заголовков_HTTP
- В конфигурационных файлах Nginx указаны непосредственные IP адреса и порты служб, которые могут работать совершенно в разных контейнерах, устройствах, виртуальных средах и т.д.
Например в этом файле nginx указан IP адрес и порт на котором работает Plex Media Servercat /etc/nginx/conf.d/plex.conf
upstream plex.pve.gregory-gost.ru { server 192.168.88.8:32400; keepalive 32; }
location / { if ($http_x_plex_device_name = '') { rewrite ^/$ https://$http_host/web/index.html; } proxy_pass https://plex.pve.gregory-gost.ru; }
А это непосредственное перенаправление на страницу https://192.168.88.8:32400/web/index.html
- В результате, в окне браузера под адресом plex.home.gregory-gost.ru мы видим тоже самое, что увидели бы при прямом заходе на https://192.168.88.8:32400/web/index.html
Единственное, мы через Nginx дополнительно подключили сертификат SSL, поэтому мы можем спокойно использовать https: вместо простого http:
- Мы на каком-то устройстве с динамическим рандомным IP адресом пытаемся открыть в браузере WEB интерфейс Plex Media Server, например сидя в кафе или у друзей.
- Т.к. мы запросили доменное имя, то необходимо, все также, распознать это имя в IP адрес. Только делать это будет уже сервис, где мы проводили регистрацию домена, по другому это еще называется «парковка» домена.
Чтобы запрос корректно обрабатывался в панели администрирования необходимо добавить все необходимые поддомены по аналогии с локальной реализацией.
Отличием будет, удобное добавление через панель управления сервиса и необходимость указывать статический IP адрес, выданный нашему роутеру провайдером.
Например у меня домен обслуживает хостинг провайдер, у которого также обслуживается сам сайт и есть удобная панель управления. У разных хостеров и регистраторов разные панели управления!
- При вводе доменного имени, запрос так или иначе попадет к нашему хостеру и его DNS служба перенаправит нас на указанный, в «A» записи, IP адрес и порт.(стандартно — http: 80 и https: 443)
- Т.к. мы указываем в «А» записи, статический IP адрес нашего роутера, то запрос попадет на роутер. Тут-то и необходимо правило перенаправления 80 и 443 порта.
Но перенаправлять мы будем уже не на BIND9, а сразу на LXC контейнер с Nginx с адресом 192.168.88.14. BIND9 в этом случае не требуется т.к. запрос идет извне нашей локальной среды. - Далее обработка идет по аналогии с первым случаем.
Надеюсь мне удалось объяснить принцип взаимодействия с доменными именами, как из локальной сети, так и из глобальной интернет среды.
Заключение
Вот таким не хитрым способом можно уйти от использования IP адресов для своих сервисов в локальной домашней или офисной сети. Заходить по одному домену, как из внешних сетей, так и внутри локальной сети.
На последок, одно важное замечание!
В моем случае порты открыты только для списка разрешенных IP адресов, в который можно попасть только через PortKnocking.
MikroTik : RouterOS : Стучимся к себе домой. Firewall Filter PortKnocking
Благодарю за ваше время!
Всего хорошего на просторах Интернета 😉
UPD 05.01.2020:
Добавлен блок «Как это работает?» для более лучшего понимания процесса запрос-ответ.
День добрый, Григорий
В описанном Вами сценарии автопродление сертификата работать не будет, потому что вы генерировалиего в интерактивном режиме (ключ —manual), что является обязательным требованием при выпуске wildcard сертификатов. При запуске команды certbot -q renew вы получите ошибку:
certbot.errors.PluginSelectionError: The manual plugin is not working; there may be problems with your existing configuration.
The error was: PluginError(‘An authentication script must be provided with —manual-auth-hook when using the manual plugin non-interactively.’)
Т.к. необходимо опять вручную изменить TXT записи в DNS.
Тут есть два выхода:
1. Заново запустить первоначальный скрипт генерации сертификатов и внести изменения в DNS вручную.
2. Если ваш хостинг DNS поддерживает API, написать скрипт автоматического внесения изменений в DNS authenticator.sh
https://certbot.eff.org/docs/using.html#pre-and-post-validation-hooks
да, автопродление не работает, каждый раз вручную приходится вносить изменения в DNS
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN —
tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN —
tcp 0 0 127.0.0.1:25 0.0.0.0:* LISTEN —
tcp 0 0 0.0.0.0:443 0.0.0.0:* LISTEN —
tcp6 0 0 :::22 :::* LISTEN —
tcp6 0 0 ::1:25 :::* LISTEN —
Так и не получилось решить проблему с https при внешнем IP из access list. Чёт какая-то мистика( В итоге пришлось делать костыль, надеюсь временный (приму любые идеи и помощь :D). Пример plex.conf:
После дальнейшего исследования обнаружил, что если убрать ssl с plex.home…..com, то из вне получается зайти по http. Https запросы как-будто дропаются роутером…
Интересная ситуация.
L2TP может влиять только если MTU не соответствующий, но это маловероятно, при условии, что все остальное работает корректно.
Если кол-во байт в Firewall NAT увеличивается, значит запрос попадает на nginx (кстати какой порт увеличивает счетчик ? 80 или 443?). Лучше конечно убедиться в этом… Включите на nginx access log и посмотрите, что попадает в него в момент запроса ресурса.
Еще я бы проверял через мобильный браузер (отключившись от своего WiFi конечно), как отрабатывает wildcard сертификат при переходе по доменному имени, а не через curl.
По сути, у вас, на внешнем хостинге должны быть прописаны все ваши поддомены (plex, torrent и т.д.) и для них указан только ваш статический IP роутера отдаваемый провайдером.
Вроде ничего не упустил.
кол-во пакетов увеличивается на форварде 80ого порта, на 443 всегда 0. Т е пакеты попадают на 80 порт nginx (192.168.88.14), он делает редирект на https — и всё вот тут происходит таймаут, при том пакеты на форварде 443 порта 0. Еще интересный факт — если попытаться telnet 192.168.88.1 443 — будет opertation timeout, а если это сделать изнутри сети — то либо refused либо timeout
А контейнер с nginx корректно слушает порт 443?
Григорий, в рамках дальнейшего изучения Вашего ресурса, решил переделать сетап
на ssl с отдельным nginx контейнером, как вот в этой статье. Всё работает без
проблем по https. Также у меня сделан icmp knocking, согласно Вашей статье
MikroTik : RouterOS : Стучимся к себе домой. Firewall Filter PortKnocking. Он
тоже работает и я могу получить доступ к своему роутеру после того, как мой айпи
попадет в Access IP лист. Проблема заключается в том, что у меня не открываются
внутренние ресурсы (такие как plex, torrent и тд) из вне даже после того, как
айпи попал в Access IP лист. Форвардинг портов 80 и 443 у меня сделан также как
у вас и на роутере я вижу, что кол-во байт увеличивается когда я делаю запрос,
если пробую запросить через curl http://plex.home.example.com то выдает 301
permanently moved, но браузер не открывает ни одну внутреннюю страницу и
кидает ошибку request timeout. У меня интернет дома по L2TP, т е ethernet провод
провайдера и по нему устанавливается тунель L2TP, в остальном сетап полностью
как у Вас, включая даже локальные IP. Фаервол настроен также как в статье про
порт кнокинг у Вас. Помогите пожалуйста разобраться!
Григорий, напишу здесь свой вопрос и пожелание: возможно ли настроить мониторинг сервера Proxmox и особенно состояние дисков используя The Dude от Mikrotik? Какова будет функциональность этого решения по сравнению с Zabbix’ом или Prometeus’ом?
Можно ли продолжить статьи про функционал The Dude и его практическое применение в собственной сети?
Конечно возможно, правда это делается с помощью скриптов и расширенных OID в SNMP (Extend), для The Dude необходимо создавать Function и Probe на основе этих функций.
Я у себя реализовал мониторинг состояния ZFS пулов zpool(rpool,rpoolz) и используемого в них места. Планирую прикрутить еще температуру CPU и температуру каждого диска.
Насколько мне известно, для Zabbix можно найти готовые темплейты под Proxmox. Но по моему их нужно допиливать. В целом кому-то Zabbix может больше понравится т.к. можно поставить отдельного агента на хост и виртуалки или испльзовать тот же самый SNMP. Меня пока полностью устраивает The Dude. К Zabbix можно еще прикрутить Graphana для красивой визуализации графиков.
С Prometeus’ом не работал, не подскажу.
Продолжить конечно можно. Все зависит от объема и уникальности материала т.к. я стараюсь давать что-то интересное и полезное не лежащее на поверхности.
Как наберу достаточно материала, буду оформлять и публиковать, а пока я собираю информацию в черновики.
Григорий, очень ценное и как всегда подробное дополнение, написанное ясно и понятно, к тому же ещё и с такими картинками! Теперь у меня всё начинает складываться в одну общую картинку, спасибо ещё раз за твои труды и прояснение всех непонятных моментов. Не скрою у меня давно уже назрела насущная необходимость иметь работающий в локальной сети WEB-сервер и вовсе не для сайтов (хотя в дальнейшем и для них тоже) а для того чтобы сделать с него загрузку на компьютеры по сети загрузочных образов — это было бы намного быстрее чем используя устаревший протокол TFTP. Но это будет целая большая тема, которой я скоро займусь т.к. теперь мне уже в теории ясно как это может быть организовано практически, а пока я опять как внимательный читатель увидел, что на картинке «Список всех поддоменов основного домена» показан имеющийся сертификат SSL на основной домен gregory-gost.ru а на все поддомены таких сертификатов нет, но как они получены как раз и рассказывается в этой статье! Пусть конечно же не на каждый из этих сервисов а на все сразу но почему его не видно в этих списках?
Все потому, что у хостеров, сертификаты Let’s Encrypt идут на конкретный домен.
И для всех поддоменов необходимо также регистрировать отдельные сертификаты, что не удобно т.к. я не знаю сколько поддоменов у меня может быть в будущем.
Также эти поддомены, в основном, используются в локальной сети и обслуживаются в ней же, к тому же она закрыта Firewall-ом роутера.
Соответственно и сертификаты необходимо получать не на хостинге, а в локальной сети. И Nginx для этого подходит очень хорошо, как и wildcard сертификат от Let’s Encrypt.
Григорий, на связи вновь тот самый внимательный читатель чьё любопытство послужило причиной написания этой статьи. Не скрою, было очень приятно получить так быстро и так развёрнуто ответ на свой вопрос, спасибо за это! Конечно же я повторю у себя всё что узнал из этой статьи но пока мне, к сожалению, мешает отсутствие некоторых базовых знаний, к примеру я так и не понял как так получается что в файлах конфигурации db.gregory-gost.ru и db.gregory-gost.ru.inverse указывается адрес 192.168.88.14 но происходит переход по адресам сервисов, а именно openhab.home.gregory-gost.ru, plex.home.gregory-gost.ru, openhab.home.gregory-gost.ru, torrent.home.gregory-gost.ru. Думаю я не один такой, в связи с этим предлагаю написать также обстоятельно и подробно статью (или цикл статей) про устройство и работу с внешними DNS, работу со своими доменами, — словом всё то что осталось в этой статье за кадром, это будет интересно всем читателям, заранее спасибо и благодарю за труд!
Павел, приветствую.
Добавил в статью блок «Как это работает?»
Надеюсь не сильно замудрено. 🙂