Полное руководство по защите SSH: от базовой настройки до сертификатов и бастионных хостов

Практическое руководство по защите SSH-доступа: от аудита и усиления sshd_config до SSH-сертификатов, двухфакторной аутентификации, бастионных хостов с ProxyJump, Fail2Ban и автоматизации через Ansible.

Введение: почему безопасность SSH важна как никогда

SSH (Secure Shell) остаётся главным протоколом удалённого администрирования Linux-серверов. Через него ежедневно проходят миллионы подключений — от ручного администрирования до автоматизированных CI/CD-пайплайнов. И именно поэтому SSH — приоритетная цель для атакующих. Компрометация SSH-доступа означает полный контроль над сервером. Без вариантов.

Статистика, надо сказать, впечатляет. По данным мониторинговых систем, средний сервер с открытым портом 22 получает от 10 000 до 100 000 попыток подбора паролей в сутки. Ботнеты вроде Mirai и его потомков непрерывно сканируют интернет в поисках слабозащищённых SSH-сервисов.

Начало 2025 года ознаменовалось обнаружением двух критических уязвимостей в OpenSSH. CVE-2025-26465 — уязвимость типа Man-in-the-Middle, позволяющая атакующему выдать себя за легитимный сервер при включённой опции VerifyHostKeyDNS. Злоумышленник перехватывает соединение и получает доступ к учётным данным. CVE-2025-26466 — DoS-уязвимость, которая позволяет исчерпать память и процессор на стороне сервера ещё до завершения аутентификации, фактически делая SSH-сервис недоступным.

А в 2024-м была обнаружена regreSSHion (CVE-2024-6387) — состояние гонки в обработчике сигналов sshd, позволяющее удалённое выполнение кода с правами root без аутентификации. Серьёзно — без аутентификации. Эта уязвимость затронула серверы на базе glibc и стала хорошим напоминанием: даже самый проверенный код может содержать критические ошибки.

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

Аудит текущей конфигурации SSH

Прежде чем что-то менять, нужно понять, с чем мы вообще работаем. Начнём с инструментов диагностики.

Утилита ssh-audit

ssh-audit — один из лучших инструментов для анализа безопасности SSH-сервера. Проверяет поддерживаемые алгоритмы, находит устаревшие и небезопасные параметры, выявляет известные уязвимости. Если вы ещё не пробовали его — самое время.

# Установка ssh-audit
pip3 install ssh-audit

# Сканирование локального сервера
ssh-audit localhost

# Сканирование удалённого сервера
ssh-audit example.com

# Аудит конфигурации клиента
ssh-audit -c

Вывод ssh-audit использует цветовую маркировку: зелёным — безопасные алгоритмы, жёлтым — допустимые, красным — небезопасные, которые нужно срочно отключить. Особое внимание стоит обратить на разделы с алгоритмами обмена ключами (KEX), шифрами и MAC-кодами.

Ещё полезно запустить ssh-audit в режиме генерации политики — это создаёт базовый профиль безопасности, который потом можно использовать для автоматических проверок:

# Генерация политики на основе текущей конфигурации
ssh-audit --make-policy example.com > ssh_policy.txt

# Проверка сервера на соответствие политике
ssh-audit --policy=ssh_policy.txt example.com

Проверка активной конфигурации sshd

Файл /etc/ssh/sshd_config может содержать директивы Include, подключающие дополнительные файлы. Чтобы увидеть итоговую конфигурацию со всеми включениями:

# Просмотр активной расширенной конфигурации
sshd -T

# Проверка конфигурации для конкретного пользователя и хоста
sshd -T -C user=deploy,host=10.0.0.1,addr=10.0.0.1

# Проверка синтаксиса конфигурации
sshd -t

sshd -T особенно полезна — она показывает абсолютно все параметры, включая значения по умолчанию, которые явно не прописаны в конфиге. Помогает выявить неожиданные настройки, о которых вы, возможно, и не подозревали.

Проверка используемых ключей хоста

# Список ключей хоста и их типов
for key in /etc/ssh/ssh_host_*_key.pub; do
    ssh-keygen -l -f "$key"
done

# Проверка наличия устаревших DSA-ключей
ls -la /etc/ssh/ssh_host_dsa_key* 2>/dev/null && \
    echo "ВНИМАНИЕ: Обнаружены DSA-ключи — их необходимо удалить"

Базовое усиление sshd_config

Итак, давайте разберёмся с основными директивами, которые должны быть настроены на каждом сервере. Ниже — эталонный фрагмент /etc/ssh/sshd_config с пояснениями.

Запрет входа root и парольной аутентификации

# Запретить прямой вход под root
PermitRootLogin no

# Отключить парольную аутентификацию
PasswordAuthentication no

# Отключить пустые пароли (на всякий случай)
PermitEmptyPasswords no

# Отключить challenge-response аутентификацию
# (предотвращает обход PasswordAuthentication через PAM)
KbdInteractiveAuthentication no

