Add ChainingRegExpFilter for prefix utilities

This patch adds ChainingRegExpFilter to filter commands prefixed to other
commands, such as 'nice' and 'ionice'. This filter only checks specified
number of arguments, and remaining arguments are filtered by the other
existing filters.

Change-Id: Ica014c472c7e1376f107a039452b215e5c2c4ee5
Implements: blueprint chaining-regexp-filter
Signed-off-by: Tomoki Sekiyama <tomoki.sekiyama@hds.com>
This commit is contained in:
Tomoki Sekiyama 2014-05-27 17:25:40 -04:00
parent b7a1a7bf92
commit e9225e2515
3 changed files with 78 additions and 0 deletions

View File

@ -263,6 +263,31 @@ Example: allow to run `ip netns exec <namespace> <command>` as long as
``ip: IpNetnsExecFilter, ip, root``
ChainingRegExpFilter
--------------------
Filter that allows to run the prefix command, if the beginning of its arguments
match to a list of regular expressions, and if remaining arguments are any
otherwise-allowed command. Parameters are:
1. Executable allowed
2. User to run the command under
3. (and following) Regular expressions to use to match first (and subsequent)
command arguments.
This filter regards the length of the regular expressions list as the number of
arguments to be checked, and remaining parts are checked by other filters.
Example: allow to run `/usr/bin/nice`, but only with first two parameters being
-n and integer, and followed by any allowed command by the other filters:
``nice: /usr/bin/nice, root, nice, -n, -?\d+``
Note: this filter can't be used to impose that the subcommand is always run
under the prefix command. In particular, it can't enforce that a particular
command is only run under "nice", since the subcommand can explicitly be
called directly.
Calling rootwrap from OpenStack services
=============================================

View File

@ -316,3 +316,32 @@ class IpNetnsExecFilter(ChainingFilter):
if args:
args[0] = os.path.basename(args[0])
return args
class ChainingRegExpFilter(ChainingFilter):
"""Command filter doing regexp matching for prefix commands.
Remaining arguments are filtered again. This means that the command
specified as the arguments must be also allowed to execute directly.
"""
def match(self, userargs):
# Early skip if number of args is smaller than the filter
if (not userargs or len(self.args) > len(userargs)):
return False
# Compare each arg (anchoring pattern explicitly at end of string)
for (pattern, arg) in zip(self.args, userargs):
try:
if not re.match(pattern + '$', arg):
# DENY: Some arguments did not match
return False
except re.error:
# DENY: Badly-formed filter
return False
# ALLOW: All arguments matched
return True
def exec_args(self, userargs):
args = userargs[len(self.args):]
if args:
args[0] = os.path.basename(args[0])
return args

View File

@ -316,6 +316,30 @@ class RootwrapTestCase(testtools.TestCase):
self.assertRaises(wrapper.NoFilterMatched,
wrapper.match_filter, filter_list, args)
def test_ChainingRegExpFilter_match(self):
filter_list = [filters.ChainingRegExpFilter('nice', 'root',
'nice', '-?\d+'),
filters.CommandFilter('cat', 'root')]
args = ['nice', '5', 'cat', '/a']
dirs = ['/bin', '/usr/bin']
self.assertIsNotNone(wrapper.match_filter(filter_list, args, dirs))
def test_ChainingRegExpFilter_not_match(self):
filter_list = [filters.ChainingRegExpFilter('nice', 'root',
'nice', '-?\d+'),
filters.CommandFilter('cat', 'root')]
args_invalid = (['nice', '5', 'ls', '/a'],
['nice', '--5', 'cat', '/a'],
['nice2', '5', 'cat', '/a'],
['nice', 'cat', '/a'],
['nice', '5'])
dirs = ['/bin', '/usr/bin']
for args in args_invalid:
self.assertRaises(wrapper.NoFilterMatched,
wrapper.match_filter, filter_list, args, dirs)
def test_ReadFileFilter_empty_args(self):
goodfn = '/good/file.name'
f = filters.ReadFileFilter(goodfn)