OnCallReady

Lesson 4.3 · Filesystem, Permissions, Disk · 29 min read

Users, groups, and who you are right now

In plain words

Imagine a school where the doors do not read names, only badge numbers. The office keeps a list that says "badge 1000 is Learner", but the lock only checks the number. Some badges also have coloured stickers for clubs (groups), and a door can say "club members may enter". When you join a new club, your badge only gets the sticker the next morning when you come in again.

In Linux, the badge is your UID, the stickers are GIDs, and the lists are /etc/passwd and /etc/group. Files store numbers, not names. id shows your badge right now; id learner shows what the office list says. sudo -u appuser cmd borrows someone's badge for one door. And usermod -G without -a replaces all your stickers, including the sudo one.

Why identity comes before permissions

"Permission denied" is always a question about who is asking. Before you can fix one, you need to know which user a process runs as, which groups it has right now, and how the box turns names into the numbers it really checks.

What you need to know already: 1.11 (sudo), 2.5 (User= in a unit), 3.14 (/proc/<pid>/status).

The kernel only knows numbers

Every process runs with a UID (user ID, a number that identifies a user) and a set of GIDs (group IDs; a group is a named set of users you can grant access to all at once). Every file stores one UID and one GID. Names are a lookup table in /etc that the tools translate for you - the kernel never sees them.

$ grep -E '^(root|learner|appuser|www-data|nobody):' /etc/passwd
root:x:0:0:root:/root:/bin/bash
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
learner:x:1000:1000:Learner:/home/learner:/bin/bash
appuser:x:1001:1001:App runtime:/opt/app:/usr/sbin/nologin

(grep -E '^(a|b):' keeps lines that start with a: or b:; ^ means "start of line".) /etc/passwd is the user list. Seven colon-separated fields:

learner : x : 1000 : 1000 : Learner : /home/learner : /bin/bash
 name    |   UID    primary  comment   home          login shell
         |          group
         password is in /etc/shadow, not here

The login shell is the program started when that user logs in - normally bash.

The UID ranges on Ubuntu, and what they tell you at a glance:

0            root. The only UID the kernel treats specially.
1-999        system accounts: daemons, created by packages (useradd -r)
1000-59999   humans (UID_MIN / UID_MAX in /etc/login.defs)
65534        nobody - "no user at all", used for sandboxes and for
             network file shares that map unknown users to it

Service accounts

A service account is a user that exists only so a program can run as it - appuser runs orders, www-data runs nginx. Nobody logs in as it. Its login shell is /usr/sbin/nologin, a program that prints a refusal and exits:

$ sudo su - appuser
This account is currently not available.
$ sudo -u appuser id
uid=1001(appuser) gid=1001(appuser) groups=1001(appuser),1002(ops)

That is why sudo -u appuser <cmd> is the tool for "can the service account read this?" - you will use it constantly in this chapter.

Passwords live somewhere else

$ ls -l /etc/passwd /etc/shadow /etc/group
-rw-r--r-- 1 root root   433 Sep 14 17:43 /etc/group
-rw-r--r-- 1 root root   969 Sep 14 17:43 /etc/passwd
-rw-r----- 1 root shadow 613 Sep 14 17:43 /etc/shadow
$ sudo grep -E '^(learner|appuser):' /etc/shadow
learner:$y$j9T$Qm1vZ2hhbGlh$Xj8Zl2kqf7mV3p0R9sT1uW4bN6cE5dA2hG8iK0oLqPr:20340:0:99999:7:::
appuser:!:20340:0:99999:7:::

/etc/passwd has to be readable by everyone (every ls -l needs it to turn UIDs into names), so the password hashes - one-way scrambles of the password that can be checked but not reversed - moved to /etc/shadow, readable by root and group shadow only. (The -rw-r--r-- strings are permission bits: 4.5 decodes them.) A hash cannot be reversed, but anyone with a copy can try millions of guessed passwords against it on their own machine - so the copy itself has to be kept away from ordinary users.

