Проброс SSH к GitLab через внешнюю VM без публичного IPНужно было развернуть Gitlab на домашнем сервере, но чтобы из интернета можно было по ssh ходить. На домашнем сервере нет публичного IP, а значит нужна внешняя виртуалка. Ситуацию усложняет тот факт, что внешняя виртуалка не связана напрямую с локальным сервером, а общение происходит через посредника как-то так:

Можно было бы создать VPN‑соединение между внешней виртуалкой и внутренней, где развернут GitLab, но тогда для каждой новой службы требовалось бы отдельное VPN‑соединение. В схеме выше нужно соединить только одну внешнюю и одну внутреннюю виртуалки под VPN, а дальше передавать трафик от внутреннего шлюза к локальным хостам. Кажется, это намного проще в понимании и инфраструктурно.
И тут нам поможет NAT ...
Цель статьи: показать кейс для понимания
NAT, который как оказалось не такой уж и сложный :)
Мы не можем просто так взять и перенаправить трафик через несколько виртуалок до конечной точки и получить ответ обратно, потому что трафик состоит из IP-пакетов, которые содержат только один набор адресов источника и назначения.
Трафик по схеме выше должен проходить через 2 шлюза/виртуалки к Gitlab и обратно, и на каждом шлюзе нужно изменять адреса источника и приемника у каждого IP-пакета. Эти операции можно писать так:
IP к внутреннему GitLab.В Linux NAT реализуется через таблицу NAT в iptables в следующих цепочках:
| Цепочка | Когда срабатывает | Что обычно делаем |
|---|---|---|
| PREROUTING | На входе виртуалки, до роутинга | DNAT (перенаправление входящих IP-пакетов) |
| POSTROUTING | На выходе виртуалки, после роутинга | SNAT / MASQUERADE (перенаправление исходящих IP-пакетов) |
Настоятельно рекомендую ознакомиться с iptables в этой статье.
Прежде чем начнем необходимо открыть следующие порты на виртуалках:
gateway-external:2222gateway-internal:2222gitlab:22Разрешить TCP-трафик на 2222 порту можно так:
$ sudo iptables -A INPUT -p tcp --dport 2222 -j ACCEPT
А просмотреть список открытых портов можно так (убрал все лишнее, оставил только результат работы прошлой команды):
$ sudo iptables -vnL
Chain INPUT (policy DROP 13 packets, 6051 bytes)
1 40 ACCEPT 6 -- * * 0.0.0.0/0 0.0.0.0/0 tcp dpt:2222
Для начала нам нужно разрешить пересылку трафика через оба наших gateway так:
$ sudo sysctl -w net.ipv4.ip_forward=1
# или так чтобы работало и после перезагрузки
$ echo "net.ipv4.ip_forward=1" | sudo tee -a /etc/sysctl.conf >/dev/null
Теперь начнем с конца и будем сразу же тестировать, попробуем gateway-internal <=> gitlab.
На gateway-internal нужно:
# перенаправляем все TCP‑запросы на порт 2222 к GitLab (192.168.3.14:22)
$ sudo iptables -t nat -A PREROUTING -p tcp -m tcp --dport 2222 -j DNAT --to-destination 192.168.3.14:22
# подменяем исходный IP у пакетов из 10.0.0.0/24, идущих к 192.168.0.0/17,
# чтобы ответы возвращались через этот шлюз
$ sudo iptables -t nat -A POSTROUTING -s 10.0.0.0/24 -d 192.168.0.0/17 -j MASQUERADE
Протестируем что мы можем подключиться по ssh из gateway-internal к gitlab с детальным логом:
$ ssh git@192.168.3.14 -vvv
Если ничего не двигается дальше строки debug3: set_sock_tos: set socket 3 IP_TOS 0x10, но значит мы не можем установить соединение, а если видим debug1: Connection established и в конечном итоге либо удалось войти либо git@192.168.3.14: Permission denied (publickey) то все сработало.
Двигаемся дальше, теперь нужно настроить трафик внутри сети WireGuard от gateway-external в gateway-internal и обратно.
На gateway-external нужно:
# перенаправляем все входящие TCP‑соединения, пришедшие на порт 2222 (на этом шлюзе),
# к внутреннему хосту 10.0.0.3, тоже на порт 2222
$ sudo iptables -t nat -A PREROUTING -p tcp -m tcp --dport 2222 -j DNAT --to-destination 10.0.0.3:2222
# подменяем исходный IP‑адрес пакетов, чтобы ответы от 10.0.0.3 шли обратно через этот же шлюз.
$ sudo iptables -t nat -A POSTROUTING -d 10.0.0.3/32 -p tcp -m tcp --dport 2222 -j MASQUERADE
Теперь нужно попробовать подключиться по ssh с gateway-external на gateway-internal на 2222 порт:
$ ssh root@10.0.0.3 -p 2222 -vvv
Анализируем вывод аналогично предыдущему тесту.
И финально пытаемся подключиться по ssh извне как если бы обычно пользовались Gitlab'ом:
$ ssh git@gitlab.example.com -p 2222 -vvv
Во время настройки может произойти ситуация, когда не удается подключиться и нет полного понимания проблемы. В этом случае может помочь анализ самой таблицы iptables, а именно метрик срабатывания правил для пакетов:
# вывести таблицу правил цепочки PREROUTING
$ sudo iptables -t nat -L PREROUTING -n -v
# вывести таблицу правил цепочки POSTROUTING
$ sudo iptables -t nat -L POSTROUTING -n -v

PREROUTING не растет, значит с вашим правилом в цепочке PREROUTING что-то не такPREROUTING растет, но не растет в POSTROUTING, значит проблема в правилах цепочки POSTROUTINGОбязательно убедитесь что нужные порты на ваших машинах открыты, иначе правила в таблице
NATне будут работать.
$ sudo iptables -vnL
Если где-то ошиблись в команде и нужно очистить таблицу NAT то можно так:
# очистить все цепочки
$ sudo iptables -t nat -F
# очистить цепочку PREROUTING
$ sudo iptables -t nat -F PREROUTING
# очистить цепочку POSTROUTING
$ sudo iptables -t nat -F POSTROUTING
Вывести команды для установки правил в таблице NAT:
$ sudo iptables -S -t nat
-P PREROUTING ACCEPT
-P INPUT ACCEPT
-P OUTPUT ACCEPT
-P POSTROUTING ACCEPT
-A PREROUTING -p tcp -m tcp --dport 2222 -j DNAT --to-destination 10.0.0.3:2222
-A POSTROUTING -d 10.0.0.3/32 -p tcp -m tcp --dport 2222 -j MASQUERADE
Это позволит увидеть весь список правил для таблицы NAT.
Краткий чек‑лист:
net.ipv4.ip_forward = 1 включён2222 и 22)DNAT/POSTROUTING добавлены на обеих виртуалкахssh -p 2222 проходит без ошибокИтого: с помощью двух простых правил в iptables мы получили полностью автоматический прокси для SSH‑доступа к GitLab во внутренней сети спрятанный за двумя виртуалками, без необходимости разворачивать отдельный VPN‑туннель для каждой машины.