Chapter 7. Root, and How to Avoid It

The root of all evil
is never far from your touch.
sudo saves your life.

The security of most Unix-like operating systems has long been considered coarsely grained. One superuser, root, can do anything. Other users are lowly sharecroppers who endure the shackles root places upon them. The problem is that root doesn’t have many shackles and it can’t individualize the ones that it has very well. Some operating systems use POSIX access control lists (ACLs) to provide more fine-grained access controls, but these are difficult to configure correctly.[15]

While it’s true that Unix-like operating systems don’t have detailed access controls, the fact is that most people don’t bother using the controls that do exist. Fortunately, you can combine groups and permissions to handle almost any problem securely.

The Root Password

The root user owns the system and has absolute power over every piece of hardware as well as certain actions that require absolute control, such as manipulating the kernel and changing authentication sources. You need root permissions to perform these tasks. You can log in as root, use su(1) to become root, or use sudo(8) (discussed later this chapter) to get certain root-level privileges without actually using the root account.

To use the root password, you can either log in as root at a console login prompt or, if you belong to the group wheel, log in as yourself and use the switch user command su(1). Of the two, I recommend using su; it logs who uses it and can be used when you are logged in from a remote system. To use su, run the following:

$ su
Password:
#

When prompted, enter the root password. Now check your current user ID with id(1).

# id
uid=0(root) gid=0(wheel) groups=0(wheel), 2(kmem), 3(sys), 4(tty), 5(operator), 20(staff), 31(guest)

As you can see, the UID is 0, which means that you now own the system, and I do mean own it. Consider every keystroke carefully. Carelessness could return your hard drive to its primordial, unformatted state.

Only users in the group wheel can use the root password to become root via su. If you give the root password to users without physical console access and who are in the wheel group, they can enter su and the root password as many times as they want, and it won’t work. (But anyone can use the root account and password at the system console, so don’t make a habit of blabbing the root password all around the office.)

Requiring group membership to use the root password leads to the question, “Who needs root access?” Root is required to configure many parts of OpenBSD, but once the system is running properly, you can greatly decrease or discontinue your use of root. For any remaining tasks that absolutely require root, use sudo.

One of the simplest ways to reduce the need for root is with groups. Unix-like operating systems classify users into groups, which consist of accounts of users who perform similar administrative functions. You can, for example, define a group named dnsadmins and add the accounts of every user who edits DNS zone files to that group. Then, by setting the permissions of the zone files and their directory appropriately, members of that group can edit zone files and reload the name server without the root password. The good news is that you could create such a group for almost any system function, and thereby avoid giving those users root access. Using groups in this manner is a powerful and often neglected system administration tool. I use groups for administering my own servers—just because I can use root doesn’t mean that I want to use root. Users can identify the groups they belong to by using id(1).

# id
uid=1000(mwlucas) gid=1000(mwlucas) groups=1000(mwlucas), 0(wheel), 2005(dnsadmin)

My UID is 1000, and my username is mwlucas. My GID, the primary group ID, is also 1000 and is also named mwlucas. I’m also in the wheel and dnsadmin groups.

The file /etc/group contains all group information. Each line contains four colon-delimited fields: the group name, password, ID number, and list of members.

wheel:*:0:root,mwlucas,pkdick

The group name is a human-friendly name for the group. This group is named wheel. Group names are completely arbitrary and you could call a group lickspittles if you want, but you should choose a name that gives an idea of the group’s purpose. While you might remember that lickspittles can edit the company web page, will that group name make any sense to your coworkers? If it does, you probably need better coworkers.

The second field, the group password, was a great theory that became an appalling practice once exposed to the real world. Modern Unix-like systems don’t do anything with the group password, but the field remains because old programs expect to find something here. The asterisk is just a placeholder to placate such software. (While OpenBSD could eliminate this field, some enterprises share /etc/group across operating systems.)

The third field gives the group’s unique numeric GID. Many programs use the GID rather than the name to identify a group. The wheel group has a GID of 0. The maximum GID is 232, or 4,294,967,296.

