Browse Source

Add root_helper to quantum agents.

When running commands that require root privileges, the linuxbridge,
openvswitch, and ryu agent now prepend the commands with the value of
the root_helper config variable. This is set to "sudo" in the plugins'
.ini files, allowing the agent to run as a non-root user with
appropriate sudo privilidges.

If root_helper is changed to "sudo quantum-rootwrap",
then the command being run will be filtered against lists of each
agent's valid commands in quantum/rootwrap. See
http://wiki.openstack.org/Packager/Rootwrap for details.

Fixes bug 948467.

Change-Id: I549515068a4ce8ae480905ec5eaab6257445d0c3
Signed-off-by: Bob Kukura <rkukura@redhat.com>
changes/05/189505/1
Bob Kukura 11 years ago
parent
commit
f97e0148c1
  1. 73
      bin/quantum-rootwrap
  2. 3
      etc/quantum/plugins/linuxbridge/linuxbridge_conf.ini
  3. 9
      etc/quantum/plugins/openvswitch/ovs_quantum_plugin.ini
  4. 5
      etc/quantum/plugins/ryu/ryu.ini
  5. 13
      quantum/plugins/linuxbridge/README
  6. 21
      quantum/plugins/linuxbridge/agent/linuxbridge_quantum_agent.py
  7. 13
      quantum/plugins/linuxbridge/tests/unit/_test_linuxbridgeAgent.py
  8. 30
      quantum/plugins/openvswitch/agent/ovs_quantum_agent.py
  9. 27
      quantum/plugins/openvswitch/tests/unit/test_tunnel.py
  10. 17
      quantum/plugins/ryu/agent/ryu_quantum_agent.py
  11. 16
      quantum/rootwrap/__init__.py
  12. 143
      quantum/rootwrap/filters.py
  13. 46
      quantum/rootwrap/linuxbridge-agent.py
  14. 36
      quantum/rootwrap/openvswitch-agent.py
  15. 31
      quantum/rootwrap/ryu-agent.py
  16. 63
      quantum/rootwrap/wrapper.py

73
bin/quantum-rootwrap

