Библиотеки PAM (Pluggable Authentication Module) используются для добавления сложного сценария проверки учетных данных и выполнения дополнительных действий при аутентификации пользователя и доступе к службам. В этой статье мы разберемся с внутренней архитектурой PAM, особенностями конфигурации и сделаем простой модуль для отправки уведомлений в Slack при входе пользователя в систему.

PAM впервые появился в Solaris в 1995 году и в дальнейшем был реализован для других POSIX-совместимых систем (например, PAM-Linux). В отличии от NSS, который мы рассмотрели в предыдущей статье, PAM имеет дело с процессом аутентификации, контроля срока действия пароля и его обновления, процессом создания сеанса пользователя. Библиотека PAM представляет для системных приложений интерфейс для запроса аутентификации, проверки учетной записи и замены пароля, а также для организации диалога с пользователем (например, при входе в систему с вводом пароля с клавиатуры) и сохранения данных модуля. Интерфейс может использоваться системными программами (пример можно посмотреть в исходном коде login в util-linux) и кодом модулей PAM, для этого доступны следующие заголовочные файлы:

  • security/pam_appl.h — доступ к API PAM (используется системными программами и модулями).

  • security/pam_ext.h — дополнительные функции (например, логирование, отображение подсказки для ввода текста, получения токена авторизации).

  • security/pam_misc.h — функции для работы с окружением.

  • security/pam_modules.h — сигнатуры функций для определения в модуле, типы данных для модуля.

  • security/pam_modutil.h — дополнительные функции, полезные для использования в модулях (в частности, здесь проксируются функции для работы с passwd/shadow NSS).

Конфигурация PAM собирается из нескольких источников: файла /etc/pam.conf, файлов в каталоге /etc/pam.d, системной конфигурации из файлов в /usr/lib/pam.d.

Для PAM поддерживается 4 группы действий:

  • auth — здесь выполняются действия, связанные с процессом аутентификации.

  • account — проверка учетной записи (например, срок действия пароля, ограничение входа по времени или сроку и т.д.).

  • session — здесь выполняются все дополнительные действия после успешной аутентификации (например, создание домашнего каталога, отображение сообщений о наличии новой почты и т.д.) и действия перед доступом к сервисам.

  • password — манипуляции с токеном авторизации (например, смена пароля).

Приложение запрашивает начало сеанса вызовом функции pam_start с передачей имени аутентифицируемого пользователя, названия сеанса и conversation-функции, которая будет использоваться для отображения сообщений в процессе диалога с пользователем. Для Linux-PAM часто в качестве conversation-функции используется misc_conv, для OpenPAM — openpam_ttyconv. При отправке запроса в conversation-функции указывается способ отображения и взаимодействия с пользователем (PAM_PROMPT_ECHO_OFF — ввод без эха, PAM_PROMPT_ECHO_ON — ввод с эхом, PAM_ERROR_MSG -—сообщение об ошибке, PAM_TEXT_INFO — вывод информации). Результатом вызова pam_start будет код состояния выполнения, который равен PAM_SUCCESS при успешном выполнении или соответствует коду ошибки (например, PAM_USER_UNKNOWN, если пользователь неизвестен). Также pam_start записывает в последний аргумент указатель pam_handle_t для управления сеансом (он также будет использовать в pam_end при завершении сеанса). Кроме pam_start доступна функция pam_start_confdir, которая позволяет переопределить каталог с конфигурационными файлами.

