This lab trains the muscle memory of writing modular sudoers files and auditing them for over-grant. Every line of sudoers you write in production should look like the files in this lab.
Objective
By the end of this lab, you can write, validate, and audit sudoers drop-in files for both human operators and service accounts.
Architecture
flowchart LR
OPER["Operators<br/>humans"]
SVC["myapp<br/>service account"]
OPS_RULES["10-operators<br/>Cmnd_Alias + User_Alias"]
SVC_RULES["20-myapp<br/>NOPASSWD Cmnd_Alias"]
AUDIT["visudo -c<br/>sudo -l -U USER<br/>grep audit"]
OPS_RULES --> AUDIT
SVC_RULES --> AUDIT
OPER -.uses.-> OPS_RULES
SVC -.uses.-> SVC_RULES
Requirements
- A Linux host (Ubuntu 24.04 LTS or Debian 12 preferred).
- Root or sudo access.
- The
sudopackage installed.
Scenario
Your team has been granted a new host. The previous admin left a
single-line /etc/sudoers granting ALL=(ALL) ALL to the admin
group. You need to:
- Replace it with modular drop-in files.
- Write a policy for two operators (
alice,bob) that lets them restartsshandchrony, view journal entries for those services, and runapt update/apt upgrade/dnf update/dnf upgrade. - Write a policy for the
myappservice account that lets it restart its own service. - Validate everything with
visudo -c. - Audit the resulting policy.
Tasks
Task 1: Take inventory
ls -l /etc/sudoers /etc/sudoers.d/
visudo -c
Note the current state. The expected outcome is a clean baseline you can compare against after Task 5.
Task 2: Write the operators policy
Create /etc/sudoers.d/10-operators using visudo -f:
sudo visudo -f /etc/sudoers.d/10-operators
The file should contain:
# Operators — human administrators with scoped sudo
# Approved: 2026-08-09 by <your name>; reviewed quarterly.
Cmnd_Alias RESTART_SVC = /usr/bin/systemctl restart ssh, \
/usr/bin/systemctl restart chrony, \
/usr/bin/systemctl reload ssh, \
/usr/bin/systemctl reload chrony
Cmnd_Alias VIEW_SVC_LOGS = /usr/bin/journalctl -u ssh, \
/usr/bin/journalctl -u chrony
Cmnd_Alias PKG_UPDATE = /usr/bin/apt update, \
/usr/bin/apt upgrade, \
/usr/bin/dnf update, \
/usr/bin/dnf upgrade
User_Alias OPS = alice, bob
OPS ALL=(root) RESTART_SVC, VIEW_SVC_LOGS, PKG_UPDATE
Defaults:OPS env_reset, secure_path="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", use_pty, log_input, log_output
Defaults:OPS iolog_dir=/var/log/sudo-io/%{user}
Notes:
- Line continuation in sudoers is a single trailing backslash, and it must be the last character on the line — a trailing space after it breaks the continuation.
- Use absolute paths in every Cmnd_Alias entry.
VIEW_SVC_LOGScarries no wildcard, so the grant is exactly the two commands written. Operators who need-nor--sinceget them from group membership rather than from a wider sudo rule — see the callout below.Defaults:OPSscoped to the user alias enables I/O logging for operators specifically. Service-account rules can disable it.
Task 3: Write the service-account policy
sudo visudo -f /etc/sudoers.d/20-myapp
The file should contain:
# myapp service account — manages its own systemd service
# Approved: 2026-08-09 by <your name>; reviewed quarterly.
Cmnd_Alias MYAPP_SVC = /usr/bin/systemctl start myapp.service, \
/usr/bin/systemctl stop myapp.service, \
/usr/bin/systemctl restart myapp.service, \
/usr/bin/systemctl status myapp.service, \
/usr/bin/systemctl reload myapp.service
myapp ALL=(root) NOPASSWD: MYAPP_SVC
Defaults:myapp !authenticate, !log_input, !log_output
The service account has no password (per the earlier user-management
lab). NOPASSWD is the only viable option. !authenticate removes
the prompt that would fail anyway.
Task 4: Validate
sudo visudo -c
sudo visudo -c -f /etc/sudoers.d/10-operators
sudo visudo -c -f /etc/sudoers.d/20-myapp
All three commands must report “parsed OK”. A failure here means your sudoers file has a syntax error — fix before continuing.
Task 5: Audit
sudo -l -U alice
sudo -l -U bob
sudo -l -U myapp
Each command lists the grants for that user. Confirm:
- alice and bob have the same scoped set (RESTART_SVC, VIEW_SVC_LOGS, PKG_UPDATE) and no broader grants.
- myapp has only MYAPP_SVC.
grep -r 'ALL.*ALL' /etc/sudoers /etc/sudoers.d/ 2>/dev/null
grep -r 'NOPASSWD' /etc/sudoers /etc/sudoers.d/ 2>/dev/null
The first command should find nothing (no broad grants remain). The second should find only the myapp NOPASSWD rule, which is appropriate.
Validation
The lab is complete when:
/etc/sudoers.d/10-operatorsvalidates withvisudo -c./etc/sudoers.d/20-myappvalidates withvisudo -c.sudo -l -U aliceandsudo -l -U bobshow only the scoped set.sudo -l -U myappshows only MYAPP_SVC.grep -r 'ALL.*ALL'returns nothing.grep -r NOPASSWDreturns only the myapp rule.
Expected outcome
A modular, audited, version-control-ready sudoers policy that demonstrates the principle of least privilege in practice.
Troubleshooting
- “parsed OK” but my rule is ignored — check the file’s
permissions and ownership:
chmod 0440, owned byroot:root. sudo -l -U myappreports “myapp is not in the sudoers file” — the myapp account does not exist on this host. Create it first (useradd -r -m -d /opt/myapp -s /usr/sbin/nologin myapp) or skip that task and audit just the alice/bob policies.Defaults:OPS iolog_dir=...fails because the directory does not exist —sudo mkdir -p /var/log/sudo-io && sudo chmod 0750 /var/log/sudo-io.- A rule denies what should be allowed — re-read the Cmnd_Alias. Wildcards and trailing-space arguments must match exactly.
syntax error: stray escape sequence— a doubled backslash (\\) where a single trailing\belongs. See the callout in Task 4.syntax error: unknown setting: 'log_input'(oriolog_dir, or!authenticate) — checksudo --version. Newer Ubuntu shipssudo-rs, a memory-safe reimplementation that does not implement I/O logging. The directives are valid in sudo from sudo.ws; on asudo-rshost, drop the twoDefaults:OPSI/O-logging lines and record that gap in the audit report, or install thesudopackage instead.
Cleanup
The sudoers changes persist. To restore the lab host to its pre-lab state, remove the drop-in files:
sudo rm /etc/sudoers.d/10-operators /etc/sudoers.d/20-myapp
sudo visudo -c
What you learned
You can now design sudoers policies that reflect operational need and nothing more. The discipline is one Cmnd_Alias per concern, visudo validation, and a quarterly audit that checks both the rules and the grants each user actually has.