Перейти к содержимому

SSH Config: wildcards и подстановка переменных

SSH-клиент читает ~/.ssh/config строчку за строчкой, но без переменных файл быстро превращается в копипасту. Разбираю, как Host *, Match exec и подстановки %h, %r, %l сокращают конфиг в разы и закрывают реальные сценарии — от динамической маршрутизации до проброса агента через bastion.

Шаблоны и wildcards в SSH config

SSH поддерживает glob-подобные шаблоны в директиве Host. Самая популярная — Host *, но работают и составные паттерны.

# Все хосты без явного описания получат общие настройки
Host *
    ServerAliveInterval 60
    ServerAliveCountMax 3
    IdentitiesOnly yes

# Все хосты домена example.com
Host *.example.com
    User ubuntu
    Port 22

# Хосты по маске: web-01, web-02, web-03
Host web-0?
    IdentityFile ~/.ssh/id_ed25519_web
Примечание

SSH читает конфиг сверху вниз и применяет первую подходящую директиву Host. Более конкретные правила ставьте выше общих.

# Правильный порядок: частное перед общим
Host bastion
    HostName bastion.example.com
    User admin

Host *.internal
    ProxyJump bastion

Host *
    ServerAliveInterval 60

Шаблоны можно комбинировать в одном Host через пробел — это работает как логическое ИЛИ.

Host dev-* staging-*
    User deploy
    IdentityFile ~/.ssh/id_ed25519_deploy

Переменные подстановки: %h, %r, %l

SSH подставляет переменные в значения директив на этапе обработки конфига. Три основных:

ПеременнаяЗначениеПример
%hИмя хоста из командной строкиssh web-01 → web-01
%rИмя пользователя на удалённой машинеubuntu
%lЛокальное имя пользователяalex

Подстановка работает в большинстве директив — ProxyCommand, IdentityFile, LocalCommand, RemoteCommand.

Host jump-*
    HostName %h.internal
    ProxyJump bastion.example.com
    User deploy

Host backup-*
    HostName %h.backup.local
    User backup
    # Логин совпадает с локальным пользователем
    IdentityFile ~/.ssh/id_ed25519_%r

Переменная %h заменяется на то, что вы передали после ssh. Это позволяет написать одно правило на сотню хостов.

# Вместо пяти отдельных блоков
Host server-{01..05}
    HostName %h.internal.corp
    User ansible
    ProxyJump bastion
Подсказка

%h подставляет то, что вы набрали, а неresolved HostName. ssh web-01 подставит web-01, а не его IP.

Match exec и динамическая маршрутизация

Директива Match позволяет задавать правила на основе условий. Без exec это проверка по user, host, localuser. С Match exec — произвольная логика через shell-команду.

# Подключаться через bastion только для внутренних хостов
Match host 10.* exec "echo true"
    ProxyJump bastion.example.com

Условие exec выполняется на локальной машине. Команда должна вернуть 0 (успех) для применения блока Match.

# Прокидывать агента только при подключении к prod
Match exec "[ '%h' = prod-* ]"
    AddKeysToAgent yes
    IdentityAgent SSH_AUTH_SOCK
# Разная маршрутизация в зависимости от сети
Match exec "hostname -I | grep -q 192.168.1"
    ProxyCommand none  # локальная сеть, прямой доступ

Match exec "[ '%h' != prod-* ]"
    ProxyJump office-jump
Предупреждение

Match exec выполняется shell-оболочкой. Обратные кавычки и переменные раскрываются. Для сложной логики выносите проверку в отдельный скрипт.

Match exec "/home/alex/.ssh/route-check.sh %h"
    ProxyJump bastion
# route-check.sh
#!/bin/bash
HOST="$1"
if [[ "$HOST" == prod-* ]]; then
    exit 1  # для prod нужна другая логика
fi
exit 0

Escape символа: как написать %

Если нужно передать символ % literally — в RemoteCommand, ProxyCommand или LocalCommand — используйте %%.

# SSH в Docker-контейнер с eval
Host docker-*
    HostName %h
    User docker
    RemoteCommand docker exec -it %h /bin/sh -c "eval $(ssh-agent -s) && exec /bin/sh"
# Добавить timestamp в remote command
Host *
    RemoteCommand echo "%%Y-%%m-%%d %%H:%%M:%%S connected to %h" >> /tmp/ssh-log
# Проброс порта через nc на удалённом хосте
Host relay
    HostName relay.example.com
    ProxyCommand ssh -W %h:%p user@bastion
    # или классический nc
    ProxyCommand nc -Z %h 22

Примеры для dev, staging, prod

Типичная схема: один файл, три окружения, общая база.

# ============================================
# Базовые настройки для всех
# ============================================
Host *
    ServerAliveInterval 60
    ServerAliveCountMax 3
    IdentitiesOnly yes
    AddKeysToAgent yes

# ============================================
# Окружения: динамический HostName
# ============================================
Host dev-*
    HostName %h.dev.internal
    User developer
    IdentityFile ~/.ssh/id_ed25519

Host staging-*
    HostName %h.staging.internal
    User deploy
    IdentityFile ~/.ssh/id_ed25519_deploy
    # Дополнительныйjump через staging-bastion
    ProxyJump staging-bastion

Host prod-*
    HostName %h.prod.internal
    User deploy
    IdentityFile ~/.ssh/id_ed25519_prod
    ProxyJump prod-bastion
    # Только через агент, без файлового ключа на диске
    IdentityAgent SSH_AUTH_SOCK

# ============================================
# Match: динамическая логика
# ============================================
# Прокидывать агента только на prod и staging
Match exec "[ '%h' = prod-* ] || [ '%h' = staging-* ]"
    ForwardAgent yes

# Отключать ForwardAgent для dev (параноидальная настройка)
Match host dev-* exec "true"
    ForwardAgent no

# ============================================
# Переменные подстановки в командах
# ============================================
Host db-*
    HostName %h.internal
    User dbadmin
    RemoteCommand psql -U postgres -c "SELECT now(), '%r'"

# ============================================
# Escape %% в аргументах
# ============================================
Host log-*
    HostName %h
    RemoteCommand echo "Connected at %%Y-%%m-%%d" >> /var/log/ssh-connections.log

После этого конфига ssh prod-web-03 подключается к prod-web-03.prod.internal через prod-bastion с ключом id_ed25519_prod, а ssh dev-app-01 — напрямую.

# Проверить, что SSH видит ваш конфиг
ssh -G dev-app-01 | grep -E "^hostname|^user|^proxyjump"

# Тест соединения без выполнения команды
ssh -v prod-db-01 echo "connected"
Примечание

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

Комбинация Host-шаблонов, подстановки переменных и Match exec превращает SSH config из набора копипаст в систему с динамической маршрутизацией. Один файл покрывает dev/staging/prod без дублирования, а %h, %r, %l избавляют от ручной правки при добавлении новых хостов.