From 93d3c77caff67e2c406a9b17c31de88e90700e13 Mon Sep 17 00:00:00 2001 From: Thierry Carrez Date: Wed, 6 Jun 2012 14:23:24 +0200 Subject: [PATCH] Move rootwrap filters definition to config files Move rootwrap filters definition from being defined within Nova code to being defined in configuration files to facilitate pluging-in new rootwrap commands. Transition notes: * nova-rootwrap now requires an additional (first) parameter pointing to the root-owned rootwrap.conf file, sudoers needs to be updated to specify that ("nova-rootwrap /etc/nova/rootwrap.conf *") * Packagers should ship {compute,network,volume}.filters inside a directory listed in rootwrap.conf rather than shipping nova/rootwrap/{compute,network,volume}.py * Filter definitions now only support strings. The KillFilter (which was using arrays as parameters) was modified and the tests updated. Implements bp nova-rootwrap-pluggable-filters Corresponding devstack change needs to land first, so that tests pass: https://review.openstack.org/8842 Change-Id: I2350154cd8057bd57926ed542de035626f7de37d --- bin/nova-rootwrap | 35 +++-- etc/nova/rootwrap.conf | 7 + etc/nova/rootwrap.d/compute.filters | 187 +++++++++++++++++++++++++ etc/nova/rootwrap.d/network.filters | 83 +++++++++++ etc/nova/rootwrap.d/volume.filters | 27 ++++ nova/rootwrap/compute.py | 207 ---------------------------- nova/rootwrap/filters.py | 19 ++- nova/rootwrap/network.py | 98 ------------- nova/rootwrap/volume.py | 45 ------ nova/rootwrap/wrapper.py | 46 ++++--- nova/tests/test_nova_rootwrap.py | 36 +++-- 11 files changed, 383 insertions(+), 407 deletions(-) create mode 100644 etc/nova/rootwrap.conf create mode 100644 etc/nova/rootwrap.d/compute.filters create mode 100644 etc/nova/rootwrap.d/network.filters create mode 100644 etc/nova/rootwrap.d/volume.filters delete mode 100644 nova/rootwrap/compute.py delete mode 100644 nova/rootwrap/network.py delete mode 100644 nova/rootwrap/volume.py diff --git a/bin/nova-rootwrap b/bin/nova-rootwrap index c5d53b46f48d..0fd44939c3d4 100755 --- a/bin/nova-rootwrap +++ b/bin/nova-rootwrap @@ -18,21 +18,21 @@ """Root wrapper for Nova - Uses modules in nova.rootwrap containing filters for commands - that nova is allowed to run as another user. + Filters which commands 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 use this, you should set the following in nova.conf: + root_helper=sudo nova-rootwrap /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 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). + 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). """ +import ConfigParser import os import subprocess import sys @@ -40,16 +40,27 @@ import sys RC_UNAUTHORIZED = 99 RC_NOCOMMAND = 98 +RC_BADCONFIG = 97 if __name__ == '__main__': # Split arguments, require at least a command execname = sys.argv.pop(0) - if len(sys.argv) == 0: + if len(sys.argv) < 2: print "%s: %s" % (execname, "No command specified") sys.exit(RC_NOCOMMAND) + configfile = sys.argv.pop(0) userargs = sys.argv[:] + # Load configuration + config = ConfigParser.RawConfigParser() + config.read(configfile) + try: + filters_path = config.get("DEFAULT", "filters_path").split(",") + except ConfigParser.Error: + print "%s: Incorrect configuration file: %s" % (execname, configfile) + sys.exit(RC_BADCONFIG) + # 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)) @@ -59,7 +70,7 @@ if __name__ == '__main__': from nova.rootwrap import wrapper # Execute command if it matches any of the loaded filters - filters = wrapper.load_filters() + filters = wrapper.load_filters(filters_path) filtermatch = wrapper.match_filter(filters, userargs) if filtermatch: obj = subprocess.Popen(filtermatch.get_command(userargs), diff --git a/etc/nova/rootwrap.conf b/etc/nova/rootwrap.conf new file mode 100644 index 000000000000..730f71695e6d --- /dev/null +++ b/etc/nova/rootwrap.conf @@ -0,0 +1,7 @@ +# Configuration for nova-rootwrap +# This file should be owned by (and only-writeable by) the root user + +[DEFAULT] +# List of directories to load filter definitions from (separated by ','). +# These directories MUST all be only writeable by root ! +filters_path=/etc/nova/rootwrap.d,/usr/share/nova/rootwrap diff --git a/etc/nova/rootwrap.d/compute.filters b/etc/nova/rootwrap.d/compute.filters new file mode 100644 index 000000000000..c2e760f0ee31 --- /dev/null +++ b/etc/nova/rootwrap.d/compute.filters @@ -0,0 +1,187 @@ +# nova-rootwrap command filters for compute nodes +# This file should be owned by (and only-writeable by) the root user + +[Filters] +# nova/virt/disk/mount.py: 'kpartx', '-a', device +# nova/virt/disk/mount.py: 'kpartx', '-d', device +kpartx: CommandFilter, /sbin/kpartx, root + +# nova/virt/disk/mount.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 +tune2fs: CommandFilter, /sbin/tune2fs, root + +# nova/virt/disk/mount.py: 'mount', mapped_device, mount_dir +# nova/virt/xenapi/vm_utils.py: 'mount', '-t', 'ext2,ext3,ext4,reiserfs'.. +mount: CommandFilter, /bin/mount, root + +# nova/virt/disk/mount.py: 'umount', mapped_device +# nova/virt/xenapi/vm_utils.py: 'umount', dev_path +umount: CommandFilter, /bin/umount, root + +# nova/virt/disk/nbd.py: 'qemu-nbd', '-c', device, image +# nova/virt/disk/nbd.py: 'qemu-nbd', '-d', device +qemu-nbd: CommandFilter, /usr/bin/qemu-nbd, root + +# nova/virt/disk/loop.py: 'losetup', '--find', '--show', image +# nova/virt/disk/loop.py: 'losetup', '--detach', device +losetup: CommandFilter, /sbin/losetup, root + +# nova/virt/disk/guestfs.py: 'guestmount', '--rw', '-a', image, '-i' +# nova/virt/disk/guestfs.py: 'guestmount', '--rw', '-a', image, '-m' dev +guestmount: CommandFilter, /usr/bin/guestmount, root + +# nova/virt/disk/guestfs.py: 'fusermount', 'u', mount_dir +fusermount: CommandFilter, /bin/fusermount, root +fusermount_usr: CommandFilter, /usr/bin/fusermount, root + +# nova/virt/disk/api.py: 'tee', metadata_path +# nova/virt/disk/api.py: 'tee', '-a', keyfile +# nova/virt/disk/api.py: 'tee', netfile +tee: CommandFilter, /usr/bin/tee, root + +# nova/virt/disk/api.py: 'mkdir', '-p', sshdir +# nova/virt/disk/api.py: 'mkdir', '-p', netdir +mkdir: CommandFilter, /bin/mkdir, root + +# nova/virt/disk/api.py: 'chown', 'root', sshdir +# nova/virt/disk/api.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/utils.py: 'chown', owner_uid, path +chown: CommandFilter, /bin/chown, root + +# nova/virt/disk/api.py: 'chmod', '700', sshdir +# nova/virt/disk/api.py: 'chmod', 755, netdir +chmod: CommandFilter, /bin/chmod, root + +# nova/virt/disk/api.py: 'cp', os.path.join(fs... +cp: CommandFilter, /bin/cp, 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' +ip: CommandFilter, /sbin/ip, root + +# nova/virt/libvirt/vif.py: 'tunctl', '-b', '-t', dev +# nova/network/linux_net.py: 'tunctl', '-b', '-t', dev +tunctl: CommandFilter, /bin/tunctl, root +tunctl_usr: 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', .... +ovs-vsctl: CommandFilter, /usr/bin/ovs-vsctl, root + +# nova/network/linux_net.py: 'ovs-ofctl', .... +ovs-ofctl: CommandFilter, /usr/bin/ovs-ofctl, root + +# nova/virt/libvirt/connection.py: 'dd', if=%s % virsh_output, ... +dd: CommandFilter, /bin/dd, root + +# nova/virt/xenapi/volume_utils.py: 'iscsiadm', '-m', ... +iscsiadm: CommandFilter, /sbin/iscsiadm, root + +# nova/virt/xenapi/vm_utils.py: parted, --script, ... +# nova/virt/xenapi/vm_utils.py: 'parted', '--script', dev_path, ..*. +parted: CommandFilter, /sbin/parted, root +parted_usr: CommandFilter, /usr/sbin/parted, root + +# nova/virt/xenapi/vm_utils.py: fdisk %(dev_path)s +fdisk: CommandFilter, /sbin/fdisk, root + +# nova/virt/xenapi/vm_utils.py: e2fsck, -f, -p, partition_path +e2fsck: CommandFilter, /sbin/e2fsck, root + +# nova/virt/xenapi/vm_utils.py: resize2fs, partition_path +resize2fs: CommandFilter, /sbin/resize2fs, root + +# nova/network/linux_net.py: 'ip[6]tables-save' % (cmd, '-t', ... +iptables-save: CommandFilter, /sbin/iptables-save, root +iptables-save_usr: CommandFilter, /usr/sbin/iptables-save, root +ip6tables-save: CommandFilter, /sbin/ip6tables-save, root +ip6tables-save_usr: CommandFilter, /usr/sbin/ip6tables-save, root + +# nova/network/linux_net.py: 'ip[6]tables-restore' % (cmd,) +iptables-restore: CommandFilter, /sbin/iptables-restore, root +iptables-restore_usr: CommandFilter, /usr/sbin/iptables-restore, root +ip6tables-restore: CommandFilter, /sbin/ip6tables-restore, root +ip6tables-restore_usr: CommandFilter, /usr/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'],.. +arping: CommandFilter, /usr/bin/arping, root +arping_sbin: CommandFilter, /sbin/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 +route: CommandFilter, /sbin/route, root + +# nova/network/linux_net.py: 'dhcp_release', dev, address, mac_address +dhcp_release: CommandFilter, /usr/bin/dhcp_release, root + +# nova/network/linux_net.py: 'kill', '-9', pid +# nova/network/linux_net.py: 'kill', '-HUP', pid +kill_dnsmasq: KillFilter, root, /usr/sbin/dnsmasq, -9, -HUP + +# nova/network/linux_net.py: 'kill', pid +kill_radvd: KillFilter, root, /usr/sbin/radvd + +# nova/network/linux_net.py: dnsmasq call +dnsmasq: DnsmasqFilter, /usr/sbin/dnsmasq, root + +# nova/network/linux_net.py: 'radvd', '-C', '%s' % _ra_file(dev, 'conf'.. +radvd: 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 +brctl: CommandFilter, /sbin/brctl, root +brctl_usr: CommandFilter, /usr/sbin/brctl, root + +# nova/virt/libvirt/utils.py: 'mkswap' +# nova/virt/xenapi/vm_utils.py: 'mkswap' +mkswap: CommandFilter, /sbin/mkswap, root + +# nova/virt/xenapi/vm_utils.py: 'mkfs' +mkfs: CommandFilter, /sbin/mkfs, root + +# nova/virt/libvirt/utils.py: 'qemu-img' +qemu-img: CommandFilter, /usr/bin/qemu-img, root + +# nova/virt/disk/api.py: 'touch', target +touch: CommandFilter, /usr/bin/touch, root + +# nova/virt/libvirt/connection.py: +read_initiator: ReadFileFilter, /etc/iscsi/initiatorname.iscsi + +# nova/virt/libvirt/connection.py: +lvremove: CommandFilter, /sbin/lvremove, root + +# nova/virt/libvirt/utils.py: +lvcreate: CommandFilter, /sbin/lvcreate, root + +# nova/virt/libvirt/utils.py: +vgs: CommandFilter, /sbin/vgs, root diff --git a/etc/nova/rootwrap.d/network.filters b/etc/nova/rootwrap.d/network.filters new file mode 100644 index 000000000000..c85ab9a33b0b --- /dev/null +++ b/etc/nova/rootwrap.d/network.filters @@ -0,0 +1,83 @@ +# nova-rootwrap command filters for network nodes +# This file should be owned by (and only-writeable by) the root user + +[Filters] +# 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' +ip: CommandFilter, /sbin/ip, root + +# nova/virt/libvirt/vif.py: 'ovs-vsctl', ... +# nova/virt/libvirt/vif.py: 'ovs-vsctl', 'del-port', ... +# nova/network/linux_net.py: 'ovs-vsctl', .... +ovs-vsctl: CommandFilter, /usr/bin/ovs-vsctl, root + +# nova/network/linux_net.py: 'ovs-ofctl', .... +ovs-ofctl: CommandFilter, /usr/bin/ovs-ofctl, root + +# nova/network/linux_net.py: 'ip[6]tables-save' % (cmd, '-t', ... +iptables-save: CommandFilter, /sbin/iptables-save, root +iptables-save_usr: CommandFilter, /usr/sbin/iptables-save, root +ip6tables-save: CommandFilter, /sbin/ip6tables-save, root +ip6tables-save_usr: CommandFilter, /usr/sbin/ip6tables-save, root + +# nova/network/linux_net.py: 'ip[6]tables-restore' % (cmd,) +iptables-restore: CommandFilter, /sbin/iptables-restore, root +iptables-restore_usr: CommandFilter, /usr/sbin/iptables-restore, root +ip6tables-restore: CommandFilter, /sbin/ip6tables-restore, root +ip6tables-restore_usr: CommandFilter, /usr/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'],.. +arping: CommandFilter, /usr/bin/arping, root +arping_sbin: CommandFilter, /sbin/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 +route: CommandFilter, /sbin/route, root + +# nova/network/linux_net.py: 'dhcp_release', dev, address, mac_address +dhcp_release: CommandFilter, /usr/bin/dhcp_release, root + +# nova/network/linux_net.py: 'kill', '-9', pid +# nova/network/linux_net.py: 'kill', '-HUP', pid +kill_dnsmasq: KillFilter, root, /usr/sbin/dnsmasq, -9, -HUP + +# nova/network/linux_net.py: 'kill', pid +kill_radvd: KillFilter, root, /usr/sbin/radvd + +# nova/network/linux_net.py: dnsmasq call +dnsmasq: DnsmasqFilter, /usr/sbin/dnsmasq, root + +# nova/network/linux_net.py: 'radvd', '-C', '%s' % _ra_file(dev, 'conf'.. +radvd: 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 +brctl: CommandFilter, /sbin/brctl, root +brctl_usr: CommandFilter, /usr/sbin/brctl, root + +# nova/network/linux_net.py: 'sysctl', .... +sysctl: CommandFilter, /sbin/sysctl, root diff --git a/etc/nova/rootwrap.d/volume.filters b/etc/nova/rootwrap.d/volume.filters new file mode 100644 index 000000000000..94a621b9823d --- /dev/null +++ b/etc/nova/rootwrap.d/volume.filters @@ -0,0 +1,27 @@ +# nova-rootwrap command filters for volume nodes +# This file should be owned by (and only-writeable by) the root user + +[Filters] +# nova/volume/iscsi.py: iscsi_helper '--op' ... +ietadm: CommandFilter, /usr/sbin/ietadm, root +tgtadm: CommandFilter, /usr/sbin/tgtadm, root + +# nova/volume/driver.py: 'vgs', '--noheadings', '-o', 'name' +vgs: CommandFilter, /sbin/vgs, root + +# nova/volume/driver.py: 'lvcreate', '-L', sizestr, '-n', volume_name,.. +# nova/volume/driver.py: 'lvcreate', '-L', ... +lvcreate: CommandFilter, /sbin/lvcreate, root + +# nova/volume/driver.py: 'dd', 'if=%s' % srcstr, 'of=%s' % deststr,... +dd: CommandFilter, /bin/dd, root + +# nova/volume/driver.py: 'lvremove', '-f', %s/%s % ... +lvremove: CommandFilter, /sbin/lvremove, root + +# nova/volume/driver.py: 'lvdisplay', '--noheading', '-C', '-o', 'Attr',.. +lvdisplay: CommandFilter, /sbin/lvdisplay, root + +# nova/volume/driver.py: 'iscsiadm', '-m', 'discovery', '-t',... +# nova/volume/driver.py: 'iscsiadm', '-m', 'node', '-T', ... +iscsiadm: CommandFilter, /sbin/iscsiadm, root diff --git a/nova/rootwrap/compute.py b/nova/rootwrap/compute.py deleted file mode 100644 index 382e4992640c..000000000000 --- a/nova/rootwrap/compute.py +++ /dev/null @@ -1,207 +0,0 @@ -# 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 import filters - - -filterlist = [ - # nova/virt/disk/mount.py: 'kpartx', '-a', device - # nova/virt/disk/mount.py: 'kpartx', '-d', device - filters.CommandFilter("/sbin/kpartx", "root"), - - # nova/virt/disk/mount.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 - filters.CommandFilter("/sbin/tune2fs", "root"), - - # nova/virt/disk/mount.py: 'mount', mapped_device, mount_dir - # nova/virt/xenapi/vm_utils.py: 'mount', '-t', 'ext2,ext3,ext4,reiserfs'.. - filters.CommandFilter("/bin/mount", "root"), - - # nova/virt/disk/mount.py: 'umount', mapped_device - # nova/virt/xenapi/vm_utils.py: 'umount', dev_path - filters.CommandFilter("/bin/umount", "root"), - - # nova/virt/disk/nbd.py: 'qemu-nbd', '-c', device, image - # nova/virt/disk/nbd.py: 'qemu-nbd', '-d', device - filters.CommandFilter("/usr/bin/qemu-nbd", "root"), - - # nova/virt/disk/loop.py: 'losetup', '--find', '--show', image - # nova/virt/disk/loop.py: 'losetup', '--detach', device - filters.CommandFilter("/sbin/losetup", "root"), - - # nova/virt/disk/guestfs.py: 'guestmount', '--rw', '-a', image, '-i' - # nova/virt/disk/guestfs.py: 'guestmount', '--rw', '-a', image, '-m' dev - filters.CommandFilter("/usr/bin/guestmount", "root"), - - # nova/virt/disk/guestfs.py: 'fusermount', 'u', mount_dir - filters.CommandFilter("/bin/fusermount", "root"), - filters.CommandFilter("/usr/bin/fusermount", "root"), - - # nova/virt/disk/api.py: 'tee', metadata_path - # nova/virt/disk/api.py: 'tee', '-a', keyfile - # nova/virt/disk/api.py: 'tee', netfile - filters.CommandFilter("/usr/bin/tee", "root"), - - # nova/virt/disk/api.py: 'mkdir', '-p', sshdir - # nova/virt/disk/api.py: 'mkdir', '-p', netdir - filters.CommandFilter("/bin/mkdir", "root"), - - # nova/virt/disk/api.py: 'chown', 'root', sshdir - # nova/virt/disk/api.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/utils.py: 'chown', owner_uid, path - filters.CommandFilter("/bin/chown", "root"), - - # nova/virt/disk/api.py: 'chmod', '700', sshdir - # nova/virt/disk/api.py: 'chmod', 755, netdir - filters.CommandFilter("/bin/chmod", "root"), - - # nova/virt/disk/api.py: 'cp', os.path.join(fs... - filters.CommandFilter("/bin/cp", "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' - filters.CommandFilter("/sbin/ip", "root"), - - # nova/virt/libvirt/vif.py: 'tunctl', '-b', '-t', dev - # nova/network/linux_net.py: 'tunctl', '-b', '-t', dev - filters.CommandFilter("/usr/sbin/tunctl", "root"), - filters.CommandFilter("/bin/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', .... - filters.CommandFilter("/usr/bin/ovs-vsctl", "root"), - - # nova/network/linux_net.py: 'ovs-ofctl', .... - filters.CommandFilter("/usr/bin/ovs-ofctl", "root"), - - # nova/virt/libvirt/connection.py: 'dd', "if=%s" % virsh_output, ... - filters.CommandFilter("/bin/dd", "root"), - - # nova/virt/xenapi/volume_utils.py: 'iscsiadm', '-m', ... - filters.CommandFilter("/sbin/iscsiadm", "root"), - - # nova/virt/xenapi/vm_utils.py: "parted", "--script", ... - # nova/virt/xenapi/vm_utils.py: 'parted', '--script', dev_path, ..*. - filters.CommandFilter("/sbin/parted", "root"), - filters.CommandFilter("/usr/sbin/parted", "root"), - - # nova/virt/xenapi/vm_utils.py: fdisk %(dev_path)s - filters.CommandFilter("/sbin/fdisk", "root"), - - # nova/virt/xenapi/vm_utils.py: "e2fsck", "-f", "-p", partition_path - filters.CommandFilter("/sbin/e2fsck", "root"), - - # nova/virt/xenapi/vm_utils.py: "resize2fs", partition_path - filters.CommandFilter("/sbin/resize2fs", "root"), - - # nova/network/linux_net.py: 'ip[6]tables-save' % (cmd,), '-t', ... - filters.CommandFilter("/sbin/iptables-save", "root"), - filters.CommandFilter("/usr/sbin/iptables-save", "root"), - filters.CommandFilter("/sbin/ip6tables-save", "root"), - filters.CommandFilter("/usr/sbin/ip6tables-save", "root"), - - # nova/network/linux_net.py: 'ip[6]tables-restore' % (cmd,) - filters.CommandFilter("/sbin/iptables-restore", "root"), - filters.CommandFilter("/usr/sbin/iptables-restore", "root"), - filters.CommandFilter("/sbin/ip6tables-restore", "root"), - filters.CommandFilter("/usr/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'],.. - filters.CommandFilter("/usr/bin/arping", "root"), - filters.CommandFilter("/sbin/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 - filters.CommandFilter("/sbin/route", "root"), - - # nova/network/linux_net.py: 'dhcp_release', dev, address, mac_address - filters.CommandFilter("/usr/bin/dhcp_release", "root"), - - # nova/network/linux_net.py: 'kill', '-9', pid - # nova/network/linux_net.py: 'kill', '-HUP', pid - filters.KillFilter("/bin/kill", "root", - ['-9', '-HUP'], ['/usr/sbin/dnsmasq']), - - # nova/network/linux_net.py: 'kill', pid - filters.KillFilter("/bin/kill", "root", [''], ['/usr/sbin/radvd']), - - # nova/network/linux_net.py: dnsmasq call - filters.DnsmasqFilter("/usr/sbin/dnsmasq", "root"), - - # nova/network/linux_net.py: 'radvd', '-C', '%s' % _ra_file(dev, 'conf'),.. - filters.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 - filters.CommandFilter("/sbin/brctl", "root"), - filters.CommandFilter("/usr/sbin/brctl", "root"), - - # nova/virt/libvirt/utils.py: 'mkswap' - # nova/virt/xenapi/vm_utils.py: 'mkswap' - filters.CommandFilter("/sbin/mkswap", "root"), - - # nova/virt/xenapi/vm_utils.py: 'mkfs' - filters.CommandFilter("/sbin/mkfs", "root"), - - # nova/virt/libvirt/utils.py: 'qemu-img' - filters.CommandFilter("/usr/bin/qemu-img", "root"), - - # nova/virt/disk/api.py: 'touch', target - filters.CommandFilter("/usr/bin/touch", "root"), - - # nova/virt/libvirt/connection.py: - filters.ReadFileFilter("/etc/iscsi/initiatorname.iscsi"), - - # nova/virt/libvirt/connection.py: - filters.CommandFilter("/sbin/lvremove", "root"), - - # nova/virt/libvirt/utils.py: - filters.CommandFilter("/sbin/lvcreate", "root"), - - # nova/virt/libvirt/utils.py: - filters.CommandFilter("/sbin/vgs", "root") - - ] diff --git a/nova/rootwrap/filters.py b/nova/rootwrap/filters.py index 6f78721198e2..fc130139f736 100644 --- a/nova/rootwrap/filters.py +++ b/nova/rootwrap/filters.py @@ -91,28 +91,33 @@ class DnsmasqFilter(CommandFilter): class KillFilter(CommandFilter): """Specific filter for the kill calls. - 1st argument is a list of accepted signals (emptystring means no signal) - 2nd argument is a list of accepted affected executables. + 1st argument is the user to run /bin/kill under + 2nd argument is the location of the affected executable + Subsequent arguments list the accepted signals (if any) This filter relies on /proc to accurately determine affected executable, so it will only work on procfs-capable systems (not OSX). """ + def __init__(self, *args): + super(KillFilter, self).__init__("/bin/kill", *args) + def match(self, userargs): if userargs[0] != "kill": return False args = list(userargs) if len(args) == 3: + # A specific signal is requested signal = args.pop(1) - if signal not in self.args[0]: + if signal not in self.args[1:]: # Requested signal not in accepted list return False else: if len(args) != 2: # Incorrect number of arguments return False - if '' not in self.args[0]: - # No signal, but list doesn't include empty string + if len(self.args) > 1: + # No signal requested, but filter requires specific signal return False try: command = os.readlink("/proc/%d/exe" % int(args[1])) @@ -120,8 +125,8 @@ class KillFilter(CommandFilter): # the end if an executable is updated or deleted if command.endswith(" (deleted)"): command = command[:command.rindex(" ")] - if command not in self.args[1]: - # Affected executable not in accepted list + if command != self.args[0]: + # Affected executable does not match return False except (ValueError, OSError): # Incorrect PID diff --git a/nova/rootwrap/network.py b/nova/rootwrap/network.py deleted file mode 100644 index 2d7ae1a2322b..000000000000 --- a/nova/rootwrap/network.py +++ /dev/null @@ -1,98 +0,0 @@ -# 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 import filters - - -filterlist = [ - # 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' - # nova/network/linux_net.py: 'ip', 'tuntap', 'add', dev, 'mode', 'tap' - filters.CommandFilter("/sbin/ip", "root"), - - # nova/network/linux_net.py: 'ip[6]tables-save' % (cmd,), '-t', ... - filters.CommandFilter("/sbin/iptables-save", "root"), - filters.CommandFilter("/usr/sbin/iptables-save", "root"), - filters.CommandFilter("/sbin/ip6tables-save", "root"), - filters.CommandFilter("/usr/sbin/ip6tables-save", "root"), - - # nova/network/linux_net.py: 'ip[6]tables-restore' % (cmd,) - filters.CommandFilter("/sbin/iptables-restore", "root"), - filters.CommandFilter("/usr/sbin/iptables-restore", "root"), - filters.CommandFilter("/sbin/ip6tables-restore", "root"), - filters.CommandFilter("/usr/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'],.. - filters.CommandFilter("/usr/bin/arping", "root"), - filters.CommandFilter("/sbin/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 - filters.CommandFilter("/sbin/route", "root"), - - # nova/network/linux_net.py: 'dhcp_release', dev, address, mac_address - filters.CommandFilter("/usr/bin/dhcp_release", "root"), - - # nova/network/linux_net.py: 'kill', '-9', pid - # nova/network/linux_net.py: 'kill', '-HUP', pid - filters.KillFilter("/bin/kill", "root", - ['-9', '-HUP'], ['/usr/sbin/dnsmasq']), - - # nova/network/linux_net.py: 'kill', pid - filters.KillFilter("/bin/kill", "root", [''], ['/usr/sbin/radvd']), - - # nova/network/linux_net.py: dnsmasq call - filters.DnsmasqFilter("/usr/sbin/dnsmasq", "root"), - - # nova/network/linux_net.py: 'radvd', '-C', '%s' % _ra_file(dev, 'conf'),.. - filters.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 - filters.CommandFilter("/sbin/brctl", "root"), - filters.CommandFilter("/usr/sbin/brctl", "root"), - - # nova/network/linux_net.py: 'ovs-vsctl', .... - filters.CommandFilter("/usr/bin/ovs-vsctl", "root"), - - # nova/network/linux_net.py: 'ovs-ofctl', .... - filters.CommandFilter("/usr/bin/ovs-ofctl", "root"), - - # nova/network/linux_net.py: 'sysctl', .... - filters.CommandFilter("/sbin/sysctl", "root"), - ] diff --git a/nova/rootwrap/volume.py b/nova/rootwrap/volume.py deleted file mode 100644 index e501b2dc1504..000000000000 --- a/nova/rootwrap/volume.py +++ /dev/null @@ -1,45 +0,0 @@ -# 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 import filters - - -filterlist = [ - # nova/volume/iscsi.py: iscsi_helper '--op' ... - filters.CommandFilter("/usr/sbin/ietadm", "root"), - filters.CommandFilter("/usr/sbin/tgtadm", "root"), - - # nova/volume/driver.py: 'vgs', '--noheadings', '-o', 'name' - filters.CommandFilter("/sbin/vgs", "root"), - - # nova/volume/driver.py: 'lvcreate', '-L', sizestr, '-n', volume_name,.. - # nova/volume/driver.py: 'lvcreate', '-L', ... - filters.CommandFilter("/sbin/lvcreate", "root"), - - # nova/volume/driver.py: 'dd', 'if=%s' % srcstr, 'of=%s' % deststr,... - filters.CommandFilter("/bin/dd", "root"), - - # nova/volume/driver.py: 'lvremove', '-f', "%s/%s" % ... - filters.CommandFilter("/sbin/lvremove", "root"), - - # nova/volume/driver.py: 'lvdisplay', '--noheading', '-C', '-o', 'Attr',.. - filters.CommandFilter("/sbin/lvdisplay", "root"), - - # nova/volume/driver.py: 'iscsiadm', '-m', 'discovery', '-t',... - # nova/volume/driver.py: 'iscsiadm', '-m', 'node', '-T', ... - filters.CommandFilter("/sbin/iscsiadm", "root"), - ] diff --git a/nova/rootwrap/wrapper.py b/nova/rootwrap/wrapper.py index c9259acab3bd..3dd7ee7e33b5 100644 --- a/nova/rootwrap/wrapper.py +++ b/nova/rootwrap/wrapper.py @@ -15,29 +15,39 @@ # License for the specific language governing permissions and limitations # under the License. + +import ConfigParser import os -import sys +import string + +from nova.rootwrap import filters -FILTERS_MODULES = ['nova.rootwrap.compute', - 'nova.rootwrap.network', - 'nova.rootwrap.volume', - ] +def build_filter(class_name, *args): + """Returns a filter object of class class_name""" + if not hasattr(filters, class_name): + # TODO(ttx): Log the error (whenever nova-rootwrap has a log file) + return None + filterclass = getattr(filters, class_name) + return filterclass(*args) -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.filterlist - 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 load_filters(filters_path): + """Load filters from a list of directories""" + filterlist = [] + for filterdir in filters_path: + if not os.path.isdir(filterdir): + continue + for filterfile in os.listdir(filterdir): + filterconfig = ConfigParser.RawConfigParser() + filterconfig.read(os.path.join(filterdir, filterfile)) + for (name, value) in filterconfig.items("Filters"): + filterdefinition = [string.strip(s) for s in value.split(',')] + newfilter = build_filter(*filterdefinition) + if newfilter is None: + continue + filterlist.append(newfilter) + return filterlist def match_filter(filters, userargs): diff --git a/nova/tests/test_nova_rootwrap.py b/nova/tests/test_nova_rootwrap.py index 4cd68184bbc1..2b6fba975c33 100644 --- a/nova/tests/test_nova_rootwrap.py +++ b/nova/tests/test_nova_rootwrap.py @@ -67,35 +67,33 @@ class RootwrapTestCase(test.TestCase): "Test requires /proc filesystem (procfs)") def test_KillFilter(self): p = subprocess.Popen(["/bin/sleep", "5"]) - f = filters.KillFilter("/bin/kill", "root", - ["-ALRM"], - ["/bin/sleep", "/usr/bin/sleep"]) - usercmd = ['kill', '-9', p.pid] + f = filters.KillFilter("root", "/bin/sleep", "-9", "-HUP") + f2 = filters.KillFilter("root", "/usr/bin/sleep", "-9", "-HUP") + usercmd = ['kill', '-ALRM', p.pid] # Incorrect signal should fail - self.assertFalse(f.match(usercmd)) + self.assertFalse(f.match(usercmd) or f2.match(usercmd)) usercmd = ['kill', p.pid] # Providing no signal should fail - self.assertFalse(f.match(usercmd)) + 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("/bin/kill", "root", - ["-9", ""], - ["/bin/sleep", "/usr/bin/sleep"]) - usercmd = ['kill', '-9', os.getpid()] + f = filters.KillFilter("root", "/bin/sleep") + f2 = filters.KillFilter("root", "/usr/bin/sleep") + usercmd = ['kill', os.getpid()] # Our own PID does not match /bin/sleep, so it should fail - self.assertFalse(f.match(usercmd)) - usercmd = ['kill', '-9', 999999] + self.assertFalse(f.match(usercmd) or f2.match(usercmd)) + usercmd = ['kill', 999999] # Nonexistant PID should fail - self.assertFalse(f.match(usercmd)) + self.assertFalse(f.match(usercmd) or f2.match(usercmd)) usercmd = ['kill', p.pid] # Providing no signal should work self.assertTrue(f.match(usercmd)) - usercmd = ['kill', '-9', p.pid] - # Providing -9 signal should work - self.assertTrue(f.match(usercmd)) def test_KillFilter_no_raise(self): """Makes sure ValueError from bug 926412 is gone""" - f = filters.KillFilter("/bin/kill", "root", [""]) + f = filters.KillFilter("root", "") # Providing anything other than kill should be False usercmd = ['notkill', 999999] self.assertFalse(f.match(usercmd)) @@ -109,9 +107,7 @@ class RootwrapTestCase(test.TestCase): def fake_readlink(blah): return '/bin/commandddddd (deleted)' - f = filters.KillFilter("/bin/kill", "root", - [""], - ["/bin/commandddddd"]) + f = filters.KillFilter("root", "/bin/commandddddd") usercmd = ['kill', 1234] # Providing no signal should work self.stubs.Set(os, 'readlink', fake_readlink)