Советы пользователям

Documentation
Login

Title: Secure Shell

Author: Vitus Wagner

Введение

Почти все, кто использует Linux и другие unix-системы слышали или даже на практике использовали протокол ssh - Secure Shell. Это такой протокол удалённого выполнения команд на другой машине (на которой выполняется ssh-демон).

Многие другие программы, которые осуществляют доступ к удалённым системам, используют протокол ssh в качестве транспорта. Например rsync может использовать его вместо своего собственного протокола, например git. Многие почтовые клиенты позволяют добираться до IMAP-сервера не через TCP/IP, а запуская процесс imapd на сервере с помощью ssh (для этого правда требуется чтобы у пользователя был на сервер доступ, позволяющий ему запускать там программы). Даже существует виртуальная файловая система sshfs, позволяющая примонтировать к своей машине файловую систему удалённого сервера.

Почему же протокол ssh так популярен? В первую очередь потому что он обеспечивает надёжную и удобную аутентификацию и авторизацию пользователя, избавляет от необходимости лишний раз вводить пароли. (хотя и позволяет их использовать).

Во-вторых, он удобен и для интерактивной, и для неинтерактивной работы. Если вы набёрете

ssh othermachine

в окне терминала, то вместо сессии shell текущей машины в этом окне появится сессия с удаленной, и там можно будет делать все так же, как будто вы сидите там локально.

В принципе, можно даже графические приложения запускать (если скорости канала передачи данных хватит) и они покажутся на вашем экране, если вы не запретили X11Forwarding в ssh.

А если вы наберёте

ssh othermachine command то команда будет выполнена там, а результаты окажутся (и могут быть обработаны здесь). Например

ssh tar -c .| tar -x

приведёт к тому, что на удалённой машине каталог будет заархивирован в stdout, а на локальной - распакован.

Ещё интересной особенностью протокола ssh является то, что он позволяет обычному пользователю выстраивать свою структуру сети, почти не завися от системных администраторов, настроек DNS и т.п., работать одновременно с машинами, находящимися под контролем разных системных администраторов и с разными политиками администрирования.

Данная статья не является попыткой заменить man-страницы ssh(1), ssh_config(5), ssh-keygen(1) и sshd(5). Это скорее краткий обзор возможностей протокола ssh и его реализации OpenSSH, призванный дать представление о том, какие задачи можно решать с помощью ssh, и какую информацию искать в его документации.

Аутентификация сервера

Новички в IT считают, что безопасность протокола обеспечивается тем, что передаваемые данные шифруются. Шифрование — вещь, конечно, важная, но не единственная. Безопасность — всегда цепочка мер, и рвётся она на самом слабом звене.

В современных криптопротоколах сессионный ключ шифрования вырабатывается совместно клиентом и сервером.

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

Это называется «Man in the middle attack» и является достаточно распространённой угрозой.

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

Для этой цели используются так называемые ключи хостов (host keys).

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

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

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

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

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

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

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

В принципе, можно хранить открытые ключи хостов в DNS. Для этой цели предусмотрена запись SSHFP. Но, как правило, так никто не делает, ограничиваясь тем, что отслеживаются изменения ключей.

Потому что сама по себе DNS не даёт гарантии того, что данные в ней никто не подменит. Существует, конечно DNSSEC, в котором записи DNS защищаются электронной подписью и цепочка подписей возводится к какому-нибудь доверенному ключу, но из-за сложностей администрирования эта система распространения не получила.

А чтобы SSH безусловно доверял ключам в DNS требуется чтобы использовалась DNSSEC. А если ssh все равно переспрашивает пользователя, то возиться с внесением записей в DNS нет смысла.

При первом соединении клиент ssh спросит у вас «А вот я знать не знаю ключа такого-то для хоста такого-то, хотите продолжить соединение?» и если вы ответите yes, сохранит этот ключ в ${HOME}/.ssh/known_hosts.

В дальнейшем, если вдруг ключ хоста изменился, ssh откажется соединяться с этим хостом, выдав на полэкрана ругательств что «Кто-то за вами следит». И для того, чтобы соединениться с этим хостом, вам придется удалить старые ключи хоста из known_hosts либо руками, либо с помощью команды ssh-keygen -R. Это вполне логично. Если вдруг на машине, где вы хотите работать, была переставлена операционная система и перегенерировались ключи, вы, наверное, об этом знаете. Если нет, то лучше выяснить вопрос, а с чего бы это вдруг такие изменения, прежде чем вводить туда свой пароль.

