sudo
, op
,
or other escalation tools, and some shell scripting experience would
help a lot.
op
access.cf
. This file contains the
base-rules and the system-wide defaults, so it changes very little change over
time. After we create that one, we'll build other configuration files to
represent the current escalation rules.
The number of additional files and their contents will change over time. Rule-set files may be added and deleted as applications shift over time. The rule-sets installed on any host may be different based on local policy.
The name of the file that a given rule comes from is not relevant to your Customer's work-flow. Some configuration error messages include the name of the rule-set and the line number to help the superuser find and repair configuration mistakes, but the Customer should never see this type of error.
op
to find out where it expects the configuration
directory. I highlighted an example below:
Create that directory path, if it doesn't exist:$ op -V op: $Id: op.m,v 2.digits op: access file `/etc/op.d/access.cf' op: using regex op: multiple configuration files accepted op: in-line script and $s accepted op: with pam support, default application "op"
Create an empty access file with the proper modes:$ su # mkdir -p /etc/op.d # chmod 0700 # /etc/op.d
# touch /etc/op.d/access.cf # chmod 0400 /etc/op.d/access.cf
To edit access.cf
you should use the
really cool program vinst
(man page) as the superuser.
This edits a copy of the file then uses install
to
update file and keep a backup copy in an OLD
directory. But if you don't want to do that just force-write with
your favorite editor.
Once you get a working access file you should move the source to your revision control structure.
op
expects. It also expects a
PAM
configuration, if complied with
Pluggable Authentication Module support.
So if you run a sanity check on the empty file you should see:
# op -S op: PAM policy: stat: /etc/pam.d/op: No such file or directory op: no rules configured, are you sure?
To fix this first configure a PAM policy for op
.
In this case we'll just link to sudo
's policy,
if you don't have one of those you can find it with a search engine, or
use the su
policy in a pinch.
Since# ls -1 /etc/pam.d | egrep '^(su|sudo|op)$' su sudo # ln -s sudo /etc/pam.d/op
PAM
configurations are not really
standard I can't list the exact one you need here. You do want to
include a session
line if you want resource
limits to work for mortal logins.
DEFAULT
stanza that
sets the uid
to a nonexistent login, such as
buddy
(that implies you have a site policy
against creating a "buddy" account). This prevents and rule without an
explicit uid
set from ever running, or even
passing sanity (under -S
).
That would make a file like this:
Add your own revision control markup where I put $Id: ... so you can keep track of the current revision.# $Id: ... DEFAULT uid=buddy # deny rules without a uid set # no rules yet
Next replace the comment ("no rules yet") with a harmless rule:
This rules allows anyone to escalate to them self. They get a plain shell with a prompt of "op$ ". Try the rule as a mortal. Test themyself MAGIC_SHELL ; users=^.*$ uid=. gid=. initgroups=. $PATH $TERMCAP $TERM $SSH_AUTH_SOCK $SSH_TTY $SHELL=/bin/sh $LOGNAME=$l USER=$l $PS1=op$$$\s
id
command and
ssh-add -l
; they should both work the same as
the parent shell.
$ op myself op$ id uid=1117(ksb) gid=0(wheel) groups=0(wheel),5(operator),20(staff),810(source) op$ ssh-add -l 1024 a2:20:5f:14:5f... op$ exit $ id uid=1117(ksb) gid=0(wheel) groups=0(wheel),5(operator),20(staff),810(source)
If that worked then the sanity check should confirm that you have a
sane op
configuration, with no output.
$ op -S $
syslog
configuration to capture valuable informationOp
logs every escalation attempt.
It opens the log stream with LOG_AUTH
under
the name given as the default PAM
module.
It logs any configuration error at
priority LOG_ERR
.
Escalation attempts (failed or succeeded) are logged at priority
notice
unless the rule is optioned as
nolog
, which drops the priority to
info
.
Normally a configuration to send attempts to
/var/log/auth.log
is enough:
Checkauth.info;authpriv.info /var/log/auth.log
syslog.conf
to see where the
messages should flow, and make sure that your test escalation is in
there. Later you can use a perl
script to
generate a summary report of all the escalations that took place on
each host for management.
Reports based on data from that log help you close
the loop on abuse, unused rules, and configuration errors. So write
a note now to look into that after you deploy op
.
My version actually gathers all the logs together to form a picture of
the whole site's usage.
users
, groups
,
and netgroups
$*
, $1
,
$2
...,
$#
,
%f
, %d
,
%u
, %g
,
%_
,!*
, !1
,
!2
...,
!#
, !f
and the like
pam
, password
,
helment
and jacket
uid
,
euid
,
gid
,
egid
,
initgroups
,
chroot
,
fib
and mac
$
VAR
$
VAR
=value,
environment
,
session
,
cleanup
,
dir
,
stdin
,
stdout
,
stderr
,
basename
,
daemon
,
umask
and nolog
By following that simple pattern I help auditors understand
the rule-base. Every escalation is explained in the same order,
so parallel rules are easy to factor into DEFAULT
stanzas and grouped into files. This makes the average rule much
shorter and easier to explain to someone who only reads the rule-base
4 times a year (or less).
This actually brings up the point that an auditor actually can
read the whole rule-base and have a pretty clear understanding of the parts.
This is really not the case with sudo
, as macros
later in the file can have impact on rules above. There is always some
doubt as to exactly what any rule (with a macro in it) allows. Since
nearly any word in any line may be a macro, that leaves a lot of doubt.
By following a few layout rules one can be sure that op
is not grabbing random values from some other place in another file.
The only places we can take defaults from are either:
DEFALUT
rule in this file, if any
DEFALUT
rule in
access.cf
When it is more clear to order the options in a given rule in a different order then do that: the clarity of the rule is more important than any style preference.
With all that said, we should look at an example (from a real rule-base):
In English I would read that as:# Allow DBA support to have an easy hook to bring oracle (back) up oracle /opt/oracle/oracle_startup ; groups=^root$,^oracle$,^dba$,^appdba$ $1=^startup$ uid=oracle initgroups=oracle $PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin
Anyone in either group root, oracle or a dba group can access "oracle startup". This executes a start the oracle_startup script from/opt/oracle
, as oracle with all of oracle's groups. This always runs with a fixedPATH
set.
access.cf
access.cf
.
However, older versions of op
require
at least 1 rule in there to keep -S
from
complaining every time we run sanity. So I have an old "help" rule that
was from the time before -l
which I include in
that file (since my Customers still use it). I also put any local rules for
operators or admins that should be on every host at the site.
Here is the old help rule. While not as pretty as I'd like it, it does get the job done.
replace the "myself" rule with something like "help" or some other rule you need every place. Update the access file until it has the basic rules you need for operations, but nothing for applications. Make sure at each step that sanity (help { cd $0 || exit 65 echo "op mnemonic UNIX command" echo "=========== ============" sed -n -e "s/#.*//" \ -e "/^DEFAULT/d" \ -e "s/^\\([^ ][^ ]*\\)[ ][ ]*\\(.*\\)[&].*/\\1#\\2/p" \ -e "/^\\([^ ][^ ]*\\)[ ][ ]*{.*/{" \ -e "s//\\1#{script}/" \ -e "h" \ -e ": loop" \ -e "N" \ -e "s/.*\\n[ ]*//" \ -e "/^[^}]/b loop" \ -e H \ -e x \ -e "s/\\n}[ ]*\\(.*\)[;&]/ \\1/p" \ -e "}" *.cf | sort | pr -t -e"#15" } $C ; users=^.*$ uid=root
op -S
)
outputs only message you'll accept by policy.
Allow access to the rules by group membership and allow a few escalations for different project-related groups. That way you have a `least privilege' structure and a way to enforce it. If you don't have a good login accounting structure, make one so changing group membership is easy.
pkill
), or
chown files under /tmp
or
/var/tmp
.
I never give operators a root shell via op
, they
have physical access to the console and a way to get a superuser shell from
there.
rsync
from a trusted repository into
the target /opt/
directory to prime
any new system for the initial load of the package, or add the
RPM for the package without a general root shell. Allow them
to truncate or roll log files that may fill disks.
You might include them in any of the previous rule lists, since
they can just su
to do it anyway, and
we'd rather log the access.
Now it is just a matter of converting or making rules to allow exactly what you need. No more "sudo /bin/bash" to do everything with the black pointy hat with stars, shells, and cats on it. No more late calls to turn trace on for a developer. No more painful audit misunderstandings.
Keep these files under revision control so you can show the auditor the differences from the last audit first. Then have them look over the whole rule-base. This actually speeds any audit, a lot.
m4
to
customize the policy for each target host's OS type and
version, the "class" of server, the "level" of test, and the list
of "services" it should be running. This allows some additional
rules to developers at the lower levels of test (stop/start) that
they are not allowed in production. This also allows the whole-sale
deletion of test rules in production.
Do this by adding-to or changing the name of the group that allows
access the extra rules. The that makes the revision tag on
the file the same, but the content slightly different when installed.
(In fact no hosts gets the m4
markup installed
because op
doesn't know how to read the
marked-up file.)
For example this m4
quotes all the
rule, except the PRIVGROUP
macro, which
is driven to be either the empty string (in production) or a list
of REs that match the puma developers login groups (in development).
`# $Id: .. puma-web /opt/syzygy/puma/ctl puma-web $* ; groups=^ppm$,^root$,^pumadev$,^cfgmgt$'PRIVGROUP` uid=puma 'dnl
There is a really handy set of tools to install marked-up files across
many hosts called msrc
,
hxmd
and xapply
. These
are very much like GNU parallel, but they are specialized to running
files through m4
for each target host.
That makes them perfect for integrations with my version of
op
which doesn't use the
default m4
quotes (`
and
'
) for anything.
Op
's expander has 2 markups to
allow the insertion of the default m4
quotes into variables: $\o
for an open
quote (`
) and $\q
for
a close quote ('
). I've never
needed them in an real rule-base. If you want m4
quotes in an in-line script you'll have to
chanequote
in the source file. Always restore
the default quotes before the end of the file, for the case where that
file is include
'd by another.
Op
allows access to at least 3 command-line
options. There are forced into the usage message for any mnemonic
rule that uses them.
-g
Any rule that uses the markup $g
or
%g
will require a -g
option on the command-line.
-u
Any rule that uses the markup $u
or
%u
will require a -u
option on the command-line.
Here is an example rule which uses -u
to
allow any member of group "source" to chown a subdirectory of
3 different hierarchies to any login who is also in group source.
level2s-chown /usr/sbin/chown -R $u:source $* ; groups=^source$ uid=root %u@g=^source$ $*=^/tmp/.*-[0-9],^/usr/msrc/,^/usr/src/ !*=/\.\./
The list commands option displays that escalation as:
op -u uid level2s-chown [args]
-f
-f
option forces a
filename (or directory name) into the op
command.
That file may be limited to a known directory, a particular mode or
type, or even by the exact file size or timestamp, see the
list in the main page.
A file might be used to start a trace log, or specify a directory to create, update, or remove. There is no set purpose for the option because it is intended to be purposed by the rule's intent.
Any rule that uses the markup $f
,
%f
, $d
or %d
will require a -f
option on the command-line.
Here is an example rule which uses -u
and
-f
:
java-generated $f $* ; users=^monitor$,^root$ uid=%u egid=%u gid=%u initgroups=%u %u@g=^wlsapp$ %f.path=^/var/syzygy/bjoss/generated/[^/]+/[^/]+/[^/]+$ %f.type=^-..x $PATH $LD_LIBRARY_PATH
The help options lists that rule as:
op -u uid -f file java-generated [args]
MAC
label under -m
op
can set a
process label on the escalated process. And a command-line option can
be part of that label.
The mac
configuration option is different than
all the other options: it's right-hand-side is expanded like an
environment variable.
All the dollar-sign markup (most notably $m
and
$M
) are available to form the new process
label. Limits my be placed on the -m
option's
specification via %m
(must match) and
!m
(must never match) limits.
op
allows a
run-time value to be substituted in place of a single percent markup.
These markups cannot be part of a longer string, and cannot be
sliced or diced when substituted, but they are polymorphic in that
they provide a context specific form of the requested data.
These "percent markups" are bound to bits of the command line or
the attributes of the parent process that op
inherited. They may be specified in place of a literal string under
some keywords to substitute run-time values where a fixed string
would normally be required.
%f
-f
file
. This path is always made into
an absolute path. The macro to expand the path as a command
parameter is $f
, or $F
as an fd. Many limits may
be placed on the file with keywords that start with "!f" or "%f".
%d
file
specified
under -f
. Available as $d
in the expander. Also available as an fd with $D
,
if you need to fchdir
(2) to it later.
%u
login
specified under
-u
. Limited by the keywords
%u
and !u
.
Available as the $u
macro, or
by uid as $U
.
%g
group
specified under
-g
. Limited by the keywords
%g
and !g
.
Available as the $g
macro, or
by gid as $G
.
%l
op
.
Limited by users
,
groups
, and
netgroups
.
Available as the $l
macro, or
by uid as $L
.
%i
op
can make as to which
login the initgroups
keyword should
default to, or the value specified to initgroups
.
Usually any uid
,
euid
.
Available via the $i
and
$I
macros.
file
is reformatted into
an absolute path for the escalated process. This is usually safer than
a relative path, but can be problematic under a
chroot
, use $X
to pass the new root down so the escalated process can remove it, or
use $F
to pass an open file descriptor on
to the escalated process.
rule /usr/local/libexec/op/myScript -r $X $f ; %f.path=^/usr/host/lv426/home/local/[^/.]+$ %f.perms=^dr.x chroot=/usr/host/lv426 ...
In the table below the dir
(expanded from
%d
) is the string that
would be output by applying dirname
to the
absolute path produced for file
. In the
example above it should always be "/usr/host/lv426/home/local".
The term login
refers to the login name
specified by uid
, if that were numeric
then match returned by getpwuid
(3).
keyword | empty | %f | %d | %u | %g | %l or . | default |
---|---|---|---|---|---|---|---|
dir | / | file | dir | login 'shome dir | login 'shome dir | none, under chroot / | |
chroot | none | file | dir | login 'shome dir | perp 'shome dir | none | |
egid | %l | file 's gid | dir 's gid | login 'sprimary group | group | real gid | op 's effectivegid |
gid list | %l | file 's gid | dir 's gid | login 'sprimary group | group | original list | egid |
uid | %l | file 's uid | dir 's uid | login 'sprimary group | perp | op 's effective uid | |
euid | %l | file 's uid | dir 's uid | login 'sprimary group | perp | uid | |
initgroups | uid 'sgroups | file 's owner'sgroups | dir 's owner'sgroups | login | original list | uid 's groups | |
session † | none | file 's owner | dir 's owner | login | perp | none | |
cleanup † | none | file 's owner | dir 's owner | login | perp | none | |
password list | none | file 's owner'spassword | dir 's owner'spassword | login 's password | perp 'spassword | none | |
pam | none | . sets-V default | none | ||||
stdout stdin stderr | /dev/null | file | original I/O |
session
and
cleanup
the markup%i
requests the same
specification as initgroups
.
In general is it poor form to use a percent keyword for two
purposes in a single escalation rule. For example using
%f
as both a chroot
and a chdir
might not be what you meant
(since the directory path is already relative to the new root).
Op
's sanity checker takes some care to
complain about semantically questionable combinations of usages.
There is no way to markup an escalation rule to tell
op
that it is semantically sane: if it
complains about something you think is always OK, submit a bug report.
op
package you may
have used some of Alec's extensions.
If so here are some ideas for converting to my branch.
fowners
and fusers
keywords should be replaced with %_.owners
and
%_.users
. There are a lot more things to
check under %_
and !_
.
By using any of the attr
specifications.
# a test rule only any $1 ; %_.perms=^-r.xr-xr-x %_.path=^/bin/ls$ !_.login=^nobody$
nolog
specification still calls
syslog
, but with the level set to
INFO
rather than NOTICE
.
So you can tune /etc/syslog.conf
to drop the
nolog
into a different file, or the bit-bucket.
The securid
should be replaced with a local
pam
policy.
The xauth
specification should be mapped
to jacket=xdisplay
. The
xdisplay
jacket is available in the
"op-jackets" package.
var=value
).
I don't name individual accounts often in rules, because
it has been my experience with both op
and
sudo
that list of logins named in the rule-base
get stale quickly.
So I don't list people by login name the rule-base. I actually use
group membership in almost every case and where I don't I use
netgroups
, both of which I manage with
a better accounting structure.
For host specific changes I use m4
to markup rules,
then process the marked-up file before I install it on every host. I've
coded a whole stack of tools to make this easy, so I don't need a macro
processor inside every program that reads a configuration file.
See msrc
(8) for a brief description, and
the HTML documents in for that program and hxmd
(8).
For audit purposes it is much better to have all the context of an
escalation rule visible on a terminal window. Using variables makes
that very hard. Using DEFAULT
stanzas
from another file is also poor form.
m4
to pre-process files.
Alec uses forms like:
# Switch inetd on and off, shows complex # shell example and 'string' arguments. $1 # in this example is expanded by op inetd /bin/sh -c ' case $1 in on) /usr/sbin/inetd -s ;; off) /usr/bin/pkill inetd ;; esac ' ; users=ACCESS_LIST $1=on|off
To make it look as much like the original as we can use an in-line script. In version 2 this is quoted with a curly brace markup:
Which outputs the usage line:# Switch inetd on or off using an in-line script inetd { case "$1" in on) /usr/sbin/inetd -s ;; off) /usr/bin/pkill inetd ;; esac } $1 ; groups=^wheel$ uid=0 $1=^on$,^off$
This assumes that the same group of people are able to turnop inetd on|off
inetd
off and on.
In this version of op
, since matching the positional
parameters also selects the rule, we could also code that as:
Which outputs 2 usage lines:# Switch inetd on or off, using separate rules inetd /usr/sbin/inetd -s ; $1=^on$ groups=^operator$,^wheel$ uid=0 inetd /usr/bin/pkill -U root inetd ; $1=^off$ groups=^wheel$ uid=0
And also has the feature that more people might be able to startop inetd on op inetd off
inetd
than can stop it.
An important difference is the explicit uid=0
specification, which keeps the -S
sanity checker
from complaining about the target uid defaulting to the superuser.
help
specification which
masks the -l
output for a rule with some fixed
text. The version doesn't have that. The list output is supposed to
show the command-line usage for each rule, hiding it would make it
very hard to access a rule. Rather we try to add some semantic clues to
the output based in some guess as to the purpose of
common REs.
Op
guesses that a string that matches all digits is
a "number". If that matches 3 dots and the rest digits we think it is
an "IP", if it has a slash and more digits on the end it is a "CIDR".
If the strings is anchored left and right and has no character ranges we
list the string as-is (viz. $1=^start$ lists "start").
Disjunctions list all the alternatives (with either "|" or "," notation).
The other use of quotes would be to quote white-space.
Unless you were to need an RE that must match
white-space you never need spaces in my version, and you can
use [:space:]
to match a space.
In the expansion of a parameter you must encode spaces as
$\s
, which is long, but I've almost never
needed to do it.
Quotes are never needed to escape a double or single quote, backslash,
comma, or dollar sign. To quote a dollar sign use
$$
, you don't need to quote
"
or '
.
To quote a comma in an RE use ,,
. This
is safe because an empty RE is never useful to op
.
To quote a literal semicolon (ampersand) in a command use a set of empty
expansions around them:
$|;$|
($|&$|
).
$_
possible:
$.
In the context of an environment variable the word break becomes a space character. So thehi echo$.hello$.world ; ... $NAME=KS$.Braunsdorf
NAME
environment
variable is assigned "KS Braunsdorf".
These expansion are used (with a bit of hand waving) to build
$_
(the name of the escalated program) before
op
expands the full specification. I'm not sure
if they are useful in many other contexts.
sudo
options under op
sudo
because
(as far as I can tell) it is largely geared for sysadmins to do
tasks mostly as root. My customers need to start and stop applications
as a pseudo-login (not as the superuser).
My customers want a mnemonic that is easy to type and does the right thing.
My customers don't want to ever type options like -u
.
They also require that I have an audit of the rule-base (for SOX 404
compliance).
I tried to make this branch of op
meet those
requirements. But I realize that some people like the options
sudo
supports, so here are some clues to
help you convert some of your rules to my way of thinking.
always_set_home
secure_path
DEFALUT
stanza (or on a given escalation rule):
DEFAULT $HOME=$h $PATH=the path you think is secure
env_reset
setenv
environment
,
or name it as an option without a value.
rule ... ; environment=^HOME$,^NAME$,^ORGANIZATION$ $TERM $TERMCAP
To pass all of the customer's environment specify
environment
without a value.
authenticate
pam
specification to tune any
authentication rules, that is what pam
is all
about.
closefrom_override
helmet
or jacket
may force a command to close all file descriptors above a specific
value, for example to close all above 3: &3
.
In addition it may specify a redirection of stdin, stdout, or
stderr (for example &0=/etc/motd
).
These commands
are output to the stdout
of the helmet process before
it releases the escalated process.
env_editor
editor
op
to escalate an
editor session, use vinst -U
to do that.
fqdn
op
to parse hostnames
in the access list. Use netgroups
to
escalate based on a host name, or markup your rule-base with
m4
and pre-process it before you install
the rule-set.
ignore_dot
helmet
.
ignore_local_sudoers
op
to distill a rule-base
from LDAP.
Build a program to update the local rule-base at intervals from any
source you like.
insults
helmet
, or add a pam
module to insult your Customers, if you feel a pressing need to do so.
log_host
log_year
loglinelen
syslog_badpri
syslog_goodpri
logfile
syslog
syslog
format of an escalation doesn't change,
because they are almost always machine parsed for audit purposes. We always
use syslog
to log escalations for the same
reason, and always at the same levels.
long_otp_prompt
passprompt_override
pwfeedback
passwd_tries
passwd_timeout
passprompt
askpass
pam
stack, or modify a module and
push the change back up-stream.
mail_always
mail_badpass
mail_no_host
mail_no_perms
mail_no_user
badpass_message
mailsub
mailerflags
mailerpath
mailfrom
mailto
noexec
LD_PRELOAD
set in the rules that need it.
path_info
op
only lists the mnemonic for the failed
escalation there is no radiation of path information.
preserve_groups
initgroups=
login
to
set the groups by login
, or
gid
=group1
,group2
... to set an explicit list. By default
the groups in the escalated process are those of
the target uid
(or euid
).
requiretty
helmet
to do with is as simple as:
Note that this checks#!/bin/sh test -t 2 && exit 0 echo "Must be run from a tty" echo 77 # EX_NOPERM
stderr
rather than
stdin
since the input to the jacket is
always the null device.
root_sudo
rootpw
runaspw
password=
whomever for the rule.
set_home
set_logname
$HOME=$H $LOGNAME=$l $USER=$l
shell_noargs
op
. If
"op su" is too long to type, then build a script named "h", since that
key is in the home row. (Yes, I'm being sarcastic.)
fast_glob
Op
doesn't glob. See my glob
shell tool to process shell meta patterns.
stay_setuid
uid
, euid
,
gid
, and/or egid
you need, per rule.
targetpw
password=%u
to ask for
-u
's password, or
Set password=%f
to ask for the owner of
-f
's file's password. Really ask for
any credentials you really want with pam
.
tty_tickets
timestamp_timeout
timestampdir
timestampowner
verifypw
stampctl
and
stamp
usage. This is way more powerful than
a simple timestamp.
umask_override
umask=
octal. There is no
relative umask feature in op
.
visiblepw
pam
's password interaction.
closefrom
Where "3" is the lowest file descriptor to close.#!/bin/sh echo "&3"
umask
umask
option.
There is not a function to create a variable umask in op
.
noexec_file
runas_default
-u
then one must be
presented. Allow the distinct version with a different mnemonic.
sudoers_locale
Op
doesn't use any locale conversions when
parsing the configuration file.
env_file
#!/bin/sh echo '$MY_VAR1=this value with spaces in it' echo "\$MY_VAR2=$PATH" echo 0
exempt_group
password
or
pam
option do not ask for any authentication.
lecture
lecture_file
jacket
that does this,
if you need it.
listpw
op
rule to allow others to see parts of
the rule-base.
env_check
env_delete
env_keep
helmet
that filters the environment is quite
possible, put such rules in there. Here is a prototype for the check
function:
Hook it into a rule with#!/bin/sh # Remove variables with percent or slash in them from the list in $env_check for name in `echo $env_check | tr ',' ' '`; do if eval expr _\"\${$name}\" : _\'.\*\[/%\].\*\' >/dev/null ; then echo "-$name" fi done echo "-env_check" echo 0
rule $NAME $PATH ... $env_check=NAME,PATH,... jacket=/usr/local/libexec/jacket/env_check
$Id: config.html,v 2.21 2012/10/06 19:39:56 ksb Exp $