Запрет входа под root — фундаментальная мера. Атакующий, перебирающий пароли, всегда пробует root первым. Даже если вы используете ключи, оставлять PermitRootLogin yes — это неоправданный риск. Для задач с привилегиями root используйте sudo.

Отключение парольной аутентификации убирает целый класс атак — brute force. Только обязательно убедитесь, что SSH-ключи уже настроены, прежде чем применять это изменение. Иначе рискуете заблокировать себя на собственном сервере. И да, я лично видел, как это происходило не раз — причём с опытными администраторами.

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

Ed25519 — современный стандарт для SSH-ключей. Обеспечивает высокую безопасность при коротких ключах и работает заметно быстрее RSA.

# Генерация ключа Ed25519
ssh-keygen -t ed25519 -C "user@hostname" -a 64

# Если требуется совместимость со старыми системами, используйте RSA 4096
ssh-keygen -t rsa -b 4096 -C "user@hostname"

Параметр -a 64 увеличивает количество раундов KDF (Key Derivation Function) для защиты приватного ключа. Это затрудняет подбор парольной фразы, если файл ключа попадёт не в те руки.

Смена порта по умолчанию

# Смена порта (security through obscurity — не замена реальной защите)
Port 2222

Смена порта с 22 на нестандартный снижает автоматизированный шум от ботов, но не является настоящей мерой безопасности. Направленный сканер найдёт SSH на любом порту за секунды. Тем не менее на практике это полезно: логи становятся чище, а нагрузка от ботов падает на 90-95%. По моему опыту, это одна из тех мер, которые не спасают от целенаправленной атаки, но заметно упрощают повседневную жизнь.

Тайминги и ограничения сессий

# Время на аутентификацию (по умолчанию 120 секунд — слишком много)
LoginGraceTime 30

# Максимальное число попыток аутентификации за соединение
MaxAuthTries 3

# Максимальное число сессий на соединение
MaxSessions 3

# Проверка активности клиента
ClientAliveInterval 300
ClientAliveCountMax 2

LoginGraceTime 30 даёт клиенту 30 секунд на аутентификацию. Не успел — соединение разрывается. Это ограничивает возможности для атак типа CVE-2025-26466, когда злоумышленник удерживает соединения в преаутентификационном состоянии.

ClientAliveInterval и ClientAliveCountMax вместе определяют таймаут неактивной сессии. При указанных значениях неактивная сессия разрывается через 10 минут (300 секунд × 2 пакета).

Ограничение пользователей и групп

# Разрешить доступ только указанным пользователям
AllowUsers deploy monitoring

# Или использовать группы (более масштабируемый подход)
AllowGroups ssh-users ssh-admins

AllowUsers и AllowGroups работают как белый список. Все, кто не указан — не подключатся по SSH, даже с валидными учётными данными. Это критически важно на серверах, где может быть куча системных учёток.

Отключение ненужных функций

# Отключить X11 forwarding (если не используется графика)
X11Forwarding no

# Отключить проброс SSH-агента (снижает риск при компрометации)
AllowAgentForwarding no

# Отключить TCP-перенаправление (если не нужны туннели)
AllowTcpForwarding no

# Отключить перенаправление потоков
AllowStreamLocalForwarding no

# Отключить туннелирование
PermitTunnel no

# Баннер перед аутентификацией
Banner /etc/ssh/banner.txt

Каждая включённая функция — это дополнительная поверхность атаки. Отключайте всё, что не нужно. Если определённым пользователям требуется TCP-forwarding, используйте Match-блоки для выборочного разрешения (об этом поговорим чуть ниже).

Баннер (Banner) отображается перед аутентификацией. Он служит юридическим предупреждением о несанкционированном доступе — и это важно во многих юрисдикциях.

Сводная эталонная конфигурация

Объединим все базовые настройки в один блок:

# /etc/ssh/sshd_config — базовое усиление

# Сетевые настройки
Port 2222
AddressFamily inet
ListenAddress 0.0.0.0

# Аутентификация
PermitRootLogin no
PasswordAuthentication no
PermitEmptyPasswords no
KbdInteractiveAuthentication no
PubkeyAuthentication yes
AuthenticationMethods publickey

# Ограничения
LoginGraceTime 30
MaxAuthTries 3
MaxSessions 3
MaxStartups 10:30:60

# Контроль сессий
ClientAliveInterval 300
ClientAliveCountMax 2

# Доступ
AllowGroups ssh-users ssh-admins

# Отключение ненужных функций
X11Forwarding no
AllowAgentForwarding no
AllowTcpForwarding no
AllowStreamLocalForwarding no
PermitTunnel no
PermitUserEnvironment no
PermitUserRC no

# Логирование
SyslogFacility AUTH
LogLevel VERBOSE

# Баннер
Banner /etc/ssh/banner.txt

