My-Tiny.Net :: Networking with Virtual Machines



Restricting Root Access with sudo



Sudo (for "superuser do" or "substitute user do") is a standard way to give users some administrative rights without giving out the root password. An advantage of sudo is that an administrator can allow different users access to specific commands based on their needs. Since commands executed via sudo are executed in the user's shell, not a root shell, the root account can be disabled. This is the default for Ubuntu, which is why you see so many helpful instructions predicated by sudo. The sudo and sudoers man pages are not the easiest to read - this page is simpler, but still detailed.

By default, sudo requires that users authenticate themselves with their own password. Once authenticated and assuming that the command is permitted, the command is executed as if by the root user.

So, once access is provided to your account in /etc/sudoers, you can pass any command that requires root privileges as an argument to the sudo command. For example:
sudo /etc/init.d/dhcpd restart            # Run the script as root
The sudo command also provides a comprehensive audit trail: sudo can log both successful and unsuccessful attempts (as well as errors) to syslog, a log file, or both. If a user who is not listed in /etc/sudoers tries to run a command via sudo, mail can be sent to the proper authorities, as defined in the sudoers file (defaults to root).

Once a user has been authenticated, a timestamp is updated and the user may then use sudo without a password for 5 minutes (unless overridden in /etc/sudoers). If the timestamp directory (/var/run/sudo by default) is not owned by root and only writeable by root, sudo will ignore the directory's contents.

Since commands executed via sudo are executed in the user's shell, not a root shell, the root account can be disabled. This is the default for Ubuntu, which is why you see so many helpful instructions predicated by sudo. The Ubuntu setup is like this

grep admin /etc/group
admin:x:115:toby

sudo cat /etc/sudoers
%admin ALL=(ALL) ALL
The above shows a user with the name toby belongs to the admin group, and every member of the admin group is allowed to run any command on any host as any other user.

Whether or not we disable the root account, sudo is useful because it lets us perform actions as our system users. For example, only the postfix user has access to the mail spool, vmail owns all of the dovecot mailboxes, slapd and the webserver run as nobody. None of these accounts have a password or a shell associated with them, but they all own files and processes. With a properly configured /etc/sudoers we can access these accounts without a root shell, like this for example:
sudo -u vmail mc
Before taking a look at /etc/sudoers there is one more important thing: sudo invokes an executable as another user, so bash built-in commands won't work. To use shell built-in commands via sudo, you need to get a shell first. There are three ways to do this:

sudo bash
Use sudo to run bash, or any other one listed in /etc/shells

sudo -s
The -s (shell) flag runs the shell specified in /etc/passwd or the one specified by the SHELL environment variable if it is set.

For these you can add the -E (preserve environment) flag to preserve your existing environment variables, if the security policy in /etc/sudoers (see below).

sudo -i
The -i (simulate initial login) flag runs the shell specified in /etc/passwd as a login shell, so login-specific resource files such as .profile or .login will be read. sudo also attempts to change to that user's home directory before running the shell.

A command specified after the -s or -i flag is passed to the shell for execution via the shell's -c option. If no command is specified, an interactive shell is executed.

visudo

Administrators wanting to edit /etc/sudoers should use the visudo command. visudo locks the sudoers file against multiple simultaneous edits, provides basic sanity checks, and checks for parse errors. If the sudoers file is currently being edited you will receive a message to try again later. visudo parses the sudoers file after the edit and will not save the changes if there is a syntax error.

Warning: It is imperative that sudoers be free of syntax errors! Any error makes sudo unusable. Always edit it with visudo to prevent errors.

The default for the editor that visudo will use is the path to vi found when sudo was compiled, which may not work if you installed a binary package. vi is not the easiest editor to use so you probably want to change it anyway.

The env_editor option is on by default, allowing visudo to use the editor defined by the VISUAL or EDITOR environment variables. This can be a security hole since it allows the user to execute any program they wish by simply setting VISUAL or EDITOR. To fix this, use a Default editor directive with a colon : separated list of editors. visudo will then choose the editor that matches the EDITOR or VISUAL environment variable if possible, or the first editor in the list that exists and is executable.
Defaults    editor=/usr/bin/mcedit:/usr/bin/vi


