У данного поста есть видео-версия: youtu.be/GBx3KEcuKFA
Я применяю, так или иначе, 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 для работы с БД, мы можем легко воспользоваться встроенным механизмом туннелей (инструкция тут). Но трудности возникать если мы хотим из кода нашего приложения подключиться к этой базе.
Кстати, этот момент актуален не только для базы данных. Иногда нужно просто подключаться к разным сервисам, которые находятся в изолированной сети, куда мы можем иметь доступ через SSH. Так что, этот пример лишь показательный и область применения крайне обширна
Для решения этого вопроса достаточно вбить следующую команду:
ssh -L 5432:postgres-db:5432
Тут ситуация аналогична предыдущей. Только вместо локального 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 туннель, который запускается и дает доступ удаленному серверу к нашему ПК.
Вместо заключения
На этом все. Это была короткая сводка про те туннели, которые я использую в своей работе. Есть еще классная штука - socks proxy, который также можно поднять похожей командой, но про нее, может быть, я напишу отдельный пост в свое время.
Благодарю за внимание!