Обратите внимание на MaxStartups 10:30:60. Эта директива ограничивает одновременные неаутентифицированные подключения: при 10 активных соединениях новые начинают отклоняться с вероятностью 30%, при 60 — отклоняются все. Это весьма эффективная мера против атак истощения ресурсов вроде CVE-2025-26466.

LogLevel VERBOSE обеспечивает детальное логирование, включая отпечатки ключей — то, что нужно для полноценного аудита.

Криптографические алгоритмы и протокол

Конфигурация криптографических алгоритмов — один из важнейших аспектов защиты SSH. Начиная с OpenSSH 9.0, поддержка DSA-ключей отключена по умолчанию, а в 9.8+ она полностью удалена. И правильно — DSA давно устарел.

Рекомендуемые алгоритмы обмена ключами (KexAlgorithms)

KexAlgorithms [email protected],curve25519-sha256,[email protected],diffie-hellman-group18-sha512,diffie-hellman-group16-sha512

Обратите внимание на [email protected] — это гибридный алгоритм, объединяющий классический X25519 с постквантовым NTRU Prime. Он обеспечивает защиту от потенциальных атак квантовых компьютеров. OpenSSH поддерживает его начиная с версии 8.5, и использовать его стоит уже сейчас.

Почему это важно? Квантовые компьютеры пока не представляют прямой угрозы, но данные, перехваченные сегодня, вполне могут быть расшифрованы завтра. Концепция «harvest now, decrypt later» — не паранойя, а вполне реалистичный сценарий.

Рекомендуемые шифры (Ciphers)

Ciphers [email protected],[email protected],[email protected]

chacha20-poly1305 — предпочтительный шифр для большинства систем. Он особенно хорош на платформах без аппаратного ускорения AES (ARM-устройства, встраиваемые системы). На серверах с поддержкой AES-NI шифры aes256-gcm и aes128-gcm обеспечивают сравнимую безопасность при высокой производительности.

Рекомендуемые MAC-алгоритмы

MACs [email protected],[email protected]

Используйте только варианты etm (Encrypt-then-MAC). Этот режим сначала шифрует данные, затем вычисляет MAC по зашифрованному тексту — криптографически это надёжнее, чем MAC-then-encrypt. Небольшой нюанс: шифры GCM и ChaCha20-Poly1305 являются AEAD-шифрами со встроенной проверкой целостности, так что настройка MACs для них не задействуется. Но указанная конфигурация применяется как запасной вариант для не-AEAD соединений.

Алгоритмы ключей хоста (HostKeyAlgorithms)

HostKeyAlgorithms [email protected],[email protected],ssh-ed25519,[email protected],[email protected],rsa-sha2-512,rsa-sha2-256

Приоритет — Ed25519 и сертификаты. Алгоритмы с префиксом sk- поддерживают аппаратные ключи безопасности (FIDO2/U2F). RSA оставлен для совместимости, но только в варианте с SHA-2. Устаревший ssh-rsa с SHA-1 — это категорическое нет.

Полный блок криптографических настроек

# /etc/ssh/sshd_config — криптографические алгоритмы
# Обновлено для OpenSSH 9.x+

KexAlgorithms [email protected],curve25519-sha256,[email protected],diffie-hellman-group18-sha512,diffie-hellman-group16-sha512

Ciphers [email protected],[email protected],[email protected]

MACs [email protected],[email protected]

HostKeyAlgorithms [email protected],ssh-ed25519,[email protected],rsa-sha2-512,rsa-sha2-256

# Использовать только Ed25519 и RSA ключи хоста
HostKey /etc/ssh/ssh_host_ed25519_key
HostKey /etc/ssh/ssh_host_rsa_key

# Удалить устаревшие ключи DSA и ECDSA
# rm /etc/ssh/ssh_host_dsa_key* /etc/ssh/ssh_host_ecdsa_key*

Аутентификация по сертификатам SSH

Аутентификация по SSH-сертификатам — это существенный шаг вперёд по сравнению с обычными ключами. Вместо того чтобы раскладывать публичные ключи по authorized_keys на каждом сервере (что превращается в настоящий кошмар при масштабировании), вы создаёте удостоверяющий центр (CA) и подписываете ключи пользователей и хостов. Серверу достаточно доверять CA — и он автоматически принимает любой подписанный им ключ.

Создание удостоверяющего центра (CA)

# Создание CA для подписи пользовательских ключей
ssh-keygen -t ed25519 -f /etc/ssh/ca/user_ca -C "User CA Key"

# Создание CA для подписи ключей хостов
ssh-keygen -t ed25519 -f /etc/ssh/ca/host_ca -C "Host CA Key"

# Защитите приватные ключи CA!
chmod 400 /etc/ssh/ca/user_ca /etc/ssh/ca/host_ca

Важно: приватные ключи CA — самый чувствительный элемент всей инфраструктуры. Они должны храниться на изолированной машине, а в идеале — на аппаратном токене (YubiKey, Nitrokey). Компрометация ключа CA означает возможность выпуска сертификатов для любого пользователя и хоста. Честно говоря, именно этот момент чаще всего недооценивают — пока не становится слишком поздно.

