CoursesLinux essentialssudo & least privilege

sudo & least privilege

Grant exactly what is needed, attributably.

Intermediate12 min · lesson 18 of 25

Every building has a master key. It opens the server room, the supply closet, the finance office, everything. You could hand a copy to everyone who might one day need a locked room. Then you have twenty master keys floating around and no record of who opened what. The safer arrangement keeps the master key at the security desk. When you need a locked room, you show your badge, the guard checks that you are on the list for that room, they open it, and the logbook notes your name and the time. On a Linux machine, sudo is that security desk.

root is the master key. It is the administrator account (user ID 0) that every permission check on the system waves straight through. root can read any file, kill any process, and rewrite any config. Because that power is total, you rarely log in as root directly. Instead you stay logged in as your own limited account and reach for sudo (short for "superuser do") when a single task needs root. sudo runs that one command with root's power and then hands the key back. Two things make this safer than living as root. You spend your day as a normal user who cannot accidentally wreck the machine, and every elevation is written down against your name. "root restarted nginx" becomes "deploy ran /usr/bin/systemctl restart nginx as root at 10:04 from this terminal." That is the difference between a shrug and an audit trail.

What you are allowed to run

Before you walk to the security desk, it helps to know which rooms you are on the list for. sudo -l (the letter l, for "list") answers exactly that. It reads the rules and tells you which commands you may run, as whom, and whether you will be asked for a password.

~/secopslog — bash
$ deploy@web01:~$ sudo -l
[sudo] password for deploy: Matching Defaults entries for deploy on web01: env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin User deploy may run the following commands on web01: (root) NOPASSWD: /usr/bin/systemctl restart nginx (root) NOPASSWD: /usr/bin/systemctl reload nginx

Read the bottom two lines like sentences. deploy may run, as root, with no password, exactly systemctl restart nginx and systemctl reload nginx. (systemctl is the command that starts, stops, and restarts background services; nginx is the web server this box exists to run.) Nothing else. Not stop, not disable, not a shell. Notice that sudo asked for deploy's password only to list the rules. The rules themselves carry the NOPASSWD tag, so those two nginx commands run without a prompt, which is what you want for automation with no human sitting there to type one. The tradeoff: anything that can act as deploy can run them unprompted, so keep the list tiny.

The two Defaults at the top are quiet security wins. env_reset scrubs your environment variables before the command runs, so you cannot smuggle in a rigged setting (like a doctored library path) that would change which code executes as root. secure_path forces sudo to use a known, trusted PATH (the list of folders it searches for the program), so "systemctl" cannot be swapped for a fake one sitting in your home directory.

Elevate for one command

Now run one. The allowed command goes through silently. Ask for something outside the list and sudo stops you cold.

~/secopslog — bash
$ deploy@web01:~$ sudo systemctl restart nginx deploy@web01:~$ sudo systemctl stop nginx
Sorry, user deploy is not allowed to execute '/usr/bin/systemctl stop nginx' as root on web01.

restart worked (no news is good news). stop was refused, because your rule lists restart and reload and nothing more. That is least privilege on one screen. deploy can bounce the web server, which is its job, and cannot stop it, wipe it, or open a root shell, which are not.

What sudo does on every call
1You run sudo <command>
as your normal, limited user
2sudo checks its rules
in /etc/sudoers and /etc/sudoers.d
3Prompts for YOUR password
unless cached, or the rule is NOPASSWD
4Runs the one command as root
user ID 0, then drops back to you
5Writes a line to the auth log
who, what, when, allowed or denied

Every use is written down

The security desk keeps a logbook, and so does sudo. On Debian and Ubuntu the entries land in a file called /var/log/auth.log. On Red Hat systems they go to /var/log/secure. If the machine runs systemd (the service manager that starts and supervises programs on most modern Linux, and the thing systemctl talks to), you can also read the same records with journalctl, its built-in log reader. Both the commands that ran and the ones that were refused show up.

~/secopslog — bash
$ root@web01:~# grep sudo: /var/log/auth.log | tail -n 2
Jul 17 10:04:12 web01 sudo: deploy : TTY=pts/0 ; PWD=/home/deploy ; USER=root ; COMMAND=/usr/bin/systemctl restart nginx Jul 17 10:05:47 web01 sudo: deploy : command not allowed ; TTY=pts/0 ; PWD=/home/deploy ; USER=root ; COMMAND=/usr/bin/systemctl stop nginx

The second line is the one a defender cares about. A user (or a process running as that user) tried to run something outside its rules. One such line is a typo. A burst of them is someone probing what they can get away with, and it belongs in your alerts.

Become another user, not only root

Sometimes you do not need the master key at all. You need to be the janitor for thirty seconds. Say you are signed in as alice, an everyday admin account that carries broad sudo rights. sudo -u <user> (the -u stands for "user") runs a command as that specific account instead of root. That is how you open a database shell as the postgres user (the account the PostgreSQL database runs under), or read a file as www-data (the low-privilege user a web server runs as), without knowing their passwords or opening a full root shell.

~/secopslog — bash
$ alice@web01:~$ sudo -u postgres psql -c 'SELECT current_user;'
current_user -------------- postgres (1 row)

Same idea, smaller blast radius. psql is the PostgreSQL command-line client. You borrowed the postgres identity for one query, then gave it straight back.

Who gets on the list, and for exactly what