Следующие методы инициируют запуск последовательности модулей (согласно файлам конфигурации):

  • pam_authenticate(pam_handle_t *pamh, int flags) — выполняет проверку возможности аутентификации пользователя. Флаги могут содержать PAM_SILENT, если приложение хотело бы исключить взаимодействие с пользователем (неинтерактивный режим), а также PAM_DISALLOW_NULL_AUTHTOK (вернуть ошибку аутентификации, если нет сохраненного токена). Для выполнения вызывается функция pam_sm_authenticate из модулей. Модуль может получить имя пользователя через вызов pam_get_user (если было определено — вернется сразу, иначе отобразится подсказка). Также полезно использовать функции pam_strerror (преобразует код ошибки в строку), pam_getenv, pam_getenvlist, pam_putenv для работы с окружением модуля и pam_get_data, pam_set_data для долговременного сохранения данных модуля. Также может быть получены настройки PAM, например для получения conversation-функции можно запросить pam_get_item(pamh, PAM_CONV, &conv). Также через pam_get_item могут быть запрошены другие общие переменные:

    • PAM_SERVICE — название сервиса, который запросил сессию;

    • PAM_USER — имя пользователя, который аутентифицируется (или уже был аутентифицирован);

    • PAM_AUTHTOK — текущий токен аутентификации (может быть паролем);

    • PAM_OLDAUTHTOK — предыдущий токен аутентификации (только в password), например для проверки, что пароль был действительно изменен;

    • PAM_TTY — название терминала, на котором выполняется вход;

    • PAM_USER_PROMPT — текст подсказки для ввода пароля.

  • Может быть возращен один из следующих кодов ответа (кроме этого могут быть коды, связанные с недостатком памяти и внутренними ошибками):

    • PAM_SUCCESS — успешно выполнено;

    • PAM_AUTH_ERR — ошибка аутентификации;

    • PAM_CRED_INSUFFICIENT — пользователь не может получить данные аутентификации;

    • PAM_USER_UNKNOWN — пользователь неизвестен;

    • PAM_MAXTRIES — истекло количество попыток;

    • PAM_AUTHINFO_UNAVAIL — информация об аутентификации не может быть получена;

    • PAM_PERM_DENIED — пользователю запрещен вход;

    • PAM_IGNORE — действие не реализовано и было проигнорировано.

  • pam_setcred(pam_handle_t *pamh, int flags) — обновление или удаление данных входа (может использоваться для перегенерации токена в активном сеансе), использует функцию pam_sm_setcred модулей: Флаги могут содержать PAM_SILENT и одно из действий:

    • PAM_ESTABLISH_CRED — устанавливает новые данные для авторизации;

    • PAM_DELETE_CRED — удаляет сохраненные данные авторизации;

    • PAM_REINITIALIZE_CRED — запросить новый токен;

    • PAM_REFRESH_CRED — продлить действие учетных данных (получить новый токен).

    Результатом выполнения может быть один из кодов возврата:

    • PAM_SUCCESS — успешно выполнено;

    • PAM_CRED_EXPIRED — срок действия сохраненных данных истек;

    • PAM_CRED_UNAVAIL — сохраненные данные авторизации для пользователя не найдены;

    • PAM_USER_UNKNOWN — пользователь неизвестен;

    • PAM_PERM_DENIED — пользователю запрещен вход;

    • PAM_IGNORE — действие не реализовано и было проигнорировано.

  • pam_acct_mgmt(pam_handle_t *pamh, int flags) — валидация учетной записи (например, срока действия пароля). Flags может быть установлен в PAM_SILENT. Использует функцию pam_sa_acct_mgmt модуля. Может вернуть один из кодов ответа (а также общие ошибки):

    • PAM_SUCCESS — успешно выполнено, валидация пройдена;

    • PAM_NEW_AUTHTOK_REQD — требуется смена пароля пользователем;

    • PAM_AUTHTOK_EXPIRED — требуется смена пароля от администратора;

    • PAM_USER_UNKNOWN — пользователь неизвестен;

    • PAM_PERM_DENIED — пользователю запрещен вход.

  • pam_open_session(pam_handle_t *pamh, int flags) — открыта сессия после успешного входа (чаще всего вызывается после pam_authenticate), использует функцию pam_sm_open_session модуля. Flags может быть установлен в PAM_SILENT. Может вернуть один из кодов ответа (а также общие ошибки):

    • PAM_USER_UNKNOWN — пользователь неизвестен;

    • PAM_PERM_DENIED — пользователю запрещен вход.

  • pam_close_session(pam_handle_t *pamh, int flags) — завершить сессию, использует функцию pam_sm_close_session модуля. Flags может быть установлен в PAM_SILENT. Может вернуть один из кодов ответа (а также общие ошибки):

    • PAM_USER_UNKNOWN — пользователь неизвестен;

    • PAM_PERM_DENIED — пользователю запрещен вход.

  • pam_chauthtok(pam_handle_t *pamh, int flags) — смена токена (например, пароля), использует функцию pam_sm_chauthtok модуля. Flags может быть установлен в PAM_SILENT или PAM_CHANGE_EXPIRED_AUTHTOK (замена пароля только, если он устарел) и PAM_NO_AUTHTOK_CHECK (не проверять соответствие пароля установленным правилам). Может вернуть один из кодов ответа (а также общие ошибки):

    • PAM_USER_UNKNOWN — пользователь неизвестен;

    • PAM_PERM_DENIED — пользователю запрещен вход;

    • PAM_AUTHTOK_ERR — ошибка при обновлении токена.

