summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTomoki Sekiyama <tomoki.sekiyama@hds.com>2014-05-27 17:25:40 -0400
committerTomoki Sekiyama <tomoki.sekiyama@hds.com>2014-06-17 13:11:56 -0400
commite9225e2515b1e5a83bd395bd2eeaba54e3d1a139 (patch)
tree27d02574427f4d43aff5e791f3f064bb26dcd169
parentb7a1a7bf92636c5f45fb0ab38e11234ea5f2054f (diff)
downloadoslo-rootwrap-e9225e2515b1e5a83bd395bd2eeaba54e3d1a139.tar.gz
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>
-rw-r--r--README.rst25
-rw-r--r--oslo/rootwrap/filters.py29
-rw-r--r--tests/test_rootwrap.py24
3 files changed, 78 insertions, 0 deletions
diff --git a/README.rst b/README.rst
index de609df..32d5232 100644
--- a/README.rst
+++ b/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
=============================================
diff --git a/oslo/rootwrap/filters.py b/oslo/rootwrap/filters.py
index 07d015f..d954e2e 100644
--- a/oslo/rootwrap/filters.py
+++ b/oslo/rootwrap/filters.py
@@ -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
diff --git a/tests/test_rootwrap.py b/tests/test_rootwrap.py
index 610f2e3..8fa8424 100644
--- a/tests/test_rootwrap.py
+++ b/tests/test_rootwrap.py
@@ -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)