/etc/sudoers

The sudoers(5) man page is not the easiest to read, so this part is a bit detailed.

The sudoers file is composed of two types of entries: aliases that define lists and user specifications that define rules about who may run what as who on which host.

A user specification has the format
User_List  Host_List = (Runas_List)  tag_list  Cmnd_List
While technically each of these is individually optional, in practice only (Runas_List) and tag_list are sometimes omitted, and the others are always there.

An alias name is a string of uppercase letters, numbers, and underscore characters _ that must start with an uppercase letter. The reserved word ALL is a built in alias that always matches successfully.

User_List is a list of user names, system group names (prefaced with %), and/or User_Alias names who are allowed to run a command.

Host_List is a list of host names, IP addresses, subnets, and/or Host_Alias names. Note that the IP address 127.0.0.1 (localhost) will never match, and the host name localhost will only match if that is the actual host name, which is usually only the case for standalone systems. The Host_List is really only useful if you are creating a master sudoers file to be used on a number of systems, possibly distributed via rdist(1) or shared via NFS-mount.

Runas_List is similar to the User_List except that instead of determining who may run the command it defines the Runas_Spec that determines the user and/or the group that a command may be run as using the -u (user) and -g (group) command line flags. There is more detail on this later.

A tag is relevant for a the rule unless and until its companion tag appears later down the line (see the example sudoers file). There are five possible tag pairs:
  • NOPASSWD / PASSWD: By default, sudo requires that a user authenticate him or herself before a command is run. This behaviour can be modified via the NOPASSWD tag.

  • NOEXEC / EXEC: The NOEXEC tag can be used to prevent programs from running further commands themselves (once a program is running with sudo it has full root privileges, so it could launch a root shell to circumvent any restrictions in the sudoers file).

  • SETENV / NOSETENV: sudo will normally reset the environment to only contain variables set to default values (see below). The SETENV tag for a command allows the user to preserve all of their environment variables with sudo -E. The SETENV tag is implied if the Cmnd_List is ALL, and NOSETENV: ALL can be used to reverse this.

  • LOG_INPUT / NOLOG_INPUT, and LOG_OUTPUT / NOLOG_OUTPUT: see the man page.
Cmnd_List is a list of command names, directories, and/or Cmnd_Alias names. A simple file name allows the user to run the command with any arguments; specific command line arguments (including wildcards) can be included, or you can specify the empty string "" to indicate that the command may only be run without command line arguments. A directory is a fully qualified path name ending with / . When you specify a directory, the user will be able to run any file in that directory, but not in any sub-directories.

Some special characters:

# (pronounced "hash") starts a comment, except for the #include and #includedir directives.

! (pronounced "bang") can be used as a logical not operator in an alias and in front of a command; but it has a different meaning in a Defaults directive.

Long lines can be continued with a backslash \ as the last character on the line (Careful! LAST means no trailing whitespace!) and \ is also used to "escape" these characters in a command, username or hostname: @ ! = : , ( ) \

Whitespace between elements in a list and special characters is optional.

Certain configuration options may be changed from their compiled-in default values with Defaults lines. Defaults also use special characters:
           Defaults @ Host_List 
           Defaults : User_List 
           Defaults > Runas_List
           Defaults ! Cmnd_List 
Note that per-command entries may not include command line arguments. If you need to specify arguments, define a Cmnd_Alias and reference that instead.

Since the sudoers file is parsed in order, the most customized options should go at the end of the file, as the later lines overrides the previous ones. The best place to put the Defaults lines is after the aliases but before the user specifications. Defaults are parsed in the following order: generic, host and user Defaults first, then runas Defaults and finally command Defaults.

Runas

The Runas_Spec (run as specification) determines the user and/or the group that a command may be run as using the -u (user) and -g (group) command line flags. All or part of a Runas_Spec can be defined as a Runas_Alias. The Runas_Spec is enclosed in ( ) in the user specifications.

