Содержание

Наглядное руководство по SSH-туннелям / Хабр

Прим. переводчика: автор статьи рассматривает практические сценарии и примеры организации SSH-туннелей. А для более наглядного объяснения того, как это работает, графически показывает потоки трафика.

Туннели SSH — это зашифрованные TCP-соединения между клиентами и серверами SSH. Трафик входит с одной стороны туннеля и прозрачно выходит с другой. Изначально этот термин относился к туннелям на виртуальных сетевых интерфейсах TUN/TAP, однако сейчас так обычно называют проброс портов SSH.

Например, вот так будет выглядеть схема обратного туннеля, в котором обращение к порту 80 SSH-клиента выполняется через SSH-сервер с определенного IP-адреса (1.2.3.4):

Здесь и далее все схемы взяты из оригинала статьи

Сценарии использования туннелей*:

  • Предоставление зашифрованных каналов для протоколов, передающих данные «открытым текстом».

  • Открытие бэкдоров в частные сети.

  • Обход межсетевых экранов.

*. Примечание

Перечень дополняется. Не стесняйтесь предлагать примеры или исправления
на GitHub.

Проброс портов

Перенаправляет порт из одной системы (локальной или удаленной) в другую.

Проброс локального порта

Проброс локальных портов позволяет перенаправить трафик с SSH-клиента на целевой хост через SSH-сервер. Другими словами, можно получить доступ к удаленным сервисам по зашифрованному соединению так, как будто они локальные. Примеры использования:

  • доступ к удаленному сервису (Redis, Memcached и т. д.) по внутреннему IP-адресу;

  • локальный доступ к ресурсам, размещенным в частной сети;

  • прозрачное проксирование запроса к удаленному сервису.

Проброс удаленного порта

Проброс удаленного порта позволяет направить трафик с SSH-сервера в пункт назначения либо через SSH-клиент, либо через другой удаленный хост. Открывает пользователям в публичных сетях доступ к ресурсам в частных сетях. Примеры использования:

Динамический проброс портов

При динамической переадресации портов на SSH-клиенте поднимается SOCKS-прокси для перенаправления TCP-трафика через SSH-сервер на удаленный хост.

Пересылка с системных (privileged) портов

Для пересылки трафика с системных портов (1–1023) необходимо запустить SSH с правами суперпользователя в системе, на которой открывается порт.

sudo ssh -L 80:example.com:80 ssh-server

Флаги командной строки SSH

Ниже приведены несколько полезных флагов при создании туннелей:

-f # переводит процесс ssh в фоновый режим;
-n # предотвращает чтение из STDIN;
-N # не выполнять удаленные команды. Полезно, если нужно только перенаправить порты;
-T # отменяет переназначение терминала.

Вот пример команды для создания SSH-туннеля в фоновом режиме и проброса локального порта через SSH-сервер:

ssh -fnNT -L 127.0.0.1:8080:example.org:80 ssh-server

Для краткости примеры ниже приводятся без флагов командной строки.

Проброс локального порта

Перенаправление соединений с порта локальной системы на порт удаленного хоста:

ssh -L 127.0.0.1:8080:example.org:80 ssh-server

Перенаправляет локальные подключения к 127.0.0.1:8080 на 80-й порт example.org через SSH-сервер. Трафик между локальной системой и SSH-сервером идет по SSH-туннелю. Трафик между SSH-сервером и example.org — нет. С точки зрения example.org трафик идет от SSH-сервера.

ssh -L 8080:example.org:80 ssh-server
ssh -L *:8080:example.org:80 ssh-server

Команды выше открывают доступ к example.org:80 через SSH-туннель для подключений на порт 8080 на всех интерфейсах локальной системы.

ssh -L 192.168.0.1:5432:127.0.0.1:5432 ssh-server

Переадресует соединения с 192.168.0.1:5432 в локальной системе на 127.0.0.1:5432 на SSH-сервере. Обратите внимание, что здесь 127.0.0.1 — localhost с точки зрения SSH-сервера.

Конфигурации SSH

Убедитесь, что на SSH-сервере включена переадресация TCP (по умолчанию так и должно быть). Проверьте запись в файле /etc/ssh/sshd_config:

AllowTcpForwarding yes

При переадресации портов на интерфейсы, отличные от 127.0.0.1, в локальной системе необходимо включить GatewayPorts/etc/ssh/ssh_config или в командной строке):

GatewayPorts yes
Сценарии использования

Подходит для случаев, когда требуется защищенное соединение для доступа к удаленному сервису, который работает с незашифрованными данными. Например, Redis и Memcached используют протоколы на основе plaintext-данных.

В случае если защищенный доступ к одному из таких сервисов на удаленном сервере организован по общедоступным сетям, можно настроить туннель от локальной системы до этого сервера.

Проброс удаленного порта

Пробрасывает порт на удаленной системе в другую систему.

ssh -R 8080:localhost:80 ssh-server

Перенаправляет трафик с порта 8080 SSH-сервера для всех интерфейсов на порт localhost:80 на локальном компьютере. Если один из интерфейсов смотрит в Интернет, трафик, адресуемый на порт 8080, будет перенаправляться на локальную систему.

ssh -R 1.2.3.4:8080:localhost:80 ssh-server

Переадресует трафик с порта 8080 SSH-сервера на localhost:80 в локальной системе, при этом доступ ко входу в SSH-туннель со стороны сервера разрешен только с IP-адреса 1.2.3.4 (обратите внимание: директиву GatewayPorts необходимо установить в clientspecified).

Прим. переводчика: здесь, возможно, ошибка. Флаг -R — это binding_address, выбор IP адреса на машине, на котором SSH-сервер будет слушать порт. Соответственно вместо одного человечка с IP должны быть человечки, а вместо ssh_server:8080 должно быть 1.2.3.4:8080.

ssh -R 8080:example.org:80 ssh-server

Переадресует трафик с порта 8080 SSH-сервера для всех интерфейсов на localhost:80 в локальной системе. Далее трафик из локальной системы направляется на example.org:80. С точки зрения example.org источником трафика выступает локальная система.

Конфигурация SSH-сервера

