Merge remote-tracking branch 'origin/pr/369'
* origin/pr/369: ext: support for non-service feature advertisement
This commit is contained in:
		
						commit
						7ffa7564cf
					
				| @ -183,6 +183,30 @@ Services and features can be then inspected from dom0 using | |||||||
|    $ qvm-features my-qube |    $ qvm-features my-qube | ||||||
|    supported-service.my-service  1 |    supported-service.my-service  1 | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
|  | Announcing supported features | ||||||
|  | ------------------------------ | ||||||
|  | 
 | ||||||
|  | For non-service features, there is similar announce mechanis to the above, but | ||||||
|  | uses ``supported-feature.`` prefix. It works like this: | ||||||
|  | 
 | ||||||
|  | 1. The TemplateVM (or StandaloneVM) announces | ||||||
|  | ``supported-feature.FEATURE_NAME=1`` (with ``FEATURE_NAME`` replaced with an | ||||||
|  | actual feature name) using ``qvm-features-request`` tool (see above how to use it). | ||||||
|  | 2. core-admin extension | ||||||
|  | :py:class:`qubes.ext.supported_features.SupportedFeaturesExtension` records | ||||||
|  | such requests as features on the same VM. | ||||||
|  | 3. Any tool that wants to check if the feature support is advertised by the VM, | ||||||
|  | can look into its features. For template-based VMs it is advised to check | ||||||
|  | also its template - there is a `vm.features.check_with_template()` | ||||||
|  | function specifically for this. | ||||||
|  | 
 | ||||||
|  | Note that (similar to services) it is not necessary for the feature to be | ||||||
|  | advertised to enable it. In fact, many features does not need any support from | ||||||
|  | the VM side, so they will work without matching ``supported-feature.`` entry. | ||||||
|  | Whether a feature requires VM-side support, is documented on case-by-case basis | ||||||
|  | in `qvm-features` tool manual page. | ||||||
|  | 
 | ||||||
| Module contents | Module contents | ||||||
| --------------- | --------------- | ||||||
| 
 | 
 | ||||||
|  | |||||||
							
								
								
									
										68
									
								
								qubes/ext/supported_features.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										68
									
								
								qubes/ext/supported_features.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,68 @@ | |||||||
|  | # -*- encoding: utf-8 -*- | ||||||
|  | # | ||||||
|  | # The Qubes OS Project, http://www.qubes-os.org | ||||||
|  | # | ||||||
|  | # Copyright (C) 2020 Marek Marczykowski-Górecki | ||||||
|  | #                               <marmarek@invisiblethingslab.com> | ||||||
|  | # | ||||||
|  | # This library is free software; you can redistribute it and/or | ||||||
|  | # modify it under the terms of the GNU Lesser General Public | ||||||
|  | # License as published by the Free Software Foundation; either | ||||||
|  | # version 2.1 of the License, or (at your option) any later version. | ||||||
|  | # | ||||||
|  | # This library is distributed in the hope that it will be useful, | ||||||
|  | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU | ||||||
|  | # Lesser General Public License for more details. | ||||||
|  | # | ||||||
|  | # You should have received a copy of the GNU Lesser General Public | ||||||
|  | # License along with this library; if not, see <https://www.gnu.org/licenses/>. | ||||||
|  | 
 | ||||||
|  | """Extension responsible for announcing supported features""" | ||||||
|  | 
 | ||||||
|  | import qubes.ext | ||||||
|  | import qubes.config | ||||||
|  | 
 | ||||||
|  | # pylint: disable=too-few-public-methods | ||||||
|  | 
 | ||||||
|  | class SupportedFeaturesExtension(qubes.ext.Extension): | ||||||
|  |     """This extension handles VM announcing non-service features as | ||||||
|  |         'supported-feature.*' features. | ||||||
|  |     """ | ||||||
|  | 
 | ||||||
