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

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

Почему пароли больше не работают

Каждый раз, когда кто-то говорит мне «у меня надежный пароль из 16 символов», я вспоминаю статистику. Современные ботнеты проверяют тысячи комбинаций в минуту, используя утекшие базы данных и словари популярных паролей. Даже если ваш пароль уникален, его можно перехватить при фишинге или украсть при компрометации устройства.

Но главная беда паролей в другом. Это статичный секрет. Украл один раз, получил доступ навсегда. Злоумышленник может месяцами сидеть в системе незамеченным, копируя данные или используя сервер для своих целей. К тому моменту, когда компрометация обнаружится, будет уже поздно что-то исправлять.

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

Как работает магия SSH-ключей

Технология основана на асимметричной криптографии. Генерируется пара ключей: закрытый (private) хранится только у меня на компьютере, открытый (public) свободно размещается на сервере. Когда я пытаюсь подключиться, происходит криптографический челлендж: сервер отправляет сообщение, зашифрованное моим открытым ключом, которое можно расшифровать только соответствующим закрытым ключом.

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

Для генерации ключа я использую алгоритм Ed25519. Он современный, компактный и обеспечивает высочайший уровень безопасности. Команда простая:

ssh-keygen -t ed25519 -C "Адрес электронной почты защищен от спам-ботов. Для просмотра адреса в браузере должен быть включен Javascript."

Утилита задаст несколько вопросов. Первый: где сохранить ключ. Оставляю значение по умолчанию ~/.ssh/id_ed25519. Второй: установить ли парольную фразу (passphrase). Настоятельно рекомендую это сделать. Даже если кто-то получит доступ к файлу закрытого ключа, без фразы он бесполезен.

После генерации получаю два файла: id_ed25519 (закрытый, строго конфиденциальный) и id_ed25519.pub (открытый, для распространения). Теперь нужно передать открытый ключ на сервер.

Копирование ключа на сервер

Существует удобная утилита, которая автоматизирует процесс:

ssh-copy-id user@server

Она подключается к серверу по паролю (в последний раз!), создает директорию .ssh если её нет, добавляет открытый ключ в файл authorized_keys и устанавливает правильные права доступа. Это критически важный момент: SSH очень требователен к правам. Директория .ssh должна иметь права 700, файл authorized_keys - 600. Любое отклонение, и аутентификация откажется работать.

Если утилиты ssh-copy-id нет под рукой, можно сделать вручную:

cat ~/.ssh/id_ed25519.pub | ssh user@server "mkdir -p ~/.ssh && cat >> ~/.ssh/authorized_keys && chmod 700 ~/.ssh && chmod 600 ~/.ssh/authorized_keys"

После этого проверяю работу ключа. Открываю новый терминал (не закрывая текущую сессию, это важно!) и пытаюсь подключиться:

ssh user@server

Если всё настроено правильно, система либо попросит ввести парольную фразу к ключу, либо сразу впустит меня на сервер. Пароль от учетной записи больше не запрашивается. Отлично, первый этап пройден.

Отключение входа по паролю навсегда

Теперь наступает момент истины. Открываю конфигурацию SSH-демона:

sudo nano /etc/ssh/sshd_config

Ищу и меняю следующие параметры:

PasswordAuthentication no
ChallengeResponseAuthentication no
UsePAM yes
PubkeyAuthentication yes

Параметр PasswordAuthentication no полностью отключает вход по паролю. С этого момента сервер глух к любым попыткам аутентификации через пароль. Можно знать правильный пароль наизусть, но без соответствующего закрытого ключа попасть внутрь невозможно.

Перезапускаю SSH-демон, чтобы изменения вступили в силу:

sudo systemctl restart sshd

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

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

Добавление второго фактора защиты

Ключи решили проблему подбора паролей, но остается один сценарий. Что если злоумышленник получит доступ к моему компьютеру? Украденный закрытый ключ даст полный доступ к серверу, даже если он защищен парольной фразой (её тоже могут украсть вместе с ключом).

Двухфакторная аутентификация закрывает эту брешь. Идея в том, чтобы требовать два независимых доказательства: что-то, что я имею (закрытый ключ), и что-то, что генерируется в реальном времени (одноразовый код). Самый популярный вариант - это TOTP (Time-based One-Time Password), временные коды, которые генерируют приложения вроде Google Authenticator или Authy.