Подписание ключей хоста

# Подписание ключа хоста сервера
ssh-keygen -s /etc/ssh/ca/host_ca \
    -I "server01.example.com" \
    -h \
    -n "server01.example.com,10.0.1.10" \
    -V +52w \
    /etc/ssh/ssh_host_ed25519_key.pub

# Результат: файл /etc/ssh/ssh_host_ed25519_key-cert.pub

Параметры команды:

  • -s — путь к ключу CA для подписи
  • -I — идентификатор сертификата (попадает в логи аудита — выбирайте что-то осмысленное)
  • -h — флаг, указывающий, что подписывается ключ хоста, а не пользователя
  • -n — список принципалов (имена и IP хоста)
  • -V +52w — срок действия сертификата (52 недели)

Подписание ключей пользователей

# Подписание ключа пользователя с ограничением срока действия
ssh-keygen -s /etc/ssh/ca/user_ca \
    -I "[email protected]" \
    -n "deploy,ivan" \
    -V +8h \
    -O source-address=10.0.0.0/8 \
    ~/.ssh/id_ed25519.pub

# Результат: файл ~/.ssh/id_ed25519-cert.pub

Обратите внимание на параметры безопасности — тут всё продумано:

  • -V +8h — сертификат действителен только 8 часов (идеально для рабочего дня)
  • -O source-address=10.0.0.0/8 — сертификат работает только при подключении из указанной подсети
  • -n "deploy,ivan" — список имён пользователей, под которыми разрешён вход

Настройка sshd для доверия CA

# /etc/ssh/sshd_config

# Доверять пользовательским сертификатам, подписанным CA
TrustedUserCAKeys /etc/ssh/ca/user_ca.pub

# Использовать подписанный сертификат хоста
HostCertificate /etc/ssh/ssh_host_ed25519_key-cert.pub

# Файл маппинга принципалов (опционально)
AuthorizedPrincipalsFile /etc/ssh/auth_principals/%u

Файл AuthorizedPrincipalsFile позволяет контролировать, какие принципалы допустимы для каждого пользователя. Скажем, для пользователя deploy:

# /etc/ssh/auth_principals/deploy
deploy
ci-service
release-manager

На стороне клиента нужно добавить доверие к CA хоста в ~/.ssh/known_hosts или глобально:

# Добавить доверие к CA хоста (на клиентской машине)
echo "@cert-authority *.example.com $(cat host_ca.pub)" >> ~/.ssh/known_hosts

Преимущества сертификатов перед обычными ключами

  • Централизованное управление — не нужно раскладывать authorized_keys по серверам
  • Автоматическое истечение — сертификаты имеют срок действия, обычные ключи — нет
  • Отзыв — можно отозвать скомпрометированный сертификат через KRL (Key Revocation List)
  • Аудит — идентификатор сертификата записывается в логи
  • Масштабируемость — новый сервер автоматически доверяет всем пользователям с валидными сертификатами

Двухфакторная аутентификация

Для сред с повышенными требованиями безопасности одного SSH-ключа может быть недостаточно. Если у вас продакшн с чувствительными данными — его определённо недостаточно. Двухфакторная аутентификация (2FA) добавляет ещё один уровень защиты, требуя подтверждения личности вторым фактором.

Настройка AuthenticationMethods

# /etc/ssh/sshd_config

# Требовать и ключ, и второй фактор
AuthenticationMethods publickey,keyboard-interactive

# Включить PAM для обработки второго фактора
UsePAM yes
KbdInteractiveAuthentication yes

Директива AuthenticationMethods определяет цепочку методов аутентификации. Запятая между методами означает «и» — оба метода обязательны. Точка с запятой означала бы «или» — достаточно одного. Путать их точно не стоит.

Настройка TOTP через google-authenticator

# Установка PAM-модуля
# Debian/Ubuntu
apt install libpam-google-authenticator

# RHEL/CentOS/Fedora
dnf install google-authenticator

Каждый пользователь должен инициализировать TOTP:

# Запуск от имени пользователя
google-authenticator -t -d -f -r 3 -R 30 -w 3

Настройка PAM (/etc/pam.d/sshd):

# Добавить в начало файла /etc/pam.d/sshd
auth required pam_google_authenticator.so nullok

# nullok позволяет пользователям без настроенного 2FA входить
# Уберите nullok после того, как все пользователи настроят 2FA

После настройки при входе по SSH пользователь сначала предъявляет свой ключ, а затем вводит шестизначный одноразовый код из приложения-аутентификатора (Google Authenticator, Authy, FreeOTP). Немного неудобно? Безусловно. Но это та самая неудобность, которая реально стоит свеч.

Аппаратные ключи безопасности (FIDO2)

