18 December 2010

op, An Alternative To sudo

**** What is op? ****

For those unfamiliar with op, one can think of op as being similar
to sudo.  As taken from the man page (op.1):
        The op tool provides a flexible means for system administrators
        to  grant  trusted users access to certain root operations without
        having to give them full superuser privileges.  Different sets of
        users may access different operations, and the security-related
        aspects of environment of  each  operation can be carefully
        controlled.
(As an aside, op can be compiled and run on Solaris, FreeBSD, and Linux,
as well as other UNIX types.)  In order for a non-root user to make use
of op, an appropriate configuration entry may be set up within op.conf.
For local builds of op, this file will likely reside at:
        /usr/local/etc/op.conf
Of note, op's build configuration also provides the availability of
an includes directory, such as:
        /usr/local/etc/op.d/*.conf
All files within /usr/local/etc/op.d with an extension of '.conf' would
be read as part of the op config, subsequently to /usr/local/etc/op.conf,
and in lexical order.  All config files must be owned by u:g root:root
(or root:wheel) with only user permissions set, thus 600.

Assuming an appropriate configuration entry for a user, the user, once
logged in to the host, can access the resources provided by op as follows
(shell prompt: server [0]):
        server [0] /usr/local/bin/op someMnemonic [arg...]
**** Configuration ****

While the manpage for op is considered to be the definitive source of
documentation (and should be referenced as necessary), the following is
simply a brief overview of how to configure op.

In example 1, user 'jdoe' is setup to allow the use of Solaris command
'snoop'.  For illustrative purposes only, preceding each config file
line is the line number.

++++ Example 1 ++++

/usr/local/etc/op.conf entry:
        1  # A simple example config for op(1). See the man page
        2  # for more information or op.conf.complex for a complex
        3  # multi-user/multi-system configuration. examples under
        4  # /usr/local/share/doc/op-1.32
        5  #
        6  #
        7  #
        8  #
        9  #executed from command as 'op snoop -d port bge0 80'
        10 watchTraf /usr/sbin/snoop $1 $3 $2 $4 ;
        11      users=jdoe
        12      uid=root
        13      gid=root
        14      password
In the above configuration entry, we have the following:
        * lines 1-9 are all comments, beginning with a pound sign (#).

        * line 10 defines the op function with a mnemonic (watchTraf),
          sets the command to execute (/usr/sbin/snoop), and defines 
          expected arguments.   Of note, the arguments on line 10 are
          ordered $1, $3, $2, $4, thus when op processes the command
          line, it will switch args 2 and 3 during command execution.
          (The arguments were deliberately positioned in the config
          file this way for point of illustration, though may be in
          any logical and sane order.) Line 10 ends with a semicolon (;)
          to denote the beginning of any options acted upon by op.

        * line 11, first option, defins option 'users' with a value of
          'jdoe'.  Any user listed here is allowed to executed this
          op function.

        * line 12 sets option 'uid' with a value of 'root'.  This is
          the euid that the command called in this op function will be
          executed as.

        * line 13 sets option 'gid' to a value of 'root'.  This is
          the egid that the command called in this op function will be
          executed as.

        * line 14 sets option 'password'.  This option tells op to prompt
          and require the user's login password prior to execution of the
          command called within this op function.
Based on the above, user 'jdoe' has logged into 'server' and wants op
to perform function 'watchTraf', which is to run 'snoop' with parameters
$1 $3 $2 $4.  This will be run as root:root and will require user jdoe's
login password in order to be executed.  User 'jdoe' would execute and
see the following (shell prompt: jdoe@server [0]):
        jdoe@server [0] /usr/local/bin/op watchTraf -d port bge0 80
        Password:
Upon successfully entering the login password for jdoe, /usr/sbin/snoop,
based upon the config entry above, would be executed as:
        /usr/sbin/snoop -d bge0 port 80
Upon unsuccessfully entering the login password for jdoe, the following
error would appear:
        watchTraf: permission denied by op
Of note, a user can see what op functions have been defined for them by
executing  op with parameter '-l'.  The following would be what jdoe
would see:
        jdoe@server [0] /usr/local/bin/op -l
        watchTraf  /usr/sbin/snoop $1 $3 $2 $4
If the user has nothing configured, no output is generated.

With regards to example 1, supposing that more than one user needed
to execute this same command, we could define a user list, as seen in
example 2:

++++ Example 2 ++++
        1  # A simple example config for op(1). See the man page
        2  # for more information or op.conf.complex for a complex
        3  # multi-user/multi-system configuration. examples under
        4  # /usr/local/share/doc/op-1.32
        5  #
        6  # user lists
        7  SNOOP_USERS=(jdoe|fsinatra)
        8  #
        9  #executed from command as 'op snoop -d port bge0 80'
        10 watchTraf /usr/sbin/snoop $1 $3 $2 $4 ;
        11      users=SNOOP_USERS 
        12      uid=root
        13      gid=root
        14      password
        15      help="watchTraf -d port bge0 80"
For the most part, example 1 and 2 have remained the same with the
exception of 4 lines.  For brevity, only those 4 will be discussed:
        * line 6 is still a comment 

        * line 7 now defines a variable of 'SNOOP_USERS' with a value of
          '(jdoe|fsinatra)', thus a value of 'jdoe' or 'fsinatra'.

        * line 11 now sets 'users' to a list made up of variable
          'SNOOP_USERS', which substitutes in values of 'jdoe' or 'fsinatra'

        * line 15 sets option 'help' to be value 'watchTraf -d port
          bge0 80'.  This option affects the output to executing 'op -l',
          substituting the output of the mnemonic with the value of 'help'.
With the new updates to the config, either 'jdoe' or 'fsinatra' could
log into the host and execute op function 'watchTraf', assuming they
have supplied their respective login passwords when prompted.

For a third example, essentially re-using example 1, a shell will be
spawned for user 'jdoe', allowing access as if 'jdoe' su'd to user
'fsinatra'.  For the example, assume 'jdoe' is a member of group
anons(501) and fsinatra is a member of group rpack(647).

++++ Example 3 ++++
        1  # A simple example config for op(1). See the man page
        2  # for more information or op.conf.complex for a complex
        3  # multi-user/multi-system configuration. examples under
        4  # /usr/local/share/doc/op-1.32
        5  #
        6  #
        7  #
        8  #
        9  # spawn a shell as euid:egid fsinatra:rpack
        10 shell MAGIC_SHELL ;
        11      users=jdoe
        12      uid=fsinatra
        13      gid=647
        14      password
In the above, lines 1-9 are all comments.
        * line 10, define function mnemonic 'shell', set the command to
          'MAGIC_SHELL', denote beginning of op options (;).
          'MAGIC_SHELL' is a predefined, internal variable to op
          which uses the $SHELL environment variable of the euid being
          executed as.  Thus if fsinatra's $SHELL is set to /bin/ksh,
          upon successful execution of the op function 'shell', jdoe
          will have a shell of /bin/ksh running as fsinatra:rpack.

        * line 12, option 'uid' is value 'fsinatra'.

        * line 13, option 'gid' is value '647'.  Of note, both 'uid'
          and 'gid' can be either a numeric user / group id or a login /
          group name.
Example 3 effectively allows user 'jdoe' to execute commands with any
privileges available to user 'fsinatra', group 'rpack', from within korn
shell, as /bin/ksh has been specified by fsinatra's $SHELL environment
variable.  A test run of the function set in example 3 could be seen below:
        jdoe@server [0] /usr/bin/id -a
        uid=783(jdoe) gid=501(anons) groups=501(anons)
        jdoe@server [0] /usr/bin/who am i
        jdoe        pts/15        Jan 1 20:30
        jdoe@server [0] /usr/ucb/whoami
        jdoe
        jdoe@server [0] /usr/local/bin/op shell
        Password:
        fsinatra@server [0] /usr/bin/id -a
        uid=432(fsinatra) gid=647(rpack) groups=647(rpack)
        fsinatra@server [0] /usr/bin/who am i
        jdoe        pts/15        Jan 1 20:30
        fsinatra@server [0] /usr/ucb/whoami
        fsinatra
        fsinatra@server [0] exit
        jdoe@server [0]
The above illustrates that the effect of 'MAGIC_SHELL' is quite similar to
if user 'jdoe' has used '/usr/bin/su -' to switch user to user 'fsinatra'.
The euid and egid are both updated to fsinatra:rpack, fsinatra's shell
is spawned off, commands executed within are executed as user fsinatra /
group rpack... the only noticeable difference is that rather than entering
the login password for 'fsinatra', user 'jdoe' can simply user their own.

Regarding the config file, indentation seen within all provided examples
is required.  op perceives any line beginning with an alphanumeric
character as the beginning of either a function or a variable.  (Line
numbers have only been included for the point of discussion and will
not otherwise exist.)  Also of note, all op variables are alphanumeric
(underscores are allowed), and must be UPPER_CASE.  To allow the user to
retain their own environment settings during op runtime, 'environment'
can be passed as an optional config parameter.  Additionally, environment
variables can be passed by identifying them as $VAR=VARIABLE.  As a final
config modification, we can set limits on a user's access, specifying
where they can log in from and an expiration of their access:
        1 MYPROG_USERS=(jdoe|fsinatra)
        2 myProg /usr/local/bin/prodApp -h $2 -u $1 -p ;
        3       users=MYPROG_USERS,jsmith@devbox.example.com/201102151730
        4       uid=proguser
        5       gid=proggroup
        6       environment
        7       password
        8       help="myProg USERNAME HOST"
        9       $MYPROG_LOG_DIR=/bitbucket/logs
On line 3 in the above example, access is given to users within the
'MYPROG_USERS' list defined on line 1.  Also, 'jsmith' is given
access only from 'devbox.example.com' and that access expires on 15
Feb 2011 at 5:30 pm localtime.  (The format for user access expiration
is YYYYMMDD[HH[MM]].)  On line 6, we allow users to retain their own
environments during op runtime, however, on line 9 we deliberately set /
override the MYPROG_LOG_DIR environment variable.

**** Further information ****

As this post is only meant to give a brief overview of common
configuration / usage of op, it is not all encompassing.
For further information, please see the manpage (op.1),
/usr/local/etc/op.conf, /usr/local/share/doc/op-1.32/op.conf.complex,
and http://swapoff.org/wiki/op.  op will likely not be installed on
your system by default, though can be downloaded for compiling from
http://swapoff.org/wiki/op#Download.  op was originally written by Tom
Christiansen and Dave Koblas and is currently maintained by Alec Thomas.
At the time of this writing, op is at version 1.32.