Начинаю с установки PAM-модуля на сервер. Для Ubuntu и Debian:

sudo apt install libpam-google-authenticator

Для CentOS и RHEL команда другая:

sudo yum install google-authenticator

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

google-authenticator

Программа задает серию вопросов. На первый («Do you want authentication tokens to be time-based») отвечаю yes. Утилита генерирует секретный ключ и показывает QR-код. Открываю приложение Google Authenticator на телефоне, сканирую код. Теперь приложение каждые 30 секунд генерирует новый шестизначный код.

Дальше утилита спрашивает, обновить ли файл .google_authenticator. Отвечаю yes. Следующий вопрос: запретить ли повторное использование одного и того же токена. Определенно yes, это защита от атак воспроизведения. Вопрос про расширение временного окна оставляю по умолчанию. И финальный вопрос: включить ли ограничение попыток (rate-limiting). Конечно yes, это защитит от перебора кодов.

Утилита также выдает резервные коды (scratch codes). Записываю их в надежное место, они спасут, если потеряю телефон.

Интеграция 2FA с SSH

Теперь нужно научить SSH использовать Google Authenticator. Открываю файл PAM-конфигурации для SSH:

sudo nano /etc/pam.d/sshd

Добавляю в начало файла строку:

auth required pam_google_authenticator.so

Это означает, что аутентификация через Google Authenticator обязательна. Если хочу сделать её опциональной (некоторые пользователи с 2FA, другие без), добавляю параметр nullok:

auth required pam_google_authenticator.so nullok

Теперь редактирую конфигурацию SSH-демона:

sudo nano /etc/ssh/sshd_config

Устанавливаю следующие параметры:

ChallengeResponseAuthentication yes
UsePAM yes
AuthenticationMethods publickey,keyboard-interactive

Параметр AuthenticationMethods критически важен. Значение publickey,keyboard-interactive означает: сначала проверка ключа, затем интерактивный запрос (это и есть код из приложения). Оба фактора обязательны, один без другого не работает.

Перезапускаю SSH-демон:

sudo systemctl restart sshd

Проверяю из нового терминала. Теперь процесс входа выглядит так: система проверяет мой SSH-ключ, затем запрашивает «Verification code:». Ввожу код из приложения на телефоне, и только после этого получаю доступ.

Подводные камни и их решения

Первая засада, с которой я столкнулся при настройке 2FA: рассинхронизация времени. TOTP-коды зависят от точного времени. Если часы сервера и телефона расходятся больше чем на минуту, коды перестают совпадать. Решение простое: настроить синхронизацию времени через NTP:

sudo apt install ntp
sudo systemctl enable ntp
sudo systemctl start ntp

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

Третий нюанс касается систем с SELinux (CentOS, RHEL, Fedora). Этот механизм безопасности может блокировать работу Google Authenticator из-за неправильного контекста файлов. Если столкнетесь с проблемами, попробуйте переместить файл .google_authenticator в директорию .ssh и восстановить контекст:

mv ~/.google_authenticator ~/.ssh/
restorecon -Rv ~/.ssh

Четвертый момент: как быть с автоматизацией и скриптами. Когда нужен неинтерактивный доступ (например, для бэкапов или деплоя), создаю отдельного пользователя с ключами, но без 2FA. Для него в PAM-конфигурации использую nullok или настраиваю через Match-блоки в sshd_config разные правила для разных пользователей.

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

После настройки ключей и 2FA не останавливаюсь на достигнутом. Меняю стандартный порт SSH с 22 на что-то менее очевидное, например 49152. Это не защита от целенаправленной атаки, но резко снижает количество автоматических сканирований:

Port 49152

Устанавливаю Fail2Ban для автоматической блокировки IP-адресов после нескольких неудачных попыток:

sudo apt install fail2ban
sudo systemctl enable fail2ban
sudo systemctl start fail2ban

Ограничиваю список пользователей, которым разрешен SSH-доступ:

AllowUsers admin deploy

Отключаю вход для root-пользователя полностью:

PermitRootLogin no

Все эти меры вместе создают многоуровневую защиту. Даже если один механизм даст сбой, остальные удержат оборону.

Что я получил в итоге

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

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

Логи сервера теперь выглядят совершенно иначе. Вместо тысяч попыток взлома тишина и порядок. Боты продолжают искать SSH на стандартном порту, но даже если найдут на нестандартном, упираются в требование ключа и отступают.

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