Last is a comma-separated list of all users in the group. As you can see, the users root, mwlucas, and pkdick are all members of the wheel group. To add users to a group, add their username to this list, but remember that no /etc/group entry can contain more than 200 users or be longer than 1024 characters.

In order to create a new group, you need a name and GID number. OpenBSD usually assigns the next free GID to a newly created group with GIDs below 1000 reserved for OpenBSD. Programs included in OpenBSD that need a dedicated group ID use one below 1000. Software installed via the OpenBSD package system or ports (discussed in Chapter 13) assigns dedicated GIDs in the 500 to 1000 range. GIDs for user accounts start at 1000 and go up. If you create groups for special roles, start at a high GID so that these administrative groups will be obviously different from user accounts.

Groups, Unprivileged Users, and Group Permissions

The simplest way to create a new group is to use adduser to create an unprivileged user for the role, and use that user’s group to assign file permissions. As with any other unprivileged user, give this account the home directory /var/empty and a shell of nologin. Do not add this user to any other groups (especially not wheel). Lastly, let adduser disable the account. Sure, the shell will prevent logins, but an extra layer of defense won’t hurt.

Now that you have an administrative user and a group, you can assign file ownership. A user and a group own every file. To view the permissions on existing files, including hidden ones, use ls -la. (If you’ve forgotten how file ownership and permissions work, read ls(1) and chmod(1).) Many system administrators focus on file ownership and owner permissions, invest somewhat less time on worldwide permissions, and gloss over group permissions as if they don’t exist. Look closely at the sample DNS files that follow.

# ls -la
total 22
drwxr-xr-x  2 mwlucas  wheel     512 Apr 16 22:02 .
drwxrwxrwt  8 root     wheel     512 Apr 16 22:00 ..
-rw-rw-r--  1 mwlucas  mwlucas 14595 Apr 16 22:02 michaelwlucas.com.db
-rw-r-----  1 mwlucas  wheel     198 Apr 16 22:02 rndc.key

This directory contains two files. The file rndc.key can be read and written by the user mwlucas; anyone in the wheel group can read rndc.key; and no one else can even read it. The file michaelwlucas.com.db can be read or written by the user mwlucas or anyone in the group wheel, but others can only read it. If you’re in the group mwlucas, you can edit this file.

Say I want my junior DNS administrators to be able to edit zone files but not be able to edit the rndc(8) configuration. The file permissions are correct, but I need the files to be owned by my DNS administrative user, dnsadmin. I also want my DNS admins to be able to create new zone files, so they need write permissions on the directory. Here’s how I would do that:

# chown dnsadmin:dnsadmin michaelwlucas.com.db
# chgrp dnsadmin rndc.key
# chown dnsadmin:dnsadmin .
# chmod 775 .
# ls -la
total 22
drwxrwxr-x  2 dnsadmin  dnsadmin    512 Apr 16 22:02 .
drwxrwxrwt  8 root      wheel       512 Apr 16 22:08 ..
-rw-rw-r--  1 dnsadmin  dnsadmin  14595 Apr 16 22:02 michaelwlucas.com
-rw-r--r--  1 root      dnsadmin    198 Apr 16 22:02 rndc.key

As you can see, these files are now owned by the user dnsadmin and the group dnsadmin. Anyone in the group dnsadmin should be able to edit michaelwlucas.com.db without using the root password. The user named—the unprivileged user for the DNS server—should be able to read both files. Add your DNS administrators to the dnsadmin group in /etc/group, and they should no longer need the root password to do their jobs.

This model has limitations, however. While your junior admins can’t accidentally edit rndc.conf, they can delete and replace it. It would be better to put that file in a directory they can read but not edit. And while our DNS administrators might think that they need the root password to restart the name server, they’re wrong. Use rndc(8) to manage the DNS server; other tasks can be managed via cron(8) or through sudo.

While the proper use of groups can almost eliminate the need for root access to edit files, that won’t help with commands that can be run only by root. You could set up a cron job to reload the name server each day at midnight, but every piece of software occasionally needs a manual restart. Because root is an all-or-nothing affair, people who have one minor task to perform have traditionally needed the root password.