Название модуля (несмотря на то, что он является библиотечным файлом) создается без префикса lib, например может называться pam_slack.so. Для реализации модуля создадим соответствующий файл для сборки через cmake:

cmake_minimum_required(VERSION 3.22)
project(pam_slack C)
set(CMAKE_C_STANDART 17)
set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}")
# подключить pam
find_package(PAM REQUIRED)
# не использовать префикс lib для библиотеки
set(CMAKE_SHARED_LIBRARY_PREFIX "")
target_link_libraries(${library_name} ${PAM_LIBRARIES})
include_directories(
  ${PAM_INCLUDE_DIR}
  ${CMAKE_BINARY_DIR}
  ${CMAKE_CURRENT_BINARY_DIR}
)
add_library(pam_slack SHARED library.c)

Также добавим конфигурацию для обнаружения PAM в каталог проекта: https://invent.kde.org/plasma/kscreenlocker/-/raw/master/cmake/FindPAM.cmake

Определим заголовочный файл для библиотеки (мы будем реализовывать функции для работы с сессией):

#include <security/pam_misc.h>

int pam_sm_open_session(pam_handle_t *pamh, int flags, int args, const char **argv);
int pam_sm_close_session(pam_handle_t *pamh, int flags, int args, const char **argv);

И реализуем простой вариант с отправкой сообщения в syslog:

#include "library.h"

#include <security/pam_ext.h>
#include <security/pam_modules.h>

int pam_sm_open_session(pam_handle_t *pamh, int flags, int args, const char **argv) {
  pam_syslog(pamh, 1, "Session is opened");
  char* service_name = malloc(128);
  pamh->get_item(pamh, PAM_SERVICE, &service_name);
  pam_syslog(pamh, 1, service_name);
  free(service_name);
  return PAM_SUCCESS;
}

int pam_sm_close_session(pam_handle_t *pamh, int flags, int args, const char **argv) {
  pam_syslog(pamh, 1, "Session is closed");
  char* service_name = malloc(128);
  pamh->get_item(pamh, PAM_SERVICE, &service_name);
  pam_syslog(pamh, 1, service_name);
  free(service_name);
  return PAM_SUCCESS;
}

Как можно увидеть, модули принимают список аргументов, подобно консольным приложениям, они извлекаются из файла конфигурации и указываются после названия библиотеки.Выполним сборку библиотеки и добавим правило к конфигурации pam.

cmake .
cmake --build .
cp pam_slack.so /usr/lib/x86_64-linux_gnu/security

Путь к каталогу для Debian-систем, может быть другим, можно поискать по размещению файла pam_unix.so. Посмотрим теперь на пример записи файла конфигурации:

session optional pam_motd.so motd=/run/motd.dynamic

Первое слово указывает на группу функций, к которым применяется модуль:

  • auth — аутентификация;

  • account — проверка учетной записи;

  • session — дополнительные действия;

  • password — обновление токена авторизации.

