SSH – Тонкая настройка клиента в Linux: config файл и ssh-agent

SSH – Тонкая настройка клиента в Linux: config файл и ssh-agent

Приветствую!

Продолжаем тему использования SSH в Linux🐧. Сегодня поговорим о тонкой настройке клиента с помощью файла конфигурации ~/.ssh/config📄, а также про автоматический импорт ключей в ssh-agent😎 без ввода паролей.

Подписывайтесь на наш телеграм @r4ven_me📱, чтобы не пропустить новые публикации на сайте😉. А если есть вопросы или желание пообщаться по тематике — заглядывайте в Вороний чат @r4ven_me_chat🧐.

Предисловие

Данная заметка является 2-й из небольшого цикла про работу с SSH в Linux. Если вы мало знакомы с данным ПО, то рекомендую ознакомиться с моей вводной частью: 🔐SSH – Безопасное подключение к удалённым хостам: введение.

Все примеры из заметки выполнялись в среде дистрибутива Linux Mint 22 Cinnamon. Но вы без труда сможете экстраполировать их и на другие популярные системы Linux😌.

И так, вводные данные: у меня есть 3 сервера со следующими имена, IP адресами и SSHD портами:

Имя сервераIP адресПорт SSHD
database192.168.122.10522
backup192.168.122.702222
monitoring192.168.122.1123333

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

Использование ключей SSH

Рекомендуемым способом является использование SSH ключей🔑. Давайте создадим такой и добавим его на сервер database:

# сперва генерируем без шифрования, просто нажмите дважды Enter
ssh-keygen -t ed25519

# копируем новый ключ на сервер database, потребуется ввод пароля
ssh-copy-id -i ~/.ssh/id_ed25519 ivan@192.168.122.105

Теперь при подключении к серверу (⚠️если разрешён доступ по ключу) нам не придется вводить пароль от учетной записи:

ssh ivan@192.168.122.105

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

По умолчанию ssh пытается пройти аутентификацию на сервере перебирая ключи со стандартными имена (id_<алгоритм>) в директории ~/.ssh. В случае, если ключ имеет нестандартное имя или расположение, он будет проигнорирован. Но его можно указать явно с помощью параметра -i. Например:

ssh ivan@192.168.122.105 -i /some/path/database.key

Хорошей практикой считается использование разных ключей для разных серверов или проектов☝️. При таком подходе, в случае компрометации одного из ключей, вам не придется удалять/заменять его на всех серверах. Поэтому давайте сгенерируем по ключу для оставшихся 2-х серверов и настроим подключение по ним:

ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519_backup

ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519_monitoring

ssh-copy-id -i ~/.ssh/id_ed25519_backup -p 2222 ivan@192.168.122.70

ssh-copy-id -i ~/.ssh/id_ed25519_monitoring -p 3333 ivan@192.168.122.112

Проверяем подключение:

ssh ivan@192.168.122.70 -i ~/.ssh/id_ed25519_backup -p 2222

ssh ivan@192.168.122.112 -i ~/.ssh/id_ed25519_monitoring -p 3333

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

Файл ~/.ssh/config

Данный конфиг файл позволяет задать индивидуальные параметры SSH для каждого отдельного хоста🖥️.

Создаем новый конфиг с помощью любого редактора кода, например vim или neovim:

nvim ~/.ssh/config

И наполняем его примерно таким содержимым:

Host database
    HostName 192.168.122.105
    User ivan
    Port 22
    IdentityFile ~/.ssh/id_ed25519

Host backup
    HostName 192.168.122.70
    User ivan
    Port 2222
    IdentityFile ~/.ssh/id_ed25519_backup

Host monitoring
    HostName 192.168.122.112
    User ivan
    Port 3333
    IdentityFile ~/.ssh/id_ed25519_monitoring
    Compression yes

Host github.com
    HostName github.com
    User git
    Port 22
    IdentityFile ~/.ssh/id_ed25519_github

# параметры по умолчанию для всех остальных хостов
Host *
    #User root
    Port 22
    IdentityFile ~/.ssh/id_ed25519
    ForwardAgent yes
    ForwardX11 yes
    ForwardX11Trusted yes
    #Compression yes
    #ServerAliveInterval 60
  • ⚠️Символом # определяются комментарии, которые игнорируются командами SSH.
  • 💡Полный список доступных параметров клиента смотрите на man странице: man ssh_config или тут.

Как видно, каждый блок отвечает за настройки отдельного хоста, в которых мы явно определили: короткий псевдоним, адрес (IP или DNS), порт подключения, имя пользователя и путь к файлу ключа!

