api/admin: add admin.vm.CreateDisposable in place of internal.vm.Create.DispVM
Add public Admin API call to create Disposable VM that would be automatically destroyed after shutdown. Do not keep this functionality for qrexec-policy tool only. Also, use admin.vm.Start there, instead of internal.vm.Start and admin.vm.Kill instead of internal.vm.CleanupDispVM (this is enough, because DispVM now have auto_cleanup property). QubesOS/qubes-issues#2974
This commit is contained in:
parent
691a6f4d8c
commit
971c7d4ac9
1
Makefile
1
Makefile
@ -43,6 +43,7 @@ ADMIN_API_METHODS_SIMPLE = \
|
||||
admin.vm.CreateInPool.DispVM \
|
||||
admin.vm.CreateInPool.StandaloneVM \
|
||||
admin.vm.CreateInPool.TemplateVM \
|
||||
admin.vm.CreateDisposable \
|
||||
admin.vm.Kill \
|
||||
admin.vm.List \
|
||||
admin.vm.Pause \
|
||||
|
@ -915,6 +915,25 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
|
||||
raise
|
||||
self.app.save()
|
||||
|
||||
@qubes.api.method('admin.vm.CreateDisposable', no_payload=True,
|
||||
scope='global', write=True)
|
||||
@asyncio.coroutine
|
||||
def create_disposable(self):
|
||||
assert not self.arg
|
||||
|
||||
if self.dest.name == 'dom0':
|
||||
dispvm_template = self.src.default_dispvm
|
||||
else:
|
||||
dispvm_template = self.dest
|
||||
|
||||
self.fire_event_for_permission(dispvm_template=dispvm_template)
|
||||
|
||||
dispvm = yield from qubes.vm.dispvm.DispVM.from_appvm(dispvm_template)
|
||||
# TODO: move this to extension (in race-free fashion, better than here)
|
||||
dispvm.tags.add('disp-created-by-' + str(self.src))
|
||||
|
||||
return dispvm.name
|
||||
|
||||
@qubes.api.method('admin.vm.Remove', no_payload=True,
|
||||
scope='global', write=True)
|
||||
@asyncio.coroutine
|
||||
|
@ -55,30 +55,6 @@ class QubesInternalAPI(qubes.api.AbstractQubesAPI):
|
||||
|
||||
return json.dumps(system_info)
|
||||
|
||||
@qubes.api.method('internal.vm.Start', no_payload=True)
|
||||
@asyncio.coroutine
|
||||
def start(self):
|
||||
assert not self.arg
|
||||
if self.dest.name == 'dom0':
|
||||
return
|
||||
|
||||
yield from self.dest.start()
|
||||
|
||||
@qubes.api.method('internal.vm.Create.DispVM', no_payload=True)
|
||||
@asyncio.coroutine
|
||||
def create_dispvm(self):
|
||||
assert not self.arg
|
||||
|
||||
dispvm = yield from qubes.vm.dispvm.DispVM.from_appvm(self.dest)
|
||||
return dispvm.name
|
||||
|
||||
@qubes.api.method('internal.vm.CleanupDispVM', no_payload=True)
|
||||
@asyncio.coroutine
|
||||
def cleanup_dispvm(self):
|
||||
assert not self.arg
|
||||
|
||||
yield from self.dest.cleanup()
|
||||
|
||||
@qubes.api.method('internal.vm.volume.ImportEnd')
|
||||
@asyncio.coroutine
|
||||
def vm_volume_import_end(self, untrusted_payload):
|
||||
|
@ -2099,6 +2099,39 @@ class TC_00_VMs(AdminAPITestCase):
|
||||
memory_kb=stats2[2]['memory_kb']),
|
||||
])
|
||||
|
||||
@unittest.mock.patch('qubes.storage.Storage.create')
|
||||
def test_640_vm_create_disposable(self, mock_storage):
|
||||
mock_storage.side_effect = self.dummy_coro
|
||||
self.vm.dispvm_allowed = True
|
||||
retval = self.call_mgmt_func(b'admin.vm.CreateDisposable',
|
||||
b'test-vm1')
|
||||
self.assertTrue(retval.startswith('disp'))
|
||||
self.assertIn(retval, self.app.domains)
|
||||
dispvm = self.app.domains[retval]
|
||||
self.assertEqual(dispvm.template, self.vm)
|
||||
mock_storage.assert_called_once_with()
|
||||
self.assertTrue(self.app.save.called)
|
||||
|
||||
@unittest.mock.patch('qubes.storage.Storage.create')
|
||||
def test_641_vm_create_disposable_default(self, mock_storage):
|
||||
mock_storage.side_effect = self.dummy_coro
|
||||
self.vm.dispvm_allowed = True
|
||||
self.app.default_dispvm = self.vm
|
||||
retval = self.call_mgmt_func(b'admin.vm.CreateDisposable',
|
||||
b'dom0')
|
||||
self.assertTrue(retval.startswith('disp'))
|
||||
mock_storage.assert_called_once_with()
|
||||
self.assertTrue(self.app.save.called)
|
||||
|
||||
@unittest.mock.patch('qubes.storage.Storage.create')
|
||||
def test_642_vm_create_disposable_not_allowed(self, storage_mock):
|
||||
storage_mock.side_effect = self.dummy_coro
|
||||
with self.assertRaises(qubes.exc.QubesException):
|
||||
self.call_mgmt_func(b'admin.vm.CreateDisposable',
|
||||
b'test-vm1')
|
||||
self.assertFalse(self.app.save.called)
|
||||
|
||||
|
||||
def test_990_vm_unexpected_payload(self):
|
||||
methods_with_no_payload = [
|
||||
b'admin.vm.List',
|
||||
|
@ -33,6 +33,7 @@ QREXEC_CLIENT = '/usr/lib/qubes/qrexec-client'
|
||||
QUBES_RPC_MULTIPLEXER_PATH = '/usr/lib/qubes/qubes-rpc-multiplexer'
|
||||
POLICY_DIR = '/etc/qubes-rpc/policy'
|
||||
QUBESD_INTERNAL_SOCK = '/var/run/qubesd.internal.sock'
|
||||
QUBESD_SOCK = '/var/run/qubesd.sock'
|
||||
|
||||
|
||||
class AccessDenied(Exception):
|
||||
@ -434,9 +435,9 @@ class PolicyAction(object):
|
||||
:return: name of new Disposable VM
|
||||
'''
|
||||
base_appvm = self.target.split(':', 1)[1]
|
||||
dispvm_name = qubesd_call(base_appvm, 'internal.vm.Create.DispVM')
|
||||
dispvm_name = qubesd_call(base_appvm, 'admin.vm.CreateDisposable')
|
||||
dispvm_name = dispvm_name.decode('ascii')
|
||||
qubesd_call(dispvm_name, 'internal.vm.Start')
|
||||
qubesd_call(dispvm_name, 'admin.vm.Start')
|
||||
return dispvm_name
|
||||
|
||||
def ensure_target_running(self):
|
||||
@ -445,8 +446,10 @@ class PolicyAction(object):
|
||||
|
||||
:return: None
|
||||
'''
|
||||
if self.target == 'dom0':
|
||||
return
|
||||
try:
|
||||
qubesd_call(self.target, 'internal.vm.Start')
|
||||
qubesd_call(self.target, 'admin.vm.Start')
|
||||
except QubesMgmtException as e:
|
||||
if e.exc_type == 'QubesVMNotHaltedError':
|
||||
pass
|
||||
@ -461,7 +464,7 @@ class PolicyAction(object):
|
||||
:param dispvm: name of Disposable VM
|
||||
:return: None
|
||||
'''
|
||||
qubesd_call(dispvm, 'internal.vm.CleanupDispVM')
|
||||
qubesd_call(dispvm, 'admin.vm.Kill')
|
||||
|
||||
|
||||
class Policy(object):
|
||||
@ -631,9 +634,13 @@ class QubesMgmtException(Exception):
|
||||
|
||||
|
||||
def qubesd_call(dest, method, arg=None, payload=None):
|
||||
if method.startswith('internal.'):
|
||||
socket_path = QUBESD_INTERNAL_SOCK
|
||||
else:
|
||||
socket_path = QUBESD_SOCK
|
||||
try:
|
||||
client_socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||
client_socket.connect(QUBESD_INTERNAL_SOCK)
|
||||
client_socket.connect(socket_path)
|
||||
except IOError:
|
||||
# TODO:
|
||||
raise
|
||||
|
@ -461,7 +461,7 @@ class TC_10_PolicyAction(qubes.tests.QubesTestCase):
|
||||
'test-vm2', rule, 'test-vm2')
|
||||
action.execute('some-ident')
|
||||
self.assertEqual(mock_qubesd_call.mock_calls,
|
||||
[unittest.mock.call('test-vm2', 'internal.vm.Start')])
|
||||
[unittest.mock.call('test-vm2', 'admin.vm.Start')])
|
||||
self.assertEqual(mock_subprocess.mock_calls,
|
||||
[unittest.mock.call([qubespolicy.QREXEC_CLIENT, '-d', 'test-vm2',
|
||||
'-c', 'some-ident', 'DEFAULT:QUBESRPC test.service test-vm1'])])
|
||||
@ -473,8 +473,7 @@ class TC_10_PolicyAction(qubes.tests.QubesTestCase):
|
||||
action = qubespolicy.PolicyAction('test.service', 'test-vm1',
|
||||
'dom0', rule, 'dom0')
|
||||
action.execute('some-ident')
|
||||
self.assertEqual(mock_qubesd_call.mock_calls,
|
||||
[unittest.mock.call('dom0', 'internal.vm.Start')])
|
||||
self.assertEqual(mock_qubesd_call.mock_calls, [])
|
||||
self.assertEqual(mock_subprocess.mock_calls,
|
||||
[unittest.mock.call([qubespolicy.QREXEC_CLIENT, '-d', 'dom0',
|
||||
'-c', 'some-ident',
|
||||
@ -488,14 +487,13 @@ class TC_10_PolicyAction(qubes.tests.QubesTestCase):
|
||||
action = qubespolicy.PolicyAction('test.service', 'test-vm1',
|
||||
'$dispvm:default-dvm', rule, '$dispvm:default-dvm')
|
||||
mock_qubesd_call.side_effect = (lambda target, call:
|
||||
b'dispvm-name' if call == 'internal.vm.Create.DispVM' else
|
||||
b'dispvm-name' if call == 'admin.vm.CreateDisposable' else
|
||||
unittest.mock.DEFAULT)
|
||||
action.execute('some-ident')
|
||||
self.assertEqual(mock_qubesd_call.mock_calls,
|
||||
[unittest.mock.call('default-dvm', 'internal.vm.Create.DispVM'),
|
||||
unittest.mock.call('dispvm-name', 'internal.vm.Start'),
|
||||
unittest.mock.call('dispvm-name',
|
||||
'internal.vm.CleanupDispVM')])
|
||||
[unittest.mock.call('default-dvm', 'admin.vm.CreateDisposable'),
|
||||
unittest.mock.call('dispvm-name', 'admin.vm.Start'),
|
||||
unittest.mock.call('dispvm-name', 'admin.vm.Kill')])
|
||||
self.assertEqual(mock_subprocess.mock_calls,
|
||||
[unittest.mock.call([qubespolicy.QREXEC_CLIENT, '-d', 'dispvm-name',
|
||||
'-c', 'some-ident', '-W',
|
||||
@ -512,7 +510,7 @@ class TC_10_PolicyAction(qubes.tests.QubesTestCase):
|
||||
qubespolicy.QubesMgmtException('QubesVMNotHaltedError')
|
||||
action.execute('some-ident')
|
||||
self.assertEqual(mock_qubesd_call.mock_calls,
|
||||
[unittest.mock.call('test-vm2', 'internal.vm.Start')])
|
||||
[unittest.mock.call('test-vm2', 'admin.vm.Start')])
|
||||
self.assertEqual(mock_subprocess.mock_calls,
|
||||
[unittest.mock.call([qubespolicy.QREXEC_CLIENT, '-d', 'test-vm2',
|
||||
'-c', 'some-ident', 'DEFAULT:QUBESRPC test.service test-vm1'])])
|
||||
@ -529,7 +527,7 @@ class TC_10_PolicyAction(qubes.tests.QubesTestCase):
|
||||
with self.assertRaises(qubespolicy.QubesMgmtException):
|
||||
action.execute('some-ident')
|
||||
self.assertEqual(mock_qubesd_call.mock_calls,
|
||||
[unittest.mock.call('test-vm2', 'internal.vm.Start')])
|
||||
[unittest.mock.call('test-vm2', 'admin.vm.Start')])
|
||||
self.assertEqual(mock_subprocess.mock_calls, [])
|
||||
|
||||
class TC_20_Policy(qubes.tests.QubesTestCase):
|
||||
@ -755,14 +753,14 @@ class TC_30_Misc(qubes.tests.QubesTestCase):
|
||||
'return_value.makefile.return_value.read.return_value': b'0\x00data'
|
||||
}
|
||||
mock_socket.configure_mock(**mock_config)
|
||||
result = qubespolicy.qubesd_call('test', 'method')
|
||||
result = qubespolicy.qubesd_call('test', 'internal.method')
|
||||
self.assertEqual(result, b'data')
|
||||
self.assertEqual(mock_socket.mock_calls, [
|
||||
unittest.mock.call(socket.AF_UNIX, socket.SOCK_STREAM),
|
||||
unittest.mock.call().connect(qubespolicy.QUBESD_INTERNAL_SOCK),
|
||||
unittest.mock.call().sendall(b'dom0'),
|
||||
unittest.mock.call().sendall(b'\x00'),
|
||||
unittest.mock.call().sendall(b'method'),
|
||||
unittest.mock.call().sendall(b'internal.method'),
|
||||
unittest.mock.call().sendall(b'\x00'),
|
||||
unittest.mock.call().sendall(b'test'),
|
||||
unittest.mock.call().sendall(b'\x00'),
|
||||
@ -778,14 +776,15 @@ class TC_30_Misc(qubes.tests.QubesTestCase):
|
||||
'return_value.makefile.return_value.read.return_value': b'0\x00data'
|
||||
}
|
||||
mock_socket.configure_mock(**mock_config)
|
||||
result = qubespolicy.qubesd_call('test', 'method', 'arg', b'payload')
|
||||
result = qubespolicy.qubesd_call('test', 'internal.method', 'arg',
|
||||
b'payload')
|
||||
self.assertEqual(result, b'data')
|
||||
self.assertEqual(mock_socket.mock_calls, [
|
||||
unittest.mock.call(socket.AF_UNIX, socket.SOCK_STREAM),
|
||||
unittest.mock.call().connect(qubespolicy.QUBESD_INTERNAL_SOCK),
|
||||
unittest.mock.call().sendall(b'dom0'),
|
||||
unittest.mock.call().sendall(b'\x00'),
|
||||
unittest.mock.call().sendall(b'method'),
|
||||
unittest.mock.call().sendall(b'internal.method'),
|
||||
unittest.mock.call().sendall(b'\x00'),
|
||||
unittest.mock.call().sendall(b'test'),
|
||||
unittest.mock.call().sendall(b'\x00'),
|
||||
@ -805,14 +804,14 @@ class TC_30_Misc(qubes.tests.QubesTestCase):
|
||||
}
|
||||
mock_socket.configure_mock(**mock_config)
|
||||
with self.assertRaises(qubespolicy.QubesMgmtException) as e:
|
||||
qubespolicy.qubesd_call('test', 'method')
|
||||
qubespolicy.qubesd_call('test', 'internal.method')
|
||||
self.assertEqual(e.exception.exc_type, 'SomeError')
|
||||
self.assertEqual(mock_socket.mock_calls, [
|
||||
unittest.mock.call(socket.AF_UNIX, socket.SOCK_STREAM),
|
||||
unittest.mock.call().connect(qubespolicy.QUBESD_INTERNAL_SOCK),
|
||||
unittest.mock.call().sendall(b'dom0'),
|
||||
unittest.mock.call().sendall(b'\x00'),
|
||||
unittest.mock.call().sendall(b'method'),
|
||||
unittest.mock.call().sendall(b'internal.method'),
|
||||
unittest.mock.call().sendall(b'\x00'),
|
||||
unittest.mock.call().sendall(b'test'),
|
||||
unittest.mock.call().sendall(b'\x00'),
|
||||
|
Loading…
Reference in New Issue
Block a user