Начиная с OpenSSH 8.2, поддерживаются аппаратные ключи FIDO2/U2F — YubiKey, SoloKey и подобные. Это, пожалуй, наиболее надёжный второй фактор: требует физического присутствия устройства и не подвержен фишингу.

# Генерация ключа, привязанного к аппаратному токену
ssh-keygen -t ed25519-sk -C "user@hostname-yubikey"

# Вариант с требованием подтверждения касанием
ssh-keygen -t ed25519-sk -O resident -O verify-required -C "user@hostname-yubikey"

Опция -O resident сохраняет ключ непосредственно на токене — можно использовать с любой машины без копирования файлов. Опция -O verify-required требует PIN-код токена при каждом использовании.

Match-блоки: гибкая конфигурация доступа

Директива Match в sshd_config позволяет применять разные настройки в зависимости от пользователя, группы, IP-адреса или имени хоста. Без этого инструмента сложно обойтись в любой более-менее серьёзной инфраструктуре.

SFTP-пользователи с изолированным окружением

# Группа для SFTP-only пользователей
Match Group sftp-users
    ChrootDirectory /data/sftp/%u
    ForceCommand internal-sftp
    AllowTcpForwarding no
    AllowAgentForwarding no
    PermitTTY no
    X11Forwarding no

Эта конфигурация полностью изолирует пользователей группы sftp-users:

  • ChrootDirectory — ограничивает видимость файловой системы каталогом конкретного пользователя
  • ForceCommand internal-sftp — разрешает только SFTP, запрещая shell-доступ
  • PermitTTY no — запрещает выделение терминала

Не забудьте про права на chroot-каталог — тут часто ошибаются:

# Chroot-каталог должен принадлежать root
chown root:root /data/sftp/username
chmod 755 /data/sftp/username

# Каталог для записи пользователя внутри chroot
mkdir /data/sftp/username/uploads
chown username:sftp-users /data/sftp/username/uploads

Различные методы аутентификации для разных групп

# Администраторы — требуется ключ + 2FA
Match Group ssh-admins
    AuthenticationMethods publickey,keyboard-interactive
    AllowTcpForwarding yes
    PermitTTY yes

# Сервисные учётные записи — только ключ, из определённой подсети
Match Group service-accounts
    AuthenticationMethods publickey
    AllowTcpForwarding no
    PermitTTY no
    AllowAgentForwarding no

# Доступ из внутренней сети — менее строгие требования
Match Address 10.0.0.0/8,172.16.0.0/12,192.168.0.0/16
    MaxAuthTries 5
    LoginGraceTime 60

# Доступ из внешней сети — максимальная строгость
Match Address *,!10.0.0.0/8,!172.16.0.0/12,!192.168.0.0/16
    MaxAuthTries 2
    LoginGraceTime 20
    AuthenticationMethods publickey,keyboard-interactive

Ограничение по конкретному пользователю

# Пользователь для резервного копирования
Match User backup
    ForceCommand /usr/local/bin/backup-script.sh
    AllowTcpForwarding no
    PermitTTY no
    X11Forwarding no

ForceCommand гарантирует, что пользователь backup может выполнить только заданный скрипт — неважно, что он пытается запустить на самом деле. Принцип минимальных привилегий в чистом виде.

Важно помнить: Match-блоки всегда размещаются в конце файла sshd_config. Все директивы после строки Match относятся к этому блоку, пока не встретится следующий Match или конец файла. И не все директивы допускаются внутри Match-блоков — проверяйте конфигурацию командой sshd -t перед применением.

Я встречал случаи, когда Match-блок с недопустимой директивой просто тихо игнорировался, и админ неделями был уверен, что всё работает как надо. Будьте внимательнее.

Бастионные хосты и ProxyJump

В правильно спроектированной инфраструктуре серверы не торчат голым SSH в интернет. Вместо этого используется бастионный хост (он же jump host) — единственная точка входа в приватную сеть.

Архитектура

Типичная схема выглядит так:

Интернет → [Бастион (публичный IP)] → [Приватная сеть]
                                        ├── web-01 (10.0.1.10)
                                        ├── db-01  (10.0.2.10)
                                        └── app-01 (10.0.3.10)

Бастион — единственный сервер с открытым SSH-портом в интернете. Все остальные принимают SSH-подключения только из приватной сети.

Настройка клиента SSH (ProxyJump)

# ~/.ssh/config

Host bastion
    HostName bastion.example.com
    User jump-user
    Port 2222
    IdentityFile ~/.ssh/id_ed25519

Host web-01
    HostName 10.0.1.10
    User deploy
    ProxyJump bastion
    IdentityFile ~/.ssh/id_ed25519

Host db-01
    HostName 10.0.2.10
    User dbadmin
    ProxyJump bastion
    IdentityFile ~/.ssh/id_ed25519

# Шаблон для всех серверов в приватной сети
Host 10.0.*
    User admin
    ProxyJump bastion
    IdentityFile ~/.ssh/id_ed25519

