Публикация портов Docker против VPN: куда уходит ответ
Опубликованный контейнер отвечает со своего адреса до обратного NAT. Две схемы могут удержать этот ответ на публичном пути, с разными затратами.
Хост продолжает отвечать по SSH после поднятия VPN с полным туннелем, а сервис, опубликованный через Docker на той же машине, замолкает. Со слушателем всё в порядке: контейнер работает, сопоставление портов существует, и пакет приходит. Контейнер здоров, и запрос действительно до него доходит. Разница в том, откуда начинается ответ, когда Linux выбирает маршрут, и эта разница решает, какая из двух схем ниже вам нужна.
Проследите кортеж через NAT назначения
Возьмите хост на 192.0.2.10, публикующий TCP 2222 на контейнер на 172.30.240.5:22. Запрос от 198.51.100.25 преобразуется до того, как достичь контейнера, и контейнер отвечает как он сам.
WAN request: 198.51.100.25 -> 192.0.2.10:2222
after DNAT: 198.51.100.25 -> 172.30.240.5:22
container reply: 172.30.240.5:22 -> 198.51.100.25
На момент решения хоста о маршруте ответ всё ещё несёт адрес источника контейнера. Обратное преобразование восстанавливает опубликованный кортеж позже, и именно поэтому пакет выглядит неправильно ровно в тот момент, когда выбирается маршрут.
Почему правило только для хоста это упускает
Правило, выбирающее таблицу WAN только для пакетов от 192.0.2.10, охватывает собственные демоны хоста и полностью упускает этот пересланный ответ. Адрес источника в ответе — 172.30.240.5, а не хост.
Избыточная коррекция — направлять всё из контейнера через WAN, что также освобождает несвязанные исходящие соединения контейнера от VPN. Это другая политика, чем запрошенная, и обычно кого-то удивляет позже.
Сопоставление после DNAT — другой hook
Документация Docker по межсетевым экранам отмечает, что обычная пользовательская фильтрация видит назначение после DNAT, поэтому сопоставление опубликованного порта 2222 в обычном правиле — не то же самое, что сопоставление порта 22 контейнера. Если исходный кортеж важен, используйте поддерживаемый механизм исходного назначения вместо догадок о том, какой порт виден.
Эта путаница с hook-ами порождает правила, которые выглядят правильными и никогда не совпадают. Проверьте счётчики на самом правиле, прежде чем переписывать политику. Правило без единого попадания говорит вам, что оно вычисляется там, куда пакет никогда не доходит.
Схема A: нести решение как состояние соединения
Метка соединения может пронести решение о входе через преобразование, но она должна быть возвращена пакету до того поиска маршрута, который имеет значение. Этапы таковы:
- Зарезервируйте поле метки и приоритет правила, которые не конфликтуют с Docker, VPN или другим владельцем политики.
- Определите только предполагаемое новое соединение WAN в точке до DNAT, где доступно отслеживание соединений.
- Запишите решение в метку соединения, не заставляя сам исходный запрос идти в таблицу WAN.
- В направлении ответа восстановите на пакете только свои биты метки перед маршрутизацией.
- Выберите полную таблицу WAN достаточно рано, чтобы правило VPN не могло победить первым.
Этой таблице нужны реальные подключённые маршруты и шлюз. Ей также нужно определённое поведение, когда маршрут отсутствует. Тихий провал в туннель — худший исход, потому что конфигурация всё ещё выглядит рабочей. Вместо этого откажите закрыто и запишите это.
Схема B: завершать на хосте
Намеренно настроенный фронтенд на стороне хоста, например TCP-прокси или обратный прокси перед контейнером, удерживает ответ исходящим с адреса хоста. Тогда правила, написанные для хоста, ведут себя как ожидалось, а контейнер остаётся в своей частной сети.
Цена — ещё одна движущаяся часть со своей конфигурацией, обработкой адресов источника и контролем доступа. Обычно это более простая поддерживаемая граница, и часто это правильный ответ, когда политику маршрутизации поддерживал бы один человек, который может уйти.
Проверяйте изменение, а не намерение
Откройте новое авторизованное соединение с публичной стороны и убедитесь, что ответ уходит через нужный интерфейс. Затем проверьте, что несвязанный запрос, инициированный контейнером, по-прежнему идёт через VPN.
Не очищайте таблицу соединений машины, чтобы проверить одно правило: существующие записи сохраняют прежнее сопоставление, а очистка ломает всё остальное на хосте. Публикация порта определяет доступность. Она не определяет путь ответа. Запишите выбранную схему с именами её таблиц, чтобы следующему человеку не пришлось открывать её заново во время инцидента.