Merge branch 'qdb-watch'
* qdb-watch: tests: add qdb_watch test ext/block: make use of QubesDB watch vm: add API for watching changes in QubesDB vm: optimize imports api/admin: don't send internal events in admin.Events Add explanation why admin.vm.volume.Import is a custom script Follow change of qubesdb path return type Rename vm.qdb to vm.untrusted_qdb
This commit is contained in:
commit
639fa26079
@ -1,4 +1,27 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
#
|
||||||
|
# This Admin API call is implemented as a custom script, instead of dumb
|
||||||
|
# passthrough to qubesd because it may get huge amount of data (whole root.img
|
||||||
|
# for example). qubesd cannot handle it because:
|
||||||
|
# 1. It loads the whole payload into memory, before even start looking at it
|
||||||
|
# (and later, do not allow to modify/append it).
|
||||||
|
# 2. There is 64kB limit on payload size that qubesd can handle (because of
|
||||||
|
# point 1).
|
||||||
|
# 3. Performance reasons (qubesd is not optimized for performance, passing
|
||||||
|
# such large data stream through it would take ages).
|
||||||
|
#
|
||||||
|
# The whole admin.vm.volume.Import consists of:
|
||||||
|
# 1. Permissions checks, getting a path from appropriate storage pool (done
|
||||||
|
# by qubesd)
|
||||||
|
# 2. Actual data import (done by this script, using dd)
|
||||||
|
# 3. Report final result, produce final response to the caller (done by
|
||||||
|
# qubesd)
|
||||||
|
#
|
||||||
|
# This way we do not pass all the data through qubesd, but still can
|
||||||
|
# control the process from there in a meaningful way. Note that the last
|
||||||
|
# part (second call to qubesd) may perform all kind of verification (like
|
||||||
|
# a signature check on the data, or so) and can also prevent VM from
|
||||||
|
# starting (hooking also domain-pre-start event) from not verified image.
|
||||||
|
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
|
@ -51,8 +51,18 @@ class QubesMgmtEventsDispatcher(object):
|
|||||||
self.send_event = send_event
|
self.send_event = send_event
|
||||||
|
|
||||||
def vm_handler(self, subject, event, **kwargs):
|
def vm_handler(self, subject, event, **kwargs):
|
||||||
|
# do not send internal events
|
||||||
if event.startswith('admin-permission:'):
|
if event.startswith('admin-permission:'):
|
||||||
return
|
return
|
||||||
|
if event.startswith('device-get:'):
|
||||||
|
return
|
||||||
|
if event.startswith('device-list:'):
|
||||||
|
return
|
||||||
|
if event.startswith('device-list-attached:'):
|
||||||
|
return
|
||||||
|
if event in ('domain-is-fully-usable',):
|
||||||
|
return
|
||||||
|
|
||||||
if not list(qubes.api.apply_filters([(subject, event, kwargs)],
|
if not list(qubes.api.apply_filters([(subject, event, kwargs)],
|
||||||
self.filters)):
|
self.filters)):
|
||||||
return
|
return
|
||||||
|
@ -51,10 +51,9 @@ class QubesMiscAPI(qubes.api.AbstractQubesAPI):
|
|||||||
|
|
||||||
prefix = '/features-request/'
|
prefix = '/features-request/'
|
||||||
|
|
||||||
keys = [key.decode('ascii', errors='strict')
|
keys = self.src.untrusted_qdb.list(prefix)
|
||||||
for key in self.src.qdb.list(prefix)]
|
|
||||||
untrusted_features = {key[len(prefix):]:
|
untrusted_features = {key[len(prefix):]:
|
||||||
self.src.qdb.read(key).decode('ascii', errors='strict')
|
self.src.untrusted_qdb.read(key).decode('ascii', errors='strict')
|
||||||
for key in keys}
|
for key in keys}
|
||||||
|
|
||||||
safe_set = string.ascii_letters + string.digits
|
safe_set = string.ascii_letters + string.digits
|
||||||
@ -79,7 +78,8 @@ class QubesMiscAPI(qubes.api.AbstractQubesAPI):
|
|||||||
safe_set = string.ascii_letters + string.digits
|
safe_set = string.ascii_letters + string.digits
|
||||||
expected_features = ('qrexec', 'gui', 'default-user')
|
expected_features = ('qrexec', 'gui', 'default-user')
|
||||||
for feature in expected_features:
|
for feature in expected_features:
|
||||||
untrusted_value = self.src.qdb.read('/qubes-tools/' + feature)
|
untrusted_value = self.src.untrusted_qdb.read(
|
||||||
|
'/qubes-tools/' + feature)
|
||||||
if untrusted_value:
|
if untrusted_value:
|
||||||
untrusted_value = untrusted_value.decode('ascii',
|
untrusted_value = untrusted_value.decode('ascii',
|
||||||
errors='strict')
|
errors='strict')
|
||||||
|
@ -44,11 +44,17 @@ Such extension should provide:
|
|||||||
domain of given identifier
|
domain of given identifier
|
||||||
- handle `device-list-attached:class` event - list currently attached
|
- handle `device-list-attached:class` event - list currently attached
|
||||||
devices to this domain
|
devices to this domain
|
||||||
|
- fire `device-list-change:class` event when device list change is detected
|
||||||
|
(new/removed device)
|
||||||
|
|
||||||
Note that device-listing event handlers can not be asynchronous. This for
|
Note that device-listing event handlers can not be asynchronous. This for
|
||||||
example means you can not call qrexec service there. This is intentional to
|
example means you can not call qrexec service there. This is intentional to
|
||||||
keep device listing operation cheap. You need to design the extension to take
|
keep device listing operation cheap. You need to design the extension to take
|
||||||
this into account (for example by using QubesDB).
|
this into account (for example by using QubesDB).
|
||||||
|
|
||||||
|
Extension may use QubesDB watch API (QubesVM.watch_qdb_path(path), then handle
|
||||||
|
`domain-qdb-change:path`) to detect changes and fire
|
||||||
|
`device-list-change:class` event.
|
||||||
'''
|
'''
|
||||||
import asyncio
|
import asyncio
|
||||||
|
|
||||||
|
@ -56,7 +56,7 @@ class BlockDevice(qubes.devices.DeviceInfo):
|
|||||||
return self.ident
|
return self.ident
|
||||||
safe_set = {ord(c) for c in
|
safe_set = {ord(c) for c in
|
||||||
string.ascii_letters + string.digits + '()+,-.:=_/ '}
|
string.ascii_letters + string.digits + '()+,-.:=_/ '}
|
||||||
untrusted_desc = self.backend_domain.qdb.read(
|
untrusted_desc = self.backend_domain.untrusted_qdb.read(
|
||||||
'/qubes-block-devices/{}/desc'.format(self.ident))
|
'/qubes-block-devices/{}/desc'.format(self.ident))
|
||||||
desc = ''.join((chr(c) if c in safe_set else '_')
|
desc = ''.join((chr(c) if c in safe_set else '_')
|
||||||
for c in untrusted_desc)
|
for c in untrusted_desc)
|
||||||
@ -69,7 +69,7 @@ class BlockDevice(qubes.devices.DeviceInfo):
|
|||||||
if self._mode is None:
|
if self._mode is None:
|
||||||
if not self.backend_domain.is_running():
|
if not self.backend_domain.is_running():
|
||||||
return 'w'
|
return 'w'
|
||||||
untrusted_mode = self.backend_domain.qdb.read(
|
untrusted_mode = self.backend_domain.untrusted_qdb.read(
|
||||||
'/qubes-block-devices/{}/mode'.format(self.ident))
|
'/qubes-block-devices/{}/mode'.format(self.ident))
|
||||||
if untrusted_mode is None:
|
if untrusted_mode is None:
|
||||||
self._mode = 'w'
|
self._mode = 'w'
|
||||||
@ -87,7 +87,7 @@ class BlockDevice(qubes.devices.DeviceInfo):
|
|||||||
if self._size is None:
|
if self._size is None:
|
||||||
if not self.backend_domain.is_running():
|
if not self.backend_domain.is_running():
|
||||||
return None
|
return None
|
||||||
untrusted_size = self.backend_domain.qdb.read(
|
untrusted_size = self.backend_domain.untrusted_qdb.read(
|
||||||
'/qubes-block-devices/{}/size'.format(self.ident))
|
'/qubes-block-devices/{}/size'.format(self.ident))
|
||||||
if untrusted_size is None:
|
if untrusted_size is None:
|
||||||
self._size = 0
|
self._size = 0
|
||||||
@ -106,6 +106,18 @@ class BlockDevice(qubes.devices.DeviceInfo):
|
|||||||
|
|
||||||
|
|
||||||
class BlockDeviceExtension(qubes.ext.Extension):
|
class BlockDeviceExtension(qubes.ext.Extension):
|
||||||
|
@qubes.ext.handler('domain-init', 'domain-load')
|
||||||
|
def on_domain_init_load(self, vm, event):
|
||||||
|
'''Initialize watching for changes'''
|
||||||
|
# pylint: disable=unused-argument,no-self-use
|
||||||
|
vm.watch_qdb_path('/qubes-block-devices')
|
||||||
|
|
||||||
|
@qubes.ext.handler('domain-qdb-change:/qubes-block-devices')
|
||||||
|
def on_qdb_change(self, vm, event, path):
|
||||||
|
'''A change in QubesDB means a change in device list'''
|
||||||
|
# pylint: disable=unused-argument,no-self-use
|
||||||
|
vm.fire_event('device-list-change:block')
|
||||||
|
|
||||||
def device_get(self, vm, ident):
|
def device_get(self, vm, ident):
|
||||||
# pylint: disable=no-self-use
|
# pylint: disable=no-self-use
|
||||||
'''Read information about device from QubesDB
|
'''Read information about device from QubesDB
|
||||||
@ -114,7 +126,7 @@ class BlockDeviceExtension(qubes.ext.Extension):
|
|||||||
:param ident: device identifier
|
:param ident: device identifier
|
||||||
:returns BlockDevice'''
|
:returns BlockDevice'''
|
||||||
|
|
||||||
untrusted_qubes_device_attrs = vm.qdb.list(
|
untrusted_qubes_device_attrs = vm.untrusted_qdb.list(
|
||||||
'/qubes-block-devices/{}/'.format(ident))
|
'/qubes-block-devices/{}/'.format(ident))
|
||||||
if not untrusted_qubes_device_attrs:
|
if not untrusted_qubes_device_attrs:
|
||||||
return None
|
return None
|
||||||
@ -124,12 +136,11 @@ class BlockDeviceExtension(qubes.ext.Extension):
|
|||||||
def on_device_list_block(self, vm, event):
|
def on_device_list_block(self, vm, event):
|
||||||
# pylint: disable=unused-argument,no-self-use
|
# pylint: disable=unused-argument,no-self-use
|
||||||
|
|
||||||
safe_set = {ord(c) for c in
|
safe_set = string.ascii_letters + string.digits
|
||||||
string.ascii_letters + string.digits}
|
|
||||||
if not vm.is_running():
|
if not vm.is_running():
|
||||||
return
|
return
|
||||||
untrusted_qubes_devices = vm.qdb.list('/qubes-block-devices/')
|
untrusted_qubes_devices = vm.untrusted_qdb.list('/qubes-block-devices/')
|
||||||
untrusted_idents = set(untrusted_path.split(b'/', 3)[2]
|
untrusted_idents = set(untrusted_path.split('/', 3)[2]
|
||||||
for untrusted_path in untrusted_qubes_devices)
|
for untrusted_path in untrusted_qubes_devices)
|
||||||
for untrusted_ident in untrusted_idents:
|
for untrusted_ident in untrusted_idents:
|
||||||
if not all(c in safe_set for c in untrusted_ident):
|
if not all(c in safe_set for c in untrusted_ident):
|
||||||
@ -138,7 +149,7 @@ class BlockDeviceExtension(qubes.ext.Extension):
|
|||||||
vm.log.warning(msg % vm.name)
|
vm.log.warning(msg % vm.name)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
ident = untrusted_ident.decode('ascii', errors='strict')
|
ident = untrusted_ident
|
||||||
|
|
||||||
device_info = self.device_get(vm, ident)
|
device_info = self.device_get(vm, ident)
|
||||||
if device_info:
|
if device_info:
|
||||||
|
@ -60,9 +60,9 @@ class R3Compatibility(qubes.ext.Extension):
|
|||||||
vmtype = 'NetVM'
|
vmtype = 'NetVM'
|
||||||
else:
|
else:
|
||||||
vmtype = 'AppVM'
|
vmtype = 'AppVM'
|
||||||
vm.qdb.write('/qubes-vm-type', vmtype)
|
vm.untrusted_qdb.write('/qubes-vm-type', vmtype)
|
||||||
|
|
||||||
vm.qdb.write("/qubes-iptables-error", '')
|
vm.untrusted_qdb.write("/qubes-iptables-error", '')
|
||||||
self.write_iptables_qubesdb_entry(vm)
|
self.write_iptables_qubesdb_entry(vm)
|
||||||
|
|
||||||
self.write_services(vm)
|
self.write_services(vm)
|
||||||
@ -81,7 +81,7 @@ class R3Compatibility(qubes.ext.Extension):
|
|||||||
|
|
||||||
def write_iptables_qubesdb_entry(self, firewallvm):
|
def write_iptables_qubesdb_entry(self, firewallvm):
|
||||||
# pylint: disable=no-self-use
|
# pylint: disable=no-self-use
|
||||||
firewallvm.qdb.rm("/qubes-iptables-domainrules/")
|
firewallvm.untrusted_qdb.rm("/qubes-iptables-domainrules/")
|
||||||
iptables = "# Generated by Qubes Core on {0}\n".format(
|
iptables = "# Generated by Qubes Core on {0}\n".format(
|
||||||
datetime.datetime.now().ctime())
|
datetime.datetime.now().ctime())
|
||||||
iptables += "*filter\n"
|
iptables += "*filter\n"
|
||||||
@ -102,7 +102,7 @@ class R3Compatibility(qubes.ext.Extension):
|
|||||||
# Deny inter-VMs networking
|
# Deny inter-VMs networking
|
||||||
iptables += "-A FORWARD -i vif+ -o vif+ -j DROP\n"
|
iptables += "-A FORWARD -i vif+ -o vif+ -j DROP\n"
|
||||||
iptables += "COMMIT\n"
|
iptables += "COMMIT\n"
|
||||||
firewallvm.qdb.write("/qubes-iptables-header", iptables)
|
firewallvm.untrusted_qdb.write("/qubes-iptables-header", iptables)
|
||||||
|
|
||||||
for vm in firewallvm.connected_vms:
|
for vm in firewallvm.connected_vms:
|
||||||
iptables = "*filter\n"
|
iptables = "*filter\n"
|
||||||
@ -154,11 +154,12 @@ class R3Compatibility(qubes.ext.Extension):
|
|||||||
iptables += '-A FORWARD -s {0} -j {1}\n'.format(ip,
|
iptables += '-A FORWARD -s {0} -j {1}\n'.format(ip,
|
||||||
str(conf.policy).upper())
|
str(conf.policy).upper())
|
||||||
iptables += 'COMMIT\n'
|
iptables += 'COMMIT\n'
|
||||||
firewallvm.qdb.write('/qubes-iptables-domainrules/' + str(xid),
|
firewallvm.untrusted_qdb.write(
|
||||||
|
'/qubes-iptables-domainrules/' + str(xid),
|
||||||
iptables)
|
iptables)
|
||||||
# no need for ending -A FORWARD -j DROP, cause default action is DROP
|
# no need for ending -A FORWARD -j DROP, cause default action is DROP
|
||||||
|
|
||||||
firewallvm.qdb.write('/qubes-iptables', 'reload')
|
firewallvm.untrusted_qdb.write('/qubes-iptables', 'reload')
|
||||||
|
|
||||||
def write_services(self, vm):
|
def write_services(self, vm):
|
||||||
for feature, value in vm.features.items():
|
for feature, value in vm.features.items():
|
||||||
@ -166,8 +167,9 @@ class R3Compatibility(qubes.ext.Extension):
|
|||||||
if service is None:
|
if service is None:
|
||||||
continue
|
continue
|
||||||
# forcefully convert to '0' or '1'
|
# forcefully convert to '0' or '1'
|
||||||
vm.qdb.write('/qubes-service/{}'.format(service),
|
vm.untrusted_qdb.write('/qubes-service/{}'.format(service),
|
||||||
str(int(bool(value))))
|
str(int(bool(value))))
|
||||||
if 'updates-proxy-setup' in vm.features.keys():
|
if 'updates-proxy-setup' in vm.features.keys():
|
||||||
vm.qdb.write('/qubes-service/{}'.format('yum-proxy-setup'),
|
vm.untrusted_qdb.write(
|
||||||
|
'/qubes-service/{}'.format('yum-proxy-setup'),
|
||||||
str(int(bool(vm.features['updates-proxy-setup']))))
|
str(int(bool(vm.features['updates-proxy-setup']))))
|
||||||
|
@ -419,7 +419,8 @@ class Storage(object):
|
|||||||
# trigger watches to update device status
|
# trigger watches to update device status
|
||||||
# FIXME: this should be removed once libvirt will report such
|
# FIXME: this should be removed once libvirt will report such
|
||||||
# events itself
|
# events itself
|
||||||
# self.vm.qdb.write('/qubes-block-devices', '') ← do we need this?
|
# self.vm.untrusted_qdb.write('/qubes-block-devices', '')
|
||||||
|
# ← do we need this?
|
||||||
|
|
||||||
def _is_already_attached(self, volume):
|
def _is_already_attached(self, volume):
|
||||||
''' Checks if the given volume is already attached '''
|
''' Checks if the given volume is already attached '''
|
||||||
|
@ -37,9 +37,10 @@ class TC_00_API_Misc(qubes.tests.QubesTestCase):
|
|||||||
|
|
||||||
def configure_qdb(self, entries):
|
def configure_qdb(self, entries):
|
||||||
self.src.configure_mock(**{
|
self.src.configure_mock(**{
|
||||||
'qdb.read.side_effect': (lambda path: entries.get(path, None)),
|
'untrusted_qdb.read.side_effect': (
|
||||||
'qdb.list.side_effect': (lambda path:
|
lambda path: entries.get(path, None)),
|
||||||
sorted(map(str.encode, entries.keys()))),
|
'untrusted_qdb.list.side_effect': (
|
||||||
|
lambda path: sorted(entries.keys())),
|
||||||
})
|
})
|
||||||
|
|
||||||
def call_mgmt_func(self, method, arg=b'', payload=b''):
|
def call_mgmt_func(self, method, arg=b'', payload=b''):
|
||||||
@ -64,10 +65,10 @@ class TC_00_API_Misc(qubes.tests.QubesTestCase):
|
|||||||
mock.call.save()
|
mock.call.save()
|
||||||
])
|
])
|
||||||
self.assertEqual(self.src.mock_calls, [
|
self.assertEqual(self.src.mock_calls, [
|
||||||
mock.call.qdb.list('/features-request/'),
|
mock.call.untrusted_qdb.list('/features-request/'),
|
||||||
mock.call.qdb.read('/features-request/feature1'),
|
mock.call.untrusted_qdb.read('/features-request/feature1'),
|
||||||
mock.call.qdb.read('/features-request/feature2'),
|
mock.call.untrusted_qdb.read('/features-request/feature2'),
|
||||||
mock.call.qdb.read('/features-request/feature3'),
|
mock.call.untrusted_qdb.read('/features-request/feature3'),
|
||||||
mock.call.fire_event('features-request', untrusted_features={
|
mock.call.fire_event('features-request', untrusted_features={
|
||||||
'feature1': '1', 'feature2': '', 'feature3': 'other'})
|
'feature1': '1', 'feature2': '', 'feature3': 'other'})
|
||||||
])
|
])
|
||||||
@ -80,7 +81,7 @@ class TC_00_API_Misc(qubes.tests.QubesTestCase):
|
|||||||
mock.call.save()
|
mock.call.save()
|
||||||
])
|
])
|
||||||
self.assertEqual(self.src.mock_calls, [
|
self.assertEqual(self.src.mock_calls, [
|
||||||
mock.call.qdb.list('/features-request/'),
|
mock.call.untrusted_qdb.list('/features-request/'),
|
||||||
mock.call.fire_event('features-request', untrusted_features={})
|
mock.call.fire_event('features-request', untrusted_features={})
|
||||||
])
|
])
|
||||||
|
|
||||||
@ -93,8 +94,8 @@ class TC_00_API_Misc(qubes.tests.QubesTestCase):
|
|||||||
self.call_mgmt_func(b'qubes.FeaturesRequest')
|
self.call_mgmt_func(b'qubes.FeaturesRequest')
|
||||||
self.assertEqual(self.app.mock_calls, [])
|
self.assertEqual(self.app.mock_calls, [])
|
||||||
self.assertEqual(self.src.mock_calls, [
|
self.assertEqual(self.src.mock_calls, [
|
||||||
mock.call.qdb.list('/features-request/'),
|
mock.call.untrusted_qdb.list('/features-request/'),
|
||||||
mock.call.qdb.read('/features-request/feature1'),
|
mock.call.untrusted_qdb.read('/features-request/feature1'),
|
||||||
])
|
])
|
||||||
|
|
||||||
def test_003_features_request_invalid2(self):
|
def test_003_features_request_invalid2(self):
|
||||||
@ -106,8 +107,8 @@ class TC_00_API_Misc(qubes.tests.QubesTestCase):
|
|||||||
self.call_mgmt_func(b'qubes.FeaturesRequest')
|
self.call_mgmt_func(b'qubes.FeaturesRequest')
|
||||||
self.assertEqual(self.app.mock_calls, [])
|
self.assertEqual(self.app.mock_calls, [])
|
||||||
self.assertEqual(self.src.mock_calls, [
|
self.assertEqual(self.src.mock_calls, [
|
||||||
mock.call.qdb.list('/features-request/'),
|
mock.call.untrusted_qdb.list('/features-request/'),
|
||||||
mock.call.qdb.read('/features-request/feature1'),
|
mock.call.untrusted_qdb.read('/features-request/feature1'),
|
||||||
])
|
])
|
||||||
|
|
||||||
def test_010_notify_tools(self):
|
def test_010_notify_tools(self):
|
||||||
@ -125,9 +126,9 @@ class TC_00_API_Misc(qubes.tests.QubesTestCase):
|
|||||||
mock.call.save()
|
mock.call.save()
|
||||||
])
|
])
|
||||||
self.assertEqual(self.src.mock_calls, [
|
self.assertEqual(self.src.mock_calls, [
|
||||||
mock.call.qdb.read('/qubes-tools/qrexec'),
|
mock.call.untrusted_qdb.read('/qubes-tools/qrexec'),
|
||||||
mock.call.qdb.read('/qubes-tools/gui'),
|
mock.call.untrusted_qdb.read('/qubes-tools/gui'),
|
||||||
mock.call.qdb.read('/qubes-tools/default-user'),
|
mock.call.untrusted_qdb.read('/qubes-tools/default-user'),
|
||||||
mock.call.fire_event('features-request', untrusted_features={
|
mock.call.fire_event('features-request', untrusted_features={
|
||||||
'gui': '1',
|
'gui': '1',
|
||||||
'default-user': 'user',
|
'default-user': 'user',
|
||||||
@ -146,9 +147,9 @@ class TC_00_API_Misc(qubes.tests.QubesTestCase):
|
|||||||
response = self.call_mgmt_func(b'qubes.NotifyTools')
|
response = self.call_mgmt_func(b'qubes.NotifyTools')
|
||||||
self.assertIsNone(response)
|
self.assertIsNone(response)
|
||||||
self.assertEqual(self.src.mock_calls, [
|
self.assertEqual(self.src.mock_calls, [
|
||||||
mock.call.qdb.read('/qubes-tools/qrexec'),
|
mock.call.untrusted_qdb.read('/qubes-tools/qrexec'),
|
||||||
mock.call.qdb.read('/qubes-tools/gui'),
|
mock.call.untrusted_qdb.read('/qubes-tools/gui'),
|
||||||
mock.call.qdb.read('/qubes-tools/default-user'),
|
mock.call.untrusted_qdb.read('/qubes-tools/default-user'),
|
||||||
mock.call.fire_event('features-request', untrusted_features={
|
mock.call.fire_event('features-request', untrusted_features={
|
||||||
'gui': '1',
|
'gui': '1',
|
||||||
'default-user': 'user',
|
'default-user': 'user',
|
||||||
@ -169,7 +170,7 @@ class TC_00_API_Misc(qubes.tests.QubesTestCase):
|
|||||||
self.call_mgmt_func(b'qubes.NotifyTools')
|
self.call_mgmt_func(b'qubes.NotifyTools')
|
||||||
self.assertEqual(self.app.mock_calls, [])
|
self.assertEqual(self.app.mock_calls, [])
|
||||||
self.assertEqual(self.src.mock_calls, [
|
self.assertEqual(self.src.mock_calls, [
|
||||||
mock.call.qdb.read('/qubes-tools/qrexec'),
|
mock.call.untrusted_qdb.read('/qubes-tools/qrexec'),
|
||||||
])
|
])
|
||||||
|
|
||||||
def test_016_notify_tools_invalid_value_gui(self):
|
def test_016_notify_tools_invalid_value_gui(self):
|
||||||
@ -185,8 +186,8 @@ class TC_00_API_Misc(qubes.tests.QubesTestCase):
|
|||||||
self.call_mgmt_func(b'qubes.NotifyTools')
|
self.call_mgmt_func(b'qubes.NotifyTools')
|
||||||
self.assertEqual(self.app.mock_calls, [])
|
self.assertEqual(self.app.mock_calls, [])
|
||||||
self.assertEqual(self.src.mock_calls, [
|
self.assertEqual(self.src.mock_calls, [
|
||||||
mock.call.qdb.read('/qubes-tools/qrexec'),
|
mock.call.untrusted_qdb.read('/qubes-tools/qrexec'),
|
||||||
mock.call.qdb.read('/qubes-tools/gui'),
|
mock.call.untrusted_qdb.read('/qubes-tools/gui'),
|
||||||
])
|
])
|
||||||
|
|
||||||
def test_020_notify_updates_standalone(self):
|
def test_020_notify_updates_standalone(self):
|
||||||
|
@ -94,13 +94,9 @@ class TestQubesDB(object):
|
|||||||
self._data = data
|
self._data = data
|
||||||
|
|
||||||
def read(self, key):
|
def read(self, key):
|
||||||
if isinstance(key, str):
|
|
||||||
key = key.encode()
|
|
||||||
return self._data.get(key, None)
|
return self._data.get(key, None)
|
||||||
|
|
||||||
def list(self, prefix):
|
def list(self, prefix):
|
||||||
if isinstance(prefix, str):
|
|
||||||
prefix = prefix.encode()
|
|
||||||
return [key for key in self._data if key.startswith(prefix)]
|
return [key for key in self._data if key.startswith(prefix)]
|
||||||
|
|
||||||
|
|
||||||
@ -120,7 +116,7 @@ class TestApp(object):
|
|||||||
class TestVM(object):
|
class TestVM(object):
|
||||||
def __init__(self, qdb, domain_xml=None, running=True, name='test-vm'):
|
def __init__(self, qdb, domain_xml=None, running=True, name='test-vm'):
|
||||||
self.name = name
|
self.name = name
|
||||||
self.qdb = TestQubesDB(qdb)
|
self.untrusted_qdb = TestQubesDB(qdb)
|
||||||
self.libvirt_domain = mock.Mock()
|
self.libvirt_domain = mock.Mock()
|
||||||
self.is_running = lambda: running
|
self.is_running = lambda: running
|
||||||
self.log = mock.Mock()
|
self.log = mock.Mock()
|
||||||
@ -143,10 +139,10 @@ class TC_00_Block(qubes.tests.QubesTestCase):
|
|||||||
|
|
||||||
def test_000_device_get(self):
|
def test_000_device_get(self):
|
||||||
vm = TestVM({
|
vm = TestVM({
|
||||||
b'/qubes-block-devices/sda': b'',
|
'/qubes-block-devices/sda': b'',
|
||||||
b'/qubes-block-devices/sda/desc': b'Test device',
|
'/qubes-block-devices/sda/desc': b'Test device',
|
||||||
b'/qubes-block-devices/sda/size': b'1024000',
|
'/qubes-block-devices/sda/size': b'1024000',
|
||||||
b'/qubes-block-devices/sda/mode': b'w',
|
'/qubes-block-devices/sda/mode': b'w',
|
||||||
})
|
})
|
||||||
device_info = self.ext.device_get(vm, 'sda')
|
device_info = self.ext.device_get(vm, 'sda')
|
||||||
self.assertIsInstance(device_info, qubes.ext.block.BlockDevice)
|
self.assertIsInstance(device_info, qubes.ext.block.BlockDevice)
|
||||||
@ -161,10 +157,10 @@ class TC_00_Block(qubes.tests.QubesTestCase):
|
|||||||
|
|
||||||
def test_001_device_get_other_node(self):
|
def test_001_device_get_other_node(self):
|
||||||
vm = TestVM({
|
vm = TestVM({
|
||||||
b'/qubes-block-devices/mapper_dmroot': b'',
|
'/qubes-block-devices/mapper_dmroot': b'',
|
||||||
b'/qubes-block-devices/mapper_dmroot/desc': b'Test device',
|
'/qubes-block-devices/mapper_dmroot/desc': b'Test device',
|
||||||
b'/qubes-block-devices/mapper_dmroot/size': b'1024000',
|
'/qubes-block-devices/mapper_dmroot/size': b'1024000',
|
||||||
b'/qubes-block-devices/mapper_dmroot/mode': b'w',
|
'/qubes-block-devices/mapper_dmroot/mode': b'w',
|
||||||
})
|
})
|
||||||
device_info = self.ext.device_get(vm, 'mapper_dmroot')
|
device_info = self.ext.device_get(vm, 'mapper_dmroot')
|
||||||
self.assertIsInstance(device_info, qubes.ext.block.BlockDevice)
|
self.assertIsInstance(device_info, qubes.ext.block.BlockDevice)
|
||||||
@ -179,20 +175,20 @@ class TC_00_Block(qubes.tests.QubesTestCase):
|
|||||||
|
|
||||||
def test_002_device_get_invalid_desc(self):
|
def test_002_device_get_invalid_desc(self):
|
||||||
vm = TestVM({
|
vm = TestVM({
|
||||||
b'/qubes-block-devices/sda': b'',
|
'/qubes-block-devices/sda': b'',
|
||||||
b'/qubes-block-devices/sda/desc': b'Test device<>za\xc4\x87abc',
|
'/qubes-block-devices/sda/desc': b'Test device<>za\xc4\x87abc',
|
||||||
b'/qubes-block-devices/sda/size': b'1024000',
|
'/qubes-block-devices/sda/size': b'1024000',
|
||||||
b'/qubes-block-devices/sda/mode': b'w',
|
'/qubes-block-devices/sda/mode': b'w',
|
||||||
})
|
})
|
||||||
device_info = self.ext.device_get(vm, 'sda')
|
device_info = self.ext.device_get(vm, 'sda')
|
||||||
self.assertEqual(device_info.description, 'Test device__za__abc')
|
self.assertEqual(device_info.description, 'Test device__za__abc')
|
||||||
|
|
||||||
def test_003_device_get_invalid_size(self):
|
def test_003_device_get_invalid_size(self):
|
||||||
vm = TestVM({
|
vm = TestVM({
|
||||||
b'/qubes-block-devices/sda': b'',
|
'/qubes-block-devices/sda': b'',
|
||||||
b'/qubes-block-devices/sda/desc': b'Test device',
|
'/qubes-block-devices/sda/desc': b'Test device',
|
||||||
b'/qubes-block-devices/sda/size': b'1024000abc',
|
'/qubes-block-devices/sda/size': b'1024000abc',
|
||||||
b'/qubes-block-devices/sda/mode': b'w',
|
'/qubes-block-devices/sda/mode': b'w',
|
||||||
})
|
})
|
||||||
device_info = self.ext.device_get(vm, 'sda')
|
device_info = self.ext.device_get(vm, 'sda')
|
||||||
self.assertEqual(device_info.size, 0)
|
self.assertEqual(device_info.size, 0)
|
||||||
@ -200,10 +196,10 @@ class TC_00_Block(qubes.tests.QubesTestCase):
|
|||||||
|
|
||||||
def test_004_device_get_invalid_mode(self):
|
def test_004_device_get_invalid_mode(self):
|
||||||
vm = TestVM({
|
vm = TestVM({
|
||||||
b'/qubes-block-devices/sda': b'',
|
'/qubes-block-devices/sda': b'',
|
||||||
b'/qubes-block-devices/sda/desc': b'Test device',
|
'/qubes-block-devices/sda/desc': b'Test device',
|
||||||
b'/qubes-block-devices/sda/size': b'1024000',
|
'/qubes-block-devices/sda/size': b'1024000',
|
||||||
b'/qubes-block-devices/sda/mode': b'abc',
|
'/qubes-block-devices/sda/mode': b'abc',
|
||||||
})
|
})
|
||||||
device_info = self.ext.device_get(vm, 'sda')
|
device_info = self.ext.device_get(vm, 'sda')
|
||||||
self.assertEqual(device_info.mode, 'w')
|
self.assertEqual(device_info.mode, 'w')
|
||||||
@ -211,24 +207,24 @@ class TC_00_Block(qubes.tests.QubesTestCase):
|
|||||||
|
|
||||||
def test_005_device_get_none(self):
|
def test_005_device_get_none(self):
|
||||||
vm = TestVM({
|
vm = TestVM({
|
||||||
b'/qubes-block-devices/sda': b'',
|
'/qubes-block-devices/sda': b'',
|
||||||
b'/qubes-block-devices/sda/desc': b'Test device',
|
'/qubes-block-devices/sda/desc': b'Test device',
|
||||||
b'/qubes-block-devices/sda/size': b'1024000',
|
'/qubes-block-devices/sda/size': b'1024000',
|
||||||
b'/qubes-block-devices/sda/mode': b'w',
|
'/qubes-block-devices/sda/mode': b'w',
|
||||||
})
|
})
|
||||||
device_info = self.ext.device_get(vm, 'sdb')
|
device_info = self.ext.device_get(vm, 'sdb')
|
||||||
self.assertIsNone(device_info)
|
self.assertIsNone(device_info)
|
||||||
|
|
||||||
def test_010_devices_list(self):
|
def test_010_devices_list(self):
|
||||||
vm = TestVM({
|
vm = TestVM({
|
||||||
b'/qubes-block-devices/sda': b'',
|
'/qubes-block-devices/sda': b'',
|
||||||
b'/qubes-block-devices/sda/desc': b'Test device',
|
'/qubes-block-devices/sda/desc': b'Test device',
|
||||||
b'/qubes-block-devices/sda/size': b'1024000',
|
'/qubes-block-devices/sda/size': b'1024000',
|
||||||
b'/qubes-block-devices/sda/mode': b'w',
|
'/qubes-block-devices/sda/mode': b'w',
|
||||||
b'/qubes-block-devices/sdb': b'',
|
'/qubes-block-devices/sdb': b'',
|
||||||
b'/qubes-block-devices/sdb/desc': b'Test device2',
|
'/qubes-block-devices/sdb/desc': b'Test device2',
|
||||||
b'/qubes-block-devices/sdb/size': b'2048000',
|
'/qubes-block-devices/sdb/size': b'2048000',
|
||||||
b'/qubes-block-devices/sdb/mode': b'r',
|
'/qubes-block-devices/sdb/mode': b'r',
|
||||||
})
|
})
|
||||||
devices = sorted(list(self.ext.on_device_list_block(vm, '')))
|
devices = sorted(list(self.ext.on_device_list_block(vm, '')))
|
||||||
self.assertEqual(len(devices), 2)
|
self.assertEqual(len(devices), 2)
|
||||||
@ -250,9 +246,9 @@ class TC_00_Block(qubes.tests.QubesTestCase):
|
|||||||
|
|
||||||
def test_012_devices_list_invalid_ident(self):
|
def test_012_devices_list_invalid_ident(self):
|
||||||
vm = TestVM({
|
vm = TestVM({
|
||||||
b'/qubes-block-devices/invalid ident': b'',
|
'/qubes-block-devices/invalid ident': b'',
|
||||||
b'/qubes-block-devices/invalid+ident': b'',
|
'/qubes-block-devices/invalid+ident': b'',
|
||||||
b'/qubes-block-devices/invalid#': b'',
|
'/qubes-block-devices/invalid#': b'',
|
||||||
})
|
})
|
||||||
devices = sorted(list(self.ext.on_device_list_block(vm, '')))
|
devices = sorted(list(self.ext.on_device_list_block(vm, '')))
|
||||||
self.assertEqual(len(devices), 0)
|
self.assertEqual(len(devices), 0)
|
||||||
@ -334,10 +330,10 @@ class TC_00_Block(qubes.tests.QubesTestCase):
|
|||||||
|
|
||||||
def test_040_attach(self):
|
def test_040_attach(self):
|
||||||
back_vm = TestVM(name='sys-usb', qdb={
|
back_vm = TestVM(name='sys-usb', qdb={
|
||||||
b'/qubes-block-devices/sda': b'',
|
'/qubes-block-devices/sda': b'',
|
||||||
b'/qubes-block-devices/sda/desc': b'Test device',
|
'/qubes-block-devices/sda/desc': b'Test device',
|
||||||
b'/qubes-block-devices/sda/size': b'1024000',
|
'/qubes-block-devices/sda/size': b'1024000',
|
||||||
b'/qubes-block-devices/sda/mode': b'w',
|
'/qubes-block-devices/sda/mode': b'w',
|
||||||
})
|
})
|
||||||
vm = TestVM({}, domain_xml=domain_xml_template.format(''))
|
vm = TestVM({}, domain_xml=domain_xml_template.format(''))
|
||||||
dev = qubes.ext.block.BlockDevice(back_vm, 'sda')
|
dev = qubes.ext.block.BlockDevice(back_vm, 'sda')
|
||||||
@ -353,10 +349,10 @@ class TC_00_Block(qubes.tests.QubesTestCase):
|
|||||||
|
|
||||||
def test_041_attach_frontend(self):
|
def test_041_attach_frontend(self):
|
||||||
back_vm = TestVM(name='sys-usb', qdb={
|
back_vm = TestVM(name='sys-usb', qdb={
|
||||||
b'/qubes-block-devices/sda': b'',
|
'/qubes-block-devices/sda': b'',
|
||||||
b'/qubes-block-devices/sda/desc': b'Test device',
|
'/qubes-block-devices/sda/desc': b'Test device',
|
||||||
b'/qubes-block-devices/sda/size': b'1024000',
|
'/qubes-block-devices/sda/size': b'1024000',
|
||||||
b'/qubes-block-devices/sda/mode': b'w',
|
'/qubes-block-devices/sda/mode': b'w',
|
||||||
})
|
})
|
||||||
vm = TestVM({}, domain_xml=domain_xml_template.format(''))
|
vm = TestVM({}, domain_xml=domain_xml_template.format(''))
|
||||||
dev = qubes.ext.block.BlockDevice(back_vm, 'sda')
|
dev = qubes.ext.block.BlockDevice(back_vm, 'sda')
|
||||||
@ -373,10 +369,10 @@ class TC_00_Block(qubes.tests.QubesTestCase):
|
|||||||
|
|
||||||
def test_042_attach_read_only(self):
|
def test_042_attach_read_only(self):
|
||||||
back_vm = TestVM(name='sys-usb', qdb={
|
back_vm = TestVM(name='sys-usb', qdb={
|
||||||
b'/qubes-block-devices/sda': b'',
|
'/qubes-block-devices/sda': b'',
|
||||||
b'/qubes-block-devices/sda/desc': b'Test device',
|
'/qubes-block-devices/sda/desc': b'Test device',
|
||||||
b'/qubes-block-devices/sda/size': b'1024000',
|
'/qubes-block-devices/sda/size': b'1024000',
|
||||||
b'/qubes-block-devices/sda/mode': b'w',
|
'/qubes-block-devices/sda/mode': b'w',
|
||||||
})
|
})
|
||||||
vm = TestVM({}, domain_xml=domain_xml_template.format(''))
|
vm = TestVM({}, domain_xml=domain_xml_template.format(''))
|
||||||
dev = qubes.ext.block.BlockDevice(back_vm, 'sda')
|
dev = qubes.ext.block.BlockDevice(back_vm, 'sda')
|
||||||
@ -394,10 +390,10 @@ class TC_00_Block(qubes.tests.QubesTestCase):
|
|||||||
|
|
||||||
def test_043_attach_invalid_option(self):
|
def test_043_attach_invalid_option(self):
|
||||||
back_vm = TestVM(name='sys-usb', qdb={
|
back_vm = TestVM(name='sys-usb', qdb={
|
||||||
b'/qubes-block-devices/sda': b'',
|
'/qubes-block-devices/sda': b'',
|
||||||
b'/qubes-block-devices/sda/desc': b'Test device',
|
'/qubes-block-devices/sda/desc': b'Test device',
|
||||||
b'/qubes-block-devices/sda/size': b'1024000',
|
'/qubes-block-devices/sda/size': b'1024000',
|
||||||
b'/qubes-block-devices/sda/mode': b'w',
|
'/qubes-block-devices/sda/mode': b'w',
|
||||||
})
|
})
|
||||||
vm = TestVM({}, domain_xml=domain_xml_template.format(''))
|
vm = TestVM({}, domain_xml=domain_xml_template.format(''))
|
||||||
dev = qubes.ext.block.BlockDevice(back_vm, 'sda')
|
dev = qubes.ext.block.BlockDevice(back_vm, 'sda')
|
||||||
@ -408,10 +404,10 @@ class TC_00_Block(qubes.tests.QubesTestCase):
|
|||||||
|
|
||||||
def test_044_attach_invalid_option2(self):
|
def test_044_attach_invalid_option2(self):
|
||||||
back_vm = TestVM(name='sys-usb', qdb={
|
back_vm = TestVM(name='sys-usb', qdb={
|
||||||
b'/qubes-block-devices/sda': b'',
|
'/qubes-block-devices/sda': b'',
|
||||||
b'/qubes-block-devices/sda/desc': b'Test device',
|
'/qubes-block-devices/sda/desc': b'Test device',
|
||||||
b'/qubes-block-devices/sda/size': b'1024000',
|
'/qubes-block-devices/sda/size': b'1024000',
|
||||||
b'/qubes-block-devices/sda/mode': b'w',
|
'/qubes-block-devices/sda/mode': b'w',
|
||||||
})
|
})
|
||||||
vm = TestVM({}, domain_xml=domain_xml_template.format(''))
|
vm = TestVM({}, domain_xml=domain_xml_template.format(''))
|
||||||
dev = qubes.ext.block.BlockDevice(back_vm, 'sda')
|
dev = qubes.ext.block.BlockDevice(back_vm, 'sda')
|
||||||
@ -422,10 +418,10 @@ class TC_00_Block(qubes.tests.QubesTestCase):
|
|||||||
|
|
||||||
def test_045_attach_backend_not_running(self):
|
def test_045_attach_backend_not_running(self):
|
||||||
back_vm = TestVM(name='sys-usb', running=False, qdb={
|
back_vm = TestVM(name='sys-usb', running=False, qdb={
|
||||||
b'/qubes-block-devices/sda': b'',
|
'/qubes-block-devices/sda': b'',
|
||||||
b'/qubes-block-devices/sda/desc': b'Test device',
|
'/qubes-block-devices/sda/desc': b'Test device',
|
||||||
b'/qubes-block-devices/sda/size': b'1024000',
|
'/qubes-block-devices/sda/size': b'1024000',
|
||||||
b'/qubes-block-devices/sda/mode': b'w',
|
'/qubes-block-devices/sda/mode': b'w',
|
||||||
})
|
})
|
||||||
vm = TestVM({}, domain_xml=domain_xml_template.format(''))
|
vm = TestVM({}, domain_xml=domain_xml_template.format(''))
|
||||||
dev = qubes.ext.block.BlockDevice(back_vm, 'sda')
|
dev = qubes.ext.block.BlockDevice(back_vm, 'sda')
|
||||||
@ -435,10 +431,10 @@ class TC_00_Block(qubes.tests.QubesTestCase):
|
|||||||
|
|
||||||
def test_046_attach_ro_dev_rw(self):
|
def test_046_attach_ro_dev_rw(self):
|
||||||
back_vm = TestVM(name='sys-usb', qdb={
|
back_vm = TestVM(name='sys-usb', qdb={
|
||||||
b'/qubes-block-devices/sda': b'',
|
'/qubes-block-devices/sda': b'',
|
||||||
b'/qubes-block-devices/sda/desc': b'Test device',
|
'/qubes-block-devices/sda/desc': b'Test device',
|
||||||
b'/qubes-block-devices/sda/size': b'1024000',
|
'/qubes-block-devices/sda/size': b'1024000',
|
||||||
b'/qubes-block-devices/sda/mode': b'r',
|
'/qubes-block-devices/sda/mode': b'r',
|
||||||
})
|
})
|
||||||
vm = TestVM({}, domain_xml=domain_xml_template.format(''))
|
vm = TestVM({}, domain_xml=domain_xml_template.format(''))
|
||||||
dev = qubes.ext.block.BlockDevice(back_vm, 'sda')
|
dev = qubes.ext.block.BlockDevice(back_vm, 'sda')
|
||||||
@ -449,10 +445,10 @@ class TC_00_Block(qubes.tests.QubesTestCase):
|
|||||||
|
|
||||||
def test_047_attach_read_only_auto(self):
|
def test_047_attach_read_only_auto(self):
|
||||||
back_vm = TestVM(name='sys-usb', qdb={
|
back_vm = TestVM(name='sys-usb', qdb={
|
||||||
b'/qubes-block-devices/sda': b'',
|
'/qubes-block-devices/sda': b'',
|
||||||
b'/qubes-block-devices/sda/desc': b'Test device',
|
'/qubes-block-devices/sda/desc': b'Test device',
|
||||||
b'/qubes-block-devices/sda/size': b'1024000',
|
'/qubes-block-devices/sda/size': b'1024000',
|
||||||
b'/qubes-block-devices/sda/mode': b'r',
|
'/qubes-block-devices/sda/mode': b'r',
|
||||||
})
|
})
|
||||||
vm = TestVM({}, domain_xml=domain_xml_template.format(''))
|
vm = TestVM({}, domain_xml=domain_xml_template.format(''))
|
||||||
dev = qubes.ext.block.BlockDevice(back_vm, 'sda')
|
dev = qubes.ext.block.BlockDevice(back_vm, 'sda')
|
||||||
@ -469,10 +465,10 @@ class TC_00_Block(qubes.tests.QubesTestCase):
|
|||||||
|
|
||||||
def test_050_detach(self):
|
def test_050_detach(self):
|
||||||
back_vm = TestVM(name='sys-usb', qdb={
|
back_vm = TestVM(name='sys-usb', qdb={
|
||||||
b'/qubes-block-devices/sda': b'',
|
'/qubes-block-devices/sda': b'',
|
||||||
b'/qubes-block-devices/sda/desc': b'Test device',
|
'/qubes-block-devices/sda/desc': b'Test device',
|
||||||
b'/qubes-block-devices/sda/size': b'1024000',
|
'/qubes-block-devices/sda/size': b'1024000',
|
||||||
b'/qubes-block-devices/sda/mode': b'r',
|
'/qubes-block-devices/sda/mode': b'r',
|
||||||
})
|
})
|
||||||
device_xml = (
|
device_xml = (
|
||||||
'<disk type="block" device="disk">\n'
|
'<disk type="block" device="disk">\n'
|
||||||
@ -491,10 +487,10 @@ class TC_00_Block(qubes.tests.QubesTestCase):
|
|||||||
|
|
||||||
def test_051_detach_not_attached(self):
|
def test_051_detach_not_attached(self):
|
||||||
back_vm = TestVM(name='sys-usb', qdb={
|
back_vm = TestVM(name='sys-usb', qdb={
|
||||||
b'/qubes-block-devices/sda': b'',
|
'/qubes-block-devices/sda': b'',
|
||||||
b'/qubes-block-devices/sda/desc': b'Test device',
|
'/qubes-block-devices/sda/desc': b'Test device',
|
||||||
b'/qubes-block-devices/sda/size': b'1024000',
|
'/qubes-block-devices/sda/size': b'1024000',
|
||||||
b'/qubes-block-devices/sda/mode': b'r',
|
'/qubes-block-devices/sda/mode': b'r',
|
||||||
})
|
})
|
||||||
device_xml = (
|
device_xml = (
|
||||||
'<disk type="block" device="disk">\n'
|
'<disk type="block" device="disk">\n'
|
||||||
|
@ -65,6 +65,20 @@ class TC_00_Basic(qubes.tests.SystemTestCase):
|
|||||||
with self.assertNotRaises(qubes.exc.QubesException):
|
with self.assertNotRaises(qubes.exc.QubesException):
|
||||||
vm.storage.verify()
|
vm.storage.verify()
|
||||||
|
|
||||||
|
def test_040_qdb_watch(self):
|
||||||
|
flag = set()
|
||||||
|
|
||||||
|
def handler(vm, event, path):
|
||||||
|
if path == '/test-watch-path':
|
||||||
|
flag.add(True)
|
||||||
|
|
||||||
|
vm = self.app.domains[0]
|
||||||
|
vm.watch_qdb_path('/test-watch-path')
|
||||||
|
vm.add_handler('domain-qdb-change:/test-watch-path', handler)
|
||||||
|
self.assertFalse(flag)
|
||||||
|
vm.untrusted_qdb.write('/test-watch-path', 'test-value')
|
||||||
|
self.loop.run_until_complete(asyncio.sleep(0.1))
|
||||||
|
self.assertTrue(flag)
|
||||||
|
|
||||||
class TC_01_Properties(qubes.tests.SystemTestCase):
|
class TC_01_Properties(qubes.tests.SystemTestCase):
|
||||||
# pylint: disable=attribute-defined-outside-init
|
# pylint: disable=attribute-defined-outside-init
|
||||||
|
@ -24,14 +24,9 @@
|
|||||||
'''Qubes Virtual Machines
|
'''Qubes Virtual Machines
|
||||||
|
|
||||||
'''
|
'''
|
||||||
|
import asyncio
|
||||||
import datetime
|
|
||||||
import os
|
|
||||||
import re
|
import re
|
||||||
import string
|
import string
|
||||||
import subprocess
|
|
||||||
import sys
|
|
||||||
import xml.parsers.expat
|
|
||||||
|
|
||||||
import lxml.etree
|
import lxml.etree
|
||||||
|
|
||||||
@ -271,6 +266,8 @@ class BaseVM(qubes.PropertyHolder):
|
|||||||
# self.app must be set before super().__init__, because some property
|
# self.app must be set before super().__init__, because some property
|
||||||
# setters need working .app attribute
|
# setters need working .app attribute
|
||||||
#: mother :py:class:`qubes.Qubes` object
|
#: mother :py:class:`qubes.Qubes` object
|
||||||
|
self._qdb_watch_paths = set()
|
||||||
|
self._qdb_connection_watch = None
|
||||||
self.app = app
|
self.app = app
|
||||||
|
|
||||||
super(BaseVM, self).__init__(xml, **kwargs)
|
super(BaseVM, self).__init__(xml, **kwargs)
|
||||||
@ -393,6 +390,55 @@ class BaseVM(qubes.PropertyHolder):
|
|||||||
]).render(vm=self)
|
]).render(vm=self)
|
||||||
return domain_config
|
return domain_config
|
||||||
|
|
||||||
|
def watch_qdb_path(self, path):
|
||||||
|
'''Add a QubesDB path to be watched.
|
||||||
|
|
||||||
|
Each change to the path will cause `domain-qdb-change:path` event to be
|
||||||
|
fired.
|
||||||
|
You can call this method for example in response to
|
||||||
|
`domain-init` and `domain-load` events.
|
||||||
|
'''
|
||||||
|
|
||||||
|
if path not in self._qdb_watch_paths:
|
||||||
|
self._qdb_watch_paths.add(path)
|
||||||
|
if self._qdb_connection_watch:
|
||||||
|
self._qdb_connection_watch.watch(path)
|
||||||
|
|
||||||
|
def _qdb_watch_reader(self, loop):
|
||||||
|
'''Callback when self._qdb_connection_watch.watch_fd() FD is
|
||||||
|
readable.
|
||||||
|
|
||||||
|
Read reported event (watched path change) and fire appropriate event.
|
||||||
|
'''
|
||||||
|
import qubesdb # pylint: disable=import-error
|
||||||
|
try:
|
||||||
|
path = self._qdb_connection_watch.read_watch()
|
||||||
|
for watched_path in self._qdb_watch_paths:
|
||||||
|
if watched_path == path or (
|
||||||
|
watched_path.endswith('/') and
|
||||||
|
path.startswith(watched_path)):
|
||||||
|
self.fire_event('domain-qdb-change:' + watched_path,
|
||||||
|
path=path)
|
||||||
|
except qubesdb.DisconnectedError:
|
||||||
|
loop.remove_reader(self._qdb_connection_watch.watch_fd())
|
||||||
|
self._qdb_connection_watch.close()
|
||||||
|
self._qdb_connection_watch = None
|
||||||
|
|
||||||
|
def start_qdb_watch(self, name, loop=None):
|
||||||
|
'''Start watching QubesDB
|
||||||
|
|
||||||
|
Calling this method in appropriate time is responsibility of child
|
||||||
|
class.
|
||||||
|
'''
|
||||||
|
import qubesdb # pylint: disable=import-error
|
||||||
|
self._qdb_connection_watch = qubesdb.QubesDB(name)
|
||||||
|
if loop is None:
|
||||||
|
loop = asyncio.get_event_loop()
|
||||||
|
loop.add_reader(self._qdb_connection_watch.watch_fd(),
|
||||||
|
self._qdb_watch_reader, loop)
|
||||||
|
for path in self._qdb_watch_paths:
|
||||||
|
self._qdb_connection_watch.watch(path)
|
||||||
|
|
||||||
|
|
||||||
class VMProperty(qubes.property):
|
class VMProperty(qubes.property):
|
||||||
'''Property that is referring to a VM
|
'''Property that is referring to a VM
|
||||||
|
@ -55,6 +55,9 @@ class AdminVM(qubes.vm.BaseVM):
|
|||||||
self._qdb_connection = None
|
self._qdb_connection = None
|
||||||
self._libvirt_domain = None
|
self._libvirt_domain = None
|
||||||
|
|
||||||
|
if not self.app.vmm.offline_mode:
|
||||||
|
self.start_qdb_watch('dom0')
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
@ -167,7 +170,7 @@ class AdminVM(qubes.vm.BaseVM):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def qdb(self):
|
def untrusted_qdb(self):
|
||||||
'''QubesDB handle for this domain.'''
|
'''QubesDB handle for this domain.'''
|
||||||
if self._qdb_connection is None:
|
if self._qdb_connection is None:
|
||||||
import qubesdb # pylint: disable=import-error
|
import qubesdb # pylint: disable=import-error
|
||||||
|
@ -291,12 +291,12 @@ class NetVMMixin(qubes.events.Emitter):
|
|||||||
base_dir = '/qubes-firewall/' + vm.ip + '/'
|
base_dir = '/qubes-firewall/' + vm.ip + '/'
|
||||||
# remove old entries if any (but don't touch base empty entry - it
|
# remove old entries if any (but don't touch base empty entry - it
|
||||||
# would trigger reload right away
|
# would trigger reload right away
|
||||||
self.qdb.rm(base_dir)
|
self.untrusted_qdb.rm(base_dir)
|
||||||
# write new rules
|
# write new rules
|
||||||
for key, value in vm.firewall.qdb_entries(addr_family=4).items():
|
for key, value in vm.firewall.qdb_entries(addr_family=4).items():
|
||||||
self.qdb.write(base_dir + key, value)
|
self.untrusted_qdb.write(base_dir + key, value)
|
||||||
# signal its done
|
# signal its done
|
||||||
self.qdb.write(base_dir[:-1], '')
|
self.untrusted_qdb.write(base_dir[:-1], '')
|
||||||
|
|
||||||
def set_mapped_ip_info_for_vm(self, vm):
|
def set_mapped_ip_info_for_vm(self, vm):
|
||||||
'''
|
'''
|
||||||
@ -307,14 +307,15 @@ class NetVMMixin(qubes.events.Emitter):
|
|||||||
# add info about remapped IPs (VM IP hidden from the VM itself)
|
# add info about remapped IPs (VM IP hidden from the VM itself)
|
||||||
mapped_ip_base = '/mapped-ip/{}'.format(vm.ip)
|
mapped_ip_base = '/mapped-ip/{}'.format(vm.ip)
|
||||||
if vm.visible_ip:
|
if vm.visible_ip:
|
||||||
self.qdb.write(mapped_ip_base + '/visible-ip', vm.visible_ip)
|
self.untrusted_qdb.write(mapped_ip_base + '/visible-ip',
|
||||||
|
vm.visible_ip)
|
||||||
else:
|
else:
|
||||||
self.qdb.rm(mapped_ip_base + '/visible-ip')
|
self.untrusted_qdb.rm(mapped_ip_base + '/visible-ip')
|
||||||
if vm.visible_gateway:
|
if vm.visible_gateway:
|
||||||
self.qdb.write(mapped_ip_base + '/visible-gateway',
|
self.untrusted_qdb.write(mapped_ip_base + '/visible-gateway',
|
||||||
vm.visible_gateway)
|
vm.visible_gateway)
|
||||||
else:
|
else:
|
||||||
self.qdb.rm(mapped_ip_base + '/visible-gateway')
|
self.untrusted_qdb.rm(mapped_ip_base + '/visible-gateway')
|
||||||
|
|
||||||
|
|
||||||
@qubes.events.handler('property-del:netvm')
|
@qubes.events.handler('property-del:netvm')
|
||||||
|
@ -26,6 +26,8 @@ from __future__ import absolute_import
|
|||||||
import asyncio
|
import asyncio
|
||||||
import base64
|
import base64
|
||||||
import datetime
|
import datetime
|
||||||
|
import errno
|
||||||
|
import grp
|
||||||
import os
|
import os
|
||||||
import os.path
|
import os.path
|
||||||
import shutil
|
import shutil
|
||||||
@ -34,11 +36,8 @@ import subprocess
|
|||||||
import uuid
|
import uuid
|
||||||
import warnings
|
import warnings
|
||||||
|
|
||||||
import grp
|
|
||||||
|
|
||||||
import errno
|
|
||||||
import lxml
|
|
||||||
import libvirt # pylint: disable=import-error
|
import libvirt # pylint: disable=import-error
|
||||||
|
import lxml
|
||||||
|
|
||||||
import qubes
|
import qubes
|
||||||
import qubes.config
|
import qubes.config
|
||||||
@ -249,6 +248,18 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM):
|
|||||||
|
|
||||||
This event is a good place to add your custom entries to the qdb.
|
This event is a good place to add your custom entries to the qdb.
|
||||||
|
|
||||||
|
.. event:: domain-qdb-change:watched-path (subject, event, path)
|
||||||
|
|
||||||
|
Fired when watched QubesDB entry is changed. See
|
||||||
|
:py:meth:`watch_qdb_path`. *watched-path* part of event name is
|
||||||
|
what path was registered for watching, *path* in event argument
|
||||||
|
is what actually have changed (which may be different if watching a
|
||||||
|
directory, i.e. a path with `/` at the end).
|
||||||
|
|
||||||
|
:param subject: Event emitter (the qube object)
|
||||||
|
:param event: Event name (``'domain-qdb-change'``)
|
||||||
|
:param path: changed QubesDB path
|
||||||
|
|
||||||
.. event:: backup-get-files (subject, event)
|
.. event:: backup-get-files (subject, event)
|
||||||
|
|
||||||
Collects additional file to be included in a backup.
|
Collects additional file to be included in a backup.
|
||||||
@ -590,7 +601,7 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM):
|
|||||||
yield block_dev
|
yield block_dev
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def qdb(self):
|
def untrusted_qdb(self):
|
||||||
'''QubesDB handle for this domain.'''
|
'''QubesDB handle for this domain.'''
|
||||||
if self._qdb_connection is None:
|
if self._qdb_connection is None:
|
||||||
if self.is_running():
|
if self.is_running():
|
||||||
@ -727,6 +738,9 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM):
|
|||||||
if self.storage is None:
|
if self.storage is None:
|
||||||
self.storage = qubes.storage.Storage(self)
|
self.storage = qubes.storage.Storage(self)
|
||||||
|
|
||||||
|
if not self.app.vmm.offline_mode and self.is_running():
|
||||||
|
self.start_qdb_watch(self.name)
|
||||||
|
|
||||||
@qubes.events.handler('property-set:label')
|
@qubes.events.handler('property-set:label')
|
||||||
def on_property_set_label(self, event, name, newvalue, oldvalue=None):
|
def on_property_set_label(self, event, name, newvalue, oldvalue=None):
|
||||||
# pylint: disable=unused-argument
|
# pylint: disable=unused-argument
|
||||||
@ -1714,53 +1728,53 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM):
|
|||||||
'''
|
'''
|
||||||
# pylint: disable=no-member
|
# pylint: disable=no-member
|
||||||
|
|
||||||
self.qdb.write('/name', self.name)
|
self.untrusted_qdb.write('/name', self.name)
|
||||||
self.qdb.write('/type', self.__class__.__name__)
|
self.untrusted_qdb.write('/type', self.__class__.__name__)
|
||||||
self.qdb.write('/qubes-vm-updateable', str(self.updateable))
|
self.untrusted_qdb.write('/qubes-vm-updateable', str(self.updateable))
|
||||||
self.qdb.write('/qubes-vm-persistence',
|
self.untrusted_qdb.write('/qubes-vm-persistence',
|
||||||
'full' if self.updateable else 'rw-only')
|
'full' if self.updateable else 'rw-only')
|
||||||
self.qdb.write('/qubes-debug-mode', str(int(self.debug)))
|
self.untrusted_qdb.write('/qubes-debug-mode', str(int(self.debug)))
|
||||||
try:
|
try:
|
||||||
self.qdb.write('/qubes-base-template', self.template.name)
|
self.untrusted_qdb.write('/qubes-base-template', self.template.name)
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
self.qdb.write('/qubes-base-template', '')
|
self.untrusted_qdb.write('/qubes-base-template', '')
|
||||||
|
|
||||||
self.qdb.write('/qubes-random-seed',
|
self.untrusted_qdb.write('/qubes-random-seed',
|
||||||
base64.b64encode(qubes.utils.urandom(64)))
|
base64.b64encode(qubes.utils.urandom(64)))
|
||||||
|
|
||||||
if self.provides_network:
|
if self.provides_network:
|
||||||
# '/qubes-netvm-network' value is only checked for being non empty
|
# '/qubes-netvm-network' value is only checked for being non empty
|
||||||
self.qdb.write('/qubes-netvm-network', self.gateway)
|
self.untrusted_qdb.write('/qubes-netvm-network', self.gateway)
|
||||||
self.qdb.write('/qubes-netvm-gateway', self.gateway)
|
self.untrusted_qdb.write('/qubes-netvm-gateway', self.gateway)
|
||||||
self.qdb.write('/qubes-netvm-netmask', self.netmask)
|
self.untrusted_qdb.write('/qubes-netvm-netmask', self.netmask)
|
||||||
|
|
||||||
for i, addr in zip(('primary', 'secondary'), self.dns):
|
for i, addr in zip(('primary', 'secondary'), self.dns):
|
||||||
self.qdb.write('/qubes-netvm-{}-dns'.format(i), addr)
|
self.untrusted_qdb.write('/qubes-netvm-{}-dns'.format(i), addr)
|
||||||
|
|
||||||
if self.netvm is not None:
|
if self.netvm is not None:
|
||||||
self.qdb.write('/qubes-ip', self.visible_ip)
|
self.untrusted_qdb.write('/qubes-ip', self.visible_ip)
|
||||||
self.qdb.write('/qubes-netmask', self.visible_netmask)
|
self.untrusted_qdb.write('/qubes-netmask', self.visible_netmask)
|
||||||
self.qdb.write('/qubes-gateway', self.visible_gateway)
|
self.untrusted_qdb.write('/qubes-gateway', self.visible_gateway)
|
||||||
|
|
||||||
for i, addr in zip(('primary', 'secondary'), self.dns):
|
for i, addr in zip(('primary', 'secondary'), self.dns):
|
||||||
self.qdb.write('/qubes-{}-dns'.format(i), addr)
|
self.untrusted_qdb.write('/qubes-{}-dns'.format(i), addr)
|
||||||
|
|
||||||
|
|
||||||
tzname = qubes.utils.get_timezone()
|
tzname = qubes.utils.get_timezone()
|
||||||
if tzname:
|
if tzname:
|
||||||
self.qdb.write('/qubes-timezone', tzname)
|
self.untrusted_qdb.write('/qubes-timezone', tzname)
|
||||||
|
|
||||||
for feature, value in self.features.items():
|
for feature, value in self.features.items():
|
||||||
if not feature.startswith('service.'):
|
if not feature.startswith('service.'):
|
||||||
continue
|
continue
|
||||||
service = feature[len('service.'):]
|
service = feature[len('service.'):]
|
||||||
# forcefully convert to '0' or '1'
|
# forcefully convert to '0' or '1'
|
||||||
self.qdb.write('/qubes-service/{}'.format(service),
|
self.untrusted_qdb.write('/qubes-service/{}'.format(service),
|
||||||
str(int(bool(value))))
|
str(int(bool(value))))
|
||||||
|
|
||||||
self.qdb.write('/qubes-block-devices', '')
|
self.untrusted_qdb.write('/qubes-block-devices', '')
|
||||||
|
|
||||||
self.qdb.write('/qubes-usb-devices', '')
|
self.untrusted_qdb.write('/qubes-usb-devices', '')
|
||||||
|
|
||||||
# TODO: Currently the whole qmemman is quite Xen-specific, so stay with
|
# TODO: Currently the whole qmemman is quite Xen-specific, so stay with
|
||||||
# xenstore for it until decided otherwise
|
# xenstore for it until decided otherwise
|
||||||
@ -1771,6 +1785,8 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM):
|
|||||||
|
|
||||||
self.fire_event('domain-qdb-create')
|
self.fire_event('domain-qdb-create')
|
||||||
|
|
||||||
|
self.start_qdb_watch(self.name)
|
||||||
|
|
||||||
# TODO async; update this in constructor
|
# TODO async; update this in constructor
|
||||||
def _update_libvirt_domain(self):
|
def _update_libvirt_domain(self):
|
||||||
'''Re-initialise :py:attr:`libvirt_domain`.'''
|
'''Re-initialise :py:attr:`libvirt_domain`.'''
|
||||||
|
Loading…
Reference in New Issue
Block a user