💡Обратите внимание, что в файле можно описать параметры подключения и для удаленных Git сервисов, таких как GitHub.

Отдельно стоит отметить последний блок: Host * — параметры из него будут применены для всех хостов, которые явно не определены в конфиге. Размещать этот блок рекомендуется в конце файла🔚.

Сохраняем внесенные изменения💾 и возвращаемся в терминал🧑‍💻. Теперь подключаться к хостам можно просто по их псевдонимам. Все параметры подтянутся автоматически🤖.

Пример:

ssh database

Согласитесь, совсем другое дело😉.

💡Список хостов можно “протабать”. Просто введите часть имени хоста в команде подключения и нажмите клавишу Tab.

Конфиг распространяется на все утилиты пакета OpenSSH. Пример:

hostname > /tmp/test.txt

scp /tmp/test.txt database:/tmp

ssh database cat /tmp/test.txt

К ним же относится и sftp:

sftp database

К слову, чаще всего другие приложения, в т.ч. и графические, также подхватывают настройки из config файла SSH. Например, файловый менеджер Nemo из Linux Mint. Если ввести в строке пути:

sftp://database

Мы попадем в корень файловой системы удаленного сервера:

Удобно? На мой взгляд да!

У вас может быть несколько конфиг файлов клиента SSH. Чтобы явно указать, какой нужно использовать при подключении добавьте к команде параметр -F <путь_до_файла> .

Этот параметр можно использовать, если вам вдруг нужно игнорировать текущий конфиг файл:

ssh -F /dev/null user@example.com

В этом случае будут применены только общесистемные параметры по умолчанию, которые заданы в файле /etc/ssh/ssh_config:

cat /etc/ssh/ssh_config

Так, с конфигом вроде разобрались.

Утилита ssh-agent

Уверен, что среди читателей найдутся пользователи, предпочитающие не хранить файлы ключей в открытом виде. Т.е. использовать парольную фразу для расшифровки ключа перед его использованием. Что равносильно вводу пароля при каждом подключении к удаленным хостам — лично для меня это неудобно🤷‍♂️. Но безопасное и почти удобное решение есть.

Рад представить вам агента Смита SSH — ssh-agent. Данная утилита идет в составе пакета openssh-client. При запуске, она работает в фоновом режиме и может хранить приватные ключи в памяти в расшифрованном виде. Принцип работы:

  1. агент запускается один раз, пример: eval $(ssh-agent);
  2. в него добавляются ключи, пример: ssh-add ~/.ssh/id_ed25519;
  3. агент кэширует ключ в памяти;
  4. если ключ защищен парольной фразой, то перед кэшированием он запрашивается один раз;
  5. SSH-клиент при подключении к хостам обращается к агенту не требуя ввода пароля.

Работа с агентом происходит через сокет, используя переменные окружения SSH_AUTH_SOCK и SSH_AGENT_PID. Ключи остаются в памяти до явного удаления или перезагрузки ОС.

Ключевое тут “до перезагрузки ОС”, т.е. пароль от защищенных ключей все равно необходимо, хоть и единожды за сеанс, но вводить😥.

Но! Если вы пользователь Linux Desktop — выход есть. И их больше, чем один:

  • 1) Механизм Keyring (связка ключей) с компонентом SSH: актуален для пользователей дистрибутивов с рабочими столами Gnome/KDE и производных (в т.ч. Cinnamon🍃);
  • 2) Bash скрипт (ssh-agent + Keyring (secret-tool) + expect): универсальный вариант для любых рабочих столов😉.

⚠️Далее речь пойдет об автоматизации работы ssh-agent в GUI системах. Добиться аналогичного функционала в системах без GUI несколько сложнее, возможно сделаю отдельную заметку по этой теме.

В случае 1-го варианта вам практически ничего не нужно делать. Все уже настроено за вас😨. Я лишь расскажу подробности работы механизма. Если это не ваш метод и вы предпочитаете больше контроля, то рассмотрите мою реализацию 2-го способа.

А пока мы поговорим про…

Автоматическое добавление ключей (в т.ч. шифрованных) в ssh-agent

В современных десктопных дистрибутивах Linux таких, как Linux Mint, Ubuntu, Manjaro и т.д. из коробки работает сервис, так называемой “связки ключей”, он же Keyring.

Keyring — это механизм безопасного хранения чувствительных данных (паролей, ключей, токенов). Внутри он представляет собой зашифрованное хранилище (обычно в ~/.local/share/keyrings/), доступ к которому открывается после аутентификации пользователя (при входе на рабочий стол). Приложения, такие как барузер, почтовый клиент, файловый менеджер и др. обращаются к нему через стандартизированный API для сохранения и извлечения секретов✍️.

