diff --git a/Makefile b/Makefile index 308ceb2a..ccf17a59 100644 --- a/Makefile +++ b/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 \ diff --git a/qubes/api/admin.py b/qubes/api/admin.py index 0aaaf657..c8d694a6 100644 --- a/qubes/api/admin.py +++ b/qubes/api/admin.py @@ -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 diff --git a/qubes/api/internal.py b/qubes/api/internal.py index 97232d02..a458309c 100644 --- a/qubes/api/internal.py +++ b/qubes/api/internal.py @@ -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): diff --git a/qubes/tests/api_admin.py b/qubes/tests/api_admin.py index a8873623..7ce27f4a 100644 --- a/qubes/tests/api_admin.py +++ b/qubes/tests/api_admin.py @@ -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', diff --git a/qubespolicy/__init__.py b/qubespolicy/__init__.py index 0d8a6452..37532d2c 100755 --- a/qubespolicy/__init__.py +++ b/qubespolicy/__init__.py @@ -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 diff --git a/qubespolicy/tests/__init__.py b/qubespolicy/tests/__init__.py index 1f937e95..f1e4ac7c 100644 --- a/qubespolicy/tests/__init__.py +++ b/qubespolicy/tests/__init__.py @@ -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'),