Relentless Coding

A Developer’s Blog

Sudo With Examples

I’ve recently finished “Sudo Mastery” by Michael W. Lucas. He’s a great and fun author that writes both technical guides and novels. I bought his “$ git commit murder” some years ago and was sold.

This post is basically a write-down of what I learned about sudo from his book.

Why Use sudo?

The most common reason to use sudo is to not hand out the root password to every person that logs on to a server. The problem with handing everyone the root password is that:

  1. They would be able to arbitrarily execute every command as root (there’s no limiting the commands that users can run as root).
  2. There would be no audit trail other than that root executed these commands.
  3. If you want to revoke someone’s root access, you would have to change the root password and communicate the new password to the other root users.

sudo aims to solve these problems:

  1. You can specify which users can execute which commands (even including parameters to commands).
  2. sudo can log all commands that are executed and notify when a user fails to authenticate, tries to execute commands she is not allowed to and even when she tries but is not allowed to run sudo at all.
  3. To revoke sudo access, the sysadmin just removes the user from the sudoers policy.

Now, when you are the sole sysadmin of a server, sudo might be overkill for you. You can just use su to become root and execute privileged commands. However, running all commands as root might be dangerous if you make mistakes. Having to type sudo !! (“this time I mean it, dammit!") might give you a little time to think the command you’re about to execute over.

What Everybody Knows About sudo

Run command as root:

$ sudo docker ps

Run command as a different user:

$ sudo -u john docker ps

Run command as a different group:

$ sudo -g http less /var/log/nginx/access.log

Become root by using your own password:

sudo -i

The -i (long: --login) makes sure you get an environment that is equal to what you would get if you logged in as root. This means the environment from the user running sudo is not kept, and root's login-specific resource files (.profile, .bash_profile, etc.) will be run. Specifying -i/--login is recommended.

What Does a Simple sudo Policy Look Like?

Sudo stores its policy in /etc/sudoers. It contains rules, defaults and aliases for hosts, users, run-as groups and commands.

Let’s take a look at a rule.

stefan  www = (root:admin) NOPASSWD: /usr/bin/docker

This policy specifies that the user stefan can execute the command /usr/bin/docker as user root or group admin on the machine with hostname www and does not need to provide a password.

The syntax of a rule in sudoers looks like:

<user> <host> = (<user>:<group>) <tag> <commands>

A rule could be as simple as:

stefan ALL = ALL

This gives user stefan carte blanche to execute whatever command tickles his fancy.

Instead of a Single User, Specify a List of Users

stefan,tom,rik www = /usr/bin/docker

Or you could define a User_Alias and use that:

User_Alias ADMIN = stefan,tom,rik

ADMIN www = /usr/bin/docker

To permit all users to execute a command as root, use the catch-all ALL.

Instead of a Single Machine, Specify a List of Machines

stefan www1,www2,www3 = /usr/bin/docker

Or you could define a Host_Alias and use that:

User_Alias DBAS = henk,trudy,jane
Host_Alias DBS = pg-db-tst,pg-db-acc,pg-db-pro

DBAS DBS = /usr/bin/psql

This allows all DBAs (users henk, trudy and jane) to execute /usr/bin/psql on the machines pg-db-tst, pg-db-acc and pg-db-pro.

Just as with users, you could specify ALL as a catch-all to specify all machines.

Instead of a Single Run-As User or Group, Specify a List of Users or Groups

The run-as specification of a rule (the optional (user:group) part), likewise, can be a list of users and groups:

stefan ALL = (root,mike,jane:root,admin,log) ALL

We could use a Runas_Alias:

Runas_Alias = root,mike,jane,%root,%admin,%log

To specify a group in a Runas_Alias, prefix it with %.

Again, (ALL:ALL) could be used to indicate that the rules allows executing a command masquerading as anybody or as a member of any group. Use (ALL) without the :<group> to allow becoming just any user.

Use Tags to Change sudo with Regard to Behavior of Commands

NOPASSWD: is called a “tag” and you can provide several of them on a single rule:

stefan  www = (root:admin) SETENV: NOPASSWD: /usr/bin/docker

These tags change some aspect of the commands following it. In our example, SETENV: allows the user to use the -E flag (preventing its environment variables from being stripped by sudo) and to keep environment variables set on the command line:

sudo DOCKER_UID=$(id -u http) /usr/bin/docker 

Normally, these variables would be stripped (see man sudoers for more details):

$ sudo FOO=bar printenv FOO
sudo: sorry, you are not allowed to set the following environment variables: FOO

The NOPASSWD: tag allows the user to run the command without entering a password.

Provide a List of Commands in a Single Rule or Use Command Aliases

stefan www = (root) /usr/bin/docker,/usr/bin/docker-compose

Alternatively:

Cmnd_Alias DOCKER = /usr/bin/docker,/usr/bin/docker-compose

stefan www = (root) DOCKER

Ground Rules

Use visudo to Edit the Sudoers Policy

The policy sudo uses is stored in /etc/sudoers. If you syntactically break the policy, sudo won’t run. Therefore, always edit it using visudo. visudo copies the sudoers file to a temporary file and allows you to edit it using your favorite editor. When you close the temporary file, it validates the syntax before overwriting /etc/sudoers. If the syntax is invalid, it will show a prompt with the line number that contains the error and asks you what to do. This allows you to edit the file again (press e and Enter), fix the mistake and save the correct version.

WARNING

Before starting to experiment with sudo, make sure you can su to root so you can fix your sudoers policy. It will be a nasty surprise when you screw up the sudoers policy and only then find out you cannot become root through su.

Whitelist, Not Blacklist

As is common with setting up firewalls, use a whitelist approach rather than a blacklist approach. It is hard to enumerate all the programs a user is not allowed to execute. Rather, explicitly specify the programs a user needs to do her job.

stefan ALL = /usr/bin/createdb,/usr/bin/dropdb,/usr/bin/psql

Rather than

stefan ALL = !/usr/bin/passwd,!/usr/bin/mount,!/usr/bin/hostnamectl

Set noexec to Prevent Shell Escapes

If you allow users to execute editors and pagers as root, you basically give them the keys to the kingdom. Why? Editors such as vi and vim, and pagers such as less and more allow users to execute commands in a subshell. Try it yourself: open a file with less, type an exclamation mark ! and then ls. It will list the files in the directory you executes less from. These commands are executed as the user that runs less, which is root if we had used sudo less file.txt. If, instead of ls, we had typed bash, we would have a root shell.

To prevent this from happening, you can set the noexec parameter, either as a default for everyone (recommended) or per-command:

Defaults!ALL noexec
# alternatively
stefan ALL = ALL, NOEXEC: /usr/bin/less

What this does is prevent executables from running further commands itself. After setting this, executing ls from within less now returns:

!done  (press RETURN)

However, you may find that by setting a global noexec parameter you also can’t run visudo anymore:

$ sudo visudo
visudo: unable to run /usr/bin/vim: Permission denied
visudo: /etc/sudoers.tmp unchanged

Oops. visudo tries to spawn a text editor, but it cannot run additional commands and so it fails. We need to exclude visudo from the default rule:

Defaults!ALL noexec
Defaults!/usr/bin/visudo !noexec

However, we might not want to give everybody access to visudo. A good way to solve this is with a command alias:

Defaults!ALL noexec
Cmnd_Alias MAYEXEC = /usr/bin/visudo
stefan ALL = ALL, EXEC: MAYEXEC

Last Rule Matches, Wins

What does the following sudoers policy do:

stefan ALL = !/usr/bin/docker,!/usr/bin/docker-compose
stefan ALL = ALL

You’ve guessed it: it allows user stefan to execute any command it wants. This looks straightforward, but don’t forget that a sudoers policy might contain includes. For example, the sudoers policy shipped with Arch Linux at the moment of this writing contains this at the end:

#includedir /etc/sudoers.d

Sudo will read every file in the specified directory in lexical order. This means you need to understand what lexical ordering entails, or start every file with a zero-padded number (001-, 002-, etc.).

Every file could have rules that supersede the rules in sudoers.

Use sudoedit, Not sudo ed

Because editing a file as another user is such a common task, a convenience command sudoedit was created. This creates a temporary copy of the file that can be edited by the user invoking sudo (no elevated privileges needed) and when the file gets closed, it replaces the old file with the edited copy. The benefit of using sudoedit is that a user cannot abuse his elevated privileges with shell escapes, because the editor is run as an ordinary (non-privileged) user.

Common Tasks in sudo

Change Default sudo Editor

Defaults editor = /usr/bin/ed

You can specify more than one by separating them with colons:

Defaults editor = /usr/bin/ed:/usr/bin/ex:/usr/bin/vi:/usr/bin/vim

This preferences is used for both visudo and sudoedit, unless one of the SUDO_EDITOR, VISUAL or EDITOR environment variables are set. Keep in mind that sudo strips these environment variables unless explicitly allowed in the env_keep list.

Extend Authentication Timeout Period

By default, sudo will ask you to reauthenticate every five minutes. If you use sudo a lot, you might want to increase this interval.

Defaults timestamp_timeout = 30  # minutes

Authenticate in One Terminal, Be Authenticated Everywhere

By default, sudo will require you to authenticate once per terminal. So, if you open two terminals, authenticate in the first, and then run sudo in the other, you will be required to authenticate again. You can change this behavior by setting timestamp_type to global (authenticate once and you’re authenticated in all your user’s sessions on the same machine), ppid (commands that run from the same parent process, say a shell, are all authenticated) or tty (user’s sessions are authenticated separately per terminal, which is the default).

Defaults timestamp_type = global

Passwordless sudo For Some Commands

To run some commands with elevated privileges without having to enter a password:

stefan ALL = NOPASSWD: /usr/bin/docker

Keep Select Environment Variables

By default, sudo removes most of the environment variables from the invoking user’s environment, because they change the behavior of commands and inappropriate environment variables may break a system. To explicitly allow certain variables:

Defaults env_keep += "EDITOR SYSTEMD_EDITOR"

Note the double quotes around the environment variables. Also note that we’re appending to the env_keep list. Use = instead of += to replace the list.

Run sudo -V as root (sudo sudo -V) to see which variables get stripped by your installation of sudo and which environment variables are already in env_keep.

Flush Authentication Timestamp

When stepping away from your computer, you might want to flush your sudo credentials, so that nobody will be able to run sudo with your cached credentials. Run sudo -K (long: --remove-timestamp) to remove your cached credentials everywhere from the system.

Intruder Detection by Using Command Digests

sudo provides functionality to check if the command you want to run has been tampered with. You can provide a digest of the command and sudo will check it before running the command.

Generate a sha224 digest of /usr/bin/docker:

$ openssl sha224 -binary /usr/bin/docker | base64
EQy+FoFpPakt/QdMqJGJQLLeWMHmsFWG0N1A8Q==

Put the name of the selected digest, a colon and the digest before the command:

stefan ALL = sha224:EQy+FoFpPakt/QdMqJGJQLLeWMHmsFWG0N1A8Q== /usr/bin/docker

When you update the packages on your system, the digest will change. If you plan to use digests, you will probably want to write a script that updates the digests after every upgrade.

Replay sudo Sessions with sudoreplay

To see what your users have been up to, you can replay entire sudo sessions keypress-by-keypress with sudoreplay:

Defaults log_output
Defaults!/usr/bin/sudoreplay !log_output
Cmnd_Alias      REBOOT = /sbin/halt, /sbin/reboot, /sbin/poweroff
Defaults!REBOOT !log_output

log_output tells sudo to log all output send to the terminal (if you change this to log_input it will also log everything the user types). The next lines tell sudo not to log the output of sudoreplay itself and not to log a reboots and shutdowns. Trying to log a reboot or shutdown can delay the process, because sudo might try to write logs to a disk that has just been unmounted as part of it.

sudoreplay offers extensive functionality to find the session you’re interested in. However, that is outside the scope of this post. Read man sudoreplay for more information.

Read More

Read man sudo (sudo command), man sudoers (sudo policy) or if you’re lazy, buy Michael W. Lucas’ book!