Один публичный хост для VPN и веба: как разделить роли
Один публичный IP может завершать туннели, обслуживать HTTPS и достигать двух частных сетей LAN. Сначала решите, кому принадлежат адрес, каждый порт и политика пересылки.
Одна машина может удовлетворить все три потребности сразу: завершать туннели, отвечать по HTTPS и пересылать между двумя частными сетями LAN. Разумно ли это, сводится к трём вещам, которые можно решить на бумаге: кому принадлежит публичный адрес, кому принадлежит каждая пара порта и протокола и какому процессу разрешено пересылать пакеты. Если всё верно, общий хост ничем не примечателен. Если нет, вы потратите выходные на сервис, который чисто запускается и никогда не отвечает.
Далее идёт процедура принятия решений, а не чертёж сборки. Каждый шаг заканчивается проверкой, которая доказывает, что только что добавленная роль действительно изолирована.
Выберите владельца адреса раньше портов
Конечная точка туннеля и веб-сервер могут делить одно ядро; одновременное совместное использование одного адреса, порта и протокола — это то, что ломается. WireGuard обычно занимает UDP 51820, OpenVPN занимает UDP 1194 или TCP 443, а веб-сервер занимает TCP 443. Болезненная пара — OpenVPN поверх TCP 443 рядом с HTTPS, потому что обоим нужен один и тот же сокет, и ядро отдаёт его тому, кто привязался первым. Только демультиплексор впереди, например sslh или прокси, понимающий TLS, позволяет им делить этот порт, и он добавляет компонент, отказ которого выглядит в точности как проблема брандмауэра.
Посмотрите на владельцев, прежде чем что-то менять: ss -lntup печатает локальный адрес, порт и процесс каждого слушающего сокета. Две строки с 0.0.0.0:443 означают, что вторая служба так и не привязалась, каким бы бодрым ни был её init-скрипт. Мост тоже переносит владельца. Когда eth0 присоединяется к br0, IP переходит на br0 и каждое правило брандмауэра, упоминающее eth0, перестаёт совпадать, поэтому привяжите службы на мосту и подтвердите через ip -br addr.
То же правило владения объясняет неожиданность, которая возникает с виртуальными машинами. Туннельный сервер, работающий внутри гостя в той же сети LAN, сохраняет адрес гостя, поэтому соединения, направленные на адрес самого хоста, остаются на хосте, и туннель гостя их никогда не видит, что бы ни утверждал профиль клиента.
Маршрутизируйте между двумя сетями LAN или сделайте NAT с одной стороны
Две сети LAN, которые не видят друг друга, могут каждая обратиться к публичному хосту, который затем пересылает между туннелями. Такой маршрутизируемый дизайн требует трёх вещей: включённой пересылки, маршрута к каждому удалённому префиксу и политики пересылки, пропускающей транзит. sysctl -w net.ipv4.ip_forward=1 покрывает первую, ip route add 10.20.0.0/24 dev wg1 вторую, а строка nftables вроде nft add rule inet filter forward iifname "wg0" oifname "wg1" accept третью.
NAT источника проще добавить и труднее отлаживать. Маскирование переписывает адрес клиента, поэтому дальняя сторона видит концентратор, а не настоящий хост, и её правила доступа и журналы перестают быть полезными для атрибуции. Когда обе сети ваши, маршрутизируйте. Когда одна сторона принадлежит третьей стороне, которая не добавит обратный маршрут, NAT — это вариант, который выйдет на этой неделе. В любом случае запишите план адресов, потому что две перекрывающиеся /24 нельзя маршрутизировать никакой конфигурацией.
Ничто в том, что машина служит интернет-выходом для ноутбука, не мешает той же машине завершать входящие туннели. Мешают только перекрывающиеся адреса, и поэтому план адресов — тот документ, который важен.
Проверьте разделение при двух остановленных ролях
Полезный тест — вычитание: остановите две роли и убедитесь, что третья всё ещё отвечает. Веб-запрос должен завершаться при выключенной службе туннеля, а рукопожатие туннеля должно завершаться при остановленном веб-сервере. Если оба работают только тогда, когда работает всё, у вас случайная зависимость, например общая таблица брандмауэра, заблудившийся маршрут или имя, указывающее на неверный адрес.
Три команды делают этот тест конкретным. curl -sk --resolve example.test:443:203.0.113.10 https://example.test/ доказывает, что локальный слушатель отвечает без внешнего DNS, wg show на клиенте доказывает, что рукопожатие завершено, а tcpdump -ni any port 51820 на концентраторе отделяет проблему маршрутизации от проблемы ключа. Рукопожатие, которое никогда не появляется в записи, умерло до хоста: брандмауэр провайдера, неверный публичный адрес или правило NAT по пути.
Повторите вычитание для пересылаемых сетей LAN. При отключённом партнёре ping через концентратор должен проваливаться; при повторном подключении ping должен восстанавливаться без касания правила. Ping, который восстанавливается только после перезапуска туннеля, указывает на политику, а не на достижимость, и это другая правка.
Держите набор правил читаемым, пока вычитаете роли. nft list ruleset должен показывать одно разрешающее правило на каждое действительно нужное направление, а цепочка пересылки, принимающая всё, — это не разделение, а работающая демонстрация.
Линии доступа только по IPv6 и службы IPv4
Некоторые линии доступа дают только достижимость IPv6, а службы всё равно нужно публиковать по IPv4. Сам туннель идёт по тому семейству, которое ещё пропускает трафик, тогда как опубликованная служба находится на адресе внутри туннеля. Это переворачивает обычное допущение и тихо ломает мониторинг: правило брандмауэра или проверка доступности, написанные для пути IPv4, теперь следят за интерфейсом, который ничего не несёт.
Проверяйте каждое семейство отдельно с помощью curl -4 и curl -6, потому что один успешный запрос ничего не говорит о пути, который вы не пробовали. Владение портом не меняется между семействами, а слушатель меняется: ss -lntup перечисляет сокеты IPv4 и IPv6 отдельными строками, и 0.0.0.0:443 не покрывает [::]:443, если процесс не запросил dual-stack сокет.
Служба, которая отвечает по одному семейству и отказывает по другому, обычно проблема слушателя, а не маршрутизации. Прежде чем назвать дизайн готовым, убедитесь, что у каждой роли есть свой адрес или порт, что пересылка включена и разрешена и что каждая роль всё ещё отвечает при двух остановленных.