Пользователи и группы — UID, GID и две текстовые базы данных
Когда вы набираете whoami и видите levoely — это ложь. Kernel ничего не знает про «levoely». Для него вы — число. Чаще всего четырёхзначное вроде 1000. Это UID (user ID), и именно по нему kernel принимает все решения: можно ли вам открыть этот файл, убить этот процесс, прочитать чужие письма.
Имена существуют только для людей. Маппинг «имя -> число» лежит в текстовом файле /etc/passwd, который любой пользователь может прочитать. В этом уроке разберём, как устроена эта система: что хранится где, чем UID отличается от GID, что такое NSS, и почему в Docker-контейнере у вас может оказаться UID=1000, хотя на хосте вы 1001.
Зачем понимать UID/GID
«Я просто пользователь, мне разве важно?» Важно, если:
- Вы пишете Dockerfile — от выбора UID зависит, кто внутри контейнера сможет писать в смонтированные тома.
- Вы развёртываете сервис — надо понимать, под каким UID он бежит, что ему доступно.
- Вы дебажите permission denied — 90% случаев это не «политика безопасности», а тупо несовпадение UID/GID.
- Вы переносите файлы между серверами — chown с числом понятнее, чем с именем (имена могут не совпадать).
И главное: всё, что вы знаете про «пользователей» — это надстройка над числами. Понимая числа, вы понимаете всё.
UID: ваш номер в системе
UID (User ID) — 32-битное целое (на старых системах 16-битное). Несколько зарезервированных:
- 0 — root. Полная власть. Может всё.
- 1-99 — системные аккаунты (исторически).
daemon,bin,sys. - 100-999 — современные системные аккаунты для служб.
postgres,redis,nginx. - 1000+ — обычные пользователи. Первый созданный человек обычно 1000.
- 65534 —
nobody/nogroup. Используется для NFS root squash, контейнеров.
Узнать свой UID:
id
# uid=1000(levoely) gid=1000(levoely) groups=1000(levoely),27(sudo),100(users)
# Только число:
id -u
# 1000
# UID конкретного процесса:
cat /proc/1234/status | grep -i uid
# Uid: 1000 1000 1000 1000
# Это real, effective, saved set-uid, filesystem UID (об этом ниже)
Зачем четыре разных UID? Главное — безопасность. Программа, запущенная под root через sudo, может временно понижать привилегии, выполнять опасную часть как обычный пользователь, потом восстанавливать root. Это пример principle of least privilege: даже сейчас, когда есть root, держать его эффективно только в нужный момент.
/etc/passwd: текстовая база пользователей
/etc/passwd — обычный текстовый файл, по одной строке на пользователя. Семь полей через ::
username:password:UID:GID:GECOS:home_dir:shell
# Посмотреть содержимое:
cat /etc/passwd | head -5
# root:x:0:0:root:/root:/bin/bash
# daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
# bin:x:2:2:bin:/bin:/usr/sbin/nologin
# sys:x:3:3:sys:/dev:/usr/sbin/nologin
# levoely:x:1000:1000:Lev Negov,,,:/home/levoely:/bin/bash
Разбираем поле за полем:
- username — имя для людей. Используется при логине, ls, и т.п.
- password — буква
xозначает «лежит в/etc/shadow». Раньше здесь был сам пароль (хеш), но это видно всем — небезопасно. - UID — наш герой. Число.
- GID — primary group ID. По умолчанию ваш UID = ваш primary GID (схема UPG, User Private Group).
- GECOS — произвольная инфа: имя человека, телефон, etc. Часто пусто или просто полное имя.
- home_dir — куда
cd ~ведёт. Обычно/home/username. - shell — что запускается при логине.
/bin/bash,/bin/zsh, или/usr/sbin/nologinдля сервисных аккаунтов.
/etc/passwd читаем всем. Это OK, потому что хешей паролей там больше нет, а имена и UID — не секрет.
/etc/shadow: хеши паролей
/etc/shadow — параллельный файл, который читает ТОЛЬКО root. В нём действительно секретное: хеши паролей и политика их смены.
sudo cat /etc/shadow | head -3
# root:$y$j9T$f3wEgrG...:19847:0:99999:7:::
# daemon:*:19847:0:99999:7:::
# levoely:$y$j9T$xQNQ8...:19847:0:99999:7:::
Поля:
- username — маппинг к /etc/passwd.
- password hash — сам хеш. Формат
$algo$salt$hash.$y$= yescrypt,$6$= SHA-512.*или!означает «логин заблокирован, пароля нет». 3-9. Политика паролей — когда менялся, как часто менять, предупреждение и т.п.
Если вам нужен непривилегированный сервис, который должен «существовать» в системе но не пускать к логину — его пароль ставят в *. Например, postgres:!:... означает — логиниться по паролю нельзя, только переключаться через sudo -u postgres.
/etc/group: группы
Параллельный механизм — группы. Пользователь принадлежит к одной primary group (записана в /etc/passwd) и к произвольному числу supplementary groups.
cat /etc/group | head -5
# root:x:0:
# daemon:x:1:
# bin:x:2:
# sys:x:3:
# users:x:100:levoely,alice
# sudo:x:27:levoely,bob
Поля:
- groupname
- password —
x(опять же в shadow-параллели/etc/gshadow, но обычно не используется). - GID
- member list — через запятую, кто входит в группу.
Зачем supplementary groups? Чтобы один пользователь мог иметь доступ к разным наборам файлов:
- Я в группе
sudo— могу делатьsudo. - Я в группе
docker— могу управлять Docker безsudo. - Я в группе
developers— доступ к коду проекта.
Каждая группа — независимый набор прав. Когда пользователь логинится, его процесс получает все его группы (primary + supplementary), и kernel проверяет доступ к файлам через эти GID.
# Посмотреть свои группы:
groups
# levoely sudo users docker
# С GID:
id -G
# 1000 27 100 999
# Все процессы и под какими группами они идут:
ps -eo user,group,supgrp,cmd | head
NSS: Name Service Switch — больше чем просто файлы
В современном Linux пользователи не обязаны жить в /etc/passwd. Источники могут быть:
- Файлы (
files) —/etc/passwd,/etc/group. - LDAP / Active Directory (
ldap,sss) — корпоративная директория. - NIS (
nis) — старый Sun-style, ныне редко. - systemd-homed (
systemd) — современная альтернатива. - Database-driven (
db,nis+).
Какие источники и в каком порядке — настраивается в /etc/nsswitch.conf:
cat /etc/nsswitch.conf
# passwd: files systemd
# group: files [SUCCESS=merge] systemd
# shadow: files
# hosts: files mdns4_minimal [NOTFOUND=return] dns
Это значит: «для passwd сначала смотри файлы, потом systemd-homed. Для group — файлы, потом systemd, и результаты сливаются».
NSS вызывается не напрямую — через библиотечные функции getpwnam, getpwuid, getgrnam. Если NSS настроен на LDAP, эти функции уходят запрашивать LDAP-сервер. Утилиты типа id, ls -l, chown работают через эти функции — и автоматически видят LDAP-пользователей наряду с файловыми.
# Запросить пользователя через NSS (а не только /etc/passwd):
getent passwd levoely
# levoely:x:1000:1000:Lev Negov,,,:/home/levoely:/bin/bash
# getent работает с любым NSS-источником -- file или LDAP:
getent passwd 1000
getent group docker
# Это правильный способ программно узнать про пользователя:
# НЕ парсить /etc/passwd, А звать getent или getpwnam.
UID одинаков для процесса и его файлов
Когда процесс создаёт файл, kernel записывает в inode UID и GID процесса. Это владелец файла. Когда другой процесс пытается открыть этот файл, kernel сравнивает UID процесса с UID файла — и решает по правилам rwx (следующий урок), можно или нет.
# Создать файл и посмотреть владельца:
touch /tmp/myfile
ls -l /tmp/myfile
# -rw-r--r-- 1 levoely levoely 0 May 18 12:00 /tmp/myfile
# Те же UID числами:
ls -ln /tmp/myfile
# -rw-r--r-- 1 1000 1000 0 May 18 12:00 /tmp/myfile
# Сменить владельца (нужен root):
sudo chown 1001:1001 /tmp/myfile
ls -ln /tmp/myfile
# -rw-r--r-- 1 1001 1001 0 May 18 12:00 /tmp/myfile
# Если UID 1001 нет в /etc/passwd -- ls покажет просто число:
ls -l /tmp/myfile
# -rw-r--r-- 1 1001 1001 0 May 18 12:00 /tmp/myfile
Это объясняет странные ситуации в Docker: вы монтируете volume хоста в контейнер. Внутри файлы принадлежат UID 1000. Хост UID 1000 — это вы. UID 1000 внутри контейнера — какой-то непредсказуемый пользователь. Если внутри контейнера UID 1000 = appuser, ls покажет appuser. На хосте тот же файл = вы. Это не магия, это просто число.
В Docker-контейнере имя пользователя НЕ имеет значения для permissions. Важен ТОЛЬКО UID/GID. Если хост-файл создан UID 1000, и в контейнере процесс работает под UID 1000 — доступ есть (как бы их не звали). Поэтому в Dockerfile часто пишут USER 1000:1000 — явно по числу, без зависимости от имени.
Реальный пример: создание пользователя
# Создать пользователя 'alice' с UID 1500:
sudo useradd -u 1500 -m -s /bin/bash alice
# Что произошло:
grep alice /etc/passwd
# alice:x:1500:1500::/home/alice:/bin/bash
grep alice /etc/group
# alice:x:1500:
# Установить пароль (запишется в /etc/shadow):
sudo passwd alice
# Добавить в существующую группу:
sudo usermod -aG docker alice
# -a = append (не заменять supplementary groups)
# -G = supplementary groups list
# Проверить:
id alice
# uid=1500(alice) gid=1500(alice) groups=1500(alice),999(docker)
Под капотом useradd просто пишет строки в /etc/passwd, /etc/group, /etc/shadow, и создаёт домашнюю директорию. Никакой магии.
Попробуй сам
# 1. Своя личность:
id
whoami
groups
# 2. Что внутри /etc/passwd:
cat /etc/passwd | wc -l # сколько пользователей
cat /etc/passwd | grep -v nologin | grep -v false | head
# Кто реально может логиниться (исключая системные)
# 3. Все системные сервисы под кем работают:
ps -eo user,cmd --sort=user | uniq -f0
# postgres, redis, www-data -- каждый под своим аккаунтом
# 4. Кто владеет какими процессами:
ps -eo user | sort | uniq -c | sort -rn | head
# Топ пользователей по числу процессов
# 5. Реальное использование NSS:
getent passwd $(id -u) # вы через NSS
getent passwd 0 # root
getent group docker # участники группы
# 6. Где живут хеши паролей:
sudo head -3 /etc/shadow
# Только root может прочитать -- проверьте права:
ls -la /etc/passwd /etc/shadow
# /etc/passwd -- 644 (читают все)
# /etc/shadow -- 640 или 600 (только root)