Зачем PAM существует
Без PAM каждое приложение читало /etc/passwd и /etc/shadow
напрямую. Чтобы добавить LDAP, Kerberos, 2FA, fail-after-N-attempts -
пришлось бы патчить ВСЕ программы. PAM решает это вынесением логики в
shared-library и стек модулей.
user → ssh login
↓
sshd вызывает pam_authenticate("sshd")↓
PAM читает /etc/pam.d/sshd
↓
Стек модулей по очереди:
pam_unix.so → проверка /etc/shadow
pam_faillock.so → счётчик неудач
pam_google_authenticator.so → 2FA
↓
«authenticated» / «failed» → возврат в sshd
Где что лежит
/etc/pam.d/<service>- конфиги для конкретных приложений (по одному на сервис:/etc/pam.d/sshd,/etc/pam.d/sudo,/etc/pam.d/login)./etc/pam.d/other- fallback для приложений без своего конфига. Стандартный - paranoid: всё запрещать. Не править на permissive./lib64/security/*.so(RHEL/Fedora) или/lib/x86_64-linux-gnu/security/*.so(Debian/Ubuntu) - сами модули./etc/security/*.conf- конфиги отдельных модулей (limits.conf, faillock.conf и т.д.).
Именование сервиса = имя файла в /etc/pam.d/. Когда sudo зовёт PAM -
читается /etc/pam.d/sudo.
Синтаксис строки конфига
type control module [arguments]
Пример из /etc/pam.d/sshd:
auth required pam_env.so
auth substack password-auth
account required pam_nologin.so
session required pam_loginuid.so
password include system-auth
Четыре type (что проверяем)
| type | Что делает |
|---|---|
auth | Проверка кто ты (пароль, токен, биометрия) |
account | Можно ли тебе сейчас входить (юзер не заблокирован, не expired, есть в группе) |
password | Смена пароля (вызывается при passwd/chpasswd) |
session | Что делать ДО/ПОСЛЕ входа: монтирование home, лимиты, audit |
Один service-файл обычно содержит все 4 типа. PAM запускает только тот тип, который нужен вызывающему приложению.
Control flags (что делать с результатом модуля)
| flag | Поведение при success | Поведение при fail |
|---|---|---|
required | продолжить стек | вернуть fail В КОНЦЕ стека (отложенно) |
requisite | продолжить стек | СРАЗУ выйти с fail (не запускать дальше) |
sufficient | СРАЗУ выйти с success (если до этого не было required-fail) | продолжить стек |
optional | продолжить стек | продолжить стек, fail игнорируется |
include | вставить весь стек из другого файла | то же |
substack | как include, но изолирует «done»-флаг для возврата | то же |
Ключевое различие required vs requisite: оба требуют успеха, но
requisite падает мгновенно - это используют когда нужно не показать
следующий prompt (если username не существует, не запрашивать пароль).
sufficient пропускает остаток стека при успехе - частая модель для
«либо ключ, либо пароль»:
auth sufficient pam_unix.so # пароль успешен → впустили
auth required pam_deny.so # иначе fallback fail
Самые частые модули
| Модуль | Что делает |
|---|---|
pam_unix.so | Проверка /etc/passwd + /etc/shadow (стандарт) |
pam_pwquality.so | Policy для нового пароля (длина, классы, словарь) |
pam_faillock.so | Блокировка после N неудачных попыток (был pam_tally2) |
pam_nologin.so | Если есть /etc/nologin - пускает только root |
pam_limits.so | Применяет /etc/security/limits.conf (ulimit'ы) |
pam_loginuid.so | Записывает audit-uid в /proc/self/loginuid |
pam_mkhomedir.so | Создаёт ~/ при первом входе (LDAP-юзеры) |
pam_env.so | Подгружает env из /etc/environment |
pam_succeed_if.so | Условный пропуск: «если uid >= 1000» |
pam_selinux.so | Установка SELinux-контекста сессии (см. selinux-apparmor) |
pam_systemd.so | Создание user.slice / XDG_RUNTIME_DIR |
pam_google_authenticator | TOTP 2FA |
Shared-стек: common-auth / system-auth
Чтобы не дублировать одни и те же модули в каждый pam.d/<service>,
есть общие файлы:
- Debian/Ubuntu:
/etc/pam.d/common-{auth,account,password,session}- подключаются через@include common-auth. - RHEL/Fedora:
/etc/pam.d/system-auth,password-auth- подключаются черезauth substack system-auth.
Менять глобальное поведение (например требование 2FA для всех консольных логинов) → правишь общий файл, не каждый сервис.
Типичные кейсы
Заблокировать юзера после 5 неудачных попыток
# /etc/pam.d/system-auth (RHEL) или /etc/pam.d/common-auth (Debian)
auth required pam_faillock.so preauth silent deny=5 unlock_time=900
auth sufficient pam_unix.so
auth [default=die] pam_faillock.so authfail deny=5 unlock_time=900
account required pam_faillock.so
Проверка: faillock --user serge. Сброс: faillock --user serge --reset.
Усилить пароли - minimum 12 chars, 3 класса
# /etc/security/pwquality.conf
minlen = 12
minclass = 3
Применяется через pam_pwquality.so который уже подключён в password-стеке.
Запретить root-логин по SSH
Не через PAM а через PermitRootLogin no в sshd_config. Через PAM - так:
# /etc/pam.d/sshd, в auth-стеке:
auth required pam_succeed_if.so user != root
Дебаг - что делать когда сломал
Главное правило: НИКОГДА не редактируй PAM-конфиг без открытой второй root-сессии. Если сломал - логинов не будет, в том числе через sudo.
# 1. Логи
sudo journalctl -t sshd -t sudo --since "10 min ago"
sudo tail -f /var/log/auth.log # Debian
sudo tail -f /var/log/secure # RHEL
# 2. Тест без перелогина
sudo -k && sudo -v # сбросить sudo-кеш и переаутентифицироваться
# 3. Проверка что модуль вообще загружается
ls /lib64/security/pam_unix.so # или /lib/x86_64-linux-gnu/security/
# 4. Документация на конкретный модуль
man pam_faillock
man pam_unix
Если сломал так что войти нельзя - boot в single-user mode (kernel cmdline:
systemd.unit=rescue.target) и починить файл.
Безопасный fallback для /etc/pam.d/<service>:
auth required pam_unix.so
account required pam_unix.so
password required pam_unix.so
session required pam_unix.so
Это вернёт «как было до PAM» - проверка только /etc/shadow.