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:
parent
76994a02d4
commit
bf845feb11
@ -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
|
||||||
|
@ -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:
|
||||||
|
@ -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):
|
||||||
|
24
vif_plug_linux_bridge/privsep.py
Normal file
24
vif_plug_linux_bridge/privsep.py
Normal 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],
|
||||||
|
)
|
Loading…
Reference in New Issue
Block a user