В какой‑то момент у меня возникла необходимость разобраться с простыми SSH‑туннелями: как их запускать и какие туннели могут помочь мне (обычному web‑разработчику). Разобраться в этом удалось и я решил поделиться пояснениями в простой понятной форме.
Сразу важная мысль: основу для для этого поста в свою голову впитал из этой статьи, за что автору огромное спасибо! Эта статья чуть более хардкорная для понимания, но куда более емкая, чем моя. Мой вариант для тех, кто в этом почти ничего не понимает и хочет разобраться с картинками и деталями.
Если вам более удобен видео‑формат, то можете посмотреть его на YouTube.
С чего возникла необходимость в туннелях?
Я применяю, так или иначе, SSH туннели регулярно в своей работе. Иногда необходимо получить доступ к ресурсу, базе данных или сервису, который находится в закрытой сети, но у меня есть SSH доступ к одному из серверов в этой сети.
Все описанное ниже является исключительно интерпретацией моего опыта и изучения различных статей по теме. Буду очень признателен, если вы найдете ошибку и сообщите мне об этом в комментариях. Спасибо!
Мое первое знакомство с туннелями произошло при моей попытке развернуть разработанный проект на сервере клиента. Я приехал к заказчику в офис, заранее сохранив в заметки ссылки на наши репозитории, названия докер образов и тд.
Но меня ждал сюрприз. Клиент просто дал мне клавиатуру, показал монитор и сказал «на, разворачивай». По политике безопасности клиента я не мог получить удаленный доступ к этому серверу, чтобы удобно заливать все из своего офиса. Поэтому мне предстояло руками разворачивать все на этом сервере, вводя все ссылки и названия по букве.
Я с ужасом представил, как я буду руками вбивать каждую ссылку для репозитория и понял, что на это все уйдет ни один час.
Я решил не тратить на это ни минуты и потратил достаточно времени, чтобы подключить этот сервер к нашей openvpn сети, чтобы потом спокойно заходить на этот сервер удаленно. Это было достаточно муторно, но своей цели я добился и релизил потом уже удаленно.
Решив эту задачу я стал изучать другие, более упрощенные способы, решения этой задачи. Так я познакомился с туннелями.
Основная идея туннелей
В целом суть туннеля наглядно изображена на анимации выше. Но дополню текстом:
У вас есть сервер, к которому вы хотите получить доступ, но не можете ходить на него напрямую. Назовем его сервер 1;
Между вами и целевым сервером есть сервер, который имеет доступ к целевому и к которому имеете SSH доступ вы. Назовем его сервер 2;
Вы можете зайти по SSH на сервер 2 и, через консоль, уже отправлять любые запрос на сервер 1. Также вы можете зайти по SSH на сервер 1 и двигаться дальше.
Примерно так и работает SSH‑туннели. Только нам не всегда обязательно заходить на один сервер и с него уже слать запросы на следующий. Для упрощения этого процесса мы можем воспользоваться переадресацией портов (Port Forwarding).
Port forwarding на пальцах
У меня ушло какое-то количество мыслетоплива, чтобы понять суть работы переадресации портов.
Расширим ситуацию, описанную выше:
У нас есть сервер S1, к которому у нас есть доступ на любой порт;
Есть сервер S2, к 80 порту которого хотим получить доступ мы через наш S1;
Мы одной командой можем открыть на S1 80 порт, чтобы он пересылал эти запросы на 80 порт S2 и потом передавал нам ответ;
Также, например, одной командой, мы можем открыть 81 порт, который будет переадресовывать запрос на 80 порт другого сервера S3.
Именно эта «пересылка» портов и называется переадресацией портов или Port Forwarding. Теперь давайте перейдем к реальным кейсам использования туннелей.
Реальные примеры из практики
Кейс с nginx
Рассмотрим простой кейс. У нас есть сервер, который закрыт миру, но мы имеем к нему SSH доступ. На этом сервере запущен сервер nginx, который слушает 80 порт.
Для того, чтобы получить доступ к тому, что опубликовано на 80 порту сервера, мы можем открыть туннель, который подключится к серверу, и опубликовать на локальном 80 порту. То есть, мы будем стучаться на localhost:80 и видеть ровно то, что опубликовано на 80 порту удаленного сервера. Для этого вводим следующую команду:
ssh -L 80:127.0.0.1:80 remote.server.address
После ввода этой команды мы подключимся на сервер по SSH и, что важно, пока активно это соединение, у нас будет активен этот туннель. Теперь мы можем в браузере открыть localhost:80 и откроется то, что опубликовано на 80 порту удаленного сервера. Подробнее что-есть-что в этой команде:
Первым параметром идет флаг «‑L» который указывает на то, что нам необходимо открыть туннель и начать слушать порт.
Далее указываем интерфейс на котором мы хотим слушать (по‑умолчанию localhost и в рамках данного примера он не указан). В нашем случае указываем порт 80 (на рис. «локальный порт»).
Далее указываем куда надо будет подключаться на удаленном сервере. В нашем примере 127.0.0.1, что значит, что туннель будет пробрасывать на сам же удаленный сервер. На что можно заменять это значение можно прочитать ниже.
Порт на удаленном сервере — в нашем случае это 80. Если бы nginx, например, видел бы на 8080, то мы бы указали 8080 в этом месте.
Адрес удаленного сервера. Указываем его тем же самым образом, что и при обычном подключении к SSH.
Теперь можно переходить на localhost в браузере и видеть то, что на сервере на 80 порту. В моем примере можно увидеть только ошибку nginx, которую выдает nginx‑proxy на сервере.
Кейс с базой данных Postgres в докере
Теперь к еще одному полезному кейсу, с которым мы сталкиваемся во время разработки, так или иначе, регулярно.
Есть я и мой ноутбук
На ноутбуке есть код моего backend приложения
В основном я работаю с локальной копией базы данных во время разработки
-
По какой-то причине мне надо подключиться к базе данных, которая находится на сервере (тестовом или боевом) в изолированной сети Docker.
Когда мы говорим про работу через IDE для работы с БД, мы можем легко воспользоваться встроенным механизмом туннелей (инструкция по продуктам от JetBrains тут). Но трудности возникают если мы хотим из кода нашего приложения подключиться к этой базе.
Кстати, этот момент актуален не только для базы данных. Иногда нужно просто подключаться к разным сервисам, которые находятся в изолированной сети, куда мы можем иметь доступ через SSH. Так что, этот пример лишь показательный и область применения крайне обширна
Для решения этого вопроса достаточно вбить следующую команду:
ssh -L 5432:postgres-db:5432 remote.server.address
Тут ситуация аналогична предыдущей. Только вместо локального ip адреса самого сервера мы указываем ip адрес (в данном случае имя хоста postgres-db
) в качестве целевого адреса.
В случае работы с Docker я использую этот контейнер, чтобы знать адреса всех контейнеров на сервере. После запуска имя хоста каждого контейнера можно получить в файле
/etc/hosts
. И после перезапуска ip адрес меняется, а хост остается
Дальше я уже могу указывать localhost:5432
в качестве сервера postgres в моем приложении и подключусь уже напрямую к базе данных в docker контейнере на том сервере.
Таких туннелей можно запускать сколько угодно. Все запускаются по аналогии. Например можно построить такую схему:
Есть сервера S1, S2, S3.
S1 имеет доступ к S2 и S2
Я имею доступ только к S1 через SSH на 22 порту
На серверах S2 и S3 запущены Postgres на 5432 и HTTP на 80 портах соответственно.
Я запускаю 2 туннеля и работаю с этими серверами как будто с локальными. Команды для запуска туннелей ниже:
ssh -p 22 -l 5432:s2:5432 s1
ssh -p 22 -l 80:s3:80 s1
Обратные туннели
Ок, мы разобрались как получить доступ к удаленному серверу, который находится в одной сети с доступным нам сервером. Но иногда бывают ситуацию, обратные этой. Например:
Я работаю на своем ноутбуке и у меня крутится копия сайта клиента на 3000 порту
Нахожусь при этом в кафе с бесплатным wifi
Клиент находится у себя в офисе в закрытой сети
Я хочу показать текущую версию сайта клиента прямо со своего ноутбука
И я и клиент имеем доступ к одному серверу во внешней сети (например мой VPS)
Именно для такого кейса уже есть готовые решения — localtunnel или ngrok. Я сам ими пользуюсь, когда надо быстренько поднять и показать. Но иногда надо подключиться на удаленном сервере, например, к моей локальной базы для повторения какого‑нибудь особого кейса.
Для решения этого вопроса нам как раз может помочь обратный туннель (Reverse port forwarding). Чтобы клиент мог видеть сайт на своем ПК, мне необходимо запустить следующую команду:
ssh -R 0.0.0.0:3001:127.0.0.1:3000 server.ru
Давайте разберемся с каждым параметром в этой команде:
Справа налево:
Адрес сервера, который доступен и мне и клиенту
Локальный адрес и порт, который я хочу сделать доступным (localhost:3000)
Адрес и порт на общем сервере, на котором будет отображаться мой сайт с 3000 порту
Флаг, передающий SSH серверу, что я намерен открыть обратный туннель на мой локальный порт
Клиент, после запуска такого туннеля, может видеть наш сайт по адресу server.ru:3001.
Правда сделать это может быть чуть сложнее чем «просто вбить одну команду». Дело в том, что «из коробки» sshd демон не допускает подключения на внешний интерфейс.
Другими словами — то, что мы указали «0.0.0.0» как адрес для прослушивания, говорит серверу следующее: «принимай любые соединения на любой интерфейс на порт 3001, даже если запрос идет извне». Но эта настройка, обычно, выключена. Для того, чтобы внешние соединения заработали необходимо в файле /etc/ssh/sshd_config
добавить строчку GatewayPorts yes
Стоит быть крайне внимательным и аккуратным с этой настройкой. Ставя её в значение
yes
вы открываете порт всему миру и это может стать прекрасной дырой в безопасности для злоумышленников. ВСЕГДА ЗАКРЫВАЙТЕ ТУННЕЛИ после завершения работы через них.
Для чего еще можно использовать обратный туннель
Я использовал обратные туннели, также, при отладке проектов на PHP. В случае, когда код расположен на удаленном сервере и мы работаем с кодом через SFTP, бывает необходимость отладить код, который запущен на сервере, но через локальный XDebug. Тут на помощь также приходит SSH туннель, который запускается и дает доступ удаленному серверу к нашему ПК.
Вместо заключения
На этом все. Это была короткая сводка про те туннели, которые я использую в своей работе. Благодарю за внимание! Буду рад вашей подписки на меня в YouTube и Telegram
Комментарии (24)
s3rs3r
31.03.2024 20:46Не могли бы вы, пожалуйста, дополнить, где вы запускаете эти команды, на ноутбуке или на удаленном сервере?
А то что-то к концу чтения все потихоньку смешалось. Так и не понял, почему для примера с кафе нельзя использовать прямой туннель только поменять местами параметры..
И по-моему в примере с обратным туннелем опечатка в номере порта.
amorev Автор
31.03.2024 20:46Запускаю все со своего компа/ноутбука. На сервере только настройку демона сделал, чтобы принимал соединения из вне.
В примере с кафе вы не сможете подключиться с удаленного сервера на свой ноутбук, т.к. ноутбук находится в закрытой сети без белого ip адреса.
alexyr
31.03.2024 20:46Использую обратный SSH тоннель для доступа в домашнюю сеть без белого IP.
Домашний сервер держит подключение к внешнему, на бесплатном хостинге.
Ну и дублируется с помощью Tailscale, на случай если всё упало.Johan_Palych
31.03.2024 20:46Да, штука годная даже с Tailscale at home(Pricing per active user/month $0)
Up to 3 users w/ public domain, Up to 100 devices
Больше года пользуюсь Headscale:
Headscale - an open source implementation of the Tailscale control server
https://hub.docker.com/r/headscale/headscale
https://github.com/juanfont/headscale
Kil1J0y
31.03.2024 20:46У меня тоже ssh для доступа в локалку, только через ddns, сервер далеко, задержка большая, в планах развернуть wg на локальном сервере rdp over ssh показал себя не очень
Kirikekeks
31.03.2024 20:46Читаю подобные статьи, потому что не запоминается, редко использую. И никогда нет описания как оформить эти команды, да с дополнительными параметрами подключения в .ssh/config. А преимуществ такой записи много, это и сокращение до ssh S3 и склерозник на будущее. Забыл - заглянул в config. Если эту тему прокачали, и часто используете, может допишете? Полезная же, и никак не освещённая.
amorev Автор
31.03.2024 20:46Регулярно пользуюсь config. Даже пытался написать bash-completion на базе
.ssh/config
но не удалось:)Tony-Sol
31.03.2024 20:46Не bash конечно, но как то пользовался этим https://github.com/sunlei/zsh-ssh - довольно удобно
difhel
Ого, спасибо за статью. Честно говоря, в закладках лежит уже несколько статей про SSH туннели, но только ваша позволила мне полностью понять, как они работают :)
Расскажите, пожалуйста: допустим, я хочу развернуть приложение на сервере у себя дома, без статичного IP. SSH-соединения имеют свойство разрываться. Соответственно, туннель тогда тоже перестанет работать. Какие есть хорошие решения этой проблемы?
nicosha
Ngrok
difhel
У ngrok есть проблема, что туннель работает не больше 2 часов.
amorev Автор
ниже в комментах предложили ngrok, но я бы поднимал через autossh (писал про это тут https://amorev.ru/autossh/) У меня так несколько туннелей крутится на постоянной основе
aruslantsev
Отвечу здесь же, потому что использую похожее решение.
Я использую или systemd unit, который перезапускается при падении туннеля с обычным ssh, не auto, или баш скрипт, который в просто в цикле дергает ssh с нужными параметрами. Но в случае с баш скриптом его надо руками запускать после перезагрузки системы.
amorev Автор
интересный способ. Мне хватило autossh для моих задач)
Taurinz359
Я не силён в ssh, но, беглый гугл говорит что можно использовать mac адрес устройства
xaqbyca
Если некритично время разрыва, можно и в крон команду положить
amorev Автор
крон не подойдет. конфликтовать будет. Выше написать про autossh. У меня годами крутятся туннели так)
xaqbyca
А у меня годами через крон :)
amorev Автор
гениально... Проверяет что порт открыт и, если открыт, то поднимает туннель...
amorev Автор
Спасибо огромное за такой крутой пример! Уже придумал как можно это использовать в других местах, где софт запускать надо только если порт открылся!
xaqbyca
Рад, что Вам понравилось :)))
Ещё бывает какая-нибудь астралинукс, где нет autossh
amorev Автор
это да. тогда крон решения выходят на сцену)