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:
Marek Marczykowski-Górecki 2017-08-06 12:35:35 +02:00
parent 691a6f4d8c
commit 971c7d4ac9
No known key found for this signature in database
GPG Key ID: 063938BA42CFA724
6 changed files with 80 additions and 45 deletions

View File

@ -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 \

View File

@ -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

View File

@ -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):

View File

@ -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',

View File

@ -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

View File

@ -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'),