summaryrefslogtreecommitdiffstats
path: root/nova/rootwrap
diff options
context:
space:
mode:
authorJenkins <jenkins@review.openstack.org>2011-12-08 14:51:29 +0000
committerGerrit Code Review <review@openstack.org>2011-12-08 14:51:29 +0000
commit4bdee9eb9627093c3f4d42431ed997a30af6a56c (patch)
tree31b6e01e51edae0c2459dd4cb98f1afa5b2270ff /nova/rootwrap
parent35782f24fa50b5b96072f9b81128f352098d50af (diff)
parent3c87de7c12c30d380e12b19dc0473d1e3bcfd233 (diff)
Merge "A more secure root-wrapper alternative"
Diffstat (limited to 'nova/rootwrap')
-rwxr-xr-xnova/rootwrap/__init__.py16
-rwxr-xr-xnova/rootwrap/compute.py159
-rwxr-xr-xnova/rootwrap/filters.py80
-rwxr-xr-xnova/rootwrap/network.py83
-rwxr-xr-xnova/rootwrap/volume.py48
-rwxr-xr-xnova/rootwrap/wrapper.py59
6 files changed, 445 insertions, 0 deletions
diff --git a/nova/rootwrap/__init__.py b/nova/rootwrap/__init__.py
new file mode 100755
index 000000000..d6bd39db6
--- /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 000000000..cd8521d08
--- /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 000000000..2932c5e1a
--- /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 000000000..a930176c6
--- /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 000000000..4ddce7f25
--- /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 000000000..2f24c0787
--- /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