Существует несколько способов аутентификации пользователя:
- По паролю — стандартный механизм, но не самый надежный, я не буду заострять на нем внимание
- По ключу — самый надежный способ аутентификации, но это при условии, если правильно его использовать
- По IP адресу — режим совместимости. Привожу просто для справки, сомневаюсь, что кто-то будет использовать его в трезвом уме.
Асимметричное шифрование
SSH использует ассиметричное шифрование, суть которого заключается в том, что есть два ключа: открытый и закрытый. Зашифровать сообщение можно открытым ключом, а расшифровать — закрытым. Закрытый ключ хранится в сохранности, а публичный доступен всем. Кроме того, подписать сообщение можно закрытым ключом, а проверить эту подпись — открытым.
На схеме видно, что после обмена публичными ключами два узла могут безопасно общаться между собой через небезопасный Интернет.
Кроме того, аутентификация пользователя может производиться при помощи этой самой пары ключей.
Публичный ключ пользователя необходимо добавить в файл $HOME/.ssh/authorized_keys
В этом файле может быть несколько публичных ключей — все они будут работать.
Защита от MITM атак
MITM-атака — это вид атаки в криптографии, когда злоумышленник тайно ретранслирует и при необходимости изменяет связь между двумя сторонами, которые считают, что они непосредственно общаются друг с другом. Является методом компрометации канала связи, при котором взломщик, подключившись к каналу между контрагентами, осуществляет вмешательство в протокол передачи, удаляя или искажая информацию.
Одним из основных нововведений во второй версии протокола стала встроенная защита от MITM-атак.
Суть защиты заключается в том, что каждая сторона должна убедиться, что с другой стороны находится именно тот, кто ожидается, а не злоумышленник.
За MITM-защиту на стороне клиента (т.е. за то, чтобы определить, что ты подключаешься именно к своему серверу) отвечает параметр StrictHostKeyChecking и файл known_hosts.
Публичные ключи серверов хранятся в файлах:
$HOME/.ssh/known_hosts — пользовательский файл,
/etc/ssh/ssh_known_hosts— системный файл.
Каждая запись в known_hosts является строкой, содержащей поля и использующей пробел в качестве разделителя:
- одно или несколько имен серверов или IP-адресов, разделенных запятыми
- тип ключа
- открытый ключ
- любые дополнительные комментарии
Именно здесь происходит «идентификация» сервера
StrictHostKeyChecking=no|ask|yes
Значения могут быть следующие:
- no — подключение произойдет в любом случае, максимум мы получим предупреждение о том, что соединение небезопасно. Часто можно встретить во всяких автоматических скриптах типа автотестов и автодеплоев, тут и кроется основная проблема — если скрипт отработает нормально, то пользователь (администратор) и не заметит неладное, а заражение/компрометация произойдет.
- ask — спрашивать при новом подключении (значение по-умолчанию), если записи еще нет в known_hosts, и, в случае согласия с подключением добавляется новая запись
- yes — выполнять проверку всегда, самый безопасный вариант, который настоятельно рекомендую использовать в автоматизированных системах. Подключение производится только при наличии записи в known_hosts
Что нам это дает:
- При первом подключении мы можем определить, не представился ли сервером злоумышленник
- Гарантию, что при последующих подключениях к серверу не произойдет подмены добропорядочного сервера злоумышленником
Если со вторым пунктом все понятно, то как быть с первым?
Тут можно выделить как минимум три варианта:
- Довериться случаю и согласиться на добавление нового ключа сервера
- Добавить флаг VisualHostKey=key, при этом клиент отобразит визуальное представление открытого ключа сервера, это представление уникально и облегчит запоминание ключа
- Флешка или открытые источники: это вариант для параноиков, однако, если компрометация системы может стоить десятков тысяч долларов, то это не лишне.
Использование визуализации открытого ключа
Если открытый ключ изменился
Сообщение о смене ключа может происходить по нескольким причинам:
- Изменился адрес сервера
- Изменился ключ сервера, например, при переустановке системы
- MITM атака
Из моей практики 1 и 2 варианты в сумме стремятся к 100%, но и пункт 3 нельзя исключать тоже.
Вот пример лайфхака, который объединяет в себе веб- и ssh-подходы
curl https://server.com/ssh_fingerprint | tee -a ~/.ssh/known_hosts
На первый взгляд это не очень безопасно, однако, эту информацию можно получить при помощи команды
ssh-keyscan example.com
Однако, в первом варианте мы дополнительно проверяем подлинность полученного ключа при помощи HTTPS.
В критически важных CI/CD использование комбинации
ssh-keyscan example.com >> ~/.ssh/known_hosts
во время инициализации совместно с
StrictHostKeyChecking=yes
позволит надежно защититься от MITM-атак.
Множество ключей
Часто можно встретить такую картину:
Для каждого сервера на клиенте сгенерированы свои пары ключей, и в $HOME/.ssh/ творится полнейший беспорядок.
С одной стороны, это безопаснее, нежели использование одной пары клиентских ключей — при компрометации одного ключа становится скомпрометированой только одна система, а не все, с которыми работал пользователь.
Однако, как показывает практика, этим руководствуются пользователи в последнюю очередь, зачастую они просто выполняют все инструкции по шагам:
ssh-keygen
...
...
Иногда я наблюдал ситуацию, когда одни ключи перетирались другими без какой либо мысли в голове.
Напомню: если вы гонитесь параноидально за безопасностью, то достаточно иметь одну пару ключей, но при этом ее надо правильно защищать.
Кстати, о защите ключей
Расценивайте свой приватный SSH-ключ, как ключ от квартиры, где деньги лежат. Более беспечного отношения, чем к SSH-ключам, я не видел. Если серьезность сохранения паролей пользователи еще более или менее понимают, то о том, что никому нельзя давать доступ к приватному ключу, задумываются единицы.
Что тут можно выделить:
- Хранить ключ надо в безопасном месте, в идеале это шифрованное хранилище
- Никому не предоставлять доступ к ключу, скомпрометированный ключ компрометирует все системы где разрешен доступ по нему
- Шифрование приватного ключа увеличивает безопасность, т.к. при потере ключа необходимо еще получить пароль для его дешифрации, либо потратить время и вычислительные мощности на его взлом.
- Не забывать про установку корректных прав на файл ключа 400, кстати, это распространенная ошибка, когда клиент отказывается использовать ключ
Изменить или добавить пароль можно при помощи команды
ssh-keygen -p
SSH-Agent
Но часто бывает такая ситуация, когда необходимо со своим ключом перейти на сервер и там уже им воспользоваться. Существует три решения данной задачи:
- Скопировать свой приватный ключ на сервер
- Сгенерировать новую пару ключей и прописать ее в качестве варианта доступа
- Использовать ssh-agent
Из моей практики, пользователи в 90% случаев выбирают первые два варианта, а, как мы уже говорили: нет ничего более страшного, чем компрометация ключа.
Я же рекомендую воспользоваться третьим способом
SSH-agent хранит секретные ключи и, когда нужно, пользуется ими. Программа (например, ssh), когда ей понадобится воспользоваться секретным ключом, не делает этого сама, а обращается к ssh-agent 'у, который, в свою очередь, уже сам пользуется известными только ему данными о секретных ключах. Таким образом, секретные ключи не разглашаются никому, даже программам, принадлежащим самому пользователю.
Команда ssh-agent создает файл-сокет с именем /tmp/ssh-XXXXXXXX/agent.ppid, через который осуществляется взаимодействие с агентом. Всем дочерним процессам агент при помощи переменных окружения SSH_AUTH_SOCK (в которой хранится имя файла-сокета) и SSH_AGENT_PID (в которой хранится идентификатор процесс агента) сообщает информацию о том, как с ним можно связаться.
Агент выдает информацию в виде, удобном для использования командным интерпретатором.
SSH_AUTH_SOCK=/tmp/ssh-XXt4pHNr/agent.5087; export SSH_AUTH_SOCK;
SSH_AGENT_PID=5088; export SSH_AGENT_PID;
echo Agent pid 5088;
При указании ключа -c агент использует синтаксис C Shell. По умолчанию (и при явном указании ключа -s) используется синтаксис Bourne Shell. Эти переменные следует установить в текущем командном интерпретаторе, поэтому обычно вызов ssh-agent комбинируется с командой eval.
$ eval `ssh-agent`
Agent pid 5088
Агент работает до тех пор, пока не будет явно завершен сигналом либо вызовом
$ ssh-agent -k
Список известных агенту секретных ключей можно посмотреть той же командой ssh-add с ключом командной строки -l. Команда сообщит и отпечаток для каждого ключа.
$ ssh-add -l
1024 46:88:64:82:a7:f9:aa:ea:3b:21:9e:aa:75:be:35:80 /home/user/.ssh/id_rsa (RSA)
Пример работы с ssh-agent можно увидеть ниже
Кроме того, агент решает еще одну задачу: если вы зашифровали приватный ключ, то при обращении к нему каждый раз не будет запрашиваться пароль. Если же использовать агента, то пароль будет необходимо будет вводить только при добавлении ключа в агента.
Локальный конфиг
Если бы каждый раз, когда я наблюдал за набором строки вида
ssh -p 2022 user@server.com -o StrictHostKeyChecking=yes -i ~/.ssh/server.pem
мне давали рубль, то я бы уже давно жил в Сочи.
На локальной машине можно найти файл $HOME/.ssh/config
Host server
User root
HostName 192.168.0.3
IdentityFile ~/.ssh/server.pem
Host example.com
User admin
IdentityFile ~/.ssh/production.pem
Соответственно, можно прописать большинство параметров для хоста в этот файл и не вводить каждый раз.
Как еще можно улучшить безопасность
1. При использовании аутентификации по ключу не забывайте, что остается еще пароль, и, как бы вы ни защищали свой приватный ключ, злоумышленник сможет подобрать ваш пароль qwerty и войти в систему без необходимости похищать ключ
Можно выделить несколько решений:
- Задать очень сложный пароль, который будет сложно подобрать и хранить его в защищенном месте. Это позволит иметь альтернативный способ входа.
- Удалить пароль совсем, но тогда могут возникнуть проблемы с sudo и прочим
- Запретить на сервере аутентификацию по паролю, собственно, как правило, для пользователя root это и запрещается. Остается запретить для всех пользователей.
PasswordAuthentication no в /etc/ssh/sshd_config
2. Еще можно запретить использование ssh v1
Protocol 2 в /etc/ssh/sshd_config
3. И обязательно закрывать на фаерволе доступ к SSH для ненадежных источников
4. Раньше бы я посоветовал сменить порт со стандартного 22 на другой, но наблюдая за тем, сколько бывает попыток авторизоваться на нестандартных портах я могу сказать однозначно: это не является защитой от взлома.
5. Если уже рассматривать подбор пароля, то не лишним будет настроить fail2ban для того, чтобы ограничить попытки подбора пароля.
6. В протоколе и библиотеках периодически находят проблемы и уязвимости, поэму тут работает универсальный подход — следите за обновлениям ПО и старайтесь регулярно устанавливать как минимум заплатки безопасности.
7. ssh-agent безопасней, чем просто копирование файлов, однако, его тоже можно взломать, например, root пользователь на сервере. Поэтому рекомендуется включать форвардинг только в тех случаях, когда это необходимо.
Выводы
Читайте документацию, в ней все написано, как бы это пошло не звучало.
SSH очень безопасный протокол, но, зачастую, именно человеческий фактор является основным источником проблем — следите за безопасностью.
Использованные материалы
Защита с помощью SSH ключей
Управление ключами SSH с помощью агента
Комментарии (53)
Fedcomp
21.06.2018 13:04Но часто бывает такая ситуация, когда необходимо со своим ключем перейти на сервер и там уже им воспользоваться. Существует три решения данной задачи
Если ssh-agent запускается на удаленном сервере то четыре.
Локальный ssh-agent + ForwardAgent yesRomanKu Автор
21.06.2018 13:19Ну по факту это развитие 3 пункта: проброс ssh-agent по цепочке
Fedcomp
21.06.2018 13:21Не согласен. Кидать свой приватный ключ на удаленный сервер — рисково. Пробрасывать с локальной машины — безопасно. Большая разница.
RomanKu Автор
21.06.2018 13:49Мне кажется, что мы друг друга не поняли.
Локальный ssh-agent + ForwardAgent yes — я это и указывал
А цепочку можно построить так:
$ ssh-add $ ssh -A server connected $ ssh-add # будет добавлен ключ из первого шага $ ssh -A server2 connected $ ssh-add # будет добавлен ключ из первого шага $ ssh -A server3
alexac
21.06.2018 14:07Пробрасывать ssh-agent — настолько же не безопасно, насколько и кидать приватный ключ на удаленный сервер. root на сервере может как утянуть файлик с приватным ключом, так и утянуть сокет ssh-agent. И защиты от этого нет.
Fedcomp
21.06.2018 14:08он будет иметь доступ к ключу ровно на время сеанса, и не более. В случае прокидывания ключа — утекает сам файл (который может быть запаролен, и все же).
torkve
21.06.2018 19:26В очень большом проценте случаев этого достаточно, чтобы, воспользовавшись агентом, добраться до машины пользователя (где этот же ключ лежит в authorized_keys) и дальше с правами пользователя выполнить что угодно:
— стянуть приватный ключ, если он не запаролен
— прописать свой ключ в authorized_keys и ходить на пользовательскую машину пользоваться уже локальным агентом
— запустить любые туннели или сервисыFedcomp
21.06.2018 19:32Вообще вроде как совсем у малого процента пользователей внешний айпи ведущий на их машину. Еще меньше пользователей которые прописывают свой же ключ в authorized_keys. К примеру на рабочем ноутбуке у меня такого файла нет.
torkve
21.06.2018 19:46У достаточно большого, не статический, но внешний.
Не могу сейчас найти, но был даже публичный сервис, который предлагал всем желающим ssh-нуться к ним и показывал проблемы и уязвимости вашего клиентсвкого конфига. Они как раз статистику собирали.
klirichek
21.06.2018 15:16А там им воспользоваться зачем? Залогиниться на локальный (для удалённого сервера) узел?
Лично мне хватает пары записей в конфиге:
host server1 User vasya Port 9222 host server2 User petya ProxyCommand ssh server1 -W %h:%p
Открытый ключ прописан на обоих серверах в authorized_keys.
И всё, на server2 заходится в один клик, без всяких манипуляций с агентами и т.д.
Это что, способ 4?vmspike
22.06.2018 11:24+1Кто скажет насколько безопасен данный способ с
ProxyCommand
, если на промежуточномserver1
сидит злодей под рутом и жаждет расширить сферу своего влияния на тебя.
Как я понимаю это получше, чем агента прокидывать, но безопасно ли проделывать такое с не доверенными хостами?klirichek
22.06.2018 12:38Дык приватный ключ никуда не пробрасывается.
ProxyCommand логинит на первый сервер по публичному ключу. И через него на второй — по тому же публичному ключу. Приватный остаётся у себя и никуда не пробрасывается.
Подозреваю, что это ничуть не более опасно, чем просто дать злодею напрямую прослушивать трафик (который зашифрован).
mr_tron
21.06.2018 14:10> Как еще можно улучшить безопасность
Отказаться уже наконец в 2018-м году от доступа по ключам и перейти к доступу по сертификатам. Например github.com/gravitational/teleport (ну или убера есть похожая штука)
А ещё приватные ключи можно хранить например на аппаратных кошельках криптовалют (например ledger nano s это умеет).ggo
22.06.2018 10:31Чем данный подход принципиально отличается от традиционного?
У сертификатов тоже вроде приватная часть есть.mr_tron
22.06.2018 12:35Принципиально он отличается тем что
1. на все машины не надо разливать ключи тех кому туда надо ходить
2. у ключей появляется срок действия и понятная логика их обновления
neb0t
21.06.2018 14:15Можно еще добавить о ciphers-ах, MACs, KexAlgorithms и о важности отключения древних.
MMik
21.06.2018 14:23Опять двадцать пять! Очередная статья об одном и том же.
Расскажите, как вы будете управлять ключами десяти тысяч сотрудников на трёх сотнях тысяч серверов. Доступы у сотрудников добавляются и удаляются сотнями в день, сервера добавляются и исчезают тоже сотнями в день. Подсказка:Читайте документацию, в ней все написано, как бы это пошло не звучало.
Вот это будет интересный пост. Заранее спасибо.
gnomeby
21.06.2018 14:42Тоже интересно. Любое увольнение должно инвалидировать ключ, а как это сделать — непонятно.
RomanKu Автор
21.06.2018 15:04в рамках данной статьи удалить его из $HOME/.ssh/authorized_keys
А в энтерпрайзе другие подходы
Amet13
22.06.2018 20:46Использовать Hashicorp Vault в связке со LDAP например, для генерации SSH OTP. В таком случае сертификаты вообще не нужны. Правда на хостах должен стоять ssh-helper, так что решение не для всех подойдет.
acmnu
21.06.2018 14:54Я для этого писал кучу скриптов и хакал ssh_agent :) Но у меня были тепличные условия: 50 сотрудников делились на примерно 5 груп и коннектились на сервера клиентов (~1k машин) на 2-3 аккаунта.
RomanKu Автор
21.06.2018 15:15асскажите, как вы будете управлять ключами десяти тысяч сотрудников на трёх сотнях тысяч серверов.
PAM?
На хабре была статья про использование промежуточных SSH серверов, через них уже ппоизводился выход на целевые сервера. Опять же, если рассматривать тысячи серверов, то это уже enterprise решения и выходит за рамки этой статьи
У меня опыт был использования PAM + ldap для управления пользователями
inkvizitor68sl
21.06.2018 14:46Пф. Тоже мне белые пятна.
Белое пятно — что root на сервере, куда вы пришли с форвардом ключа, может реюзнуть сокет и фактически воспользоваться ключем, пока открыта ваша сессия.RomanKu Автор
21.06.2018 14:59скомпрометированный root на машине без selinux и прочих защит (да и с ними тоже) — это компрометация всей системы, «ЭТО ФИАСКО, БРАТАН» (с)
Дальше о безопасности говорить уже смысла нетinkvizitor68sl
21.06.2018 15:01Бла-бла-бла.
Вы не подумали о ситуации, когда вполне себе штатный рут захочет воспользоваться ключем? Или когда машина вовсе чужая и вы не можете быть в курсе, что она похакана?
Форвард ключа штука опасная, а все её советуют включить по дефолту.RomanKu Автор
21.06.2018 15:16Какие варианты?
Я могу предложить только временные ключи на время сессииinkvizitor68sl
21.06.2018 15:28Как минимум, не включать форвард на все хосты, а заходить с форвардом только по необходимости.
NoRegrets
22.06.2018 13:22+1Взлом одной из машин, через которую вы прошли, означает компрометацию всех серверов в которые вы могли зайти с помощью своего агента. Действительно, если вы пробросили агента, дальше о безопасности говорить нет смысла.
Есть смысл говорить о том, стоит ли вообще пробрасывать агента куда-либо. Я считаю, что не стоит этого делать, от слова совсем.
ibKpoxa
21.06.2018 17:56А еще ключи можно прописывать в файл authorized_keys2, а не только в authorized_keys, ключи будут читаться из обоих файлов.
Если вы накладываете на файл authorized_keys immatable атрибут, что можно сделать только рутом, то не забывайте делать authorized_keys2 и его тоже защищать.RomanKu Автор
21.06.2018 22:50authorized_keys2 ещё в 2001 году был с пометкой deprecated, но проверю на своих системах его поддержку
ibKpoxa
22.06.2018 11:33+1Debian 9, man sshd
AUTHORIZED_KEYS FILE FORMAT
AuthorizedKeysFile specifies the files containing public keys for public key authentication; if this option is not specified, the default is ~/.ssh/authorized_keys and ~/.ssh/authorized_keys2.
arheops
22.06.2018 00:33А что мешает сделать первую сесиию на удаленный хост с пробросом удаленного порта 2го хоста на локальный сервер и потом просто зайти на этот(локальный, проброшенный) порт?
Никаких тебе агентов и у рута на удаленной машине нет доступа к агенту.RomanKu Автор
22.06.2018 00:53Технически не мешает и подобное я часто использую (делал внутренний доклад про сценарии использования, однако, на Хабре уже есть статьи на эту тему), но что, если надо зайти на сервер и выполнить
$ git pull
Или
scp
илиrsync
да и много чего ещё (на самом деле нет).
Можно использовать парольную аутентификацию или извращаться, а можно воспользоваться агентом.
Часто встречаю ситуацию когда на серверах разработки код стянут по https, ты вызываешь
git pull
А у тебя
GIT
спрашивает пароль какого-то разраба. Почему нельзя было склонить по SSH? Секурность? Нет — глу… незнание
MikalaiR
22.06.2018 01:47По поводу компрометации сокета ssh-agent:
Например, под Windows, есть агент, встроенный в KeePass (в виде плагина) и он выдает запрос на каждое использование ключей.
Также есть такие интересные штуки как Krypton, про которые почему-то в статье не упомянули. (Вкратце: ключи генерируются и хранятся на мобильном устройстве в защищенном хранилище и никогда оттуда не извлекаются, это просто невозможно без прав рута и прочих хаков)
lasc
22.06.2018 05:23А для линя\мака можно самому такой скрипт написать или взять готовый github.com/theseal/ssh-askpass
9660
22.06.2018 07:06На локальной машине можно найти файл $HOME/.ssh/config
А если бы мне давали рубль за каждый случай «я забыл пароль/адрес/параметр» я жил бы недалече от вас.
$HOME/.ssh/config и ключи это удобно но это сильно провоцирует забывчивость.
ppl2scripts
22.06.2018 08:27Ещё в конфигурационном файле вы можете использовать маски, чтобы не писать много раз одно и то-же:
ppl2scripts
22.06.2018 10:03Например:
Host vagrant HostName 127.0.0.1 User vagrant IdentityFile ~/.vagrant/machines/default/virtualbox/private_key Port 2222 Host * !bitbucket.org !10.2.* !10.1.* User ubuntu IdentityFile ~/.ssh/id_rsa Host * StrictHostKeyChecking=no UserKnownHostsFile=/dev/null
RomanKu Автор
22.06.2018 21:39Думал расписать более подробно конфиг, пожалуйста, стоило это сделать. Надо будет дополнить.
vmspike
22.06.2018 11:09В критически важных CI/CD использование комбинации
ssh-keyscan example.com >> ~/.ssh/known_hosts
Во время инициализации совместно с
StrictHostKeyChecking=yes
Позволит надежно защититься от MITM-атакЭто же совсем не так,
man ssh-keyscan
говорит:
SECURITY If an ssh_known_hosts file is constructed using ssh-keyscan without verifying the keys, users will be vulnerable to man in the middle attacks. On the other hand, if the security model allows such a risk, ssh-keyscan can help in the detection of tampered keyfiles or man in the middle attacks which have begun after the ssh_known_hosts file was created.
Т.е. без верификации ключа ты уязвим к MITM.
Или я что-то недопонимаю в вашем высказывании?RomanKu Автор
22.06.2018 11:33Или я что-то недопонимаю в вашем высказывании?
Ключевое слово было инициализации, т.е. когда вы точно уверены, что MITM атаки нет, а если уверенности нет, то можно использовать отдельное хранилище или пример с https, что я приводил выше.
Envek
22.06.2018 12:19А ещё SSH-ключи можно записывать на хардварные носители, чтобы их не держать на машинах: https://evilmartians.com/chronicles/stick-with-security-yubikey-ssh-gnupg-macos
Blck-1
22.06.2018 15:45Спасибо за статью. Приватные ключи еще можно держать в менеджерах паролей. Например KeepassXC с версии 2.3.2 прикидывается ssh-agent на всех платформах (правда на Mac-ах требует использования упомянутого выше ssh-askpass
RomanKu Автор
22.06.2018 21:42На маке вместо KeepassXC решил использовать MacPass — интерфейс нативный и функционал неплох.
BSergius
23.06.2018 07:54Читаешь такой статью, думаешь как интересно, а потом видишь слово «дешифрация» в тематической статье и охватывает настоящий фейспалм.
RedComrade
а где комментарий о том что в заголовке опечатка?
RomanKu Автор
Он достается вам ;-)
Спасибо за замечание, исправили.