summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-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)