Второе слово определяет реакцию на код ответа, отличный от PAM_SUCCESS:

  • optional — код ответа будет проигнорирован, выполнение других модулей продолжится;

  • required — будет отмечено, что произошла ошибка, но выполнение других модулей продолжится;

  • requisite — при возникновении ошибки обработка последовательности вызовов модулей прервется (операция будет считаться ошибочной);

  • sufficient — получение успешного кода приводит к завершению последовательности вызовов (операция будет считаться успешной);

  • include — добавление другого файла с конфигурацией (вместо названия so-файла указывается название файла конфигурации), также могут добавлять через директиву @include название_файла.

Альтернативно вместо реакции может указываться группа правил в квадратных скобках, определяющая связь между кодом ответа (слева, записано прописными буквами), справа результат, например:

[success=ok ignore=ignore module_unknown=ignore default=bad]

Далее в строке указываются называется .so-файлов и параметры, которые передаются в соответствующие функции через argc / argv. В нашем случае создадим новый файл в /etc/pam.d:

#%PAM-1.0
session	optional	pam_slack.so

Для отладки добавим в /etc/syslog.conf строку:

*.debug     /var/log/debug.log

Теперь при успешном входе пользователя (например, можно запустить login user) в файл будут добавляться сообщения об успешном входе. Также можно увидеть, что название сервиса будет указано как login (при удаленном входе сервис будет обозначен как remote). Это поможет нам для разработки второй части нашего приложения - отправки уведомления в Slack через web-hook.

Для выполнения запроса будем использовать libcurl. Для Slack поддерживает форма web hook, в котором содержание встраивается в параметры POST-запроса. Например, при исполнении запроса в консоли он может выглядить так:

curl -s -X POST https://hooks.slack.com/services/WORKSPACEID/CHANNELID/token -H "Content-Type: application/json" --data "{\"text\":\"Пользователь зашел в систему\"}"   

Для отправки уведомления создадим отдельную функцию, которая будет принимать имя пользователя и отправлять сообщение на адрес, полученный в параметрах вызова модуля:

#include <stdio.h>
#include <string.h>
#include <curl/curl.h>
 
int notify(char* user_name, char* url)
{
  CURL *curl;
  CURLcode res;
 
  curl_global_init(CURL_GLOBAL_ALL);
 
  curl = curl_easy_init();
  if(curl) {
    curl_easy_setopt(curl, CURLOPT_URL, url);
    char* message = strcat(strcat("\"text\":\"User", user_name), " is logged in\"}");                
    curl_easy_setopt(curl, CURLOPT_POSTFIELDS, message);
    res = curl_easy_perform(curl);
    if(res != CURLE_OK)
      fprintf(stderr, "curl_easy_perform() failed: %s\n",
              curl_easy_strerror(res));
    curl_easy_cleanup(curl);
  }
  curl_global_cleanup();
  return 0;
}

И теперь выполним вызов при открытии сессии от сервиса login (локальный вход в систему):

#include "library.h"

#include <security/pam_ext.h>
#include <security/pam_modules.h>

int pam_sm_open_session(pam_handle_t *pamh, int flags, int args, const char **argv) {
  pam_syslog(pamh, 1, "Session is opened");
  char* service_name = malloc(128);
  pamh->get_item(pamh, PAM_SERVICE, &service_name);
  if (strcmp(service_name, "login")==0) {
    char* user_name = malloc(128);
    pam->get_item(pamh, PAM_USER, &user_name);
    notify(user_name, argv[0]);
    free(user_name);
  }
  pam_syslog(pamh, 1, service_name);
  free(service_name);
  return PAM_SUCCESS;
}

int pam_sm_close_session(pam_handle_t *pamh, int flags, int args, const char **argv) {
  pam_syslog(pamh, 1, "Session is closed");
  char* service_name = malloc(128);
  pamh->get_item(pamh, PAM_SERVICE, &service_name);
  pam_syslog(pamh, 1, service_name);
  free(service_name);
  return PAM_SUCCESS;
}

Расширим CMakeLists.txt:

