To understand this document

You need to have used both sudo and op at least once.

Providing a sudo interface to op via sudop

The Problem

In a sensitive production environment where escalated privilege must be allowed for multiple projects managed by multiple teams, an administrator will want a tool that allows very specific and auditable access to be given to each project managed in a scalable way. A tool that can be used meet this need is op. However, there is a significant user base that is already familiar with and user automation written for sudo. Unfortunately, sudo is lacking in its ability to strictly specify rules, harder to scale to many projects, and much harder to audit than op.

The Solution

With sudop, it is possible to provide a sudo interface to customers while still managing the rules in op. Only a little bit of glue in the op rule-base is needed as a hint sudop for it to map a sudo rule that allows the execution of only a single file or range of files with a full path on the command line to an op rule.

The user calls sudop in place of sudo. Either the user's path is modified to find a symlink to sudop before sudo itself, or the sudo binary is simply replaced with a symlink to sudo. The sudo options that can be translated to op are honored. Options that are not relevant to an op rule, such as listing default sudo permissions, are silently ignored. Wherever possible, the output of sudop is made to match sudo.

How it works

sudop does not do any privilege escalation on its own. If a command is successfully mapped to an op rule, op is simply execed as the calling user with the arguments specified by the mapping followed by the arguments specified by the user. The exit code of sudop is the exit code of op unless the mapping itself failed. If the -b option is specified, op is double forked instead and success is always returned.

The mapping data sudop uses is stored in the op access configuration files in /usr/local/lib/op alongside the real op rules. These access files are not world readable for good reason, so sudop itself needs privilege escalation to lookup the rule mappings. A support script, sudomap is provided and installed in /usr/local/libexec/sudop. An op rule must be installed just to allow sudop to run this script as the right user (see below).

When a user passes a command to sudop, the sudomap rule is called with the command the user specified, but not any of the arguments. sudomap uses mk to scan all of the .cf files in the current working directory for mk rules with the name Sudop. If a pattern in the submarker matches the command the user specifies, the mk rule is executed as a shell command, and the output is returned to sudop.

Note that executing a Sudop mk rule is not the same as executing an op rule. The Sudop mk rules should only return rule mapping information to sudop. Generally, the command executed by mk rule will only be an echo statement, however much more sophisticated mapping is possible.

If specified by the user when calling sudop, the options -u and -g are also passed to sudomap. When these options are given, the environment variables USER and GROUP are defined when mk is executed. These can be used to setup the -u and -g options to op. Note that the -f option is also particularly useful when combined with the %s mk expander (see below).

The output line from sudomap is captured by sudop and passed as-is as arguments to op. If sudomap does not return any output, it is assumed the mapping has failed, and a sudop exits with a failure.

If the user calls sudop with the -l option, this option is passed to sudomap with no other arguments. sudomap will then have mk execute every Sudop mk rule with a submarker of -list. The combined output of all those executions in translated into a list of possible commands for the user.

Installing

Prerequisites

Both /usr/local/bin and /usr/bin will be searched for these tools.

Executable

By default, the sudop script itself is installed under it's own name in /usr/local/bin. If sudo is not installed or you simply want users to be able to choose the alternate interface, you can make a symlink from /usr/local/bin/sudo to /usr/local/bin/sudop. Any user that has /usr/local/bin in their path before a path to a real copy of sudo will use the sudop interface instead.

There is a mk rule on the sudop Makefile, NukeSudo, that will replace any copy of sudo in /usr/local/bin with a symlink to sudop. This should override sudo in favor of sudop for all the users on the system.

mk -m NukeSudo -droot@host Makefile

Command Mapper

An op rule needs to be created that runs sudomap as a user that can read the access files, usually root. The op rule itself must be named sudomap, and it will be called transparently by sudop. A user should never need to run it directly, but any user that is to be allowed to run sudop needs to be able to run the rule. The current working directory of the rule must be where rule mappings are stored, which should be the op access file directory.

This example rule should work for almost all cases.

sudomap	/usr/local/libexec/sudop/sudomap $* ;
	uid=root
	dir=/usr/local/lib/op
	users=.*

Creating Rule Mappings

Mapping rules convert the mnemonic command into an escalated environment specification and a shell command to run in that envronment.

Mapping commands to op rules

For each sudo command to be mapped, there must be a corresponding mk rule in one of the op access files with the marker Sudop. The submarker in the parenthesis that follow should be an absolute path that matches a specific command to be mapped a single op rule. If a range of sudo commands need to be mapped to a single op rule, then the submarker should be an asterisk and an embedded mk mapfile should be used to match the commands by regular expression (see example below).

When run, the mk rule that matches the command must output a single line that includes the name of the op rule to run in place of the sudo command. The mk rule must not run the op itself. Any extra options to op can be specified by placing them before the name of the op rule in the output line. This is especially useful for op's -f and -s options.

Generally, a Sudop mk rule takes the following form and only an echo statement will be executed.

# $Sudop(command): echo "op_options op_rule"

When a range of commands must be handled by a rule, an embedded mk mapfile should be used to match the given command to a regular expression. Multiple matches may be placed in a single embedded mapfile.

# $Sudop(*): %|"# "%J echo "%<%j>"
# %s command_regexp op_options op_rule
# %s command_regexp op_options op_rule
# $Sudop(*): %j%^

Advertising command support

There is a special submarker, -list, to support sudop's -l option. Support for a particular sudo command or command pattern can be advertised to the user by adding a Sudop(-list) mk rule. Each one of these mk rules will result in a line of output in the user's list. The mk must produce one line of output consists of two fields separated by a colon, a list of users and the sudo-like command glob pattern.

