summaryrefslogtreecommitdiffstats
path: root/nova/rootwrap
diff options
context:
space:
mode:
authorThierry Carrez <thierry@openstack.org>2012-11-16 15:50:01 +0100
committerThierry Carrez <thierry@openstack.org>2012-12-03 15:12:06 +0100
commit12e264d58f052f192f3408f5cd8637809eff085b (patch)
tree8e121921f75e1a00e67845761a681a653feeb6c1 /nova/rootwrap
parent651637ad5475153ef3f2bc15ff0037aebec414c3 (diff)
downloadnova-12e264d58f052f192f3408f5cd8637809eff085b.tar.gz
nova-12e264d58f052f192f3408f5cd8637809eff085b.tar.xz
nova-12e264d58f052f192f3408f5cd8637809eff085b.zip
Configurable exec_dirs to find rootwrap commands
Adds support for a configurable set of trusted directories to search executables in (exec_dirs), which defaults to system PATH. If your filter specifies an exec_path that doesn't start with '/', then it will be searched in exec_dirs. Avoids having to write multiple filters to care for distro differences. Fixes bug 1079723. Also returns a specific error rather than try to run absent executables. Change-Id: Idab03bb0be6832a75ffeed4e78d25d0543f5caf9
Diffstat (limited to 'nova/rootwrap')
-rw-r--r--nova/rootwrap/filters.py29
-rw-r--r--nova/rootwrap/wrapper.py38
2 files changed, 53 insertions, 14 deletions
diff --git a/nova/rootwrap/filters.py b/nova/rootwrap/filters.py
index 46a812e5d..a3e5f1c3c 100644
--- a/nova/rootwrap/filters.py
+++ b/nova/rootwrap/filters.py
@@ -26,6 +26,23 @@ class CommandFilter(object):
self.exec_path = exec_path
self.run_as = run_as
self.args = args
+ self.real_exec = None
+
+ def get_exec(self, exec_dirs=[]):
+ """Returns existing executable, or empty string if none found"""
+ if self.real_exec is not None:
+ return self.real_exec
+ self.real_exec = ""
+ if self.exec_path.startswith('/'):
+ if os.access(self.exec_path, os.X_OK):
+ self.real_exec = self.exec_path
+ else:
+ for binary_path in exec_dirs:
+ expanded_path = os.path.join(binary_path, self.exec_path)
+ if os.access(expanded_path, os.X_OK):
+ self.real_exec = expanded_path
+ break
+ return self.real_exec
def match(self, userargs):
"""Only check that the first argument (command) matches exec_path"""
@@ -33,12 +50,13 @@ class CommandFilter(object):
return True
return False
- def get_command(self, userargs):
+ def get_command(self, userargs, exec_dirs=[]):
"""Returns command to execute (with sudo -u if run_as != root)."""
+ to_exec = self.get_exec(exec_dirs=exec_dirs) or self.exec_path
if (self.run_as != 'root'):
# Used to run commands at lesser privileges
- return ['sudo', '-u', self.run_as, self.exec_path] + userargs[1:]
- return [self.exec_path] + userargs[1:]
+ return ['sudo', '-u', self.run_as, to_exec] + userargs[1:]
+ return [to_exec] + userargs[1:]
def get_environment(self, userargs):
"""Returns specific environment to set, None if none"""
@@ -82,9 +100,10 @@ class DnsmasqFilter(CommandFilter):
return True
return False
- def get_command(self, userargs):
+ def get_command(self, userargs, exec_dirs=[]):
+ to_exec = self.get_exec(exec_dirs=exec_dirs) or self.exec_path
dnsmasq_pos = userargs.index('dnsmasq')
- return [self.exec_path] + userargs[dnsmasq_pos + 1:]
+ return [to_exec] + userargs[dnsmasq_pos + 1:]
def get_environment(self, userargs):
env = os.environ.copy()
diff --git a/nova/rootwrap/wrapper.py b/nova/rootwrap/wrapper.py
index 3dd7ee7e3..742f23b14 100644
--- a/nova/rootwrap/wrapper.py
+++ b/nova/rootwrap/wrapper.py
@@ -23,6 +23,20 @@ import string
from nova.rootwrap import filters
+class NoFilterMatched(Exception):
+ """This exception is raised when no filter matched."""
+ pass
+
+
+class FilterMatchNotExecutable(Exception):
+ """
+ This exception is raised when a filter matched but no executable was
+ found.
+ """
+ def __init__(self, match=None, **kwargs):
+ self.match = match
+
+
def build_filter(class_name, *args):
"""Returns a filter object of class class_name"""
if not hasattr(filters, class_name):
@@ -50,23 +64,29 @@ def load_filters(filters_path):
return filterlist
-def match_filter(filters, userargs):
+def match_filter(filters, userargs, exec_dirs=[]):
"""
Checks user command and arguments through command filters and
- returns the first matching filter, or None is none matched.
+ returns the first matching filter.
+ Raises NoFilterMatched if no filter matched.
+ Raises FilterMatchNotExecutable if no executable was found for the
+ best filter match.
"""
-
- found_filter = None
+ first_not_executable_filter = None
for f in filters:
if f.match(userargs):
# Try other filters if executable is absent
- if not os.access(f.exec_path, os.X_OK):
- if not found_filter:
- found_filter = f
+ if not f.get_exec(exec_dirs=exec_dirs):
+ if not first_not_executable_filter:
+ first_not_executable_filter = f
continue
# Otherwise return matching filter for execution
return f
- # No filter matched or first missing executable
- return found_filter
+ if first_not_executable_filter:
+ # A filter matched, but no executable was found for it
+ raise FilterMatchNotExecutable(match=first_not_executable_filter)
+
+ # No filter matched
+ raise NoFilterMatched()