На недавно прошедшей конференции Zeronights я рассказывал про двухфакторную аутентификацию, и какие проблемы могут быть при ее внедрении. К сожалению, времени выступления для полного погружения в тему было мало, поэтому я постараюсь раскрыть некоторые детали в рамках отдельных постов.
И начну с самой популярной темы, а именно двухфакторной аутентификации на linux — какие есть варианты настройки и почему даже очень хорошее решение требуется доработать напильником.
Как я и говорил, тема 2fa для linux очень популярна, есть много решений, которые позволяют это сделать.
Существует два принципиальных способа сделать второй фактор при аутентификации через ssh. Первый способ — псевдо-второй фактор, запуск произвольного бинарника после стадии авторизации через опцию ForceCommand в настройках sshd:
www.openssh.com/txt/release-4.4
Что хорошо в этом методе? Появилась поддержка с openssh4.4, поэтому вы вряд ли найдете сервер, где нельзя будет включить соответствующие настройки. На этом плюсы по сути заканчиваются. Минусов гораздо больше:
Именно поэтому рассмотрим второй способ, который приводится в большинстве инструкций. Конечно же, это настройка с помощью pam-модуля.
Классический сценарий представляет собой модификацию конфига /etc/pam.d/sshd, когда в самое начало помещают нужный модуль примерно в таком формате:
Но это не решает проблему, если аутентификация выполняется не на уровне pam’а, а выполняется средствами самого sshd-сервера, например аутентификация по ключам. Как же быть в таком случае?
К счастью, в openssh начиная с версии 6.2 сделали нативную поддержку второго фактора. Теперь с помощью опции AuthenticationMethods можно перечислить методы аутентификации, которые обязательно должны быть успешно пройдены для входа на сервер:
lwn.net/Articles/544640
Пример подобной конфигурации:
Что же здесь написано? Для успешной аутентификации нужно пройти одну из следующих комбинаций:
Но где подключить модуль для двухфакторной аутентификации? Он подключается в методе keyboard-interactive. То есть если вы хотите, чтобы после аутентификации через publickey требовалось подтверждение через тот же модуль google auth, то задаете в конфиге sshd следующее:
а в файле pam.d/sshd указываете:
Все довольно просто. Но ровно до тех пор, пока вы не захотите включить несколько методов аутентификации, при этом если один метод выполняется на уровне самого sshd (publickey или kerberos), а другой — на уровне pam (тот же password). Проблема заключается в том, что и password и keyboard-interactive обрабатываются в одном и том же pam-конфиге. Нужно как-то научиться отделять первую стадию аутентификации от второй в случае такого конфига sshd:
Я долго искал решение этой проблемы, и наткнулся на описание того, как 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, и с какими опциями можно подключать модуль. Все помнят стандартные варианты подключения в секции auth — required, requisite, sufficient, optional.
Но эксперименты по комбинированию модулей только с этими опциями ни к чему не привели. Либо у пользователя будут запрашивать пароль даже в случае аутентификации по ключу, либо можно будет обойти второй фактор повторным вводом пароля.
И тогда мы начинаем настраивать pam более тонко. Для каждого результирующего статуса можно указывать своё влияние на стэк, в т.ч. «пропускать» один или несколько подключаемых модулей.
Например, вот таким образом.
У себя при настройки двухфакторки через duosecurity мы сделали ровно такую конфигурацию pam, так как было замечено, что в случае невозможности интерактивного взаимодействия с пользователем модуль возвращает как раз PAM_ABORT. То есть аутентификация начинает выглядеть так:
А конфиг pam.d/sshd становится вот таким:
Рассмотрим, что происходит в первом варианте возможной аутентификации — 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
Вот пример такой конфигурации:
И что мы выяснили в результате тестирования? Что если пользователь, согласно примеру, состоит в группе 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:
В итоге если модуль подключается не на стадии keyboard-interactive, то будет возвращаться ответ PAM_AUTHINFO_UNAVAIL, который обрабатывается в стеке. Во всех остальных вариантах будет переход на pam_deny или pam_permit в случае успеха.
Также для тех учетных записей, которые используются в сервисах и соответственно должны входить по одному фактору, можно сделать отдельные настройки с помощью параметра Match в конфиге sshd.
Подводя итог скажу, что даже очень хорошее решение часто требуется немного доработать напильником, чтобы для пользователей это не стало вот таким:
![](https://habrastorage.org/r/w1560/files/290/e81/abf/290e81abf7d64360ba768955547c456e.png)
Также готов поделиться еще информацией про двухфакторную аутентификацию, например, какие грабли были в момент интеграции или описать интересные фичи провайдера duosecurity.
И начну с самой популярной темы, а именно двухфакторной аутентификации на 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.
Подводя итог скажу, что даже очень хорошее решение часто требуется немного доработать напильником, чтобы для пользователей это не стало вот таким:
![](https://habrastorage.org/files/290/e81/abf/290e81abf7d64360ba768955547c456e.png)
Также готов поделиться еще информацией про двухфакторную аутентификацию, например, какие грабли были в момент интеграции или описать интересные фичи провайдера duosecurity.