SSH: RSA-ключи и ssh-agent – управление SSH-ключами и их паролями

Автор: | 01/12/2019

По ходу настройки keyring для Nextcloud-клиента (см. Linux: Nextcloud клиент, qtkeychain и ошибка «The name org.freedesktop.secrets was not provided by any .service files») – решил навести порядок в своих SSH-ключах, которых много, и аутентификация иногда преврашается в достаточно геморройный процесс.

В целом, для упрощения работы можно использовать системное хранилище секретов – gnome-keyring, либо KeeyPassXC, про них в другой раз.

В этом посте посмотрим примеры работы с ssh-agent и то, как можно хранить и управлять запароленными RSA-ключами без таких бекендов.

Примеры выполняются на Arch Linux (и, местами, для проверки – на Manjaro Linux с Budgie DE).

ssh-agent

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

Запуск агента

Выполняем:

[simterm]

$ ssh-agent
SSH_AUTH_SOCK=/tmp/ssh-dMDE5mED77tM/agent.436347; export SSH_AUTH_SOCK;
SSH_AGENT_PID=436348; export SSH_AGENT_PID;
echo Agent pid 436348;

[/simterm]

Для работы клиентов важны переменные, которые задаются агентом:

  • SSH_AGENT_PID: с PID процесса с ssh-agent, которая будет использоваться при, например, ssh-agent -k для остановки агента
  • SSH_AUTH_SOCK: указывает на путь к unix-сокету, который будут использовать другие процессы для коммуникации с ssh-agent

Что бы запустить агента без вывода всей этой информации – используем:

[simterm]

$ eval $(ssh-agent) > /dev/null

[/simterm]

Вариантов запуска много, рассмотрим их в конце, в Запуск ssh-agent и несколько консолей.

Примеры

Рассмотрим базовые примеры использования ssh-agent.

Создание ключа

Генерируем ключ:

[simterm]

$ ssh-keygen -t rsa -b 2048 -f /home/setevoy/.ssh/test-key -C "Testing key" -P pass 
Generating public/private rsa key pair. 
Your identification has been saved in /home/setevoy/.ssh/test-key. 
Your public key has been saved in /home/setevoy/.ssh/test-key.pub. 
The key fingerprint is: 
SHA256:pTyrGtk1hnNHB6b8ilp5jRe1+K4KrLHg50yUGilApLY Testing key 
The key's randomart image is: 
+---[RSA 2048]----+ 
|.o        o      | 
|o      . o .     | 
|o.      o o o    | 
|o .. . o = + .   | 
|.Eo o o S = .    | 
| . + + B O o     | 
|  o = B = o .    | 
| . +.B + . .     | 
|  .oB.. .....    | 
+----[SHA256]-----+

[/simterm]

Опции тут:

  • -t: тип RSA
  • -b: длина ключа в битах (по-умолчанию 3072 для RSA)
  • -f: путь к файлу ключа (по-умолчанию будет ~/.ssh/id_rsa)
  • -C: комментарий к ключу (по-умолчанию будет username@hostname)
  • -P: пароль для доступа к ключу
Проверка пароля

Что бы проверить пароль ключа – используем -f для указания ключа, и -y для вывода информации о ключе, поэтому запросит пароль:

[simterm]

$ ssh-keygen -y -f /home/setevoy/.ssh/test-key
Enter passphrase:
ssh-rsa AAAAB***gud2vedL/V Testing key

[/simterm]

Смена пароля

Что бы изменить пароль, заня старый:

[simterm]

$ ssh-keygen -p -f ~/.ssh/test-key
Enter old passphrase:
Key has comment 'Testing Key'
Enter new passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved with the new passphrase.

[/simterm]

ssh-copy-id – копирование ключа на сервер

Скопировать ключ можно вручную, получив его публичную часть:

[simterm]

$ cat .ssh/test-key.pub
ssh-rsa AAAAB***gud2vedL/V Testing key

[/simterm]

И скопировав содержимое в файл ~/.ssh/authorized_keys на удалённом хосте.

Другой вариант – используя утилиту ssh-copy-id, которая, по сути, выполнит всё тоже самое, но при этом ещё и проверит права доступа на каталоги и файлы (самая частая проблема при использовании SSH-ключей для аутентификации):

[simterm]

$ ssh-copy-id -i /home/setevoy/.ssh/test-key [email protected]
/usr/bin/ssh-copy-id: INFO: Source of key(s) to be installed: "/home/setevoy/.ssh/test-key.pub"
/usr/bin/ssh-copy-id: INFO: attempting to log in with the new key(s), to filter out any that are already installed
/usr/bin/ssh-copy-id: INFO: 1 key(s) remain to be installed -- if you are prompted now it is to install the new keys
[email protected]'s password:

Number of key(s) added: 1

Now try logging into the machine, with:   "ssh '[email protected]'"
and check to make sure that only the key(s) you wanted were added.

[/simterm]

И пробуем подключиться, используя этот ключ:

[simterm]

