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:
- They would be able to arbitrarily execute every command as root (there’s no limiting the commands that users can run as root).
- There would be no audit trail other than that root executed these commands.
- 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:
- You can specify which users can execute which commands (even including parameters to commands).
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 runsudo
at all.- 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!