Skip to main content
RunBook Academy

← All labs in Linux

Lab · intermediate · ~45 min

Lab: Design and audit a sudoers policy

B · Nested virtualisationC · Simulation

Objectives

  • Write a drop-in sudoers file for human operators with appropriate scoping
  • Write a drop-in sudoers file for a service account that needs root
  • Validate every drop-in with visudo -c
  • Audit the final sudoers state for over-grant

Prerequisites

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 sudo package 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:

  1. Replace it with modular drop-in files.
  2. Write a policy for two operators (alice, bob) that lets them restart ssh and chrony, view journal entries for those services, and run apt update / apt upgrade / dnf update / dnf upgrade.
  3. Write a policy for the myapp service account that lets it restart its own service.
  4. Validate everything with visudo -c.
  5. 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_LOGS carries no wildcard, so the grant is exactly the two commands written. Operators who need -n or --since get them from group membership rather than from a wider sudo rule — see the callout below.
  • Defaults:OPS scoped 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-operators validates with visudo -c.
  • /etc/sudoers.d/20-myapp validates with visudo -c.
  • sudo -l -U alice and sudo -l -U bob show only the scoped set.
  • sudo -l -U myapp shows only MYAPP_SVC.
  • grep -r 'ALL.*ALL' returns nothing.
  • grep -r NOPASSWD returns 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 by root:root.
  • sudo -l -U myapp reports “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 existsudo 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' (or iolog_dir, or !authenticate) — check sudo --version. Newer Ubuntu ships sudo-rs, a memory-safe reimplementation that does not implement I/O logging. The directives are valid in sudo from sudo.ws; on a sudo-rs host, drop the two Defaults:OPS I/O-logging lines and record that gap in the audit report, or install the sudo package 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.

Deliverables

  • · A drop-in sudoers file for operators (e.g., /etc/sudoers.d/10-operators)
  • · A drop-in sudoers file for the myapp service (e.g., /etc/sudoers.d/20-myapp)
  • · The output of visudo -c for each file
  • · An audit report listing every sudo grant on the host

Verification status

Last reviewed
2026-08-09
Executed end to end
not yet run on hardware

The commands and configuration here have been reviewed against the verified software versions, but nobody has run this lab start to finish on a system meeting its prerequisites. Treat the Expected Outcome as the intended result rather than an observed one, and keep the Cleanup section to hand.