Обзор
Вопрос «кто может войти в систему и что ему при этом полагается» в Linux разложен на три слоя, которые живут почти независимо и обычно смешиваются в голове в один. Первый отвечает за то, что такое пользователь: набор записей с числовыми идентификаторами, лежащих в файлах или приходящих из каталога. Второй — за то, как принимается решение пустить: стек модулей PAM, вызываемый программой входа. Третий — за то, что происходит после: сессия, её ресурсы и судьба процессов, которыми управляет systemd-logind.
Смешение слоёв объясняет большую часть недоумений. Пользователь есть в /etc/passwd, но войти не может — вопрос ко второму слою. Пароль принят, а команда сразу завершается — к первому (оболочка nologin). Вход прошёл, но фоновый процесс умирает при выходе — к третьему. Правка в /etc/pam.d/ дала не тот результат — почти всегда к порядку строк, а не к самому модулю.
Статья идёт по этим слоям снизу вверх. Особое внимание — PAM: это единственный из трёх, где неверная правка стоит потери доступа к машине, и единственный, чья модель вычисления результата неочевидна настолько, что «поправить по аналогии» регулярно даёт противоположный эффект.
Версии-якоря: Linux-PAM 1.7, shadow-utils 4.20, systemd 261. Там, где поведение зависит от дистрибутива или менялось между версиями, это сказано явно — это как раз та тема, где умолчания Debian и Fedora расходятся, а документация описывает upstream.
Учётные записи
UID — единственное, что видит ядро
Ядро не знает имён пользователей. Оно оперирует числами: у процесса есть UID и GID, у файла — владелец и группа, тоже числа. Имя alice существует исключительно в пространстве пользователя, как результат поиска по числу в какой-нибудь базе.
Из этого следуют вещи, которые иначе выглядят странностями:
- Две записи с одним UID и разными именами — для ядра один и тот же пользователь. Права разделяются полностью; это иногда используют намеренно (второй root с UID 0 под другим именем), и это же изредка получается по ошибке.
- Удаление пользователя не меняет владельца его файлов. Владелец остаётся числом, и
ls -lначинает показывать голый UID вместо имени. Следующий созданный пользователь, получивший тот же номер, унаследует чужие файлы. - Переименование пользователя ничего не ломает: файлы принадлежат числу, а не имени.
Три файла
/etc/passwd alice:x:1000:1000:Alice:/home/alice:/bin/bash
имя :пароль:UID:GID:GECOS:домашний каталог:оболочка
/etc/shadow alice:$y$j9T$...:20304::::::
имя:хеш:смена:мин:макс:предупр:неактив:истечение:резерв
/etc/group developers:x:1001:alice,bob
имя:пароль:GID:список дополнительных членов
Разделение passwd и shadow историческое, но существенное: /etc/passwd обязан быть читаем всем (иначе ls -l не покажет имена), а хеши паролей в общедоступном файле — это офлайновый перебор. Поэтому x во втором поле означает «смотри в /etc/shadow», доступный только root.
В полях shadow стоит разобраться отдельно, потому что через них выражается устаревание паролей. Второе поле — хеш, третье — дата последней смены в днях от 1970-01-01, дальше — минимальный и максимальный возраст пароля, срок предупреждения, срок неактивности и дата истечения учётной записи. Ноль в поле даты смены означает «сменить при следующем входе», пустое поле — устаревание отключено.
Заметное изменение: в shadow-utils 4.20 поле минимального возраста пароля объявлено устаревшим — документация говорит, что оно должно быть пустым и игнорируется. Заодно убраны сами инструменты: chage -m, passwd -n и переменная PASS_MIN_DAYS в login.defs больше не существуют, и указание любого из них теперь завершается ошибкой, а не предупреждением. Следом помечены устаревшими и остальные поля устаревания — sp_max, sp_warn, sp_inact вместе с chage -M, -W и -I; upstream ссылается на NIST SP 800-63B, который прямо запрещает требовать периодическую смену пароля. Arch перешёл на 4.20 летом 2026 года, в стабильных дистрибутивах живут более старые ветки, так что на конкретной машине проверять надо свою.
Управлять этим руками не нужно, для всего есть команды:
chage -l alice # показать параметры устаревания
chage -E 2026-12-31 alice # учётная запись истекает в конце года
passwd -l alice # заблокировать пароль
passwd -S alice # статус: L/P/NP — заблокирован/пароль/без пароля
Заблокирован, без пароля и «пароль не подойдёт»
Три состояния поля пароля различаются, и разница практическая. Восклицательный знак в начале означает заблокированный пароль: passwd -l просто дописывает ! перед хешем, и снятие блокировки возвращает прежний пароль. Звёздочка означает, что подобрать пароль невозможно в принципе — никакой ввод не даёт такой хеш; так помечают системные учётные записи. Пустое поле означает вход без пароля — и это ровно то, чего никогда не должно случиться на работающей системе.
Важная тонкость: ни !, ни * не запрещают вход другими способами. Заблокированная парольная аутентификация не мешает войти по SSH-ключу. Чтобы действительно закрыть учётную запись, меняют оболочку или ставят дату истечения.
Системные и обычные пользователи
Граница — договорённость, выраженная в /etc/login.defs. По умолчанию обычные пользователи получают UID из диапазона UID_MIN–UID_MAX, то есть 1000–60000, а системные — из SYS_UID_MIN–SYS_UID_MAX, где нижняя граница по умолчанию 101, а верхняя — UID_MIN минус единица. Дистрибутивы эти числа подправляют, но порядок величин сохраняют.
Системным учётным записям обычно ставят оболочку /usr/sbin/nologin. Разница между ней и /bin/false небольшая, но осмысленная: nologin печатает вежливое сообщение и завершается с ненулевым кодом, false молча завершается. Сообщение настраивается через /etc/nologin.txt, и в логах его наличие помогает понять, что вход отвергла именно оболочка, а не аутентификация.
Хеши паролей
Формат поля — $идентификатор$параметры$соль$хеш. Что встречается сегодня:
| Префикс | Алгоритм | Комментарий |
|---|---|---|
$y$ |
yescrypt | умолчание в Debian, Ubuntu и Fedora; устойчив к перебору на GPU |
$6$ |
sha512crypt | долгое время стандарт, upstream-умолчание shadow-utils |
$2b$ |
bcrypt | распространён в приложениях, в системном shadow реже |
$1$ |
md5crypt | безнадёжно устарел, встречается на древних системах |
Алгоритм задаётся параметром ENCRYPT_METHOD в /etc/login.defs, который принимает BCRYPT, SHA256, SHA512 или YESCRYPT. Здесь снова расходятся upstream и дистрибутивы: в самом shadow-utils умолчание — SHA512, а Debian и производные ставят YESCRYPT в своём login.defs. Отсюда встречающаяся на практике пёстрая картина: на одной машине в /etc/shadow соседствуют записи с $6$ и $y$ — старые учётные записи сохраняют прежний хеш до следующей смены пароля.
nsswitch.conf: откуда вообще берутся пользователи
Предположение «пользователи лежат в /etc/passwd» верно ровно до первой машины, подключённой к каталогу. Что именно является источником, определяет /etc/nsswitch.conf:
passwd: files systemd sss
group: files systemd sss
shadow: files
hosts: files resolve [!UNAVAIL=return] dns
Источники опрашиваются слева направо. files — привычные текстовые файлы, sss — SSSD, то есть LDAP, Active Directory или FreeIPA, systemd — модуль nss-systemd.
Последний стоит знать даже на машине без каталогов. Он отвечает за пользователей, которых нет ни в одном файле: динамические учётные записи сервисов с DynamicUser=, учётные записи systemd-homed, записи из JSON-файлов в /etc/userdb/. Он же гарантирует, что root и nobody остаются разрешимыми, даже если /etc/passwd повреждён или отсутствует. Документация рекомендует ставить systemd после files, чтобы файлы имели приоритет.
Практический вывод один, и он важнее остальных: проверять пользователей нужно через getent, а не через grep по файлу.
getent passwd alice # спросит все источники по порядку
getent group developers
getent passwd | wc -l # все видимые записи, включая каталог
id alice # UID, GID и полный список групп
grep alice /etc/passwd ничего не найдёт для доменного пользователя, который при этом прекрасно входит в систему, — и это регулярный источник путаницы при разборе чужой инфраструктуры.
PAM
Зачем он есть
До PAM каждая программа, проверявшая пароль, делала это сама: читала /etc/shadow, вычисляла хеш, сравнивала. Добавить в систему двухфакторную аутентификацию или LDAP означало пропатчить login, su, sshd, ftpd и всё остальное.
PAM (Pluggable Authentication Modules) выносит эту логику в разделяемые библиотеки, вызываемые через общий интерфейс. Программа говорит «аутентифицируй пользователя», а из каких модулей это сложится — вопрос конфигурации в /etc/pam.d/, где на каждый сервис приходится файл со своим именем.
Четыре независимых стека
Первое, что нужно уложить в голове: типов не четыре фазы одного процесса, а четыре независимых стека, каждый со своим набором строк и своим результатом.
| Тип | Отвечает за | Типичное содержимое |
|---|---|---|
auth |
доказательство личности | проверка пароля, токен, отпечаток |
account |
право входить сейчас | срок действия записи, время суток, источник |
password |
смену пароля | требования к сложности, запись нового хеша |
session |
обвязку сессии | монтирование домашнего каталога, лимиты, логирование |
Успешная аутентификация ничего не говорит о том, что вход разрешён: account может отвергнуть пользователя с истёкшей записью, у которого пароль абсолютно верный. Симптом «пароль принимается, но вход не проходит» — это почти всегда account, а не auth, и искать надо в другом стеке.
Флаги управления и почему порядок критичен
Каждая строка — это тип, флаг управления, модуль и его параметры:
auth required pam_unix.so try_first_pass
Флагов четыре, и их семантика неочевидна:
| Флаг | При неудаче модуля | При успехе |
|---|---|---|
required |
стек продолжается, но итог уже провален | продолжается |
requisite |
немедленный выход с ошибкой | продолжается |
sufficient |
игнорируется, стек продолжается | немедленный успех, если раньше не падал required |
optional |
важно только если модуль в стеке единственный | то же |
Главная неочевидность — required. Провал не прерывает стек: остальные модули всё равно отработают, а отказ вернётся в конце. Сделано это намеренно, чтобы по времени ответа и поведению нельзя было определить, на каком именно шаге вход отвергнут. requisite ведёт себя «как ожидается» и прерывает сразу — его используют там, где продолжать бессмысленно или опасно.
Второй источник ошибок — sufficient. Он даёт немедленный успех только если ни один предшествующий required не провалился. Отсюда прямое следствие: sufficient, поставленный выше нужного required, открывает вход в обход проверки, а поставленный ниже — может не сработать вовсе. Порядок строк здесь не стилистика, а логика.
Полезно знать, что простые флаги — это сокращения. Документация приводит точные соответствия:
required [success=ok new_authtok_reqd=ok ignore=ignore default=bad]
requisite [success=ok new_authtok_reqd=ok ignore=ignore default=die]
sufficient [success=done new_authtok_reqd=done default=ignore]
optional [success=ok new_authtok_reqd=ok default=ignore]
Развёрнутая форма [значение=действие ...] умеет больше: die — прервать с ошибкой, done — прервать с успехом, ignore — не влиять на итог, а число означает «пропустить столько следующих строк». Именно числовые переходы делают сложные стеки нечитаемыми, и именно они встречаются в конфигурации, которую генерируют дистрибутивные инструменты.
include и substack
Строки не пишут заново для каждого сервиса — общие куски выносят. Debian и производные держат их в common-auth, common-account, common-password, common-session; RHEL и Fedora — в system-auth и password-auth.
Разница между двумя способами подключения тонкая, но иногда решающая. include вставляет строки так, будто они написаны здесь: переход die внутри включённого файла обрывает весь стек. substack изолирует: done и die внутри прерывают только вложенный стек, числовые переходы не выпрыгивают наружу, а сам подстек снаружи считается одним модулем при подсчёте пропускаемых строк.
Практически это значит, что заменять include на substack (и наоборот) «для порядка» нельзя — меняется поведение при отказах.
Разбор реального стека
Пример из документации pam_faillock — хорошая иллюстрация того, как выглядит настоящая конфигурация:
auth required pam_securetty.so
auth required pam_env.so
auth required pam_nologin.so
auth [success=1 default=bad] pam_unix.so
auth [default=die] pam_faillock.so authfail
auth sufficient pam_faillock.so authsucc
auth required pam_deny.so
Читается это так. Три первых модуля — предварительные проверки: терминал, окружение, наличие файла /etc/nologin. Дальше pam_unix проверяет пароль, и здесь начинается интересное: при успехе (success=1) пропускается одна следующая строка, то есть управление уходит на pam_faillock authsucc, который сбрасывает счётчик неудач и возвращает успех. При неудаче срабатывает default=bad, выполнение доходит до pam_faillock authfail, тот записывает неудачную попытку и обрывает стек через default=die.
Завершающий pam_deny.so — обязательный элемент: он гарантирует отказ, если управление дошло до конца стека, не встретив успеха. Стек auth, заканчивающийся ничем, вернул бы успех.
Из этого разбора видно, почему «добавить строку в /etc/pam.d/» — операция с последствиями. Вставка строки в середину сдвигает всё, на что указывают числовые переходы, и success=1 начинает пропускать не тот модуль.
Модули, которые встречаются постоянно
pam_unix — классическая проверка по /etc/shadow; он же участвует в смене пароля и в стеке сессии.
pam_faillock блокирует учётную запись после нескольких неудачных попыток; настраивается через /etc/security/faillock.conf. Пришёл на смену pam_tally2. Существенная деталь: root по умолчанию не блокируется, и это не недосмотр, а защита от отказа в обслуживании — иначе любой желающий мог бы запереть администратора снаружи, просто перебирая пароль root.
faillock --user alice # показать неудачные попытки
faillock --user alice --reset # сбросить счётчик
pam_limits применяет ограничения ресурсов из /etc/security/limits.conf и limits.d/ — количество открытых файлов, процессов, размер памяти. Работает в стеке session, и отсюда типичная ловушка: лимиты применяются к сессиям входа, но не к сервисам systemd — у тех свои директивы в юнит-файлах.
pam_systemd регистрирует сессию в logind, без него не будет ни XDG_RUNTIME_DIR, ни корректного учёта сессий. Тоже в session.
pam_env расставляет переменные окружения из /etc/environment и /etc/security/pam_env.conf.
Отладка без потери доступа
PAM — то место, где ошибка блокирует вход всем, включая вас. Правило одно, и оно не обсуждается: держать вторую открытую сессию с правами root на протяжении всей правки и проверять вход в третьей, не закрывая первые две. Уже открытая сессия переживает поломку конфигурации: PAM вычисляется при входе, а не постоянно.
# Терминал 1 — открыт, root, ничего не трогаем: это путь к откату
# Терминал 2 — правим конфигурацию
# Терминал 3 — проверяем вход
ssh -v user@host
Проверить стек, не подключаясь, помогает pamtester (пакет с тем же именем):
pamtester sshd alice authenticate # прогнать стек auth сервиса sshd
pamtester -v login alice acct_mgmt # проверить стек account
Отладочный вывод самих модулей включается параметром debug в строке и уходит в системный журнал:
journalctl -f -t sshd -t sudo -t su
journalctl _COMM=sshd --since "5 min ago"
Отдельно стоит помнить про /etc/nologin: если файл существует, pam_nologin не пустит никого, кроме root. Смотрит модуль и в /run/nologin — именно его systemd-user-sessions.service создаёт перед выключением и снимает после загрузки, так что искать надо оба. Файл иногда остаётся после неудачного завершения работы системы и выглядит как загадочный отказ во входе при заведомо верном пароле.
systemd-logind
Сессии и seats
logind учитывает, кто и как вошёл. Единица учёта — сессия; сессии группируются по seat — комплекту физических устройств ввода-вывода, обычно единственному (seat0). Каждой сессии соответствует cgroup, что и позволяет отслеживать её процессы как целое.
loginctl list-sessions
loginctl show-session 3 -p Type -p Remote -p Active
loginctl user-status alice
loginctl terminate-session 3
Почему процессы умирают при выходе
Классический сюрприз: запустили долгую задачу, вышли из SSH, вернулись — процесса нет. Причина в KillUserProcesses: при значении yes cgroup сессии уничтожается вместе со всеми процессами при выходе.
Здесь важное расхождение, объясняющее противоречивые советы в интернете. Upstream-умолчание — yes, оно появилось в systemd 230 (май 2016) и вызвало заметный скандал именно потому, что ломало screen, tmux и ssh-agent. Debian вернул no в своей сборке, и производные унаследовали это. Поэтому на Debian и Ubuntu процессы обычно выживают, а на системах со сборкой по умолчанию — нет.
Проверить, что у вас:
loginctl show-session --property=KillUserProcesses # без аргумента — свойства менеджера
Чинить это выключением KillUserProcesses — плохой путь: настройка глобальная и отключает учёт ресурсов сессии целиком. Правильных способов два.
Linger — разрешить пользователю иметь процессы вне сессий. Тогда для него постоянно работает экземпляр user@UID.service, живущий независимо от входов и выходов:
loginctl enable-linger alice
loginctl show-user alice -p Linger
Вынести процесс из сессии в отдельный scope под пользовательским менеджером — это то, что документация systemd предлагает для screen и tmux:
systemd-run --scope --user tmux new -d -s work
systemd-run --user --unit=backup /usr/local/bin/backup.sh
Оба варианта решают задачу правильно, в отличие от nohup и setsid: те отвязывают процесс от терминала, но не выводят его из cgroup сессии, поэтому при KillUserProcesses=yes он всё равно будет убит.
user@.service
Пользовательский экземпляр systemd — это полноценный менеджер юнитов, работающий от имени пользователя. Юниты лежат в ~/.config/systemd/user/, управление через systemctl --user, и там работают те же таймеры и зависимости, что и в системном. Общая механика юнитов разобрана в статье про systemd; здесь существенно то, что без linger этот экземпляр запускается при первом входе и останавливается после последнего выхода.
Нюансы и trade-off'ы
Правка файлов вручную. /etc/passwd, /etc/shadow и /etc/group правят через vipw, vipw -s и vigr, а не редактором напрямую. Эти обёртки берут блокировку и проверяют синтаксис перед сохранением — а сломанная строка в /etc/passwd означает, что войти не сможет никто.
Членство в группах не обновляется на лету. Добавили пользователя в группу — его текущие сессии об этом не узнают, потому что список групп фиксируется при входе. Требуется перелогин; newgrp даёт временный обходной путь в рамках одной оболочки. Регулярный источник недоумения при настройке доступа к docker или dialout.
Повторное использование UID. Удалили пользователя, создали нового — тот может получить освободившийся номер и стать владельцем файлов предшественника. При удалении лучше либо переносить файлы, либо оставлять UID зарезервированным.
Сломанный источник NSS блокирует вход. Если sss или другой модуль в nsswitch.conf перестал отвечать, обращения за пользователями начинают ждать тайм-аута. Симптом — вход занимает минуту вместо секунды, ls -l подвисает, getent passwd не возвращается. Диагностика начинается с getent, а не с PAM: если он висит, дело не в аутентификации.
Лимиты применяются не ко всему. pam_limits работает для сессий входа. Сервис systemd не проходит через PAM, и лимиты для него задаются в юните (LimitNOFILE= и родственные). Настройка limits.conf в надежде повлиять на демона — распространённая и безрезультатная процедура.
Разница между su и su -. Первая форма меняет UID, но сохраняет окружение, вторая имитирует полноценный вход и проходит другой стек PAM. Отсюда расхождения в PATH и в переменных, которые ставит pam_env.
Сравнение с FreeBSD
Модель учётных записей выглядит похоже, но устроена иначе в двух отношениях.
База пользователей компилируется. Основной файл — /etc/master.passwd, у него больше полей, чем у линуксовой пары passwd/shadow: в одной строке и хеш, и класс входа, и время смены пароля, и срок истечения записи. Текстовый файл — только источник: pwd_mkdb собирает из него хеш-базы /etc/pwd.db и /etc/spwd.db (последняя доступна только root) и заодно генерирует урезанный /etc/passwd для совместимости. Прямая правка master.passwd без последующей сборки не даёт никакого эффекта — редактируют через vipw, который вызывает pwd_mkdb сам.
Классы входа вместо разрозненных механизмов. Пятое поле записи — login class, ссылка на запись в /etc/login.conf. Класс задаёт лимиты ресурсов, переменные окружения, учёт и параметры аутентификации разом. То, что в Linux размазано между limits.conf, pam_env и pam_limits, во FreeBSD собрано в одном месте и применяется по принадлежности пользователя к классу. Файл login.conf тоже компилируется — после правки нужен cap_mkdb /etc/login.conf, иначе изменения не подхватятся.
PAM во FreeBSD есть, но это другая реализация — OpenPAM, а не Linux-PAM. Конфигурация лежит в /etc/pam.d/ (политики портов и пакетов — в /usr/local/etc/pam.d/) и выглядит знакомо, типы те же. А вот флаги расходятся: у OpenPAM их пять — добавлен binding, который при успехе обрывает цепочку с успехом, а при неудаче проваливает её целиком, — зато развёрнутой формы [значение=действие] и substack нет вовсе, так что числовые переходы из линуксовых стеков перенести не на что. Различия продолжаются в наборе модулей: части линуксовых модулей просто нет, а имена и параметры совпадают не всегда, поэтому переносить стек между системами копированием не стоит.
Управление сессиями отдельной подсистемы уровня logind не имеет: сессия завершается вместе с процессами по обычным правилам POSIX, никаких cgroup-групп и вопроса «убивать ли процессы при выходе» не возникает. Фоновый процесс, отвязанный через nohup или daemon, переживает выход всегда.
Источники
- shadow(5) и дерево shadow-utils — поля
/etc/shadow, признаки блокировки, объявление поля минимального возраста устаревшим в 4.20 - Release 4.20.0: Ibores — удаление
chage -m,passwd -nиPASS_MIN_DAYS, список отложенных на будущее удалений и обоснование через NIST SP 800-63B - login.defs(5) —
ENCRYPT_METHOD(BCRYPT,SHA256,SHA512,YESCRYPT), диапазоныUID_MIN/UID_MAX(1000–60000) иSYS_UID_MIN/SYS_UID_MAX - pam.conf(5), раздел синтаксиса — семантика флагов, их эквиваленты в форме
[значение=действие], различиеincludeиsubstack - pam_faillock(8) — блокировка после неудачных попыток, разобранный пример стека, объяснение, почему root не блокируется
- nss-systemd(8) —
DynamicUser=,systemd-homed, drop-in записи в/etc/userdb/, рекомендация ставитьsystemdпослеfiles - logind.conf(5) и loginctl(1) —
KillUserProcesses,KillExcludeUsers,enable-linger - systemd 230: смена умолчания KillUserProcesses — история изменения и возврат к
noв Debian - OpenPAM: pam.conf(5) в дереве FreeBSD — порядок поиска политик, пять флагов управления вместе с
binding, отсутствие формы[значение=действие] - FreeBSD: passwd(5), pwd_mkdb(8), login.conf(5) —
master.passwd, компиляция баз, классы входа
Связанные заметки
- Linux — безопасность: hardening, SELinux/AppArmor — DAC, capabilities и что делать с правами после того, как пользователь вошёл
- sudo, doas и sudo-rs — повышение привилегий — механизм повышения привилегий поверх этой модели; на Linux все три опираются на стек PAM
- Linux — systemd: init, сервисы, таймеры — юниты, cgroups и пользовательский экземпляр менеджера
- SSH углублённо — что вызывает стек
/etc/pam.d/sshdи как отлаживать вход, не потеряв доступ - FreeBSD — безопасность — учётные записи и ограничения во FreeBSD
- Linux — время и синхронизация — часы, от которых зависит TOTP-модуль в стеке
auth - KDF для паролей: почему медленно и с памятью — механизм за таблицей префиксов: чем yescrypt отличается от sha512crypt и bcrypt и почему в
shadowоказался он
Из библиотеки института уносят только знания. Читателей здесь не считают: посещение не регистрируется, отметок в издании не остаётся, счётчиков нет ни у одного издания фонда.
Издание пополняется и пересверяется. Текст доступен на условиях CC BY-SA 4.0 — унесённое можно передавать дальше на тех же условиях.