SSH-сервер конфигурируется в файле /etc/ssh/sshd_config.

По умолчанию переадресуемые порты недоступны для доступа из Интернета. Для переадресации публичного интернет-трафика на локальный компьютер добавьте в sshd_config на удаленном сервере такую строку:

GatewayPorts yes

Также в sshd_config можно указать, каким клиентам разрешен доступ:

GatewayPorts clientspecified

Динамический проброс портов

Перенаправление трафика с нескольких портов на удаленный сервер.

ssh -D 3000 ssh-server

Команда выше поднимает SOCKS-прокси на порту 3000 для всех интерфейсов в локальной системе. Теперь трафик, отправленный через прокси-сервер на SSH-сервер, можно адресовать на любой порт или конечный хост. По умолчанию используется протокол SOCKS5, поддерживающий TCP и UDP.

ssh -D 127.0.0.1:3000 ssh-server

Поднимает SOCKS-прокси на 127.0.0.1:3000 в локальной системе.

При работающем SOCKS-прокси можно настроить браузер на его использование для доступа к ресурсам так, как будто соединения исходят от SSH-сервера. Например, если у SSH-сервера есть доступ к другим серверам в частной сети, с помощью SOCKS-прокси можно заходить на эти серверы локально (словно вы находитесь в той же сети), и не нужно настраивать VPN.

Работу SOCKS-прокси можно проверить командой:

curl -x socks5://127.0.0.1:12345 https://example.org

Конфигурация SSH-клиента

SSH-клиент настраивается в файле /etc/ssh/ssh_config.

Включите GatewayPorts в системе, чтобы открыть доступ другим интерфейсам к SOCKS-прокси:

GatewayPorts yes

Поскольку GatewayPorts конфигурируется на SSH-клиенте, вместо ssh_config для настройки можно использовать параметры командной строки:

ssh -o GatewayPorts=yes -D 3000 ssh-server

Jump-хосты и команды прокси-сервера

Прозрачное подключение к удаленному хосту через промежуточные.

ssh -J user1@jump-host user2@remote-host
ssh -o "ProxyJump user1@jump-host" user2@remote-host

Устанавливает SSH-соединение с jump-хостом и переадресуют трафик на удаленный хост. 

Подключается к удаленному хосту через промежуточный jump-хост. Приведенная выше команда должна сработать сразу, если у jump-хоста уже есть SSH-доступ к удаленному хосту. Если нет, можно использовать agent forwarding для проброса SSH-ключа с локального компьютера на удаленный хост.

ssh -J jump-host1,jump-host2 ssh-server

Можно указать несколько jump-хостов через запятую.

ssh -o ProxyCommand="nc -X 5 -x localhost:3000 %h %p" user@remote-host

Подключение к удаленному серверу через SOCKS5 с использованием netcat. С точки зрения сервера, IP-адрес отправителя принадлежит прокси-хосту. При этом для SSH-соединения используется сквозное шифрование, так что прокси-хост видит только зашифрованный трафик между локальной системой и удаленным хостом.

Конфигурация SSH-клиента

SSH-клиент конфигурируется в файле /etc/ssh/ssh_config.

Для подключения agent forwarding можно добавить локальный SSH-ключ к локальному SSH-агенту с помощью ssh-add:

ssh-add

Надежные SSH-туннели

Перечисленные выше команды работают на разовой основе: вы сразу получаете конкретный результат, и на этом их работа заканчивается. Поэтому, чтобы обеспечить защиту SSH-туннелей от сбоев в сети или ненадежных соединений, придется выполнить дополнительную настройку.

По умолчанию TCP-соединение, используемое для создания SSH-туннеля, может разрываться после некоторого периода бездействия. Чтобы это предотвратить, нужно настроить сервер (

/etc/ssh/sshd_config) на отправку heartbeat-сообщений:

ClientAliveInterval 15
ClientAliveCountMax 4

На отсылку таких сообщений также можно настроить клиент (/etc/ssh/ssh_config):

ServerAliveInterval 15
ServerAliveCountMax 4

Использование AutoSSH

Указанные выше настройки могут предотвратить разрывы из-за неактивности, но они не в состоянии восстановить прерванные соединения. Для автоматического перезапуска SSH-туннеля можно воспользоваться утилитой autossh — она создает SSH-туннель и отслеживает его работоспособность.

AutoSSH принимает те же аргументы для настройки переадресации портов, что и SSH:

autossh -R 2222:localhost:22 ssh-server

Эта команда создает туннель, способный восстанавливаться после сетевых сбоев. По умолчанию AutoSSH открывает дополнительные порты на SSH-клиенте/сервере для проверки работоспособности (healthcheck). Если трафик не проходит между healthcheck-портами, AutoSSH перезапускает туннель.

autossh -R 2222:localhost:22 \
  -M 0 \
  -o "ServerAliveInterval 10" -o "ServerAliveCountMax 3" \
  remote-host

Флаг -M 0 отключает healthcheck-порты (за проверку работоспособности в этом случае отвечает SSH-клиент). В примере выше сервер должен посылать сигналы каждые 10 секунд. Если не проходят три подряд сигнала, SSH-клиент завершает работу, а AutoSSH поднимает новый туннель.

Дополнительные примеры и сценарии использования

Прозрачный доступ к удаленному ресурсу в частной сети.

Предположим, в частной сети есть Git-репозиторий, доступ к которому возможен только через выделенный сервер в этой сети. Сервер не подключен к Интернету. Есть прямой доступ к серверу, но нет VPN-доступа к частной сети.

Требуется открыть доступ к Git-репозиторию, как если бы подключение к нему шло напрямую из локальной системы. Если есть SSH-доступ к другому серверу, который доступен как с локального компьютера, так и с выделенного сервера, можно создать SSH-туннель и воспользоваться директивами ProxyCommand.

ssh -L 127.0.0.1:22:127.0.0.1:2222 intermediate-host

Команда выше пробрасывает порт 2222 на промежуточном хосте на порт 22 выделенного сервера. Теперь можно подключиться к SSH-серверу на выделенном сервере, несмотря на то, что он недоступен из Интернета.

