Вступление
Сложно подобрать емкий заголовок, отражающий смысл, поэтому сразу опишу задачу, которую перед собой поставил.
Есть «умный дом». В моем случае это безвентиляторный домашний сервер с ioBroker, хотя это не принципиально. Помимо домашних штучек хочется к нему цеплять датчики извне (например, на ESP32 из удаленной теплицы). Это решил делать через mqtt. Доступ к интерфейсу из Интернета.
Обычное дело. Но есть нюансы:
- У провайдера нет возможности дать мне публичный IP адрес. И других провайдеров нет.
- Не люблю привязку к конкретным облачным сервисам. Внешний сервис и закрыться может (как недавно gbridge уведомление прислал). И просто в случае отказа не понятно, что делать. Предпочитаю свое, что можно в случае чего перенести, переделать малой кровью.
- Безопасность важна. Не паранойя, но выставить ioBroker в Интернет, особенно с учетом, что там выставляется несколько сервисов (flot…). Нет уж.
Дальше хочу показать не сразу результат, а процесс. Как шел, как трансформировались хотелки, менялись решения. Вполне возможно, что некоторые моменты можно решить более правильно/эффективно (я не сисадмин, не разработчик). А может кто-то не пойдет так далеко, и воспользуется промежуточным решением, которое я, к примеру, счел недостаточно для себя безопасным или удобным. Собственно, описанное в этой части — вполне рабочий вариант, но для меня «промежуточный».
Публичный адрес для mqtt
Дома публичного IP на роутере не светит (я не про фиксированный, это решаемо через dyndns и аналоги), а именно провайдер дает 10.х.х.х, без вариантов. Значит, надо арендовать маленький VPS, и сделать проброс через него.
Самый простой способ – туннель через ssh. На домашнем сервере (буду называть его iob.xxx.xx) выполняю:
ssh -N -T -R pub.xxx.xx:1883:127.0.0.1:1883 a@iob.xxx.xx
Подключаясь к порту 1883 внешнего сервера pub.xxx.xx, в реальности оказываешься на домашнем iob:1883 c запущенным контейнером mqtt (mosquito).
Естественно, нужно, чтобы это стартовало автоматически, соединение восстанавливалось после сбоя. Поэтому воспользовался autossh, оформив как сервис.
/etc/systemd/system/ssh_mqtt.service:
[Unit]
Description=SSH Tunnel mqtt
After=network.target
[Service]
Environment="AUTOSSH_GATETIME=0"
ExecStart=autossh -M 0 -o "ServerAliveInterval 30" -o "ServerAliveCountMax 3" -NR pub.xxx.xx:1883:127.0.0.1:1883 -i /home/a/.ssh/id_rsa a@pub.xxx.xx
[Install]
WantedBy=multi-user.target
Всякие systemctl enable/restart и т.п. я уж не буду описывать.
К сожалению, несмотря на autossh, меня преследовали постоянные зависания. Так что я решил, что незачем плодить сущности и остановился на обычном ssh:
/etc/systemd/system/ssh_mqtt.service:
[Unit]
Description=SSH Tunnel mqtt
After=network.target
[Service]
Restart=always
RestartSec=20
User=anri
ExecStart=/bin/ssh -N -T -R pub.xxx.xx:1883:127.0.0.1:1883 -i /home/a/.ssh/id_rsa a@pub.xxx.xx
[Install]
WantedBy=multi-user.target
Впоследствии, кстати, оказалось, что это провайдер такой косой. Ну или самый дешевый тариф за 45 руб/мес так криво у него работает. Сессия периодически зависает, даже когда просто по ssh подключен (MobaXterm). Так что заказал я себе в итоге VPS у другого (55 руб/мес), и проблемы с зависаниями исчезли.
Кстати, выбирал я себе, где VPS взять не только исходя из цены, но и с учетом пинга (10-20ms).
В общем, такой вариант вполне норм, особенно с учетом того, что впоследствии я сделал подключение через Интернет исключительно на порт 8883 через TLS. Т.е. пароль для mosquitto передавался зашифрованным.
Впоследствии сделал обязательными и клиентские сертификаты. Т.е. сначала на уровне TLS надо предъявить клиентский сертификат, а потом уже авторизоваться именем-паролем, заданным в mosquito. Т.е. до фазы перебора паролей не так просто добраться.
Соответственно сначала использовал сертификат сервера от LetsEncrypt, потом, из-за необходимости клиентских сертификатов, перешел на самоподписание.
Поскольку в процессе я дорабатывал и ПО для ESP32, заметил (или просто подумал, уже не помню), что при проблемах с VPN подключением аккумулятор будет расходоваться существенно быстрее. При нормальной работе цикл: Проснуться, подать питание на датчики, подключиться к WiFi, установить соединение с сервером mqtt, считать показания датчиков, как будут готовы, передать на mqtt, отключить питание с датчиков, уйти в deep sleep минут на 10.
В норме такой цикл занимает примерно 4 секунды. 1.5-2 секунды — подключение к WiFi, дополнительная секунда из-за перехода на mqtt over TLS. 4 секунды устраивает, все равно датчикам нужно время, чтобы проснуться. А вот если VPN лег (это было хорошо заметно, когда autossh отваливался), что делать? Я, конечно, настроил, чтобы через 20 секунд все равно система засыпала. Но 20 секунд вместо 4 – это весьма ощутимо.
В общем, я решил, что mqtt сервер лучше держать на внешнем VPS. Сейчас, когда все работает, как часы, я не уверен, что это необходимо. Но и переделывать обратно смысла не вижу.
Публичный адрес для Vis
Vis – популярная система визуализации в ioBroker. Можно было не заморачиваться, и ее саму настроить на https и аналогично просто пробросить порт. Тем более, что пароль она может спрашивать на уровне приложения.
Но это не круто. Особенно с учетом того, что для работы она подключает дополнительные сервисы. Скажем, графики я рисую в flot, Условно говоря, подключаюсь я к vis.xxx.xx:8082/vis/index.html, а внутри есть ссылки на графики vis.xxx.xxx:8082/flot/index.html. В какой-то момент оказалось, что при подключении к /vis пароль спрашивается, а интерфейс графиков доступен без пароля.
В какие-то моменты вообще странно было – на vis авторизовался, график вижу, но справа внизу полупрозразчное окошко «No connection to server». Переписал для этого блока css, чтобы скрыть его. Но, как только стал использовать frame для переключения между графиками на одном экране, оказалось, что мое перекрытие на отображение во фрейме не действует. (Как потом выяснилось, так и должно быть). Отключаю авторизацию – все прекрасно, никаких ругательств.
Так что решил я на том самом внешнем сервере поднять nginx в режиме reverse proxy. И уже на нем сделать авторизацию.
Из браузера заработало. Но родное приложение iobroker.vis из Play Market так авторизоваться не смогло. А хотелось им пользоваться. Хоть это и фактически браузер в окошке, но есть у него ряд приятных фич. Скажем, задал масштаб (93% при вертикальном режиме), и изображение вписалось. На другом устройстве с другим разрешением экрана просто подбираешь коэффициент, и все. А в браузере надо каждый раз подстраиваться…
Ладно, думаю. Добавлю вместо пароля хитрый код в URL. Типа vis.xxx.xx:8082/<длинная последовательность>/vis/index.html. Часто такой трюк используется.
Почти заработало. Но с глюками, копание показало, что вот это веб приложение написано не совсем корректно. Многие ссылки внутри него не относительные, а от корня.
Ладно, несколько ссылок выявил, для них написал, мол, если referrer содержит такой код, все равно доверяй, перепиши URL и т.п. Но они постепенно еще выявлялись. Так что я решил, что это криво, неправильно, и нужен другой подход.
VPN
Решил я немного пожертвовать универсальностью доступа. Пусть у меня будет доступ с моего ноута, смартфона. Но с чужих устройств, из интернет-кафе не надо, обойдусь. Тогда можно поставить небольшого клиента, который будет устанавливать VPN. И внутри него не требуется ни SSL, ни авторизация. И при этом ссылки переделывать не надо.
Простейшим способом для меня оказался Zerotier. Для моих ОС (Windows, Android, Linux) клиенты есть. Причем есть даже готовые в докере. Да. Я все запускаю в докере, про особенности этого позже.
Устанавливаешь клиента, вводишь уникальный код своей сети, потом в Web-интерфейсе на my.zerotier.com его подтверждаешь, при необходимости задаешь статический адрес из личной приватной сети (а-ля 10.20.30.0), и все. Все подключенные клиенты видят друг друга.
Единственное, с чем пришлось поразбираться немного, это «как с устройства в домашнем WiFi подключиться к удаленному серверу, не запуская клиента». Ну у меня же сервер домашний уже является клиентом, пусть и маршрутизирует себе. Оказалось, все просто. Домашнюю сеть 192.168.х.0 надо на my.zerotier.com прописать в разделе Managed Routes, указав в качестве gateway, естественно, этот мой домашний сервачок. Ну и в WiFi сети настроить маршрут соответственно (на WiFi роутере статика 10.20.30.0 на домашний сервер).
Можно при подключении клиента Zerotier указывать другой DNS сервер. Т.е. подключил клиента, и доменное имя резолвится уже не в публичный адрес, а в приватный, поскольку DNS теперь указывает на домашний сервер, где dnsmasq подсовывает для отдельных записей IP из приватной сети Zerotier.
Еще Zerotier порадовал эффективным выбором маршрута для подключения. Если я в домашнем WiFi активирую клиента Zerotier, пинг до домашнего компьютера (его IP адреса, выданного Zerotier) те же пара милисекунд, что и без клиента (просто по WiFi). Т.е. подключение к облаку идет только в первый момент. Дальше обмен трафиком осуществляется напрямую, не через облако. Если установить, к примеру, OpenVPN на VPS, аналогичный трафик бежал от клиента на VPS, а потом обратно в ту же WiFi сеть к домашнему серверу.
В принципе есть даже фишка ставить свои moon серверы. Чуть ли не в отрезанной от Интернета сети все это хозяйство развернуть.
Что в итоге?
ESP32 шлет свои данные на mqtt сервер, развернутый на VPS. Over TLS, client certificate required.
С домашним сервером установлен VPN через Zerotier. Через этот домашний сервер с mqtt на VPS связывается Sonoff rfBridge с прошивкой Tasmota. Там нет возможности задать TLS с клиентским сертифкатом, потому настроен обычный MQTT на 1883. Все равно ведь домашний сервер этот трафик зашифрует с помощью Zerotier.
Ну и я подключаюсь к vis из домашней сети непосредственно, а из Интернета активировав клиента Zerotier. Можно его вообще не отключать, так тоже работает. Вот только иногда мне и другие VPN клиенты нужны (например, сходить на «РКН-запрещенку»). Два VPN на одном смартфоне сразу не сдружились, а разбираться я не стал.
Все очень просто. Но червячок душу глодал. Хоть у меня и не ядерный реактор, но вдруг? Был же случай, когда сломали TeamViewer (компанию, а не конкретно клиентское ПО), и через них получили доступ к многим аккаунтам. И вообще я писал в самом начале, что все свое люблю.
Так что следующим шагом я перешел с Zerotier на OpenVPN. Тут уж все в моих руках.
Единственное «чужое» — это VPS у провайдера. Ну так я специально все в докер контейнерах запускаю, чтобы иметь возможно моментально переехать.
Если б знал, сколько придется разбираться с OpenVPN, может и не стал бы. Справедливости ради – основные проблемы были именно из-за контейнеров.
Заключение
В следующей статье расскажу про OpenVPN и особенности настройки в моих условиях (контейнеры, маршрутизация других устройств из домашней сети). Там будет больше конфигов, технических деталей и сложностей. Но сразу вторую часть писать без этой не стал. Было бы не понятно, зачем вообще такие извращения.
И на всякий случай вопрос к знающим: хоть у меня VPS и маленький (512MB RAM), используется он меньше, чем на 1%. docker stats:
И появилась у меня мысль запустить это все именно как контейнер на каком-нибудь Google Cloud Run, Amazon Fargate или чем-то подобном. Развернуть сервер со всякими fail2ban через ansible – не проблема. Установить Docker тоже. Но зачем, если нужна только маленькая толика его ресурсов?
Однако, по моим расчетам тот же Fargate мне обошелся бы в разы дороже.
Может я чего-то не понял? Так-то было бы интересно иметь маленький контейнер чисто для проброса порта домой, а не целый VPS. Нет такого?
NermaN
Лучше сразу переходите с OpenVPN на Wireguard, гораздо быстрее и стабильнее
NermaN
И в настройке проще
Anrikigai Автор
Что-то я не задумался о таком. Спасибо за идею. Посмотрю повнимательнее.
Правильно ли я понял, что в этом случае мне будет лучше отказаться от запуска его в контейнере? И установить туннель (через провайдерские NAT) прямо из systemd а-ля
# cat /etc/systemd/network/15-vpn.netdev
[NetDev]
Name=vpn
Kind=wireguard
[WireGuard]
PrivateKey = SOME_PRIVATE_KEY
ListenPort = 51820
[WireGuardPeer]
PublicKey = SOME_PUBLIC_KEY
PresharedKey = SOME_PSK_KEY
AllowedIPs = 172.16.0.0/12
Endpoint = 1.2.3.4:1234
terebenkov
если нужен просто туннель из за nat на порт, можно посмотреть github.com/fatedier/frp. пробовал забирать с его помощью rtsp поток с камеры за нат — работает стабильно
Anrikigai Автор
Сколько нового и интересного, оказывается, есть :)