Ни один из последних моих проектов не обходился без подключения по SSH, чтобы что-то посмотреть/настроить/отладить/пофиксить и т.д. Но часто для этого нужно подключиться к определённому TCP-порту удалённого сервера, а он недоступен: то открыт лишь на localhost, то где-то маршрута не хватает, то безопасники запрещают. Во всех этих случаях на выручку приходили SSH-туннели. Расскажу о них чуть подробнее, вдруг кто-то не знал или недооценивал 🧐
Что это?
Фича SSH клиента и сервера, позволяющая "пробросить" порт с удалённого сервера на свою локальную машину (где запущен SSH клиент). То есть сделать так, чтобы обращение на этот порт у localhost'а волшебным образом приводило к обращению на этот же (или другой) порт у сервера, но при этом весь траффик оставался закрытым внутри SSH-соединения.
По смыслу это похоже на port-forwarding у
kubectl и docker, но может применяться в бОльшем числе случаев ⚒️Зачем?
Чтобы мультиплексировать множество соединений с удалённым сервером по одному SSH-каналу без открытия портов на внешних сетевых интерфейсах сервера. Проще говоря, чтобы завернуть весь траффик внутрь SSH независимо от протокола. Например:
• чтобы удалённо отлаживать JVM-приложение, не выпячивая порт отладчика наружу;
• чтобы мониторить приложение по JMX, не заморачиваясь с настройкой SSL для этого;
• чтобы дергать Spring-приложение за actuator, не затеваясь с получением сертификатов для HTTPS 📌
Как включить?
В общем случае включение сводится примерно к такой команде в консоли:
ssh -L <local-port>:<remote-address>:<remote-port> -N -f <ssh-host>
, где:
•
-L — команда на включение локального туннеля;•
local-port — порт, на котором вы хотите видеть удалённый сервис на своей машине (как правило, равен remote-port);•
remote-port — порт удаленного сервера, который нужно сделать доступным у себя (как правило, равен local-port);•
remote-address — IP или имя сетевого интерфейса, на котором открыт удалённый пробрасываемый порт; чаще всего это localhost, но могут быть и другие;•
-N — признак отсутствия команды, которую нужно выполнить на удалённом сервере (обычно она не нужна);•
-f — признак запуска туннеля в фоновом режиме; если не добавить, управление не вернётся в консоль, и туннель будет висеть в ней;•
<ssh-host> — либо имя хоста для подключения (может быть с именем пользователя, например, root@somehost.com), либо название настроек подключения из файла ~/.ssh/config, например:Host my-sandbox-vps
Hostname 55.555.555.0
User root
IdentityFile ~/.ssh/id_dsa
IdentitiesOnly yes
С такими настройками включение SSH-туннеля для удалённой отладки может выглядеть так:
ssh -L 5005:localhost:5005 -N -f my-sandbox-vps
Причём и эта команда, и файл настроек останутся точно такими же даже под Windows, если у вас установлен OpenSSH 🪟
Как выключить?
Поскольку каждый туннель — это отдельный процесс, для его выключения достаточно этот процесс остановить. На Linux/MacOS это может выглядеть примерно так:
$ ss -tulpn | grep 5005
tcp LISTEN 0 128 127.0.0.1:5005 0.0.0.0:* users:(("ssh",pid=307017,fd=5))
$ kill 307017
А если туннель запущен без флага
-f, то в его консоли достаточно нажать Ctrl+C✖️Какие недостатки?
• быстродействие: как правило, скорость передачи заметно падает, поэтому смотреть видосики в UHD по такой схеме будет не комфортно;
• искажение цепочки вызовов: все запросы в целевой сервис будут приходить с localhost, где запущен SSH-сервер; это может создавать проблемы;
• непрозрачность: если целевой хост перестанет быть доступен, туннель просто молча упадёт, не подавая явных признаков и не пытаясь переподключиться (это всё преодолимо, но потребует усилий) 🧟
P.S. Картинка взята из этой статьи Ивана Величко, где он также объясняет и другие варианты построения туннелей на SSH. Почитайте, и пусть вам это никогда не пригодится 🫠