SSH запоминает в файле known_hosts не только имя хоста, но и его IP-адрес, и если IP-адрес для данного имени изменился, считает это признаком атаки. Это можно отключить в файле конфигурации ${HOME}/.ssh/config либо вообще, либо для конкретного хоста, либо для хостов, имена которых удовлетворяют определённому шаблону. Например, если есть локальная сеть, в которой IP-адреса раздаются динамически, можно для ее хостов отключить проверку IP.

Конфигурационный файл

У SSH есть конфигурационный файл ${HOME}/.ssh/config, в котором можно написать как директивы, относящиеся ко всем исходящихм с данной системы коннектам в целом, так и директивы, относящиеся к конкретному хосту.

Последняя возможность очень удобна в том случае, если у вас на разных машинах разное имя пользователя, или ssh-сервер слушает на нестандартном порту[^1], или нужная вам машина имеет длинное и неудобное доменное имя, а вы не можете или не хотите добавить этот домен в опцию search вашего /etc/resolv.conf.

[^1]: Стандартный порт для протокола ssh - 22. Но если сервер доступен из интернета, его почти наверняка будут мучить попытками соединения с подбором имени пользователя и пароля. Поэтому многие системные администраторы предпочитают использовать нестандартный порт.

Все эти проблемы можно решить через .ssh/config.

Например, написав в этом файле

Host myserver ; Длинное и неудобное доменное имя Hostname myserver.datacenter-1.provider.com ; Нестандартный порт Port 2222 ; Несовпадающее имя пользователя User webmaster

вы в дальнейшем сможете использовать команду ssh myserver вместо

ssh -p 2222 webmaster@myserver.datacenter-1.provider.com

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

Авторизация по ключам

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

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

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

Файл, в который надо поместить свой открытый ключ, называется .ssh/authorized_keys.

SSH довольно трепетно относится к правам доступа на этот файл. Поэтому если хотя бы какой-то каталог в пути к этому файлу (корень файловой системы /home, /home/user, /home/user/.ssh) или сам файл имеет права записи для группы, не говоря уж о прочих, пускать по этому ключу он откажется. Даже если в этой группе никаких других пользователей нет.

ssh-agent

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

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

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

Но существует программа ssh-agent (и в ряде других реализаций — свои агенты). Эта программа запускается в начале сеанса, в нее загружаются ключи, и после этого любая запущенная копия ssh вместо того, чтобы читать ключи и выполнять криптографические операции сама, обращается к к агенту через сокет, имя которого содержится в переменной среды SSH_AUTH_SOCK и просит его выполнить эту операцию. (в принципе, агент тоже может не сам выполнять эти операции. ssh поддерживает работу с аппаратными криптографическими токенами, а в этом случае все операции, требующие секретного ключа, производятся на токене и ключ никогда это устройство не покидает).

Это уже лучше. Мы один раз ввели пассфразу, а потом сотни раз запускаем ssh как руками, так и через посредство разных программ и все они прекрасно пользуются этим ключом постольку поскольку у них есть права на чтение-запись в аутентификационный сокет и известно его имя.

Но самое интересное в том, что в протоколе ssh предусмотрен Agent Forwarding. То есть вы заходите со включённым форвардингом по ssh на удалённую машину, и запускаете ssh там. А он, когда третья машина запрашивает у него аутентификацию, вместо того чтобы подписать присланный челледж сам, просит об этом sshd, который передаёт эту просьбу ssh на той машине, на которой вы сидите, а тот - ssh-agent

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

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

Ограничение возможностей ключа

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

Поэтому хочется сделать так, чтобы тот, кто аутентифицировался этим ключом не мог ничего лишнего натворить.

ssh такую возможность предоставляет. В файле authorized_keys можно перед собственно ключом (вернее перед идентификатором его типа) написать разнообразные опции, ограничивающие то, что можно с этим ключом делать.

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

Все подробности того, что можно сделать с помощью опций в файле authorized_keys описаны в man-странице sshd(8).

Сертификаты

Иногда приходится иметь дело с таким количеством ключей (пользователей или хостов) что ручная поддержка их списков файлах authorized_keys и known_hosts становится слишком трудоёмкой.

