linux_bridge: convert over to use privsep module

Instead of relying on root wrap, convert the Linux bridge
network and iptables code to use the privsep module to
run privileged actions.

Change-Id: Ief902f63c49e4529bae43c619c1284fe51c90a90
This commit is contained in:
Daniel P. Berrange 2016-03-02 11:44:57 +00:00
parent 76994a02d4
commit bf845feb11
4 changed files with 112 additions and 58 deletions

View File

@ -7,6 +7,7 @@ netaddr>=0.7.12,!=0.7.16
oslo.config>=3.4.0 # Apache-2.0 oslo.config>=3.4.0 # Apache-2.0
oslo.log>=1.14.0 # Apache-2.0 oslo.log>=1.14.0 # Apache-2.0
oslo.i18n>=1.5.0 # Apache-2.0 oslo.i18n>=1.5.0 # Apache-2.0
oslo.privsep>=1.3.0 # Apache-2.0
oslo.versionedobjects>=0.13.0 oslo.versionedobjects>=0.13.0
six>=1.9.0 six>=1.9.0
stevedore>=1.5.0 # Apache-2.0 stevedore>=1.5.0 # Apache-2.0

View File

@ -26,6 +26,7 @@ import re
from oslo_concurrency import lockutils from oslo_concurrency import lockutils
from oslo_concurrency import processutils from oslo_concurrency import processutils
from vif_plug_linux_bridge import privsep
import six import six
@ -41,6 +42,32 @@ def get_binary_name():
binary_name = get_binary_name() binary_name = get_binary_name()
@privsep.vif_plug.entrypoint
def iptables_save():
return processutils.execute('iptables-save',
'-c', attempts=5)
@privsep.vif_plug.entrypoint
def ip6tables_save():
return processutils.execute('ip6tables-save',
'-c', attempts=5)
@privsep.vif_plug.entrypoint
def iptables_restore(input):
return processutils.execute('iptables-restore',
'-c', attempts=5,
process_input=input)
@privsep.vif_plug.entrypoint
def ip6tables_restore(input):
return processutils.execute('ip6tables-restore',
'-c', attempts=5,
process_input=input)
class IptablesRule(object): class IptablesRule(object):
"""An iptables rule. """An iptables rule.
@ -330,23 +357,19 @@ class IptablesManager(object):
rules. This happens atomically, thanks to iptables-restore. rules. This happens atomically, thanks to iptables-restore.
""" """
s = [('iptables', self.ipv4)] s = [(iptables_save, iptables_restore, self.ipv4)]
if self.use_ipv6: if self.use_ipv6:
s += [('ip6tables', self.ipv6)] s += [(ip6tables_save, ip6tables_restore, self.ipv6)]
for cmd, tables in s: for save, restore, tables in s:
all_tables, _err = processutils.execute('%s-save' % (cmd,), all_tables, _err = save()
'-c', attempts=5,
run_as_root=True)
all_lines = all_tables.split('\n') all_lines = all_tables.split('\n')
for table_name, table in six.iteritems(tables): for table_name, table in six.iteritems(tables):
start, end = self._find_table(all_lines, table_name) start, end = self._find_table(all_lines, table_name)
all_lines[start:end] = self._modify_rules( all_lines[start:end] = self._modify_rules(
all_lines[start:end], table, table_name) all_lines[start:end], table, table_name)
table.dirty = False table.dirty = False
processutils.execute('%s-restore' % (cmd,), '-c', restore('\n'.join(all_lines))
process_input='\n'.join(all_lines),
attempts=5, run_as_root=True)
def _find_table(self, lines, table_name): def _find_table(self, lines, table_name):
if len(lines) < 3: if len(lines) < 3:

View File