@ -0,0 +1,73 @@
#!/usr/bin/env python
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright (c) 2012 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 Quantum
Uses modules in quantum.rootwrap containing filters for commands
that quantum agents are allowed to run as another user.
To switch to using this, you should:
* Set "--root_helper=sudo quantum-rootwrap" in the agents config file.
* Allow quantum to run quantum-rootwrap as root in quantum_sudoers:
quantum ALL = (root) NOPASSWD: /usr/bin/quantum-rootwrap
(all other commands can be removed from this file)
To make allowed commands node-specific, your packaging should only
install quantum/rootwrap/quantum-*-agent.py on compute nodes where
agents that need root privileges are run.
"""
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, "quantum", "__init__.py")):
sys.path.insert(0, possible_topdir)
from quantum.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,
env=filtermatch.get_environment(userargs))
obj.wait()
sys.exit(obj.returncode)
print "Unauthorized command: %s" % ' '.join(userargs)
sys.exit(RC_UNAUTHORIZED)

3
etc/quantum/plugins/linuxbridge/linuxbridge_conf.ini

@ -22,3 +22,6 @@ physical_interface = eth1
[AGENT]
#agent's polling interval in seconds
polling_interval = 2
# Change to "sudo quantum-rootwrap" to limit commands that can be run
# as root.
root_helper = sudo

9
etc/quantum/plugins/openvswitch/ovs_quantum_plugin.ini

@ -31,6 +31,11 @@ integration-bridge = br-int
# Set local-ip to be the local IP address of this hypervisor.
# local-ip = 10.0.0.3
[AGENT]
# Change to "sudo quantum-rootwrap" to limit commands that can be run
# as root.
root_helper = sudo
#-----------------------------------------------------------------------------
# Sample Configurations.
#-----------------------------------------------------------------------------
@ -41,6 +46,8 @@ integration-bridge = br-int
# [OVS]
# enable-tunneling = False
# integration-bridge = br-int
# [AGENT]
# root_helper = sudo
#
# 2. With tunneling.
# [DATABASE]
@ -51,3 +58,5 @@ integration-bridge = br-int
# tunnel-bridge = br-tun
# remote-ip-file = /opt/stack/remote-ips.txt
# local-ip = 10.0.0.3
# [AGENT]
# root_helper = sudo

5
etc/quantum/plugins/ryu/ryu.ini

@ -11,3 +11,8 @@ integration-bridge = br-int
# openflow-rest-api = <host IP address of ofp rest api service>:<port: 8080>
openflow-controller = 127.0.0.1:6633
openflow-rest-api = 127.0.0.1:8080
[AGENT]
# Change to "sudo quantum-rootwrap" to limit commands that can be run
# as root.
root_helper = sudo

13
quantum/plugins/linuxbridge/README

@ -116,9 +116,20 @@ mysql> FLUSH PRIVILEGES;
to the compute node.
$ Run the following:
sudo python linuxbridge_quantum_agent.py linuxbridge_conf.ini
python linuxbridge_quantum_agent.py linuxbridge_conf.ini
(Use --verbose option to see the logs)
Note that the the user running the agent must have sudo priviliges
to run various networking commands. Also, the agent can be
configured to use quantum-rootwrap, limiting what commands it can
run via sudo. See http://wiki.openstack.org/Packager/Rootwrap for
details on rootwrap.
As an alternative to coping the agent python file, if quantum is
installed on the compute node, the agent can be run as
bin/quantum-linuxbridge-agent.
# -- Running Tests
(Note: The plugin ships with a default SQLite in-memory database configuration,

21
quantum/plugins/linuxbridge/agent/linuxbridge_quantum_agent.py

@ -30,6 +30,7 @@ import ConfigParser
import logging as LOG
import MySQLdb
import os
import shlex
import signal
import sqlite3
import sys
@ -53,16 +54,18 @@ DB_CONNECTION = None
class LinuxBridge:
def __init__(self, br_name_prefix, physical_interface):
def __init__(self, br_name_prefix, physical_interface, root_helper):
self.br_name_prefix = br_name_prefix
self.physical_interface = physical_interface
self.root_helper = root_helper
def run_cmd(self, args):
LOG.debug("Running command: " + " ".join(args))
p = Popen(args, stdout=PIPE)
cmd = shlex.split(self.root_helper) + args
LOG.debug("Running command: " + " ".join(cmd))
p = Popen(cmd, stdout=PIPE)
retval = p.communicate()[0]
if p.returncode == -(signal.SIGALRM):
LOG.debug("Timeout running command: " + " ".join(args))
LOG.debug("Timeout running command: " + " ".join(cmd))
if retval:
LOG.debug("Command returned: %s" % retval)
return retval
@ -287,12 +290,15 @@ class LinuxBridge:
class LinuxBridgeQuantumAgent:
def __init__(self, br_name_prefix, physical_interface, polling_interval):
def __init__(self, br_name_prefix, physical_interface, polling_interval,
root_helper):
self.polling_interval = int(polling_interval)
self.root_helper = root_helper
self.setup_linux_bridge(br_name_prefix, physical_interface)
def setup_linux_bridge(self, br_name_prefix, physical_interface):
self.linux_br = LinuxBridge(br_name_prefix, physical_interface)
self.linux_br = LinuxBridge(br_name_prefix, physical_interface,
self.root_helper)
def process_port_binding(self, port_id, network_id, interface_id,
vlan_id):
@ -439,6 +445,7 @@ def main():
br_name_prefix = BRIDGE_NAME_PREFIX
physical_interface = config.get("LINUX_BRIDGE", "physical_interface")
polling_interval = config.get("AGENT", "polling_interval")
root_helper = config.get("AGENT", "root_helper")
'Establish database connection and load models'
global DB_CONNECTION
DB_CONNECTION = config.get("DATABASE", "connection")
@ -462,7 +469,7 @@ def main():
try:
plugin = LinuxBridgeQuantumAgent(br_name_prefix, physical_interface,
polling_interval)
polling_interval, root_helper)
LOG.info("Agent initialized successfully, now running...")
plugin.daemon_loop(conn)
finally:

13
quantum/plugins/linuxbridge/tests/unit/_test_linuxbridgeAgent.py

@ -22,6 +22,7 @@ import logging as LOG
import unittest
import sys
import os
import shlex
import signal
from subprocess import *
@ -392,20 +393,24 @@ class LinuxBridgeAgentTest(unittest.TestCase):
self.physical_interface = config.get("LINUX_BRIDGE",
"physical_interface")
self.polling_interval = config.get("AGENT", "polling_interval")
self.root_helper = config.get("AGENT", "root_helper")
except Exception, e:
LOG.error("Unable to parse config file \"%s\": \nException%s"
% (self.config_file, str(e)))
sys.exit(1)
self._linuxbridge = linux_agent.LinuxBridge(self.br_name_prefix,
self.physical_interface)
self.physical_interface,
self.root_helper)
self._linuxbridge_quantum_agent = linux_agent.LinuxBridgeQuantumAgent(
self.br_name_prefix,
self.physical_interface,
self.polling_interval)
self.polling_interval,
self.root_helper)
def run_cmd(self, args):
LOG.debug("Running command: " + " ".join(args))
p = Popen(args, stdout=PIPE)
cmd = shlex.split(self.root_helper) + args
LOG.debug("Running command: " + " ".join(cmd))
p = Popen(cmd, stdout=PIPE)
retval = p.communicate()[0]
if p.returncode == -(signal.SIGALRM):
LOG.debug("Timeout running command: " + " ".join(args))

30
quantum/plugins/openvswitch/agent/ovs_quantum_agent.py

@ -21,6 +21,7 @@
import ConfigParser
import logging as LOG
import shlex
import sys
import time
import signal
@ -57,15 +58,17 @@ class VifPort:
class OVSBridge:
def __init__(self, br_name):
def __init__(self, br_name, root_helper):
self.br_name = br_name
self.root_helper = root_helper
def run_cmd(self, args):
# LOG.debug("## running command: " + " ".join(args))
p = Popen(args, stdout=PIPE)
cmd = shlex.split(self.root_helper) + args
LOG.debug("## running command: " + " ".join(cmd))
p = Popen(cmd, stdout=PIPE)
retval = p.communicate()[0]
if p.returncode == -(signal.SIGALRM):
LOG.debug("## timeout running command: " + " ".join(args))
LOG.debug("## timeout running command: " + " ".join(cmd))
return retval
def run_vsctl(self, args):
@ -207,7 +210,8 @@ class LocalVLANMapping:
class OVSQuantumAgent(object):
def __init__(self, integ_br):
def __init__(self, integ_br, root_helper):
self.root_helper = root_helper
self.setup_integration_br(integ_br)
def port_bound(self, port, vlan_id):
@ -220,7 +224,7 @@ class OVSQuantumAgent(object):
self.int_br.clear_db_attribute("Port", port.port_name, "tag")
def setup_integration_br(self, integ_br):
self.int_br = OVSBridge(integ_br)
self.int_br = OVSBridge(integ_br, self.root_helper)
self.int_br.remove_all_flows()
# switch all traffic using L2 learning
self.int_br.add_flow(priority=1, actions="normal")
@ -323,13 +327,15 @@ class OVSQuantumTunnelAgent(object):
# Upper bound on available vlans.
MAX_VLAN_TAG = 4094
def __init__(self, integ_br, tun_br, remote_ip_file, local_ip):
def __init__(self, integ_br, tun_br, remote_ip_file, local_ip,
root_helper):
'''Constructor.
:param integ_br: name of the integration bridge.
:param tun_br: name of the tunnel bridge.
:param remote_ip_file: name of file containing list of hypervisor IPs.
:param local_ip: local IP address of this hypervisor.'''
self.root_helper = root_helper
self.available_local_vlans = set(
xrange(OVSQuantumTunnelAgent.MIN_VLAN_TAG,
OVSQuantumTunnelAgent.MAX_VLAN_TAG))
@ -423,7 +429,7 @@ class OVSQuantumTunnelAgent(object):
Create patch ports and remove all existing flows.
:param integ_br: the name of the integration bridge.'''
self.int_br = OVSBridge(integ_br)
self.int_br = OVSBridge(integ_br, self.root_helper)
self.int_br.delete_port("patch-tun")
self.patch_tun_ofport = self.int_br.add_patch_port("patch-tun",
"patch-int")
@ -442,7 +448,7 @@ class OVSQuantumTunnelAgent(object):
:param remote_ip_file: path to file that contains list of destination
IP addresses.
:param local_ip: the ip address of this node.'''
self.tun_br = OVSBridge(tun_br)
self.tun_br = OVSBridge(tun_br, self.root_helper)
self.tun_br.reset_bridge()
self.patch_int_ofport = self.tun_br.add_patch_port("patch-int",
"patch-tun")
@ -630,6 +636,8 @@ def main():
if not len(db_connection_url):
raise Exception('Empty db_connection_url in configuration file.')
root_helper = config.get("AGENT", "root_helper")
except Exception, e:
LOG.error("Error parsing common params in config_file: '%s': %s"
% (config_file, str(e)))
@ -659,10 +667,10 @@ def main():
sys.exit(1)
plugin = OVSQuantumTunnelAgent(integ_br, tun_br, remote_ip_file,
local_ip)
local_ip, root_helper)
else:
# Get parameters for OVSQuantumAgent.
plugin = OVSQuantumAgent(integ_br)
plugin = OVSQuantumAgent(integ_br, root_helper)
# Start everything.
options = {"sql_connection": db_connection_url}

27
quantum/plugins/openvswitch/tests/unit/test_tunnel.py

@ -63,14 +63,16 @@ class TunnelTest(unittest.TestCase):
self.TUN_OFPORT = 'PATCH_TUN_OFPORT'
self.mox.StubOutClassWithMocks(ovs_quantum_agent, 'OVSBridge')
self.mock_int_bridge = ovs_quantum_agent.OVSBridge(self.INT_BRIDGE)
self.mock_int_bridge = ovs_quantum_agent.OVSBridge(self.INT_BRIDGE,
'sudo')
self.mock_int_bridge.delete_port('patch-tun')
self.mock_int_bridge.add_patch_port(
'patch-tun', 'patch-int').AndReturn(self.TUN_OFPORT)
self.mock_int_bridge.remove_all_flows()
self.mock_int_bridge.add_flow(priority=1, actions='normal')
self.mock_tun_bridge = ovs_quantum_agent.OVSBridge(self.TUN_BRIDGE)
self.mock_tun_bridge = ovs_quantum_agent.OVSBridge(self.TUN_BRIDGE,
'sudo')
self.mock_tun_bridge.reset_bridge()
self.mock_tun_bridge.add_patch_port(
'patch-int', 'patch-tun').AndReturn(self.INT_OFPORT)
@ -86,7 +88,8 @@ class TunnelTest(unittest.TestCase):
b = ovs_quantum_agent.OVSQuantumTunnelAgent(self.INT_BRIDGE,
self.TUN_BRIDGE,
REMOTE_IP_FILE,
'10.0.0.1')
'10.0.0.1',
'sudo')
self.mox.VerifyAll()
def testProvisionLocalVlan(self):
@ -105,7 +108,8 @@ class TunnelTest(unittest.TestCase):
a = ovs_quantum_agent.OVSQuantumTunnelAgent(self.INT_BRIDGE,
self.TUN_BRIDGE,
REMOTE_IP_FILE,
'10.0.0.1')
'10.0.0.1',
'sudo')
a.available_local_vlans = set([LV_ID])
a.provision_local_vlan(NET_UUID, LS_ID)
self.mox.VerifyAll()
@ -121,7 +125,8 @@ class TunnelTest(unittest.TestCase):
a = ovs_quantum_agent.OVSQuantumTunnelAgent(self.INT_BRIDGE,
self.TUN_BRIDGE,
REMOTE_IP_FILE,
'10.0.0.1')
'10.0.0.1',
'sudo')
a.available_local_vlans = set()
a.local_vlan_map[NET_UUID] = LVM
a.reclaim_local_vlan(NET_UUID, LVM)
@ -137,7 +142,8 @@ class TunnelTest(unittest.TestCase):
a = ovs_quantum_agent.OVSQuantumTunnelAgent(self.INT_BRIDGE,
self.TUN_BRIDGE,
REMOTE_IP_FILE,
'10.0.0.1')
'10.0.0.1',
'sudo')
a.local_vlan_map[NET_UUID] = LVM
a.port_bound(VIF_PORT, NET_UUID, LS_ID)
self.mox.VerifyAll()
@ -147,7 +153,8 @@ class TunnelTest(unittest.TestCase):
a = ovs_quantum_agent.OVSQuantumTunnelAgent(self.INT_BRIDGE,
self.TUN_BRIDGE,
REMOTE_IP_FILE,
'10.0.0.1')
'10.0.0.1',
'sudo')
a.available_local_vlans = set([LV_ID])
a.local_vlan_map[NET_UUID] = LVM
a.port_unbound(VIF_PORT, NET_UUID)
@ -165,7 +172,8 @@ class TunnelTest(unittest.TestCase):
a = ovs_quantum_agent.OVSQuantumTunnelAgent(self.INT_BRIDGE,
self.TUN_BRIDGE,
REMOTE_IP_FILE,
'10.0.0.1')
'10.0.0.1',
'sudo')
a.available_local_vlans = set([LV_ID])
a.local_vlan_map[NET_UUID] = LVM
a.port_dead(VIF_PORT)
@ -187,7 +195,8 @@ class TunnelTest(unittest.TestCase):
a = ovs_quantum_agent.OVSQuantumTunnelAgent(self.INT_BRIDGE,
self.TUN_BRIDGE,
REMOTE_IP_FILE,
'10.0.0.1')
'10.0.0.1',
'sudo')
all_bindings = a.get_db_port_bindings(db)
lsw_id_bindings = a.get_db_vlan_bindings(db)

17
quantum/plugins/ryu/agent/ryu_quantum_agent.py

@ -58,8 +58,9 @@ class VifPort:
class OVSBridge:
def __init__(self, br_name):
def __init__(self, br_name, root_helper):
self.br_name = br_name
self.root_helper = root_helper
self.datapath_id = None
def find_datapath_id(self):
@ -71,10 +72,11 @@ class OVSBridge:
self.datapath_id = dp_id
def run_cmd(self, args):
pipe = Popen(args, stdout=PIPE)
cmd = shlex.split(self.root_helper) + args
pipe = Popen(cmd, stdout=PIPE)
retval = pipe.communicate()[0]
if pipe.returncode == -(signal.SIGALRM):
LOG.debug("## timeout running command: " + " ".join(args))
LOG.debug("## timeout running command: " + " ".join(cmd))
return retval
def run_vsctl(self, args):
@ -190,7 +192,8 @@ def check_ofp_mode(db):
class OVSQuantumOFPRyuAgent:
def __init__(self, integ_br, db):
def __init__(self, integ_br, db, root_helper):
self.root_helper = root_helper
(ofp_controller_addr, ofp_rest_api_addr) = check_ofp_mode(db)
self.nw_id_external = rest_nw_id.NW_ID_EXTERNAL
@ -198,7 +201,7 @@ class OVSQuantumOFPRyuAgent:
self._setup_integration_br(integ_br, ofp_controller_addr)
def _setup_integration_br(self, integ_br, ofp_controller_addr):
self.int_br = OVSBridge(integ_br)
self.int_br = OVSBridge(integ_br, self.root_helper)
self.int_br.find_datapath_id()
self.int_br.set_controller(ofp_controller_addr)
for port in self.int_br.get_external_ports():
@ -297,12 +300,14 @@ def main():
integ_br = config.get("OVS", "integration-bridge")
root_helper = config.get("AGENT", "root_helper")
options = {"sql_connection": config.get("DATABASE", "sql_connection")}
db = SqlSoup(options["sql_connection"])
LOG.info("Connecting to database \"%s\" on %s",
db.engine.url.database, db.engine.url.host)
plugin = OVSQuantumOFPRyuAgent(integ_br, db)
plugin = OVSQuantumOFPRyuAgent(integ_br, db, root_helper)
plugin.daemon_loop(db)
sys.exit(0)

16
quantum/rootwrap/__init__.py

@ -0,0 +1,16 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright (c) 2012 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.

143
quantum/rootwrap/filters.py

@ -0,0 +1,143 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright (c) 2012 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:]
def get_environment(self, userargs):
"""Returns specific environment to set, None if none"""
return None
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 [self.exec_path] + userargs[3:]
def get_environment(self, userargs):
env = os.environ.copy()
env['FLAGFILE'] = userargs[0].split('=')[-1]
env['NETWORK_ID'] = userargs[1].split('=')[-1]
return env
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.
This filter relies on /proc to accurately determine affected
executable, so it will only work on procfs-capable systems (not OSX).
"""
def match(self, userargs):
if userargs[0] != "kill":
return False
args = list(userargs)
if len(args) == 3:
signal = args.pop(1)
if signal not in self.args[0]:
# 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
return False
try:
command = os.readlink("/proc/%d/exe" % int(args[1]))
if command not in self.args[1]:
# Affected executable not in accepted list
return False
except (ValueError, OSError):
# Incorrect PID
return False
return True
class ReadFileFilter(CommandFilter):
"""Specific filter for the utils.read_file_as_root call"""
def __init__(self, file_path, *args):
self.file_path = file_path
super(ReadFileFilter, self).__init__("/bin/cat", "root", *args)
def match(self, userargs):
if userargs[0] != 'cat':
return False
if userargs[1] != self.file_path:
return False
if len(userargs) != 2:
return False
return True