Существует 2 популярные реализации механизма связки ключей🔑:

Графическое окружениеБэкендФронтенд
Gnomegnome-keyringSeahorse
KDEkwalletKWalletManager

Сегодня мы рассмотрим работу gnome-keyring, т.к. он используется по умолчанию в Linux Mint.

Способ №1: gnome-keyring с компонентом SSH

Как это связано с ssh-agent? Напрямую: в Linux Mint программа gnome-keyring может выступать надстройкой над ssh-agent и управлять им. Такая связка сохраняет SSH-ключи из ~/.ssh в защищённом хранилище, доступ к которому открывается при входе в систему — удобно и безопасно.

Покажу, как это работает. Но для начала необходимо убедиться, что в вашей системе запущен и работает сервис Keyring:

echo $SSH_AUTH_SOCK

pgrep -af ssh-agent

Если команды выше имеют подобный вывод:

Значит у вас запущен ssh-agent, которым управляет gnome-keyring. Чаще всего сервис gnome-keyring с компонентом SSH запускает специальный .desktop файл: /etc/xdg/autostart/gnome-keyring-ssh.desktop при старте графического сеанса пользователя.

Ниже пример содержимого такого файла:

[Desktop Entry]
Type=Application
Name=SSH Key Agent
Comment=GNOME Keyring: SSH Agent
Exec=/usr/bin/gnome-keyring-daemon --start --components=ssh
OnlyShowIn=GNOME;Unity;MATE;
X-GNOME-Autostart-Phase=PreDisplayServer
X-GNOME-AutoRestart=false
X-GNOME-Autostart-Notify=true
X-Ubuntu-Gettext-Domain=gnome-keyring

Настройки автозапуска gnome-keyring можно также увидеть в разделе автозагрузки приложений:

Если вы храните ключи в директории ~/.ssh и в вашей системе работает gnome-keyring, то скорее всего ваши ключи уже автоматически добавлены в ssh-agent. Как проверить? В предустановленном приложении Seahorse, оно же “Пароли и ключи”:

⚠️Согласно документации для корректной работы gnome-keyring важно, чтобы в каталоге ~/.ssh/ публичные ключи имели то же имя, что и соответствующие приватные, но с расширением .pub.

Наличие ключей в агенте можно проверить консольной командой:

ssh-add -l

Видим, что агент автоматически обнаруживает ключи в ~/.ssh и добавляет к себе. Это справедливо и для ключей защищенных парольной фразой, ☝️если они были сгенерированы при работающем gnome-keyring с компонентом SSH. Довольно смелая самодеятельность🤔.

В целях демонстрации давайте защитим паролем некоторые из наших ключей:

ssh-keygen -p -f ~/.ssh/id_ed25519

ssh-keygen -p -f ~/.ssh/id_ed25519_backup

💡Этой же командой можно этот пароль поменять, предварительно указав старый.

Теперь попробуем авторизоваться с их помощью на удаленных хостах:

ssh database hostname

ssh backup hostname

Видно, что запрос парольной фразы для расшифровки пароля не происходит, хотя мы в ssh-agent ничего не добавляли🪄. Убедиться в том, что ключ защищен паролем можно так:

ssh-keygen -y -f ~/.ssh/id_ed25519

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

Есть один нюанс, когда ssh-agent’ом управляет демон gnome-keyring: если ключи с парольной защитой генерировался в момент, когда gnome-keyring с компонентом ssh не был запущен или генерация происходила на другом сервере, то пароль от ключа в хранилище Keyring не попадет🤷‍♂️.

Для наглядности вышесказанного сгенерируем новый, защищенный паролем ключ на удаленном сервере, скопируем его на наш локальный хост и настроим по нему подключение:

ssh -t database ssh-keygen -t ed25519 -f ~/id_ed25519_test

scp database:./id_ed25519_test{,.pub} ~/.ssh/

ssh database cat ./id_ed25519_test.pub \>\> ./.ssh/authorized_keys

Теперь при запущенном gnome-keyring и аутентификации по новому ключу:

ssh -i ~/.ssh/id_ed25519_test database

У вас должно появиться ⚠️графическое окно с запросом парольной фразы. Именно графическое, не консольное. В этом окне будет возможность установить чекбокс для сохранения парольной фразы в связке ключей:

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

ssh -i ~/.ssh/id_ed25519_test database hostname

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

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

Способ №2: Bash скрипт