cmake_minimum_required(VERSION 3.22)
project(pam_slack C)
set(CMAKE_C_STANDART 17)
set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}")
# подключить pam
find_package(PAM REQUIRED)
find_package(CURL REQUIRED)
# не использовать префикс lib для библиотеки
set(CMAKE_SHARED_LIBRARY_PREFIX "")
target_link_libraries(${library_name} ${PAM_LIBRARIES} ${CURL_LIBRARIES})
include_directories(
  ${PAM_INCLUDE_DIR}
  ${CURL_INCLUDE_DIR}
  ${CMAKE_BINARY_DIR}
  ${CMAKE_CURRENT_BINARY_DIR}
)
add_library(pam_slack SHARED library.c)

И повторим сборку и публикацию, но теперь укажем дополнительно адрес webhook в Slack:

session optional pam_slack.so https://hooks.slack.com/services/WORKSPACEID/CHANNELID/token

Теперь при входе пользователя будет отправляться сообщение в подключенный канал в Slack. По умолчанию в системе установлено и настроено большое количество модулей, которые отвечают за аутентификацию пользователей (например, pam_unix.so использует файлы shadow, pam_wheel.so требует принадлежности пользователя к группе администраторов wheel, pam_rootok.so будет успешным только для пользователя root), проверки пароля (например, pam_passwdqc.so или pam_pwquality.so), настройки лимитов (pam_limits.so) и значения umask (pam_umask.so), ограничения повторного входа (pam_faildelay.so), ограничения по времени входа (pam_time.so), расшифровки раздела после аутентификации (pam_ecryptfs), контроля корректности оболочки (pam_shells, проверяет shell на соответствие списку из /etc/shells), проверки пароля на сложность ко взлому (pam_cracklib), вход с использованием сертификата или смарт-карты (pam_pkcs11.so). Есть возможность подсчитывать количество логинов (pam_tally.so) и запрещать вход при наличии файла /etc/nologin (pam_nologin.so). Также есть универсальные "заглушки" pam_permit.so (всегда возвращает PAM_SUCCESS) и pam_deny.so (всегда отказ доступа), их можно использовать в конце списка, чтобы определить поведение по умолчанию. Дополнительную информацию выводит pam_mail.so (сообщение о новой локальной почте), pam_motd.so (содержимое файла /etc/motd), pam_lastlog.so (время последнего входа в систему).

Для использования внешних способов аутентификации может использоваться pam_ldap (для применения LDAP-совместимого сервера как источника учетных данных и способа проверки пароля) или pam_userdb (использование базы данных для проверки входа). Для отладки также можно использовать pam_debug, который возвращает значения из функций, предопределенные в аргументах модуля.

Также существует большое количество модулей для использования аппаратных средств аутентификации (например, Yubikey), Bluetooth-устройств (пакет libpam-blue), биометрических данных (libpam-biometric), двухфакторной аутентификации (например, пакеты libpam-u2f, libpam-otpw, libpam-google-authenticator или libpam-oath), SSH-ключей (libpam-ssh), серверов RADIUS (libpam-radius), базы данных PostgreSQL (libpam-pgsql), MySQL (libpam-mysql). Можно также установить обертку для разработки PAM-модулей на Python (libpam-python). Для защиты от подбора пароля полезно использовать libpam-abl и libpam-shield. Ну и конечно, всегда есть возможность разработать собственный PAM-модуль и в этой статье мы рассмотрели общие принципы их создания и установки в систему.


Скоро состоится открытый урок «Docker. Работа с данными и сетью (Storage и Network drivers)», на котором мы:

— Рассмотрим особенности Docker по работе с данными и сетями.
— Разберем, как устроены и какие различают способы выгрузки данных с помощью механизма volumes на примере запуска docker-контейнеров и docker-compose.
— Познакомимся со Storage и Network драйверами.
— Рассмотрим, какие бывают Storage драйверы и с какими типами хранилищ они работают, рассмотрим совместимые backing filesystem.
— Разберемся, какие docker поддерживает Network-драйверы, какие сетевые топологии они позволяют организовать и какие бывают особенности и исключения для разных ОС.

Регистрируйтесь по ссылке.

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