A Sudop(-list) mk rule should take the following form and only consist of an echo statement.

# $Sudop(-list): echo "user:command_glob"

Examples

Allow any user in the trusted group to run tcpdump as root. Limit the options so that -w can only specify a file under /tmp/tcpdumpsto prevent the user from writing on sensitive files as root. Notice that a sudo argument glob can't match the -w option to tcpdump specifically, so we do the best we can and just tell the user any option is acceptable when they list the rule.

# $Sudop(/usr/sbin/tcpdump): echo "tcpdump"
# $Sudop(-list): echo "root:/usr/sbin/tcpdump *"
tcpdump		/usr/sbin/tcpdump $* ;
		$*=^([^-]|-[^w]|-w/tmp/tcpdumps/)
		uid=root
		groups=^trusted$

Only execute a start script with no options. Run only as user myapp. Allow only users in group myappdev.

# $Sudop(/opt/myapp/libexec/start): echo "myapp-start"
# $Sudop(-list): echo "myapp:/opt/myapp/libexec/start"
myapp-start	/opt/myapp/libexec/start ;
		uid=myapp
		groups=^myappdev$

Execute anything in the application's libexec directory with any options. We pass the full command path the user specified to op's -f so that an arbitrary executable can be run. However, the allowed path of the command is restricted by a regular expression in the op rule. We use the mapfile feature of mk to match the command in the submarker against a regular expression.

# $Sudop(*): %|"# "%J echo "%<%j>"
# %s ^/opt/myapp/libexec/ -f \"%s\" myapp
# $Sudop(*): %j%^
# $Sudop(-list): echo "myapp:/opt/myapp/libexec/* *"
myapp		$f $* ;
		%f.path=^/opt/myapp/libexec/
		uid=myapp
		groups=^myappdev$

Allow the user to run a status command as any application installed under /opt. Do not allow the command to be run as root. With the listing rule, we tell the user they can run the command as any user with the sudo keyword ALL as there is no specific list of users. We decline to tell them they can't run the command as root.

# $Sudop(*): %|"# "%J echo "%<%j>"
# %s ^/opt/[^/]+/libexec/status -f \"%s\" -u \"$USER\" appstatus
# $Sudop(*): %j%^
appstatus	$f ;
		%f.path=^/opt/[^/]+/libexec/status
		!u=^root$
		uid=%u
		groups=^myappdev$

We can put multiple mappings in the same mapfile. Also note that we can create as many Sudop(*) rules with embedded mapfiles as we want. Each is tested until the fist mapping that matches is used.

# $Sudop(*): %|"# "%J echo "%<%j>"
# %s ^/opt/[^/]+/libexec/status$ -f \"%s\" -u \"$USER\" appstatus
# %s ^/opt/foo(bar|baz|qux)/scripts/restart$ -f \"%s\" foorestart
# $Sudop(*): %j%^
# $Sudop(-list): echo "ALL:/opt/*/libexec/status"
# $Sudop(-list): echo "ALL:/opt/foo*/scripts/restart"

appstatus	$f ;
		%f.path=^/opt/[^/]+/libexec/status
		!u=^root$
		uid=%u
		groups=^myappdev$

foorestart	$f ;
		%f.path=^/opt/foo(bar|baz|qux)/scripts/restart
		uid=%f
		groups=^foodev$

Option Support

Some options are ignored, some are acted upon.

Honored

-l
sudo - Run the command as a specific user, if allowed.
sudop - Run the command as a specific user, if allowed.
-l
sudo - List allowed commands for the user.
sudop - List all sudop mappings in the same format as sudo.
-b
sudo - Run the command in the background.
sudop - Run the command in the background.
-h
sudo - Print sudo usage.
sudop - Print sudo usage.
-V
sudo - Print sudo version number.
sudop - Print sudop and op version number.

Ignored

-H
sudo - Set the environment variable HOME the the target user's home directory instead of the callin user.
sudop - An op rule specififies how environment variables are to be handled, not the user.
-s
sudo - Run the shell in the environment variable SHELL, if specified, or else the default shell specified in the user's account.
sudop - An op rule specififies how environment variables are to be handled and whether the user's shell is to be used.
-P
sudo - Preserve the calling user's groups when setting the egid.
sudop - An op rule initializes the groups of the target user.
-v
sudo - Update authorization timestamp.
sudop - Not relevant. op doesn't use authorization timestamps.
-K
sudo - Reset a user's authorization timestamp.
sudop - Not relevant. op doesn't use authorization timestamps.
-K
sudo - Remove a user's authorization timestamp.
sudop - Not relevant. op doesn't use authorization timestamps.
-L
sudo - Describe possible options to Defaults in sudoers.
sudop - Not relevant.
-S
sudo - Read the password from standard in instead of the controlling tty.
sudop - Not relevant. An op rule does not require a password
-p
sudo - Change the password prompt.
sudop - Not relevant. An op rule does not require a password
-a
sudo - Select the type of authentication to use.
sudop - The op configuration determines authentication.
-c
sudo - Set resource limitations for the command by associating it with a login class.
sudop - The op rule specifies any resource limitations.
-r
sudo - SELinux security context role to use.
sudop - Not supported.
-r
sudo - SELinux security context type to use.
sudop - Not supported.

$Id: sudop.html,v 1.12 2012/03/29 21:18:41 ksb Exp $