Доступ к службе на клиенте VPN: обратный туннель или DNAT
Доступ к службе на клиенте OpenVPN без проброса порта: сравните обратный туннель SSH с DNAT на сервере и три условия, определяющие выбор.
Даже ноутбук за NAT оператора может обслуживать коллегу, если сервер VPN служит точкой встречи. Туда ведут два механизма: клиент подключается наружу и просит сервер держать слушающий порт, либо сервер публикует порт и транслирует его в туннель. Оба решают одну и ту же проблему доступности и ведут себя по-разному при сбое, надзоре и журналировании. Три условия определяют, какой из них подходит вашей конфигурации.
Две формы, одна точка встречи
В первой форме клиент открывает сеанс SSH наружу и просит сервер слушать выбранный порт, возвращая каждое соединение через этот сеанс. На сервере ничего не пересылается и трансляция пакетов не происходит, поэтому единственные требования — учётная запись SSH, разрешённая пересылка и правило брандмауэра для порта. Служба остаётся привязанной к loopback на клиенте, что обычно и требуется.
ssh -N -R 8081:127.0.0.1:8080 -o ExitOnForwardFailure=yes -o ServerAliveInterval=30 [email protected]
Во второй форме сервер владеет публичным портом и переписывает каждое соединение в сторону адреса туннеля клиента. Для этого нужны включённая пересылка IP, трансляция назначения на входе и трансляция источника на выходе, и это продолжает работать, когда сеанса SSH нет. Порт принадлежит серверу, а не сеансу, и именно это свойство решает большинство реальных случаев.
Ни одна из форм не безопаснее по своей конструкции. Обе в итоге открывают один порт службы всем, кто может достичь публичного адреса сервера, и обе по-прежнему зависят от собственной аутентификации службы.
Условие первое: должен ли порт жить дольше сеанса
Обратная пересылка живёт ровно столько, сколько сеанс SSH, её создавший. Когда связь падает, слушатель исчезает, и коллега видит отказ в соединении вместо медленного пути. Надзор за командой через юнит systemd и политику перезапуска решает это, а ExitOnForwardFailure=yes делает неудачную привязку сразу видимой вместо того, чтобы оставлять сеанс без пересылки.
Транслированный порт переживает перезагрузки, если правила сохранены, но зависит от того, сохранит ли клиент тот же адрес туннеля. Динамический пул, который после переподключения даёт ноутбуку другой адрес, молча направляет правило в никуда. Сначала зарезервируйте адрес туннеля, затем напишите правило и считайте это одним изменением.
Если служба должна отвечать в три часа ночи без присмотра, у формы с опубликованным сервером портом меньше движущихся частей, потому что единственный процесс, который должен оставаться запущенным, — сам туннель.
Условие второе: кому принадлежат брандмауэр и журналы
Обратная форма держит всю политику в двух местах. sshd решает через AllowTcpForwarding, разрешены ли удалённые пересылки вообще, а GatewayPorts решает, привязывается ли слушатель к loopback или к каждому адресу. При значении по умолчанию no коллега вне сервера не может достичь порта, хотя пересылка удалась, и это выглядит как сломанный туннель, хотя на деле это адрес привязки.
sudo ss -ltn '( sport = :8081 )'
sudo grep -E '^(GatewayPorts|AllowTcpForwarding)' /etc/ssh/sshd_config
Транслированной форме нужны включённая пересылка и два правила, и именно на втором правиле ошибается большинство рецептов. Ограничение трансляции источника состоянием conntrack DNAT означает, что переписываются только соединения, созданные правилом назначения, а не каждый пакет, который сервер отправляет в туннель.
sudo sysctl -w net.ipv4.ip_forward=1
sudo iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 8081 -j DNAT --to-destination 10.88.0.6:8080
sudo iptables -t nat -A POSTROUTING -o tun0 -m conntrack --ctstate DNAT -j SNAT --to-source 10.88.0.1
Эта перезапись уничтожает и атрибуцию. После неё клиент видит каждого вызывающего как адрес туннеля сервера, что схлопывает любой лимит скорости по адресу в одно ведро и скрывает, кто что отправил. Если атрибуция важна, завершайте соединение на сервере или оставьте решение о разрешении там и передавайте идентичность в заголовке, которому служба доверяет. Выбор между этими двумя исправлениями зависит от того, читает ли служба заголовок прокси вообще.
Условие третье: один порт или много, и кому принадлежит имя
Одна служба на одном порту — это случай, когда обратная пересылка выигрывает по трудозатратам. Нужны одна команда, одна учётная запись и одно правило брандмауэра, а уборка — это завершение сеанса. Несколько служб на одном публичном адресе толкают вас к форме с публикацией на сервере, потому что каждой обратной пересылке нужен свой порт сервера, и набор растёт с каждой службой.
Имена ещё больше всё усложняют. Обратная пересылка может стоять за прокси TCP, который маршрутизирует по SNI, тогда как транслированный порт обычно один порт на службу. Если служба сама завершает TLS и нуждается в исходном имени хоста, подход с прокси сохраняет его, а подход с DNAT сохраняет только тогда, когда служба принимает это имя на этом порту.
Запишите, какое из трёх условий определило форму. Иначе следующий человек, добавляющий вторую службу, снова откроет то же сравнение с меньшим контекстом.
Решите, затем проверьте снаружи
Проверьте путь из сети, у которой нет маршрута до ноутбука, и наблюдайте за счётчиками на сервере, пока выполняется запрос. Проверка с сервера на его собственный публичный адрес здесь ничего не доказывает, потому что пакет создаётся локально и никогда не доходит до входящего крюка трансляции, поэтому тест может пройти, пока реальный путь сломан.
sudo iptables -t nat -L PREROUTING -n -v
sudo tcpdump -ni any 'tcp port 8081' -c 20
Счётчики, которые остаются нулевыми, означают, что пакет так и не дошёл до правила, что указывает на провайдера или брандмауэр хоста. Счётчики, которые растут, пока служба отказывает, указывают на собственный брандмауэр клиента или на адрес, на котором служба слушает. Туннель, который несёт пакет, но ничего не возвращает, — это проблема маршрута или обратного пути, и ни один из этих трёх исходов не следует отлаживать правкой адаптера клиента. Запишите, какой счётчик сдвинулся, прежде чем что-то менять, потому что следующему тесту нужна эта базовая линия.
Когда форма заработает, запишите порт, владельца и метод надзора рядом с конфигурацией туннеля. Исправление доступности, существующее только в чьей-то истории командной оболочки, находится в одной перезагрузке от повторного инцидента.