summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorThierry Carrez <thierry@openstack.org>2013-01-17 11:36:22 +0100
committerThierry Carrez <thierry@openstack.org>2013-01-17 13:32:30 +0100
commit476f15d610b84a7a5fe97a88f46dea38dc409794 (patch)
tree40300cedd0b7a78d21e171f0d7876b57f48a6fca
parentd806266d2367535f19f542a0716cadf6c64d243b (diff)
downloadnova-476f15d610b84a7a5fe97a88f46dea38dc409794.tar.gz
nova-476f15d610b84a7a5fe97a88f46dea38dc409794.tar.xz
nova-476f15d610b84a7a5fe97a88f46dea38dc409794.zip
Use common rootwrap from oslo-incubator
Make Nova use common rootwrap code from oslo-incubator. Implements bp nova-common-rootwrap Change-Id: I3282d65940375589fceb8485829097380d84d946
-rwxr-xr-xbin/nova-rootwrap14
-rw-r--r--nova/openstack/common/rootwrap/__init__.py (renamed from nova/rootwrap/__init__.py)0
-rw-r--r--nova/openstack/common/rootwrap/filters.py (renamed from nova/rootwrap/filters.py)22
-rw-r--r--nova/openstack/common/rootwrap/wrapper.py (renamed from nova/rootwrap/wrapper.py)6
-rw-r--r--nova/tests/test_nova_rootwrap.py198
-rw-r--r--openstack-common.conf2
6 files changed, 21 insertions, 221 deletions
diff --git a/bin/nova-rootwrap b/bin/nova-rootwrap
index c8e880d79..72a8c6309 100755
--- a/bin/nova-rootwrap
+++ b/bin/nova-rootwrap
@@ -16,20 +16,18 @@
# License for the specific language governing permissions and limitations
# under the License.
-"""Root wrapper for Nova
+"""Root wrapper for OpenStack services
- Filters which commands nova is allowed to run as another user.
+ Filters which commands a service is allowed to run as another user.
- To use this, you should set the following in nova.conf:
+ To use this with nova, you should set the following in nova.conf:
rootwrap_config=/etc/nova/rootwrap.conf
You also need to let the nova user run nova-rootwrap as root in sudoers:
nova ALL = (root) NOPASSWD: /usr/bin/nova-rootwrap /etc/nova/rootwrap.conf *
- To make allowed commands node-specific, your packaging should only
- install {compute,network,volume}.filters respectively on compute, network
- and volume nodes (i.e. nova-api nodes should not have any of those files
- installed).
+ Service packaging should deploy .filters files only on nodes where they are
+ needed, to avoid allowing more than is necessary.
"""
import ConfigParser
@@ -75,7 +73,7 @@ if __name__ == '__main__':
if os.path.exists(os.path.join(possible_topdir, "nova", "__init__.py")):
sys.path.insert(0, possible_topdir)
- from nova.rootwrap import wrapper
+ from nova.openstack.common.rootwrap import wrapper
# Load configuration
try:
diff --git a/nova/rootwrap/__init__.py b/nova/openstack/common/rootwrap/__init__.py
index 671d3c173..671d3c173 100644
--- a/nova/rootwrap/__init__.py
+++ b/nova/openstack/common/rootwrap/__init__.py
diff --git a/nova/rootwrap/filters.py b/nova/openstack/common/rootwrap/filters.py
index 8958f1ba1..905bbabea 100644
--- a/nova/rootwrap/filters.py
+++ b/nova/openstack/common/rootwrap/filters.py
@@ -20,7 +20,7 @@ import re
class CommandFilter(object):
- """Command filter only checking that the 1st argument matches exec_path."""
+ """Command filter only checking that the 1st argument matches exec_path"""
def __init__(self, exec_path, run_as, *args):
self.name = ''
@@ -30,7 +30,7 @@ class CommandFilter(object):
self.real_exec = None
def get_exec(self, exec_dirs=[]):
- """Returns existing executable, or empty string if none found."""
+ """Returns existing executable, or empty string if none found"""
if self.real_exec is not None:
return self.real_exec
self.real_exec = ""
@@ -46,7 +46,7 @@ class CommandFilter(object):
return self.real_exec
def match(self, userargs):
- """Only check that the first argument (command) matches exec_path."""
+ """Only check that the first argument (command) matches exec_path"""
if (os.path.basename(self.exec_path) == userargs[0]):
return True
return False
@@ -60,12 +60,12 @@ class CommandFilter(object):
return [to_exec] + userargs[1:]
def get_environment(self, userargs):
- """Returns specific environment to set, None if none."""
+ """Returns specific environment to set, None if none"""
return None
class RegExpFilter(CommandFilter):
- """Command filter doing regexp matching for every argument."""
+ """Command filter doing regexp matching for every argument"""
def match(self, userargs):
# Early skip if command or number of args don't match
@@ -89,15 +89,15 @@ class RegExpFilter(CommandFilter):
class DnsmasqFilter(CommandFilter):
- """Specific filter for the dnsmasq call (which includes env)."""
+ """Specific filter for the dnsmasq call (which includes env)"""
CONFIG_FILE_ARG = 'CONFIG_FILE'
def match(self, userargs):
if (userargs[0] == 'env' and
- userargs[1].startswith(self.CONFIG_FILE_ARG) and
- userargs[2].startswith('NETWORK_ID=') and
- userargs[3] == 'dnsmasq'):
+ userargs[1].startswith(self.CONFIG_FILE_ARG) and
+ userargs[2].startswith('NETWORK_ID=') and
+ userargs[3] == 'dnsmasq'):
return True
return False
@@ -114,7 +114,7 @@ class DnsmasqFilter(CommandFilter):
class DeprecatedDnsmasqFilter(DnsmasqFilter):
- """Variant of dnsmasq filter to support old-style FLAGFILE."""
+ """Variant of dnsmasq filter to support old-style FLAGFILE"""
CONFIG_FILE_ARG = 'FLAGFILE'
@@ -164,7 +164,7 @@ class KillFilter(CommandFilter):
class ReadFileFilter(CommandFilter):
- """Specific filter for the utils.read_file_as_root call."""
+ """Specific filter for the utils.read_file_as_root call"""
def __init__(self, file_path, *args):
self.file_path = file_path
diff --git a/nova/rootwrap/wrapper.py b/nova/openstack/common/rootwrap/wrapper.py
index 70bd63c47..4452177fe 100644
--- a/nova/rootwrap/wrapper.py
+++ b/nova/openstack/common/rootwrap/wrapper.py
@@ -22,7 +22,7 @@ import logging.handlers
import os
import string
-from nova.rootwrap import filters
+from nova.openstack.common.rootwrap import filters
class NoFilterMatched(Exception):
@@ -93,7 +93,7 @@ def setup_syslog(execname, facility, level):
def build_filter(class_name, *args):
- """Returns a filter object of class class_name."""
+ """Returns a filter object of class class_name"""
if not hasattr(filters, class_name):
logging.warning("Skipping unknown filter class (%s) specified "
"in filter definitions" % class_name)
@@ -103,7 +103,7 @@ def build_filter(class_name, *args):
def load_filters(filters_path):
- """Load filters from a list of directories."""
+ """Load filters from a list of directories"""
filterlist = []
for filterdir in filters_path:
if not os.path.isdir(filterdir):
diff --git a/nova/tests/test_nova_rootwrap.py b/nova/tests/test_nova_rootwrap.py
deleted file mode 100644
index 1029e0c2c..000000000
--- a/nova/tests/test_nova_rootwrap.py
+++ /dev/null
@@ -1,198 +0,0 @@
-# vim: tabstop=4 shiftwidth=4 softtabstop=4
-
-# Copyright 2011 OpenStack LLC
-#
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-import ConfigParser
-import logging
-import logging.handlers
-import os
-import subprocess
-
-from nova.rootwrap import filters
-from nova.rootwrap import wrapper
-from nova import test
-
-
-class RootwrapTestCase(test.TestCase):
-
- def setUp(self):
- super(RootwrapTestCase, self).setUp()
- self.filters = [
- filters.RegExpFilter("/bin/ls", "root", 'ls', '/[a-z]+'),
- filters.CommandFilter("/usr/bin/foo_bar_not_exist", "root"),
- filters.RegExpFilter("/bin/cat", "root", 'cat', '/[a-z]+'),
- filters.CommandFilter("/nonexistent/cat", "root"),
- filters.CommandFilter("/bin/cat", "root") # Keep this one last
- ]
-
- def test_RegExpFilter_match(self):
- usercmd = ["ls", "/root"]
- filtermatch = wrapper.match_filter(self.filters, usercmd)
- self.assertFalse(filtermatch is None)
- self.assertEqual(filtermatch.get_command(usercmd),
- ["/bin/ls", "/root"])
-
- def test_RegExpFilter_reject(self):
- usercmd = ["ls", "root"]
- self.assertRaises(wrapper.NoFilterMatched,
- wrapper.match_filter, self.filters, usercmd)
-
- def test_missing_command(self):
- valid_but_missing = ["foo_bar_not_exist"]
- invalid = ["foo_bar_not_exist_and_not_matched"]
- self.assertRaises(wrapper.FilterMatchNotExecutable,
- wrapper.match_filter, self.filters, valid_but_missing)
- self.assertRaises(wrapper.NoFilterMatched,
- wrapper.match_filter, self.filters, invalid)
-
- def _test_DnsmasqFilter(self, filter_class, config_file_arg):
- usercmd = ['env', config_file_arg + '=A', 'NETWORK_ID=foobar',
- 'dnsmasq', 'foo']
- f = filter_class("/usr/bin/dnsmasq", "root")
- self.assertTrue(f.match(usercmd))
- self.assertEqual(f.get_command(usercmd), ['/usr/bin/dnsmasq', 'foo'])
- env = f.get_environment(usercmd)
- self.assertEqual(env.get(config_file_arg), 'A')
- self.assertEqual(env.get('NETWORK_ID'), 'foobar')
-
- def test_DnsmasqFilter(self):
- self._test_DnsmasqFilter(filters.DnsmasqFilter, 'CONFIG_FILE')
-
- def test_DeprecatedDnsmasqFilter(self):
- self._test_DnsmasqFilter(filters.DeprecatedDnsmasqFilter, 'FLAGFILE')
-
- def test_KillFilter(self):
- if not os.path.exists("/proc/%d" % os.getpid()):
- self.skipTest("Test requires /proc filesystem (procfs)")
- p = subprocess.Popen(["cat"], stdin=subprocess.PIPE,
- stdout=subprocess.PIPE,
- stderr=subprocess.STDOUT)
- try:
- f = filters.KillFilter("root", "/bin/cat", "-9", "-HUP")
- f2 = filters.KillFilter("root", "/usr/bin/cat", "-9", "-HUP")
- usercmd = ['kill', '-ALRM', p.pid]
- # Incorrect signal should fail
- self.assertFalse(f.match(usercmd) or f2.match(usercmd))
- usercmd = ['kill', p.pid]
- # Providing no signal should fail
- self.assertFalse(f.match(usercmd) or f2.match(usercmd))
- # Providing matching signal should be allowed
- usercmd = ['kill', '-9', p.pid]
- self.assertTrue(f.match(usercmd) or f2.match(usercmd))
-
- f = filters.KillFilter("root", "/bin/cat")
- f2 = filters.KillFilter("root", "/usr/bin/cat")
- usercmd = ['kill', os.getpid()]
- # Our own PID does not match /bin/sleep, so it should fail
- self.assertFalse(f.match(usercmd) or f2.match(usercmd))
- usercmd = ['kill', 999999]
- # Nonexistent PID should fail
- self.assertFalse(f.match(usercmd) or f2.match(usercmd))
- usercmd = ['kill', p.pid]
- # Providing no signal should work
- self.assertTrue(f.match(usercmd) or f2.match(usercmd))
- finally:
- # Terminate the "cat" process and wait for it to finish
- p.terminate()
- p.wait()
-
- def test_KillFilter_no_raise(self):
- # Makes sure ValueError from bug 926412 is gone.
- f = filters.KillFilter("root", "")
- # Providing anything other than kill should be False
- usercmd = ['notkill', 999999]
- self.assertFalse(f.match(usercmd))
- # Providing something that is not a pid should be False
- usercmd = ['kill', 'notapid']
- self.assertFalse(f.match(usercmd))
-
- def test_KillFilter_deleted_exe(self):
- # Makes sure deleted exe's are killed correctly.
- # See bug #967931.
- def fake_readlink(blah):
- return '/bin/commandddddd (deleted)'
-
- f = filters.KillFilter("root", "/bin/commandddddd")
- usercmd = ['kill', 1234]
- # Providing no signal should work
- self.stubs.Set(os, 'readlink', fake_readlink)
- self.assertTrue(f.match(usercmd))
-
- def test_ReadFileFilter(self):
- goodfn = '/good/file.name'
- f = filters.ReadFileFilter(goodfn)
- usercmd = ['cat', '/bad/file']
- self.assertFalse(f.match(['cat', '/bad/file']))
- usercmd = ['cat', goodfn]
- self.assertEqual(f.get_command(usercmd), ['/bin/cat', goodfn])
- self.assertTrue(f.match(usercmd))
-
- def test_exec_dirs_search(self):
- # This test supposes you have /bin/cat or /usr/bin/cat locally
- f = filters.CommandFilter("cat", "root")
- usercmd = ['cat', '/f']
- self.assertTrue(f.match(usercmd))
- self.assertTrue(f.get_command(usercmd, exec_dirs=['/bin',
- '/usr/bin']) in (['/bin/cat', '/f'], ['/usr/bin/cat', '/f']))
-
- def test_skips(self):
- # Check that all filters are skipped and that the last matches
- usercmd = ["cat", "/"]
- filtermatch = wrapper.match_filter(self.filters, usercmd)
- self.assertTrue(filtermatch is self.filters[-1])
-
- def test_RootwrapConfig(self):
- raw = ConfigParser.RawConfigParser()
-
- # Empty config should raise ConfigParser.Error
- self.assertRaises(ConfigParser.Error, wrapper.RootwrapConfig, raw)
-
- # Check default values
- raw.set('DEFAULT', 'filters_path', '/a,/b')
- config = wrapper.RootwrapConfig(raw)
- self.assertEqual(config.filters_path, ['/a', '/b'])
- self.assertEqual(config.exec_dirs, os.environ["PATH"].split(':'))
- self.assertFalse(config.use_syslog)
- self.assertEqual(config.syslog_log_facility,
- logging.handlers.SysLogHandler.LOG_SYSLOG)
- self.assertEqual(config.syslog_log_level, logging.ERROR)
-
- # Check general values
- raw.set('DEFAULT', 'exec_dirs', '/a,/x')
- config = wrapper.RootwrapConfig(raw)
- self.assertEqual(config.exec_dirs, ['/a', '/x'])
-
- raw.set('DEFAULT', 'use_syslog', 'oui')
- self.assertRaises(ValueError, wrapper.RootwrapConfig, raw)
- raw.set('DEFAULT', 'use_syslog', 'true')
- config = wrapper.RootwrapConfig(raw)
- self.assertTrue(config.use_syslog)
-
- raw.set('DEFAULT', 'syslog_log_facility', 'moo')
- self.assertRaises(ValueError, wrapper.RootwrapConfig, raw)
- raw.set('DEFAULT', 'syslog_log_facility', 'local0')
- config = wrapper.RootwrapConfig(raw)
- self.assertEqual(config.syslog_log_facility,
- logging.handlers.SysLogHandler.LOG_LOCAL0)
- raw.set('DEFAULT', 'syslog_log_facility', 'LOG_AUTH')
- config = wrapper.RootwrapConfig(raw)
- self.assertEqual(config.syslog_log_facility,
- logging.handlers.SysLogHandler.LOG_AUTH)
-
- raw.set('DEFAULT', 'syslog_log_level', 'bar')
- self.assertRaises(ValueError, wrapper.RootwrapConfig, raw)
- raw.set('DEFAULT', 'syslog_log_level', 'INFO')
- config = wrapper.RootwrapConfig(raw)
- self.assertEqual(config.syslog_log_level, logging.INFO)
diff --git a/openstack-common.conf b/openstack-common.conf
index ea33ab235..a0b14e651 100644
--- a/openstack-common.conf
+++ b/openstack-common.conf
@@ -1,7 +1,7 @@
[DEFAULT]
# The list of modules to copy from openstack-common
-modules=cfg,cliutils,context,excutils,eventlet_backdoor,fileutils,gettextutils,importutils,iniparser,jsonutils,local,lockutils,log,network_utils,notifier,plugin,policy,setup,timeutils,rpc,uuidutils
+modules=cfg,cliutils,context,excutils,eventlet_backdoor,fileutils,gettextutils,importutils,iniparser,jsonutils,local,lockutils,log,network_utils,notifier,plugin,policy,rootwrap,setup,timeutils,rpc,uuidutils
# The base module to hold the copy of openstack.common
base=nova