There are two ways onto the sudo list. The blunt way is group membership. On Debian and Ubuntu, anyone in the group named sudo gets full root. On Red Hat and Fedora the group is called wheel. That is handy for your own admin account and far too broad for a service.

~/secopslog — bash
$ alice@web01:~$ groups alice
alice : alice sudo

alice is in sudo, so alice can become root and do anything. The precise way is a written rule in /etc/sudoers, or better, a small file dropped into the /etc/sudoers.d/ directory so each grant stays separate and easy to review. Here is the rule behind deploy's two commands.

/etc/sudoers.d/deploy
# deploy may bounce nginx on this host, and nothing else.
# Fields: who host=(run-as) TAG: exact commands
deploy web01=(root) NOPASSWD: /usr/bin/systemctl restart nginx, \
/usr/bin/systemctl reload nginx

Read it left to right. deploy is the user. web01 is the host the rule applies to (the same file can ship to many machines and mean different things on each). (root) is who deploy becomes. NOPASSWD says do not prompt. Then the exact commands, by full absolute path. Those paths are not decoration. If you wrote systemctl instead of /usr/bin/systemctl, or ended a rule with a wildcard, you hand back control you meant to keep. A rule like /usr/bin/systemctl restart * lets deploy restart any unit on the box, not only nginx.

Edit sudoers with visudo, always

The sudoers file is the lock on the security desk's own door. Put a syntax error in it and sudo can refuse to work for anyone, which means nobody can use sudo to fix the file they just broke. visudo is the tool that prevents that. It opens the file in your editor, and when you save, it checks the syntax first and refuses to install a broken file.

~/secopslog — bash
$ alice@web01:~$ sudo visudo -f /etc/sudoers.d/deploy

Say you fat-finger the rule and leave a stray word on line 4. visudo catches it on save instead of letting you lock yourself out.

output
>>> /etc/sudoers.d/deploy: syntax error near line 4 <<<
What now?
Options are:
(e)dit sudoers file again
(x)it without saving changes to sudoers file
(Q)uit and save changes to sudoers file (DANGER!)

Press e, fix the line, and you are safe. You can also validate every sudoers file without opening an editor, which is what you want inside a CI check (continuous integration, the automated pipeline that tests and ships changes) before config lands on a fleet of machines.

~/secopslog — bash
$ root@web01:~# visudo -c
/etc/sudoers: parsed OK /etc/sudoers.d/deploy: parsed OK
A narrow rule can still be full root
The prize an attacker hunts for is a sudo rule that reaches a program which can run other programs. Grant NOPASSWD on an editor (vim can run :!sh), a pager (less can run !sh), an interpreter (python, perl, awk), or tools like find (with -exec) or tar (with --checkpoint-action), and you have not granted one command. You have granted a root shell. These escapes are catalogued at a site called GTFOBins, which is the first place an attacker looks after reading your sudo -l. Grant the narrowest specific command you can, avoid NOPASSWD unless a machine truly needs it, and audit sudo grants as carefully as you audit SUID binaries (programs flagged to run as their owner, often root, no matter who launches them).

This is also your daily audit. Run sudo -l for each account and read what it can really do. Follow every allowed command and ask one question: can this program spawn a shell or write a file it shouldn't? Keep /etc/sudoers.d/ small enough that you can read the whole thing in a sitting. On a compromised box, sudo -l is the attacker's opening move because it maps the route from the account they landed on up to root. Your job is to make that map short.

Quick check
01A teammate proposes giving the ci user this rule: ci ALL=(root) NOPASSWD: /usr/bin/vim /etc/nginx/nginx.conf, arguing it only lets ci edit one file. What is the real problem?
Incorrect — It looks scoped, but the danger is the program it names, not the file.
Correct — A rule is only as narrow as what its program can do, and vim can open a root shell.
Incorrect — NOPASSWD works fine in sudoers.d; that is not the issue.
Incorrect — A crashed service is minor next to the full root shell vim hands over.
02A sudo rule lets deploy run /usr/bin/systemctl restart nginx, and the Defaults line includes secure_path. What does secure_path actually protect against here?
Incorrect — password caching is a separate behavior; secure_path is about which directories sudo searches.
Correct — secure_path pins the search path to trusted system directories, so the real systemctl runs, not an attacker's stand-in.
Incorrect — secure_path has nothing to do with log encryption.
Incorrect — host scoping is the host field of the rule, not secure_path.
03In /var/log/auth.log you find fifteen lines in two minutes, all like deploy : command not allowed ; ... COMMAND=/usr/bin/... across a run of different commands. What should you read into this?
Correct — one denied line is a typo, but a rapid run of them across many commands is enumeration of the sudo rules.
Incorrect — the denials mean the commands are outside deploy's rules, which is sudo working correctly, not a misconfiguration.
Incorrect — a successful command logs as USER=root running the command, not as 'command not allowed'.
Incorrect — sudo logs refused commands too, which is precisely why this signal is available to you.

So when you write a sudo rule, name the exact command, pin it to an absolute path, leave off the wildcard, and ask what else the named program can be talked into doing. A good rule reads like a single sentence with no room for a second reading.

Try this

Work through “Edit sudoers with visudo, always” yourself on a sandbox you can throw away, following the commands above in order. Then break one step deliberately and re-run, so you have seen the failure before it finds you.

Takeaway

The trap worth remembering here: a narrow rule can still be full root. Check that on your own systems before you need to, because it is cheaper to find on a quiet afternoon than during an incident.

Related