2FA для SSH — один из самых практичных шагов в hardening: даже если пароль утёк или ключ оказался скомпрометирован, злоумышленнику всё ещё нужен второй фактор. Но в админской реальности важно внедрить 2FA так, чтобы не потерять доступ, не сломать автоматизацию и иметь понятный путь отката. Ниже — рабочая схема TOTP на Linux через pam_google_authenticator с аккуратным подключением к SSH и (опционально) к sudo.
Логика такая: сначала настраиваем TOTP для одного пользователя, потом включаем требование второго фактора в sshd и только после успешного теста думаем про 2FA для sudo.
Что именно настраиваем и какие есть режимы
TOTP — одноразовый код, который генерирует приложение-аутентификатор (Google Authenticator, FreeOTP, Aegis и т.п.) на основе общего секрета и времени. На сервере код проверяет PAM-модуль pam_google_authenticator.so. Секрет по умолчанию хранится в ~/.google_authenticator конкретного пользователя.
Для SSH чаще всего используют один из сценариев:
- Ключ или пароль + TOTP: классический «два фактора» — сначала основной метод, затем код.
- Только TOTP: обычно плохая идея, потому что вы теряете смысл «двух факторов» и усложняете аварийное восстановление.
Для sudo добавлять TOTP имеет смысл только с учётом кэша (timestamp) и исключений, иначе вы получите постоянные запросы кода на каждую команду.
Не включайте 2FA «для всех и сразу» без второго канала доступа. Правильная стратегия: держите открытую запасную сессию, имейте доступ к консоли и меняйте настройки поэтапно.
Предварительные меры безопасности: как не потерять сервер
Перед изменениями убедитесь, что у вас есть запасной способ доступа:
- вторая открытая SSH-сессия под админом (не закрывайте до конца работ);
- консоль провайдера/виртуализации (out-of-band);
- резервный админский аккаунт и/или аварийный SSH-ключ.
Если у вас агрессивные таймауты, временно увеличьте LoginGraceTime в SSH, чтобы при первом тесте успеть ввести код. После настройки вернёте обратно.
Если сервер размещён на VDS, заранее проверьте доступность VNC/serial-консоли в панели: это самый быстрый способ вернуть доступ при ошибке в PAM/SSHD.

