Update windows-related feature requests
Handle 'os' feature - if it's Windows, then set rpc-clipboard feature. Handle 'gui-emulated' feature - request for specifically stubdomain GUI. With 'gui' feature it is only possible to enable gui-agent based on, or disable GUI completely. Handle 'default-user' - verify it for weird characters and set 'default_user' property (if wasn't already set). QubesOS/qubes-issues#3585
This commit is contained in:
parent
ce87451c73
commit
af7d54d388
@ -76,7 +76,8 @@ class QubesMiscAPI(qubes.api.AbstractQubesAPI):
|
|||||||
|
|
||||||
untrusted_features = {}
|
untrusted_features = {}
|
||||||
safe_set = string.ascii_letters + string.digits
|
safe_set = string.ascii_letters + string.digits
|
||||||
expected_features = ('qrexec', 'gui', 'default-user')
|
expected_features = ('qrexec', 'gui', 'gui-emulated', 'default-user',
|
||||||
|
'os')
|
||||||
for feature in expected_features:
|
for feature in expected_features:
|
||||||
untrusted_value = self.src.untrusted_qdb.read(
|
untrusted_value = self.src.untrusted_qdb.read(
|
||||||
'/qubes-tools/' + feature)
|
'/qubes-tools/' + feature)
|
||||||
|
@ -32,7 +32,7 @@ class CoreFeatures(qubes.ext.Extension):
|
|||||||
return
|
return
|
||||||
|
|
||||||
requested_features = {}
|
requested_features = {}
|
||||||
for feature in ('qrexec', 'gui', 'qubes-firewall'):
|
for feature in ('qrexec', 'gui', 'gui-emulated', 'qubes-firewall'):
|
||||||
untrusted_value = untrusted_features.get(feature, None)
|
untrusted_value = untrusted_features.get(feature, None)
|
||||||
if untrusted_value in ('1', '0'):
|
if untrusted_value in ('1', '0'):
|
||||||
requested_features[feature] = bool(int(untrusted_value))
|
requested_features[feature] = bool(int(untrusted_value))
|
||||||
@ -44,7 +44,7 @@ class CoreFeatures(qubes.ext.Extension):
|
|||||||
# gui agent presence (0 or 1)
|
# gui agent presence (0 or 1)
|
||||||
|
|
||||||
qrexec_before = vm.features.get('qrexec', False)
|
qrexec_before = vm.features.get('qrexec', False)
|
||||||
for feature in ('qrexec', 'gui'):
|
for feature in ('qrexec', 'gui', 'gui-emulated'):
|
||||||
# do not allow (Template)VM to override setting if already set
|
# do not allow (Template)VM to override setting if already set
|
||||||
# some other way
|
# some other way
|
||||||
if feature in requested_features and feature not in vm.features:
|
if feature in requested_features and feature not in vm.features:
|
||||||
|
64
qubes/ext/windows.py
Normal file
64
qubes/ext/windows.py
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
# -*- encoding: utf-8 -*-
|
||||||
|
#
|
||||||
|
# The Qubes OS Project, http://www.qubes-os.org
|
||||||
|
#
|
||||||
|
# Copyright (C) 2017 Marek Marczykowski-Górecki
|
||||||
|
# <marmarek@invisiblethingslab.com>
|
||||||
|
#
|
||||||
|
# This program is free software; you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation; either version 2 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program 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 General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License along
|
||||||
|
# with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import qubes.ext
|
||||||
|
|
||||||
|
class WindowsFeatures(qubes.ext.Extension):
|
||||||
|
# pylint: disable=too-few-public-methods
|
||||||
|
@qubes.ext.handler('features-request')
|
||||||
|
def qubes_features_request(self, vm, event, untrusted_features):
|
||||||
|
'''Handle features provided requested by Qubes Windows Tools'''
|
||||||
|
# pylint: disable=no-self-use,unused-argument
|
||||||
|
if getattr(vm, 'template', None):
|
||||||
|
vm.log.warning(
|
||||||
|
'Ignoring qubes.NotifyTools for template-based VM')
|
||||||
|
return
|
||||||
|
|
||||||
|
guest_os = None
|
||||||
|
if 'os' in untrusted_features:
|
||||||
|
if untrusted_features['os'] in ['Windows']:
|
||||||
|
guest_os = untrusted_features['os']
|
||||||
|
|
||||||
|
qrexec = None
|
||||||
|
if 'qrexec' in untrusted_features:
|
||||||
|
if untrusted_features['qrexec'] == '1':
|
||||||
|
# qrexec feature is set by CoreFeatures extension
|
||||||
|
qrexec = True
|
||||||
|
|
||||||
|
del untrusted_features
|
||||||
|
|
||||||
|
if guest_os:
|
||||||
|
vm.features['os'] = guest_os
|
||||||
|
if guest_os == 'Windows' and qrexec:
|
||||||
|
vm.features['rpc-clipboard'] = True
|
||||||
|
|
||||||
|
@qubes.ext.handler('domain-add', system=True)
|
||||||
|
def on_domain_add(self, app, _event, vm, **kwargs):
|
||||||
|
# pylint: disable=no-self-use,unused-argument
|
||||||
|
if getattr(vm, 'template', None) is None:
|
||||||
|
# handle only template-based vms
|
||||||
|
return
|
||||||
|
|
||||||
|
template = vm.template
|
||||||
|
if template.features.check_with_template('os', None) != 'Windows':
|
||||||
|
# ignore non-windows templates
|
||||||
|
return
|
||||||
|
|
||||||
|
# TODO: consider copying template's root volume here
|
@ -131,11 +131,14 @@ class TC_00_API_Misc(qubes.tests.QubesTestCase):
|
|||||||
self.assertEqual(self.src.mock_calls, [
|
self.assertEqual(self.src.mock_calls, [
|
||||||
mock.call.untrusted_qdb.read('/qubes-tools/qrexec'),
|
mock.call.untrusted_qdb.read('/qubes-tools/qrexec'),
|
||||||
mock.call.untrusted_qdb.read('/qubes-tools/gui'),
|
mock.call.untrusted_qdb.read('/qubes-tools/gui'),
|
||||||
|
mock.call.untrusted_qdb.read('/qubes-tools/gui-emulated'),
|
||||||
mock.call.untrusted_qdb.read('/qubes-tools/default-user'),
|
mock.call.untrusted_qdb.read('/qubes-tools/default-user'),
|
||||||
|
mock.call.untrusted_qdb.read('/qubes-tools/os'),
|
||||||
mock.call.fire_event_async('features-request', untrusted_features={
|
mock.call.fire_event_async('features-request', untrusted_features={
|
||||||
'gui': '1',
|
'gui': '1',
|
||||||
'default-user': 'user',
|
'default-user': 'user',
|
||||||
'qrexec': '1'}),
|
'qrexec': '1',
|
||||||
|
'os': 'Linux'}),
|
||||||
('fire_event_async().__iter__', (), {}),
|
('fire_event_async().__iter__', (), {}),
|
||||||
])
|
])
|
||||||
self.assertEqual(self.app.mock_calls, [mock.call.save()])
|
self.assertEqual(self.app.mock_calls, [mock.call.save()])
|
||||||
@ -153,11 +156,14 @@ class TC_00_API_Misc(qubes.tests.QubesTestCase):
|
|||||||
self.assertEqual(self.src.mock_calls, [
|
self.assertEqual(self.src.mock_calls, [
|
||||||
mock.call.untrusted_qdb.read('/qubes-tools/qrexec'),
|
mock.call.untrusted_qdb.read('/qubes-tools/qrexec'),
|
||||||
mock.call.untrusted_qdb.read('/qubes-tools/gui'),
|
mock.call.untrusted_qdb.read('/qubes-tools/gui'),
|
||||||
|
mock.call.untrusted_qdb.read('/qubes-tools/gui-emulated'),
|
||||||
mock.call.untrusted_qdb.read('/qubes-tools/default-user'),
|
mock.call.untrusted_qdb.read('/qubes-tools/default-user'),
|
||||||
|
mock.call.untrusted_qdb.read('/qubes-tools/os'),
|
||||||
mock.call.fire_event_async('features-request', untrusted_features={
|
mock.call.fire_event_async('features-request', untrusted_features={
|
||||||
'gui': '1',
|
'gui': '1',
|
||||||
'default-user': 'user',
|
'default-user': 'user',
|
||||||
'qrexec': '1'}),
|
'qrexec': '1',
|
||||||
|
'os': 'Linux'}),
|
||||||
('fire_event_async().__iter__', (), {}),
|
('fire_event_async().__iter__', (), {}),
|
||||||
])
|
])
|
||||||
self.assertEqual(self.app.mock_calls, [mock.call.save()])
|
self.assertEqual(self.app.mock_calls, [mock.call.save()])
|
||||||
|
@ -21,6 +21,7 @@
|
|||||||
from unittest import mock
|
from unittest import mock
|
||||||
|
|
||||||
import qubes.ext.core_features
|
import qubes.ext.core_features
|
||||||
|
import qubes.ext.windows
|
||||||
import qubes.tests
|
import qubes.tests
|
||||||
|
|
||||||
|
|
||||||
@ -163,3 +164,53 @@ class TC_00_CoreFeatures(qubes.tests.QubesTestCase):
|
|||||||
('features.__contains__', ('qrexec',), {}),
|
('features.__contains__', ('qrexec',), {}),
|
||||||
('features.__contains__', ('gui',), {}),
|
('features.__contains__', ('gui',), {}),
|
||||||
])
|
])
|
||||||
|
|
||||||
|
class TC_10_WindowsFeatures(qubes.tests.QubesTestCase):
|
||||||
|
def setUp(self):
|
||||||
|
super().setUp()
|
||||||
|
self.ext = qubes.ext.windows.WindowsFeatures()
|
||||||
|
self.vm = mock.MagicMock()
|
||||||
|
self.features = {}
|
||||||
|
self.vm.configure_mock(**{
|
||||||
|
'features.get.side_effect': self.features.get,
|
||||||
|
'features.__contains__.side_effect': self.features.__contains__,
|
||||||
|
'features.__setitem__.side_effect': self.features.__setitem__,
|
||||||
|
})
|
||||||
|
|
||||||
|
def test_000_notify_tools_full(self):
|
||||||
|
del self.vm.template
|
||||||
|
self.ext.qubes_features_request(self.vm, 'features-request',
|
||||||
|
untrusted_features={
|
||||||
|
'gui': '1',
|
||||||
|
'version': '1',
|
||||||
|
'default-user': 'user',
|
||||||
|
'qrexec': '1',
|
||||||
|
'os': 'Windows'})
|
||||||
|
self.assertEqual(self.vm.mock_calls, [
|
||||||
|
('features.__setitem__', ('os', 'Windows'), {}),
|
||||||
|
('features.__setitem__', ('rpc-clipboard', True), {}),
|
||||||
|
])
|
||||||
|
|
||||||
|
def test_001_notify_tools_no_qrexec(self):
|
||||||
|
del self.vm.template
|
||||||
|
self.ext.qubes_features_request(self.vm, 'features-request',
|
||||||
|
untrusted_features={
|
||||||
|
'gui': '1',
|
||||||
|
'version': '1',
|
||||||
|
'default-user': 'user',
|
||||||
|
'qrexec': '0',
|
||||||
|
'os': 'Windows'})
|
||||||
|
self.assertEqual(self.vm.mock_calls, [
|
||||||
|
('features.__setitem__', ('os', 'Windows'), {}),
|
||||||
|
])
|
||||||
|
|
||||||
|
def test_002_notify_tools_other_os(self):
|
||||||
|
del self.vm.template
|
||||||
|
self.ext.qubes_features_request(self.vm, 'features-request',
|
||||||
|
untrusted_features={
|
||||||
|
'gui': '1',
|
||||||
|
'version': '1',
|
||||||
|
'default-user': 'user',
|
||||||
|
'qrexec': '1',
|
||||||
|
'os': 'Linux'})
|
||||||
|
self.assertEqual(self.vm.mock_calls, [])
|
||||||
|
@ -284,6 +284,7 @@ fi
|
|||||||
%{python3_sitelib}/qubes/ext/qubesmanager.py
|
%{python3_sitelib}/qubes/ext/qubesmanager.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/windows.py
|
||||||
|
|
||||||
%dir %{python3_sitelib}/qubes/tests
|
%dir %{python3_sitelib}/qubes/tests
|
||||||
%dir %{python3_sitelib}/qubes/tests/__pycache__
|
%dir %{python3_sitelib}/qubes/tests/__pycache__
|
||||||
|
1
setup.py
1
setup.py
@ -74,6 +74,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.windows = qubes.ext.windows:WindowsFeatures',
|
||||||
],
|
],
|
||||||
'qubes.devices': [
|
'qubes.devices': [
|
||||||
'pci = qubes.ext.pci:PCIDevice',
|
'pci = qubes.ext.pci:PCIDevice',
|
||||||
|
Loading…
Reference in New Issue
Block a user