doas mastery
The doas utility comes with man pages, both for the doas command and the doas.conf config file, but some people prefer a touch more narrative in their documentation. So here is the official doas mastery book. Or pamphlet, anyway.
UNIX systems have two classes of user, the super user and regular users. The super user is super, and everybody else is not. This concentration of power keeps things simple, but also means that often too much power is granted. Usually we only need super user powers to perform one task. We would rather not have such power all the time. Think of the responsibility that would entail! Like the sudo command, doas allows for subdivision of super user privileges, granting them only for specific tasks.
The doas command itself has a few options, which we’ll discuss somewhat later, but the most interesting part is the configuration file. This is where the real magic happens.
doas.conf
The simplest possible doas.conf config really is quite simple. Don’t blink or you’ll miss it.
The empty config doesn’t do anything, but allows us to discuss the default rule set. There is none. doas starts evaluating rules in the DENY state. If no rules match, then the action is denied. That’s right. Even root will receive a permission denied error. This is not particularly useful, per se, but one can be assured that reading the installed doas.conf file is sufficient to fully understand the system behavior.
If there’s no configuration file at all, doas will instead exit after printing a message that it is not enabled.
The simplest useful configuration probably looks more like this.
permit :wheel
This lets any user in the wheel group run any command as root. It’s approximately equivalent to the su command. For a more complete emulation, we’d like to allow root to run commands without a password.
permit :wheel
permit nopass keepenv root
We add the root rule second because doas evaluates rules in a last match manner. root is in the wheel group, so the first rule will match, and then we need to override that with a second rule. Remember to always start with general rules, then make them more specific.
Why even run doas as root? Because sometimes you’d like to switch to a less privileged user. Or you have a script which uses doas to elevate privileges for an important operation, but you’re already root when you run it.
passwords
The default behavior of doas is to require authentication every time the user runs a command. Normally this means entering their password. This can become tedious if multiple commands are executed. There are two keywords that can be added to a doas.conf rule to alter this requirement.
Adding the nopass
keyword to a rule means the user is always permitted to run the command without entering a password. We’ve seen this above in the rule for root. The user is already root, so they can do anything they like and there’s no reason to require a password.
By adding the persist
keyword, doas will remember that the user authenticated previously and not require further confirmation for a timeout of five minutes.
permit persist :wheel
This rule recreates the common sudo configuration of requiring a password for wheel users the first time a command is run.
The authentication information doas uses is recorded in the kernel and attached to the current session. Unlike filesystem tickets, it is not accessible to other users and difficult to fake. The timeout will always take place in real time, not computer time, meaning that adjusting the system clock backwards can not grant new life to an expired ticket. Repeated executions will reset the timeout, but only if the rule is marked persist. Rules cannot be both persist and nopass, nor will nopass rules extend the timeout.
If you have multiple shell logins to a machine, each login will require authentication. Additionally, the authentication information includes the parent shell process ID. This means that executing doas again in a shell script will require authentication. Or, to repeat that another way, if you run a script or program of uncertain quality, it won’t be able to silently elevate privileges. (That’s the theory anyway. In practice the check leaves quite some wiggle room.)
permit persist :wheel
permit :wheel cmd reboot
Authentication checks are only skipped for rules marked persist. To prevent mistakes, important commands can be relisted without persist to always require a password.
The -L
command line option to doas is analagous to logout and immediately deletes any persisted authentication information in this terminal without waiting for the timeout.
environment
There are two basic ways information is provided to executed commands. The first and most obvious is the command line. The second and less visible way is via environment variables. Despite being mostly invisible, environment variables can have dramatic effects on a program’s behavior. Therefore it is important that doas provide some filtering to prevent unintended consequences. By default, only a short list of variables is copied.
There are two config keywords related to environment, keepenv
and setenv
. The first is very simple. We’ve seen it before, in the rule for root above. keepenv
simply means that the entire environment should be retained and passed as is to the executed command. This is a convenient shortcut for trusted users.
Using setenv
is a little more complicated, because it allows a combination of adding, modifying, and deleting environment variables. Let’s look at an example.
permit setenv { PKG_PATH ENV=/root/.kshrc PS1=$DOAS_PS1 } zoltan
This rule allows zoltan to run commands as root. The PKG_PATH environment is retained. The ENV environment, which points to a configuration file for ksh, is redirected to one owned by root. Finally, we override the PS1 setting which controls the shell’s prompt display. We don’t specify the new value in this configuration file, but instead use whatever value is in DOAS_PS1. This lets zoltan adjust the root prompt as desired.
users and targets
Each rule in doas.conf applies to an individual or group, specified after permit and any options. There’s no keyword to distinguish between users and groups. Instead, syntax similar to chown is used, with user
names by themselves or :group
names with a leading colon. Now is a good time for a reminder that rules are always evaluated in order with the last matching rule winning. Specific user rules are not automatically preferred over group rules unless they come later in the file.
In most situations, doas is used to run commands as root. This requires no additional syntax. However, we may also wish to restrict some rules to targeting certain users.
permit nopass zoltan as dbadmin
This rule will let zoltan run commands as the database administrator without entering a password, but by itself doesn’t permit zoltan to run anything as root.
commands
We’re nearing the end of our tour of the doas.conf syntax. doas can also be restricted so that rules only apply for certain commands, or even certain commands with particular arguments.
permit nopass :operator cmd reboot
Normally, reboot requires root privileges. It is instead executed indirectly by the setuid program shutdown, whose execution is restricted to the operator group. The above rule allows these users to run reboot directly. However, operators won’t be able to run other commands as root or obtain a shell.
permit zoltan cmd sh args /etc/netstart
Here we allow zoltan to rerun the netstart script that configures network interfaces. We don’t give zoltan permission to run any shell command, only the netstart script.
In both of these examples, the cmd
was specified with only the base name. In these cases, doas will restrict itself to only executing commands in the system PATH (/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin
). zoltan won’t be able to install a sh binary in his home directory and set PATH to ~/bin to subvert our intentions. Absolute pathnames can also be specified, however the user will also be required to type them out in full.
Any command arguments specified must be specified in their entirety.
permit zoltan cmd ifconfig args iwm0 up
permit zoltan cmd ifconfig args iwm0 down
These two rules will allow zoltan to turn the wifi interface on and off, but not change any of its other parameters.
Some userland utilities that gather information from the kernel only present a restricted subset of information when as regular users. To see the full information requires running as root. For example, fstat will only print minimal information about unix domain sockets.
tedu Xorg 94581 16* unix stream 0x0
tedu Xorg 94581 17* unix stream 0x0
tedu Xorg 94581 18* unix stream 0x0
tedu Xorg 94581 19* unix stream 0x0
tedu Xorg 94581 20* unix stream 0x0
But when run again as root, we see much more information.
tedu Xorg 94581 16* unix stream 0xffff800000b45980 <-> 0xffff800000b3d700
tedu Xorg 94581 17* unix stream 0xffff800000b3d880 <-> 0xffff800000b45500
tedu Xorg 94581 18* unix stream 0xffff800000b45480 <-> 0xffff800000b45300
tedu Xorg 94581 19* unix stream 0xffff800000b45080 <-> 0xffff800000128e80
tedu Xorg 94581 20* unix stream 0xffff800000b60280 <-> 0xffff800000b60780
This allows us to match these sockets up with the process on the other end.
tedu xterm 33159 3* unix stream 0xffff800000b45300 <-> 0xffff800000b45480
tedu Xorg 94581 18* unix stream 0xffff800000b45480 <-> 0xffff800000b45300
These kernel addresses are normally hidden because they reveal information about the kernel’s memory layout which can be used to facilitate exploits, but if we trust tedu (and who doesn’t, really?) then we can change this with a simple config rule.
permit nopass tedu cmd fstat args -u tedu
Using fstat, one can always see the open files of other users’ processes, but we specify arguments here to prevent tedu from matching up connections between processes.
deny
In contrast to all the permit
rules we’ve seen so far, it’s also possible to create a deny
rule that specifically denies command execution. This feature is most useful as a safeguard against accidental typos by trusted operators. It should not be used as a security feature because an exhaustive blacklist is exhausting to create. Better to craft a ruleset that doesn’t grant unintentional privileges.
permit :wheel
deny zoltan cmd reboot
Assuming that zoltan is in group wheel, we’ll let him run any command. Just not reboot. Maybe zoltan is a little trigger happy and has a habit of typing into the wrong terminal. This ruleset won’t actually prevent zoltan from rebooting the machine by any means; the first rule grants sufficient privilege to edit doas.conf and remove the second rule, among many other possibilities. The assumption is that everybody is on the same team.
doas
The doas command itself has a few options.
Since we just finished looking at the config file syntax, the -C
option can be used to syntax check a new file before installing. It also permits checking the result of rule set evaluation for a given command and arguments without actually running the command. Continuing with the fstat example from earlier, we can check that only the command with arguments is matched.
$ doas -C doas.conf fstat
deny
$ doas -C doas.conf fstat -u tedu
permit nopass
When writing possibly noninteractive scripts that incorporate doas, the -n
option is helpful to prevent future failures. Only nopass
rules will successfully execute. Any rule that requires a password will instead immediately fail. This includes any rules with the persist
keyword, regardless of whether the user recently authorized.
thanks
It would not have been possible to finish doas without the support of many other OpenBSD developers and users. In particular, Vadim Zhukov contributed immensely to the config parser and regress testsuite; Todd Miller, Damien Miller, and Martijn van Duren provided ideas and inspiration; Theo de Raadt provided backup to rejecting feature requests; Henning Brauer gave me the idea for tying authorization persistence to the terminal; and I owe Michael Lucas for stealing a catchy title.