@ -25,6 +25,8 @@ from oslo_concurrency import lockutils
from oslo_concurrency import processutils from oslo_concurrency import processutils
from oslo_log import log as logging from oslo_log import log as logging
from vif_plug_linux_bridge import privsep
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
_IPTABLES_MANAGER = None _IPTABLES_MANAGER = None
@ -48,36 +50,40 @@ def _ip_bridge_cmd(action, params, device):
return cmd return cmd
@privsep.vif_plug.entrypoint
def ensure_vlan_bridge(vlan_num, bridge, bridge_interface, def ensure_vlan_bridge(vlan_num, bridge, bridge_interface,
net_attrs=None, mac_address=None, net_attrs=None, mac_address=None,
mtu=None): mtu=None):
"""Create a vlan and bridge unless they already exist.""" """Create a vlan and bridge unless they already exist."""
interface = ensure_vlan(vlan_num, bridge_interface, mac_address, mtu=mtu) interface = _ensure_vlan_privileged(vlan_num, bridge_interface,
ensure_bridge(bridge, interface, net_attrs) mac_address, mtu=mtu)
_ensure_bridge_privileged(bridge, interface, net_attrs)
_ensure_bridge_filtering(bridge, None)
return interface return interface
@lockutils.synchronized('nova-lock_vlan', external=True) @lockutils.synchronized('nova-lock_vlan', external=True)
def ensure_vlan(vlan_num, bridge_interface, mac_address=None, mtu=None): def _ensure_vlan_privileged(vlan_num, bridge_interface, mac_address, mtu):
"""Create a vlan unless it already exists.""" """Create a vlan unless it already exists.
This assumes the caller is already annotated to run
with elevated privileges.
"""
interface = 'vlan%s' % vlan_num interface = 'vlan%s' % vlan_num
if not device_exists(interface): if not device_exists(interface):
LOG.debug('Starting VLAN interface %s', interface) LOG.debug('Starting VLAN interface %s', interface)
processutils.execute('ip', 'link', 'add', 'link', processutils.execute('ip', 'link', 'add', 'link',
bridge_interface, 'name', interface, 'type', bridge_interface, 'name', interface, 'type',
'vlan', 'id', vlan_num, 'vlan', 'id', vlan_num,
check_exit_code=[0, 2, 254], check_exit_code=[0, 2, 254])
run_as_root=True)
# (danwent) the bridge will inherit this address, so we want to # (danwent) the bridge will inherit this address, so we want to
# make sure it is the value set from the NetworkManager # make sure it is the value set from the NetworkManager
if mac_address: if mac_address:
processutils.execute('ip', 'link', 'set', interface, processutils.execute('ip', 'link', 'set', interface,
'address', mac_address, 'address', mac_address,
check_exit_code=[0, 2, 254], check_exit_code=[0, 2, 254])
run_as_root=True)
processutils.execute('ip', 'link', 'set', interface, 'up', processutils.execute('ip', 'link', 'set', interface, 'up',
check_exit_code=[0, 2, 254], check_exit_code=[0, 2, 254])
run_as_root=True)
# NOTE(vish): set mtu every time to ensure that changes to mtu get # NOTE(vish): set mtu every time to ensure that changes to mtu get
# propogated # propogated
_set_device_mtu(interface, mtu) _set_device_mtu(interface, mtu)
@ -87,6 +93,14 @@ def ensure_vlan(vlan_num, bridge_interface, mac_address=None, mtu=None):
@lockutils.synchronized('nova-lock_bridge', external=True) @lockutils.synchronized('nova-lock_bridge', external=True)
def ensure_bridge(bridge, interface, net_attrs=None, gateway=True, def ensure_bridge(bridge, interface, net_attrs=None, gateway=True,
filtering=True): filtering=True):
_ensure_bridge_privileged(bridge, interface, net_attrs, gateway, filtering)
if filtering:
_ensure_bridge_filtering(bridge, gateway)
@privsep.vif_plug.entrypoint
def _ensure_bridge_privileged(bridge, interface, net_attrs, gateway,
filtering=True):
"""Create a bridge unless it already exists. """Create a bridge unless it already exists.
:param interface: the interface to create the bridge on. :param interface: the interface to create the bridge on.
@ -104,35 +118,28 @@ def ensure_bridge(bridge, interface, net_attrs=None, gateway=True,
""" """
if not device_exists(bridge): if not device_exists(bridge):
LOG.debug('Starting Bridge %s', bridge) LOG.debug('Starting Bridge %s', bridge)
processutils.execute('brctl', 'addbr', bridge, processutils.execute('brctl', 'addbr', bridge)
run_as_root=True) processutils.execute('brctl', 'setfd', bridge, 0)
processutils.execute('brctl', 'setfd', bridge, 0, # processutils.execute('brctl setageing %s 10' % bridge)
run_as_root=True) processutils.execute('brctl', 'stp', bridge, 'off')
# processutils.execute('brctl setageing %s 10' % bridge,
# run_as_root=True)
processutils.execute('brctl', 'stp', bridge, 'off',
run_as_root=True)
# (danwent) bridge device MAC address can't be set directly. # (danwent) bridge device MAC address can't be set directly.
# instead it inherits the MAC address of the first device on the # instead it inherits the MAC address of the first device on the
# bridge, which will either be the vlan interface, or a # bridge, which will either be the vlan interface, or a
# physical NIC. # physical NIC.
processutils.execute('ip', 'link', 'set', bridge, 'up', processutils.execute('ip', 'link', 'set', bridge, 'up')
run_as_root=True)
if interface: if interface:
LOG.debug('Adding interface %(interface)s to bridge %(bridge)s', LOG.debug('Adding interface %(interface)s to bridge %(bridge)s',
{'interface': interface, 'bridge': bridge}) {'interface': interface, 'bridge': bridge})
out, err = processutils.execute('brctl', 'addif', bridge, out, err = processutils.execute('brctl', 'addif', bridge,
interface, check_exit_code=False, interface, check_exit_code=False)
run_as_root=True)
if (err and err != "device %s is already a member of a bridge; " if (err and err != "device %s is already a member of a bridge; "
"can't enslave it to bridge %s.\n" % (interface, bridge)): "can't enslave it to bridge %s.\n" % (interface, bridge)):
msg = _('Failed to add interface: %s') % err msg = _('Failed to add interface: %s') % err
raise Exception(msg) raise Exception(msg)
out, err = processutils.execute('ip', 'link', 'set', out, err = processutils.execute('ip', 'link', 'set',
interface, 'up', check_exit_code=False, interface, 'up', check_exit_code=False)
run_as_root=True)
# NOTE(vish): This will break if there is already an ip on the # NOTE(vish): This will break if there is already an ip on the
# interface, so we move any ips to the bridge # interface, so we move any ips to the bridge
@ -145,8 +152,7 @@ def ensure_bridge(bridge, interface, net_attrs=None, gateway=True,
fields = line.split() fields = line.split()
if fields and 'via' in fields: if fields and 'via' in fields:
old_routes.append(fields) old_routes.append(fields)
processutils.execute('ip', 'route', 'del', *fields, processutils.execute('ip', 'route', 'del', *fields)
run_as_root=True)
out, err = processutils.execute('ip', 'addr', 'show', 'dev', interface, out, err = processutils.execute('ip', 'addr', 'show', 'dev', interface,
'scope', 'global') 'scope', 'global')
for line in out.split('\n'): for line in out.split('\n'):
@ -158,33 +164,33 @@ def ensure_bridge(bridge, interface, net_attrs=None, gateway=True,
params = fields[1:-1] params = fields[1:-1]
processutils.execute(*_ip_bridge_cmd('del', params, processutils.execute(*_ip_bridge_cmd('del', params,
fields[-1]), fields[-1]),
check_exit_code=[0, 2, 254], check_exit_code=[0, 2, 254])
run_as_root=True)
processutils.execute(*_ip_bridge_cmd('add', params, processutils.execute(*_ip_bridge_cmd('add', params,
bridge), bridge),
check_exit_code=[0, 2, 254], check_exit_code=[0, 2, 254])
run_as_root=True)
for fields in old_routes: for fields in old_routes:
processutils.execute('ip', 'route', 'add', *fields, processutils.execute('ip', 'route', 'add', *fields)
run_as_root=True)
if filtering:
# Don't forward traffic unless we were told to be a gateway def _ensure_bridge_filtering(bridge, gateway):
global _IPTABLES_MANAGER # This method leaves privsep usage to iptables manager
ipv4_filter = _IPTABLES_MANAGER.ipv4['filter'] # Don't forward traffic unless we were told to be a gateway
if gateway: LOG.debug("ENsuring filtering %s to %s" % (bridge, gateway))
for rule in _IPTABLES_MANAGER.get_gateway_rules(bridge): global _IPTABLES_MANAGER
ipv4_filter.add_rule(*rule) ipv4_filter = _IPTABLES_MANAGER.ipv4['filter']
else: if gateway:
ipv4_filter.add_rule('FORWARD', for rule in _IPTABLES_MANAGER.get_gateway_rules(bridge):
('--in-interface %s -j %s' ipv4_filter.add_rule(*rule)
% (bridge, else:
_IPTABLES_MANAGER.iptables_drop_action))) ipv4_filter.add_rule('FORWARD',
ipv4_filter.add_rule('FORWARD', ('--in-interface %s -j %s'
('--out-interface %s -j %s' % (bridge,
% (bridge, _IPTABLES_MANAGER.iptables_drop_action)))
_IPTABLES_MANAGER.iptables_drop_action))) ipv4_filter.add_rule('FORWARD',
_IPTABLES_MANAGER.apply() ('--out-interface %s -j %s'
% (bridge,
_IPTABLES_MANAGER.iptables_drop_action)))
_IPTABLES_MANAGER.apply()
def configure(iptables_mgr): def configure(iptables_mgr):

View File

@ -0,0 +1,24 @@
#
# Copyright (C) 2016 Red Hat, Inc
#
# 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 oslo_privsep import capabilities as c
from oslo_privsep import priv_context
vif_plug = priv_context.PrivContext(
"vif_plug_linux_bridge",
cfg_section="vif_plug_linux_bridge_privileged",
pypath=__name__ + ".vif_plug",
capabilities=[c.CAP_NET_ADMIN],
)