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 # nova/virt/disk/mount/nbd.py: 'blockdev', '--flushbufs', device
blockdev: RegExpFilter, blockdev, root, blockdev, (--getsize64|--flushbufs), /dev/.* 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', 'tuntap', 'add', dev, 'mode', 'tap'
# nova/virt/libvirt/vif.py: 'ip', 'link', 'set', dev, 'up' # nova/virt/libvirt/vif.py: 'ip', 'link', 'set', dev, 'up'
# nova/virt/libvirt/vif.py: 'ip', 'link', 'delete', dev # 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-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: # nova/virt/libvirt/storage/dmcrypt.py:
cryptsetup: CommandFilter, cryptsetup, root cryptsetup: CommandFilter, cryptsetup, root

View File

@ -15,13 +15,14 @@
"""Setup privsep decorator.""" """Setup privsep decorator."""
from oslo_privsep import capabilities
from oslo_privsep import priv_context from oslo_privsep import priv_context
# NOTE(tonyb): DAC == Discriminatory Access Control. Basically this context # NOTE(tonyb): DAC == Discriminatory Access Control. Basically this context
# can bypass permissions checks in the file-system. # can bypass permissions checks in the file-system.
dac_admin_pctxt = priv_context.PrivContext( dac_admin_pctxt = priv_context.PrivContext(
'nova', 'nova',
cfg_section='nova_privileged', cfg_section='nova_dac_admin',
pypath=__name__ + '.dac_admin_pctxt', pypath=__name__ + '.dac_admin_pctxt',
# NOTE(tonyb): These map to CAP_CHOWN, CAP_DAC_OVERRIDE, # NOTE(tonyb): These map to CAP_CHOWN, CAP_DAC_OVERRIDE,
# CAP_DAC_READ_SEARCH and CAP_FOWNER. Some do not have # CAP_DAC_READ_SEARCH and CAP_FOWNER. Some do not have
@ -29,3 +30,12 @@ dac_admin_pctxt = priv_context.PrivContext(
# for more information # for more information
capabilities=[0, 1, 2, 3], 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() remaining = file_like_object.tell()
return (file_like_object.read(), remaining) 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) domain=fake_domain)
self.assertTrue(self.log_error_called) 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 """Tests that the xml is logged when enabling hairpin mode for the
domain fails. domain fails.
""" """
# Guest.enable_hairpin is only called for nova-network. # Guest.enable_hairpin is only called for nova-network.
# TODO(mikal): remove this test when nova-net goes away
self.flags(use_neutron=False) self.flags(use_neutron=False)
fake_xml = "<test>this is a test</test>" fake_xml = "<test>this is a test</test>"
fake_domain = FakeVirtDomain(fake_xml) fake_domain = FakeVirtDomain(fake_xml)
def fake_execute(*args, **kwargs): mock_writefile.side_effect = IOError
raise processutils.ProcessExecutionError('error')
def fake_get_interfaces(*args): def fake_get_interfaces(*args):
return ["dev"] return ["dev"]
@ -14180,14 +14181,11 @@ class LibvirtConnTestCase(test.NoDBTestCase,
self.create_fake_libvirt_mock() self.create_fake_libvirt_mock()
self.mox.ReplayAll() self.mox.ReplayAll()
drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), True) drvr = libvirt_driver.LibvirtDriver(fake.FakeVirtAPI(), True)
self.stubs.Set(nova.utils, 'execute', fake_execute)
self.stubs.Set( self.stubs.Set(
nova.virt.libvirt.guest.Guest, 'get_interfaces', nova.virt.libvirt.guest.Guest, 'get_interfaces',
fake_get_interfaces) fake_get_interfaces)
self.assertRaises(processutils.ProcessExecutionError, self.assertRaises(IOError, drvr._create_domain, domain=fake_domain,
drvr._create_domain,
domain=fake_domain,
power_on=False) power_on=False)
self.assertTrue(self.log_error_called) self.assertTrue(self.log_error_called)

View File

@ -24,7 +24,6 @@ from nova import context
from nova import exception from nova import exception
from nova import test from nova import test
from nova.tests.unit.virt.libvirt import fakelibvirt 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 config as vconfig
from nova.virt.libvirt import guest as libvirt_guest from nova.virt.libvirt import guest as libvirt_guest
from nova.virt.libvirt import host from nova.virt.libvirt import host
@ -85,26 +84,23 @@ class GuestTestCase(test.NoDBTestCase):
self.assertRaises(test.TestingException, self.guest.launch) self.assertRaises(test.TestingException, self.guest.launch)
self.assertEqual(1, mock_safe_decode.called) 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') @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"] mock_get_interfaces.return_value = ["vnet0", "vnet1"]
self.guest.enable_hairpin() self.guest.enable_hairpin()
mock_execute.assert_has_calls([ mock_writefile.assert_has_calls([
mock.call( mock.call('vnet0'),
'tee', '/sys/class/net/vnet0/brport/hairpin_mode', mock.call('vnet1')]
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.patch.object(encodeutils, 'safe_decode') @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') @mock.patch.object(libvirt_guest.Guest, 'get_interfaces')
def test_enable_hairpin_exception(self, mock_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_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.assertRaises(test.TestingException, self.guest.enable_hairpin)
self.assertEqual(1, mock_safe_decode.called) self.assertEqual(1, mock_safe_decode.called)

View File

@ -960,21 +960,15 @@ class LibvirtVifTestCase(test.NoDBTestCase):
run_as_root=True), run_as_root=True),
mock.call('brctl', 'stp', 'qbrvif-xxx-yyy', 'off', mock.call('brctl', 'stp', 'qbrvif-xxx-yyy', 'off',
run_as_root=True), 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', mock.call('ip', 'link', 'set', 'qbrvif-xxx-yyy', 'up',
run_as_root=True), run_as_root=True),
mock.call('brctl', 'addif', 'qbrvif-xxx-yyy', mock.call('brctl', 'addif', 'qbrvif-xxx-yyy',
'qvbvif-xxx-yyy', run_as_root=True)], 'qvbvif-xxx-yyy', run_as_root=True)],
'create_ivs_vif_port': [mock.call('qvovif-xxx-yyy', 'aaa-bbb-ccc', 'create_ivs_vif_port': [mock.call('qvovif-xxx-yyy', 'aaa-bbb-ccc',
'ca:fe:de:ad:be:ef', '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( with test.nested(
mock.patch.object(linux_net, 'device_exists', mock.patch.object(linux_net, 'device_exists',
@ -982,15 +976,20 @@ class LibvirtVifTestCase(test.NoDBTestCase):
mock.patch.object(utils, 'execute'), mock.patch.object(utils, 'execute'),
mock.patch.object(linux_net, '_create_veth_pair'), mock.patch.object(linux_net, '_create_veth_pair'),
mock.patch.object(linux_net, 'create_ivs_vif_port'), 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, ) as (device_exists, execute, _create_veth_pair, create_ivs_vif_port,
path_exists): path_exists, disable_ipv6, disable_multicast_snooping):
d = vif.LibvirtGenericVIFDriver() d = vif.LibvirtGenericVIFDriver()
d.plug(self.instance, self.vif_ivs) d.plug(self.instance, self.vif_ivs)
device_exists.assert_has_calls(calls['device_exists']) device_exists.assert_has_calls(calls['device_exists'])
_create_veth_pair.assert_has_calls(calls['_create_veth_pair']) _create_veth_pair.assert_has_calls(calls['_create_veth_pair'])
execute.assert_has_calls(calls['execute']) execute.assert_has_calls(calls['execute'])
create_ivs_vif_port.assert_has_calls(calls['create_ivs_vif_port']) 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): def test_unplug_ivs_hybrid(self):
calls = { calls = {

View File

@ -40,7 +40,7 @@ import six
from nova.compute import power_state from nova.compute import power_state
from nova import exception from nova import exception
from nova.i18n import _ from nova.i18n import _
from nova import utils from nova.privsep import libvirt as libvirt_privsep
from nova.virt import hardware from nova.virt import hardware
from nova.virt.libvirt import compat from nova.virt.libvirt import compat
from nova.virt.libvirt import config as vconfig from nova.virt.libvirt import config as vconfig
@ -200,12 +200,7 @@ class Guest(object):
interfaces = self.get_interfaces() interfaces = self.get_interfaces()
try: try:
for interface in interfaces: for interface in interfaces:
utils.execute( libvirt_privsep.enable_hairpin(interface)
'tee',
'/sys/class/net/%s/brport/hairpin_mode' % interface,
process_input='1',
run_as_root=True,
check_exit_code=[0, 1])
except Exception: except Exception:
with excutils.save_and_reraise_exception(): with excutils.save_and_reraise_exception():
LOG.error('Error enabling hairpin mode with XML: %s', 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', 'addbr', br_name, run_as_root=True)
utils.execute('brctl', 'setfd', br_name, 0, 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('brctl', 'stp', br_name, 'off', run_as_root=True)
utils.execute('tee', nova.privsep.libvirt.disable_multicast_snooping(br_name)
('/sys/class/net/%s/bridge/multicast_snooping' % nova.privsep.libvirt.disable_ipv6(br_name)
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])
if not linux_net.device_exists(v2_name): if not linux_net.device_exists(v2_name):
mtu = vif['network'].get_meta('mtu') 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 A dac-admin privsep daemon has been added and needs to be included in your
rootwrap configuration. 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 The following commands are no longer required to be listed in your rootwrap
configuration: cat; chown; readlink; touch. configuration: cat; chown; readlink; tee; touch.