Я подготовил Bash скрипт ssh_agent.sh, который автоматизирует описанные действия из первого способа, но силами консольных утилит и языка оболочки Bash. Предварительно необходимо доустановить пару пакетов📦:

sudo apt install -y curl libsecret-tools expect
  • curl — утилита для взаимодействия с веб: понадобится для скачивания скрипта из GitHub;
  • libsecret-tools — пакет с необходимой для работы скрипта утилитой — secret-tool
  • expect — утилита для автоматизации интерактивного ввода: нужна для добавления в ssh-agent защищенных SSH ключей.

⚠️Если в вашей системе нет “связки ключей”, то можно установить тот же gnome-keyring:

sudo apt install -y gnome-keyring

Скачиваем скрипт в /usr/local/bin и делаем его исполняемым:

sudo curl -fL https://raw.githubusercontent.com/r4ven-me/bash/main/ssh_agent.sh \
    -o /usr/local/bin/ssh_agent

sudo chmod +x /usr/local/bin/ssh_agent
Клик на спойлер для просмотра кода ssh_agent.sh

#!/usr/bin/env bash

# Set system PATH
export PATH="/usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/usr/sbin:/sbin"

# Строгий режим выполнения скрипта
set -euo pipefail

# Общие переменные
IFS=$'\n\t'
SSH_ENV="$HOME/.ssh/environment"
UTILS=("ssh-agent" "ssh-add" "ssh-keygen" "expect" "secret-tool")

# Проверка наличия в системе необходимых утилит
for util in "${UTILS[@]}"; do 
    if ! which "$util" > /dev/null; then
        echo "$util not found or not installed"
        exit 1
    fi
done

# Функция добавления защищенных SSH ключей в агент
ssh_add() {
    local key="$1"
    local pass="$2"

    if ! expect << EOF
spawn ssh-add $key
expect "Enter passphrase for $key"
send -- "$pass\r"
expect {
    "Bad passphrase" {
        exit 1
    }
    "Identity added" {
        exit 0
    }
    eof
}
EOF
    then
        return 1
    fi
    return 0
}

# Основная логика загрузки SSH ключей
load_keys() {
    echo "Loading SSH keys..."

    local pass
    local keys
    readarray -t keys < <(find "$HOME/.ssh" -type f \( \
        -name 'id_rsa*' -o -name 'id_dsa*' -o \
        -name 'id_ecdsa*' -o -name 'id_ed25519*' \
        \) ! -name '*.pub')

    for key in "${keys[@]}"; do
        if ! ssh-keygen -y -P "" -f "$key" &> /dev/null; then
            if pass=$(secret-tool lookup unique ssh-store:"${key}"); then
                ssh_add "$key" "$pass" &> /dev/null && echo "Loaded: $key" || echo "Failed to load: $key"
            else
                echo "Failed to get passphrase for $key"
                if [[ -t 0 ]]; then
                    echo "Let's add it to keyring:"
                    secret-tool store --label="$(basename "$key")" unique ssh-store:"${key}" 
                    pass=$(secret-tool lookup unique ssh-store:"${key}")
                    ssh_add "$key" "$pass" &> /dev/null && echo "Loaded: $key" || echo "Failed to load: $key"
                fi
            fi
        else
            ssh-add "$key" >/dev/null 2>&1 && echo "Loaded: $key" || echo "Failed to load: $key"
        fi
    done
}

# Инициализация агента SSH
start_agent() {
    echo "Initializing new SSH agent..."
    ssh-agent | sed 's/^echo/#echo/' > "$SSH_ENV"
    chmod 600 "$SSH_ENV"
    # shellcheck disable=SC1090
    source "$SSH_ENV" > /dev/null
    echo "SSH agent started successfully."
    load_keys
}

# Проверка процесса агента
is_agent_running() {
    if [[ -n "${SSH_AGENT_PID:-}" ]]; then
        ps -p "$SSH_AGENT_PID" -o comm= 2>/dev/null | grep -q '^ssh-agent$'
    else
        return 1
    fi
}

# Основной поток исполнения
if [[ -f "$SSH_ENV" ]]; then
    # shellcheck disable=SC1090
    source "$SSH_ENV" > /dev/null

    if ! is_agent_running; then
        echo "Stale SSH agent found. Restarting..."
        start_agent
    fi
else
    start_agent
fi