В этом случае может оказаться полезным централизовать обработку ключей и выписывать на каждый ключ сертификат. Сертификат это файл, содержащий в себе открытый ключ, указание кому он принадлежит (пользователю или хосту, это называется «принципал») и набор опций, ограничивающих возможности этого ключа, подписанный электронной подписью на специальном ключе, называющемся ключом certification authority (по-русски это называется удостоверяющий центр). Удостоверяющим центром в случае SSH может работать любой ключ, который прописан как ключ CA в authorized_keys.

Соответственно у вас в known_hosts или authorized_keys лежит не сотня ключей хостов или пользователей, а один ключ удостоверяющего центра. А все хосты или пользователи соответственно, в момент соединения не просто удостоверяют, что они имеют в распоряжении секретный ключ от имеющегося у вас открытого, а сначала предоставляют сертификат на свой открытый ключ, подписанный ключом CA, которому вы доверяете.

Если вы пользуетесь сертификатами, то придётся освоить также и концепцию key revocation lists.

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

Если же вы используете сертификаты, то в распоряжении злоумышленника имеется сертификат на украденный открытый ключ (поскольку сертификат и секретный ключ обычно лежат в соседних файлах) подписанный ключом CA, которым подписана ещё сотня ключей. Поэтому CA должен выпустить key revocation list, перечисляющий ключи, которым больше доверять не следует. Правда, в существующих реализациях sshd такой список может быть подключён только глобально, на уровне конфигурационного файла сервера, в то время как ключ CA может быть задан в пользовательском authorized_keys. Впрочем, можно и файл с ключами доверенных CA задать глобально. Тогда пользователи смогут логиниться с именами, указанными в их сертификате, независимо от наличия домашней директории и файла .ssh/authorized_keys.

Аэродром подскока

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

В принципе, не так уж сложно, зайдя по ssh запустить ssh ещё раз. Но, если вы используете ssh как транспорт для других программ, например копируете файлы с помощью scp, или пробрасываете порты, то удобнее иметь прямое соединение.

Для этой цели существует понятие Proxy, которое в конфиге ssh может присутствовать в двух инкарнациях — ProxyCommand и ProxyJump. ProxyJump — более простой вариант. Вы просто указываете URL вида user@host:port (в простейшем случае - просто имя хоста) куда нужно зайти по ssh, чтобы оттуда попасть на целевую машину.

Этой директиве конфигурационного файла соотвтствует опция -J командной строки. (директива конфигурации ProxyJump и ключ -J появились в ssh относительно недавно. И если у вас дистрибутив десятилетней давности, например Centos 6 или SLES 11, вам придётся обходиться без неё).

ProxyCommand — более гибкая и сложная конструкция. Эта директива указывает команду, которая должна быть выполнена, после чего ssh будет использовать её stdin/stdout вместо прямого TCP-соединения к удалённой машине. Используя в качестве прокси-команды nc можно воспользоваться для доступа по ssh http-прокси (при условии, что там разрешён метод CONNECT на нужный порт). А воспользовавшись ключом -W у ssh на промежуточный хост, вы можете сымитировать поведение опции ProxyJump.

Ключа командной строки для ProxyCommand не предусмотрено. Поэтому если надо использовать её однократно и не хочется прописывать в конфиг, то придётся использовать ключик -o, позволяющий указать в командной строке любую директиву конфигурации.

SSH как транспорт для других программ

scp

Наиболее распространённой командой, для транспортом для которой служит ssh, наверное является scp. Она входит в состав пакета ssh и является необходимым дополнением. Где выполнение команд, там и работа с файлами.

Авторы scp старались сделать её возможно более похожей на unix программу cp. Поэтому опция -p, которая в ssh указывает нестандартный порт, оказалась занятой под копирование прав доступа. А порт надо указывать через -P.

Ещё одной, на мой взгляд, неприятной, особенностью scp является то, что если забыть указать во втором аргументе двоеточие, отделяющее имя удалённой машины от пути на ней, то программа ни слова не говоря сработает как обычная локальная cp.

sftp

Лично мне никогда не приходило в голову пользоваться этой программой (вернее этой подсистемой в экосистеме ssh). При работе из командной строки и тем более из скриптов у sftp нет никаких преимуществ с точки зрения удобства перед scp. У авторов графических файловых менеджеров может быть другое мнение, но что они там внутри используют — scp, sftp или sshfs мне, как пользователю всё равно.

rsync

