diff --git a/nova/network/neutron.py b/nova/network/neutron.py index eef2abe36414..e6d47c9a6b39 100644 --- a/nova/network/neutron.py +++ b/nova/network/neutron.py @@ -1601,6 +1601,13 @@ class API: 'pf_mac_address': pf_mac, 'vf_num': vf_num, }) + + # Update port binding capabilities using PCI device's network + # capabilities if they exist. + pci_net_caps = pci_dev.network_caps + if pci_net_caps: + vf_profile.update({'capabilities': pci_net_caps}) + return vf_profile def _get_pci_device_profile(self, pci_dev): diff --git a/nova/objects/pci_device.py b/nova/objects/pci_device.py index 14f9cca14a97..ab775b4554ac 100644 --- a/nova/objects/pci_device.py +++ b/nova/objects/pci_device.py @@ -589,6 +589,13 @@ class PciDevice(base.NovaPersistentObject, base.NovaObject): """ return self.extra_info.get('mac_address') + @property + def network_caps(self): + """PCI device network capabilities or empty list if not available""" + caps_json = self.extra_info.get('capabilities', '{}') + caps = jsonutils.loads(caps_json) + return caps.get('network', []) + @base.NovaObjectRegistry.register class PciDeviceList(base.ObjectListBase, base.NovaObject): diff --git a/nova/tests/fixtures/libvirt_data.py b/nova/tests/fixtures/libvirt_data.py index f022860f615d..f921a5e2df37 100644 --- a/nova/tests/fixtures/libvirt_data.py +++ b/nova/tests/fixtures/libvirt_data.py @@ -2182,6 +2182,7 @@ _fake_NodeDevXml = { + """, # noqa:E501 diff --git a/nova/tests/unit/network/test_neutron.py b/nova/tests/unit/network/test_neutron.py index c551191e4cf8..0789022cfae1 100644 --- a/nova/tests/unit/network/test_neutron.py +++ b/nova/tests/unit/network/test_neutron.py @@ -8144,17 +8144,20 @@ class TestAPIPortbinding(TestAPIBase): 'pf_mac_address': '52:54:00:1e:59:c6', 'vf_num': 1, }, + 'network_caps': ['gso', 'sg', 'tso', 'tx'], 'dev_type': obj_fields.PciDeviceType.SRIOV_VF, } PciDevice = collections.namedtuple('PciDevice', ['vendor_id', 'product_id', 'address', 'card_serial_number', 'sriov_cap', - 'dev_type', 'parent_addr']) + 'dev_type', 'parent_addr', + 'network_caps']) mydev = PciDevice(**pci_dev) self.assertEqual(self.api._get_vf_pci_device_profile(mydev), {'pf_mac_address': '52:54:00:1e:59:c6', 'vf_num': 1, - 'card_serial_number': 'MT2113X00000'}) + 'card_serial_number': 'MT2113X00000', + 'capabilities': ['gso', 'sg', 'tso', 'tx']}) @mock.patch.object( neutronapi.API, '_get_vf_pci_device_profile', diff --git a/nova/tests/unit/objects/test_pci_device.py b/nova/tests/unit/objects/test_pci_device.py index 1e971c5a2149..e0570b69a8ef 100644 --- a/nova/tests/unit/objects/test_pci_device.py +++ b/nova/tests/unit/objects/test_pci_device.py @@ -171,6 +171,16 @@ class _TestPciDeviceObject(object): self.pci_device = pci_device.PciDevice.create(None, self.dev_dict) self.assertEqual(self.pci_device.card_serial_number, '42') + def test_pci_device_extra_info_network_capabilities(self): + self.dev_dict = copy.copy(dev_dict) + self.pci_device = pci_device.PciDevice.create(None, self.dev_dict) + self.assertEqual(self.pci_device.network_caps, []) + + self.dev_dict = copy.copy(dev_dict) + self.dev_dict['capabilities'] = {'network': ['sg', 'tso', 'tx']} + self.pci_device = pci_device.PciDevice.create(None, self.dev_dict) + self.assertEqual(self.pci_device.network_caps, ['sg', 'tso', 'tx']) + def test_update_device(self): self.pci_device = pci_device.PciDevice.create(None, dev_dict) self.pci_device.obj_reset_changes() diff --git a/nova/tests/unit/virt/libvirt/test_host.py b/nova/tests/unit/virt/libvirt/test_host.py index 9f82aefda027..a94dea48d3fc 100644 --- a/nova/tests/unit/virt/libvirt/test_host.py +++ b/nova/tests/unit/virt/libvirt/test_host.py @@ -1345,7 +1345,7 @@ Active: 8381604 kB "parent_ifname": "ens1", "capabilities": { "network": ["rx", "tx", "sg", "tso", "gso", "gro", "rxvlan", - "txvlan", "rxhash"], + "txvlan", "rxhash", "switchdev"], "sriov": {"pf_mac_address": "52:54:00:1e:59:c6", "vf_num": 1}, # Should be obtained from the parent PF in this case. diff --git a/releasenotes/notes/translate_vf_network_capabilities_to_port_binding-48abbfe0ce2923cf.yaml b/releasenotes/notes/translate_vf_network_capabilities_to_port_binding-48abbfe0ce2923cf.yaml new file mode 100644 index 000000000000..b5ee283c8c4c --- /dev/null +++ b/releasenotes/notes/translate_vf_network_capabilities_to_port_binding-48abbfe0ce2923cf.yaml @@ -0,0 +1,16 @@ +--- +fixes: + - | + Previously ``switchdev`` capabilities should be configured manually by a + user with admin privileges using port's binding profile. This blocked + regular users from managing ports with Open vSwitch hardware offloading + as providing write access to a port's binding profile to non-admin users + introduces security risks. For example, a binding profile may contain a + ``pci_slot`` definition, which denotes the host PCI address of the + device attached to the VM. A malicious user can use this parameter to + passthrough any host device to a guest, so it is impossible to provide + write access to a binding profile to regular users in many scenarios. + + This patch fixes this situation by translating VF capabilities reported + by Libvirt to Neutron port binding profiles. Other VF capabilities are + translated as well for possible future use.