Op
uses pam
to
authenticate access requests, and that works most of the time.
It also sanity checks parameters, all the better. But sometimes
you need another person or process to
authorize command the makes a meaningful change to the instance.
op
rules
and in-line scripts, to understand why one might need a helmet.
A clear understanding of the UNIX™ process model would be really
helpful, because we are using wait
(2) and
pipe
(2) here clever in ways.
Op
does a pretty good job of limiting access
to privilege escalation rules in the configuration file to
authentic logins (the session is owned by the correct login, in an
allowed group or netgroup, and they might know the right password).
But sometimes a rule should only be accessed based on some criteria that
op
doesn't understand. For example, the time
of day, or the lack of a key process, or a missing, full, or read-only
filesystem. Or a permission credential provide by another user.
Many other examples are be ridiculously site specific, so there is no way anyone could code for all of them.
Even more to the point, all of those possible rules would be impossible to
express in the limits of the declarative op
configuration file's language. That format is otherwise adequate to
describe escalation rules: so I don't want to change it.
But most of these limits are easy to check in a shell or perl script, or
a C program.
Thus op
out-sources the work to a co-process that
should be coded in the most appropriate style for the task at hand.
That leaves a simple set of data-flow tasks:
op
get context information to the helmet?
op
?
Each of those tasks is described below.
helmet
[-C
config
] [-f
file
] [-g
group
] [-j
job
] [-m
mac
] [-R
root
] [-u
user
]mnemonic
program
euid
:egid
cred_type
:cred
helmet
-C
config
mk
,
or you could only allow escalation from that file in a time-box.
-f
file
-f
,
which has already passed any %f
or
!f
checks.
Since op
has a fairly complete set of checks for
files, I can only guess why you'd need this. I suppose it could be
a local domain socket that you need to chat with, heck I'd do that.
-g
group
-g
,
which has already passed any %g
or
!g
checks.
I don't know what you might check with a group, but I'm sure you'll
think of something.
-j
job
-j
,
which is only available in op
version 3.
-m
mac
-m
.
-R
root
chroot
derived from
the formula given (which might include $f
or $d
, for example).
Since this might tell you where to mount
a file system, or install a wrapper socket, it might be the most useful
data passed to a helmet.
-u
user
-u
,
which has already passed any %u
or
!u
checks. I have heard of sites that
use a RADIUS service for passwords, but I think I'd use
the pam
module before I'd use
a perl
script.
mnemonic
mk
, for example.
program
stat
(2) it.
With all the checks op
does under
%_
and !_
I
don't really know what else you might be looking at.
euid
:egid
cred_type
:cred
In all of these cases you could make some other local check on the
attribute that allows the escalation, or check the other
credential (viz. check user if group allowed) since op
uses "or" logic to allow access, and you might prefer "and" logic.
All of the parameters listed above are provided to give a complete
description of the context of the escalation. Other information is
always passed in the environment. This allows every helmet to have
a separate set of specifications that do not overlap any other (so
multiple helmets could be chained together (see the helmet named
coat
, which does just that).
The additional environment variables usually start with the name of the
helmet (or jacket) that reads them and an
under-bar (_
). As they are consumed the
helmet process requests that op
delete them from
the escalated environment. This reduces the radiation of information to
black-hat crackers who are trying to suborn an escalation rule.
These bits are still in the process table (for the life of the helmet),
but there is little to be done about that. Should we use a pipe?
That forces all the helmets read and parse a stream? I think not.
For example, the stamp
helmet reads
STAMP_SPEC
for the specification of the
required authorization.
jacket
specificationjacket
-P
pid
[-C
config
]...same options...mnemonic
program
euid
:egid
cred_type
:cred
-P
pid
The jacket program runs after the helmet, it is expected to start
the escalated process (which is waiting for the jacket to
close
stdout
).
Once the process is running the jacket may
take any actions required to manage the escalation.
When the escalated process exit
's the
jacket should wait
for the process and
produce an apropos exit
code based on
the status
returned from the escalation.
A jacket has only 2 more clues than a helmet: it knows the process-id of
the proposed escalation (which has not changed effective uid or gid yet)
and it can see the environment proposed by the helmet. These are
not (generally) useful to make any new authorization decisions, but
it is possible to use a jacket to reject or modify the access, see
below.
Many jackets work as helmets without the -P
option.
stdout
. After which
the jacket continues to run in parallel with
the escalated process. But how does one do this in the common
scripting languages?
In perl
one may:
Inopen STDOUT, ">/dev/null";
sh
one may:
In C one would:exec 1>&-
orclose(1);
fclose(stdout);
If the last suggested exit code is 0 (or none were suggested),
op
starts the process under
the pid
specified under -P
.
This process is a child of the jacket process, so it is the
jacket's task to wait for the child process and interpret the
success/failure of the escalated child.
In addition the jacket may provide services to the process, may
clean-up any pre-escalation setup, or may kill
the escalated process after some time limit or event.
op
gained when
executed, or those assumed by any sentinel configuration.
If you need to drop effective as the client process did, then consult the
euid
:egid
specified on the command-line.
In the rest of the text assume that anything a helmet can do a
jacket can do just the same. The only special feature the jacket has
is the ability to start the escalation, and run as a co-process for
that escalated process. The last action of the jacket should be to
wait
for the escalated process to
return an exit
code based on the
status
returned.
Every output to stdout
by a helmet is
read by op
for single-line commands.
Each command is processed in the order read.
#
comment
op
is
compiled for debugging the comments from helmets are output to
stderr
to help debug the configuration
and processing of the helmet (or jacket).
-
VAR
$
VAR
=
value
$
VAR
~
prefix
~hide_
command. This allows the helmet
to have a different forced PATH
from the
escalated process, while both are specified in the configuration of
the rule.
While this is a little strange, the operation makes some specifications
much more clear, and easier to understand.
&
0redirection
&
1redirection
&
2redirection
redirection
, which has one of
the following forms (mocking the shell):
file
<
file
>
file
<>
file
>>
file
socket
&
fd
fd
.
The typical value of fd
is 3.
This prevents escalated processes from finding unexpected open files.
Because almost no program expects non-standard open descriptors
there is no way (in a helmet) to redirect other fds.
exit-code
(a decimal number)
"
cmd
"
op
might confuse in the authorization stream, these characters should be
replaced as follows:
"
with \d
(for double quote)
`
with \o
(for open m4
quote)
'
with \q
(for quotes closed, in m4
)
\n
\t
Other single letter C escapes (e.g. \r
)
may be optionally replaced, as op
doesn't
treat these characters as special.
Note that octal escapes are not supported.
This does limit the use of non-Roman languages or 8-bit character in
the text of environment variables. This limitation may be removed
in the 3.1 release of op
.
Also note that the terminating newline must follow the closing quote, trailing white-space is not allowed.
exit
fails the escalation.
If the program is a jacket specification, them the code represents the
success of the escalated process.
helmet
and
jacket
specify the path to
the trusted program that acts on behalf of the
superuser (or sentinel user), so we generally expect an
absolute path to be specified.
Later we'll see that there is a way to get more than one helmet and jacket, but that hinders some of the features of the native structure.
The examples in this section do not provide any authorization services. These are designed to be easy to read and understand, not to provide much additional functionality. Which is not to say they are useless, just not super useful for authorization.
timebox
timebox
helmet, which does exactly that.
Most of the helmets and jackets I've coded use the standard
-H
and -V
options to
output usage and version information. The version information includes
the names of the environment variables each expects as specifications:
That doesn't give you the format of the specification, or the semantics -- but it is a good reminder of what to look for in the documentation or code. Some helmets take$ /usr/local/libexec/jacket/timebox -V timebox: ...timebox,v 1.7 2012/... timebox: TIMEBOX_REVEAL, TIMEBOX_INSIDE, TIMEBOX_FORBID, TIMEBOX_WARN
-H
to
output a better reminder of the configuration options.
$ /usr/local/libexec/jacket/timebox -H TIMEBOX_FORBID comma separated list of excluded times: [!]*strftime[!=]=strftime TIMEBOX_INSIDE comma separated list of time relations: [!]strftime(<=?strftime)+; TIMEBOX_REVEAL remove prefix from environment entries TIMEBOX_WARNING escalation denied message for the customer (Sorry)
I've used the same suffix to mean the same thing in each helmet that supports them: These conventions are not mandatory: your local site policy may vary.
_REVEAL
op
prefixed with the ~
command, the name of
the variable is sent prefixed with a -
command.
The effect of this is to reveal some environment variables and remove
the one that did it.
_WARN
stderr
when the client access is rejected. Otherwise some common default
like the vanilla "Sorry." or "Access denied." is output.
_STALL
timebox
helmet uses 2 of the above and
2 specific to the task at hand: TIMEBOX_FORBID
and TIMEBOX_INSIDE
.
Here is a base example of an op
rule that allows
anyone in group wheel
(aka group 0) to
get a superuser shell:
su MAGIC_SHELL ; # what groups=^wheel$,#^0$ # whom uid=0 gid=0 initgroups=root # escalation PERP=$l RCSINIT=-w$l # details
That rule could be run anytime by any Admin. If we want to limit it to
off-peak hours (for our fish store peak 10:30 to 16:30) we can
install a timebox
helmet specification into that
rule:
Note that we explicitly set a time zone, so that we don't take the system default. You might want to force $TZ in a... PERP=$l RCSINIT=-w$l # details helmet=/usr/local/libexec/jacket/timebox $TZ=America/Denver $TIMEBOX_INSIDE=!1030.00<=%H%M.%S<1630.00 $TIMEBOX_WARN=Peak$\shours,$\stry$\slater.
DEFAULT
stanza for your rule-base.
If we would rather not have any changes on Thursday, because the boss is not in the store to help, we would use this helmet:
This takes advantage of the fact that... helmet=/usr/local/libexec/jacket/timebox $TZ=America/Denver $TIMEBOX_FORBID=%u!=Thu $TIMEBOX_WARN=No$\ssu$\son$\sThursday.
timebox
converts
day of week (in English) into a number for
comparison (following %u
's rules).
The timebox
helmet lets you deny access based on
the current time. That's all it does, but there are other helmets to
do other things.
Also note that long running processes are not killed when they leave the specified time-range. That could be implemented as a jacket, but it was never needed (as starting a service should not leave a time-bomb in the process table). If someone is leaving a superuser shell around there are other ways to deal with that.
xdisplay
DISPLAY
environment
variable and lie about HOME
to find the
.xauth
database. But when we want to become
a different mortal login we need to copy the authentication data to
the new login's authentication database.
That's what the xdisplay
jacket does. To
do this it requires some specific configuration. The jacket
requires the current display name (in DISPLAY
)
and the target login's home directory (in HOME
):
That extracts the current key from the active... $DISPLAY $HOME=$H jacket=/usr/local/libexec/helmet/xdisplay
.Xauthority
and installs it
into the target login's .Xauthority
(as
the same display name).
After the escalation exit
s is may remove the
installed data (when used as a jacket, the helmet usage cannot).
stamp
Op
does a good job of authentication: it
assures that the login, group membership or netgroup membership
required is met by the calling process. It makes sure that
the parameters provides meet any restrictions that would make them
unsafe (aka not `authentic'). It consults PAM
to
make sure the person at the keyboard is the person represented.
All the work above is matching the person to the task. That's authentication in a nut-shell: matching a person to the escalation.
Authorization means that another person or process agrees that now is
the time to act.
This is very different from knowing who is acting.
You could think of the timebox
helmet as using
the clock to authorize an action (or possibly to deny it when, the
escalation would be inappropriate).
As an example where people take action: imagine that there is a 24 hour by 365.24 day monitoring group at a major data center for example.com. They get alerts when an application service sends a error messages. The monitors then filter those alerts, only calling application support when there is an actual service disruption.
The management at example.com doesn't want application support to randomly or accidentally stop or restart running applications. They do want them to be able to act when the service is not working correctly. So they give the operations monitoring team a rule to enable the application support members to control the application on a given host for a fixed time. The theory here is that the operations team only calls the production support for the application when their is a service disruption, so restarting the application might be better than no service.
So the application team has a control rule ("tiger stop", "tiger restart", and so forth). But those rules only work for the application support account "joe" when an operations team member runs "tiger enable joe" and joe is a member of the support team. This enchantment lasts for some fixed window, or until it has not been used for some idle time limit. The notion above is that an interactive session might be granted a window of time in which repeated authorizations are not needed. Otherwise the operators would have to type 1 command for each escalation required to bring the service back on-line (which is also possible, but has proved to be a really bad usage pattern).
It is also common to use a 2 key commit style of authorization. In this
case any two people from a group are required to run the
escalated command. One creates a stamp for himself via a group access
rule, with a -u
option to specify the team member
that will use the stamp.
The stamp-check allows any stamp built by someone else to
authorize the action (viz. Owner!$l:Allow=$l
).
This prevents anyone from authorizing their own changes.
See stamp(7l).
This might also be used by a person to authorize themselves to access
a rule-base repeatedly. This is parallel to the "timestamp" feature
built-in to the popular sudo
escalation program.
But it is not "part" of op
, and it requires
a specific escalation (via local site policy) to build the timed stamp.
It does make almost the same thing possible, and adds some features that
add security and a lot of versatility.
op
the stamp
helmet
connects to an existing socket in a specific place in the filesystem.
The process listening on that socket is (usually) created with the
stampctl
program, which has several modes of
operation (see stampctl(8l)):
stampctl
-V
Output all the version information compiled into stampctl
.
stampctl
-V | tr -s '\t ' ' '| sed -n -e 's/.*cache directory: \([^ ]*\).*/\1/p'
Output the top-level stamp directory. This is often used to purge the stamp directory of dead domain sockets at system boot.
stampctl
[-g
group
]
[-m
mode
]
[-u
user
]
[facilities
]
At system boot time an init.d (aka rc.d) script
should make a call to the helmet (as the superuser) to setup the
stamp directory structure. The facilities
are
simply the names of subdirectories that must be instanced.
The optional owner
,
group
, mode
may be
mixed with the names of directories (the last one set is used in each case).
Note that dot (PATH=/usr/local/libexec/jacket:$PATH STAMP_FACILITY=. stampctl -B -m 755 -u root -g 0 . \ -m 750 su \ # sudo-like root stamps -m 1777 -g daemon stamps \ # test stamps for mortals -m 750 -g cats tiger puma \ # applications -m 700 -u source -g staff msrc # master source
.
) is a synonym for the
top-level directory which sets the default mode, uid, and gid if it
exists, otherwise the default mode is 750, uid 0, gid 0.
Existing unmentioned directories are not changed, so multiple start-up scripts
may each build their own facility. Absolute paths are allowed, but
discouraged. The code will not build implied directories, this is
a safety feature, since the modes of any such directory cannot be specified.
Modes may contain optional bits is in
instck
(8l)
(as 750/1 or rwxr-x--?), the modes on the enclosing directory are used
to mask optional bits (which is local site policy).
Also note that the environment variable
TIMESTAMP_FACILITY
specifies the default
top-level directory (absolute) or subdirectory (relative).
It is poor form to use the facility name "OLD", since
install
might build a backup directory
by that name.
stampctl
-M
name
[-n
] [-
max
][-E
end
] [-I
idle
] [name
=value
s]
This creates a new stamp entry for name
, the name
is taken relative to the hard-coded directory that all stamps are under,
unless it starts with a leading slash. Most applications use the name of
the facility followed by the Customer's login name or uid.
The -n
switch does the opposite: it builds a stamp
that denys every authorization request. This is called "penalty mode".
I'm not sure why you'd want to lock escalations for a limited time,
but you may.
The max
integer specifies a limit on the number of
authorizations allowed (denied) by the stamp. Common values are less than 3,
for a single-shot access, usually with an idle time limit of
20m
or so.
Lock escalations more permanently by building a plain file where the
stamp would be placed. The file is displayed as a rejection message for
every escalation request that would open the stamp, and is not removed
by stampctl
(use rm
to
remove the lock).
Note that running stampctl
itself is usually
an escalated operation, and that action may, itself, require a stamp:
this allows as many "sign-off" levels as required.
stampctl
-M
name
-R
remote
[:
roap
] [-nX
] [-
max
] [-E
end
] [-I
idle
] [-T
timeout
] [name
=value
s]
Build a stamp which includes tableau entries from an off-host service.
The remote
host must provide a
tcpmux
service which accepts credential information from
the client. See RFC 1918.
Any proposed tokens from the command-line are replace those sent
by the remote service. (Under -X
this measure
is reversed, then the command-line overrides the remote service.)
The remote
specification may include the
name of the tcpmux
service after a colon
(:
). The default service name is
roapmux
, which happens to be a real program.
See roapmux
, and the
HTML document for it. The
API for
the service requires: the client to connect, the server replies with
a positive connect message (like any tcpmux
service) or a failure; the client sends single line:
The five parameters sent should be valid, but the remote service is allowed to reject even valid request. Note that plural elements are separated by spaces.login
:
groups
:
netgroups
:
domain
:
query
Unauthorized clients get a negative reply. This starts with a
leading dash (-
) followed by a rejection
message, terminated with a newline. Any isspace
character may be removed from the end of the reply. For example,
to disallow without radiating any useful information a service may
reply with:
-Sorry
Authorized clients get a positive reply, which may begin with three integer values, like this:
These values limit the values specified on the command-line. Values of zero are ignored.+
max
,idle
,timeout
ignored-text
The service then produces a tableau list (one per line), then closes
the connection. Each tableau entry may be enclosed in double quotes to
allow embedded newlines and the common C
single-letter backslash escapes.
For example to allow 10 escalated commands with an idle timeout of
13 minutes, while keeping the local timeout
the reply might look like:
+10,780,0 MAY_SHOVEL=yes MAY_START_tiger=yes MAY_STOP_tiger=no
A value of -1
for max
forces the stamp into penalty mode
(which forbids any access until the stamp timeout expires).
For example we might reject a invalid login for a kilosecond with:
+-1,1000,1000 no login john REASON="You do not exist.\nSee an admin, john."
Note that stampctl
removes 1 carriage return character
from the end of each line, to compensate for 1918 network encoding. Missing
carriage returns are silently ignored.
stampctl
-k
[stamps
]
stampctl
-K
[stamps
]
Kill the sessions which are associated with each name. When none are
specified, the implied one is a session names for the real uid. Note
that this is mostly useful to kill sessions at logout time. Sockets
owned by the real uid or spelled with the real uid or login name as
the name of the socket are terminated, if
connect
(2) allows the access and
the stamp is not in penalty mode. The uppercase version tries to
signal the stamp to quit to overcome any penalty restrictions, but
requires a connection to the socket to find the process ID.
stampctl
-N
[stamps
]
Convert the given stamp to penalty mode. Thus blocking any future
escalations with that authorization until the stamp times out, or
it is destroyed via -K
.
stampctl
-Q
stamp
[-F
] tableaus
tableau
entries. If any of those entries do not exist fail. Under the
-F
option the format of each entry (1 per line)
is the format op
expects from a helmet to
force an environment variable to a known value, otherwise it is the
decoded values separated by spaces.
Usually there is an escalation rule to control the stamp creation, and one to end the session explicitly. There may also be other services that create stamps as part of a work-flow process. These tend to be owned by an application login that has ownership of a facility directory reserved for the application.
There may also be an escalation rule that allow an application login to create the directory on system (or application) startup. These rules are usually also available to operations to restore service after a storage or service failure.
stamp
checks a specific stamp for
access, tableau values, and possibly for environment information.
It accepts only the standard helmet options, so any specifications
are provided in the environment. (This is quite common for helmets.)
Each socket represents an active session, via a process which is attached to the socket. The "credentials" the stamp are 3 fold:
If no stamp exists then no authorization is implied. So the a successful connection to the stamp at the required location means that the question of authorization has been asked and answered. But we have to query the socket for that answer.
An unresponsive stamp (the process has exited or the system start-up
did not removed the dead domain socket) gives us an answer without
any follow up: the stamp
request always fails.
A plain file is taken as a failed request, so stamp
presents the contents of the file as an failure message on
stderr
.
The stamp
jacket may request the verification of
tableau entries, either by existence or by value. Thus a stamp might
only be authorized for a particular tty, login name, parent process-id,
or day of the week -- or perhaps the intersection of all of those.
Note that there is no disjunction operation included for this specification.
If any required entries or values fail to match the specification the escalation fails. There is no practical way to tell a configuration error from a legitimately denied authorization. This is because creating a stamp that could never allow any access is not necrobacillary an error.
There should be local site policy conventions for meaning of common
tableau names. I use Owner
,
Perp
, and some others, but you might
choose terms that are already common at your site.
The stamp
helmet requests the authorization
status each stamp (actually that's the last thing it does). If the reply is
"y" (yes), it allows the escalation. Other replies might include "p"
(penalty) an "n" (no such stamp) -- they all fail the escalation.
Five environment variables are consulted for
a stamp
helmet request (these are documented
in the stamp
(7l) manual page as well):
STAMP_FACILITY
=path
This specifies subdirectory under the system stamp directory (usually
/var/op
which should contain the specified
stamp. It may be an absolute path, but that is poor form.
STAMP_SPEC
=stamp
[:
name
=value
]*
This specifies the path to the stamp
, which may
contain slashes, but never
dot-dot (..
). This may also be an absolute
path, but you might guess that I think that's a bad idea.
The name-value check actually allows 5 forms:
name
name
must exist in the tableau.
name
=value
name
must exist in the tableau and
must have exactly the specified value
.
name
!value
name
must exist in the tableau and
must not have the specified value
.
name
relop
value
relop
is one of the C numeric relational
operators: <
,
<=
,
>
,
>=
,
==
, or
!=
.
In this case leading integers are converted from text to their
numeric values. The given relational operator must be true between
the values. Any trailing characters are also compared with
strcmp
, when provided.
name
matchop
RE
matchop
is one of the perl matching operators:
=~
or !~
.
The given tableau entry must match (=~) or not match (!~) the given
regular expression.
These allow checks to assure that the same session that gained the stamp is the one returning for additional escalated commands.
Very rarely one needs to consult multiple stamps to authorize an escalation.
Any environment variable that begins with STAMP_SPEC
is actually permissible.
A parallel usage for _WARN
and _SET
is honored.
See an example below.
STAMP_SET
=names
The listed tableau values are pushed into the escalated environment
only if they are defined in the tableau.
(Any undefined name
is left as-is in
the environment. It is not a configuration error to set a default
value in the configuration of a given rule.)
STAMP_REVEAL
=prefix
This supports the common reveal logic in op
,
see op-jacket(7l).
STAMP_WARN
=sorry
When the authorization fails we output this string (else "Sorry"). This is not displayed when the rejection is provided by a plain file.
When you need additional check for authentication see
coat
below to nest jackets.
When called in jacket mode,
the stamp
process reconnects to the stamp
socket at intervals to prevent the stamp from reaching the timeout
limit. This doesn't defeat the session expiry limit.
/var/run
so we need a rule to
do that as the correct login (since the stamps do not have to
be run as the superuser):
DEFAULT uid=games gid=games # test only stamp owner games MAGIC_SHELL ; # get a test shell as games initgroups=games users=^ksb$
This rule allows anyone to open a session. It could be limited by
any authentication require by local site policy. As an example, it
just opens a default session for the client login
(the $l
after -M
):
The session contains a lot of tableau data so we can poke at it. We pass the complete environment so the stamp may copystamp /usr/local/libexec/jacket/stampctl -M $l -I 6m BLAME=$l:$b TTY=$y TERM TERMCAP EDITOR DISPLAY ORIG_PATH=${PATH} ; $1=^-M$,^make$,^open$,^start$,^touch$ users=^.*$ environment initgroups=%l
TERM
and the rest. This is better than
trying to pass the values on the command line as:
Because that sets unset environment variables to an empty string in the tableau, which makes it hard to tell if it had been empty or unset at the time the stamp was created. But for most application I suppose that doesn't really matter, unless you use an existence check. It doesn't hurt to pass the whole environment to the stamp, as it neverBLAME=$l:$b TTY=$y TERM=${TERM} TERMCAP=${TERMCAP} EDITOR=${EDITOR}... ;
fork
's or
execve
's in any case. (Also, elements not listed
in the tableau are not recoverable from the socket interface.)
This rule allows anyone to terminate their own default session, if the session process allows it (they might be in the penalty state):
I usually don't allow thestamp /usr/local/libexec/jacket/stampctl $1 $l ; $1=^-k|-K|-n$ users=^.*$
-K
spell in production, as it removes penalty stamps. If you never
use a penalty stamp then it doesn't matter. The does allow
This rule checks for the authorization stamp and recovers the
TERMCAP
variable for inspection:
This rule allows anyone to reset the idle timer on their stamp:check { echo TERMCAP="$TERMCAP" } ; users=^.*$ helmet=/usr/local/libexec/jacket/stamp $STAMP_SPEC=$l:TTY=$y $STAMP_SET=TERMCAP:TERM:DISPLAY $STAMP_WARN=There$\sis$\sno$\sstamp$\sfor$\s$l
it may be merged with the kill rule above if the same authentication is required for both.stamp /usr/local/libexec/jacket/stampctl -v $l ; $1=^-v$,^ping$,^refresh$ users=^.*$
In this example the tableau for the new stamp comes from an
authorization server (central.example.com
:
central /usr/local/libexec/jacket/stampctl -M $l -R $0.example.com -I 60m TTY=$y RT=$1 ; $1=^[0-9][0-9]*$ $STAMP_FACILITY=rt $STAMP_SPEC=$l $STAMP_WARN=$l$.no$.love$.from$.$0
stamp
to do that by accepting a stamp
from anyone in the group (e.g. group commit
) to
create a stamp in the commit directory named for a ticket number. The key
feature here is that the stamp contains the login name (in the tableau entry
Auth
=$l
):
This is run my someone in groupready /usr/local/libexec/jacket/stampctl -M commit/ready-$1 -I 15m Auth=$l Ticket=$1 ; $1=^[0-9][0-9]*$ groups=^commit$,^qm$ uid=stamp gid=audit
qm
or
group commit
to allow a commit operation under a
given ticket number (you could change the RE to match letters as well):
$ op ready 121393
Next we configure a rule to make the commit. The key is to only accept a login that is not the same login, but is in any allowed group:
This is accessed by anyone in groupcommit commit-command... $@ ; $1=^[0-9][0-9]*$ groups=^commit$,^appdev$ $STAMP_SPEC=commit/ready-$1:Auth!$l $STAMP_WARN=No$\sauthorization$\sstamp$\sfor$\s$1 $PATH $TERM $ENV $TERMCAP $EDITOR uid=source gid=source
appdev
or
group commit
to execute
a commit operation under the authorized ticket:
$ op commit 121393 Makefile stamp.m
To make that rule a little more useful we can tell the commit-command
the name of the login that authorized the commit by passing the
"Auth" tableau entry to the environment (as $Auth
)
and the ticket number (as $Ticket
):
commit commit-command... $@ ; ... $STAMP_SET=Auth:Ticket
Because we used the ticket number as part of the path to the stamp, we must open a stamp for each ticket. The stamp timeout of 15 minutes is an idle timeout, so as long as the commits keep coming the ticket will stay open. If you didn't make a script to run the commits, then you are not really serious about it; because that script is the best list to review.
qm
) and an
Operations Manager (from ops
).
They both need to get on-board with a change to allow the technical
staff (in group admin
) to get a superuser shell.
Which gives both managers a command to type (or select from a GUI) like:qm /usr/local/sbin/stampctl -M qm/$2 -E 4h ; groups=^qm$ $1=^allow$,^ok$ $2=^[0-9][0-9]*$ # the RT number .... ops /usr/local/sbin/stampctl -M ops/$2 -E 4h ; $1=^allow$,^ok$ $2=^[0-9][0-9]*$ # the RT number ....
$ op qm ok 317811
After both managers have approved the change, the admin has about 4 hours to run the install command as the superuser. We configure her rule as follows:
Which gives her a usage like:admin /bin/sh -c $* ; # local site policy, of course groups=^admin$ uid=root gid=wheel initgroups=root # more local site policy here $1=^[0-9][0-9]$ # the RE number helmet=/usr/local/libexec/jacket/stamp $PERP=$l $RT=$1 environment=^TERMCAP$,^TERM$,^DISPLAY,.... $STAMP_SPEC_1=ops/$2 $STAMP_SPEC_2=qm/$2 $STAMP_WARN=There$.is$.no$.stamp$.for$.RT$1
$ op admin 317811 make install
We could add more restrictions: we could make the managers specify the admin's login name (then check that from the tableau in each stamp). Actually there is a lot you could do, but the question is really what does your local site policy require. Yeah, we can do that.
Notes that the parallel usage for STAMP_SET
means that STAMP_SET_5
is read when
consulting the stamp specified under STAMP_SPEC_5
,
and STAMP_WARN_5
will be output in favor of
the common STAMP_WARN
. In the example I
chose to radiate very little information, which is usually a good idea.
An finally the last specification (in alpha order) is taken as the stamp to refresh in jacket mode.
.ssh
directory to the
local accounting system. This system pushes each login's
ssh
configuration to newly created home
directory to provide a much better customer experience. We'd really
like to allow each customer to submit the files they want installed,
but how can we do it securely?
We want the Customer to be able to submit the files (or file names) securely, and we want the accounting user to be able to fetch the files only when requested to do so. If we don't secure both ends a suborner could install her keys in someone else's account, or collect keys from someone's account (possibly to their account).
So let's give every login a rule to make run stampctl
to build a stamp in a secure directory with a random name. This rule
then sends the name of the stamp file to the accounting system over a
plain-text tcpmux
service. The accounting system
ssh
's back to the host as a mortal login, runs
op
with the stamp file (under
-f
) and the login that requested the
recovery (under -u
).
The stamp
spell for the rule checks the speficied stamp (%f
)
for the owner (%u
) and for confirming token
attribute (viz. Purpose=Recovery
) which the
stamp creation rule added to the tableau.
When all that passes muster, the rule collects the files (as a
tar
stream) and outputs that back to the
accounting system. That archive is extracted as the mortal login to
build their .ssh
directory when their
account is created on new instances.
This is really hard to suborn: the stamp has to be in a local
directory that only the superuser can write to -- with an active
process bound to that name; the tableau must have an entry in it
that no other rule installs; the stamp only lives for a relatively
short time. The plain-text message with the random stamp name (and
login name of the requestor) radiates almost no information. The
ssh
to the client host runs only an escalated
rule, of which the accounting system is the only possible client.
The tar
archive generated as the requesting
customer, and is benign until it is unpacked. It may be frisked for
traps before any deployment: then it is unpacked as the new mortal
account from their home directory.
The only issue I see is that a login name on the requesting system may overlap a login name on some other host: that is clearly a matter of local site policy.
envauth
-- match environment variables to REs
ENVAUTH_VAR_
name
=re
ENVAUTH_NOT_
name
=forbidden
This checks the contents of the dynamic environment variables set by
other helmets. Since op
only checks the
environment as presented by the client, we might need this after a
helmet has added new elements.
sheval
-- assign dynamic environment variables from shell command output
SHEVAL_SET_
var
=cmd
SHEVAL_UNSET
=list
This is a good way to get some information recorded in the environment while still escalated. Can be replaced by in-line scripts, or replace an in-line script. See sheval(7l).
This is also a great way to check for dual group membership. Given
that sheval
fails when a command
exit
s non-zero, we can match the group list
$a
plus the client's real group
$r
against a group with
expr
. In this example we'll match the
group disk
in the rule, and the group
wheel
in the jacket (you may have to pass
$PATH
to make the jacket work):
ktest1 echo You are in both wheel and disk ; groups=disk helmet=/usr/local/libexec/jacket/sheval $SHEVAL_SET_trapWheel=/usr/bin/expr$.',$r,$a,'$.:$.'.*,wheel,.*' $SHEVAL_UNSET=trapWheel
wrope
-- escalated environment access client's diversions via wrapw
WROPE_TO
=template
Runs an instance of wrapw
as the client, but
changes the ownership of the diversion socket to the escalated login.
This allows the escalated-process access to the client's active diversions.
Very useful to run regression tests as a mortal login.
Unlike other jackets, there is no way to get an authorization warning from
wrope
: a failure to start wrapw
is treated an an error, and fails the escalation as a OS error.
(Which does get logged as a failed escalation.)
See wrope(7l) and
wrapw(1l).
proxy-agent
-- escalated environment accesses client's ssh-agent
socket
SPROXY_FROM
=env
SPROXY_ENV
=env
SPROXY_TO
=template
Grant the escalated login access to any single local socket
service. Almost always used to gain access or ssh
's
$SSH_AUTH_SOCKET
socket (which is the default).
Actually this will proxy any local domain socket. It builds a
safe directory under template
with
mkdtemp
(3) and mktemp
(3).
We use 2 (or more) sets of
XXXXXX
's to assure that the make temporary
filename call is secure (since mkdtemp
is
atomic, the mktemp
in the newly created
directory is also secure, due to limited permissions).
See proxy-agent(7l).
signed
-- check signature hash or checksum on the proposed program
SIGNED_FILTER_
cmd
=output
SIGNED_CMD_
cmd
=output
This may be used to assure that a script or binary program was not been changed since the rule-base was last updated by keeping one or more hashes or checksums in the rule-base. These are compared to a run-time version of the same hash which must match as an additional authorization check. See signed(7l).
manifest
-- match the proposed program to a list of REs
MANIFEST_LIST
spec
=file
MANIFEST_WARN
spec
=sorry
This helmet allows the consolidation of many parallel rules into a
single rule, as long as all the escalated scripts have the same usage and
client authentication constraints.
The path to the program
specified by
op
in the helmet parameters must match one
of the regular expression in at least one file
.
The RE may be prefixed with a forced non-zero exit code to deny the
escalation.
ls
,
cat
, and true
,
but not other commands:
The^/bin/ls$ ^/bin/cat$ ^/bin/true$ 77=.*
op
configuration might look like:
do $@ ; helmet=/usr/local/libexec/jacket/manifest $MANIFEST_LIST=/path/to/file/above users=... uid=...
This reduces the number of mnemonic rules, while adding little cost.
Manifest
is most often used to allow many similar
application support scripts to the grouped into a single rule definition.
See manifest(7l).
coat
-- apply multiple jackets to an escalation
COAT
=jackets
This allows more than a single jacket or helmet. Both may be done with the reveal hook, due the limits of the environment specification the jacket must be hidden with a prefix from the helmet.
Each of the required jackets in $COAT
is
started in turn. After all the external command output is consumed
and the exit code is still 0, the next is started. If all the
listed jackets start we allow the escalation.
See coat(7l).
op
a wider range of
application and power, without adding every possible escalation
limit into the core tool. Use them with care.
$Id: jacket.html,v 1.34 2013/08/22 15:47:34 ksb Exp $ by ksb.