Чи можуть клієнти на двох серверах OpenVPN дістатися один одного?
Два сервери OpenVPN створюють два пули клієнтів, а не одну мережу. Маршрутизуйте між ними через окремі підмережі, взаємні маршрути та явну межу довіри.
Підняти другий OpenVPN сервер означає лише друге місце, куди можуть підключатися клієнти, і нічого більше. Він не поміщає цих клієнтів у мережу першого сервера, а опція client-to-client змінює лише пересилання всередині одного процесу сервера. Два пули стають однією мережею, коли ви навмисно маршрутизуєте між ними, з власником для кожного напрямку. Усе нижче припускає режим TUN у просторі користувача з вимкненим таким внутрішнім пересиланням, тож рішення ухвалює ядро.
Міф: один список облікових записів, одна мережа
Автентифікацію й доступність плутають, бо обидві живуть у конфігурації сервера. Користувач, який може увійти на будь-який із двох шлюзів, ззовні виглядає як користувач однієї системи з двома дверима. Усередині кожен сервер видає адреси зі свого пулу й встановлює маршрути лише для тих клієнтів, які він обслуговує. Тому клієнт на шлюзі A, який надсилає пакет клієнту на шлюзі B, запитує призначення, про яке його власний сервер ніколи не чув. Пакет іде за маршрутом за замовчуванням A, залишає тунель і прибуває до міжмережевого екрана, якого ніколи не просили його пропустити.
Тож додавання другого сервера не додає резервування, виявлення чи спільного широкомовного домену. Воно додає маршрутизовану мережу, якій потрібен план, і план починається з адресації.
Дайте кожному шлюзу власний пул і транзитну адресу
Два діапазони, які не можуть перетинатися ні з чим іншим, плюс невеликий транзитний канал між хостами — ось увесь план адрес. Тримайте транзит у приватному сегменті, який ви контролюєте, і запишіть числа до того, як будь-який сервер видасть оренду.
| Gateway | Client pool | Transit address |
|---|---|---|
| gateway-a | 10.64.10.0/24 | 172.28.44.1/30 |
| gateway-b | 10.64.20.0/24 | 172.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 усе одно сам автентифікує застосунок, тому внутрішній сервіс має й надалі його використовувати.