$ ssh [email protected] -i .ssh/test-key
Enter passphrase for key '.ssh/test-key':
Linux rtfm-do-production 4.9.0-8-amd64 #1 SMP Debian 4.9.144-3.1 (2019-02-19) x86_64
...
setevoy@rtfm-do-production:~$

[/simterm]

ssh-add

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

При каждом обращении к серверу – нам придётся вводить пароль заново – Enter passphrase for key ‘/home/setevoy/.ssh/test-key’.

Что бы избежать этого – добавим ключ в ssh-agent, используя ssh-add.

Проверим, что агент запущен:

[simterm]

$ ps aux | grep ssh-agent
setevoy     1322  0.0  0.0   5796   456 ?        Ss   Nov30   0:00 ssh-agent -s
setevoy     1324  0.0  0.0   5796  2160 ?        Ss   Nov30   0:00 ssh-agent -s
...

[/simterm]

Could not open a connection to your authentication agent

Самая частая ошибка при  использовании агента – когда к нему невозможно подключиться, и ssh-add сообщает:

[simterm]

$ ssh-add
Could not open a connection to your authentication agent.

[/simterm]

Первым делом проверяем SSH_AGENT_PID (или $SSH_AUTH_SOCK, т.к. запросы от ssh-add выполняются через сокет, заданный в этой переменной):

[simterm]

$ test -z $SSH_AGENT_PID; echo $?
0

[/simterm]

Переменная пустая.

ssh-agent был запущен в другом терминале (об этом тоже поговорим ниже), поэтому перезапустим его.

Для “чистоты эксперимента” – убиваем запущеные инстансы агента:

[simterm]

$ killall ssh-agent

[/simterm]

И запускаем заново:

[simterm]

$ eval $(ssh-agent -s)
Agent pid 452333

[/simterm]

-s используем для создания переменных, т.к. не все будут запускать из bash, а eval – что бы сразу выполнить експорт переменных (export SSH_AUTH_SOCK) из output самого ssh-agent.

Проверяем ещё раз:

[simterm]

$ test -z $SSH_AGENT_PID; echo $?
1

[/simterm]

И ssh-add:

[simterm]

$ ssh-add -l
The agent has no identities.

[/simterm]

Тут всё готово.

Добавление ключа

Выполняем:

[simterm]

$ ssh-add /home/setevoy/.ssh/test-key
Enter passphrase for /home/setevoy/.ssh/test-key:
Identity added: /home/setevoy/.ssh/test-key (Testing key)

[/simterm]

Проверка ключей в агенте

Проверяем загруженные в агент ключи, используя -l:

[simterm]

$ ssh-add -l
2048 SHA256:pTyrGtk1hnNHB6b8ilp5jRe1+K4KrLHg50yUGilApLY Testing key (RSA)

[/simterm]

Удаление ключа

Используем -d для удаления одного ключа:

[simterm]

$ ssh-add -d .ssh/test-key
Identity removed: .ssh/test-key (Testing key)

[/simterm]

Или -D для удаления всех ключей:

[simterm]

$ ssh-add -D
All identities removed.

[/simterm]

Автоматическое добавление в ssh-agent

Что бы sshgit, например) всегда добавляли ключ в ssh-agent без явного ручного вызова ssh-add – добавьте AddKeysToAgent в ~/.ssh/config, указав yes, confirm или ask (см. SSH_ASKPASS):

[simterm]

$ head -1 .ssh/config
AddKeysToAgent yes

[/simterm]

Проверяем – сейчас ключей в агенте нет:

[simterm]

$ ssh-add -l
The agent has no identities.

[/simterm]

Выполняем подключение, вводим пароль ключа:

[simterm]

$ ssh -i .ssh/test-key [email protected]
Enter passphrase for key '.ssh/test-key':
...
setevoy@rtfm-do-production:~$

[/simterm]

Отключаемся, проверяем ключи в агенте:

[simterm]

setevoy@rtfm-do-production:~$ logout
Connection to rtfm.co.ua closed.
$ ssh-add -l
2048 SHA256:pTyrGtk1hnNHB6b8ilp5jRe1+K4KrLHg50yUGilApLY Testing key (RSA)

[/simterm]

И теперь при повторном подключении – ключ уже будет взят из агента, и пароль вводить не потребуется:

[simterm]

$ ssh -i .ssh/test-key [email protected]
...
setevoy@rtfm-do-production:~$

[/simterm]

Запуск ssh-agent и несколько консолей

Одна из основных проблем, это то, что при запуске новой вкладки в Konsole и инициализации новой bash-сесии – там не будет переменной $SSH_AUTH_SOCK, и ssh-клиент не сможет получить доступ к ключу.

Например, при вызове ssh-add в новом терминале получим уже упомянутую ошибку “Could not open a connection to your authentication agent“:

[simterm]

$ ssh-add -l
Could not open a connection to your authentication agent.

[/simterm]

~/.bashrc

Вариантов много, например самый простой – добавить в ~/.bashrc:

if [ -z "$SSH_AUTH_SOCK" ] ; then
  eval `ssh-agent -s`
  ssh-add /home/setevoy/.ssh/test-key
fi

Но тогда для каждой сессии bash будет запускаться новый агент.