|  |     @qubes.ext.handler('features-request') | ||||||
|  |     def supported_features(self, vm, event, untrusted_features): | ||||||
|  |         """Handle advertisement of supported features""" | ||||||
|  |         # pylint: disable=no-self-use,unused-argument | ||||||
|  | 
 | ||||||
|  |         if getattr(vm, 'template', None): | ||||||
|  |             vm.log.warning( | ||||||
|  |                 'Ignoring qubes.FeaturesRequest from template-based VM') | ||||||
|  |             return | ||||||
|  | 
 | ||||||
|  |         new_supported_features = set() | ||||||
|  |         for requested_feature in untrusted_features: | ||||||
|  |             if not requested_feature.startswith('supported-feature.'): | ||||||
|  |                 continue | ||||||
|  |             if untrusted_features[requested_feature] == '1': | ||||||
|  |                 # only allow to advertise feature as supported, lack of entry | ||||||
|  |                 #  means feature is not supported | ||||||
|  |                 new_supported_features.add(requested_feature) | ||||||
|  |         del untrusted_features | ||||||
|  | 
 | ||||||
|  |         # if no feature is supported, ignore the whole thing - do not clear | ||||||
|  |         # all features in case of empty request (manual or such) | ||||||
|  |         if not new_supported_features: | ||||||
|  |             return | ||||||
|  | 
 | ||||||
|  |         old_supported_features = set( | ||||||
|  |             feat for feat in vm.features | ||||||
|  |             if feat.startswith('supported-feature.') and vm.features[feat]) | ||||||
|  | 
 | ||||||
|  |         for feature in new_supported_features.difference( | ||||||
|  |                 old_supported_features): | ||||||
|  |             vm.features[feature] = True | ||||||
|  | 
 | ||||||
|  |         for feature in old_supported_features.difference( | ||||||
|  |                 new_supported_features): | ||||||
|  |             del vm.features[feature] | ||||||
| @ -395,3 +395,64 @@ class TC_20_Services(qubes.tests.QubesTestCase): | |||||||
|             'service.guivm-gui-agent', '') |             'service.guivm-gui-agent', '') | ||||||
| 
 | 
 | ||||||
|         self.assertEqual(os.path.exists(service_path), False) |         self.assertEqual(os.path.exists(service_path), False) | ||||||
|  | 
 | ||||||
|  | class TC_30_SupportedFeatures(qubes.tests.QubesTestCase): | ||||||
|  |     def setUp(self): | ||||||
|  |         super().setUp() | ||||||
|  |         self.ext = qubes.ext.supported_features.SupportedFeaturesExtension() | ||||||
|  |         self.features = {} | ||||||
|  |         specs = { | ||||||
|  |             'features.get.side_effect': self.features.get, | ||||||
|  |             'features.items.side_effect': self.features.items, | ||||||
|  |             'features.__iter__.side_effect': self.features.__iter__, | ||||||
|  |             'features.__contains__.side_effect': self.features.__contains__, | ||||||
|  |             'features.__setitem__.side_effect': self.features.__setitem__, | ||||||
|  |             'features.__delitem__.side_effect': self.features.__delitem__, | ||||||
|  |         } | ||||||
|  |         vmspecs = {**specs, **{ | ||||||
|  |             'template': None, | ||||||
|  |             'maxmem': 1024, | ||||||
|  |             'is_running.return_value': True, | ||||||
|  |             }} | ||||||
|  |         dom0specs = {**specs, **{ | ||||||
|  |             'name': "dom0", | ||||||
|  |             }} | ||||||
|  |         self.vm = mock.MagicMock() | ||||||
|  |         self.vm.configure_mock(**vmspecs) | ||||||
|  |         self.dom0 = mock.MagicMock() | ||||||
|  |         self.dom0.configure_mock(**dom0specs) | ||||||
|  | 
 | ||||||