Установка pam_google_authenticator
В Debian/Ubuntu пакет обычно называется libpam-google-authenticator, в RHEL-подобных — google-authenticator.
Debian/Ubuntu
sudo apt update
sudo apt install -y libpam-google-authenticator
Rocky/Alma/CentOS
sudo dnf install -y google-authenticator
Проверьте, что модуль на месте (путь зависит от дистрибутива):
sudo ls -la /lib/security/pam_google_authenticator.so
sudo ls -la /lib64/security/pam_google_authenticator.so
Создаём TOTP-секрет пользователю
Генерацию секрета запускайте под тем пользователем, для которого включаете 2FA: файл секрета создаётся в его домашнем каталоге.
google-authenticator
Обычно безопасный набор ответов выглядит так:
- Time-based tokens: y (TOTP)
- Обновлять файл: y
- Ограничить повторное использование: y
- Окно верификации: лучше небольшое/по умолчанию; увеличивайте только если реально гуляет время
- Rate-limiting: y
В процессе вы получите QR-код/секрет — добавьте его в приложение-аутентификатор. Scratch-коды сохраните отдельно (офлайн/в защищённом хранилище): это аварийный вход, если телефон потерян или недоступен.
Время на сервере должно быть синхронизировано
Если часы уезжают — коды начинают «не подходить». Проверьте NTP/chrony:
timedatectl status
chronyc tracking
Включаем 2FA в SSH через PAM (sshd + /etc/pam.d/sshd)
Здесь два шага: включить keyboard-interactive в sshd и подключить PAM-модуль в /etc/pam.d/sshd. Все изменения делайте с открытой запасной сессией.
Шаг 1: настройки sshd_config
Откройте /etc/ssh/sshd_config и убедитесь, что PAM включён, а keyboard-interactive разрешён:
UsePAM yes
KbdInteractiveAuthentication yes
ChallengeResponseAuthentication yes
Параметр ChallengeResponseAuthentication встречается в старых конфигурациях. Если OpenSSH его игнорирует — это нормально; главное, чтобы работал KbdInteractiveAuthentication.
Дальше задайте, что именно требуется для входа, через AuthenticationMethods.
Рекомендуемый базовый вариант (ключ + TOTP):
AuthenticationMethods publickey,keyboard-interactive
Если вам нужно временно разрешить и пароль + TOTP (например, на период миграции ключей), задайте несколько цепочек:
AuthenticationMethods publickey,keyboard-interactive password,keyboard-interactive
Если оставить просто
publickeyбезkeyboard-interactive, то при входе по ключу второго фактора не будет. Следите, чтобы в вашей схеме 2FA реально требовалась.
Шаг 2: подключаем pam_google_authenticator в /etc/pam.d/sshd
Откройте /etc/pam.d/sshd и добавьте правило в секцию auth так, чтобы оно срабатывало при интерактивной аутентификации (обычно — рядом с другими строками auth, до включений вроде @include common-auth).
Строгий режим (если файла секрета нет — вход запрещён):
auth required pam_google_authenticator.so
Мягкий режим для поэтапного внедрения (если файла нет — пропускаем TOTP):
auth required pam_google_authenticator.so nullok
nullok удобен, чтобы «раскатывать» по пользователям: тем, кто создал секрет, будет запрошен TOTP, остальные войдут по старой схеме. Для финального состояния чаще всего nullok убирают.
Проверяем конфигурацию и перезапускаем SSH
Перед перезапуском проверьте синтаксис:
sudo sshd -t
Перезапуск (зависит от дистрибутива, поэтому даю оба варианта отдельными командами):
sudo systemctl restart ssh
sudo systemctl restart sshd
Не закрывайте текущую сессию, пока не проверите вход в новой.
Исключения: как не сломать автоматизацию и аварийные входы
TOTP плохо сочетается с неинтерактивными сценариями: CI/CD, Ansible, бэкапы по SSH, мониторинг, rsync/scp без TTY. Вместо того чтобы «пытаться заставить бота вводить код», обычно выбирают одну из безопасных архитектурных развилок.
Подход A: отдельный порт или Match-блок без 2FA
Идея: основной SSH-вход требует 2FA, а для узкого набора источников (например, bastion/админская подсеть) делаете отдельное правило через Match в sshd_config, где допускаете другой метод.
Это удобно для автоматизаций, но требует дисциплины в firewall и строгого контроля IP-источников.
Подход B: отдельный пользователь для автоматизаций с минимальными правами
Практичный вариант: создаёте отдельного пользователя для бэкапов/деплоя, разрешаете вход только по ключу и ограничиваете возможности (без sudo, без shell, с ограниченными командами в authorized_keys). Тогда отсутствие TOTP не превращается в «дырищу», потому что права аккаунта изначально минимальны.
Подход C: включать TOTP только для части пользователей (группа)
Если хотите, чтобы 2FA требовалась, например, только для админов, удобная тактика — завести группу и в PAM включать модуль только для неё (через pam_succeed_if или эквивалентные механизмы). Базовая подготовка группы:
sudo groupadd -f ssh2fa
sudo usermod -aG ssh2fa youradmin
Дальше конкретная строка в PAM зависит от вашей PAM-цепочки и дистрибутива. Обязательно тестируйте на отдельной сессии и держите консольный доступ, потому что ошибкой в условии можно либо выключить 2FA, либо запереть себя.
Если нужна максимально предсказуемая модель: требуйте 2FA на bastion/jump-host, а к серверам пускайте только по ключам из закрытой сети. Так проще и для безопасности, и для эксплуатации.
Кстати, если вы переносите проекты на отдельный сервер под админские нужды, заранее продумайте миграцию так, чтобы не потерять доступ и не уронить сервисы: пригодится инструкция про перенос сайта без простоя.
Добавляем TOTP для sudo без самострела
2FA для sudo полезна, если вы боитесь сценария «злоумышленник получил shell пользователя и пытается повысить привилегии». Но sudo вызывается часто — если заставить вводить TOTP на каждую команду, жить станет очень тяжело.
Рабочий компромисс:
- оставить кэширование
sudo(timestamp) на 5–15 минут; - запрашивать TOTP только при «первом»
sudoпосле истечения кэша; - не распространять 2FA на сервисные аккаунты и неинтерактивные сценарии.
Вариант: добавить pam_google_authenticator в /etc/pam.d/sudo
Обычно достаточно добавить строку в /etc/pam.d/sudo (а иногда отдельно в /etc/pam.d/sudo-i, если у вас активно используется sudo -i). После этого при запросе пароля sudo дополнительно спросит TOTP.
Для поэтапного включения:
auth required pam_google_authenticator.so nullok
После теста на себе можно убрать nullok для строгой политики админов.
Нюанс: неинтерактивные сценарии и отсутствие TTY
Часть задач выполняется без полноценного терминала (некоторые systemd-юниты, автоматизации). В таких случаях PAM не сможет запросить код и будет возвращать отказ, из-за чего «сломается не только sudo, а ваш пайплайн/обслуживание».
Практические правила:
- не включайте 2FA на сервисных аккаунтах;
- проверяйте, где реально нужен интерактив;
- если 2FA для sudo обязательна, документируйте аварийный способ входа и отката.