Прим. переводчика: возможно, что здесь тоже ошибка: перепутаны местами 22 и 2222 порты в команде.

ssh -p 2222 user@localhost

Для большего удобства можно добавить несколько директив в локальный ~/.

ssh/config:

Host git.private.network
  HostName git.private.network
  ForwardAgent yes
  ProxyCommand ssh private nc %h %p
Host private
  HostName localhost
  Port 2222
  User private-user
  ForwardAgent yes
  ProxyCommand ssh tunnel@intermediate-host nc %h %p

В результате пользователь получает доступ к локальному Git-репозиторию, как если бы находился в частной сети.

P.S.

Читайте также в нашем блоге:

  •  «Автоматизация SSH-доступа к нодам Kubernetes с помощью Fabric и интеграции от CoreOS»;

  • «Немного хардкора: как поднять Kubernetes на двух старых ноутбуках с Gentoo»;

  • «Как с tcpserver и netcat открыть туннель в Kubernetes pod или контейнер».

SSH-туннели: практические примеры и основные функции

SSH-туннели нужны для установки защищенных каналов между локальной машиной и удалённым сервером. Через них можно, например, передавать и редактировать файлы, запускать приложения, сохранять резервные копии.

Как устроен туннель

SSH — это Secure Shell, защищённый сетевой протокол для удалённого управления. Он шифрует трафик и работает со всеми популярными операционными системами. 

Для установки безопасного подключения к удалённой машине используется SSH-туннелирование. Однако под туннелем здесь подразумевается не инкапсуляция одного протокола в другом. Речь идёт о том, что администратор выполняет через SSH проброс портов. Технология подразумевает передачу TCP-пакетов и трансляцию IP-заголовков при передаче информации, если соблюдаются правила.

При сравнении SSH и VPN важно понимать разницу. После настройки VPN информацию можно передавать в любом направлении. Secure Shell же — это проброс портов поверх протокола. У такого канала связи есть одна точка входа и одна точка выхода.

Как создать туннель

Для идентификации пользователя нужны два ключа — приватный и публичный (открытый). Открытый хранится на сервере, приватный — на локальной машине. Создание безопасного подключения отличается в зависимости от операционной системы.

Ubuntu и другие дистрибутивы Linux/macOS

Запустите терминал и выполните команду: 

ssh-keygen -t rsa

В терминале появится диалог:

Enter file in which to save the key (/home/user/.ssh/id_rsa):

По умолчанию приватная часть сохраняется в папку .ssh в файл id_rsa. Вы можете указать другие адрес и имя файла.

Затем система предложит создать пароль для дополнительной защиты ключа. Это нужно, чтобы его не могли использовать все, кто имеет доступ к локальной машине. Полезная настройка безопасности, если одним компьютером пользуются несколько человек. Если пароль не нужен, оставьте поле пустым и нажмите Enter.

Enter passphrase (empty for no passphrase):

После успешного создания пары ключей в терминале появится такая запись:

Your identification has been saved in /home/user/. ssh/id_rsa.
Your public key has been saved in /home/user/.ssh/id_rsa.pub.
The key fingerprint is:
476:b2:a8:7f:08:b4:c0:af:81:25:7e:21:48:01:0e:98 user@localhost

Теперь нужно скопировать публичный ключ и добавить его на удалённую машину. Можно открыть файл /home/user/.ssh/id_rsa.pub через любой текстовый редактор или вывести его содержимое в терминале:

cat ~/.ssh/id_rsa.pub

Скопируйте ключ и добавьте его на сервер. Убедитесь, что в нём нет пробелов и переносов.

Можно скопировать открытый ключ автоматически, используя команду:

ssh-copy-id user@remoteserver

Вместо user вы указываете имя пользователя, вместо remoteserver — хост или IP-адрес удалённой машины на cloud.timeweb.com или другой платформе.

Создание безопасного подключения завершено. Чтобы подключиться через SSH-port, выполните:

ssh root@HOST

Вместо параметра HOST укажите публичный IP-адрес сервера, на который вы добавили открытый ключ. Если вы не создавали пароль для дополнительной защиты закрытой части, то вводить ничего больше не нужно. Система сверит открытый и закрытый ключи и при обнаружении соответствия установит защищенное соединение. 

При первом подключении система предупредит о неизвестном хосте и уточнит, доверяете ли вы ему. Введите ‘yes’ и нажмите Enter.

Чтобы сделать подключение безопаснее, не стоит использовать аккаунт с правами суперпользователя. Кроме того, можно ограничить связь между консолями локальной машины и удалённого сервера, указав параметр -N. Пример команды:

ssh -N -L -g 3389:192.168.0.105:3389 [email protected]

Это исключит ситуацию, при которой пользователь случайно выполняет на удалённой машине команды, предназначенные для локального компьютера.

Windows

На Windows создать пару ключей можно двумя способами — через консоль PowerShell и с помощью программы PuTTygen (устанавливается вместе с PuTTy).

Чтобы сгенерировать ключи через PowerShell, откройте терминал и выполните:

ssh-keygen -t rsa

Защитите приватный ключ паролем или оставьте поле пустым и нажмите Enter.

По умолчанию открытый ключ сохраняется в файле ~/.ssh/id_rsa.pub. Приватный ключ с именем id_rsa лежит в той же папке. Откройте файл с публичным ключом через текстовый редактор или воспользуйтесь командой cat в PowerShell:

cat ~/.ssh/id_rsa.pub

Скопируйте публичный ключ и добавьте его на удаленный сервер. 

Чтобы подключиться к серверу, выполните:

ssh root@HOST

Вместо параметра HOST укажите публичный IP-адрес удаленного сервера, на который вы добавили открытый ключ. Если вы не создавали пароль для дополнительной защиты закрытого ключа, то вводить ничего больше не нужно. Система сверит открытый и закрытый ключи и при обнаружении соответствия установит SSH-туннель в Windows.

 

При первом подключении ОС предупредит о неизвестном хосте и уточнит, доверяете ли вы ему. Введите ‘yes’ и нажмите Enter.

