Move execs of tee to privsep.

Instead of calling tee to write to files as root, we should just
write to files as root.

Change-Id: Ic48087fdf283b3ba503294a944be91be0c338132
This commit is contained in:
Michael Still 2017-08-01 10:28:38 +10:00
parent d83e9c0b17
commit 0952f80d01
9 changed files with 66 additions and 57 deletions

View File

@ -37,10 +37,6 @@ blkid: CommandFilter, blkid, root
# nova/virt/disk/mount/nbd.py: 'blockdev', '--flushbufs', device
blockdev: RegExpFilter, blockdev, root, blockdev, (--getsize64|--flushbufs), /dev/.*
# nova/virt/libvirt/guest.py: 'tee',
# nova/virt/libvirt/vif.py: utils.execute('tee',
tee: CommandFilter, tee, 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
@ -204,6 +200,7 @@ privsep-rootwrap-os_brick: RegExpFilter, privsep-helper, root, privsep-helper, -
privsep-rootwrap-dac_admin: RegExpFilter, privsep-helper, root, privsep-helper, --config-file, /etc/(?!\.\.).*, --privsep_context, nova.privsep.dac_admin_pctxt, --privsep_sock_path, /tmp/.*
privsep-rootwrap-dacnet_admin: RegExpFilter, privsep-helper, root, privsep-helper, --config-file, /etc/(?!\.\.).*, --privsep_context, nova.privsep.dacnet_admin_pctxt, --privsep_sock_path, /tmp/.*
# nova/virt/libvirt/storage/dmcrypt.py:
cryptsetup: CommandFilter, cryptsetup, root

View File