Очень удобная и мощная программа копирования файлов и директорий. Её основное преимущество в том, что она умеет не копировать лишнего. Если какой-то файл в точке назначения уже присутствует, то его по сети передавать и не будут, если он совпадает. А если большой файл менялся только местами, может и поблочно обработать, передав только изменившиеся блоки. rsync имеет свой собственный транспортный протокол и может работать сервером, к которому такая же rsync будет коннектиться непосредственно по TCP. Но использование ssh-транспорта автоматически решает все вопросы аутентификации пользователей, и избавляет от необходимости настраивать сервер.

git и прочие системы управления версиями

Почти все системы управления версиями, начиная c CVS, поддерживают в качестве транспорта ssh. В основном из-за того, что авторизация по ключам это очень удобно, особенно при наличии агента.

Форвардинг портов

Мы уже упоминали пару случаев проброса портов через защищённое ssh соединение - X11 forwarding и agent-форвардинг.

Но если есть эти частные случаи, то можно реализовать и универсальный.

И ssh его реализует. Есть два вида форвардинга - удалённый и локальный. В первом случае мы предоставляем возможность программе работающей там подключиться к серверу здесь. В эту категорию как раз попадают X11-forwarding и agent-forwarding.

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

Приведём несколько примеров.

  1. Допустим, мы работаем в офисе и у нас есть сервер с локальным репозиторием debian-пакетов, доступный по http (порт 80) на машине localrepo.

    Мы хотим зайти на машину, расположенную у заказчика за тремя ProxyJump-ами, двумя VPN-ами и четырьмя файрволлами и поставить там пакет из этого репозитория.

    local-workstation $ ssh -R 8080:localrepo:80 customer-server customer-server $ echo "deb http://localhost:8080/debian stable main"| sudo tee /etc/apt/sources.list.d/forwarded.list customer-server $ sudo apt-get update Cущ:1 http://deb.debian.org/debian stable InRelease Пол:2 http://localhost:8080/debian stable InRelease [6,4 kB] Пол:3 http://localhost:8080/debian stable/main/amd64/Packages [18.6 kB] Получено 26 kB за 1с (30 kB/s) Чтение списков пакетов… Готово customer-server $ sudo apt-get install our-package

    Это пример на remote forwarding

  2. У нас есть сервер виртуальных машин, на котором запущен windows-сервер, доступный по RDP. Но только с локальной машины. Та виртуальная сеть, в которую включены эти вирутальные машины, вообще снаружи не доступна

    ssh -N -n -L 3389:virt-win-srv:3389 virt-machines-host & xfreerdp /v:localhost /clipboard /home-drive

    В данном случае нам не нужно выполнять никаких команд на удалённой системе, поэтому мы воспользовались опцией -N у ssh, и нужно чтобы ssh не пытался читать с терминала, проглатывая ввод следующих команд. Поэтому -n.

    У читателя может вознинкнуть вопрос: а как же управлять таким процессом, который ушел в фоновый режим, stdin не читает и т.д. В принципе у ssh есть и такие возможности, см директивы конфигурации ControlMaster и ControlPath и опцию командной строки -O. Но у меня как-то никогда не возникало необходимости в этом разбираться.

  3. Мы хотим получить доступ ко всем http и https ресурсам офисной сети через браузер.

    ssh -D 4080 workstation

    После чего идем в настройки браузера и указываем ему использовать SOCKS-прокси по адресу localhost порт 4080. После этого все tcp-соединения браузер будет не открывать самостоятельно, а делегировать этой прокси,то есть нашей ssh-сессии. Это еще не VPN но уже близко.

Впрочем полноценную VPN с помощью ssh тоже можно организовать. Правда для этого потребуются root-права на обоих концах соединения и возможность управлять роутингом той сети, которую расширяет эта vpn.

Для этого предназначен ключик -w.

Другие реализации ssh

Очевидно, мы тут изложили далеко не все возможности OpenSSH. Кроме того, не следует забывать и того, что OpenSSH — не единственная реализация клиента и сервера протокола ssh. В Windows например популярен комплект программ putty. Там кроме GUI-программы putty, объединяющей клиент ssh и эмулятор терминала есть командно-строчный вариант plink, свой агент pageant и программа копирования файлов pscp. А возможности форвардинга настраиваются в осовном через меню.

Есть и реализация в виде библиотеки libssh2, библиотека для языка python asyncssh и так далее.

Какие-то из них имеют больше возможностей, какие-то меньше.