Чтобы сгенерировать пароль с помощью PuTTygen:

  1. Нажмите кнопку Generate.
  2. Подвигайте курсором в любом направлении до тех пор, пока строка прогресса не заполнится.
  3. Сохраните публичный и приватный ключи.
  4. Откройте файл с публичным ключом. 
  5. Скопируйте публичный ключ и добавьте его на удаленный сервер.

Для подключения к удалённому серверу также можно использовать программу PuTTy. Запустите программу, в поле Host Name введите имя или IP-адрес хоста и нажмите Open.

Настроить в PuTTy SSH tunnel можно во вкладке Connection — SSH — Tunnels. Здесь указывается Source Port, Destination, а также параметры динамического подключения. Что это за настройки и как они влияют на соединение, рассмотрим далее.

Как использовать SSH Proxy

SSH-tunnel Proxy помогает организовать доступ к домашней или корпоративной системе. Важно только, чтобы приложения, которые используются для работы при подключении, поддерживали SOCKS-прокси. Один из вариантов такого использования туннелей — сотрудник подключается к рабочей сети, используя публичную сеть в другом конце мира, при этом вся информация шифруется.

Для туннелирования через прокси достаточно выполнить:

ssh -D 8888 user@remoteserver

Этой командой вы запускаете SOCKS-прокси. Порт — 8888. В этом случае служба работает на localhost. Но можно изменить команду, чтобы прослушивать Ethernet и Wi-Fi. Это позволит приложениям подключаться к прокси-серверу через Secure Shell.

Например, можно запустить браузер Google Chrome:

google-chrome --proxy-server="socks5://192.168.1.10:8888"

Команда создаёт SOCKS-прокси, а также инициирует туннелирование DNS-запросов

Что такое динамический SSH-tunnel

Динамический SSH-tunnel открывает локальный сокет TCP и использует его как SOCKS4/SOCKS5-прокси. Часто применяется, когда нужен VPN, но развернуть его нельзя. Такой туннель отвечает всем требованиям безопасности.

Динамический туннель также подходит для однократного выхода в интернет:

ssh -D 1080 [email protected]

SOCKS-прокси работает через порт 1080. Динамический туннель не предусматривает открытие портов во внешнюю сеть. Весь трафик проходит по нему, скрывая деятельность пользователя так же, как при VPN.

Для Windows динамический SSH-туннель в PuTTy настраивается на вкладке Connection — SSH — Tunnels.

Примеры использования туннелей

Рассмотрим несколько примеров использования туннелей.

Проброс портов — SSH Port Forwarding

SSH Forwarding — одна из самых распространенных операций, для которой требуется создать туннель. Вы открываете порт на локальной машине и выбираете порт на другом конце — удаленном сервере:

ssh  -L 9999:127.0.0.1:80 user@remoteserver

В этой команде вы говорите, что нужно слушать порт 9999. Для проброса используется порт 80.

Обратный туннель

Туннель может работать и в обратном направлении. Для этого достаточно подключить слушающий порт к другому порту на локальной машине:

ssh -v -R 0.0.0.0:1999:127.0.0.1:902 192.168.1.100 user@remoteserver

В этом туннеле соединение идёт от удалённого сервера к порту 1999, затем к порту 902 на локальной машине.

Удалённое выполнение команд

Через Secure Shell можно создать интерфейс для выполнения команд на удалённой машине. Сами команды прописываются в качестве последнего аргумента. Пример:

ssh remoteserver "cat /var/log/nginx/access.log" | grep badstuff.php

После скачивания лога на удалённом сервере выполнится команда grep.

Rsync через SSH

Бэкапы важных данных можно делать с помощью bzip2. Порядок простой — сначала вы сжимаете каталог на удалённом сервере, а затем распаковываете на другой стороне: на локальной машине или на другом сервере, заведённом под хранение резервных копий. Пример команды:

tar -cvj /datafolder | ssh remoteserver "tar -xj -C /datafolder"

Если бэкапы нужно делать регулярно (обычно так и происходит), то проще использовать команду rsync:

rsync -az /home/testuser/data proglibserver:backup/

Rsync не копирует файлы с нуля, а сравнивает отличия в разных временных точках. Это экономит время на создание бэкапов, а также помогает найти данные для восстановления при утере или повреждении.

Запуск приложений

Через SSH-туннель можно запускать GUI-приложения на удалённом сервере:

ssh -X remoteserver vmware

Несмотря на то, что приложение выполняется на удалённом сервере, его интерфейс доступен на локальной машине. Это удобно если, например, нужно больше ресурсов для работы, а локальная машина уже не справляется. 

Прыжки по хостам

Если сеть сегментирована, то при туннелировании придётся пройти через несколько хостов. При этой рабочий сеанс должен быть полностью зашифрованным. Добиться такого результата можно с помощью параметра -J. Для установления связи с каждым следующим хостом будет использоваться переадресация:

ssh -J host1,host2,host3 [email protected]

Локальная папка на удаленной машине

С помощью sshfs можно примонтировать локальный каталог к удалённому серверу.

sshfs user@proglibserver:/media/data ~/data/

Это удобное решение для обмена файлами и выполнения других операций.

Заключение

В этой статье мы узнали, что такое SSH-туннель, как им пользоваться, как его настроить на Linux, macOS и Windows, какие у него есть особенности и параметры конфигурации. Несколько распространенных сценариев также описаны в примерах использования. 

Запоминать все команды и аргументы не нужно. Основные операции вы быстро начнёте делать на автомате, а более сложные случаи всегда можно посмотреть в справке или нашем руководстве на cloud.timeweb.com

Переадресация локальных и удаленных портов

  • Основы компьютерных сетей для разработчиков
  • Иллюстрированное введение в Linux iptables
  • Наглядное руководство по туннелям SSH: переадресация локальных и удаленных портов

  • Мост и коммутатор: что я узнал из данных Center Tour
  • Лабораторная работа в сети: Широковещательные домены Ethernet
  • Лаборатория в области сети: Сопоставление сегментов L3 и L2
  • Лаборатория в области сети: Simple VLAN

Не пропустите новые сообщения в этой серии! Подпишитесь на обновления блога и получайте подробные технические статьи по темам Cloud Native прямо в свой почтовый ящик.