46
quantum/rootwrap/linuxbridge-agent.py

@ -0,0 +1,46 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright (c) 2012 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 quantum.rootwrap import filters
filterlist = [
# quantum/plugins/linuxbridge/agent/linuxbridge_quantum_agent.py:
# 'brctl', 'addbr', bridge_name
# 'brctl', 'addif', bridge_name, interface
# 'brctl', 'addif', bridge_name, tap_device_name
# 'brctl', 'delbr', bridge_name
# 'brctl', 'delif', bridge_name, interface_name
# 'brctl', 'delif', current_bridge_name, ...
# 'brctl', 'setfd', bridge_name, ...
# 'brctl', 'stp', bridge_name, 'off'
filters.CommandFilter("/usr/sbin/brctl", "root"),
filters.CommandFilter("/sbin/brctl", "root"),
# quantum/plugins/linuxbridge/agent/linuxbridge_quantum_agent.py:
# 'ip', 'link', 'add', 'link', ...
# 'ip', 'link', 'delete', interface
# 'ip', 'link', 'set', bridge_name, 'down'
# 'ip', 'link', 'set', bridge_name, 'up'
# 'ip', 'link', 'set', interface, 'down'
# 'ip', 'link', 'set', interface, 'up'
# 'ip', 'link', 'show', 'dev', device
# 'ip', 'tuntap'
# 'ip', 'tuntap'
filters.CommandFilter("/usr/sbin/ip", "root"),
filters.CommandFilter("/sbin/ip", "root"),
]

