From 0952f80d013c4ab85ff82355312feb2464796e38 Mon Sep 17 00:00:00 2001 From: Michael Still Date: Tue, 1 Aug 2017 10:28:38 +1000 Subject: [PATCH] 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 --- etc/nova/rootwrap.d/compute.filters | 5 +---- nova/privsep/__init__.py | 12 +++++++++- nova/privsep/libvirt.py | 22 +++++++++++++++++++ nova/tests/unit/virt/libvirt/test_driver.py | 12 +++++----- nova/tests/unit/virt/libvirt/test_guest.py | 22 ++++++++----------- nova/tests/unit/virt/libvirt/test_vif.py | 21 +++++++++--------- nova/virt/libvirt/guest.py | 9 ++------ nova/virt/libvirt/vif.py | 15 ++----------- ...queens-rootwrap-adds-907aa1bc8e3eb2ca.yaml | 5 ++++- 9 files changed, 66 insertions(+), 57 deletions(-) diff --git a/etc/nova/rootwrap.d/compute.filters b/etc/nova/rootwrap.d/compute.filters index 8786df809ac5..1a6127815d48 100644 --- a/etc/nova/rootwrap.d/compute.filters +++ b/etc/nova/rootwrap.d/compute.filters @@ -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 diff --git a/nova/privsep/__init__.py b/nova/privsep/__init__.py index 608a040790b3..76029ad30bf4 100644 --- a/nova/privsep/__init__.py +++ b/nova/privsep/__init__.py @@ -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], +) diff --git a/nova/privsep/libvirt.py b/nova/privsep/libvirt.py index d5dc6c8c84a3..6c2844f31852 100644 --- a/nova/privsep/libvirt.py +++ b/nova/privsep/libvirt.py @@ -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') diff --git a/nova/tests/unit/virt/libvirt/test_driver.py b/nova/tests/unit/virt/libvirt/test_driver.py index b9b05466b968..537373f1fc07 100644 --- a/nova/tests/unit/virt/libvirt/test_driver.py +++ b/nova/tests/unit/virt/libvirt/test_driver.py @@ -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 = "this is a 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) diff --git a/nova/tests/unit/virt/libvirt/test_guest.py b/nova/tests/unit/virt/libvirt/test_guest.py index d181aa3e9a83..36eb54df981d 100644 --- a/nova/tests/unit/virt/libvirt/test_guest.py +++ b/nova/tests/unit/virt/libvirt/test_guest.py @@ -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) diff --git a/nova/tests/unit/virt/libvirt/test_vif.py b/nova/tests/unit/virt/libvirt/test_vif.py index ed3eb462d513..25c76177e891 100644 --- a/nova/tests/unit/virt/libvirt/test_vif.py +++ b/nova/tests/unit/virt/libvirt/test_vif.py @@ -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 = { diff --git a/nova/virt/libvirt/guest.py b/nova/virt/libvirt/guest.py index 0acc13befb70..ed614f60dc23 100644 --- a/nova/virt/libvirt/guest.py +++ b/nova/virt/libvirt/guest.py @@ -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', diff --git a/nova/virt/libvirt/vif.py b/nova/virt/libvirt/vif.py index 39e8b73a997d..f1bd88fcc5a1 100644 --- a/nova/virt/libvirt/vif.py +++ b/nova/virt/libvirt/vif.py @@ -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') diff --git a/releasenotes/notes/privsep-queens-rootwrap-adds-907aa1bc8e3eb2ca.yaml b/releasenotes/notes/privsep-queens-rootwrap-adds-907aa1bc8e3eb2ca.yaml index 874ca7522526..13939515da30 100644 --- a/releasenotes/notes/privsep-queens-rootwrap-adds-907aa1bc8e3eb2ca.yaml +++ b/releasenotes/notes/privsep-queens-rootwrap-adds-907aa1bc8e3eb2ca.yaml @@ -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. \ No newline at end of file + configuration: cat; chown; readlink; tee; touch. \ No newline at end of file