|  |     def test_010_supported_features(self): | ||||||
|  |         self.ext.supported_features(self.vm, 'features-request', | ||||||
|  |             untrusted_features={ | ||||||
|  |                 'supported-feature.test1': '1',  # ok | ||||||
|  |                 'supported-feature.test2': '0',  # ignored | ||||||
|  |                 'supported-feature.test3': 'some text',  # ignored | ||||||
|  |                 'no-feature': '1',  # ignored | ||||||
|  |             }) | ||||||
|  |         self.assertEqual(self.features, { | ||||||
|  |             'supported-feature.test1': True, | ||||||
|  |         }) | ||||||
|  | 
 | ||||||
|  |     def test_011_supported_features_add(self): | ||||||
|  |         self.features['supported-feature.test1'] = '1' | ||||||
|  |         self.ext.supported_features(self.vm, 'features-request', | ||||||
|  |             untrusted_features={ | ||||||
|  |                 'supported-feature.test1': '1',  # ok | ||||||
|  |                 'supported-feature.test2': '1',  # ok | ||||||
|  |             }) | ||||||
|  |         # also check if existing one is untouched | ||||||
|  |         self.assertEqual(self.features, { | ||||||
|  |             'supported-feature.test1': '1', | ||||||
|  |             'supported-feature.test2': True, | ||||||
|  |         }) | ||||||
|  | 
 | ||||||
|  |     def test_012_supported_features_remove(self): | ||||||
|  |         self.features['supported-feature.test1'] = '1' | ||||||
|  |         self.ext.supported_features(self.vm, 'features-request', | ||||||
|  |             untrusted_features={ | ||||||
|  |                 'supported-feature.test2': '1',  # ok | ||||||
|  |             }) | ||||||
|  |         self.assertEqual(self.features, { | ||||||
|  |             'supported-feature.test2': True, | ||||||
|  |         }) | ||||||
|  | |||||||
| @ -425,6 +425,7 @@ done | |||||||
| %{python3_sitelib}/qubes/ext/pci.py | %{python3_sitelib}/qubes/ext/pci.py | ||||||
| %{python3_sitelib}/qubes/ext/r3compatibility.py | %{python3_sitelib}/qubes/ext/r3compatibility.py | ||||||
| %{python3_sitelib}/qubes/ext/services.py | %{python3_sitelib}/qubes/ext/services.py | ||||||
|  | %{python3_sitelib}/qubes/ext/supported_features.py | ||||||
| %{python3_sitelib}/qubes/ext/windows.py | %{python3_sitelib}/qubes/ext/windows.py | ||||||
| 
 | 
 | ||||||
| %dir %{python3_sitelib}/qubes/tests | %dir %{python3_sitelib}/qubes/tests | ||||||
|  | |||||||
							
								
								
									
										1
									
								
								setup.py
									
									
									
									
									
								
							
							
						
						
									
										1
									
								
								setup.py
									
									
									
									
									
								
							| @ -70,6 +70,7 @@ if __name__ == '__main__': | |||||||
|                 'qubes.ext.pci = qubes.ext.pci:PCIDeviceExtension', |                 'qubes.ext.pci = qubes.ext.pci:PCIDeviceExtension', | ||||||
|                 'qubes.ext.block = qubes.ext.block:BlockDeviceExtension', |                 'qubes.ext.block = qubes.ext.block:BlockDeviceExtension', | ||||||
|                 'qubes.ext.services = qubes.ext.services:ServicesExtension', |                 'qubes.ext.services = qubes.ext.services:ServicesExtension', | ||||||
|  |                 'qubes.ext.supported_features = qubes.ext.supported_features:SupportedFeaturesExtension', | ||||||
|                 'qubes.ext.windows = qubes.ext.windows:WindowsFeatures', |                 'qubes.ext.windows = qubes.ext.windows:WindowsFeatures', | ||||||
|             ], |             ], | ||||||
|             'qubes.devices': [ |             'qubes.devices': [ | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user
	 Marek Marczykowski-Górecki
						Marek Marczykowski-Górecki