На недавно прошедшей конференции 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.

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



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

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


  1. drMildrex
    02.03.2017 20:05
    +1

    Интересная статья, особенно полезно про включение нескольких методов аутентификации. Несколько лет назад я проводил сравнение поставщиков: https://habrahabr.ru/post/238589/. У Duo много плагинов, но их решение дорогое. Выбрал Protectimus для своих проектов. Позже озадачился вопросом защиты SSH, но собственных модулей у них не оказалось, поэтому настроил модуль Google Authenticator, но с секретными ключами токенов от Protectimus. А сейчас у них появились новые перепрограммируемые токены, которые могут использоваться как замена Google Authenticator и других TOTP совместимых токенов, так что, в некотором плане, работать стало даже проще.


    1. videns
      02.03.2017 21:11

      Да, находил вашу статью, когда выбирал решение. Имхо критериев мало, например не рассмотрено наличие admin api, какие типы интеграций поддерживаются и тд. Если будут заинтресованные, то готов детальнее рассказать, какие критерии мы рассматривали при выборе решения у себя


  1. TaHKucT
    02.03.2017 22:31

    У меня вопрос\уточнение по формату статьи: мне всегда казалось что правильный формат использования selinux это:
    1) Выключаем selinux
    2) Настраиваем сервис
    3) Пишем документ с названием 'Политика ...'
    4) Настраиваем selinux на основании п.3
    5) Включаем новые политики на тестовом сервере
    6) На основании п. 5 редактируем п. 3 и п. 4
    7) Повторяем п. 6 до получения нужного результата


    Причем мне всегда казалось что логично поручить п. 2, 3 и 4-7 разным людям что бы они могли "родить истину в споре".
    Что думаете о подобном подходе к проблеме?