While proper use of groups can almost eliminate the need to give out the root password to edit files, that won't help with certain commands that can only be run by root. You could set up a cron job to, say, reload the nameserver each day at midnight, but on occasion your DNS administrator might need to restart the nameserver by hand. The ndc(8) command that's used for nameserver administration can only be run by root. Because root is an all-or-nothing affair, traditionally people who have had one minor task to perform have needed the root password.
OpenBSD includes the sudo(8) program and its associated tools, which implement fine-grained access control for commands that can only be run as particular users. With proper setup, the systems administrator can allow others to run any command as any other user. Sudo(8) is a very powerful tool, and can be configured to allow or restrict almost anything in any combination. This makes the documentation quite thick, with the result that the documentation tends to scare off new users. We're going to do a basic sudo setup that will cover almost all uses, but you should be aware that many more combinations are possible, and are documented in sudo(8) and sudoers(5).
Other than the obvious fine-grained access control sudo provides, there are a few other benefits to using sudo. One of the biggest advantages is the command logging. Every sudo(8) command is logged, making it very easy to track who has done what. Also, once you have sudo(8) configured correctly, the senior sysadmin can change the root password and not give it out. Nobody should need the root password if they have the correct sudo permissions, after all! Reducing the number of people who have the root password can help reduce security risk.
Also, sudo(8) can be run on almost all UNIX and UNIX-like operating systems. What's more, a single configuration file can be used on all of these systems, vastly easing administrator overhead.
By far, the most common disadvantage to sudo(8) is that junior administrators don't like it. If people have traditionally had root access on a system, they will perceive that they're losing something when the senior administrator implements sudo(8). The key to overcoming this is to make sure that people have the access that they have to actually perform the tasks that they're responsible for. If a junior administrator complains that he cannot perform a task, it means that he has either overreached his responsibilities or he needs more privileges.
The permissions syntax can be confusing until you understand it. Getting everything correct can be difficult the first time. Once you understand how sudo(8) manages its permissions, however, it's very quick and easy.
Finally, a faulty sudo(8) setup can create security holes. A thoughtless configuration will create holes in the system that a clever junior administrator can use to actually become root. This problem is best dealt with by a combination of careful configuration and administrative policy. [5]
In short, sudo(8) is a setuid root wrapper that can run other commands as any user. It takes the command you want to run and compares it to its internal list of permissions and privileges. If sudo's permissions allow that particular user to run that command as the specified user, sudo runs that command. As root can run commands as any user, sudo can also run commands as any arbitrary system user. You can use this to give any user the ability to run particular commands as root, as any other user, or any combination desired.
The sudo system has three pieces. The first is the actual sudo(8) command, the setuid root wrapper. There's also a configuration file, /etc/sudoers. This file describes who may run what commands as which user and is fully documented in sudoers(5). Finally, the visudo(8) command allows administrators to edit the sudoers file without risking corruption of the sudo system. We'll consider each component in turn.
If the syntax in your sudoers file is incorrect, sudo will not run. If you're relying on sudo to provide access to the sudoers file and you corrupt the sudoers file, you can simultaneously lock yourself out of root-level activities on the system and be unable to correct your error. This is bad. Visudo(8) provides some protection against this sort of error.
Much like vipw(8), visudo(8) locks the file so only one person can edit the configuration file at a time. It then opens the sudo configuration file in an editor (vi(1) by default, but it respects the $EDITOR environment variable). When you exit the editor, visudo parses the file and confirms that there are no sudo syntax errors. This is not a guarantee that the configuration will do what you want, merely a confirmation that the file is actually valid. Visudo(8) will accept a configuration file that says "nobody may do anything via sudo" if the rules are properly formatted.
If visudo finds an error when you exit the editor, it will print out the line number and ask you what you want to do.
# visudo >>> sudoers file: syntax error, line 44 <<< What now?
Here, we've made an error on line 44. You have three choices: edit the file again, quit without saving any of the changes you made, or force visudo to write the sudoers file you created.
If you press "e", visudo will send you back to the editor. You can go to the line it complained about, and try to find your error.
If you enter "x", visudo will quit and revert the configuration file to what it was before you started editing. Your changes will be lost, but that may be all right. It's better to have the old, working configuration than to have a new, nonfunctional configuration.
Entering "Q" forces visudo to accept the file, syntax error and all. If your configuration file has incorrect syntax, sudo(8) will not run. Essentially, you're telling visudo(8) to break sudo(8) until such time as you log in as root to fix the problem. This is almost certainly not what you want to do!
The sudoers file tells sudo who may run which commands as which users. OpenBSD stores the sudoers file as /etc/sudoers. (If you're using this section as a reference for the sudo system on another operating system, finding the sudoers file is your problem.) Never edit this file directly, even if you think you know exactly what change you want to make; always use visudo(8).
The various sample sudoers files you'll find on the Internet frequently look horrid and complicated, as they demonstrate all the nifty things sudo can do. At this stage you don't want to do nifty things — just boring, simple things like give particular users access to run certain commands. The bare syntax is very simple, however. Each rule entry in sudoers has the following format:
1 username 2 host= 3 command
The 1 username is the username of the user who may execute the command or an alias for the username.
The 2 host is the host name of the system where this rule applies. Sudo is designed so you can use one sudoers file on all of your systems. This allows you to set per-host rules.
The 3 command space lists the commands this rule applies to. You must have a full path to each command name, or sudo will not recognize it! (You wouldn't want people to be able to adjust their $PATH variable to access renamed versions of commands, now would you?)
You can use ALL keyword in any of these fields to match all possible options.
For example, suppose I trust user "chris" to run absolutely any command as root, on any system.
chris ALL = ALL
Giving a single junior sysadmin total control of one of my systems isn't very likely. As Chris works for me, I know what duties I have assigned him and exactly what commands I want him to be able to run. Suppose Chris is in charge of the nameserver portion of this system. We control actual editing of the zone files with group permissions, but that won't help when the nameserver must be started, reloaded, or stopped. Here, I'll give him permission to run just the name daemon controller program, ndc(8), on any machine.
chris ALL = /usr/sbin/ndc
If I'm sharing this file across several machines, it's quite probable that many of those machines are not even running a nameserver program. Here, I'll restrict which machine Chris may run this program on to the server called "dns1."
chris dns1 = /usr/sbin/ndc
On the other hand, Chris is the administrator of the email server "mail1." This server is his responsibility, and he can run any commands on it whatsoever. I can set entirely different permissions for him on the mail server and yet use the same sudoers file on all the systems.
chris dns1 = /usr/sbin/ndc chris mail = ALL
You can specify multiple entries in a single field by separating them with commas. Here, I'd like Chris to be able to mount floppy disks with mount(8), as well as control the nameserver.
chris dns1 = /usr/sbin/ndc, /bin/mount
You can specify a username in parentheses before a command to say that the user can use sudo to run those commands as that particular user. For example, suppose we have our nameserver set to run as the user "named," and all commands to control the server must be run as that user.
chris dns1 = (named) /usr/sbin/ndc
As you can imagine, once you have several different machines with multiple administrators with different levels of privilege, this gets complicated very quickly. When you have a few users with identical privileges, and large lists of commands that you'd like them to be able to use, maintenance becomes a challenge, as you have to wade through long lists of users, commands, and machines. Aliases can simplify these tasks and greatly clean up your sudo(8) configuration.
Basically, an alias is a group of users, hosts, or commands. When a user's duties change, you can just add them to the appropriate user alias to give them correct privileges. If you want your system operators to be able to back up the system but not restore data, you can remove restore(8) from their command alias. When you install a new server, adding the server name to the proper server alias will allow you to instantly give sysadmins the proper permissions to do their jobs.
An alias must be defined before it can appear in the sudoers file. For that reason, aliases generally appear at the top of the file. Each alias entry has a label saying what sort of alias it is, a label for the alias, and a list of the members of that alias.
User aliases are groups of users and are labeled with the string User_Alias. They contain a list of users that are in that alias.
User_Alias DNSADMINS = chris,mwlucas
The user alias DNSADMINS contains two users, mwlucas and chris.
A "run as" alias is a special type of user alias. This lists users that other users can run commands as. We earlier mentioned that the nameserver could be run as the user "named." The DNS administrator would need to be able to run commands as that user, and you might have a run as alias for that. Many database applications require their own user, and run as that user. In many cases, a system administrator responsible for an application would also want to be able to run system backups as the user "operator". A run as alias allows you to do exactly that; one user can execute commands as another user, as specified by the sudo rules. These usernames could be listed in parentheses in front of the command, as described in "Running Commands as Non-root Users." Or, you could just create a single run as alias to group these commands. Run as aliases are labeled with Runas_Alias.
Runas_Alias APPADMIN = dbuser,operator
A host alias is just a list of hosts. It's labeled with the string Host_Alias. A host alias can be defined in terms of host names, IP addresses, or network blocks. Remember, if you're using host names your sudo configuration could be vulnerable to DNS problems! Here are examples of all three:
Host_Alias DNSSERVERS = dns1,dns2,dns3 Host_Alias SECURITYSERVERS = 192.168.1.254,192.168.113.254 Host_Alias COMPANYNETWORK = 192.168.1.0/16
A command alias is a list of commands. They're labeled with the string Cmnd_Alias. Here, we have an alias that includes all the commands necessary to back up or restore the system to or from tape.
Cmnd_AliasBACKUPS = /bin/mt,/sbin/restore,/sbin/dump
You might have a command alias that includes all the commands in a particular directory. Suppose we have a custom application that runs as a particular user and places all of its commands in the app user's home directory. Rather than list all the commands, you can just list a directory and use a wildcard to include everything in the directory.
Cmnd_AliasDBCOMMANDS = /usr/home/dbuser/bin/*
Every entry in /etc/sudoers must be on a single line. This can make the lines very long. If you have a long list of alias members or rules, you can skip to another line by using the \ character at the end of each incomplete line.
Cmnd_Alias SHELLS = /bin/sh, /bin/csh, /usr/local/bin/ksh, \ /usr/local/bin/tcsh, /usr/local/bin/bash
To use an alias, just put the alias name in the rule where you would normally list the user, command, or host name. Here, we've previously defined a user alias DNSADMINS. The users listed in the DNSADMINS alias get to run any commands at all on all of our servers.
DNSADMINS ALL = ALL
Let's suppose that our user Phil has to manage an application that runs as a particular user. He can run any command on the system as this application user. We defined a run as alias in the last section for the user alias, APPADMIN, and an alias for commands needed to run the application, DBCOMMANDS.
phil ALL = (APPADMIN)DBCOMMANDS
As the application administrator, Phil might also have to run backups. We have already given the APPOWNER run as alias operator privileges, and we have a separate command alias for backup commands. We can combine them all like this:
phil ALL = (APPOWNER) DBCOMMANDS, (APPOWNER)BACKUPS
This is much simpler to read than what this rule expands to.
phil ALL = (dbuser,operator)/usr/home/dbuser/bin/*,\ (dbuser,operator)/bin/mt, (dbuser,operator)/sbin/restore,\ (dbuser,operator)/sbin/dump
Some of the permissions granted by sudo in this case are unnecessary — having the database user run as alias is not necessary for running backups. Still, it's far tighter than just giving Phil the root password! You can also redefine rules to restrict your users as tightly as you desire.
You can include aliases in aliases. For example, could group the DBCOMMANDS alias and the BACKUPS commands into a single group of commands.
Cmnd_Alias DBADMINS = BACKUPS,DBCOMMANDS
Sudo(8) can pull group information from the system and incorporate it into sudoers as a user alias. Rather than explicitly define a user alias, you can give the OpenBSD group name preceded by a percent sign (%) to indicate it's a group name.
%wheel ALL = ALL
Anyone in the system's wheel group can issue any command as root, on any server.
You can reuse alias names. The user alias DBADMINS is not the same as the command alias DBADMINS. It's quite possible to have entries like this.
Cmnd_Alias DBAPP = /usr/home/dbuser/bin/* Host_Alias DBAPP = server8,server12,server15 RunasAlias DBAPP = dbuser,operator User_Alias DBAPP = chris,mwlucas DBAPP DBAPP = (DBAPP) DBAPP
If you do this, anyone who has to debug with your sudo(8) configuration will curse your name at great length. Even if you consider being cursed as a job perk, things like this tend to result in phone calls during the middle of whatever scant hours the senior sysadmin is permitted to sleep in.
Now that you understand how sudo permissions are set, let's look at how to actually use sudo. Tell sudo that your account has privileges to run any command. (Because any readers of this book should already have root on at least one system — preferably their OpenBSD test box — this won't be a security issue.)
The first time you run sudo(8), it will prompt you for a password. Enter the password for your own account, not the root password. If you give an incorrect password, sudo will insult your typing abilities, mental facilities, or ancestry, and let you try again. After three incorrect passwords, sudo gives up on you. You'll have to re-enter the command you want to run.
Once you enter a correct password, sudo(8) records the time. If you run sudo(8) again within five minutes, it won't ask you for a password. After you don't use sudo for five minutes, however, you must re-authenticate. This makes work easier when you're issuing a series of commands under sudo, but times out reasonably quickly in case you walk away from the computer.
When you're a user on a system with sudo, one thing you'll probably want to know is what commands the systems administrator has permitted you to run. Sudo's -l flag will tell you this:
# sudo -l Password: User mwlucas may run the following commands on this host: (root) ALL #
If you had tighter restrictions, they would be displayed.
To run commands via sudo, just put the word "sudo" before the command you actually want to run. For example, here's how you would become a root by using su via sudo:
# sudo su Password: #
Using sudo(8) to become root simply allows the senior sysadmin keep the root password a closely held secret. This isn't entirely useful, as with unrestricted sudo access junior administrators can change the root password. Still, it's a start toward keeping the system more secure.
You can run more complicated commands under sudo(8), with all of their regular arguments. For example, "tail -f" is excellent to view the end of a log file, and to have new log entries appear on the end of the screen. Some log files are only visible to root — for example, the log that contains sudo access information. You might want to view these logs without bothering to become root.
# sudo tail -f /var/log/authlog openbsd/usr/src/usr.bin/sudo;sudo tail -f /var/log/secure Jul 29 13:24:19 openbsd sudo: mwlucas : TTY=ttyp0 ; PWD=/home/mwlucas ; USER=root ; COMMAND=list Jul 29 13:30:03 openbsd sudo: mwlucas : TTY=ttyp0 ; PWD=/home/mwlucas ; USER=root ; COMMAND=/usr/bin/tail -f /var/log/authlog ...
You can choose to run commands as a user other than root, if you have the appropriate permissions. For example, suppose we have our database application where commands must be run as the database user. We saw in /etc/sudoers how to set up permission to do this. You tell sudo to run as a particular user by using the "-u" flag and a username. For example, the operator user has the privileges necessary to run dump(8) and back up the system.
# sudo -u operator dump /dev/sd0s1
Now that you know the basics of sudo, let's look at a common situation that trips up even experienced systems administrators. Sometimes you want to disallow users from executing certain commands, but give them access to every other command. You can try to do this with the "!" operator, but it's not entirely effective. Because it's a popular setup, however, we'll discuss how this works and then what's wrong with it.
First, define command aliases that contain the forbidden commands. Popular commands to exclude are shells (if you execute a shell as a user, you become that user) and su(1). Then give your user a command rule that excludes those aliases with the "!" operator.
Cmnd_Alias SHELLS = /bin/sh,/bin/csh,/usr/local/bin/tcsh Cmnd_Alias SU = /usr/bin/su mwlucas ALL = ALL,!SHELLS,!SU
Looks great, doesn't it? And it seems to work.
openbsd~;sudo sh Password: Sorry, user mwlucas is not allowed to execute '/bin/sh' as root on openbsd. openbsd~;
Remember, sudo uses full paths for all the commands. You're allowing the user to run any command they want, except for a few that are specified by their full path. All that user needs to do is change their path to one of these commands to run it! The easiest way to do this is by copying the command to another location.
# id uid=1000(mwlucas) gid=1000(mwlucas) groups=1000(mwlucas), 0(wheel) # cp /bin/sh /tmp/sh # sudo /tmp/sh # id uid=0(root) gid=0(wheel) groups=0(wheel), 2(kmem), 3(sys), 4(tty), 5(operator), 20(staff), 31(guest) #
Hello, root!
This sort of restriction can be bypassed trivially by anyone who understands even the basics of how sudo works. This problem is well documented in the sudo manual and the other literature. And people still insist upon using it to protect production systems!
The lesson is: if you have users that you do not trust with unrestricted access to the system, do not exclude commands from their sudo permissions. Instead, explicitly list the commands that they may use, and leave it at that. If these users want more access, they will have to ask you for particular commands — and if you don't trust them, you'll want to know what they're running!
All this tracking and accountability is nice, but where does it account to? Sudo messages are logged to /var/log/secure. Each log message contains a time stamp, the name of the user, the directory where sudo was run, and the command that was run.
Jul 29 11:21:02 openbsd sudo: chris : TTY=ttyp0 ; PWD=/home/chris ; USER=root ; COMMAND=/sbin/mount /dev/fd0 /mnt
In the worst case, you can backtrack exactly what happened when something breaks. For example, if one of my systems doesn't reboot correctly because /etc/rc.conf is missing or corrupt, I can check the sudo logs to see who touched it.
Jul 29 11:34:56 openbsd sudo: chris : TTY=ttyp0 ; PWD=/home/chris ; USER=root ; COMMAND=/bin/rm /etc/rc.conf
If everyone had been using su(1) or even using "sudo su" instead of sudo(8) to run each individual command, I would have had no clue about why the system broke. With sudo(8) logs, once I get this computer up and running again I know who to blame. In this case, my ability to justifiably scream at Chris until I feel better in and of itself makes sudo(8) worth implementing.
[5]Despite the hopes of managers around the world, technical solutions only work so well at solving administrative problems. If people refuse to behave, eventually you need to break out the Big Stick and smack them until they get the idea.