Skip to main content

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
PositionMeaning
First character- file, d directory, l symlink
Next threeWhat the owner may do
Next threeWhat the group may do
Last threeWhat 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.

ModeMeansTypical use
600rw-------A private key, an env file with secrets
644rw-r--r--An ordinary readable file
700rwx------A private directory, like ~/.ssh
755rwxr-xr-xA 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:

  1. The VPS_SSH_KEY GitHub 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.
  2. The deploy user is in the docker group 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 to docker and sudo, and installs the SSH key with install -d -m 700 and mode 600 — not chmod afterwards.
  • deploy/preflight.sh checks that the env file is mode 600 using stat -c %a, and fails the preflight if it is not.
  • deploy/backup.sh sets umask 077 at 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 x means "may enter".
  • umask removes bits from the default. umask 077 gives 600 files and 700 directories, and setting it before writing avoids a window where the file is readable.
  • The docker group is root. Anyone who can run a container can mount the host filesystem.

Next: 12 — Processes, ports, logs, disk.