OpenBSD includes sudo(8) and its associated tools, which implement fine-grained access control for commands that can be run only by particular users. When configured properly, sudo lets normal users run specific programs as other users, including root. Configured improperly, sudo permits full root access. I’ll explain a basic sudo setup that covers almost all uses, but remember that many more combinations are possible. And don’t be afraid to read sudo(8), sudoers(5), and the documentation at the sudo home page (http://www.gratisoft.us/sudo/).

Why Use sudo?

The sudo tool provides benefits beyond fine-grained privilege control. Every command run via sudo is logged, making it very easy to track who did what. The senior sysadmin can change the root password and not give it out, even to people who have root-level access.

The sudo configuration file is designed to be shared across multiple systems, so one sudo policy can cover your entire network and every operating system. Admittedly, you’ll have trouble using a single sudo configuration on operating systems with wildly unique directory layouts, such as Mac OS X, but you can easily share one configuration among OpenBSD, other BSDs, Linux, and even OpenSolaris or AIX.

If /etc/sudoers contains incorrect syntax, sudo will not run. If you rely on sudo to provide root-level access to the system and you break your sudoers file, you’ll lock yourself out of the root account and lose the ability to correct your error. That is bad.

Fortunately, the visudo(8) program provides some protection against this sort of error by locking /etc/sudoers so that only one person can edit the configuration at a time. It then opens /etc/sudoers in a text editor (vi by default, but it respects the $EDITOR environment variable). Make your changes and save your work. When you exit the editor, visudo should parse the file for syntactic correctness.

If visudo detects an error, it prints out the offending line number and asks what you want to do.

>>> /etc/sudoers: syntax error near line 34 <<<
What now?

Here, I’ve made an error near line 34. I can reedit the file to fix the error, quit without saving any changes, or force visudo to accept this file.

Press the E key, and visudo should return you to the editor. Go to the offending line, fix your error, save the file, and exit the editor again.

Enter the X key, and visudo should quit and revert the configuration file to its original state. Your changes will be lost, but that might be acceptable. It’s better to have an old, working configuration than a new, broken one.

Pressing Q forces visudo to accept the file, busted syntax and all. If sudo can’t parse /etc/sudoers, it will immediately exit. Essentially, you’re telling visudo to break sudo until you log in as root to fix the problem. If you think you understand /etc/sudoers better than visudo does, you’re probably wrong. Even if you’re right, you’re wrong.

The visudo program doesn’t guarantee that the configuration will do what you desire, only that the configuration parses and is valid. A properly formatted configuration that declares “No one may do anything via sudo” is perfectly acceptable to visudo.

The /etc/sudoers file determines who may run which commands as which users. Never edit /etc/sudoers directly, even if you think you know exactly what change you want to make. Always use visudo to change /etc/sudoers.

The various sudoers sample configurations you’ll find are usually very complicated, as they demonstrate all the nifty things sudo can do. At this point, however, you want to do only simple, boring things, like giving particular users access to run specific commands. And the bare syntax is very simple. Every sudoers rule follows this format:

Username    host=command
  • The username is the username of the user who may execute the command, an alias for usernames, or a system group.

  • The host is the hostname of the system this rule applies to. You can share /etc/sudoers across multiple systems. This entry permits per-host rules.

  • The command space lists the commands this rule applies to. You must list the full path to each command, or sudo will not recognize it. If this weren’t a requirement, some untrustworthy soul could just adjust his $PATH to access renamed versions of commands.

For example, suppose I trust user sbaxter to run any command, on any system, as root. I use the keyword ALL to match all possible options for host and command:

sbaxter    ALL=ALL

As the lead sysadmin, I should know which duties I have assigned sbaxter, and exactly which commands he needs. Suppose sbaxter is my DNS minion. I control the actual editing of zone files with group permissions, but there are many legitimate occasions for him to stop, restart, or otherwise slap around the name server program. I want him to use the system script /etc/rc.d/named for this task, and this sudoers entry gives him permission to use the script on all machines.

sbaxter    ALL=/etc/rc.d/named

If I share this file across several machines, it’s likely that many of those machines don’t even run a name server. To restrict my minion’s access to only the DNS server, I’ll change the host field.

sbaxter    dns1=/etc/rc.d/named

Then again, sbaxter is the administrator of the email server mail1. This server is his responsibility, so he needs to run any command. I can set entirely different privileges for him on the mail server and still use the same sudoers file on all the systems.

sbaxter    dns1=/etc/rc.d/named
sbaxter    mail1=ALL

Yes, sbaxter can use visudo on mail1, but he already has full privileges on that machine. I’m comfortable with this, as he knows I’ll hold him responsible for any downtime.

Take several machines with different roles, add multiple sysadmins with differing privilege levels, and your /etc/sudoers file will quickly become complicated. When you have a few users with identical privileges and long lists of commands that you would like them to access, maintaining consistency in each user’s privilege list becomes tedious. Aliases simplify these tasks and make /etc/sudoers much more comprehensible, which makes your life easier.

An alias is a group of users, hosts, or commands. You can use aliases anywhere you would normally use users, hosts, or commands. You might, for example, create an alias called DATABASE_COMMANDS that contains all of the commands your database administrators need to run using sudo.

Let’s take database administrator dwsmith and use an alias to specify his commands.

dwsmith    db1 = (_postgresql) DATABASE_COMMANDS

This alias might not seem to save us much, but suppose we have several database administrators. We could create an alias called DBAs that includes all of them.

DBAs    db1 = (_postgresql) DATABASE_COMMANDS

Suddenly, this one line represents multiple rules. All of the database admins have identical sudo privileges, and when you discover that you need to give them access to an additional command, add the command to the alias, and it immediately becomes available to every database admin. There’s no tedious and error-prone copying of entries between users.

You must define an alias before you can use it, so aliases normally appear at the top of the file. Each alias is made up of a label identifying its type, a name, and a list of its items. Alias types include user aliases, run as aliases, host aliases, and command aliases.

Use an alias exactly as you would normally list the user, command, or hostname. In the previous examples, I defined the user alias DBAs, the run as alias APPOWNER, the host alias DB, and the command alias APPCOMMANDS. Here’s how they might be used:

DBAs    DB = ALL

Here, the user group DBAs can run any command on any server in the DB group, as any user. The members of the group own the servers, and if they screw them up, it’s not my problem.

Well, this attitude sounds good, but the truth is that when they destroy the server, I must get involved. Even if it’s not my fault that they drove the database server into the ditch, it is my problem. I must lock down the commands that they can run, restricting them to only the commands in the APPCOMMANDS alias. So, the DBAs group can now run any command in APPCOMMANDS on the DB servers.

DBAs    DB = APPCOMMANDS

Then I discover that my database admins are either cleverer or dafter than I thought. They run certain database commands as root, creating log files owned by root. The unprivileged database user _postgresql cannot write to these log files, and so the application server crashes. Fixing this requires changing the permissions on those log files, but the database admins do not have permission to run chown. If I give them the ability to change the permissions on arbitrary files, I might as well just give them root access.

To keep this from happening again, I restrict their privileges so they can run their commands only as the application unprivileged users.

DBAs    DB = (APPOWNER) APPCOMMANDS

Everyone in the DBAs group can run any command in APPCOMMANDS, as any user in APPOWNER, on any server in DB. I can change their access by adding entries to the various aliases.

Without aliases, what would this rule look like?

dwsmith,kkrusch    db1,db2,db3 = (_postgresql,www) \
     /home/appowner/bin/*,/usr/local/bin/pg_*

That’s ugly, and it does exactly the same thing.

If you name your aliases well, you’ll find rules easier to understand. While these example aliases are fairly short, I’ve used aliases with up to 20 members. The resulting rules are appalling without aliases.

You can customize sudo’s behavior, or its behavior for certain users, hosts, or aliases, with the Defaults field. For example, one feature of sudo is that if you enter the wrong password, it insults you.

$ sudo -l
Password:
My pet rat can type better than you!
Password:

I typed my password incorrectly. sudo insulted me and offered me a chance to enter my password again. If I enter the wrong password three times, sudo exits.

Insulting the user is just fine in an open source environment, but if you’re in a company, someone will complain to management. You can either go to sensitivity training or proactively disable insults by adding the following line to sudoers:

Defaults !insults

The Defaults statement indicates that the following item affects one or more sudo defaults. The insults option controls insulting the user. The exclamation point (!) is a negation symbol. By putting an exclamation point in front of the option, you turn off the feature. The system will no longer insult users when they demonstrate that they cannot type as well as my pet rat.

$ sudo -l
Password:
Sorry, try again.
Password:

You can override defaults globally or on a per-alias basis.

Certain environment variables can cause problems. For example, $HOME is an obvious one—a user cannot create files in another user’s home directory. Others, such as LD_LIBRARY_PATH, can cause endless annoyance as well as security issues, as applications try to link against the wrong libraries. The sudo program can remove suspicious environment variables, completely reset the user’s environment, or be configured to preserve the original user’s environment.

The env_reset sudoers option is set by default. It purges all environment variables except LOGNAME, SHELL, USER, USERNAME, and anything beginning with SUDO_. You can change this behavior by disabling env_reset, but I strongly recommend against disabling environment purging.

Instead of letting users blindly carry all the random garbage in their environment along with them, create a list of necessary and safe environment variables that they can retain. You’ll see examples in OpenBSD’s default sudoers file using the env_keep option.

Defaults env_keep +="DESTDIR DISTDIR EDITOR FETCH_CMD FLAVOR FTPMODE GROUP MAKE"
Defaults env_keep +="MAKECONF MULTI_PACKAGES NOMAN OKAY_FILES OWNER PKG_CACHE"
Defaults env_keep +="PKG_DBDIR PKG_DESTDIR PKG_PATH PKG_TMPDIR PORTSDIR"
Defaults env_keep +="RELEASEDIR SHARED_ONLY SSH_AUTH_SOCK SUBPACKAGE VISUAL"
Defaults env_keep +="WRKOBJDIR"

The OpenBSD team deems these environment variables safe to pass into a new user account. The += means “add these to the existing list of items to keep.” The environment variables themselves are in quotation marks.

If you need to pass your SSH environment around your servers, you can use scp(1) and sftp(1) to move files to other servers. Read the documentation, create a list of approved environment variables, and add an entry.

Defaults env_keep += "SSH_CLIENT SSH_CONNECTION SSH_TTY SSH_AUTH_SOCK"

Now that you know how to set sudo permissions, let’s see how to actually use it. First, let’s tell sudo that your account has permission to run any command. (You should have root access on your test machine, at least, so this won’t be a security issue.)

The easy way to accomplish this is to uncomment the sudoers entry allowing wheel members access to all commands.

%wheel ALL=(ALL) SETENV: ALL

As a user in wheel, check your sudo permissions.

$ sudo -l
Password:
Matching Defaults entries for mwlucas on this host:
    env_keep+="DESTDIR DISTDIR EDITOR FETCH_CMD FLAVOR FTPMODE GROUP MAKE",
    env_keep+="MAKECONF MULTI_PACKAGES NOMAN OKAY_FILES OWNER PKG_CACHE",
    env_keep+="PKG_DBDIR PKG_DESTDIR PKG_PATH PKG_TMPDIR PORTSDIR",
    env_keep+="RELEASEDIR SHARED_ONLY SSH_AUTH_SOCK SUBPACKAGE VISUAL",
    env_keep+=WRKOBJDIR
User mwlucas may run the following commands on this host:
    (ALL) SETENV: ALL

When sudo asks for a password, enter your own password, not the root password.

The -l flag tells sudo to show you which privileges and settings you have. In response, sudo parses /etc/sudoers and spits out all of the settings that apply to your account on this system. Any host-specific limitations are already evaluated and do not appear.

My flunky sbaxter needs to edit the named configuration file, /etc/named.conf. Consider this sudo configuration:

sbaxter    dns1=/etc/rc.d/named,/sbin/mount_nfs,/usr/bin/vi /etc/named.conf

Looks good, right?

Uh, no.

The first problem is that I’m requiring sbaxter to use a specific editor. Minimal competence in vi is required for system administrators, but I don’t want to force him to use a specific editor to do his day-to-day job. Also, many editors offer shell escapes. While most people are aware of escaping to a shell in vi, emacs has a shell escape as well. If my flunky can escape to a shell while running an editor as root, he gains root access. This is exactly what I want to avoid.

The sudoedit feature lets users edit specific files with their preferred editor, or a default chosen by the sysadmin, without working as root.

sbaxter    dns1=/etc/rc.d/named,/sbin/mount_nfs, \
        sudoedit /etc/named.conf, /etc/rndc.key

The keyword sudoedit is followed by a list of the files that the user can edit, thereby permitting the user to change those files without root privileges.

The user edits the file by passing a filename to sudoedit.

$ sudoedit /etc/named.conf

Technically, the user doesn’t edit the actual file; instead, sudoedit copies the file to a temporary file owned by the user, and when the user closes the editor, it copies the temporary file to the original location. The user never runs the editor as root.

The sudoedit keyword uses the editor given by the environment variable $SUDO_EDITOR, $VISUAL, or $EDITOR. Users can set that variable in their shell if they don’t like what the system offers them.

Now that you know the basics of sudo, let’s consider a configuration that trips up even experienced system administrators. Sometimes you want to prevent users from executing specific commands but give them access to every other command. The sudoers documentation says that you can do this using the exclamation point (!) as a negation character, but that’s not entirely effective. Because this is a popular method, however, I’ll discuss how it works, and then demonstrate how your users automatically get root if you use it.

Start by defining command aliases that contain the forbidden commands. One popular exclusion is su. Another common exclusion is user shells, because if you execute a shell as a user, you become that user.

Cmnd_Alias    SHELLS = /bin/sh,/bin/csh,/usr/local/bin/tcsh
Cmnd_Alias    SU = /usr/bin/su

Now configure a command alias that excludes those commands.

pkdick	ALL = ALL, !SHELLS,!SU

Looks sensible, doesn’t it? And it seems to work.

$ sudo sh
Password:
Sorry, user pkdick is not allowed to execute '/usr/bin/su' as root.

Here’s the catch: Commands are defined by full paths. You’re allowing the user to run any command except for a few specified by full path. All this user needs to do is copy the command to another location and run it.

$ cp /bin/sh /tmp/sh
$ sudo /tmp/sh
#

Welcome to root!

Negating commands can be bypassed by anyone who understands even the basics of sudo, as you’ll find well documented in the sudo manual and other literature. People still insist on using it to protect production systems. Don’t be one of those people.

Every sudo command is logged to /var/log/secure by syslogd. Each log message contains a timestamp, a username, a terminal, the directory where the command was run, the user the command was run as, and the command used.

Apr 30 14:16:50 treble sudo:  mwlucas : TTY=ttyp8 ; PWD=/home/mwlucas ; USER=root ; COMMAND=/usr/bin/su -m

By checking the file secure, you can track exactly who did what and when. (Send your syslog messages to a logging server that your users cannot access to prevent those who screw up from deleting the logs of their screwup.)

May 15 09:14:55 treble sudo:  lasnyder : TTY=ttyp4 ; PWD=/etc ; USER=root ; COMMAND=/bin/rm pf.conf

I know exactly who broke this system and when. The log entry transforms what’s about to happen from “homicide” to “justifiable manslaughter.” That alone makes sudo worth using properly.

This chapter has given you some tips on how to avoid screwing up your system accidentally. Now let’s look at ways to really mess up your system, by mucking with disks and filesystems.



[15] I could just say that “I have never seen POSIX ACLs configured correctly,” but personal anecdotal evidence is not proof. Even the dozens of horrifying personal anecdotes I’ve gathered over decades in this business are not proof. Feel free to prove me wrong, but please, do it on your server.

[16] Oh, all right—battier. Happy?