Теперь подключение к серверу в приватной сети — буквально одна команда:

# SSH прозрачно проходит через бастион
ssh web-01

# Или напрямую через командную строку
ssh -J bastion.example.com [email protected]

Усиление безопасности бастиона

# /etc/ssh/sshd_config на бастионном хосте

# Разрешить TCP-перенаправление (необходимо для ProxyJump)
AllowTcpForwarding yes

# Запретить интерактивные сессии на самом бастионе
Match User jump-user
    PermitTTY no
    X11Forwarding no
    AllowAgentForwarding no
    ForceCommand /usr/sbin/nologin
    PermitOpen 10.0.0.0/8:22

PermitOpen ограничивает, к каким адресам и портам можно пробрасывать соединения — только порт 22 в приватной подсети. Даже при компрометации учётки на бастионе атакующий не сможет пробиться к другим сервисам.

Дополнительно на бастионе стоит настроить минимальное окружение:

# Дополнительные меры на бастионном хосте

# Минимальный набор пакетов — удалить всё лишнее
# Отключить историю команд (на случай получения shell-доступа)
# /etc/profile.d/no-history.sh
unset HISTFILE
HISTSIZE=0

# Настроить файрвол: разрешить только SSH-входящий и SSH-исходящий в приватную сеть
nft add rule inet filter input tcp dport 2222 accept
nft add rule inet filter output ip daddr 10.0.0.0/8 tcp dport 22 accept
nft add rule inet filter output ct state established accept
nft add rule inet filter output drop

ProxyJump vs Agent Forwarding: вопрос безопасности

Раньше для доступа через промежуточный сервер часто использовали проброс SSH-агента (ssh -A). Так вот — это серьёзная уязвимость. Если бастион скомпрометирован, злоумышленник может использовать ваш проброшенный агент для подключения к любому серверу, к которому у вас есть доступ.

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

# НЕБЕЗОПАСНО — агент проброшен на потенциально скомпрометированный хост
ssh -A bastion
ssh web-01

# БЕЗОПАСНО — ключ остаётся на клиенте
ssh -J bastion web-01

Fail2Ban и мониторинг

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

Настройка Fail2Ban для SSH

# /etc/fail2ban/jail.local

[sshd]
enabled = true
port = 2222
filter = sshd
backend = systemd
maxretry = 3
findtime = 600
bantime = 3600
banaction = nftables-multiport

# Агрессивная блокировка для повторных нарушителей
[sshd-aggressive]
enabled = true
port = 2222
filter = sshd[mode=aggressive]
backend = systemd
maxretry = 1
findtime = 86400
bantime = 604800
banaction = nftables-multiport

Основной jail [sshd] блокирует IP на 1 час после 3 неудачных попыток в течение 10 минут. Дополнительный [sshd-aggressive] банит на целую неделю после первой же подозрительной активности (сканирование, попытки использования запрещённых алгоритмов).

Жёстко? Возможно. Но работает.

Мониторинг логов с journalctl

# Просмотр логов SSH в реальном времени
journalctl -u sshd -f

# Неудачные попытки входа за последние 24 часа
journalctl -u sshd --since "24 hours ago" | grep -i "failed\|invalid\|refused"

# Успешные входы
journalctl -u sshd --since "24 hours ago" | grep "Accepted"

# Статистика по IP-адресам атакующих
journalctl -u sshd --since "24 hours ago" \
    | grep "Failed password" \
    | awk '{print $(NF-3)}' \
    | sort | uniq -c | sort -rn | head -20

Настройка оповещений

Для оперативного реагирования настройте уведомления о критических событиях:

# /etc/fail2ban/action.d/notify.conf
# Добавить действие отправки уведомления при блокировке

[Definition]
actionban = notify-send-email <ip> <name>

# Или использовать systemd-монитор с webhook
# /etc/systemd/system/ssh-alert.service
[Unit]
Description=SSH Login Alert

[Service]
Type=oneshot
ExecStart=/usr/local/bin/ssh-alert.sh
#!/bin/bash
# /usr/local/bin/ssh-alert.sh
# Скрипт оповещения о входе по SSH

LOGINS=$(journalctl -u sshd --since "5 minutes ago" | grep "Accepted")
if [ -n "$LOGINS" ]; then
    echo "$LOGINS" | mail -s "SSH Login Alert: $(hostname)" [email protected]
fi

Признаки вторжения в логах

Знание характерных признаков атаки помогает быстро обнаружить компрометацию. Вот на что стоит обращать внимание:

  • Массовые неудачные попытки с одного IP — классический brute force
  • Неудачные попытки под разными именами — перебор учётных записей
  • Успешный вход в нерабочее время — потенциальная компрометация (если ваши админы, конечно, не страдают бессонницей)
  • Вход с неизвестного IP после множества неудачных попыток — успешный brute force
  • Сообщения о несовпадении версий протокола — попытка эксплуатации уязвимостей
  • Попытки использования отключённых методов — целенаправленная разведка
  • Необычный объём данных в рамках SSH-сессии — возможная эксфильтрация
  • Множественные SSH-сессии от одного пользователя — потенциально захваченные учётные данные