36
quantum/rootwrap/openvswitch-agent.py

@ -0,0 +1,36 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright (c) 2012 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 quantum.rootwrap import filters
filterlist = [
# quantum/plugins/openvswitch/agent/ovs_quantum_agent.py:
# "ovs-vsctl", "--timeout=2", ...
filters.CommandFilter("/usr/bin/ovs-vsctl", "root"),
filters.CommandFilter("/bin/ovs-vsctl", "root"),
# quantum/plugins/openvswitch/agent/ovs_quantum_agent.py:
# "ovs-ofctl", cmd, self.br_name, args
filters.CommandFilter("/usr/bin/ovs-ofctl", "root"),
filters.CommandFilter("/bin/ovs-ofctl", "root"),
# quantum/plugins/openvswitch/agent/ovs_quantum_agent.py:
# "xe", "vif-param-get", ...
filters.CommandFilter("/usr/bin/xe", "root"),
filters.CommandFilter("/usr/sbin/xe", "root"),
]

31
quantum/rootwrap/ryu-agent.py

@ -0,0 +1,31 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright (c) 2012 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 quantum.rootwrap import filters
filterlist = [
# quantum/plugins/ryu/agent/ryu_quantum_agent.py:
# "ovs-vsctl", "--timeout=2", ...
filters.CommandFilter("/usr/bin/ovs-vsctl", "root"),
filters.CommandFilter("/bin/ovs-vsctl", "root"),
# quantum/plugins/ryu/agent/ryu_quantum_agent.py:
# "xe", "vif-param-get", ...
filters.CommandFilter("/usr/bin/xe", "root"),
filters.CommandFilter("/usr/sbin/xe", "root"),
]

63
quantum/rootwrap/wrapper.py

@ -0,0 +1,63 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright (c) 2012 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 = ['quantum.rootwrap.linuxbridge-agent',
'quantum.rootwrap.openvswitch-agent',
'quantum.rootwrap.ryu-agent',
]
def load_filters():
"""Load filters from modules present in quantum.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
# may be shipped with specific nodes
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.
"""
found_filter = None
for f in filters:
if f.match(userargs):
# Try other filters if executable is absent
if not os.access(f.exec_path, os.X_OK):
if not found_filter:
found_filter = f
continue
# Otherwise return matching filter for execution
return f
# No filter matched or first missing executable
return found_filter
Loading…
Cancel
Save