A Runas_Spec can have two lists, where the first list indicates which users the command may be run as with sudo -u, and the second list (which starts with a :) defines groups that can be specified with sudo -g.

If no Runas_Spec is specified, the command will be run as root with the group listed in the target user's password database entry, and a -g on the command line will cause an error.

If only the first list is specified, the command may be run as any user in the list with the group listed in the target user's password database entry. A -g on the command line will cause an error.

If the first list is empty and the second is specified, the command will be run as the invoking user with the group set to any one on the list. This permits but does not force the user to select a group. If no group is specified on the command line, the command will run with the group listed in the target user's password database entry.

If both lists are specified, the command may be run with any combination of user and group listed.

Environment Variables

The SETENV / NOSETENV tags control the setting of environment variables on a per-command basis. The options env_reset (on by default) and setenv (off by default) control the ability to set environment variables more generally.

When env_reset is on, or the NOSETENV tag applies to a command, sudo runs the command with the environment variables TERM, (inherited from the old environment), PATH, HOME, MAIL, SHELL, LOGNAME, USER, USERNAME (set to default values) and SUDO_* (see the sudo(8) man page), plus any variables in the caller's environment that match the env_keep and env_check lists. If env_reset is off, variables that are not explicitly denied by env_check and env_delete are are inherited from the invoking process.

When setenv is on, or when the SETENV tag applies to a command, the user can disable env_reset from the command line using sudo -E. As a special case, sudo -i (initial login) will ignore the value of env_reset and use values for DISPLAY, PATH and TERM inherited from the invoking process; HOME, MAIL, SHELL, USER, and LOGNAME are set based on the target user.

Run sudo -V as root to see the list of environment variables that sudo allows or denies.

Logging

By default, sudo logs via syslog. A line like the following in sudoers corresponds to the default values for syslog facility, priority to use when the user authenticates unsuccessfully, and priority to use when the user authenticates successfully.
Defaults syslog=authpriv, syslog_badpri=alert, syslog_goodpri=notice
Run sudo -V |less as root to see the current settings.

Make sure you have an entry in your syslog.conf file to save the sudo messages, and don't forget to send a SIGHUP to your syslogd so that it re-reads its conf file. Also, remember that syslogd does *not* create log files, you need to create the file before syslogd can write to it.

This line stops logging via syslog and turns on logging to a file
Defaults !syslog, logfile=/root/admin/sudo.log


Some notes

  • As a general rule, SIGTSTP should be used instead of SIGSTOP when you wish to suspend a command being run by sudo.

  • Sudo must be setuid root to do its work: chmod 4111 /usr/local/bin/sudo

  • Unlike files included via #include, visudo will not edit the files in a #includedir directory unless one of them contains a syntax error. It is still possible to run visudo with the -f flag to edit these files directly.

  • If your changes to /etc/sudoers don't seem to have any effect, check that they are not trying to use aliases that are not defined yet and that no other user specifications later in the file are overriding what you are trying to accomplish.

  • This example from the man page shows how to make a usage listing of the directories in /home. Note that this runs the commands in a root sub-shell to make the cd and file redirection work.

    sudo sh -c "cd /home; du -s * | sort -rn > USAGE"

  • When requiretty is set in /etc/sudoers, sudo can only be run from a login session ("tty" is the name for a screen or "terminal" in the Unix tradition, usually held to be short for Teletype). By default, when you ssh without a command to execute (just to log in), a "pseudo tty" is allocated automatically. When you specify a command to execute that is not just plain text output, you need to tell ssh specifically to open a tty for you, like this:

    ssh -t user@host.name htop.

    We need to do the same thing when sudo requires a tty:

    ssh -t user@box.example.com "sudo -n -u user -c runcommand"

    The -n option to sudo will prevent sudo from prompting for a password, but if the command requires a password you'll get an error. See the Smoother SSH page under SSL and SSH on the menu for some ways around this.