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:
parent
b7a1a7bf92
commit
e9225e2515
25
README.rst
25
README.rst
@ -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
|
||||
=============================================
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user