@ -15,13 +15,14 @@
"""Setup privsep decorator."""
from oslo_privsep import capabilities
from oslo_privsep import priv_context
# NOTE(tonyb): DAC == Discriminatory Access Control. Basically this context
# can bypass permissions checks in the file-system.
dac_admin_pctxt = priv_context.PrivContext(
'nova',
cfg_section='nova_privileged',
cfg_section='nova_dac_admin',
pypath=__name__ + '.dac_admin_pctxt',
# NOTE(tonyb): These map to CAP_CHOWN, CAP_DAC_OVERRIDE,
# CAP_DAC_READ_SEARCH and CAP_FOWNER. Some do not have
@ -29,3 +30,12 @@ dac_admin_pctxt = priv_context.PrivContext(
# for more information
capabilities=[0, 1, 2, 3],
)
# NOTE(mikal): DAC + CAP_NET_ADMIN, required for network sysfs changes
dacnet_admin_pctxt = priv_context.PrivContext(
'nova',
cfg_section='nova_dacnet_admin',
pypath=__name__ + '.dacnet_admin_pctxt',
capabilities=[0, 1, 2, 3, capabilities.CAP_NET_ADMIN],
)

View File

@ -55,3 +55,25 @@ def _last_bytes_inner(file_like_object, num):
remaining = file_like_object.tell()
return (file_like_object.read(), remaining)
@nova.privsep.dacnet_admin_pctxt.entrypoint
def enable_hairpin(interface):
"""Enable hairpin mode for a libvirt guest."""
with open('/sys/class/net/%s/brport/hairpin_mode' % interface, 'w') as f:
f.write('1')
@nova.privsep.dacnet_admin_pctxt.entrypoint
def disable_multicast_snooping(interface):
"""Disable multicast snooping for a bridge."""
with open('/sys/class/net/%s/bridge/multicast_snooping' % interface,
'w') as f:
f.write('0')
@nova.privsep.dacnet_admin_pctxt.entrypoint
def disable_ipv6(interface):
"""Disable ipv6 for a bridge."""
with open('/proc/sys/net/ipv6/conf/%s/disable_ipv' % interface, 'w') as f:
f.write('1')

View File

@ -14154,17 +14154,18 @@ class LibvirtConnTestCase(test.NoDBTestCase,
domain=fake_domain)
self.assertTrue(self.log_error_called)
def test_create_domain_enable_hairpin_fails(self):
@mock.patch('nova.privsep.libvirt.enable_hairpin')
def test_create_domain_enable_hairpin_fails(self, mock_writefile):
"""Tests that the xml is logged when enabling hairpin mode for the
domain fails.
"""
# Guest.enable_hairpin is only called for nova-network.
# TODO(mikal): remove this test when nova-net goes away
self.flags(use_neutron=False)
fake_xml = "<test>this is a test</test>"
fake_domain = FakeVirtDomain(fake_xml)
def fake_execute(*args, **kwargs):
raise processutils.ProcessExecutionError('error')
mock_writefile.side_effect = IOError
def fake_get_interfaces(*args):
return ["dev"]
@ -14180,14 +14181,11 @@ class LibvirtConnTestCase(test.NoDBTestCase,
self.create_fake_libvirt_mock()
self.mox.ReplayAll()
drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), True)
self.stubs.Set(nova.utils, 'execute', fake_execute)
self.stubs.Set(
nova.virt.libvirt.guest.Guest, 'get_interfaces',
fake_get_interfaces)
self.assertRaises(processutils.ProcessExecutionError,
drvr._create_domain,
domain=fake_domain,
self.assertRaises(IOError, drvr._create_domain, domain=fake_domain,
power_on=False)
self.assertTrue(self.log_error_called)

View File

@ -24,7 +24,6 @@ from nova import context
from nova import exception
from nova import test
from nova.tests.unit.virt.libvirt import fakelibvirt
from nova import utils
from nova.virt.libvirt import config as vconfig
from nova.virt.libvirt import guest as libvirt_guest
from nova.virt.libvirt import host
@ -85,26 +84,23 @@ class GuestTestCase(test.NoDBTestCase):
self.assertRaises(test.TestingException, self.guest.launch)
self.assertEqual(1, mock_safe_decode.called)
@mock.patch.object(utils, 'execute')
@mock.patch('nova.privsep.libvirt.enable_hairpin')
@mock.patch.object(libvirt_guest.Guest, 'get_interfaces')
def test_enable_hairpin(self, mock_get_interfaces, mock_execute):
def test_enable_hairpin(self, mock_get_interfaces, mock_writefile):
mock_get_interfaces.return_value = ["vnet0", "vnet1"]
self.guest.enable_hairpin()
mock_execute.assert_has_calls([
mock.call(
'tee', '/sys/class/net/vnet0/brport/hairpin_mode',
run_as_root=True, process_input='1', check_exit_code=[0, 1]),
mock.call(
'tee', '/sys/class/net/vnet1/brport/hairpin_mode',
run_as_root=True, process_input='1', check_exit_code=[0, 1])])
mock_writefile.assert_has_calls([
mock.call('vnet0'),
mock.call('vnet1')]
)
@mock.patch.object(encodeutils, 'safe_decode')
@mock.patch.object(utils, 'execute')
@mock.patch('nova.privsep.libvirt.enable_hairpin')
@mock.patch.object(libvirt_guest.Guest, 'get_interfaces')
def test_enable_hairpin_exception(self, mock_get_interfaces,
mock_execute, mock_safe_decode):
mock_writefile, mock_safe_decode):
mock_get_interfaces.return_value = ["foo"]
mock_execute.side_effect = test.TestingException('oops')
mock_writefile.side_effect = test.TestingException
self.assertRaises(test.TestingException, self.guest.enable_hairpin)
self.assertEqual(1, mock_safe_decode.called)

View File