$y$ marks yescrypt, Ubuntu's hashing method. ! in the password field means "no password will ever match": the account is locked for password login, which is exactly right for a service account.

Groups: one primary, many supplementary

$ grep -E '^(sudo|ops|adm):' /etc/group
adm:x:4:syslog,learner
sudo:x:27:learner
ops:x:1002:learner,appuser
$ id
uid=1000(learner) gid=1000(learner) groups=1000(learner),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),100(users),1002(ops)

/etc/group lines are name:x:GID:members.

sudo works for you because you are in group sudo, and /etc/sudoers (the rules file for sudo) says %sudo ALL=(ALL:ALL) ALL - members of group sudo may run anything as anyone. adm is what lets you read most of /var/log without sudo. sudo -l lists what you are allowed:

$ sudo -l
User learner may run the following commands on oncall-lab:
    (ALL : ALL) ALL

Creating accounts

$ sudo groupadd deploy
$ sudo useradd -m -s /bin/bash -G ops alice                 # a human
$ sudo useradd -r -m -d /var/lib/reportgen -s /usr/sbin/nologin reportgen   # a service

groupadd makes a group; useradd makes a user. The flags, because the defaults surprise people:

$ getent passwd reportgen
reportgen:x:999:997::/var/lib/reportgen:/usr/sbin/nologin
$ ls -ld /var/lib/reportgen
drwxr-x--- 2 reportgen reportgen 4096 Sep 22 20:00 /var/lib/reportgen

getent passwd NAME asks the system's user lookup - the same one every program uses - rather than reading one file. On a company server users often come from a central directory service (LDAP, Active Directory) and are not in /etc/passwd at all. Use getent, not grep, when the answer matters.

Errors you will meet:

$ useradd alice
useradd: Permission denied.
useradd: cannot lock /etc/passwd; try again later.
$ sudo useradd alice
useradd: user 'alice' already exists
$ sudo usermod -aG nosuchgroup alice
usermod: group 'nosuchgroup' does not exist

The -G trap

usermod changes an existing user:

sudo usermod -aG deploy learner     # APPEND deploy to learner's groups
sudo usermod -G deploy learner      # REPLACE all supplementary groups with deploy

The second one silently removes you from sudo. On a box where you are the only admin, the next sudo says learner is not in the sudoers file and you are locked out of your own server. Always -aG. To add or remove one group, gpasswd -a / gpasswd -d:

$ sudo gpasswd -a appuser adm
Adding user appuser to group adm
$ sudo gpasswd -d appuser adm
Removing user appuser from group adm

Groups are read at login, not live

This one costs everyone an hour once:

$ sudo usermod -aG deploy learner
$ id                      # THIS shell, credentials fixed when you logged in
uid=1000(learner) gid=1000(learner) groups=1000(learner),4(adm),...,1002(ops)
$ id learner               # the DATABASE
uid=1000(learner) gid=1000(learner) groups=1000(learner),4(adm),...,1002(ops),1003(deploy)

A process's groups are copied into it when the login happens, and inherited by every child process. /etc/group changing later does not reach into running processes. Your options: log out and back in, or newgrp deploy for a sub-shell that has it (exit to leave it).

The same is true for services. You add appuser to a group so the service can read a directory - and it still cannot, because the running process was started before the change. systemctl restart is what applies it. Check a live process with grep Groups /proc/<pid>/status (numeric GIDs), never with id appuser.

Files keep numbers, not names

$ ls -ln /srv
drwxr-xr-x 4 0    0    4096 Sep 14 17:43 cache
drwxr-xr-x 2 1000 1002 4096 Sep 14 17:43 ops
$ sudo touch /srv/alice.txt; sudo chown alice /srv/alice.txt
$ sudo userdel -r alice
$ ls -l /srv/alice.txt
-rw-r--r-- 1 1002 root 0 Sep 22 20:00 /srv/alice.txt

ls -n shows the numbers instead of names. chown changes a file's owner (4.5); userdel -r deletes a user and their home. Their files elsewhere now show a bare UID. Create the next user and they may be given the same UID - and silently own every one of those files. After a userdel, sweep for orphans (the find syntax is 4.15):

sudo find / -xdev -nouser -o -xdev -nogroup

Later (Ch 10): a container's processes are ordinary processes with host UIDs, so "the container runs as root" usually means UID 0 on the host too. Everything here - numbers not names, group bits doing the work - is why that matters, and why a volume owned by UID 1001 is unreadable to an app running as UID 1000.

What you can now do

Why it helps

Most "Permission denied" tickets are really identity questions: which UID does the service run as, which groups does the running process have, does the UID that owns a directory match the UID the service runs as. Knowing that group changes only apply to new logins and new processes saves the classic hour of "I added appuser to the group and it still cannot read it" (restart the service).

It also underpins every security review: which services run as root, which service accounts can log in, whose files a deleted user left behind. And usermod -aG versus -G is the kind of mistake that locks you out of your own server; knowing it is a senior habit.

Commands in this lesson

id grep getent su ls groups sudo groupadd useradd usermod gpasswd touch

FAQ

What is the difference between su and sudo -u?

su - user starts that user's login shell, asking for the target user's password (unless you are root) and running their shell from /etc/passwd. For service accounts with /usr/sbin/nologin, it prints "This account is currently not available" and exits. sudo -u user cmd runs one command as that UID, asks for your own password and ignores the login shell, which makes it the right way to test what a service account can access.

Why do I still not have the new group after usermod?

A process's group list is set when the login happens and inherited by every child. Adding you to a group changes /etc/group, not your running shell. id learner reads the database and shows it; plain id shows your current process and does not. Log out and in, or use newgrp group for a subshell. For services, restart them, then check grep Groups /proc/PID/status.

What does the ! in /etc/shadow mean?

A password field starting with ! or * can never match any password, so password login is impossible. System and service accounts use it. usermod -L locks an account by prefixing ! to the existing hash. Note it does not disable key-based SSH login or sudo -u; to disable an account fully, also expire it (usermod -e 1) or give it a nologin shell.

Should I use useradd or adduser?

On Ubuntu, adduser is the friendly Debian wrapper: it creates the home directory, copies skeleton files, sets a sensible shell and asks for a password. useradd is the low-level tool: without -m no home, default shell /bin/sh, no questions, which is what scripts and configuration management want. For service accounts, useradd -r -s /usr/sbin/nologin is the standard.

Why use getent instead of grepping /etc/passwd?

getent passwd name asks the name service switch, the same lookup programs use, which covers /etc/passwd plus LDAP, SSSD, Active Directory or systemd's dynamic users. On corporate servers, many users do not exist in local files at all, so grep finds nothing while the user can log in. getent group name does the same for groups.

In an interview Junior

Explain the fields of /etc/passwd, and why passwords are not stored there.

Seven colon-separated fields, e.g. learner:x:1000:1000:Learner:/home/learner:/bin/bash: name, x (the password is elsewhere), UID, primary GID, comment, home directory, login shell. Service accounts like appuser get /usr/sbin/nologin, so nobody can log in as them.

The kernel only knows the numbers; /etc/passwd is the lookup table that turns UIDs into names, and every ls -l and ps needs it, so it must be readable by everyone (644). Storing password hashes there would let any user copy them and guess offline. So the hashes live in /etc/shadow, readable only by root and group shadow (640). $y$ marks yescrypt; ! means locked - right for a service account.

UID ranges on Ubuntu: 0 root, 1-999 system accounts, 1000+ humans, 65534 nobody. To look someone up, use getent passwd name: it also finds users from a central directory.

Also asked: You added a service account to a group, but the service still gets Permission denied. Why? · What is the difference between usermod -G and usermod -aG? · What is the difference between a primary group and a supplementary group?

Practise this lesson in the terminal Free, in your browser - a real Ubuntu terminal to try it in, with missions that check your work.