TL;DR Переадресация портов SSH в виде шпаргалки для печати .

SSH — еще один пример древней технологии, которая до сих пор широко используется. Вполне может быть, что изучение пары трюков с SSH в долгосрочной перспективе окажется более выгодным, чем освоение дюжины инструментов Cloud Native, которым суждено стать устаревшими в следующем квартале.

Одна из моих любимых частей этой технологии — туннели SSH. Используя только стандартные инструменты и часто используя всего одну команду, вы можете добиться следующего:

  • Доступ к внутренним конечным точкам VPC через общедоступный экземпляр EC2.
  • Откройте порт с локального хоста виртуальной машины разработки в браузере хоста.
  • Предоставить любой локальный сервер из домашней/частной сети внешнему миру.

И еще 😍

Но, несмотря на то, что я ежедневно использую SSH-туннели, мне всегда требуется время, чтобы определить правильную команду. Должен ли это быть локальный или удаленный туннель? Что такое флаги? Это local_port:remote_port или наоборот? Итак, я решил, наконец, разобраться в этом, и в результате получился ряд лабораторных работ и наглядная шпаргалка 🙈

Полная версия 1,5 МБ

Предварительные требования

Туннели SSH предназначены для подключения хостов по сети, поэтому каждая лабораторная работа ниже ожидаемо включает несколько «машин». Однако мне лень раскручивать полноценные инстансы, особенно когда вместо них можно использовать контейнеры. Вот почему в итоге я использовал только одну бродячую виртуальную машину с Docker.

Теоретически подойдет любой Linux-компьютер с Docker Engine. Однако запуск приведенных ниже примеров как есть с Docker Desktop будет невозможен, поскольку предполагается возможность доступа к контейнерам компьютеров по их IP-адресам.

В качестве альтернативы лабораторные работы можно выполнить с помощью Lima (QEMU + nerdctl + containerd + BuildKit), но не забудьте сначала выполнить limactl shell bash .

В каждом примере требуется действующая пара ключей без парольной фразы на хосте, которая затем монтируется в контейнеры для упрощения управления доступом. Если у вас его нет, создать его так же просто, как всего ssh-keygen на хосте.

Важно: демоны SSH в контейнерах здесь предназначены исключительно для образовательных целей — контейнеры в этом посте предназначены для представления полноценных «машин» с SSH-клиентами и серверами на них. Имейте в виду, что использование SSH в реальных контейнерах редко бывает хорошей идеей!

Переадресация локального порта

Начиная с того, который я использую чаще всего. Часто может быть служба, прослушивающая локальный хост или частный интерфейс машины, к которой я могу подключиться только по SSH через ее общедоступный IP-адрес. И мне очень нужен доступ к этому порту извне. Несколько типичных примеров:

  • Доступ к базе данных (MySQL, Postgres, Redis и т. д.) с помощью модного инструмента пользовательского интерфейса с вашего ноутбука.
  • Использование браузера для доступа к веб-приложению, доступному только в частной сети.
  • Доступ к порту контейнера с вашего ноутбука без публикации его на общедоступном интерфейсе сервера.

Все вышеперечисленные варианты использования могут быть решены с помощью одной команды

ssh :

 ssh -L [local_addr:]local_port:remote_addr:remote_port [user@]sshd_addr
 

Флаг -L указывает, что мы начинаем переадресацию локального порта . На самом деле это означает следующее:

  • На вашем компьютере клиент SSH начнет прослушивать local_port (вероятно, localhost , но зависит от — проверьте настройку GatewayPorts ).
  • Любой трафик на этот порт будет перенаправлен на remote_private_addr:remote_port на машине, к которой вы подключились по SSH.

Вот как это выглядит на диаграмме:

Лабораторная работа 1: Использование SSH-туннелей для переадресации локальных портов 👨‍🔬

Лабораторная работа воспроизводит настройку из диаграммы выше. Во-первых, нам нужно подготовить сервер — машину с демоном SSH и простой веб-службой, прослушивающей 127.0.0.1:80 :

.
 $ docker buildx build -t сервер: последний -<<'EOD'