Описание логики скрипта:

  1. Настройка окружения:
    • устанавливается переменная PATH для гарантированного доступа к исполняемым файлам;
    • включается строгий режим выполнения (set -euo pipefail);
    • определяются общие переменные: IFSSSH_ENV (путь к файлу окружения SSH), UTILS (список необходимых утилит);
  2. Проверка зависимостей:
    • проверяется наличие всех необходимых утилит (ssh-agentssh-addssh-keygenexpectsecret-tool);
    • если какая-то утилита отсутствует, скрипт завершается с ошибкой;
  3. Функция ssh_add:
    • добавляет защищённый SSH-ключ в агент с помощью expect, автоматически вводя пароль, извлеченный из Keyring;
  4. Функция load_keys:
    • ищет все приватные SSH-ключи в ~/.ssh/ (кроме файлов с расширением .pub);
    • для каждого ключа:
      • проверяет, требуется ли пароль (ssh-keygen -y);
      • если пароль нужен, извлекает его из хранилища в разделе “Пароли –> Вход” с помощью утилиты secret-tool;
      • если пароль не найден, предлагает сохранить его в Keyring;
      • добавляет ключ в агент (утилитой ssh-add или функцией ssh_add);
  5. Функция start_agent:
    • запускает новый ssh-agent, сохраняет его переменные окружения в ~/.ssh/environment;
    • загружает ключи (load_keys);
  6. Функция is_agent_running:
    • проверяет, работает ли текущий SSH-агент (по переменной SSH_AGENT_PID);
  7. Основная логика:
    • если файл окружения (~/.ssh/environment) существует, загружает его;
    • если процесс агента отсутствует, перезапускает его (start_agent);
    • если файла окружения нет, запускает агент впервые.

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

  • В интерактивном режиме (например, при запуске в терминале):
    • скрипт обнаружит защищённый ключ и запросит пароль у пользователя;
    • после однократного ввода пароль будет сохранён в gnome-keyring с помощью secret-tool;
    • в последующих запусках скрипт автоматически будет извлекать пароль из хранилища и передавать его ssh-agent с помощью expect;
  • В неинтерактивном режиме (например, через cron или ssh-команду) скрипт просто проигнорирует данный ключ.

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

Запускаем скачанный скрипт:

ssh_agent

Агент успешно стартовал, предварительно запросив пароль для двух защищенных ключей. Эти пароли сохранились в gnome-keyring с помощью secret-rool.

⚠️Обратите внимание, что скрипт сохраняет пароли в gnome-keyring в разделе “Пароли –> Вход“, а не в “Ключи OpenSSH” — это два разных хранилища!

Для доступа к данным ssh-agent’а в текущей сессии терминала необходимо подгрузить переменные окружения из ~/.ssh/environment. Также проверяем наличие процесса ssh-agent и список загруженных ключей:

pgrep -af ssh-agent

source ~/.ssh/environment

ssh-add -l

Если вам необходимо добавить защищенный ключ в Keyring вручную, чтобы он корректно считывался скриптом, воспользуйтесь командой:

secret-tool store --label=id_ed25519 unique ssh-store:/home/ivan/.ssh/id_ed25519

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

⚠️Если на одном из ваших SSH ключей изменилась парольная фраза, то ее необходимо обновить в связке ключей. Либо через GUI “Пароли и ключи”, либо подобной выше командой.

Остается настроить автозапуск скрипта ssh_agent. Сделать это можно через .*rc файл:

# для bash
echo '( { sleep 5 && ssh_agent } & ) > /dev/null 2>&1' >> ~/.bashrc

# для zsh
echo '( { sleep 5 && ssh_agent } & ) > /dev/null 2>&1' >> ~/.zshrc

Или лучше в настройках системы:

Разница в том, что при автозапуске через настройки системы скрипт запуститься один раз, в случае же .*rc файла скрипт будет запускаться каждый раз, но при этом ssh-agent повторно не запуститься — логика скрипта предусматривает этот момент👌.

Ах да, чтобы не выполнять source $HOME/.ssh/environment в каждой сессии терминала, добавьте следующую команду в ваш .*rc файл:

# для bash
echo 'if [[ -f "$HOME/.ssh/environment" ]]; then source $HOME/.ssh/environment; fi' >> ~/.bashrc

# для zsh
echo 'if [[ -f "$HOME/.ssh/environment" ]]; then source $HOME/.ssh/environment; fi' >> ~/.zshrc

Для итоговой проверки рекомендую перезайти в сеанс или перезапустить ОС:

reboot

Проеряем работу агента с авторизацией на хосте по защищенному ключу:

ssh-add -l

ssh database

Никаких паролей🔥.

Не забывайте про нашу телегу📱и чат💬
Всех благ✌️

That should be it. If not, check the logs 🙂

Полезные материалы

Подписаться
Уведомить о
0 комментариев
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии