11 — Users, permissions, and umask
Read this first: this lesson explains the permission bits you have been reading since
lesson 01, how umask decides what new files get, and why membership in the
docker group is the same thing as being root. It is short and it changes how you read every
ls -l from here on.
Time: about 35 minutes. Runs on the lab box from lesson 10.
Who you are
./ssh.sh 'id'
uid=1001(deploy) gid=1001(deploy) groups=1001(deploy),27(sudo),996(docker)
Three things: your user, your primary group, and every group you belong to. Groups are how a system
grants a capability to a set of people without handing out the root account. Here, sudo lets you
administer the box and docker lets you run containers.
The three triples
-rw-r--r-- 1 deploy deploy 0 Aug 5 12:32 f2
drwxr-xr-x 2 deploy deploy 4096 Aug 5 11:59 /srv/greeter
| Position | Meaning |
|---|---|
| First character | - file, d directory, l symlink |
| Next three | What the owner may do |
| Next three | What the group may do |
| Last three | What everyone else may do |
Each triple is read / write / execute. On a directory, x means "may enter it" rather than "may
run it" — a directory you can read but not enter lets you list names and open nothing.
Numeric mode is the same thing in octal: read is 4, write is 2, execute is 1.
| Mode | Means | Typical use |
|---|---|---|
600 | rw------- | A private key, an env file with secrets |
644 | rw-r--r-- | An ordinary readable file |
700 | rwx------ | A private directory, like ~/.ssh |
755 | rwxr-xr-x | A directory or program everyone may use |
umask — what new files get by default
You rarely set permissions explicitly. Mostly you rely on the default, and the default is umask.
Predict: umask prints 0002. What mode will a newly-created file have? A newly-created
directory?
umask
touch f2
ls -l f2
0002
-rw-rw-r-- 1 deploy deploy 0 Aug 5 12:32 f2
umask is a mask of bits to remove. Files start from 666 and directories from 777 — never with
execute for files, which is why a new file is not runnable. Take away 002 and you get 664 for files,
775 for directories.
Now the one that matters:
umask 077
touch f1
mkdir d1
ls -l f1
ls -ld d1
-rw------- 1 deploy deploy 0 Aug 5 12:32 f1
drwx------ 2 deploy deploy 4096 Aug 5 12:32 d1
077 removes everything from group and other. The file is 600 and the directory is 700 — exactly
the modes SSH demanded in lesson 10.
This is why the real deploy workflow does it. From .github/workflows/deploy.yml:
umask 077
printf '%s\n' "$PROD_ENV" > .env
Setting umask before the redirect is what makes the file land 600. The alternative — write it
and then chmod — leaves a window in which the production secrets are world-readable, and windows
like that are exactly what an audit asks about.
Break it on purpose: the world-readable secret
umask 022
echo "APP_SECRET=your-secret-here" > insecure.env
ls -l insecure.env
-rw-r--r-- 1 deploy deploy 27 Aug 5 12:35 insecure.env
Every user on that machine can read your production secrets. On a box with one user that feels harmless; on a box with a compromised web process it is the whole game.
Fix it, and notice the ordering problem:
chmod 600 insecure.env
The file was readable between creation and chmod. umask 077 first avoids that entirely.
sudo, and how to check what a file needs
sudo ls -l /etc/shadow
-rw-r----- 1 root shadow 1234 Aug 5 11:59 /etc/shadow
Owner root may read and write; group shadow may read; everyone else gets nothing. That is why
reading it needs sudo.
On the lab box sudo needs no password, which is a lab-only convenience and is flagged as such
in the Dockerfile. The real VPS requires one.
Break it on purpose: the docker group is root
This is the most important thing in the lesson, and it surprises almost everyone.
Predict: you are the unprivileged deploy user. You are in the docker group. Can you read
/etc/shadow — the file that needs root — without sudo?
docker run --rm -v /:/host alpine:3 head -2 /host/etc/shadow
root:*:20664:0:99999:7:::
daemon:*:20664:0:99999:7:::
Yes. You mounted the host's entire filesystem into a container and read it as the container's root user.
There is no bug here. The Docker daemon runs as root, and anything that can ask the daemon to start a
container can ask it to mount anything and run as anyone. Membership in the docker group is
equivalent to root access, without appearing in any sudo log.
Two consequences worth carrying:
- The
VPS_SSH_KEYGitHub Secret is, effectively, a root credential for that server. Treat it that way — it is the reason the deploy workflow's secret-check step prints only character counts and never values. - The
deployuser is in thedockergroup anyway, because it has to be to deploy. That is a deliberate, understood trade, not an oversight — and knowing it is a deliberate trade is the difference between an engineer and someone who copied a tutorial.
Where this shows up in MotorPH
- deploy/bootstrap.sh creates
deploy, adds it todockerandsudo, and installs the SSH key withinstall -d -m 700and mode600— notchmodafterwards. - deploy/preflight.sh checks that the env file is mode
600usingstat -c %a, and fails the preflight if it is not. - deploy/backup.sh sets
umask 077at the top, because the archive it writes contains every secret the system has.
Recap
- Permissions are three triples — owner, group, other — and on a directory
xmeans "may enter". umaskremoves bits from the default.umask 077gives600files and700directories, and setting it before writing avoids a window where the file is readable.- The
dockergroup is root. Anyone who can run a container can mount the host filesystem.