[Перевод] Расширенное использование SSH

bb0304e2d958118e9c6514b114fdc2be.png

Протокол SSH v. 2 является важной составляющей безопасных компьютерных сетей. Он криптографически надёжен, быстр, универсален и широко распространён. Многие крупные облачные провайдеры даже не пытаются заменить его каким-либо альтернативным, собственным решением, что подчёркивает его силу. 

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

Возможности клиента OpenSSH (ssh, scp, sftp)

Сначала рассмотрим особенности клиентских возможностей OpenSSH — не затрагивая настройки серверной стороны. 

Хранение конфигурации в ~/.ssh/config

OpenSSH имеет мощный механизм настройки клиента, который, на мой взгляд, недооценён людьми, использующими ssh во время ежедневной работы. Имею в виду текстовый файл с расположением по умолчанию .ssh/config в домашнем каталоге каждого пользователя. Его отсутствие не мешает ssh при вызове, но, если вы будете использовать его правильно, он может значительно облегчить вам жизнь.

Я часто замечал, что некоторые настраивают alias в своей оболочке, чтобы подстроить ssh под себя. В большинстве случаев вы можете заменить любой alias, определяющий параметры OpenSSH, несколькими строками в конфигурационном файле. Это удобнее, ведь другие инструменты, предоставляемые OpenSSH, также подхватят набор нужных параметров.

В качестве примера, предположим, что у вас есть хост с именем crunch.example.com, с sshd, прослушивающим порт 2222 (вместо 22 по умолчанию), и вам нужен определенный SSH-идентификатор, расположенный по адресу ~/.id_c для подключения.

Вы можете решить эту проблему, создав псевдоним оболочки вида crunch='ssh -p2222 -i~/.id_c crunch.example.org' в одном из ваших файлов конфигурации оболочки, и использовать его всякий раз, когда вам нужно войти в crunch по ssh. Это удобно, если только вам не нужно перенести некоторые файлы, — для этого придётся вызывать scp или sftp вместо ssh, к которому применён ваш alias

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

Host crunch
  Hostname crunch.example.com
  Port 2222
  IdentityFile /home/user/.id_c

Поместив эти строки в ~/.ssh/config и выполнив команду, которая читает ssh crunch, вы обнаружите, что две необходимые опции применяются к сеансу автоматически и с правильным именем хоста, определённым через DNS. scp и sftp также подхватят их — и есть даже некоторые сторонние инструменты, которые либо используют эти утилиты под капотом, либо интерпретируют файл конфигурации SSH самостоятельно, чтобы обеспечить тот же набор функций и соглашений без дополнительных усилий.

Как мы видим, конфигурационный файл можно использовать как alias, особенно для длинного имени хоста. Системные администраторы могут использовать общесистемный файл /etc/ssh/ssh_config для настройки сессий, которые они хотели бы предоставлять для своих пользователей, тем самым контролируя доступные сессии.

Escape sequences (Управляющая последовательность)

Есть несколько хитрых нюансов при работе в терминале во время установленной ssh-сессии с удалённым хостом. Например, нажатие [Ctrl] + [z] на клавиатуре переведёт в фоновый режим не локальный терминал ssh, соединяющий вас с удалённым хостом, а любой процесс, запущенный на переднем плане на удалённой стороне сеанса SSH. Та же история с [Ctrl] + [c], которая прозрачно пройдёт через ssh и вместо этого передаст сигнал SIGINT процессу на удалённой стороне.

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

При выполнение определённой комбинации можно запустить процесс ssh, зависимый от протокола внутрисетевого взаимодействия. Это позволяет пользователю взаимодействовать с ним, хотя и ограниченным образом. По умолчанию процесс активизируется, когда пользователь вводит комбинацию из перевода строки (т.е. нажимает [Enter]) и тильды ([~]).

Следующее нажатие клавиши сразу после этой магической последовательности имеет важное значение: оно может, например, завершить ту самую сессию, которую вы используете в данный момент. Чтобы узнать о своих возможностях, нажмите клавишу [?].

Чтобы понять, что такое escape sequences и как их использовать, если (или, скорее, когда) они вам понадобятся, лучше всего поиграть с ними в сессии, которую вы инициируете специально для экспериментов.

RemoteForward и LocalForward

SSH можно использовать для туннелирования сетевого трафика между узлами вашей локальной и удаленной сети сервера SSH. Этот туннель будет обладать всеми теми же свойствами, что и сам сеанс SSH: трафик между вашим локальным узлом и удаленным узлом SSH-сервера шифруется и происходит внутри того же TCP-соединения, которое также обрабатывает ваш интерактивный сеанс (если таковой существует, поскольку вы также можете использовать SSHv2 для пересылки только сеансов).

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

Чтобы показать простой пример начала работы, предположим, что вы вошли на локальный хост, который находится в собственной локальной сети, с сервером, к которому он может обратиться по адресу mynas.lan, и на котором запущен sshd, привязанный к TCP-порту 22. Опираясь на предыдущий пример, мы хотим разрешить другим пользователям хоста crunch подключаться к процессу SSH-сервера на mynas.lan (например, для доступа к ресурсам на этом конкретном хосте). Для этого мы должны выполнить команду: ssh -R1337:mynas.lan:22 crunchкоторая после аутентификации переведёт нас в оболочку входа на хост crunch.

