PAM и вход в систему
PAM — промежуточный слой аутентификации между программами (sshd, login, sudo) и реальным механизмом проверки. Стек имеет четыре группы: auth, account, password, session. Флаги управления (required/requisite/sufficient) определяют, фатален ли сбой модуля.
Ты добавляешь LDAP-аутентификацию на сервер — и теперь локальные пользователи не могут войти, даже root. Или модуль pam_faillock блокирует учётки после трёх неверных попыток, но он останавливает и SSH-входы по ключу, и ты не понимаешь почему. Или нужно принудительно задать лимиты ресурсов (дескрипторы файлов, максимум процессов) на пользователя, а настройки ulimit сбрасываются при каждом входе. Или аудитор просит объяснить каждый шаг от ввода пароля до получения оболочки. У всех этих вопросов один ответ: PAM — промежуточный слой аутентификации, которому делегирует каждая программа входа в систему на современном Linux.
После этого урока ты сможешь читать PAM-конфиг и предсказывать результат при успехе или сбое любого модуля, объяснять четыре группы PAM и четыре флага управления, отслеживать поток входа sshd через PAM, понимать назначение nsswitch.conf для поиска пользователей и безопасно изменять PAM-стек для типовых задач.
PAM отделяет политику аутентификации от реализации. До PAM (начало 1990-х) каждая программа, требующая аутентификации пользователей — login, ftp, rsh — содержала собственный жёстко закодированный вызов функции проверки Unix-пароля. Переход с паролей на токены означал перекомпиляцию каждой программы. PAM (Pluggable Authentication Modules, 1995) вставил стандартный API между программами и бэкендами аутентификации: программы вызывают pam_authenticate(), PAM читает политику из /etc/pam.d/<service> и загружает указанные модули в рантайме.
# Каталог /etc/pam.d/: один файл на сервис
ls /etc/pam.d/
# common-auth common-account common-password common-session
# login sshd sudo su
# gdm-password cron ...
# Программы компонуются с libpam и вызывают её — они не содержат логики аутентификации
ldd /usr/bin/sudo | grep pam
# libpam.so.0 => /lib/x86_64-linux-gnu/libpam.so.0
# PAM API с точки зрения программы:
# 1. pam_start(service, user, conv, &handle) — открывает PAM-транзакцию
# 2. pam_authenticate(handle, flags) — запускает стек 'auth'
# 3. pam_acct_mgmt(handle, flags) — запускает стек 'account'
# 4. pam_open_session(handle, flags) — запускает стек 'session'
# 5. pam_end(handle, retval) — очищает ресурсы
# Функция conversation (conv) — как PAM запрашивает пароль или токен у пользователя;
# программа обеспечивает ввод-вывод, PAM обеспечивает политикуЭта архитектура означает, что 2FA, LDAP, смарт-карты или биометрию можно добавить во все программы входа одновременно, отредактировав конфигурацию PAM — без перекомпиляции чего-либо. Она также означает, что ошибка в конфигурации PAM может одновременно сломать все каналы аутентификации.
PAM имеет четыре группы модулей (называемых «типами»), каждая с отдельной ролью. PAM-конфиг — это последовательность строк, каждая привязывает один модуль к одной группе.
# Формат файла: type control module [arguments]
cat /etc/pam.d/sshd
# @include common-auth <- включить общий стек auth
# @include common-account
# @include common-session
# session optional pam_motd.so motd=/run/motd.dynamic
# session optional pam_motd.so noupdate
# session required pam_limits.so <- применить ulimits
# session [success=1 default=ignore] pam_succeed_if.so service in cron quiet use_uid
# session required pam_unix.so
# Четыре группы:
# auth: Является ли пользователь тем, за кого себя выдаёт? (пароль, токен, ключ)
# account: Разрешён ли вход этой учётке? (истекла? заблокирована? временное ограничение?)
# password: Как меняются пароли (вызывается passwd(1))
# session: Что происходит после успешной аутентификации (монтирование home, лимиты, лог, env)Директива @include подтягивает /etc/pam.d/common-auth, /etc/pam.d/common-account и т.д. Debian/Ubuntu централизует общую политику в этих common-файлах, чтобы включение 2FA в common-auth применялось ко всем сервисам сразу. На RHEL/Fedora authselect управляет этими общими файлами через профили.
Флаги управления определяют, является ли сбой модуля фатальным или пропускаемым. Это тончайшая часть PAM и источник большинства ошибок конфигурации.
# Четыре основных флага управления:
# required: модуль запускается; сбой означает провал стека — НО продолжать выполнение
# оставшихся модулей (чтобы вызывающий не знал, КАКОЙ модуль провалился)
# requisite: как required, но при сбое НЕМЕДЛЕННО завершает стек
# (останавливает стек; быстрый сбой; раскрывает позицию сбоя)
# sufficient: если этот модуль УСПЕШЕН (и ни один required до него не провалился),
# стек немедленно успешен — пропустить оставшиеся модули
# optional: запустить; игнорировать возвращаемое значение для прохода/провала
# Читаем /etc/pam.d/common-auth:
cat /etc/pam.d/common-auth
# auth [success=1 default=ignore] pam_unix.so nullok
# auth requisite pam_deny.so
# auth required pam_permit.so
# Скобочная нотация [success=N default=ignore] — обобщение:
# success=1 означает "при успехе пропустить СЛЕДУЮЩУЮ 1 строку" (прыгнуть через pam_deny.so)
# default=ignore означает "при любом другом результате игнорировать и продолжать"
# Вместе: если pam_unix.so (проверка пароля) успешен, прыгнуть через pam_deny.so
# и попасть на pam_permit.so (явный успех). Если pam_unix.so провалился,
# упасть в pam_deny.so (явный провал).
# Почему 'required' продолжает при сбое вместо немедленной остановки:
# Защита от атаки по времени. Если сбой проверки пароля выходит немедленно,
# а проверка LDAP занимает 200 мс, атакующий по измерению времени аутентификации
# может определить, существует ли пользователь локально или удалённо.
# Запуск всех required-модулей независимо от результата нормализует время.Различие requisite и required важно: используй requisite в начале стека, когда быстрый сбой безопасен и желателен (например, проверка, что учётка не заблокирована, до запроса пароля). Используй required, когда нужна нормализация времени или последующие модули должны выполняться независимо от результата.
nsswitch.conf управляет ТЕМ, ГДЕ ищется информация о пользователях и группах — ещё до запуска PAM. PAM аутентифицирует, но не может аутентифицировать пользователя, которого нет в службе имён.
# /etc/nsswitch.conf: конфигурация переключателя служб имён
cat /etc/nsswitch.conf
# passwd: files systemd
# group: files systemd
# shadow: files
# hosts: files mdns4_minimal [NOTFOUND=return] dns myhostname
# networks: files
# ...
# 'passwd: files systemd' означает:
# 1. Сначала искать в /etc/passwd ('files')
# 2. Если не найдено, спросить systemd-userdb ('systemd') — обрабатывает systemd-homed пользователей
# В корпоративной среде: 'passwd: files ldap' добавляет LDAP как запасной вариант
# getent учитывает nsswitch — grep /etc/passwd не учитывает
getent passwd alice # проверяет files, затем systemd
grep '^alice:' /etc/passwd # проверяет только /etc/passwd
# Порядок разрешения важен: 'files' первым означает, что локальные пользователи
# перекрывают LDAP-пользователей с тем же именем — важное свойство безопасности
# 'ldap' перед 'files' позволил бы LDAP перекрывать локальных пользователей (опасно)
# При добавлении LDAP:
# passwd: files ldap — локальные пользователи всегда побеждают; LDAP заполняет пробелы
# group: files ldap
# shadow: files — shadow ОСТАЁТСЯ только files (LDAP обрабатывает свою аутентификацию)
# Тест разрешения имён без входа в систему
getent passwd someuser # работает даже для LDAP-пользователей
id someuser # разрешает UID, GID, все группыСлой nsswitch отвечает на вопрос «существует ли этот пользователь и каковы его UID/GID?». Затем PAM отвечает на вопрос «может ли этот пользователь аутентифицироваться?». Оба необходимы для успешного входа. Классическая ошибка при добавлении LDAP — поставить ldap перед files в nsswitch, что позволяет скомпрометированному LDAP-серверу затенять локальных root или системные учётки.
Группа session — место настройки пост-аутентификационного окружения — и источник многих производственных неожиданностей. pam_limits.so применяет настройки ulimit; pam_env.so задаёт переменные окружения; pam_unix.so в session логирует вход в wtmp/utmp.
# pam_limits.so читает /etc/security/limits.conf и /etc/security/limits.d/
cat /etc/security/limits.d/myapp.conf
# @myapp soft nofile 65536
# @myapp hard nofile 131072
# @myapp soft nproc 4096
# @myapp hard nproc 8192
# domain: @group означает всех членов группы 'myapp'
# type: soft = по умолчанию; hard = потолок (пользователь может поднять до hard)
# item: nofile = открытые файловые дескрипторы; nproc = максимум процессов
# Эти лимиты применяются pam_limits.so в группе session
# Они применяются ТОЛЬКО к сессиям входа, проходящим через PAM
# Если сервис использует systemd, используй вместо этого директивы unit-файла:
# LimitNOFILE=65536 <- в секции [Service] unit-файла
# pam_env.so: задать переменные окружения из файла
cat /etc/security/pam_env.conf
# EDITOR DEFAULT=/usr/bin/vim
# TZ DEFAULT=UTC
# Трассировка полного SSH-входа через PAM (логическая последовательность):
# 1. sshd аутентифицирует ключ/пароль
# 2. sshd вызывает pam_authenticate() -> запускает стек common-auth
# 3. sshd вызывает pam_acct_mgmt() -> запускает common-account (учётка валидна?)
# 4. sshd вызывает pam_open_session() -> модули session:
# - pam_loginuid.so (устанавливает /proc/self/loginuid — аудит-подсистема)
# - pam_limits.so (применяет limits.conf)
# - pam_env.so (задаёт окружение)
# - pam_unix.so (логирует в utmp/wtmp)
# - pam_motd.so (выводит /etc/motd)
# Sufficient-модуль в стеке auth, замыкающий цикл session:
# pam_sss.so (SSSD) с 'sufficient': если LDAP-аутентификация успешна,
# pam_unix.so (проверка локального пароля) пропускается — но session всё равно
# выполняется полностью, потому что модули session НЕ замыкаются 'sufficient' в authФлаг sufficient в группе auth НЕ пропускает модули session. Session всегда выполняется полностью после успеха auth. Это удивляет операторов, добавляющих sufficient LDAP-модуль в надежде обойти pam_limits — они применяются всё равно.
Диагностика ситуации, когда SSH-аутентификация по ключу успешна, но пользователь немедленно отключается.
Симптом: ssh alice@server показывает баннер, затем отключается с «Connection closed by server». Аутентификация по паролю тоже не работает. Root SSH работает нормально.
# Шаг 1: проверить лог аутентификации sshd
journalctl -u ssh --since '5 minutes ago' | grep alice
# Jun 21 14:50:01 server sshd[9821]: Accepted publickey for alice from 10.0.0.5
# Jun 21 14:50:01 server sshd[9821]: pam_unix(sshd:session): session opened for user alice
# Jun 21 14:50:01 server sshd[9821]: pam_limits(sshd:session): Could not set limit for 'nproc'
# Jun 21 14:50:01 server sshd[9821]: fatal: PAM: pam_open_session(): System error
# Jun 21 14:50:01 server sshd[9821]: Disconnected from user alice 10.0.0.5
# Аутентификация прошла! Отключение происходит в группе SESSION, а не auth.
# pam_limits.so не смог применить лимит nproc.
# Шаг 2: проверить limits.conf
grep -r 'alice\|@alice\|nproc' /etc/security/limits.conf /etc/security/limits.d/
# @myapp hard nproc 4096
# alice состоит в группе 'myapp' — этот лимит сам по себе нормален
# Шаг 3: проверить жёсткий лимит на уровне системы
cat /proc/sys/kernel/pid_max
# 32768
# limits.conf задал nproc hard=4096, что нормально
# Шаг 4: проверить, есть ли у alice личный лимит
grep 'alice' /etc/security/limits.d/*.conf
# alice hard nproc 0
# Нашли! Кто-то задал nproc hard=0 для alice конкретно — ни одного процесса нельзя
# Шаг 5: исправить и проверить
sudo sed -i '/alice.*nproc.*0/d' /etc/security/limits.d/alice.conf
# Повторить SSH — alice теперь получает оболочку
# Урок: сбои PAM в группе session выглядят для пользователя как сбои аутентификации,
# но логируются по-другому. Всегда проверяй журнал sshd на строки pam_*,
# а не только на строку 'Accepted'.▸Почему это работает
Почему RHEL использует authselect вместо прямого редактирования PAM-файлов? На RHEL 8+ authselect управляет файлами common-auth, common-account, common-session и common-password через профили (такие как sssd, winbind, nis). Прямое редактирование common-файлов означает, что authselect перезапишет твои изменения при следующем запуске, ломая стек аутентификации незаметно. Правильный рабочий процесс для RHEL: authselect select sssd --force (переключить на SSSD-профиль), затем authselect apply-changes. В Debian/Ubuntu аналогичного инструмента нет — ты редактируешь common-файлы напрямую. Именно поэтому PAM-руководства для Debian часто ломаются на RHEL.
▸Частая ошибка
Наиболее опасная ошибка в PAM — добавление sufficient модуля в начало стека auth без понимания последствий: если этот модуль возвращает успех для ЛЮБОГО пользователя (включая root), весь остальной стек аутентификации обходится. Классический инцидент: оператор добавил auth sufficient pam_permit.so в common-auth «временно», чтобы отладить проблему входа, и забыл удалить. Следующие три дня любой мог зайти на сервер по SSH без пароля — pam_permit.so всегда возвращает PAM_SUCCESS. Всегда тестируй изменения PAM из второй открытой SSH-сессии перед закрытием первой; никогда не используй pam_permit.so в продакшне.
Стек auth PAM: (1) required pam_faillock.so preauth, (2) sufficient pam_unix.so, (3) required pam_deny.so. Пользователь вводит верный локальный пароль. Какие модули выполняются и каков результат?
PAM отделяет политику аутентификации от реализации. Программы вызывают libpam; PAM читает /etc/pam.d/<service> и запускает настроенные модули. Четыре группы: auth (проверка идентичности), account (проверка политики — заблокирована? истекла?), password (смена учётных данных), session (настройка окружения: лимиты, env, журнал). Четыре флага управления: required (сбой продолжает стек, весь результат провален), requisite (сбой немедленно останавливает стек), sufficient (успех немедленно останавливает стек — пропустить оставшиеся), optional (результат игнорируется). /etc/nsswitch.conf управляет поиском записей пользователей (files, ldap, systemd) — работает до PAM. Всегда ставь files перед ldap в nsswitch. Модули session (pam_limits.so, pam_env.so) НЕ пропускаются sufficient в группе auth — они всегда выполняются после успеха auth. Изменяй PAM из второй открытой сессии и тестируй перед закрытием первой.
Практика
Начни сверху. Задачи идут от простого к сложному: вспомнить факт, применить к случаю, затем senior-уровень. Открой, попробуй, потом открой ответ.
Что-то непонятно?
Задай вопрос по этому уроку. Вопросы анонимны и попадают напрямую автору — урок станет лучше.