Hardening sudo
Narrow rules, logging, and the escape hatches.
Every time you type sudo (short for 'superuser do'), a program holding the keys to the whole machine checks a list, decides whether to let you through, runs your job as root (the machine's all-powerful superuser account, the one with user id 0), and writes a line in a logbook. That program is the bouncer on the door of your server. The list is a file called /etc/sudoers. Hardening sudo is two jobs: make the list say exactly who may do exactly what, and keep the logbook honest enough to reconstruct any night. Most real Linux break-ins do not need a clever kernel bug. They need one sloppy line in that list.
A rule in the sudoers file looks like alice ALL=(root) /usr/bin/systemctl restart nginx.service. Read it as: the user alice, on any host (that first ALL), may run that one command as root. (systemctl is the command that starts, stops, and restarts services.) The trap is that sudo trusts the command completely. Whatever you name runs with the full authority of user id 0, so the only thing standing between a normal account and total control is how tightly that one command is written. You never edit sudoers with a plain text editor. You use visudo, a wrapper that checks the syntax before it saves, because a single typo can lock every account out of sudo at once and leave you booting from rescue media to fix it.
See What You Actually Grant
Before you tighten anything, find out what is already promised. Two commands do most of the work. sudo -l prints what the current user may run. Then a recursive grep (a search that walks through files line by line) hunts the sudoers files for the three patterns that most often hide a hole: NOPASSWD (the user needs no password), ALL (any command at all), and * (a wildcard that quietly widens a rule past what you meant).
Read every hit, because the search is deliberately noisy and most lines are fine. The root and %sudo lines are the distribution's own defaults: real administrators are meant to have full root, so those stay. Pasting that same blanket (ALL:ALL) ALL onto a service account, though, is how one compromised app turns into root, so watch for it. The alice line is the shape you actually want, one exact command with nothing left open. Two lines should stop you cold. The %ops ... tar * grant hands the ops group a wildcard on tar, and tar can run any command you feed it through its --checkpoint-action option, so that single line is a standing root shell for everyone in the group. The find line looks narrow, but find is one of the shell-spawning programs you will meet in a minute, so it earns a second look too.
Narrow The Command, Kill The Wildcard
A good sudo rule is a key cut for exactly one lock. /usr/bin/systemctl restart nginx.service opens that one door and nothing else. A wildcard is a master key you did not mean to hand out. sudo compares the command and its arguments as written, character for character, so /usr/bin/systemctl * matches anything you put after systemctl. That includes systemctl status, which pages its output through a pager (a program like less that shows long output one screen at a time, and one you can break out of), or a unit file the attacker planted. Spell the arguments out and the user has to match them exactly and cannot bolt on their own. That is what makes the narrow form safe.
# Validate before you trust it: visudo -cf /etc/sudoers.d/10-deploy# A Cmnd_Alias is a named group of commands you can reuse, which keeps# the file readable and easy to audit.Cmnd_Alias NGINX_CTL = /usr/bin/systemctl restart nginx.service, \/usr/bin/systemctl reload nginx.service%deploy ALL=(root) NOPASSWD: NGINX_CTL# NEVER write these:# %deploy ALL=(root) NOPASSWD: /usr/bin/systemctl * (any unit + pager escape)# %deploy ALL=(root) NOPASSWD: ALL (blanket root)
Leave the arguments off a command entirely and the user may pass whatever they like to it, so a bare command name is nearly as loose as a wildcard. For a script you own, you can go one step further and pin the exact binary by its cryptographic hash (a short fingerprint that changes if even one byte of the file changes), so nobody can swap your script for a trojan. Prefix the command with sha256: and the digest, as in %deploy ALL=(root) NOPASSWD: sha256:b5f1...9c /usr/local/bin/deploy.sh. If the file on disk no longer matches that fingerprint, sudo refuses to run it.
vi or nano. Go through visudo (or visudo -f /etc/sudoers.d/10-deploy for a drop-in file), because it refuses to save anything that would not parse, and a broken sudoers means nobody can become root until you boot from rescue media. Second, sudo silently ignores any file in /etc/sudoers.d/ whose name contains a dot or ends in ~. A file called hardening.conf is never read, and your careful rules do nothing at all. Name it 10-hardening, with no extension.The Programs That Hand Back A Shell
Some commands keep a second exit quiet, like a supply closet with a hidden door into the vault. Give someone the right to run less as root to read a log, and from inside less they can type !sh and step straight out as root. less, more, vi, view, man, find, tar, awk, nmap, and git, among many others, can each launch a program or a shell from inside themselves. There is a public catalogue of these tricks called GTFOBins (the name is a play on 'get the heck out' plus 'binaries'), a curated list of standard Unix tools that can be talked into breaking out of their intended job. The rule of thumb: if a program can open a file, run a command, or start an editor, granting it through sudo is the same as granting a root shell.
This is why even a grant that looks read-only is dangerous. sudo systemctl status ssh (a harmless-looking peek at the remote-login service) pipes long output through less when you are on a terminal, and from that pager you are one !sh away from root. To read safely, defeat the pager: add --no-pager, or blank it out with sudo SYSTEMD_PAGER='' systemctl status ssh. To edit a root-owned file safely, use sudoedit (also written sudo -e). It copies the file to a scratch location, opens that copy with your editor running as you, then writes the result back as root. Your editor never holds root's power, so there is no shell to escape into.
# Let the web group edit nginx config safely.# The editor runs as you, not as root, so there is no shell to break out to.%web ALL=(root) sudoedit /etc/nginx/sites-available/*
Record Every Session
A guard who keeps no logbook is useless the morning after. sudo can write two kinds of record: a one-line-per-command log, and a full recording of everything typed and printed during a session that you can play back later like a screen capture. Turn on both.
# Validate: visudo -cf /etc/sudoers.d/10-hardeningDefaults logfile="/var/log/sudo.log" # one line per sudo commandDefaults log_input, log_output # full session capture (keystrokes + output)Defaults iolog_dir="/var/log/sudo-io" # where the recordings are storedDefaults use_pty # run the command under its own pseudo-terminalDefaults !visiblepw # refuse to run if the password would echo on screenDefaults timestamp_timeout=5 # re-ask for the password after 5 idle minutesDefaults passwd_tries=3
timestamp_timeout is how long sudo remembers your password before it asks again. The default is 15 minutes. Drop it to 5, or to 0 to demand the password every single time, and you shrink the window in which a walked-away terminal is a free root shell. use_pty runs the command inside its own pseudo-terminal (a fake terminal device the kernel hands out on demand), which stops a backgrounded malicious process from shoving fake keystrokes back into your real terminal and makes the output recording reliable. With log_output on, every session lands under /var/log/sudo-io, and you replay it with sudoreplay.
Because the recording captures output as well as input, it captures the escape too. If someone ran sudo vi and dropped to a shell with :!sh, the replay shows the shell prompt appear and every command they typed after it. That is the difference between knowing a command's name and knowing what actually happened on the box.
After any edit, prove it. Delete the loose drop-ins first, then check what is left. visudo -c confirms that every sudoers file still parses, and sudo -l -U <user> reads a named user's grants back exactly as sudo will apply them. If the list shows only the narrow commands you meant to grant, and nothing carrying a * or a bare ALL, the change did what you wanted.
requiretty, which forces sudo to run only from a real login terminal. It sounds safe, but it quietly breaks cron jobs (scheduled background tasks), Ansible (a tool that configures servers over the network), and anything else that runs with no terminal attached, and modern distributions dropped it from their defaults for exactly that reason. Prefer use_pty, which gives you the security win (a private pseudo-terminal plus reliable logging) without blocking headless tooling. If you do set requiretty, expect scripted sudo to start failing until you carve out exceptions.ops ALL=(root) NOPASSWD: /usr/bin/systemctl status * is safe, because status only reads state and never changes anything. Why is it still a full root compromise?systemctl is not setuid; here it is sudo that grants root, not a permission bit on the file.sudo matches the literal word status; a different verb would not match this rule at all.Put a recurring reminder on your calendar to re-run the grep from the top of this lesson across every host you own. Sudoers files grow the way a kitchen junk drawer does: someone adds a NOPASSWD line for a deploy that shipped two years ago, and it never leaves. The audit that finds it takes thirty seconds. The breach that uses it does not announce itself.
Try this
Work through “Record Every Session” 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: two easy ways to lock yourself out. Check that on your own systems before you need to, because it is cheaper to find on a quiet afternoon than during an incident.