diff --git a/bin/nova-rootwrap b/bin/nova-rootwrap new file mode 100755 index 00000000..80bb55ca --- /dev/null +++ b/bin/nova-rootwrap @@ -0,0 +1,72 @@ +#!/usr/bin/env python +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright (c) 2011 Openstack, LLC. +# All Rights Reserved. +# +# 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. + +"""Root wrapper for Nova + + Uses modules in nova.rootwrap containing filters for commands + that nova is allowed to run as another user. + + To switch to using this, you should: + * Set "--root_helper=sudo nova-rootwrap" in nova.conf + * Allow nova to run nova-rootwrap as root in nova_sudoers: + nova ALL = (root) NOPASSWD: /usr/bin/nova-rootwrap + (all other commands can be removed from this file) + + To make allowed commands node-specific, your packaging should only + install nova/rootwrap/{compute,network,volume}.py respectively on + compute, network and volume nodes (i.e. nova-api nodes should not + have any of those files installed). +""" + +import os +import subprocess +import sys + + +RC_UNAUTHORIZED = 99 +RC_NOCOMMAND = 98 + +if __name__ == '__main__': + # Split arguments, require at least a command + execname = sys.argv.pop(0) + if len(sys.argv) == 0: + print "%s: %s" % (execname, "No command specified") + sys.exit(RC_NOCOMMAND) + + userargs = sys.argv[:] + + # Add ../ to sys.path to allow running from branch + possible_topdir = os.path.normpath(os.path.join(os.path.abspath(execname), + os.pardir, os.pardir)) + if os.path.exists(os.path.join(possible_topdir, "nova", "__init__.py")): + sys.path.insert(0, possible_topdir) + + from nova.rootwrap import wrapper + + # Execute command if it matches any of the loaded filters + filters = wrapper.load_filters() + filtermatch = wrapper.match_filter(filters, userargs) + if filtermatch: + obj = subprocess.Popen(filtermatch.get_command(userargs), + stdin=sys.stdin, + stdout=sys.stdout, + stderr=sys.stderr) + sys.exit(obj.returncode) + + print "Unauthorized command: %s" % ' '.join(userargs) + sys.exit(RC_UNAUTHORIZED) diff --git a/nova/rootwrap/__init__.py b/nova/rootwrap/__init__.py new file mode 100755 index 00000000..d6bd39db --- /dev/null +++ b/nova/rootwrap/__init__.py @@ -0,0 +1,16 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright (c) 2011 Openstack, LLC. +# All Rights Reserved. +# +# 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. diff --git a/nova/rootwrap/compute.py b/nova/rootwrap/compute.py new file mode 100755 index 00000000..cd8521d0 --- /dev/null +++ b/nova/rootwrap/compute.py @@ -0,0 +1,159 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright (c) 2011 Openstack, LLC. +# All Rights Reserved. +# +# 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. + + +from nova.rootwrap.filters import CommandFilter, DnsmasqFilter + +filters = [ + # nova/virt/disk.py: 'kpartx', '-a', device + # nova/virt/disk.py: 'kpartx', '-d', device + CommandFilter("/sbin/kpartx", "root"), + + # nova/virt/disk.py: 'tune2fs', '-c', 0, '-i', 0, mapped_device + # nova/virt/xenapi/vm_utils.py: "tune2fs", "-O ^has_journal", part_path + # nova/virt/xenapi/vm_utils.py: "tune2fs", "-j", partition_path + CommandFilter("/sbin/tune2fs", "root"), + + # nova/virt/disk.py: 'mount', mapped_device, tmpdir + # nova/virt/disk.py: 'mount', device, container_dir + # nova/virt/disk.py: 'mount' + # nova/virt/xenapi/vm_utils.py: 'mount', '-t', 'ext2,ext3,ext4,reiserfs'.. + CommandFilter("/bin/mount", "root"), + + # nova/virt/disk.py: 'umount', mapped_device + # nova/virt/disk.py: 'umount', container_dir + # nova/virt/xenapi/vm_utils.py: 'umount', dev_path + CommandFilter("/bin/umount", "root"), + + # nova/virt/disk.py: 'qemu-nbd', '-c', device, image + # nova/virt/disk.py: 'qemu-nbd', '-d', device + CommandFilter("/usr/bin/qemu-nbd", "root"), + + # nova/virt/disk.py: 'losetup', '--find', '--show', image + # nova/virt/disk.py: 'losetup', '--detach', device + CommandFilter("/sbin/losetup", "root"), + + # nova/virt/disk.py: 'tee', metadata_path + # nova/virt/disk.py: 'tee', '-a', keyfile + # nova/virt/disk.py: 'tee', netfile + CommandFilter("/usr/bin/tee", "root"), + + # nova/virt/disk.py: 'mkdir', '-p', sshdir + # nova/virt/disk.py: 'mkdir', '-p', netdir + CommandFilter("/bin/mkdir", "root"), + + # nova/virt/disk.py: 'chown', 'root', sshdir + # nova/virt/disk.py: 'chown', 'root:root', netdir + # nova/virt/libvirt/connection.py: 'chown', os.getuid(), console_log + # nova/virt/libvirt/connection.py: 'chown', os.getuid(), console_log + # nova/virt/libvirt/connection.py: 'chown', 'root', basepath('disk') + # nova/virt/xenapi/vm_utils.py: 'chown', os.getuid(), dev_path + CommandFilter("/bin/chown", "root"), + + # nova/virt/disk.py: 'chmod', '700', sshdir + # nova/virt/disk.py: 'chmod', 755, netdir + CommandFilter("/bin/chmod", "root"), + + # nova/virt/libvirt/vif.py: 'ip', 'tuntap', 'add', dev, 'mode', 'tap' + # nova/virt/libvirt/vif.py: 'ip', 'link', 'set', dev, 'up' + # nova/virt/libvirt/vif.py: 'ip', 'link', 'delete', dev + # nova/network/linux_net.py: 'ip', 'addr', 'add', str(floating_ip)+'/32'i.. + # nova/network/linux_net.py: 'ip', 'addr', 'del', str(floating_ip)+'/32'.. + # nova/network/linux_net.py: 'ip', 'addr', 'add', '169.254.169.254/32',.. + # nova/network/linux_net.py: 'ip', 'addr', 'show', 'dev', dev, 'scope',.. + # nova/network/linux_net.py: 'ip', 'addr', 'del/add', ip_params, dev) + # nova/network/linux_net.py: 'ip', 'addr', 'del', params, fields[-1] + # nova/network/linux_net.py: 'ip', 'addr', 'add', params, bridge + # nova/network/linux_net.py: 'ip', '-f', 'inet6', 'addr', 'change', .. + # nova/network/linux_net.py: 'ip', 'link', 'set', 'dev', dev, 'promisc',.. + # nova/network/linux_net.py: 'ip', 'link', 'add', 'link', bridge_if ... + # nova/network/linux_net.py: 'ip', 'link', 'set', interface, "address",.. + # nova/network/linux_net.py: 'ip', 'link', 'set', interface, 'up' + # nova/network/linux_net.py: 'ip', 'link', 'set', bridge, 'up' + # nova/network/linux_net.py: 'ip', 'addr', 'show', 'dev', interface, .. + # nova/network/linux_net.py: 'ip', 'link', 'set', dev, "address", .. + # nova/network/linux_net.py: 'ip', 'link', 'set', dev, 'up' + CommandFilter("/sbin/ip", "root"), + + # nova/virt/libvirt/vif.py: 'tunctl', '-b', '-t', dev + CommandFilter("/usr/sbin/tunctl", "root"), + + # nova/virt/libvirt/vif.py: 'ovs-vsctl', ... + # nova/virt/libvirt/vif.py: 'ovs-vsctl', 'del-port', ... + # nova/network/linux_net.py: 'ovs-vsctl', .... + CommandFilter("/usr/bin/ovs-vsctl", "root"), + + # nova/virt/libvirt/connection.py: 'dd', "if=%s" % virsh_output, ... + CommandFilter("/bin/dd", "root"), + + # nova/virt/xenapi/volume_utils.py: 'iscsiadm', '-m', ... + CommandFilter("/sbin/iscsiadm", "root"), + + # nova/virt/xenapi/vm_utils.py: "parted", "--script", ... + # nova/virt/xenapi/vm_utils.py: 'parted', '--script', dev_path, ..*. + CommandFilter("/sbin/parted", "root"), + + # nova/virt/xenapi/vm_utils.py: fdisk %(dev_path)s + CommandFilter("/sbin/fdisk", "root"), + + # nova/virt/xenapi/vm_utils.py: "e2fsck", "-f", "-p", partition_path + CommandFilter("/sbin/e2fsck", "root"), + + # nova/virt/xenapi/vm_utils.py: "resize2fs", partition_path + CommandFilter("/sbin/resize2fs", "root"), + + # nova/network/linux_net.py: 'ip[6]tables-save' % (cmd,), '-t', ... + CommandFilter("/sbin/iptables-save", "root"), + CommandFilter("/sbin/ip6tables-save", "root"), + + # nova/network/linux_net.py: 'ip[6]tables-restore' % (cmd,) + CommandFilter("/sbin/iptables-restore", "root"), + CommandFilter("/sbin/ip6tables-restore", "root"), + + # nova/network/linux_net.py: 'arping', '-U', floating_ip, '-A', '-I', ... + # nova/network/linux_net.py: 'arping', '-U', network_ref['dhcp_server'],.. + CommandFilter("/usr/bin/arping", "root"), + + # nova/network/linux_net.py: 'route', '-n' + # nova/network/linux_net.py: 'route', 'del', 'default', 'gw' + # nova/network/linux_net.py: 'route', 'add', 'default', 'gw' + # nova/network/linux_net.py: 'route', '-n' + # nova/network/linux_net.py: 'route', 'del', 'default', 'gw', old_gw, .. + # nova/network/linux_net.py: 'route', 'add', 'default', 'gw', old_gateway + CommandFilter("/sbin/route", "root"), + + # nova/network/linux_net.py: 'dhcp_release', dev, address, mac_address + CommandFilter("/usr/bin/dhcp_release", "root"), + + # nova/network/linux_net.py: 'kill', '-9', pid + # nova/network/linux_net.py: 'kill', '-HUP', pid + # nova/network/linux_net.py: 'kill', pid + CommandFilter("/bin/kill", "root"), + + # nova/network/linux_net.py: dnsmasq call + DnsmasqFilter("/usr/sbin/dnsmasq", "root"), + + # nova/network/linux_net.py: 'radvd', '-C', '%s' % _ra_file(dev, 'conf'),.. + CommandFilter("/usr/sbin/radvd", "root"), + + # nova/network/linux_net.py: 'brctl', 'addbr', bridge + # nova/network/linux_net.py: 'brctl', 'setfd', bridge, 0 + # nova/network/linux_net.py: 'brctl', 'stp', bridge, 'off' + # nova/network/linux_net.py: 'brctl', 'addif', bridge, interface + CommandFilter("/sbin/brctl", "root"), + CommandFilter("/usr/sbin/brctl", "root"), + ] diff --git a/nova/rootwrap/filters.py b/nova/rootwrap/filters.py new file mode 100755 index 00000000..2932c5e1 --- /dev/null +++ b/nova/rootwrap/filters.py @@ -0,0 +1,80 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright (c) 2011 Openstack, LLC. +# All Rights Reserved. +# +# 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 os +import re + + +class CommandFilter(object): + """Command filter only checking that the 1st argument matches exec_path""" + + def __init__(self, exec_path, run_as, *args): + self.exec_path = exec_path + self.run_as = run_as + self.args = args + + def match(self, userargs): + """Only check that the first argument (command) matches exec_path""" + if (os.path.basename(self.exec_path) == userargs[0]): + return True + return False + + def get_command(self, userargs): + """Returns command to execute (with sudo -u if run_as != root).""" + 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:] + + +class RegExpFilter(CommandFilter): + """Command filter doing regexp matching for every argument""" + + def match(self, userargs): + # Early skip if command or number of args don't match + if (len(self.args) != len(userargs)): + # DENY: argument numbers don't match + 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): + break + except re.error: + # DENY: Badly-formed filter + return False + else: + # ALLOW: All arguments matched + return True + + # DENY: Some arguments did not match + return False + + +class DnsmasqFilter(CommandFilter): + """Specific filter for the dnsmasq call (which includes env)""" + + def match(self, userargs): + if (userargs[0].startswith("FLAGFILE=") and + userargs[1].startswith("NETWORK_ID=") and + userargs[2] == "dnsmasq"): + return True + return False + + def get_command(self, userargs): + return userargs[0:2] + [self.exec_path] + userargs[3:] diff --git a/nova/rootwrap/network.py b/nova/rootwrap/network.py new file mode 100755 index 00000000..a930176c --- /dev/null +++ b/nova/rootwrap/network.py @@ -0,0 +1,83 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright (c) 2011 Openstack, LLC. +# All Rights Reserved. +# +# 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. + + +from nova.rootwrap.filters import CommandFilter, DnsmasqFilter + +filters = [ + # nova/network/linux_net.py: 'ip', 'addr', 'add', str(floating_ip)+'/32'i.. + # nova/network/linux_net.py: 'ip', 'addr', 'del', str(floating_ip)+'/32'.. + # nova/network/linux_net.py: 'ip', 'addr', 'add', '169.254.169.254/32',.. + # nova/network/linux_net.py: 'ip', 'addr', 'show', 'dev', dev, 'scope',.. + # nova/network/linux_net.py: 'ip', 'addr', 'del/add', ip_params, dev) + # nova/network/linux_net.py: 'ip', 'addr', 'del', params, fields[-1] + # nova/network/linux_net.py: 'ip', 'addr', 'add', params, bridge + # nova/network/linux_net.py: 'ip', '-f', 'inet6', 'addr', 'change', .. + # nova/network/linux_net.py: 'ip', 'link', 'set', 'dev', dev, 'promisc',.. + # nova/network/linux_net.py: 'ip', 'link', 'add', 'link', bridge_if ... + # nova/network/linux_net.py: 'ip', 'link', 'set', interface, "address",.. + # nova/network/linux_net.py: 'ip', 'link', 'set', interface, 'up' + # nova/network/linux_net.py: 'ip', 'link', 'set', bridge, 'up' + # nova/network/linux_net.py: 'ip', 'addr', 'show', 'dev', interface, .. + # nova/network/linux_net.py: 'ip', 'link', 'set', dev, "address", .. + # nova/network/linux_net.py: 'ip', 'link', 'set', dev, 'up' + CommandFilter("/sbin/ip", "root"), + + # nova/network/linux_net.py: 'ip[6]tables-save' % (cmd,), '-t', ... + CommandFilter("/sbin/iptables-save", "root"), + CommandFilter("/sbin/ip6tables-save", "root"), + + # nova/network/linux_net.py: 'ip[6]tables-restore' % (cmd,) + CommandFilter("/sbin/iptables-restore", "root"), + CommandFilter("/sbin/ip6tables-restore", "root"), + + # nova/network/linux_net.py: 'arping', '-U', floating_ip, '-A', '-I', ... + # nova/network/linux_net.py: 'arping', '-U', network_ref['dhcp_server'],.. + CommandFilter("/usr/bin/arping", "root"), + + # nova/network/linux_net.py: 'route', '-n' + # nova/network/linux_net.py: 'route', 'del', 'default', 'gw' + # nova/network/linux_net.py: 'route', 'add', 'default', 'gw' + # nova/network/linux_net.py: 'route', '-n' + # nova/network/linux_net.py: 'route', 'del', 'default', 'gw', old_gw, .. + # nova/network/linux_net.py: 'route', 'add', 'default', 'gw', old_gateway + CommandFilter("/sbin/route", "root"), + + # nova/network/linux_net.py: 'dhcp_release', dev, address, mac_address + CommandFilter("/usr/bin/dhcp_release", "root"), + + # nova/network/linux_net.py: 'kill', '-9', pid + # nova/network/linux_net.py: 'kill', '-HUP', pid + # nova/network/linux_net.py: 'kill', pid + CommandFilter("/bin/kill", "root"), + + # nova/network/linux_net.py: dnsmasq call + DnsmasqFilter("/usr/sbin/dnsmasq", "root"), + + # nova/network/linux_net.py: 'radvd', '-C', '%s' % _ra_file(dev, 'conf'),.. + CommandFilter("/usr/sbin/radvd", "root"), + + # nova/network/linux_net.py: 'brctl', 'addbr', bridge + # nova/network/linux_net.py: 'brctl', 'setfd', bridge, 0 + # nova/network/linux_net.py: 'brctl', 'stp', bridge, 'off' + # nova/network/linux_net.py: 'brctl', 'addif', bridge, interface + CommandFilter("/sbin/brctl", "root"), + CommandFilter("/usr/sbin/brctl", "root"), + + # nova/network/linux_net.py: 'ovs-vsctl', .... + CommandFilter("/usr/bin/ovs-vsctl", "root"), + ] diff --git a/nova/rootwrap/volume.py b/nova/rootwrap/volume.py new file mode 100755 index 00000000..4ddce7f2 --- /dev/null +++ b/nova/rootwrap/volume.py @@ -0,0 +1,48 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright (c) 2011 Openstack, LLC. +# All Rights Reserved. +# +# 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. + + +from nova.rootwrap.filters import CommandFilter + +filters = [ + # nova/volume/iscsi.py: iscsi_helper '--op' ... + CommandFilter("/usr/sbin/ietadm", "root"), + CommandFilter("/usr/sbin/tgtadm", "root"), + + # nova/volume/driver.py: 'vgs', '--noheadings', '-o', 'name' + CommandFilter("/sbin/vgs", "root"), + + # nova/volume/driver.py: 'lvcreate', '-L', sizestr, '-n', volume_name,.. + # nova/volume/driver.py: 'lvcreate', '-L', ... + CommandFilter("/sbin/lvcreate", "root"), + + # nova/volume/driver.py: 'dd', 'if=%s' % srcstr, 'of=%s' % deststr,... + CommandFilter("/bin/dd", "root"), + + # nova/volume/driver.py: 'lvremove', '-f', "%s/%s" % ... + CommandFilter("/sbin/lvremove", "root"), + + # nova/volume/driver.py: 'lvdisplay', '--noheading', '-C', '-o', 'Attr',.. + CommandFilter("/sbin/lvdisplay", "root"), + + # nova/volume/driver.py: 'iscsiadm', '-m', 'discovery', '-t',... + # nova/volume/driver.py: 'iscsiadm', '-m', 'node', '-T', ... + CommandFilter("/sbin/iscsiadm", "root"), + + # nova/volume/driver.py:'/var/lib/zadara/bin/zadara_sncfg', * + # sudoers does not allow zadara_sncfg yet + ] diff --git a/nova/rootwrap/wrapper.py b/nova/rootwrap/wrapper.py new file mode 100755 index 00000000..2f24c078 --- /dev/null +++ b/nova/rootwrap/wrapper.py @@ -0,0 +1,59 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright (c) 2011 Openstack, LLC. +# All Rights Reserved. +# +# 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 os +import sys + + +FILTERS_MODULES = ['nova.rootwrap.compute', + 'nova.rootwrap.network', + 'nova.rootwrap.volume', + ] + + +def load_filters(): + """Load filters from modules present in nova.rootwrap.""" + filters = [] + for modulename in FILTERS_MODULES: + try: + __import__(modulename) + module = sys.modules[modulename] + filters = filters + module.filters + except ImportError: + # It's OK to have missing filters, since filter modules are + # shipped with specific nodes rather than with python-nova + pass + return filters + + +def match_filter(filters, userargs): + """ + Checks user command and arguments through command filters and + returns the first matching filter, or None is none matched. + """ + + for f in filters: + if f.match(userargs): + # Skip if executable is absent + if not os.access(f.exec_path, os.X_OK): + continue + # Otherwise return matching filter for execution + return f + + # No filter matched + return None diff --git a/nova/tests/test_nova_rootwrap.py b/nova/tests/test_nova_rootwrap.py new file mode 100644 index 00000000..f73f9857 --- /dev/null +++ b/nova/tests/test_nova_rootwrap.py @@ -0,0 +1,65 @@ +# 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. + +from nova.rootwrap.filters import CommandFilter, RegExpFilter, DnsmasqFilter +from nova.rootwrap.wrapper import match_filter +from nova import test + + +class RootwrapTestCase(test.TestCase): + + def setUp(self): + super(RootwrapTestCase, self).setUp() + self.filters = [ + RegExpFilter("/bin/ls", "root", 'ls', '/[a-z]+'), + CommandFilter("/usr/bin/foo_bar_not_exist", "root"), + RegExpFilter("/bin/cat", "root", 'cat', '/[a-z]+'), + CommandFilter("/nonexistant/cat", "root"), + CommandFilter("/bin/cat", "root") # Keep this one last + ] + + def tearDown(self): + super(RootwrapTestCase, self).tearDown() + + def test_RegExpFilter_match(self): + usercmd = ["ls", "/root"] + filtermatch = 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"] + filtermatch = match_filter(self.filters, usercmd) + self.assertTrue(filtermatch is None) + + def test_missing_command(self): + usercmd = ["foo_bar_not_exist"] + filtermatch = match_filter(self.filters, usercmd) + self.assertTrue(filtermatch is None) + + def test_dnsmasq_filter(self): + usercmd = ['FLAGFILE=A', 'NETWORK_ID="foo bar"', 'dnsmasq', 'foo'] + f = DnsmasqFilter("/usr/bin/dnsmasq", "root") + self.assertTrue(f.match(usercmd)) + self.assertEqual(f.get_command(usercmd), + ['FLAGFILE=A', 'NETWORK_ID="foo bar"', '/usr/bin/dnsmasq', 'foo']) + + def test_skips(self): + # Check that all filters are skipped and that the last matches + usercmd = ["cat", "/"] + filtermatch = match_filter(self.filters, usercmd) + self.assertTrue(filtermatch is self.filters[-1])