A more secure root-wrapper alternative

Alternative to using a sudoers file to limit which commands can be run as
root in Nova. This one makes use of command filters defined in Nova code
itself, which can be customized to deeply inspect command arguments before
allowing a command to be executed.

This change puts the infrastructure in place, together with command filters
that replicate the level of filtering provided by a sudoers file (no deep
argument inspection yet). An example of an advanced filter (RegExpFilter) is
also provided. This new root wrapper is not active by default (root_helper
still defaults to "sudo"). Implements blueprint nova-rootwrap.

Change-Id: I7ad723b55e9446758876f21b4fbb09374a910425
This commit is contained in:
Thierry Carrez 2011-11-21 16:28:02 +01:00
parent 6fdb709302
commit 1273f2c1fd
9 changed files with 583 additions and 0 deletions

72
bin/nova-rootwrap Executable file
View File

@ -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)

16
nova/rootwrap/__init__.py Executable file
View File

@ -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.

159
nova/rootwrap/compute.py Executable file
View File

@ -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"),
]

80
nova/rootwrap/filters.py Executable file
View File

@ -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:]

83
nova/rootwrap/network.py Executable file
View File

@ -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"),
]

48
nova/rootwrap/volume.py Executable file
View File

@ -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
]

59
nova/rootwrap/wrapper.py Executable file
View File

@ -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

View File

@ -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])

View File

@ -104,6 +104,7 @@ setup(name='nova',
'bin/nova-manage',
'bin/nova-network',
'bin/nova-objectstore',
'bin/nova-rootwrap',
'bin/nova-scheduler',
'bin/nova-spoolsentry',
'bin/stack',