Для централизованного сбора и анализа SSH-логов рекомендую пересылать их в SIEM-систему (Wazuh, Elastic Security, Splunk) или хотя бы использовать logwatch для ежедневных отчётов:

# Установка и настройка logwatch для ежедневных отчётов SSH
apt install logwatch

# Запуск отчёта за последние сутки
logwatch --service sshd --range yesterday --detail high --output mail --mailto [email protected]

Автоматизация и CI/CD

Ручная настройка SSH на десятках серверов — занятие не из приятных, а на сотнях — это уже просто нереалистично. Автоматизация обеспечивает единообразие конфигурации и возможность быстро обновить всё, когда вдруг появляется очередной CVE.

Ansible-плейбук для настройки SSH

# ssh-hardening.yml
---
- name: SSH Hardening
  hosts: all
  become: yes
  vars:
    ssh_port: 2222
    ssh_allowed_groups:
      - ssh-users
      - ssh-admins

  tasks:
    - name: Установить актуальную версию OpenSSH
      ansible.builtin.package:
        name: openssh-server
        state: latest

    - name: Развернуть усиленную конфигурацию sshd
      ansible.builtin.template:
        src: templates/sshd_config.j2
        dest: /etc/ssh/sshd_config
        owner: root
        group: root
        mode: '0600'
        validate: 'sshd -t -f %s'
      notify: Restart sshd

    - name: Удалить устаревшие ключи хоста
      ansible.builtin.file:
        path: "{{ item }}"
        state: absent
      loop:
        - /etc/ssh/ssh_host_dsa_key
        - /etc/ssh/ssh_host_dsa_key.pub
        - /etc/ssh/ssh_host_ecdsa_key
        - /etc/ssh/ssh_host_ecdsa_key.pub
      notify: Restart sshd

    - name: Сгенерировать Ed25519 ключ хоста (если отсутствует)
      ansible.builtin.command:
        cmd: ssh-keygen -t ed25519 -f /etc/ssh/ssh_host_ed25519_key -N ""
        creates: /etc/ssh/ssh_host_ed25519_key

    - name: Развернуть баннер
      ansible.builtin.copy:
        content: |
          ******************************************************************
          *  Доступ к этой системе разрешён только авторизованным          *
          *  пользователям. Все действия регистрируются и отслеживаются.   *
          ******************************************************************
        dest: /etc/ssh/banner.txt
        mode: '0644'

    - name: Настроить Fail2Ban для SSH
      ansible.builtin.template:
        src: templates/fail2ban-sshd.j2
        dest: /etc/fail2ban/jail.d/sshd.local
      notify: Restart fail2ban

  handlers:
    - name: Restart sshd
      ansible.builtin.systemd:
        name: sshd
        state: restarted

    - name: Restart fail2ban
      ansible.builtin.systemd:
        name: fail2ban
        state: restarted

Интеграция ssh-audit в CI/CD

# .gitlab-ci.yml (фрагмент)
ssh-audit:
  stage: security
  image: python:3.12-slim
  script:
    - pip install ssh-audit
    - |
      SERVERS="web-01.example.com db-01.example.com app-01.example.com"
      FAILED=0
      for server in $SERVERS; do
        echo "=== Аудит $server ==="
        if ! ssh-audit --level warn "$server"; then
          FAILED=1
          echo "ОШИБКА: $server не прошёл аудит безопасности"
        fi
      done
      exit $FAILED
  rules:
    - if: $CI_PIPELINE_SOURCE == "schedule"
# GitHub Actions вариант
# .github/workflows/ssh-audit.yml
name: SSH Security Audit
on:
  schedule:
    - cron: '0 6 * * 1'  # Каждый понедельник в 6:00

jobs:
  audit:
    runs-on: ubuntu-latest
    steps:
      - name: Install ssh-audit
        run: pip install ssh-audit

      - name: Run audit
        run: |
          ssh-audit --level warn ${{ secrets.SERVER_HOST }}

      - name: Notify on failure
        if: failure()
        uses: slackapi/slack-github-action@v1
        with:
          payload: |
            {"text": "SSH аудит не пройден для ${{ secrets.SERVER_HOST }}"}
        env:
          SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }}

Автоматизированная проверка соответствия

#!/bin/bash
# ssh-compliance-check.sh
# Скрипт проверки соответствия конфигурации SSH политикам безопасности

SSHD_CONFIG="/etc/ssh/sshd_config"
ERRORS=0

