Не дайте клиентам VPN дотянуться друг до друга в туннеле
Дойти до сервиса — это маршрут, дойти до другого клиента — это политика. Найдите точку пересылки, которая решает трафик между партнёрами, и докажите запрет с клиента.
Два клиента могут оба открыть промежуточный хост и при этом не иметь никакого дела друг до друга. Дойти до сервиса — решение о пересылке, а дойти до партнёра — решение политики, и большинство стеков VPN держат их в отдельных объектах. Ничто из настроенного для первого не обеспечивает второе. Поэтому задача не в том, чтобы добавить правило, а в том, чтобы найти объект, который на этом сервере решает трафик между партнёрами, записать запрет там и доказать его с клиента.
Маршрут к общему сервису — не разрешение между партнёрами
Объявление клиентской подсети сообщает партнёру, как положить пакет на провод в сторону адреса другого клиента. Будет ли пакет доставлен, решается позже тем, что его пересылает. На шлюзе Linux это цепочка FORWARD; внутри демона в пользовательском пространстве — собственная таблица сеансов туннеля. Маршрут отвечает на достижимость и оставляет разрешение нетронутым.
Поэтому проверка изоляции, читающая только маршруты, ничего не доказывает. /24 можно протолкнуть каждому клиенту, тогда как каждый пакет между партнёрами отбрасывается на крючке пересылки. Опасен обратный случай: нет явного правила, разрешающая настройка по умолчанию и изоляция, которая существует лишь потому, что её никто не проверял.
Запишите требование одной строкой, прежде чем открыть терминал. Достигайте блока сервисов и промежуточного хоста, не достигайте ничего другого внутри блока клиентов.
Выясните, кто на самом деле пересылает пакет партнёра
Два поведения сервера выглядят со стороны клиента одинаково и требуют разных правил. Либо ядро снова пересылает пакет из интерфейса туннеля, либо демон копирует его между сеансами, и ядро его никогда не видит.
WireGuard относится к первой группе: у него нет переключателя между партнёрами, поэтому пакет от 10.8.0.4 до 10.8.0.5 приходит на wg0, попадает на крючок пересылки и снова маршрутизируется из wg0, когда AllowedIPs покрывает оба адреса. OpenVPN — вторая группа, как только в конфигурации сервера задан client-to-client, потому что эта директива передаёт пакет между сеансами в пользовательском пространстве. Прежде чем писать что-либо, проверьте загруженную конфигурацию на эту директиву. Правило ядра, которое никогда не видит трафик, неотличимо от работающего правила, и именно так развёртывание остаётся совсем без изоляции.
Читайте действующую конфигурацию, а не файл, который вы считаете загруженным, потому что пакеты дистрибутивов приносят свои включения. Если пересылает демон, запрет принадлежит его собственным средствам управления по каждому клиенту, а брандмауэр хоста фильтрует только трафик к хосту. Если пересылает ядро, крючок пересылки — единственное место, которое имеет значение.
Маршрутизируемые и транслируемые развёртывания требуют ещё одной проверки: источник, который видит удалённый клиент, может быть не тем, который вы ожидаете. Тогда правило, совпадающее с клиентской подсетью, не совпадает ни с чем. Запишите адрес, который выживает после трансляции, прежде чем писать совпадение, и предпочитайте совпадение по паре интерфейсов догадкам об адресах NAT.
Дайте каждому адресу отдельно отзываемую идентичность
Изоляция рушится, как только два устройства делят одно удостоверение. Правило, привязанное к этой идентичности, не может их разделить, а отзыв потерянного ноутбука отзывает всю группу. Сертификаты на устройство или ключи на партнёра держат отзыв локальным, а политику читаемой, потому что каждый адрес соответствует ровно одному человеку или машине. Там, где проект этого не может, скажите это в руководстве, а не называйте правило мелкозернистым.
Распределение адресов должно согласовываться с политикой. Зарезервируйте блок для сервисов, блок для клиентов и выдавайте адреса клиентов из пула, который вы контролируете, а не из диапазона «кто первый пришёл». Если сервер транслирует или мостит, держите таблицу отображения рядом с конфигурацией, потому что именно она нужна следующему читателю, чтобы заново вывести правило.
Поставьте запрет там, где пакет пересекает туннель
На шлюзе, который пересылает трафик WireGuard, случай покрывают два правила: разрешить блок сервисов, затем отбросить остаток блока клиентов. Пара интерфейсов важна не меньше адресов, потому что совпадение только по назначению ловит и трафик из LAN. Загружайте их в этом порядке, чтобы разрешение оценивалось первым.
С nftables пара попадает в существующую таблицу inet filter, сначала разрешение, затем отбрасывание. Оба совпадения привязывают пару интерфейсов и адреса.
nft add rule inet filter forward iifname "wg0" oifname "wg0" ip saddr 10.8.0.0/24 ip daddr 10.8.2.0/24 accept
nft add rule inet filter forward iifname "wg0" oifname "wg0" ip saddr 10.8.0.0/24 ip daddr 10.8.0.0/24 drop
После загрузки прочитайте цепочку обратно, вместо того чтобы доверять коду выхода, и подтвердите, что оба правила стоят в задуманном порядке. Добавьте подходящие правила ip6 для пула IPv6, иначе пара v4 — только украшение на двухстековом туннеле. Если у сервера уже есть разрешающая политика пересылки, добавьте отбрасывание, а не заменяйте цепочку целиком.
Когда на OpenVPN включён client-to-client, эти правила пересылки никогда не совпадают с трафиком между партнёрами, и запрет должен жить в собственных средствах управления демона. Оставьте правила ядра для трафика к хосту, но не считайте их средством изоляции.
Докажите запрет с клиента, а не с сервера
Проверка со шлюза ничего не доказывает, потому что локально созданный трафик идёт путём вывода и никогда не пересекает крючок пересылки. Используйте двух настоящих клиентов: один должен достичь сервиса, другой должен быть отвергнут. Сначала запустите случай разрешения, чтобы сломанный туннель не приняли за работающую политику.
С разрешённого клиента подтвердите, что сервис отвечает на своём порту. С того же клиента попробуйте адрес партнёра и ждите тихого отбрасывания, а не отказа:
ping -c 3 -W 2 10.8.0.5
nc -z -w 2 10.8.0.5 22
nft list chain inet filter forward
iptables -vnL FORWARD
ping -6 -c 3 -W 2 fd00:8::5
Отброшенный пакет и партнёр не в сети выглядят одинаково как тайм-аут, поэтому проверьте счётчики, прежде чем делать выводы. Подтвердите, что счётчик правила отбрасывания растёт, пока партнёр заведомо доступен. Повторите через IPv6, где отсутствующее правило ip6 оставляет запрет v4 выглядящим эффективным. Запишите принятые и отвергнутые результаты в руководство с датой, потому что следующая миграция снова выведет правило.