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

Автор: | 12/01/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-ключами пользователя и их паролями, что бы не вводить пароль к ключу каждый раз при использовании.

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

Выполняем:

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;

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

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

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

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

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

Примеры

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

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

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

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]-----+

Опции тут:

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

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

ssh-keygen -y -f /home/setevoy/.ssh/test-key
Enter passphrase:
ssh-rsa AAAAB***gud2vedL/V Testing key
Смена пароля

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

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.
ssh-copy-id — копирование ключа на сервер

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

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

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

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

ssh-copy-id -i /home/setevoy/.ssh/test-key setevoy@rtfm.co.ua
/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
setevoy@rtfm.co.ua's password:
Number of key(s) added: 1
Now try logging into the machine, with:   "ssh 'setevoy@rtfm.co.ua'"
and check to make sure that only the key(s) you wanted were added.

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

ssh setevoy@rtfm.co.ua -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:~$

ssh-add

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

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

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

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

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
...

Could not open a connection to your authentication agent

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

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

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

test -z $SSH_AGENT_PID; echo $?

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

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

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

killall ssh-agent

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

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

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

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

test -z $SSH_AGENT_PID; echo $?
1

И ssh-add:

ssh-add -l
The agent has no identities.

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

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

Выполняем:

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

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

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

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

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

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

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

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

ssh-add -D
All identities removed.

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

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

head -1 .ssh/config
AddKeysToAgent yes

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

ssh-add -l
The agent has no identities.

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

ssh -i .ssh/test-key setevoy@rtfm.co.ua
Enter passphrase for key '.ssh/test-key':
...
setevoy@rtfm-do-production:~$

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

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

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

ssh -i .ssh/test-key setevoy@rtfm.co.ua
...
setevoy@rtfm-do-production:~$

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

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

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

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

~/.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.

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

mkdir -p .config/systemd/user/

И создаём файл ~/.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:

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

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

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

killall ssh-agent

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

echo $XDG_RUNTIME_DIR
/run/user/1000

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

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

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

systemctl --user start ssh-agent

Проверяем:

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/user@1000.service/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;

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

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

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

ssh-add -l
The agent has no identities.

Работает.

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

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.

~/.xinitrc

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

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

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

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



Also published on Medium.