op
's
jacket stamp
may be used to provide
remote authorization for a client. The roapmux
service mocks a stampctl
tableau based on
the login
, groups
,
netgroups
,
domain
, and
query
,
presented by a specific client
host.
If you have never read the
jacket HTML document, please
read it before you continue with this document. Also the
stamp
(8)
and
stampctl
(7)
manual pages might provide more context.
To install this service you must be able to edit
system configuration files (viz. /etc/inetd.conf
,
/etc/tcpmux.conf
, or
/etc/xinetd.d/roapmux.conf
).
Then you must be able to restart (reload) the apropos service.
Most implementations of inetd
had an implementation
of tcpmux
included, until recently. Since some
versions of xinetd
do not include one, so I coded
a stand-alone version that you may configure to support local services.
See tcpmux
,
if your local version of inetd
doesn't internally
support the fabulous RFC1078 mux.
roapmux
?roapmux
as providing "authorization tableau to
specific network clients".
This allows recognized clients to request a list of abstract tokens.
Each of these tokens represents an authorization to perform some
(set of) privileged commands (usually under op
or sudo
).
The client must have an IP address
that reverse-maps to a hostname contained in the meta-configuration data on
the server, and it must specify a legitimate combination of credentials.
This sounds like it could be a security issue, but a list of abstract tokens is hardly confidential, when sent over a local network -- if you can't trust a local IP address, you have larger issues.
roapmux
?roapmux
to pull a current
list of the actions some person has granted to some other person
(or automation) for a limited window. That list could be fairly
static, or completely dynamic.
The default generator
is
hxmd
, which provides direct access to
m4
, make
, and
sh
processing. An option is available to
replace the default processor, but it might be better to code a
replacement service, if you don't like hxmd
.
/etc/tcmpmux.conf
(note that the backslashes
should be removed, as it is really a single line):
That is a pretty long spell, but it allows you to test most of the features you'll need. You'll need to replace "ksb:source" with your mortal account and a valid group.tcpmux/roap stream tcp nowait ksb:source /usr/local/libexec/roapmux \ roapmux -C/tmp/ksb/test.cf -R/tmp/ksb/test.rev ENV1=testing \ /tmp/ksb/gen.sh /tmp/ksb/data.host
Then I built test files to make that work:
$ mkdir /tmp/ksb $ cd /tmp/ksb $ touch test.cf test.rev data.host gen.sh $ chmod +x gen.sh $ vi test.cf %HOST localhost w02.example.com $ vi test.rev # $127.0.0.1(*): ${echo:-echo} localhost # $*(*): ${echo:-echo} w02.example.com $ vi data.host dnl map a host to a realm, test with earth for now. `REALM=earth LOGIN='ROAP_LOGIN` 'ifelse(-1,index(` 'ROAP_GROUPS` ',` wheel '),,`SUPER=yes ')dnl $ vi gen.sh #!/usr/bin/env ksh # Below we set: max connect, session, idle echo "+10,180,180" cat "$@" exit 0
Then I tested those with a spell which mocks most of the command-line options
roapmux
passes hxmd
:
That proves that we should see something sane. Then I tested the$ hxmd -G w02.example.com -C./test.cf -D!ROAP_LOGIN=test gen.sh data.host REALM=earth LOGIN=test $
tcpmux
interface to this:
That shows the basic mechanics of the data-flow. Then I put my test account in group$ muxcat localhost roap "test:charon::" REALM=earth LOGIN=test $
wheel
:
$ muxcat localhost roap "test:charon wheel::" REALM=earth LOGIN=test SUPER=yes $
This shows how a change in the credentials sent might change the output authorization tokens.
These are just test values, but they do represent most of the features
you need to scale this out. Assume that the credentials passed are
from a trusted source, because if they are not, it doesn't matter.
If a Bad Guy pokes at your authorization service for hours they may
find out the names of several authorization tokens that some credentialed
logins have access to -- but those tokens do not have
to have meaningful names -- even if they do, the information
radiated is less useful than the list of people in groups 0, staff or
operator (which are all in the /etc/group
file).
roapmux
?muxcat
, as above; see
muxcat(1l).
However, most access is via stampctl
's
-R
option.
Stampctl
builds a credential list from the
real uid
, gid
,
and the list returned by getgroups
, the
information in the local /etc/netgroup
file,
and the YP/NIX domain
fetched from
getdomainname
(or sysctl
's
MIB
kern.domainname
). The last field
query
is a token passed to the generator
that has local meaning. The query
is
sent in plain-text, so include only public data.
To fetch the tableau from roapmux
, it opens a
connection to the roap
server specified
under -R
. It presents the service name
"roapmux" to the mux service, which should reply with a positive
service start. By convention the positive reply is the name the
service believes the client to be. The client presents the
credentials collected above. The service replies with a positive
reply, which may include suggested limits on the life of the
credentials, followed by a list of tableau entries.
hxmd
instance.
The selection of hxmd
as the driver provides
these facilities:
-G
-D
roapmux
converts them into m4
macro definitions under
-D!ROAPMUX_LOGIN=
login
and the like. See below.
-C
,
-Z
, and -X
roapmux
, and presented multiple times as part of
the generator
specification. A single selected
host specification (under hxmd
's
-G
option) selects all the meta-data for
the client host.
-N
, -K
/-Q
/-r
-F
hxmd
's HTML document).
The structure of the generator
options to
hxmd
specify the files, cache directories,
and commands that form the reply. Those elements combine to do the
"heavy lifting" of forming the per-request policy, rejecting disallowed
authorizations, and reporting access requests.
The command-line presented to hxmd
looks like:
Withhxmd
-G
reverse
\-C
config
\-D!ROAP_HOST
=
reverse
\-D!ROAP_IP
=
IP
\ [-D!ROAP_LOGIN
=
login
] \ [-D!ROAP_GROUPS
=
groups
] \ [-D!ROAP_NETGROUPS
=
netgroups
] \ [-D!ROAP_DOMAIN
=
domain
] \-D!ROAP_QUERY
=
query
\-D!ROAP_MASK
=
mask
\ [-X
ex-config
] \ [-Z
zero-config
] \generator
HXMD_LIB
set by -L
and other environment variables set by envs
The explict macro definitions on the command line (above) are passed down
to allow the generator
to expose only the
authorization tokens allowed to the client. Several are left
undefined when they are presented as either the empty string or
the sentinel string dot (.
). Those
are ROAP_LOGIN
,
ROAP_GROUPS
,
ROAP_NETGROUPS
, and
ROAP_DOMAIN
.
The host and IP are always passed down.
The exclaimation point markup in the definition prevents these values
from being passed on under hxmd
's
-o
option. This means any transfer of these
to a child process or recursive call to hxmd
must explicitly send them down.
pstree
command in my generator
script to trace the process tree. Here is the relevant output
(I truncated the long lines):
This shows that the process tree is not "simple", but the complexity is managed by`-xinetd -stayalive -pidfile /var/run/xinetd.pid `-perl /usr/local/libexec/roapmux -C/tmp/ksb/test.cf -R/tmp/ksb/test.re `-hxmd -G w02.example.com -C/tmp/ksb/test.cf -D!ROAP_HOST=w02.examp |-hxmd -G w02.example.com -C/tmp/ksb/test.cf -D!ROAP_HOST=w02.e `-xclate -ms -N >&6 -- xapply -fzmu -sP6 -2 %+ - - `-xapply -fzmu -sP6 -2 %+ - - `-xclate -s 0 `-ksh /tmp/ksb/gen.sh /tmp/hxtfKjejjx/K7RZ5i/data.h `-pstree -Aa
hxmd
.
I have yet to find an authorization rule I cannot encode using
this structure. Here are some example support functions I include
from the generator
command-line:
Given those support macros it is easier to read rules like these:dnl $Id... dnl roap support macros (ksb) dnl dnl Does client have any named group? If so output "$1", else nothing. pushdef(`rp_in_group',`ifelse(-1,index(` 'ROAP_GROUPS` ',` '$2` '),`ifelse($#,2,`',`rp_in_group($1,shift(shift($@)))')',`$1')')dnl dnl dnl Does client have any named netgroup? Same as rp_in_group mostly. pushdef(`rp_in_netgroup',`ifelse(-1,index(` 'ROAP_NETGROUPS` ',` '$2` '),`ifelse($#,2,`',`rp_in_netgroup($1,shift(shift($@)))')',`$1')')dnl dnl dnl Does client need us to check ypcat for their domain? pushdef(`rp_have_nis_groups',`rp_in_netgroup(`ifdef(`ROAP_DOMAIN',`$1')',`+')')dnl dnl dnl Withdraw the remote op authorization protocol macro support pushdef(`rp_pop',`popdef(`rp_in_group')popdef(`rp_in_netgroup')popdef(`rp_pop')popdef(`rp_have_nis_groups')popdef(`rp_pop')')dnl
dnl Can client su to anyone? dnl On Solaris hosts use group 0 as root, otherwise 0 is wheel; dnl members of group 0 can su to anyone (via a superuser shell). rp_in_group(`SU_any=yes ',ifelse(HOSTTYPE,SUN5,`root',`wheel'))dnl dnl dnl Database support staff can su to sqladm rp_in_group(`SU_sqladm=yes ',dba,dbm,wheel,root)dnl dnl dnl Anyone in hostmaster can reload the nameserver rp_in_group(`CTL_named=yes ',hostmast,hostmaster,root,wheel)dnl dnl dnl The owner of a workstation can reboot it rp_in_netgroup(`REBOOT=yes ',workstation)dnl dnl If netgroups are not local look at ypcat domain....
If you don't like m4
for mapping, you can use
any language you like, the driver script could be the
only item in the generator
command, and that
program could be any shell command you like. Getting the params from
hxmd
is easy. Let's build an m4
parameter file to build a configuration file you can read, I'll make
one of the "ini" style files:
Replace the$ vi data.host dnl $Id... dnl get params from hxmd `[client] 'ROAP_HOST `[ip] 'ROAP_IP ifdef(`ROAP_LOGIN',``[login] 'ROAP_LOGIN ')dnl ifdef(`ROAP_GROUPS',``[groups] 'patsubst(ROAP_GROUPS ,` ',` ')')`'dnl ifdef(`ROAP_NETGROUPS',``[netgroups] 'patsubst(ROAP_NETGROUPS ,` ',` ')')`'dnl ifdef(`ROAP_DOMAIN',``[doamin] 'ROAP_DOMAIN ')dnl dnl We actually always get a query, but be paranoid. ifdef(`ROAP_QUERY',``[query] 'ROAP_QUERY ')dnl dnl if we were called under -M we get one of these ifdef(`ROAP_MASK',``[mask] 'ROAP_MASK ')dnl dnl no other data to convert here, except host macros $ muxcat localhost roap "test:charon wheel:::thing" [client] w02.example.com [ip] 127.0.0.1 [login] test [groups] charon wheel [query] thing $
cat
shim with any command that
takes the "ini" format file and you win. Adapters are nice to get
started, eventually you'll just code the rule in m4
.
make
recipe and all the documentation in the world.
I've tried, but it is always an option. On the plus side, it allows
the use of nuclear weapons against the problem at hand.
On the otherhand, building a shell script with m4
markup is more traditional. So let's play it that way first.
Recall that stampctl
can fetch values from an
existsing stamp under -Q
. If the
query string (which has no other specified meaning) is taken as a
key to find a local stamp socket, then the authorization may be
based on the tableau of that stamp.
Say the query string is the relative name of a authorizing stamp under
/var/local/creds
. We'll ask that stamp for
the tableau entry for
OK_
login
.
I'll use some m4
in data.host
to build a command to find such an authorization stamp.
While roapmux
strips shell meta-characters from
the (untrusted) login and query values it doesn't know how the
generator
might use the
query
. So we'll have to check for
directory climbing ourselves:
If that didn't produce the failure we expected, then you have a version of$ vi data.host dnl lookup local authorization for login from query's stamp (ksb) ifelse(-1,regexp(ROAP_QUERY,`^\.\./|/\.\./|/\.\.$'),`', `errprint(`query may not include dot-dot in path')m4exit(78)')dnl ifdef(`ROAP_QUERY',``stampctl -Q /var/local/creds/'`'dnl patsubst(ROAP_QUERY,` .*',`')` OK_'ifdef(`ROAP_LOGIN',`ROAP_LOGIN',`root')')` 'dnl $ muxcat localhost roap "test:charon wheel:::thing" stampctl -Q /var/local/creds/thing OK_test $ muxcat localhost roap "test:charon wheel:::../other/thing" muxcat: localhost: test:charon,wheel:::../other/thing: query may not include dot-dot in path
m4
which doesn't have the
regexp
macro. Install a better version of
m4
, like the FreeBSD one (I fixed math in that
one too). Then set the PATH
in the
tcpmux.conf
line (replace "ENV1=testing"
with the new PATH
assignment).
the command, because we didn't setup the "thing" stamp. Build one
with stampctl
's -M
option and you can actually run the file specified by
$1
in gen.sh
to
fetch the value.
That puts more of the checking in shell code, which is harder to
make really secure. At that point I'd use the shell shim to
call Perl
, C
, or
Python
.
reverse
map file under -R
on the roapmux
command-line. This forces the
mux to call mk
to select a shell command from
the reverse
file to map the host to
the correct name in the config
file:
Note that themk -s -l0 -m
hostname
-s
IP-address
-DCONFIG=
config
reverse
hostname
will be
@
for unmapped IP addresses.
If the reverse
specified on the command line is
dot (.
), then the configuration file is
searched for a matching marked line. All the normal mk
judo works: build a script to find the name or match it from a map file
of regular expressions, or chain to some other program. The client
connection is actually still open on stdin
, but
this is not a good way to augment the protocol, really.
reverse
map filemsrcmux
, so see the description in
that HTML document.
That document also explains how to configure xinetd
to
provide RFC1078
service.
roapmux
service provides client-based limits for
authorization access, granular to login, group, netgroup, domain, and
a client provided query string. The client's op
configuration authenticates the request, runs stampctl
-R
to request an authorization tableau, thus
additional authorization (access) is allowed for the life of the stamp.
$Id: roapmux.html,v 1.3 2012/11/02 21:14:30 ksb Exp $