Безопасный доступ к умному дому при отсутствии публичного IP (часть 1)

    Вступление


    Сложно подобрать емкий заголовок, отражающий смысл, поэтому сразу опишу задачу, которую перед собой поставил.

    Есть «умный дом». В моем случае это безвентиляторный домашний сервер с 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:

    image

    И появилась у меня мысль запустить это все именно как контейнер на каком-нибудь Google Cloud Run, Amazon Fargate или чем-то подобном. Развернуть сервер со всякими fail2ban через ansible – не проблема. Установить Docker тоже. Но зачем, если нужна только маленькая толика его ресурсов?

    Однако, по моим расчетам тот же Fargate мне обошелся бы в разы дороже.

    Может я чего-то не понял? Так-то было бы интересно иметь маленький контейнер чисто для проброса порта домой, а не целый VPS. Нет такого?
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама

    Комментарии 17

      0

      Лучше сразу переходите с OpenVPN на Wireguard, гораздо быстрее и стабильнее

        0

        И в настройке проще

          0
          Что-то я не задумался о таком. Спасибо за идею. Посмотрю повнимательнее.

          Правильно ли я понял, что в этом случае мне будет лучше отказаться от запуска его в контейнере? И установить туннель (через провайдерские 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
            +1
            если нужен просто туннель из за nat на порт, можно посмотреть github.com/fatedier/frp. пробовал забирать с его помощью rtsp поток с камеры за нат — работает стабильно
              0
              Сколько нового и интересного, оказывается, есть :)
          0

          Почему бы не использовать услуги APN для удаленного доступа с серой адресацией?

            0
            Если под APN вы имеете ввиду «поставить на обеих площадках дополнительные WiFi роутеры с SIM картами в выделенной APN», то в моем случае овчинка выделки не стоит.
            Если что-то другое (скажем, есть сервисы, предлагающие прокинуть порты), подскажите варианты. Может быть что-то подойдет.

            В свое время я из такого гонял ngrok. Но он на Amazon AWS, насколько я понял. Т.е. и пинг минимум вдвое дольше, чем сейчас у меня до VPS. И риск внезапной блокировки (как это было с водонагревателями Аристон). И к стабильности претензии были. Даже если из-за бесплатного плана, для меня это отталкивающий знак. Пусть тестовый период лучше по времени будет ограничен, чем портить впечатление, добавляя разрывы сессий.
              0

              А почему не тот же PPTP сервер на VPS и дальше проброс портов?
              У меня такая схема работала пару лет, пока в поле не проросла оптика на 200мбит, пользовался 4г от МТС, естественно с серыми адресами.

                0

                Ну можно ещё было miredo попробовать для ipv6

                  0
                  Не люблю привязку к конкретным облачным сервисам. Внешний сервис и закрыться может (как недавно gbridge уведомление прислал). И просто в случае отказа не понятно, что делать. Предпочитаю свое, что можно в случае чего перенести, переделать малой кровью.

                  Почему бы не взять какого нибудь большого провайдера — Amazon, Azure и поставить там облачного MQTT брокера? Да, не бесплатно, но доступность будет скорей всего не меньше 99%. Можно даже взять что-то попроще — CloudMQTT, например.

                  Также такой брокер можно сравнительно легко продублировать.
                    0
                    Если только облачного брокера туда выставить (CloudMQTT как сервис) — можно, конечно.
                    Но в изначальную концепцию, когда я хотел также публиковать и Vis, это не вписывалось.
                    Кроме того, исторически у меня еще и локальный сервер Blynk развернут. В кессоне стоит ESP шка, отвечающая за полив, подсчет воды… mqtt — не единственный порт/сервис, который хотелось публиковать.

                    Ну а разворачивать полноценный надежный сервер в Амазоне, тогда уж туда переносить ioBroker… Круто, конечно. Только вот при падении канала (неважно, виноват РКН или мой домащний провайдер), отвалится управление домашнм оборудованием.
                    Нет уж, основные мозги должны быть дома, и работа не должна зависеть от Интернета.
                    Собственно ESP8266 в кессоне имеет RTC, чтобы по часам и расписанию работать даже без подключения.

                    Впрочем, вариант:
                    — Облачный сервис MQTT
                    — ioBroker + MQTT broker дома
                    — Zerotier
                    вполне неплохо безо всяких VPS.
                    Может быть, может быть…
                      0
                      Кстати, а чего тогда уже не Яндексовый MQTT IOT Core?
                      72 руб за 1 миллион запросов в месяц вполне норм
                      0

                      Еще один плюс за VPS, там можно nextcloud поднять, с синхронизацией контактов, календаря и файлов.

                        0
                        Единственное «чужое» — это VPS у провайдера. Ну так я специально все в докер контейнерах запускаю, чтобы иметь возможно моментально переехать.
                        Если б знал, сколько придется разбираться с OpenVPN, может и не стал бы. Справедливости ради – основные проблемы были именно из-за контейнеров.

                        гхм, поставить openvpn — одна команда, скопировать конфиг — ещё одна команда.
                        поставить докер — одна команда, скопировать контейнер — ещё одна команда.
                        в чём экономия?


                        если уж хочется сделать переезд максимально лёгким, то лучше было бы плейбук ансибла написать ИМХО.

                          0
                          Чтобы плейбук написать, надо знать, что туда писать. У меня вот не сразу завелось.

                          Теперь да, вы совершщенно правы, понятно, как расширить свой стандартный, которым допиливаю всякие fail2ban с отключением PermitRootLogin и т.е. на свежеразвернутых VPS.
                            0
                            А почему я предпочел докер даже для простеньких задач, я вот тут попытался объяснить:
                              0

                              cходил, прочитал, в целом согласен, но вот для случая openvpn я не вижу потребности в докере. а проблемы есть, вы сами об этом написали.

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

                        Самое читаемое