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:
parent
6fdb709302
commit
1273f2c1fd
72
bin/nova-rootwrap
Executable file
72
bin/nova-rootwrap
Executable 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
16
nova/rootwrap/__init__.py
Executable 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
159
nova/rootwrap/compute.py
Executable 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
80
nova/rootwrap/filters.py
Executable 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
83
nova/rootwrap/network.py
Executable 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
48
nova/rootwrap/volume.py
Executable 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
59
nova/rootwrap/wrapper.py
Executable 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
|
65
nova/tests/test_nova_rootwrap.py
Normal file
65
nova/tests/test_nova_rootwrap.py
Normal 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])
|
Loading…
Reference in New Issue
Block a user