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

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

SSH Hardening 2026: гайд по защите

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

Об авторе Tobias Lindqvist

Tobias spent six years on Canonical's kernel team in the LTS hardening group, mostly working on Livepatch tooling and the long tail of CVE backports nobody wants to do. He left in 2025 to consult independently with Nordic infrastructure customers running large Ubuntu fleets, where most of the work is AppArmor profiles, unattended-upgrades that don't break things, and explaining what kernel.unprivileged_userns_clone actually does. He co-maintains a small open-source tool for diffing kernel config across distributions and contributes occasionally to the linux-hardening mailing list. His side project is a Yocto-based minimal image for industrial gateways that boots in under four seconds with full secure boot. Tobias writes mostly about kernel-level security primitives - namespaces, seccomp, LSMs - with the assumption that you've already read the man page.