Тестирование: чек-лист перед тем, как считать задачу закрытой
Откройте новую SSH-сессию и убедитесь, что вход проходит только при наличии TOTP.
Проверьте ваши инструменты (Ansible/rsync/CI) по выбранной схеме (исключение, отдельный пользователь или bastion).
Один раз проверьте вход по scratch-коду и отметьте, что он израсходован.
Проверьте
sudo -vи поведение кэшаsudo(например, после 10 минут).Посмотрите логи на предмет отказов PAM/SSH.
Где смотреть ошибки
Для SSH:
sudo journalctl -u ssh -n 200 --no-pager
sudo journalctl -u sshd -n 200 --no-pager
sudo journalctl -t sshd -n 200 --no-pager
Для случаев с sudo часто достаточно общего журнала:
sudo journalctl -n 200 --no-pager
Откат: что делать, если что-то пошло не так
Если вы потеряли SSH-доступ из-за 2FA:
- зайдите через консоль провайдера/виртуализации;
- откатите изменения в
/etc/pam.d/sshd(удалите или закомментируйте строку сpam_google_authenticator.so); - если меняли
AuthenticationMethods, временно верните более мягкую схему (например, только ключ); - проверьте
sshd -tи перезапустите sshd.
Чтобы сбросить 2FA конкретному пользователю, обычно достаточно переименовать или удалить ~/.google_authenticator (учитывая, используете ли вы nullok и какие условия в PAM).
Практичные рекомендации по hardening вместе с 2FA
2FA лучше всего работает в связке с базовой гигиеной SSH:
- отключайте парольный вход там, где можно жить только на ключах;
- ограничивайте доступ по IP (firewall, bastion);
- включайте разумные лимиты попыток и наблюдаемость (логи, алерты);
- держите актуальные OpenSSH/PAM;
- имейте аварийный доступ (консоль, второй админ, запасные ключи).
Частые проблемы и быстрые решения
Код не принимается, хотя я уверен, что ввожу правильно
Чаще всего виновато время. Проверьте синхронизацию (NTP/chrony) и убедитесь, что на телефоне включена автоматическая синхронизация времени.
Автоматизация зависает на вводе кода
Значит, вы потребовали TOTP там, где нет интерактива. Решение: отдельный пользователь с минимальными правами, Match-исключение, или перенос «интерактивного входа» на bastion с 2FA.
Страшно потерять телефон
Минимум — сохраните scratch-коды. Лучше — предусмотрите второй независимый способ восстановления (второе устройство или защищённый менеджер секретов) и регламент замены/ротации.
Итог
Связка TOTP + pam_google_authenticator даёт быстрый и понятный способ включить реальный второй фактор для SSH и при желании усилить sudo. Главное — внедрять поэтапно, с тестом в новой сессии, планом отката и продуманными исключениями для автоматизаций. Тогда 2FA будет работать как защита, а не как источник простоя.