Ещё одним вариантом запуска через .bashrc может быть такой:

ssh-add -l &>/dev/null
if [ "$?" == 2 ]; then
  test -r ~/.ssh-agent-env && \
    eval "$(<~/.ssh-agent-env)" >/dev/null

  ssh-add -l &>/dev/null
  if [ "$?" == 2 ]; then
    (umask 066; ssh-agent > ~/.ssh-agent-env)
    eval "$(<~/.ssh-agent-env)" >/dev/null
    ssh-add /home/setevoy/.ssh/test-key
  fi
fi

Тут выполняется (см. коды ответа ssh-agent в документации):

  1. пытается выполнить ssh-add -l, перенаправляет весь вывод в /dev/null
  2. проверяет код выхода предыдущей комнады
    1. если код == 2 (ошибка подключения к агенту)
      1. проверяет доступен ли на чтение ~/.ssh-agent-env, считывает его содержимое, и передаёт его в bash
      2. повторяет ssh-add -l
      3. если код по-прежнему 2:
        1. создаёт файл ~/.ssh-agent-env с правами 660 (чтение и запись только владельцу)
        2. запускает ssh-agent, и перенаправляет его вывод в файл .ssh-agent-env
        3. считывает содержимое .ssh-agent-env, и передаёт его в bash
        4. выполняет ssh-add /home/setevoy/.ssh/test-key

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

systemd

Ещё один вариант – создать systemd unit-файл и запускать ssh-agent как сервис, см. Arch Wiki.

Добавляем каталог, если не создан:

[simterm]

$ mkdir -p .config/systemd/user/

[/simterm]

И создаём файл ~/.config/systemd/user/ssh-agent.service:

[Unit]
Description=SSH key agent

[Service]
Type=simple
Environment=SSH_AUTH_SOCK=%t/ssh-agent.socket
ExecStart=/usr/bin/ssh-agent -D -a $SSH_AUTH_SOCK

[Install]
WantedBy=default.target

Далее, Wiki говорит про файл ~/.pam_environment, но у меня Openbox и я переменные обычно задаю в .config/openbox/autostart:

[simterm]

$ head -2 .config/openbox/autostart
# ssh-agent.service
SSH_AUTH_SOCK="${XDG_RUNTIME_DIR}/ssh-agent.socket"

[/simterm]

Кстати, вспомнил про такую вещь, как BASH: переменные — передача значений по-умолчанию ${var:-defaultvalue}, замена значений — ${var:+alternatevalue} и сообщений — ${var:?message}

Останавливаем запущенные инстансы агента:

[simterm]

$ killall ssh-agent

[/simterm]

Проверяем значение XDG_RUNTIME_DIR:

[simterm]

$ echo $XDG_RUNTIME_DIR
/run/user/1000

[/simterm]

Сейчас задаём переменную $SSH_AUTH_SOCK вручную:

[simterm]

$ SSH_AUTH_SOCK="${XDG_RUNTIME_DIR}/ssh-agent.socket"

[/simterm]

И запускам агент через systemctl --user:

[simterm]

$ systemctl --user start ssh-agent

[/simterm]

Проверяем:

[simterm]

$ systemctl --user status ssh-agent
● ssh-agent.service - SSH key agent
  Loaded: loaded (/home/setevoy/.config/systemd/user/ssh-agent.service; disabled; vendor preset: enabled)
  Active: active (running) since Sun 2019-12-01 09:15:18 EET; 2s ago 
Main PID: 497687 (ssh-agent) 
  CGroup: /user.slice/user-1000.slice/[email protected]/ssh-agent.service 
          └─497687 /usr/bin/ssh-agent -D -a /run/user/1000/ssh-agent.socket 

Dec 01 09:15:18 setevoy-arch-pc systemd[670]: Started SSH key agent. 
Dec 01 09:15:19 setevoy-arch-pc ssh-agent[497687]: SSH_AUTH_SOCK=/run/user/1000/ssh-agent.socket; export SSH_AUTH_SOCK; 
Dec 01 09:15:19 setevoy-arch-pc ssh-agent[497687]: echo Agent pid 497687;

[/simterm]

Переменную сокета:

[simterm]

$ echo $SSH_AUTH_SOCK
/run/user/1000/ssh-agent.socket

[/simterm]

И пробуем ssh-add:

[simterm]

$ ssh-add -l
The agent has no identities.

[/simterm]

Работает.

Можно добавить в автозапуск:

[simterm]

$ systemctl --user enable ssh-agent
Created symlink /home/setevoy/.config/systemd/user/default.target.wants/ssh-agent.service → /home/setevoy/.config/systemd/user/ssh-agent.service.

[/simterm]

~/.xinitrc

Ещё один вариант – запускать из ~/.xinitrc.

В таком случае, при вызове startx (как у меня, когда login manager нет, и X.Org запускается вручную, через вызов startx в консоли) будет запущен агент, а затем – Openbox, см. документацию:

[simterm]

$ cat ~/.xinitrc
eval $(ssh-agent) &
exec openbox-session

[/simterm]

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