Но сессия SSH, которая привела нас туда, также установила прослушивающий сокет на TCP-порту 1337 на crunch (по умолчанию, только для адресов, настроенных на его loopback-интерфейсе), и будет перетягивать весь трафик, поступающий туда, в туннель, который ssh создал благодаря параметру -R нашего переключателя. Весь этот трафик пройдёт через туннель на другую сторону SSH-сессии и будет передан на mynas.lan:22 по TCP в локальную сеть, откуда была отправлена ваша команда ssh. Обратите внимание, что для того, чтобы это сработало, crunch не нужно знать о mynas.lan — это конкретное имя хоста будет использоваться только локальной частью SSH-сессии, которая указала его в параметре -R.

Ключ -L работает почти наоборот: он может туннелировать сетевой ресурс на удалённой стороне сессии (т.е. что-то в сети, к чему crunch может подключиться и воспринимает его как локальный в своей собственной сети) в локальную сеть и таким образом открывать его для локальных пользователей. То есть команда ssh -L9876:coolstuff.local:23 crunchзаставит TCP-порт 9876 локального хоста передавать трафик по зашифрованному SSH-туннелю в crunch, а там передавать его хосту, идентифицированному в (локальном DNS crunch) как coolstuff.local, нацеливаясь на его TCP-порт 23. С хоста, инициировавшего процесс ssh, вы могли бы пообщаться по telnet с coolstuff.local.

Чтобы узнать больше о тонкостях -L и -R, лучше всего прочитать соответствующую документацию в ssh_config (5). Обратите внимание на LocalForward и RemoteForward. (Это ключевые слова, которые вы будете использовать для постоянного создания этих типов переадресаций в ~/.ssh/config). Вы увидите, что необязательно всегда привязывать прослушивающий сокет к интерфейсу loopback (если конфигурация sshd удаленного компьютера позволяет это сделать).

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

DynamicForward

И LocalForward, и RemoteForward — полезные функции, но они могут вести только к одному виду конечных. Для некоторых случаев использования, конечно, было бы неплохо получить некоторую гибкость, и DynamicForward обеспечивает это:  

Если вы подключитесь к нашему старому другу crunch через ssh -D1234 crunch, это заставит локальный процесс ssh установить прослушивающий сокет на TCP-порту 1234 (привязанный к адресам интерфейса loopback). TCP-трафик, приходящий туда и соответствующий протоколу проксирования SOCKS5, будет передан по зашифрованной линии на удаленную сторону SSH-сессии, запущенной на crunch, и смело отправится туда, куда скажет ему магия SOCKS-прокси. Если у вас есть приложение на локальной машине, которое может быть настроено на использование SOCKS-прокси, направьте его на localhost:1234, и его трафик «телепортируется» через туннель SSH DynamicForward на crunch.

Чтобы использовать эту возможность в примере с curl, откройте второй shell на вашем локальном хосте (с открытой SSH-сессией, в которой было указано -D1234), и запустите командную строку, начинающуюся с curl --preproxy socks5://localhost:1234 ..., добавив URL по вашему выбору. Это заставит curl проксировать своё TCP-соединение через SOCKS5 proxy listener, который ssh-запрос DynamicForward создал ранее, и заставит весь трафик curl, прибывающий в конечный пункт назначения, выглядеть и ощущаться так, как будто он пришёл из crunch. Следовательно, этот трафик будет подчиняться любым сетевым условиям crunch, правилам брандмауэра и т. д., что, вероятно, приведёт к другому набору возможностей и ограничений, чем те, которые применяются к вашему локальному хосту.

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

ControlMaster

Окей, здесь все становится немного забавным. Да, ControlMaster не для слабонервных, но это настолько полезная функция для частых случаев использования, что я не могу оставить ее без упоминания.

SSH показывает относительно низкую производительность, когда вы используете его для создания множества недолговечных сессий и выполняете небольшое количество операций в каждой из них. Популярные инструменты конфигурации, такие как ansible, виновны в таком поведении, и есть различные возможности оптимизации, чтобы сделать создание сеанса SSH быстрее (как, например, переход с ключей RSA на ecdsa или ed25519). Правильная настройка режима ControlMaster, конечно, значительно превосходит любые другие способы настройки, которые вы можете придумать, но также имеет долю недостатков.

Давайте рассмотрим это более подробнее.

A persistent SSH connection socket per remote host

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

Это позволит обойти трёхстороннее рукопожатие TCP для нового соединения на четвёртом уровне OSI, а также пропустить аутентификацию SSH на седьмом уровне OSI, что позволяет сократить два наиболее трудоёмких и вызывающих задержку этапа, через которые должно пройти новое соединение SSH в нормальных условиях.

