ADG Оставить заявку
Блог Информационная безопасность 5 мин чтения

TOTP через PAM и FreeRADIUS: единый второй фактор для SSH и OpenVPN

Подключаем TOTP-аутентификацию через linux-pam-google-authenticator и FreeRADIUS к SSH и OpenVPN: один second factor, никакой замены клиентов, и подводные камни с time skew.

Контекст момента

Усиление требований к аутентификации при удалённом доступе на фоне роста атак credential stuffing в 2020 году

С начала года атаки типа credential stuffing заметно участились - слитые базы логинов и паролей скармливаются в автоматизированные переборщики, и серверы с доступом по паролю начинают светиться в логах тысячами попыток из десятков адресов одновременно. Нескольким клиентам мы уже закрыли SSH по ключам и включили fail2ban, но этого теперь недостаточно: появилось требование добавить второй фактор. Не «желательно», а «обязательно, и покажите как».

Задача на первый взгляд простая. На практике - три отдельных сервиса (SSH, OpenVPN, иногда ещё веб-консоль), разные механизмы аутентификации, и пользователи, которые не хотят учиться пользоваться новым клиентом.

Почему RADIUS в середине

Первый порыв - прикрутить google-authenticator PAM-модуль прямо в /etc/pam.d/sshd и считать задачу решённой. Для одного сервера это работает. Для инфраструктуры, где SSH-серверов несколько, а OpenVPN-шлюз ещё отдельно - получаешь TOTP-секреты, размазанные по машинам, и нет единой точки управления: отозвать доступ уволенного сотрудника означает обойти всё по одному.

FreeRADIUS решает это иначе: PAM-модуль на каждом сервере делегирует проверку второго фактора в RADIUS, а секреты хранятся централизованно. Схема выглядит так:

SSH / OpenVPN -> PAM (pam_radius_auth) -> FreeRADIUS -> pam_google_authenticator

SSH и OpenVPN видят обычный интерфейс: «передай логин, пароль и OTP-код». Клиентские приложения не меняются - OpenVPN-клиент просто спрашивает пароль и OTP через обычный --auth-user-pass.

Сборка на FreeRADIUS 3.x

На CentOS 7 / RHEL 7 ставим из репозитория, на Ubuntu 18.04 - из стандартного пакетного:

# Ubuntu
apt install freeradius freeradius-utils libpam-google-authenticator

# RHEL/CentOS (с EPEL)
yum install freeradius freeradius-utils google-authenticator-libpam

В /etc/freeradius/3.0/mods-enabled/pam убеждаемся что модуль подключён. В /etc/freeradius/3.0/sites-enabled/default в секцию authenticate добавляем pam и убираем всё лишнее - для изолированного MFA-сервера нам не нужны ldap/sql/files на этапе проверки пароля.

Файл /etc/pam.d/radiusd настраиваем так:

auth requisite pam_google_authenticator.so secret=/etc/raddb/totp/${USER}/.google_authenticator nullok
auth requisite pam_unix.so

Порядок имеет значение: сначала TOTP, потом пароль. Если сделать наоборот - при неверном пароле TOTP всё равно проверяется, что даёт атакующему лишнее время на подбор кода.

На SSH-серверах и OpenVPN-хосте ставим libpam-radius-auth, в /etc/pam_radius_auth.conf прописываем адрес RADIUS-сервера и shared secret. В /etc/pam.d/sshd добавляем:

auth required pam_radius_auth.so

В sshd_config:

ChallengeResponseAuthentication yes
UsePAM yes

Подводные камни

Time skew - главная боль. TOTP-коды действительны 30 секунд, и если часы на RADIUS-сервере и у пользователя расходятся больше чем на 30-60 секунд - получаешь отказ аутентификации без внятного сообщения об ошибке. Пользователь видит «Access denied», а в логах /var/log/freeradius/radius.log - FAILED: Auth: Login incorrect. Мы добавили window_size 3 в конфиг pam_google_authenticator, что даёт окно в ±90 секунд, но это компромисс: слишком большое окно снижает защиту. Правильное решение - NTP на всех участниках цепочки, и проверить это надо до введения MFA в продакшн, не после.

Fallback-логика - осторожно. В pam_google_authenticator есть опция nullok, которая пропускает пользователей без настроенного TOTP. На время миграции это удобно: сотрудники переходят на второй фактор постепенно, а те, кто ещё не настроил, заходят как раньше. Проблема в том, что nullok легко забыть убрать после завершения миграции. Мы фиксируем это в чеклисте: когда последний пользователь настроил TOTP - убрать nullok, перезапустить FreeRADIUS, проверить. Иначе через полгода обнаружится, что два технических аккаунта так и ходят без второго фактора.

OpenVPN и диалог пароля. Клиенты OpenVPN при --auth-user-pass выводят один диалог «Username / Password». OTP-код нужно передавать либо конкатенацией пароль\nOTP в поле пароля, либо через static-challenge в конфиге клиента. Второй вариант чище - добавляем в серверный .ovpn:

static-challenge "Enter OTP code" 1

Тогда клиент показывает два отдельных поля. Это работает в стандартном OpenVPN-клиенте и в Tunnelblick, проверяли.

Хранение секретов. Файлы .google_authenticator с TOTP-ключами лежат на RADIUS-сервере в директориях пользователей. Права - 0400, владелец root. Создавать их можно скриптом: google-authenticator --time-based --disallow-reuse --force --quiet --qr-mode=none выводит секрет и recovery-коды в stdout, которые можно передать пользователю по защищённому каналу. Интерактивный режим тоже работает, но у нас пользователей достаточно, чтобы это автоматизировать.

Что получилось

На двух клиентских площадках схема работает второй месяц. Пользователи входят в OpenVPN через штатный клиент, SSH - через PuTTY или терминал, никаких новых программ устанавливать не пришлось. Администрирование централизованное: добавить нового пользователя - создать файл с секретом на RADIUS, убрать доступ - удалить файл. Rotate секрет - перегенерировать файл и передать пользователю новые коды.

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

Контакт

Нужна такая же инженерная работа?

Опишите задачу и контекст. Ответим в течение рабочего дня, при необходимости подпишем NDA.