Поднять второй OpenVPN сервер означает лишь второе место, куда могут подключаться клиенты, и ничего больше. Он не помещает этих клиентов в сеть первого сервера, а опция client-to-client меняет только пересылку внутри одного процесса сервера. Два пула становятся одной сетью, когда вы намеренно маршрутизируете между ними, с владельцем для каждого направления. Всё ниже предполагает режим TUN в пространстве пользователя с отключённой такой внутренней пересылкой, так что решение принимает ядро.

Миф: один список учётных записей, одна сеть

Аутентификацию и достижимость путают, потому что и то и другое живёт в конфигурации сервера. Пользователь, который может войти в любой из двух шлюзов, снаружи выглядит как пользователь одной системы с двумя дверями. Внутри каждый сервер выдаёт адреса из своего пула и устанавливает маршруты только для тех клиентов, которые он обслуживает. Поэтому клиент на шлюзе A, отправляющий пакет клиенту на шлюзе B, запрашивает адрес назначения, о котором его собственный сервер никогда не слышал. Пакет уходит по маршруту по умолчанию A, покидает туннель и прибывает к межсетевому экрану, которого никогда не просили его пропустить.

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

Дайте каждому шлюзу свой пул и транзитный адрес

Два диапазона, которые не могут пересекаться ни с чем другим, плюс небольшой транзитный канал между хостами — вот и весь план адресации. Держите транзит в приватном сегменте, который вы контролируете, и запишите числа до того, как любой из серверов выдаст аренду.

GatewayClient poolTransit address
gateway-a10.64.10.0/24172.28.44.1/30
gateway-b10.64.20.0/24172.28.44.2/30

Противостойте соблазну дать обоим серверам один и тот же пул, чтобы профили оставались идентичными. Маршрут ядра не может указать, какой из двух хостов сейчас владеет произвольным адресом внутри общего диапазона, поэтому таблица маршрутизации указывала бы на один шлюз, а клиент мог бы быть подключён к другому. Имена интерфейсов — локальные соглашения, а не общее состояние. Оба хоста здесь называют туннельное устройство tun0, а транзитный канал transit0, и это нормально, потому что ни один пакет не несёт этих имён.

Установите маршрут на обеих сторонах

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

sysctl net.ipv4.ip_forward
ip route add 10.64.20.0/24 via 172.28.44.2 dev transit0
ip route get 10.64.20.37

Отразите это на другом хосте, поменяв пулы местами. Повторное добавление одного и того же маршрута из постороннего скрипта — это способ получить на хосте несколько копий и загадочное изменение, когда кто-то в следующий раз прочитает таблицу. Если транзитный канал сам является туннелем, маршрут указывает на топологию этого туннеля, и переход должен пережить его переподключение. Транзитный адрес, исчезающий на тридцать секунд, забирает с собой путь между пулами, и это свойство проекта стоит озвучивать, а не обнаруживать.

Проталкивайте клиентский маршрут, не подделывая владение

Клиентам нужен маршрут к удалённому пулу, иначе их собственная операционная система отправит пакет на шлюз по умолчанию, и туннель его никогда не увидит. Конфигурация сервера — естественное место, чтобы его доставить.

push "route 10.64.20.0 255.255.255.0"

Поместите зеркальную строку в конфигурацию другого сервера, затем переподключите тестовый клиент и прочитайте его таблицу. Клиенту, который игнорирует протолкнутые маршруты, нужна собственная утверждённая конфигурация, а маршрут, который никогда не приходит, — не контроль доступа. Не давайте клиенту iroute для всего пула другого шлюза. Эта директива существует для подсети за одним подключённым клиентом, и использование её, чтобы замаскировать отсутствующий маршрут между шлюзами, порождает сервер, который считает, что владеет адресами, до которых не может добраться.

Докажите один запрос и откажите в другом

Выберите реальную операцию, например HTTPS от клиента на 10.64.10.21 к сервису на 10.64.20.37, и разрешите именно её на обоих хостах. Каждый межсетевой экран пропускает запрос на входящей стороне и отслеженный ответ на обратной; политика самого сервиса назначения всё равно должна его принять.

iptables -A FORWARD -i tun0 -o transit0 -s 10.64.10.21 -d 10.64.20.37 -p tcp --dport 443 -j ACCEPT
iptables -A FORWARD -i transit0 -o tun0 -d 10.64.10.21 -s 10.64.20.37 -p tcp --sport 443 -m conntrack --ctstate ESTABLISHED -j ACCEPT

При взаимных маршрутах NAT источника не нужен и здесь активно вреден: трансляция источника скрывает, какой клиент сделал запрос, и подрывает политику на основе источника на стороне назначения. Проверьте, что существующее правило masquerade для выхода в интернет не захватывает и этот путь. Затем проверьте отказ. Сервис, который должен оставаться локальным для пула B, должен оставаться недостижимым из пула A, а пары клиентов на одном шлюзе тоже нужно проверить, поскольку изменённая вами политика пересылки распространяется и на них.

Ещё одна поправка к мысленной модели: транзитный канал несёт расшифрованные внутренние пакеты, поэтому это граница доверия, а не частная гарантия. Если он пересекает сеть, которую вы не контролируете, защитите этот переход утверждённым шифрованным туннелем и маршрутизируйте через его реальную топологию. Сквозной TLS всё равно сам аутентифицирует приложение, поэтому внутренний сервис должен продолжать его использовать.

Похожие статьи