# синтаксис=докер/dockerfile:1
ИЗ альпийских:3
# Установите зависимости:
ЗАПУСК apk добавить --no-cache openssh-server curl python3
ЗАПУСК mkdir /root/.ssh && chmod 0700 /root/.ssh && ssh-keygen -A
# Подготовьте точку входа для запуска демонов:
КОПИРОВАТЬ --chmod=755 <<'EOF' /entrypoint.sh
#!/бин/ш
установить -euo pipefail
для файла в /tmp/ssh/*.pub; делать
  кошка ${файл} >> /root/.ssh/authorized_keys
сделанный
chmod 600 /root/.ssh/authorized_keys
# Минимальная конфигурация для SSH-сервера:
sed -i '/AllowTcpForwarding/d' /etc/ssh/sshd_config
sed -i '/PermitOpen/d' /etc/ssh/sshd_config
/usr/sbin/sshd -e -D &
python3 -m http.server --bind 127.0.0.1 ${PORT} &
сон бесконечность
EOF
# Запустить его:
CMD ["/entrypoint.sh"]
ОВП
 

Запуск сервера и запись его IP-адреса:

 $ docker run -d --rm \
   -e ПОРТ=80 \
   -v $HOME/. ssh:/tmp/ssh \
   --имя сервера \
   сервер: последний
СЕРВЕР_IP=$(
  докер проверяет \
    -f '{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' \
  сервер
)
 

Поскольку веб-служба прослушивает локальный хост, она не будет доступна извне (т. е. из хост-системы в данном конкретном случае):

 $ curl ${SERVER_IP}
curl: (7) Не удалось подключиться к порту 172.17.0.2 80: соединение отклонено
 

Но изнутри «сервера» работает просто отлично:

 $ ssh -o StrictHostKeyChecking=нет root@${SERVER_IP}
7b3e49181769: $# завиток на локальном хосте


<голова>
...
 

И вот в чем хитрость: связать localhost:80 сервера с localhost:8080 хоста, используя переадресацию локального порта:

 $ ssh -o StrictHostKeyChecking=no -f -N -L 8080:localhost:80 root@${SERVER_IP}
 

Теперь вы сможете получить доступ к веб-службе через локальный порт хост-системы:

 $ завиток локальный хост: 8080
 01//EN" "http://www.w3.org/TR/html4/strict.dtd">

<голова>
...
 

Немного более подробный (но более явный и гибкий) способ достижения той же цели — использование формы local_addr:local_port:remote_addr:remote_port :

 $ ssh -o StrictHostKeyChecking=no -f -N -L \
  локальный: 8080: локальный: 80 \
  root@${SERVER_IP}
 

Переадресация локального порта с хостом-бастионом

Сначала это может показаться неочевидным, но команда ssh -L позволяет перенаправить локальный порт на удаленный порт на любой машине , а не только на самом SSH-сервере. Обратите внимание, что remote_addr и sshd_addr могут иметь или не иметь одинаковое значение:

 ssh -L [local_addr:]local_port:remote_addr:remote_port [user@]sshd_addr
 

Не уверен, насколько правомерно использование термина хост-бастион здесь, но я себе представляю этот сценарий так:

Я часто использую описанный выше прием для вызова конечных точек, доступных с хоста-бастиона , но не с моего ноутбука (например, используя экземпляр EC2 с частный и общедоступный интерфейсы для подключения к кластеру OpenSearch, полностью развернутому в облаке VPC).

Лабораторная работа 2: Переадресация локального порта с хостом-бастионом 👨‍🔬

Опять же, лабораторная работа воспроизводит настройку из схемы выше. Во-первых, нам нужно подготовить хост-бастион — машину, на которой находится только демон SSH:

 $ docker buildx build -t bastion:latest -<<'EOD'
# синтаксис=докер/dockerfile:1
ИЗ альпийских:3
# Установите зависимости:
ЗАПУСТИТЬ apk добавить --no-cache openssh-server
ЗАПУСК mkdir /root/.ssh && chmod 0700 /root/.ssh && ssh-keygen -A
# Подготовьте точку входа, которая запускает демон SSH:
КОПИРОВАТЬ --chmod=755 <<'EOF' /entrypoint.sh
#!/бин/ш
установить -euo pipefail
для файла в /tmp/ssh/*.pub; делать
  кошка ${файл} >> /root/.ssh/authorized_keys
сделанный
chmod 600 /root/.ssh/authorized_keys
# Минимальная конфигурация для SSH-сервера:
sed -i '/AllowTcpForwarding/d' /etc/ssh/sshd_config
sed -i '/PermitOpen/d' /etc/ssh/sshd_config
/usr/sbin/sshd -e -D &
сон бесконечность
EOF
# Запустить его:
CMD ["/entrypoint.sh"]
ОВП
 

Запуск хоста-бастиона и запись его IP:

 $ docker run -d --rm \
    -v $HOME/. ssh:/tmp/ssh \
    --название бастиона \
    бастион: последний
BASTION_IP=$(
  докер проверяет \
    -f '{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' \
  бастион
)
 

Теперь запускаем целевой веб-сервис на отдельной «машине»:

 $ docker run -d --rm \
    --имя сервера \
    питон: 3-альпийский \
    python3 -m http.сервер 80
СЕРВЕР_IP=$(
  докер проверяет \
    -f '{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' \
  сервер
)
 

Предположим, что вызов curl ${SERVER_IP} напрямую с хоста по какой-то причине невозможен (например, как если бы от хоста к этому IP-адресу не было маршрута). Итак, нам нужно запустить проброс портов:

 $ ssh -o StrictHostKeyChecking=no -f -N -L 8080:${SERVER_IP}:80 root@${BASTION_IP}
 

Обратите внимание, что переменные SERVER_IP и BASTION_IP имеют разные значения в приведенной выше команде.

Проверка работоспособности:

 $ завиток локальный хост: 8080
 01//EN" "http://www.w3.org/TR/html4/strict.dtd">

<голова>
...
 

Перенаправление удаленных портов

Другой популярный (но скорее обратный) сценарий — это когда вы хотите на мгновение открыть локальную службу внешнему миру. Конечно, для этого вам понадобится общедоступный сервер входного шлюза . Но не бойтесь! В качестве такого шлюза можно использовать любой общедоступный сервер с демоном SSH:

 ssh -R [удаленный_адрес:]удаленный_порт:локальный_адрес:локальный_порт [пользователь@]адрес_шлюза
 

Приведенная выше команда выглядит не сложнее, чем ее аналог ssh -L . Но есть подводный камень…

По умолчанию указанный SSH-туннель позволяет использовать в качестве удаленного адреса только локальный хост шлюза. Другими словами, ваш локальный порт станет доступным только изнутри самого сервера шлюза, и, скорее всего, это вам не нужно. Например, я обычно хочу использовать общедоступный адрес шлюза в качестве удаленного адреса, чтобы предоставить доступ к моим локальным службам общедоступному Интернету. Для этого сервер SSH должен быть настроен с GatewayPorts да настройка.

Вот для чего можно использовать переадресацию удаленных портов:

  • Предоставление службы разработки с вашего ноутбука в общедоступный Интернет для демонстрации.
  • Хмм… Я могу привести несколько эзотерических примеров, но сомневаюсь, что стоит ими здесь делиться. Любопытно услышать, для чего другие люди могут использовать перенаправление удаленных портов!

Вот как переадресация удаленных портов выглядит на схеме:

Лабораторная работа 3: Использование SSH-туннелей для переадресации удаленных портов 👨‍🔬

Лаборатория воспроизводит схему, показанную выше. Во-первых, нам нужно подготовить «dev-машину» — компьютер с SSH-клиентом и локальным веб-сервером:

 $ docker buildx build -t devel:latest -<<'EOD'
# синтаксис=докер/dockerfile:1
ИЗ альпийских:3
# Установить зависимости:
ЗАПУСК apk добавить --no-cache openssh-client curl python3
ЗАПУСК mkdir /root/. ssh && chmod 0700 /root/.ssh
# Подготовьте точку входа, которая запускает веб-сервис:
КОПИРОВАТЬ --chmod=755 <<'EOF' /entrypoint.sh
#!/бин/ш
установить -euo pipefail
cp /tmp/ssh/* /root/.ssh
chmod 600 /корень/.ssh/*
python3 -m http.server --bind 127.0.0.1 ${PORT} &
сон бесконечность
EOF
# Запустить его:
CMD ["/entrypoint.sh"]
ОВП
 

Запуск машины разработчика:

 $ docker run -d --rm \
    -e ПОРТ=80 \
    -v $HOME/.ssh:/tmp/ssh \
    --имя разработки \
    разработка: последний
 

Подготовка сервера входного шлюза — простой SSH-сервер с GatewayPorts , установленным на да в sshd_config :

 $ docker buildx build -t gateway: последний -<<'EOD'
# синтаксис=докер/dockerfile:1
ИЗ альпийских:3
# Установите зависимости:
ЗАПУСТИТЬ apk добавить --no-cache openssh-server
ЗАПУСК mkdir /root/.ssh && chmod 0700 /root/.ssh && ssh-keygen -A
# Подготовьте точку входа, которая запускает SSH-сервер:
КОПИРОВАТЬ --chmod=755 <<'EOF' /entrypoint. sh
#!/бин/ш
установить -euo pipefail
для файла в /tmp/ssh/*.pub; делать
  кошка ${файл} >> /root/.ssh/authorized_keys
сделанный
chmod 600 /root/.ssh/authorized_keys
sed -i '/AllowTcpForwarding/d' /etc/ssh/sshd_config
sed -i '/PermitOpen/d' /etc/ssh/sshd_config
sed -i '/GatewayPorts/d' /etc/ssh/sshd_config
echo 'Шлюзовые порты да' >> /etc/ssh/sshd_config
/usr/sbin/sshd -e -D &
сон бесконечность
EOF
# Запустить его:
CMD ["/entrypoint.sh"]
ОВП
 

Запуск сервера шлюза и запись его IP-адреса:

 $ docker run -d --rm \
    -v $HOME/.ssh:/tmp/ssh \
    --имя шлюза \
    шлюз: последний
ШЛЮЗ_IP=$(
  докер проверяет \
    -f '{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' \
  шлюз
)
 

Теперь изнутри машины разработчика запустите переадресацию удаленного порта :

 $ docker exec -it -e GATEWAY_IP=${GATEWAY_IP} devel sh
/ $# ssh -o StrictHostKeyChecking=no -f -N -R 0.0.0.0:8080:localhost:80 root@${GATEWAY_IP}
/ $# exit # или отсоединить с помощью ctrl-p, ctrl-q
 

И убедитесь, что локальный порт компьютера-разработчика стал доступен на общедоступном интерфейсе шлюза (из хост-системы):

 $ curl ${GATEWAY_IP}:8080
 01//EN" "http://www.w3.org/TR/html4/strict.dtd">

<голова>
...
 

Переадресация удаленного порта из домашней/частной сети

Подобно переадресации локального порта, переадресация удаленного порта имеет свой собственный режим узла-бастиона . Но на этот раз машина с SSH-клиентом (например, ваш ноутбук для разработчиков) играет роль бастиона. В частности, он позволяет открывать порты из домашней (или частной) сети, к которой ваш ноутбук имеет доступ во внешний мир через входной шлюз:

 ssh -R [удаленный_адрес:]удаленный_порт:локальный_адрес:локальный_порт [пользователь@]адрес_шлюза
 

Выглядит почти так же, как простой удаленный туннель SSH, но пара local_addr:local_port становится адресом устройства в домашней сети. Вот как это можно изобразить на диаграмме:

Поскольку я обычно использую свой ноутбук в качестве тонкого клиента, а фактическая разработка происходит на домашнем сервере, я полагаюсь на такую ​​удаленную переадресацию портов, когда мне нужно открыть службу разработки из домашний сервер в общедоступный Интернет, и единственная машина с доступом к шлюзу — это мой тонкий ноутбук.

Лабораторная работа 4: Удаленное перенаправление портов из домашней/частной сети 👨‍🔬

Как всегда, в лабораторной работе воспроизводятся настройки из диаграммы выше. Во-первых, нам нужно подготовить «тонкую машину разработки»:

.
 $ docker buildx build -t devel:latest -<<'EOD'
# синтаксис=докер/dockerfile:1
ИЗ альпийских:3
# Установите зависимости:
ЗАПУСК apk добавить --no-cache openssh-client
ЗАПУСК mkdir /root/.ssh && chmod 0700 /root/.ssh
# На этот раз мы ничего не запускаем (сначала):
КОПИРОВАТЬ --chmod=755 <<'EOF' /entrypoint.sh
#!/бин/ш
установить -euo pipefail
cp /tmp/ssh/* /root/.ssh
chmod 600 /корень/.ssh/*
сон бесконечность
EOF
# Запустить его:
CMD ["/entrypoint.sh"]
ОВП
 

Запуск "dev-машины":

 $ docker run -d --rm \
    -v $HOME/.ssh:/tmp/ssh \
    --имя разработки \
    разработка: последний
 

Запуск частного сервера разработки с использованием отдельной «машины» и указанием ее IP-адреса:

 $ docker run -d --rm \
    --имя сервера \
    питон: 3-альпийский \
    python3 -m http. сервер 80
СЕРВЕР_IP=$(
  docker inspect -f '{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' \
  сервер
)
 

Подготовка сервера входного шлюза:

 $ docker buildx build -t gateway: последний -<<'EOD'
# синтаксис=докер/dockerfile:1
ИЗ альпийских:3
# Установите зависимости:
ЗАПУСТИТЬ apk добавить --no-cache openssh-server
ЗАПУСК mkdir /root/.ssh && chmod 0700 /root/.ssh && ssh-keygen -A
# Подготовьте точку входа, которая запускает демон SSH:
КОПИРОВАТЬ --chmod=755 <<'EOF' /entrypoint.sh
#!/бин/ш
установить -euo pipefail
для файла в /tmp/ssh/*.pub; делать
  кошка ${файл} >> /root/.ssh/authorized_keys
сделанный
chmod 600 /root/.ssh/authorized_keys
sed -i '/AllowTcpForwarding/d' /etc/ssh/sshd_config
sed -i '/PermitOpen/d' /etc/ssh/sshd_config
sed -i '/GatewayPorts/d' /etc/ssh/sshd_config
echo 'Шлюзовые порты да' >> /etc/ssh/sshd_config
/usr/sbin/sshd -e -D &
сон бесконечность
EOF
# Запустить его:
CMD ["/entrypoint.sh"]
ОВП
 

И запуск:

 $ docker run -d --rm \
    -v $HOME/. ssh:/tmp/ssh \
    --имя шлюза \
    шлюз: последний
ШЛЮЗ_IP=$(
  докер проверяет \
    -f '{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' \
  шлюз
)
 

Теперь, изнутри "dev-машины", запустите удаленную переадресацию портов SERVER-GATEWAY :

 $ docker exec -it -e GATEWAY_IP=${GATEWAY_IP} -e SERVER_IP=${SERVER_IP} devel sh
/ $# ssh -o StrictHostKeyChecking=no -f -N -R 0.0.0.0:8080:${SERVER_IP}:80 root@${GATEWAY_IP}
/ $# exit # или отсоединить с помощью ctrl-p, ctrl-q
 

Наконец, убедитесь, что сервер разработки стал доступен через общедоступный интерфейс шлюза (из хост-системы):

 $ curl ${GATEWAY_IP}:8080


<голова>
...
 

Подводя итоги

После выполнения всех этих лабораторных работ и рисунков я заметил, что:

  • Слово "локальный" может означать либо клиентскую машину SSH , либо вышестоящий хост, доступный с этой машины.
  • Слово «удаленный» может означать либо сервер SSH (sshd) вышестоящего хоста, доступный с него.
  • Переадресация локального порта ( ssh -L ) подразумевает, что клиент ssh начинает прослушивание нового порта.
  • Переадресация удаленного порта ( ssh -R ) подразумевает, что сервер sshd начинает прослушивание дополнительного порта.
  • Мнемоники: "ssh -L l local:remote" и "ssh -R r emote:local", и всегда левая сторона открывает новый порт.

Вместо заключения

Надеюсь, приведенные выше материалы немного помогли вам стать мастером SSH-туннелей 🧙 Если вы находите темы для сетей интересными, но чувствуете, что большинство материалов написано для бородатых сетевых инженеров и почти неудобоваримы для простых разработчиков, У меня есть еще несколько статей, которые могут быть вам полезны:

  • Введение в компьютерные сети: Ethernet и IP
  • Иллюстрированное введение в Linux iptables
  • Мост против коммутатора: что я узнал из тура по ЦОД
  • Контейнерная сеть — это просто (не совсем)

Приятного чтения!

Ресурсы

  • Туннелирование SSH Объяснено гуру сетевых технологий из Teleport.
  • Туннелирование SSH: примеры, команда, конфигурация сервера от SSH Academy.
  • Основы компьютерных сетей для разработчиков
  • Иллюстрированное введение в Linux iptables
  • Наглядное руководство по туннелям SSH: перенаправление локальных и удаленных портов

  • Мост и коммутатор: что я узнал из поездки по ЦОД
  • Networking Lab: Simple VLAN

Не пропустите новые статьи из этой серии! Подпишитесь на обновления блога и получайте подробные технические статьи по темам Cloud Native прямо в свой почтовый ящик.

mysql - Как закрыть этот туннель ssh?

Предположим, вы выполнили эту команду: ssh -f [email protected] -L 3306:mysql-server.com:3306 -N , как описано в сообщении, на которое вы ссылаетесь.

Разбивка команды:

  1. ssh : это говорит само за себя. Вызывает ssh .
  2. -f : (со страницы man ssh )

    Запрашивает переход ssh в фоновый режим непосредственно перед выполнением команды. Это полезно, если ssh будет запрашивать пароли или парольные фразы, но пользователь хочет, чтобы это было в фоновом режиме.

    По сути, отправьте ssh в фоновый режим после того, как вы ввели какие-либо пароли для установления соединения; он возвращает вам приглашение оболочки на localhost вместо того, чтобы регистрировать вас на удаленном хосте .

  3. [email protected] : удаленный сервер, на который вы хотите войти.
  4. -L 3306:mysql-server.com:3306 : Это самое интересное. -L (со страницы man ssh ):

    [bind_address:]порт:хост:хостпорт Указывает, что данный порт на локальном (клиентском) хосте должен быть перенаправляется на заданный хост и порт на удаленной стороне.

    Итак, -L 3306:mysql-server.com:3306 связывает локальный порт 3306 с удаленным портом 3306 на хосте mysql-server. com .

    При подключении к локальному порту 3306 соединение перенаправляется по защищенному каналу на mysql-server.com . Удаленный хост , mysql-server.com затем подключается к mysql-server.com через порт 3306 .

  5. -N : не выполнять команду. Это полезно для «просто перенаправления портов» (цитируя справочную страницу).

Влияет ли эта команда на сервер?

Да, он устанавливает соединение между localhost и mysql-server.com на порт 3306 .

И как закрыть этот туннель...

Если вы использовали -f , вы заметите, что процесс ssh , который вы открыли, перешел в фоновый режим. Более приятный способ закрыть его — запустить ps aux | grep 3306 , найти pid из ssh -f . .. -L 3306:mysql-server.com:3306 -N и убить . (Или, может быть, kill -9 ; я забыл, что просто убить работает). Преимущество этого заключается в том, что , а не убивает все ваши остальные соединения ssh ; если у вас есть более одного, их восстановление может быть легкой ... болью.

... потому что теперь я не могу правильно использовать свой локальный mysql.

Это потому, что вы фактически «захватили» процесс local mysql и перенаправили любой трафик, который пытается подключиться к нему, на удаленный mysql процесс. , гораздо более приятное решение для , заключалось бы в том, чтобы не использовал локальный порт 3306 при переадресации портов. Используйте то, что не используется, например 33060. (Большие числа обычно используются реже; довольно часто для переадресации портов используется такая комбинация: «2525-> 25», «8080-> 80», «33060-> 3306» или похоже.