Linux: Managing sudo

TLDR;

You can control access to root level and other user commands with sudo. Use visudo to edit the /etc/sudoers file or, preferentially, use /etc/sudoers.d/01-rules

Here's the basic examples:

#For user paul to run anything as anyone:
paul ALL=(ALL:ALL) ALL
#For group webdevs to restart the nginx service:
%webdevs ALL=(ALL:ALL) /usr/bin/systemctl restart nginx
#For group webdevs to do anything in the context of the www-data user without requiring a password:
%webdevs ALL=(www-data) NOPASSWD: ALL

What does Sudo do?

Sudo, short for SuperUser DO, executes a program in the context of "root" regardless of what user account you are using to log in. This is important because modern security practices say you should never sign in initially as your administrator account. In addition, root is often disabled or has a very long and complex password.

đź’ˇ
What is Root?
All POSIX systems (Unix, Linux, FreeBSD, MacOS) have a user account called "root". This is User ID #0 and Group ID #0. The account is called root because when the kernel loads, the first program executed is in the context of this user, and the kernel operations occur in the context of this user when it interacts with the user layer. This means the entire process tree comes from this user so the name root was selected.

The root user has access to every file, process, memory address, and kernel function on the system regardless of any other security setting. Because of this, if you have root access then you have full access to everything on the system so this access must be monitored and controlled. This is similar to having "Administrator" access on a Windows system.

How does Sudo Work?

Sudo allows access after consulting the configuration file(s) to make sure the user should be able to execute the requested command as the requested user/group. It also validates the user's password if required by configuration and performs other functions based on plugins. After that, it executes the requested command with appropriate parameters.

Expand this for the detailed explanation on how it works

There are multiple attributes you can set on a file in Linux. Among these, you can define who can execute the file. This can be restricted differently at the levels of Owning User, Owning Group, or Everyone Else.

There is an additional option affecting the execution of the file. If the "setuid" attribute is set (identified as an "s") then when the file is executed, it is executed as the user/group who owns it regardless of who started the file. Sudo, and programs with this option set, are potentially a large security risk so the codebases for programs that do this are heavily analyzed and searched for holes in how they determine the validity of the user.

To see everything on your system taking advantage of this feature, just run this: sudo find / -type f -perm /4000

If you have a variety of users interactively using the system then it may be necessary to monitor this because if user "paul" sets a file up with public access with this option enabled then it may allow other users to execute commands as "paul" maliciously, particularly if it is a script setup like this without proper security.

Let's look at an example of how this can be useful, apart from the system tools that use it. This example will be moved to another article later

Let's say that we have a web server and we want a web deployment script, or compiled program, that will have the ability to overwrite the web files but we don't want to give developers access directly to the files. We set the script's owning user to www-data, the owning group to webdevs, and grant executable to the group level with setuid on the user level. My user, paul, is in the webdevs group. When the script is executed, it runs in the context of www-data. That means that, on the filesystem, it cannot access files for user paul, but it CAN execute git and update from there the files located in the web folders.

Traditionally this example would be accommodated via sudo but controlling this at the file permission level bypasses the requirement for root access to edit the sudo configuration.

Configuration File

Sudo, by default, stores the configuration in /etc/sudoers. To edit this file, you MUST use the visudo command. This will use the system-configured editor, in Ubuntu that means nano. The visudo command validates the configuration of the file after saving to prevent locking yourself out of your system due to a bad config file. If the configuration is invalid, it will not update the file on the system.

The configuration is read when sudo is executed, so changes are immediate.

