В какой‑то момент у меня возникла необходимость разобраться с простыми SSH‑туннелями: как их запускать и какие туннели могут помочь мне (обычному web‑разработчику). Разобраться в этом удалось и я решил поделиться пояснениями в простой понятной форме.
Сразу важная мысль: основу для для этого поста в свою голову впитал из этой статьи, за что автору огромное спасибо! Эта статья чуть более хардкорная для понимания, но куда более емкая, чем моя. Мой вариант для тех, кто в этом почти ничего не понимает и хочет разобраться с картинками и деталями.
Если вам более удобен видео‑формат, то можете посмотреть его на YouTube.
С чего возникла необходимость в туннелях?
Я применяю, так или иначе, SSH туннели регулярно в своей работе. Иногда необходимо получить доступ к ресурсу, базе данных или сервису, который находится в закрытой сети, но у меня есть SSH доступ к одному из серверов в этой сети.
Все описанное ниже является исключительно интерпретацией моего опыта и изучения различных статей по теме. Буду очень признателен, если вы найдете ошибку и сообщите мне об этом в комментариях. Спасибо!
Мое первое знакомство с туннелями произошло при моей попытке развернуть разработанный проект на сервере клиента. Я приехал к заказчику в офис, заранее сохранив в заметки ссылки на наши репозитории, названия докер образов и тд.
Но меня ждал сюрприз. Клиент просто дал мне клавиатуру, показал монитор и сказал «на, разворачивай». По политике безопасности клиента я не мог получить удаленный доступ к этому серверу, чтобы удобно заливать все из своего офиса. Поэтому мне предстояло руками разворачивать все на этом сервере, вводя все ссылки и названия по букве.
Я с ужасом представил, как я буду руками вбивать каждую ссылку для репозитория и понял, что на это все уйдет ни один час.
![](https://habrastorage.org/getpro/habr/upload_files/757/b1a/bf9/757b1abf9febcc88ddcd1da26b375f76.png)
Я решил не тратить на это ни минуты и потратил достаточно времени, чтобы подключить этот сервер к нашей openvpn сети, чтобы потом спокойно заходить на этот сервер удаленно. Это было достаточно муторно, но своей цели я добился и релизил потом уже удаленно.
Решив эту задачу я стал изучать другие, более упрощенные способы, решения этой задачи. Так я познакомился с туннелями.
Основная идея туннелей
![Если нельзя напрямую, то почему бы не пойти в обход? Если нельзя напрямую, то почему бы не пойти в обход?](https://habrastorage.org/getpro/habr/upload_files/1a4/b27/7a9/1a4b277a9cc37affe9f8667cead01511.gif)
В целом суть туннеля наглядно изображена на анимации выше. Но дополню текстом:
У вас есть сервер, к которому вы хотите получить доступ, но не можете ходить на него напрямую. Назовем его сервер 1;
Между вами и целевым сервером есть сервер, который имеет доступ к целевому и к которому имеете SSH доступ вы. Назовем его сервер 2;
Вы можете зайти по SSH на сервер 2 и, через консоль, уже отправлять любые запрос на сервер 1. Также вы можете зайти по SSH на сервер 1 и двигаться дальше.
Примерно так и работает SSH‑туннели. Только нам не всегда обязательно заходить на один сервер и с него уже слать запросы на следующий. Для упрощения этого процесса мы можем воспользоваться переадресацией портов (Port Forwarding).
Port forwarding на пальцах
У меня ушло какое-то количество мыслетоплива, чтобы понять суть работы переадресации портов.
![Пишем порт 81, 80 в уме Пишем порт 81, 80 в уме](https://habrastorage.org/getpro/habr/upload_files/ace/e83/61e/acee8361e2841278a393491c0b51d7b2.gif)
Расширим ситуацию, описанную выше:
У нас есть сервер S1, к которому у нас есть доступ на любой порт;
Есть сервер S2, к 80 порту которого хотим получить доступ мы через наш S1;
Мы одной командой можем открыть на S1 80 порт, чтобы он пересылал эти запросы на 80 порт S2 и потом передавал нам ответ;
Также, например, одной командой, мы можем открыть 81 порт, который будет переадресовывать запрос на 80 порт другого сервера S3.
Именно эта «пересылка» портов и называется переадресацией портов или Port Forwarding. Теперь давайте перейдем к реальным кейсам использования туннелей.
Реальные примеры из практики
Кейс с nginx
Рассмотрим простой кейс. У нас есть сервер, который закрыт миру, но мы имеем к нему SSH доступ. На этом сервере запущен сервер nginx, который слушает 80 порт.
![](https://habrastorage.org/getpro/habr/upload_files/907/229/5d7/9072295d7d1c1cbda186ea626cd4f288.gif)
Для того, чтобы получить доступ к тому, что опубликовано на 80 порту сервера, мы можем открыть туннель, который подключится к серверу, и опубликовать на локальном 80 порту. То есть, мы будем стучаться на localhost:80 и видеть ровно то, что опубликовано на 80 порту удаленного сервера. Для этого вводим следующую команду:
ssh -L 80:127.0.0.1:80 remote.server.address
После ввода этой команды мы подключимся на сервер по SSH и, что важно, пока активно это соединение, у нас будет активен этот туннель. Теперь мы можем в браузере открыть localhost:80 и откроется то, что опубликовано на 80 порту удаленного сервера. Подробнее что-есть-что в этой команде:
![](https://habrastorage.org/getpro/habr/upload_files/6e5/f10/0c2/6e5f100c23b3d9c6897fc32ff0dcc581.png)
Первым параметром идет флаг «‑L» который указывает на то, что нам необходимо открыть туннель и начать слушать порт.
Далее указываем интерфейс на котором мы хотим слушать (по‑умолчанию localhost и в рамках данного примера он не указан). В нашем случае указываем порт 80 (на рис. «локальный порт»).
Далее указываем куда надо будет подключаться на удаленном сервере. В нашем примере 127.0.0.1, что значит, что туннель будет пробрасывать на сам же удаленный сервер. На что можно заменять это значение можно прочитать ниже.
Порт на удаленном сервере — в нашем случае это 80. Если бы nginx, например, видел бы на 8080, то мы бы указали 8080 в этом месте.
Адрес удаленного сервера. Указываем его тем же самым образом, что и при обычном подключении к SSH.
Теперь можно переходить на localhost в браузере и видеть то, что на сервере на 80 порту. В моем примере можно увидеть только ошибку nginx, которую выдает nginx‑proxy на сервере.
![](https://habrastorage.org/getpro/habr/upload_files/ca7/64f/b2f/ca764fb2f7a5935dc60c0a11f124f319.png)
Кейс с базой данных Postgres в докере
Теперь к еще одному полезному кейсу, с которым мы сталкиваемся во время разработки, так или иначе, регулярно.
Есть я и мой ноутбук
На ноутбуке есть код моего backend приложения
В основном я работаю с локальной копией базы данных во время разработки
По какой-то причине мне надо подключиться к базе данных, которая находится на сервере (тестовом или боевом) в изолированной сети Docker.
Окольными путями напрямую в базу
Когда мы говорим про работу через IDE для работы с БД, мы можем легко воспользоваться встроенным механизмом туннелей (инструкция по продуктам от JetBrains тут). Но трудности возникают если мы хотим из кода нашего приложения подключиться к этой базе.
Кстати, этот момент актуален не только для базы данных. Иногда нужно просто подключаться к разным сервисам, которые находятся в изолированной сети, куда мы можем иметь доступ через SSH. Так что, этот пример лишь показательный и область применения крайне обширна
Для решения этого вопроса достаточно вбить следующую команду:
ssh -L 5432:postgres-db:5432 remote.server.address
![](https://habrastorage.org/getpro/habr/upload_files/e60/208/b08/e60208b088882eab57bd2a1d785f484d.png)
Тут ситуация аналогична предыдущей. Только вместо локального ip адреса самого сервера мы указываем ip адрес (в данном случае имя хоста postgres-db
) в качестве целевого адреса.
В случае работы с Docker я использую этот контейнер, чтобы знать адреса всех контейнеров на сервере. После запуска имя хоста каждого контейнера можно получить в файле
/etc/hosts
. И после перезапуска ip адрес меняется, а хост остается
Дальше я уже могу указывать localhost:5432
в качестве сервера postgres в моем приложении и подключусь уже напрямую к базе данных в docker контейнере на том сервере.
Таких туннелей можно запускать сколько угодно. Все запускаются по аналогии. Например можно построить такую схему:
![несколько туннелей на через один сервер несколько туннелей на через один сервер](https://habrastorage.org/getpro/habr/upload_files/3c7/753/50d/3c775350df20b042fa5dacef56766c44.gif)
Есть сервера 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
Обратные туннели
Ок, мы разобрались как получить доступ к удаленному серверу, который находится в одной сети с доступным нам сервером. Но иногда бывают ситуацию, обратные этой. Например:
![Когда клиент хочет увидеть "прям ща", а ты в кафе сидишь кодишь... Когда клиент хочет увидеть "прям ща", а ты в кафе сидишь кодишь...](https://habrastorage.org/getpro/habr/upload_files/e05/511/523/e05511523c5926d9e55231450871f86a.gif)
Я работаю на своем ноутбуке и у меня крутится копия сайта клиента на 3000 порту
Нахожусь при этом в кафе с бесплатным wifi
Клиент находится у себя в офисе в закрытой сети
Я хочу показать текущую версию сайта клиента прямо со своего ноутбука
И я и клиент имеем доступ к одному серверу во внешней сети (например мой VPS)
Именно для такого кейса уже есть готовые решения — localtunnel или ngrok. Я сам ими пользуюсь, когда надо быстренько поднять и показать. Но иногда надо подключиться на удаленном сервере, например, к моей локальной базы для повторения какого‑нибудь особого кейса.
Для решения этого вопроса нам как раз может помочь обратный туннель (Reverse port forwarding). Чтобы клиент мог видеть сайт на своем ПК, мне необходимо запустить следующую команду:
ssh -R 0.0.0.0:3001:127.0.0.1:3000 server.ru
![](https://habrastorage.org/getpro/habr/upload_files/2cd/d7e/e6e/2cdd7ee6e858467ba2ba1d73c63f3c53.gif)
Давайте разберемся с каждым параметром в этой команде:
![](https://habrastorage.org/getpro/habr/upload_files/cb3/b9e/333/cb3b9e333fcaf7900262b2506a8959bc.png)
Справа налево:
Адрес сервера, который доступен и мне и клиенту
Локальный адрес и порт, который я хочу сделать доступным (localhost:3000)
Адрес и порт на общем сервере, на котором будет отображаться мой сайт с 3000 порту
Флаг, передающий SSH серверу, что я намерен открыть обратный туннель на мой локальный порт
![Что будет видеть клиент Что будет видеть клиент](https://habrastorage.org/getpro/habr/upload_files/7dd/825/dc9/7dd825dc9de2f239a32db22ed7e71c92.png)
Клиент, после запуска такого туннеля, может видеть наш сайт по адресу server.ru:3001.
Правда сделать это может быть чуть сложнее чем «просто вбить одну команду». Дело в том, что «из коробки» sshd демон не допускает подключения на внешний интерфейс.
Другими словами — то, что мы указали «0.0.0.0» как адрес для прослушивания, говорит серверу следующее: «принимай любые соединения на любой интерфейс на порт 3001, даже если запрос идет извне». Но эта настройка, обычно, выключена. Для того, чтобы внешние соединения заработали необходимо в файле /etc/ssh/sshd_config
добавить строчку GatewayPorts yes
Стоит быть крайне внимательным и аккуратным с этой настройкой. Ставя её в значение
yes
вы открываете порт всему миру и это может стать прекрасной дырой в безопасности для злоумышленников. ВСЕГДА ЗАКРЫВАЙТЕ ТУННЕЛИ после завершения работы через них.
Для чего еще можно использовать обратный туннель
Я использовал обратные туннели, также, при отладке проектов на PHP. В случае, когда код расположен на удаленном сервере и мы работаем с кодом через SFTP, бывает необходимость отладить код, который запущен на сервере, но через локальный XDebug. Тут на помощь также приходит SSH туннель, который запускается и дает доступ удаленному серверу к нашему ПК.
Вместо заключения
На этом все. Это была короткая сводка про те туннели, которые я использую в своей работе. Благодарю за внимание! Буду рад вашей подписки на меня в YouTube и Telegram