@ -960,21 +960,15 @@ class LibvirtVifTestCase(test.NoDBTestCase):
run_as_root=True),
mock.call('brctl', 'stp', 'qbrvif-xxx-yyy', 'off',
run_as_root=True),
mock.call('tee', ('/sys/class/net/qbrvif-xxx-yyy'
'/bridge/multicast_snooping'),
process_input='0', run_as_root=True,
check_exit_code=[0, 1]),
mock.call('tee', ('/proc/sys/net/ipv6/conf'
'/qbrvif-xxx-yyy/disable_ipv6'),
process_input='1', run_as_root=True,
check_exit_code=[0, 1]),
mock.call('ip', 'link', 'set', 'qbrvif-xxx-yyy', 'up',
run_as_root=True),
mock.call('brctl', 'addif', 'qbrvif-xxx-yyy',
'qvbvif-xxx-yyy', run_as_root=True)],
'create_ivs_vif_port': [mock.call('qvovif-xxx-yyy', 'aaa-bbb-ccc',
'ca:fe:de:ad:be:ef',
'f0000000-0000-0000-0000-000000000001')]
'f0000000-0000-0000-0000-000000000001')],
'disable_ipv6': [mock.call('qbrvif-xxx-yyy')],
'multicast_snoop': [mock.call('qbrvif-xxx-yyy')]
}
with test.nested(
mock.patch.object(linux_net, 'device_exists',
@ -982,15 +976,20 @@ class LibvirtVifTestCase(test.NoDBTestCase):
mock.patch.object(utils, 'execute'),
mock.patch.object(linux_net, '_create_veth_pair'),
mock.patch.object(linux_net, 'create_ivs_vif_port'),
mock.patch.object(os.path, 'exists', return_value=True)
mock.patch.object(os.path, 'exists', return_value=True),
mock.patch('nova.privsep.libvirt.disable_multicast_snooping'),
mock.patch('nova.privsep.libvirt.disable_ipv6')
) as (device_exists, execute, _create_veth_pair, create_ivs_vif_port,
path_exists):
path_exists, disable_ipv6, disable_multicast_snooping):
d = vif.LibvirtGenericVIFDriver()
d.plug(self.instance, self.vif_ivs)
device_exists.assert_has_calls(calls['device_exists'])
_create_veth_pair.assert_has_calls(calls['_create_veth_pair'])
execute.assert_has_calls(calls['execute'])
create_ivs_vif_port.assert_has_calls(calls['create_ivs_vif_port'])
disable_multicast_snooping.assert_has_calls(
calls['multicast_snoop'])
disable_ipv6.assert_has_calls(calls['disable_ipv6'])
def test_unplug_ivs_hybrid(self):
calls = {

View File

@ -40,7 +40,7 @@ import six
from nova.compute import power_state
from nova import exception
from nova.i18n import _
from nova import utils
from nova.privsep import libvirt as libvirt_privsep
from nova.virt import hardware
from nova.virt.libvirt import compat
from nova.virt.libvirt import config as vconfig
@ -200,12 +200,7 @@ class Guest(object):
interfaces = self.get_interfaces()
try:
for interface in interfaces:
utils.execute(
'tee',
'/sys/class/net/%s/brport/hairpin_mode' % interface,
process_input='1',
run_as_root=True,
check_exit_code=[0, 1])
libvirt_privsep.enable_hairpin(interface)
except Exception:
with excutils.save_and_reraise_exception():
LOG.error('Error enabling hairpin mode with XML: %s',

View File

@ -582,19 +582,8 @@ class LibvirtGenericVIFDriver(object):
utils.execute('brctl', 'addbr', br_name, run_as_root=True)
utils.execute('brctl', 'setfd', br_name, 0, run_as_root=True)
utils.execute('brctl', 'stp', br_name, 'off', run_as_root=True)
utils.execute('tee',
('/sys/class/net/%s/bridge/multicast_snooping' %
br_name),
process_input='0',
run_as_root=True,
check_exit_code=[0, 1])
disv6 = '/proc/sys/net/ipv6/conf/%s/disable_ipv6' % br_name
if os.path.exists(disv6):
utils.execute('tee',
disv6,
process_input='1',
run_as_root=True,
check_exit_code=[0, 1])
nova.privsep.libvirt.disable_multicast_snooping(br_name)
nova.privsep.libvirt.disable_ipv6(br_name)
if not linux_net.device_exists(v2_name):
mtu = vif['network'].get_meta('mtu')

View File

@ -3,6 +3,9 @@ upgrade:
- |
A dac-admin privsep daemon has been added and needs to be included in your
rootwrap configuration.
- |
A dacnet-admin privsep daemon has been added and needs to be included in
your rootwrap configuration.
- |
The following commands are no longer required to be listed in your rootwrap
configuration: cat; chown; readlink; touch.
configuration: cat; chown; readlink; tee; touch.