Опыт внедрения 2fa на linux с duosecurity

На недавно прошедшей конференции Zeronights я рассказывал про двухфакторную аутентификацию, и какие проблемы могут быть при ее внедрении. К сожалению, времени выступления для полного погружения в тему было мало, поэтому я постараюсь раскрыть некоторые детали в рамках отдельных постов.
И начну с самой популярной темы, а именно двухфакторной аутентификации на linux — какие есть варианты настройки и почему даже очень хорошее решение требуется доработать напильником.

Варианты настройки


Как я и говорил, тема 2fa для linux очень популярна, есть много решений, которые позволяют это сделать.

Существует два принципиальных способа сделать второй фактор при аутентификации через ssh. Первый способ — псевдо-второй фактор, запуск произвольного бинарника после стадии авторизации через опцию ForceCommand в настройках sshd:

www.openssh.com/txt/release-4.4

Что хорошо в этом методе? Появилась поддержка с openssh4.4, поэтому вы вряд ли найдете сервер, где нельзя будет включить соответствующие настройки. На этом плюсы по сути заканчиваются. Минусов гораздо больше:

  • если у вас включен tcpportforward, то выполнить проброс порта можно в обход существующих проверок в бинарнике;
  • все, что прописано в rc-файле, будет выполнено до ForceCommand, поэтому там можно прописать выполнение exec sh и не подтверждать каждый раз вход вторым фактором;
  • когда пользователь будет подключаться к серверу через sftp или scp, то он не увидит никакого приглашения, которое вы можете напечатать в случае стандартного входа с помощью ssh-клиента. Поэтому удобство для пользователя в этом случае практически сводится на нет.

Именно поэтому рассмотрим второй способ, который приводится в большинстве инструкций. Конечно же, это настройка с помощью pam-модуля.

Второй фактор через pam


Классический сценарий представляет собой модификацию конфига /etc/pam.d/sshd, когда в самое начало помещают нужный модуль примерно в таком формате:

auth required pam_google_authenticator.so

Но это не решает проблему, если аутентификация выполняется не на уровне pam«а, а выполняется средствами самого sshd-сервера, например аутентификация по ключам. Как же быть в таком случае?

Authentication Methods


К счастью, в openssh начиная с версии 6.2 сделали нативную поддержку второго фактора. Теперь с помощью опции AuthenticationMethods можно перечислить методы аутентификации, которые обязательно должны быть успешно пройдены для входа на сервер:

lwn.net/Articles/544640

Пример подобной конфигурации:

AuthenticationMethods publickey,password hostbased,publickey

Что же здесь написано? Для успешной аутентификации нужно пройти одну из следующих комбинаций:

  • publickey+password
  • или hostbased+publickey

Но где подключить модуль для двухфакторной аутентификации? Он подключается в методе keyboard-interactive. То есть если вы хотите, чтобы после аутентификации через publickey требовалось подтверждение через тот же модуль google auth, то задаете в конфиге sshd следующее:

AuthenticationMethods publickey,keyboard-interactive

а в файле pam.d/sshd указываете:

auth required pam_google_authenticator.so

Все довольно просто. Но ровно до тех пор, пока вы не захотите включить несколько методов аутентификации, при этом если один метод выполняется на уровне самого sshd (publickey или kerberos), а другой — на уровне pam (тот же password). Проблема заключается в том, что и password и keyboard-interactive обрабатываются в одном и том же pam-конфиге. Нужно как-то научиться отделять первую стадию аутентификации от второй в случае такого конфига sshd:

AuthenticationMethods password,keyboard-interactive publickey,keyboard-interactive

Я долго искал решение этой проблемы, и наткнулся на описание того, как facebook сделали у себя второй фактор:

www.slideshare.net/yandex/004-tim-tickelchadgreene2fac
www.youtube.com/watch? v=pY4FBGI7bHM

Когда безопасники из facebook рассказывали про внедрение 2fa у себя, то они ссылались на подметоды аутентификации — Authentication Submethods. Это позволяет ограничить аутентификацию заданным устройством. В итоге у коллег получилось указать аутентификацию для keyboard-interactive именно через duo:

lwn.net/Articles/544640 (комментарий от dugsong)

Но попытки найти эти коммиты или нужную версию openssh — 6.2p1 не увенчались успехом. Поэтому решено было изучать проблематику дальше.

Эксперименты с настройкой pam


Тут мы снова вспоминаем, что же такое стэк pam, и с какими опциями можно подключать модуль. Все помнят стандартные варианты подключения в секции auth — required, requisite, sufficient, optional.

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

