Файрвол, который пропускает только порт VPN, — это не одно правило. Это три разных решения, которые случайно касаются одного и того же номера порта: цепочка входа решает, что может дойти до локального процесса, цепочка перенаправления решает, что может пройти через хост от имени клиентов туннеля, а таблица NAT переписывает адрес источника на выходе. Ошибётесь в одном из них, и результат редко бывает чистым сбоем. Вы получите хост, который принимает клиентов, но ничего не загружает, или хост, который выглядит закрытым, пока кто-нибудь не прочитает порядок цепочек.

Единственный порт, который нельзя закрыть

Слушатель VPN должен принимать подключения с адресов, которые вы не можете предсказать, поэтому по определению это публичный сервис. Это единственное исключение, вокруг которого построен дизайн: один UDP-порт для OpenVPN, один UDP-порт для WireGuard, один TCP-порт для SSL VPN. Всё остальное принадлежит за туннелем, а значит, прикладные сервисы должны привязываться к адресу туннеля или к loopback, а не ко всем интерфейсам.

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

Вход, перенаправление и NAT отвечают на разные вопросы

Входящий трафик адресован самому хосту, поэтому там живут слушатель VPN и административный путь. Трафик перенаправления проходит через хост, и клиентам туннеля он нужен вместе с net.ipv4.ip_forward=1. Классическая наполовину сломанная конфигурация разрешает исходящий поток и забывает обратное направление, поэтому клиент завершает рукопожатие и дальше ничего не загружается; правило conntrack для установленного и связанного трафика закрывает это направление.

NAT — третий вопрос. Без перезаписи в postrouting перенаправленные пакеты уходят с частным адресом туннеля и отбрасываются выше, потому что ни одна сеть за пределами хоста не знает, как маршрутизировать пул обратно. Префикс источника в этом правиле должен совпадать с пулом VPN, а не с собственным адресом хоста, и то же относится к правилу перенаправления, которое называет подсеть клиентов.

IPv6 — это вторая политика, а не запоздалая мысль. Хост со строгой цепочкой IPv4 и открытой политикой на ip6tables ничего не закрыл; сервисы всё ещё доступны через другое семейство. Либо отразите набор правил с помощью ip6tables, либо намеренно отключите IPv6 на публичном интерфейсе, и запишите, что выбрали.

Основа, которую читают за один проход

Начните с запрета, затем добавьте только то, что нужно хосту. Сначала loopback и ответы, затем административный путь, затем слушатель туннеля, затем политика перенаправления для клиентов:

iptables -P INPUT DROP
iptables -P FORWARD DROP
iptables -A INPUT -i lo -j ACCEPT
iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
iptables -A INPUT -p tcp --dport 22 -m conntrack --ctstate NEW -j ACCEPT
iptables -A INPUT -p udp --dport 1194 -m conntrack --ctstate NEW -j ACCEPT
sysctl -w net.ipv4.ip_forward=1
iptables -A FORWARD -i tun0 -o eth0 -s 10.8.0.0/24 -j ACCEPT
iptables -A FORWARD -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
iptables -t nat -A POSTROUTING -s 10.8.0.0/24 -o eth0 -j MASQUERADE

Имена интерфейсов, порт и пул — это заполнители; читайте их с сервера, а не копируйте. Сохранить результат так же важно, как написать его, потому что набор правил, живущий только в памяти, исчезает при следующей перезагрузке и тихо снова открывает хост. Сделайте его постоянным средством дистрибутива и держите копию вне машины на тот день, когда машина не загрузится.

Закрыть последний порт, не потеряв хост

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

Применяйте изменение из уже установленной сессии, а не через соединение, которое собираетесь фильтровать. Запланируйте откат, чтобы ошибка истекла, а не стала постоянной, например задание systemd-run --on-active=10min, которое возвращает прежний набор правил, и держите вторую сессию SSH открытой, пока проверяете первую. Затем убедитесь, что задание отката действительно срабатывает, потому что непроверенное восстановление равно его отсутствию.

Проверяйте путь, а не скрипт

Читайте загруженные правила, а не файл, который должен был их загрузить. С авторизованного внешнего хоста убедитесь, что отвечают только нужные порты; изнутри туннеля убедитесь, что сервисы, которые вы хотели опубликовать, доступны. Политика запрета по умолчанию, которая блокирует и трафик туннеля, выглядит так же, как работающее закрытие, пока кто-нибудь не попробует им воспользоваться.

iptables -S
iptables -t nat -S
ss -lntup
ip -4 addr show

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

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

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