âť—
It is recommended to store your sudo configuration in /etc/sudoers.d/* rather than modifying /etc/sudoers More on this further down.

The Basic permission line

There are several options we will cover supported by this file, but first I want to talk about the basic permissions line. The default configuration in Ubuntu has this:

# User privilege specification
root    ALL=(ALL:ALL) ALL

# Members of the admin group may gain root privileges
%admin ALL=(ALL) ALL

# Allow members of group sudo to execute any command
%sudo   ALL=(ALL:ALL) ALL

Here's the breakdown of what these fields mean:
<running user> <host>=(<runas user>:<runas group>) <command 1>, <command 2>

Running User

The running user field can be in one of four formats. All text is case sensitive. Here are the examples for discussion:

paul ALL=(ALL:ALL) ALL
#1001 ALL=(ALL:ALL) ALL
%webdev ALL=(ALL:ALL) ALL
%#33 ALL=(ALL:ALL) ALL

The 4 formats are:

  • A plain username, like paul which will match if the username is the same.
  • A user can be specified by uid if prefixed by #. Please note that # at the start is ALSO used for comments so don't ever start a comment in the sudoers files with a number at the beginning. The uid can be found by running id paul or checking the /etc/passwd file.
  • You can specify a group if you prefix it with %. So %webdev applies to everyone in the webdev group.
  • Finaly, you can specify a group using the gid number. The gid can be found by running id paul or checking the /etc/groups file.
đź’ˇ
If using an external authentication system, such as Active Directory, then you may have spaces in the username or group.
You should use an escape character for these. For example:
test\ user ALL=(ALL:ALL) ALL
%test\ group ALL=(ALL:ALL) ALL
Or, you can put them in quotes:
"test user" ALL=(ALL:ALL) ALL
"%test group" ALL=(ALL:ALL) ALL

You can also negate these options. The format of the negation does not need to

  • If you specify %admin, !paul, !bob then everyone in admin except for paul and bob can use this entry.
  • Perhaps more useful is to create a group called nosudo.
    Then use %admin, !%nosudo in order to exclude the group of users.
  • âť—According to the developers, negating ALL like with ALL, !%nosudo is "generally not effective"

Host

This is a field I've not seen used in any of the example files I've seen. It is typically left at ALL.

You can put a hostname (test-vm1), IP Address (192.168.1.2) or IP CIDR (192.168.1.0/24) here. Hostname is NOT case sensitive.

There are two cases I know of that this is useful:

  • It allows for you to deploy a single sudoers file to all systems but still have one-off entries that are only applicable to specific systems by specifying their host
  • It allows you to have entries that change depending on the subnet that a computer is on. For example, if your primary network is 192.168.42.0/24 then you might do this so that you don't have to re-enter your password when you are in the office but have to when away from the office:
paul 192.168.42.0/24=(ALL) NOPASSWD: ALL
paul ALL=(ALL) ALL
âť—
192.168.0.0/24 and 192.168.1.0/24 are VERY common so please don't do this if you use one of those ranges. If those are your internal network ranges, consider changing them if you wish to take advantage of this functionality.

Because these rules are evaluated at time of execution, if you connect to a VPN that puts you on a network with a special rule that special rule will begin to apply. If you used sudo to execute a command with a host specified and then disconnect from the network that command will continue to execute.

Runas User/Group

This is the simplest section of the entire line. Here you can specify which user or group you can run sudo as. Most people use sudo only for root, so (ALL) or (ALL:ALL) is really saying (root:root). It is a good idea to be more specific in which user someone can run as.

To understand this better, let's look at a few scenarios related to websites of the right way and the wrong way.

paul ALL=(www-data) ALL
bob ALL=(:www-dev) ALL
alice ALL=(ALL:www-dev) ALL
luke ALL=(www-data:ALL) ALL

Consider first that all permissions in POSIX systems (FreeBSD, Linux, Mac, etc) can be thought of as filesystem permissions. It's more complicated than that, but it's the easy way to understand it.

Both sides accept the ALL keyword and if you specify a user you don't have to specify a group and vice versa.

  • In the first example here, paul can run sudo -u www-data -i in order to get an interactive prompt in the context of the www-data user. That would allow him to run commands and access files that www-data (and its groups) can access. This is especially useful for php sites that often require running commands as the web server's user account.
  • In the second example, we've defined bob who can run sudo -g www-dev -i in order to get an interactive prompt in the context of the www-dev group. Please note bob is still the user context, this new context simply has the www-dev group added to him. This is useful if he sometimes needs access to the web files but you want to log that access separately from the user logins. Or if specific scripts need that access and need to be logged.
  • The third example looks great. We're trying to say alice can run in the context of all users who have the www-dev group. But there is a problem. The two parts are treated separately so alice can run in the context of www-dev group and can run in the context of any user on the system.
  • The fourth example may look harmless. luke can run in the context of the www-data user and run in any group's context. That second half is the issue. Consider the /etc/shadow file where only root and the shadow group can access it. It contains password hashes, so that makes sense that access is restricted. With "ALL" group specified, luke can run sudo -g shadow cat /etc/shadow and view the contents of that file.

In short, be VERY careful with the ALL keyword in this context. If you don't want the user to have full access to the system, don't use ALL anywhere in the ( ) section.

Commands

The command is what the user is allowed to do. There are a few ways to limit access:

  • ALL - The ALL keyword simply means everything.
  • /usr/bin/systemctl - This would allow the user to run systemctl and run it with everything it can do.
  • /usr/bin/systemctl restart nginx - This would allow the user to restart the service nginx
  • /usr/bin/systemctl * nginx - This would allow the user to run any parameters against nginx. Get status, enable, disable, etc. But consider that this is also allowed: systemctl disable ssh nginx Because of how the parameters work and how the wildcard works.
  • /usr/bin/systemctl restart nginx, /usr/bin/systemctl start nginx - This would allow the user to restart the service nginx or start it if it is stopped. status is natively allowed for a non-privileged user. This is an example of multiple commands on one line.
  • /*/reboot - This would allow the program reboot to be run regardless of the path. This is generally a bad idea because they can rename anything to reboot and execute it.

You can prefix each command with one or more options. Options are automatically carried to the next command:

  • NOPASSWD: - This skips the requirement of the user to enter a password.
  • MAIL: - Specifies that a notification should be emailed that this command has been executed.
  • sha512: - There are other supported hash types and sizes. This allows you to specify the hash of the executable being run to verify no tampering has occurred with the executable. The hash changes when an update is done, so use this carefully.