Конечно, использование других процессов в качестве «копий» сеанса первоначального процесса влечёт за собой ряд ограничений и потенциальных проблем. Прежде всего, если безвременно завершается процесс, который держит установленный сеанс открытым, вечеринка для всех, кто пришел после для повторного использования существующего сеанса, окончена. 

Учитывая тот факт, что в одном соединении может быть множество дополнительных параллельно активных сессий, завершение одного «неправильного» PID может иметь довольно неприятные последствия для приложений или задач, выполняющихся на этом удалённом хосте. Можно использовать ControlPersist для борьбы с этой конкретной проблемой, но наличие современного Linux-хоста на базе systemd без настроенной задержки сеанса systemd-logind все равно может превратить ваш начальный процесс ssh в бомбу замедленного действия, которая, взорвавшись, может унести с собой других. Так что будьте осторожны.

А если первая сессия в интерактивном режиме, то подключенные к сокету ControlMaster другие процессы будут препятствовать нормальному завершению начального процесса/сессии после завершения интерактивной части оболочки. Процесс ssh будет как бы «висеть», ожидая завершения работы других пользователей его средств, подобно тому, как интерактивные сессии с настроенными переадресациями (и активно используемые) завершают свою терминальную сессию. Опять же, это облегчается с помощью ControlPersist, но при этом действуют упомянутые выше условия.

Кроме того, любой пользователь, имеющий доступ к установленному на ControlPath в файловой системе сокету ControlMaster, также сможет подключиться к общей сессии. Это немного похоже на сценарий «перенаправление агента на неавторизованный хост», но потенциально хуже, поскольку атакующему даже не нужно иметь информацию о том, для каких именно устройств действуют идентификаторы агента.

Тест-драйв ControlMaster

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

Host cmaster-test
  Hostname replace-with-actual-remote.example.com
  BatchMode yes
  ControlMaster auto
  ControlPath ~/.ssh/cmaster-test-do-not-use-this-in-production
  # ControlPersist yes # also try to toggle this!

Теперь подключитесь к хосту, используя ssh cmaster-test в обеих оболочках. Обратите внимание, что вторая сессия будет запускаться заметно быстрее (и, возможно, пропустит отображение motd). Попробуйте завершить первый сеанс SSH, выйдя из системы, и посмотрите, что произойдёт со вторым. Затем создайте третий и подключитесь снова. Повторите все с помощью ControlPersist. Убейте несколько ssh-процессов друг под другом, просто чтобы подурачиться.

Подводя итог, можно сказать, что установка ControlMaster и ControlPath как включенных по умолчанию является плохой идеей. Но с учётом его уникальных преимуществ в производительности, он является настоящим удовольствием для хостов, настроенных в инвентаре ansible. Если правильно распределить правила в конфигурации ansible и в пользовательском файле ~/.ssh/config, вы сможете получить лучшее из возможного, повторно используя постоянные сокеты ControlMaster в сценариях, где это имеет смысл, и оставив его в покое в тех случаях, когда вы считаете, что это скорее риск, чем польза. 

Это может потребовать кучу времени, ведь придётся многое перепробовать, чтобы сделать все правильно. Так что думайте сами, решайте сами. Если вам понравится этот опыт, ознакомьтесь с документацией ssh_config (5) по ControlPath и убедитесь, что эти сокеты попадают в частный, защищенный каталог, доступ к которому могут получить только авторизованные учетные записи.

ProxyJump and ProxyCommand

Часто корпоративные политики безопасности (или просто меры предосторожности) заставляют использовать «бастионы» хостов — разрыв в протоколе, к которому пользователь должен подключиться с помощью SSH, а затем использовать этот бастионный хост в качестве источника соединения с реальным местом назначения, куда пользователь намеревается попасть. При правильной реализации это может действительно укрепить безопасность вашей организации. С другой стороны, это делает подключение к предполагаемому удалённому узлу сложным, и может сломать scp, sftp и другие полезные штуки.

ProxyJump может помочь вам справиться с подобными настройками и перепрыгнуть через (теоретически) произвольное количество цепочек SSH-переходов между вашим локальным узлом и предполагаемым удалённым. Эта опция вызовет «предварительное» SSH-соединение с узлом ProxyJump, а затем использует возможности протокола SSH сервера этого узла для создания переадресации на SSH-сервер-приемник адресата. Конечно, если конфигурация SSH-сервера узла бастиона не позволяет создавать переадресации, это не поможет.

Ранее OpenSSH ProxyJump не был доступен, но его можно было воспроизвести с помощью умного использования опции ProxyCommand и некоторой магии netcat/socat. Сегодня вы должны отдавать предпочтение ProxyJump, если хотите добраться до конечного хоста за один прыжок. 

Если бы я знал об этой возможности в 2007 году на одной из своих первых работ, я бы избавил себя от многих хлопот, реализуя фреймворк для обёртки ssh-соединений в виде bash-скрипта, настолько ужасного, что от него хотелось выколоть себе глаза.

Спасибо, что нашли время прочитать эту статью. Надеюсь, она была интересной. Я также готов раскрыть тему дальше.

© Habrahabr.ru