Learning Platform
Глоссарий Troubleshooting
Урок 12.01 · 20 мин
Начальный
PermissionsUsersGroupsUIDGIDNSSLinux

Пользователи и группы — 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.
  • 65534nobody/nogroup. Используется для NFS root squash, контейнеров.
Пользователи и группы на практике: /etc/passwd, shadow, useradd

Узнать свой 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 процесса -- зачем так много
Real UIDКем вы залогинились. Не меняется обычно. Это 'настоящий владелец' процесса с точки зрения подсчёта ресурсов
Effective UIDПо которому проверяются права. Может временно меняться при setuid-программах (sudo, passwd)
Saved set-UIDРезервная копия предыдущего EUID. Чтобы программа могла временно понизить привилегии и потом восстановить
Filesystem UIDТолько для filesystem операций. Используется редко, в основном для NFS server

Зачем четыре разных 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

Разбираем поле за полем:

  1. username — имя для людей. Используется при логине, ls, и т.п.
  2. password — буква x означает «лежит в /etc/shadow». Раньше здесь был сам пароль (хеш), но это видно всем — небезопасно.
  3. UID — наш герой. Число.
  4. GID — primary group ID. По умолчанию ваш UID = ваш primary GID (схема UPG, User Private Group).
  5. GECOS — произвольная инфа: имя человека, телефон, etc. Часто пусто или просто полное имя.
  6. home_dir — куда cd ~ ведёт. Обычно /home/username.
  7. 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:::

Поля:

  1. username — маппинг к /etc/passwd.
  2. 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

Поля:

  1. groupname
  2. passwordx (опять же в shadow-параллели /etc/gshadow, но обычно не используется).
  3. GID
  4. 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: кто реально отвечает на запрос 'кто такой UID 1000'
ls -l fileУтилита хочет показать имя владельца. Зовёт getpwuid(file_uid)
libc getpwuidСтандартная библиотечная функция. Смотрит в /etc/nsswitch.conf, какие модули NSS использовать
NSS filesМодуль files читает /etc/passwd. Простой случай -- большинство систем
NSS systemdsystemd-homed -- современный механизм с шифрованным home, не зависит от /etc/passwd
NSS ldapКорпоративная директория. id, ls -l автоматически разрешают пользователей из 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. На хосте тот же файл = вы. Это не магия, это просто число.

WARNING

В 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)

Проверка знанийKnowledge check
Вы запустили Docker-контейнер 'docker run -v /home/me/data:/data ubuntu'. Внутри контейнера в '/data' лежит файл с правами '-rw-r--r-- 1 1000 1000'. Хотя имя пользователя в контейнере может быть 'ubuntu' или 'root' -- кто РЕАЛЬНО владеет этим файлом и при каких условиях процесс в контейнере сможет в него ПИСАТЬ?
ОтветAnswer
Файл принадлежит UID=1000 GID=1000 -- это число, имя не имеет значения. Что 'ubuntu' внутри контейнера, что 'levoely' на хосте -- если их UID совпадают, kernel считает их 'одним и тем же пользователем' для permission checks. Для записи в файл нужно одно из: 1) Совпадение UID процесса и UID файла. Если процесс в контейнере работает под UID=1000 (через USER 1000 в Dockerfile или docker run --user 1000), он может писать (потому что owner имеет w в правах -rw- первая группа битов). 2) Процесс работает под UID=0 (root). Root игнорирует обычные permission checks. В Docker по умолчанию процессы запускаются под root внутри контейнера -- они смогут писать в любой файл монтированного тома, даже если тот принадлежит 1000. 3) Совпадение GID и в правах есть g+w. У нашего файла g-w (group r--), так что это не работает. 4) others имеет w. У нас o-w, не работает. Практические последствия: - Если в Dockerfile есть USER appuser (где appuser=1000) и вы монтируете /home/me/data, то файлы доступны для записи только если на хосте они тоже принадлежат UID 1000. - Если вы делаете docker run без --user, процесс root внутри контейнера сможет писать куда угодно -- но созданные файлы будут принадлежать UID 0. На хосте root тоже UID 0 -- значит хост-пользователю с UID 1000 эти файлы недоступны для записи. Классический pain Docker. - Решение в production -- запускать non-root в контейнере (USER в Dockerfile), и заранее знать UID/GID. Часто матчат: 'docker build --build-arg UID=$(id -u) ...' и в Dockerfile useradd с этим UID. UID 1000 внутри контейнера и UID 1000 на хосте для kernel это ОДИН пользователь. Изоляция имени не значит ничего. Real isolation требует user namespaces -- отдельная история.

Проверьте понимание

Результат: 0 из 0
Концептуальный
Вопрос 1 из 6. Kernel принимает решения о правах доступа на основе:

Закончили урок?

Отметьте его как пройденный, чтобы отслеживать свой прогресс

Войдите чтобы оценить урок

Прогресс модуля
0 из 4