check_setting() {
    local setting="$1"
    local expected="$2"
    local actual
    actual=$(sshd -T 2>/dev/null | grep -i "^${setting} " | awk '{print $2}')

    if [ "$actual" != "$expected" ]; then
        echo "[FAIL] $setting = $actual (ожидается: $expected)"
        ERRORS=$((ERRORS + 1))
    else
        echo "[ OK ] $setting = $actual"
    fi
}

echo "=== Проверка соответствия SSH-конфигурации ==="
echo "Дата: $(date)"
echo "Хост: $(hostname)"
echo ""

check_setting "permitrootlogin" "no"
check_setting "passwordauthentication" "no"
check_setting "permitemptypasswords" "no"
check_setting "x11forwarding" "no"
check_setting "maxauthtries" "3"
check_setting "protocol" "2"
check_setting "allowagentforwarding" "no"
check_setting "allowtcpforwarding" "no"

# Проверка версии OpenSSH
SSH_VERSION=$(ssh -V 2>&1 | grep -oP 'OpenSSH_\K[0-9.]+')
REQUIRED_VERSION="9.0"
if [ "$(printf '%s
' "$REQUIRED_VERSION" "$SSH_VERSION" | sort -V | head -n1)" != "$REQUIRED_VERSION" ]; then
    echo "[FAIL] Версия OpenSSH $SSH_VERSION ниже минимальной ($REQUIRED_VERSION)"
    ERRORS=$((ERRORS + 1))
else
    echo "[ OK ] Версия OpenSSH: $SSH_VERSION"
fi

# Проверка наличия устаревших ключей хоста
if [ -f /etc/ssh/ssh_host_dsa_key ]; then
    echo "[FAIL] Обнаружен устаревший DSA-ключ хоста"
    ERRORS=$((ERRORS + 1))
fi

echo ""
if [ $ERRORS -eq 0 ]; then
    echo "Результат: ВСЕ ПРОВЕРКИ ПРОЙДЕНЫ"
    exit 0
else
    echo "Результат: ОБНАРУЖЕНО ПРОБЛЕМ: $ERRORS"
    exit 1
fi

Чек-лист безопасности SSH

Ниже — краткий чек-лист для быстрой проверки SSH на ваших серверах. Можете распечатать и повесить рядом с монитором — пригодится не раз:

  1. Обновление — OpenSSH обновлён до последней стабильной версии
  2. Протокол — используется только SSH-2
  3. Root-доступPermitRootLogin no
  4. Парольная аутентификацияPasswordAuthentication no
  5. Ключи — используются Ed25519; DSA и ECDSA удалены
  6. Криптография — настроены современные KexAlgorithms, Ciphers и MACs
  7. Постквантовый обмен ключами — включён sntrup761x25519-sha512
  8. Белый список — настроены AllowUsers или AllowGroups
  9. Тайминги — LoginGraceTime ≤ 30, MaxAuthTries ≤ 3
  10. Ненужные функции — отключены X11Forwarding, AgentForwarding, TcpForwarding
  11. Баннер — настроен предупредительный баннер
  12. Сертификаты — по возможности используется аутентификация по сертификатам
  13. 2FA — настроена двухфакторная аутентификация для администраторов
  14. Match-блоки — применяются для SFTP-пользователей и сервисных учёток
  15. Бастион — SSH-доступ идёт через бастионный хост
  16. ProxyJump — используется вместо Agent Forwarding
  17. Fail2Ban — настроен и работает
  18. Мониторинг — настроены оповещения о подозрительных входах
  19. Аудит — регулярные проверки ssh-audit
  20. Автоматизация — конфигурация управляется через Ansible или аналог

Заключение

Безопасность SSH — это не одноразовая настройка, а непрерывный процесс. CVE-2025-26465 и CVE-2025-26466 в очередной раз показали: даже проверенный годами протокол может преподнести серьёзные сюрпризы. А появление квантовых компьютеров делает переход на постквантовые алгоритмы актуальным уже сегодня — не «когда-нибудь потом».

Ключевые принципы, которых стоит придерживаться:

  • Минимальные привилегии — каждый пользователь и сервис получает ровно тот доступ, который необходим, и ни байтом больше
  • Глубокая защита — не полагайтесь на один механизм; комбинируйте ключи, сертификаты, 2FA, бастионы и мониторинг
  • Автоматизация — ручная конфигурация не масштабируется и неизбежно приводит к ошибкам
  • Непрерывный мониторинг — настройте оповещения и регулярный аудит
  • Своевременное обновление — оперативно устанавливайте патчи OpenSSH

Начните с аудита текущей конфигурации при помощи ssh-audit, примените базовое усиление из этого руководства, а затем последовательно внедряйте более продвинутые меры — сертификаты, 2FA, бастионную архитектуру. Каждый шаг ощутимо повышает общий уровень защиты.

И помните: цена компрометации SSH-доступа — полный контроль над сервером. Время, потраченное на правильную настройку, окупается многократно. А вот время, потраченное на разбор последствий взлома — не окупается никогда.

Об авторе Editorial Team

Our team of expert writers and editors.