И тогда мы начинаем настраивать pam более тонко.
Для каждого результирующего статуса можно указывать своё влияние на стэк, в т.ч. «пропускать» один или несколько подключаемых модулей.

Например, вот таким образом.

auth [success=1 default=ignore] pam_radius_auth.so

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

AuthenticationMethods gssapi-with-mic,keyboard-interactive password,keyboard-interactive

А конфиг pam.d/sshd становится вот таким:

auth [success=2 abort=ignore default=1] /lib64/security/pam_duo.so
auth [success=1 default=ignore] pam_unix.so nullok_secure
auth requisite pam_deny.so
auth required pam_permit.so

Рассмотрим, что происходит в первом варианте возможной аутентификации — gssapi-with-mic, keyboard-interactive

Пользователь логинится по kerberos-тикету, далее нужно пройти keyboard-interactive аутентификацию. Модуль pam_duo успешно подключается, в случае успеха следует переход на PAM_PERMIT, во всех остальных вариациях — на PAM_DENY. Все довольно просто.

Что будет, если вход идет по паролю. Отрабатывает все тот же стэк pam-модулей, но pam_duo не может инициализироваться и возвращается PAM_ABORT. Т.к. у нас написано abort=ignore, то это ни на что не влияет, управление передается следующему модулю pam_unix. Если все хорошо, то идет переход ко второй стадии — keyboard-interactive, и повторяется выше описанная механика.

Да, вроде все ок. Но есть нюанс.

pam_duo позволяет задать дополнительные настройки — для кого требуется аутентификация, а для кого — нет.

duo.com/docs/duounix#duo-configuration-options

Вот пример такой конфигурации:

groups = users,!wheel,!*admin

И что мы выяснили в результате тестирования? Что если пользователь, согласно примеру, состоит в группе wheel, то модуль вернет PAM_SUCCESS, что довольно логично. Но это вернется до попытки провести инициализацию модуля, то есть даже в стадии password. Таким образом, зная логин такого пользователя, можно не просто обойти второй фактор, а зайти в систему, даже не зная пароль пользователя. В общем полный провал.

Также обнаруживаем вторую особенность. Если менять настройки не pam.d/sshd, а глобальные — password-auth, который обычно подключается в остальных модулях, то выясняется, что логин через локальную консоль происходит с возможностью подключать модули с интерактивным взаимодействием. То есть первая стадия проверки пропускается и сразу идет проверка второго фактора, где, как мы помним, можно знать просто имя учетки с группами-исключениями. Это тоже провал.

Доработка напильником


Но пути назад нет, поэтому начинаем читать исходники.
Основной файл, в котором идут все проверки:

github.com/duosecurity/duo_unix/blob/master/pam_duo/pam_duo.c

Нас интересует функция pam_sm_authenticate. По мере изучения исходников, находим, что можно вызвать функцию для взаимодействия между модулем и приложением, которое вызывает модуль (в нашем случае это будет sshd):

man7.org/linux/man-pages/man3/pam_get_item.3.html
man7.org/linux/man-pages/man3/pam_conv. 3.html

Примерно после 5 segfault (во время тестирования и подключения исправленного модуля) выясняем, какой из параметров будет отличаться в случае password и keyboard-interactive. Тестирование проводилось через попытку вывести пробел с использованием указанных функций. Указатель на resp в структуре pam_conv будет равен 0, если это удалось.

В итоге получаем модифицированный исходник, который выполняет две функции — возвращает PAM_AUTHINFO_UNAVAIL при подключении модуля в стадии password. И возвращает PAM_AUTHINFO_UNAVAIL, если имя сервиса отличается от sshd (для избежания указанной выше ситуации со входом через локальную консоль):

gist.github.com/videns/5348e3cc04fbce3a8c26fe3c99a61b50/revisions

Ну и для удобства установки на все сервера собираем пакет, например, с помощью того же fpm.

После установки модифицированного модуля нужно внести последние изменения в pam.d/sshd:

auth [success=2 authinfo_unavail=ignore default=1] /lib64/security/pam_duo.so
auth [success=1 default=ignore] pam_unix.so nullok_secure
auth requisite pam_deny.so
auth required pam_permit.so

В итоге если модуль подключается не на стадии keyboard-interactive, то будет возвращаться ответ PAM_AUTHINFO_UNAVAIL, который обрабатывается в стеке. Во всех остальных вариантах будет переход на pam_deny или pam_permit в случае успеха.

Также для тех учетных записей, которые используются в сервисах и соответственно должны входить по одному фактору, можно сделать отдельные настройки с помощью параметра Match в конфиге sshd.

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

290e81abf7d64360ba768955547c456e.png

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

Комментарии (0)

© Habrahabr.ru