diff --git a/doc/qubes-policy.rst b/doc/qubes-policy.rst index 0be819ca5..1d4003853 100644 --- a/doc/qubes-policy.rst +++ b/doc/qubes-policy.rst @@ -30,6 +30,10 @@ Each line consist of three values separated by white characters (space(s), tab(s - `$dispvm:vm-name` - _new_ Disposable VM created from AppVM `vm-name` - `$dispvm` - _new_ Disposable VM created from AppVM pointed by caller property `default_dispvm`, which defaults to global property `default_dispvm` + - `$adminvm` - Admin VM aka dom0 + + Dom0 can only be matched explicitly - either as `dom0` or `$adminvm` keyword. + None of `$anyvm`, `$tag:some-tag`, `$type:AdminVM` will match. 3. Action and optional action parameters, one of: diff --git a/qubespolicy/__init__.py b/qubespolicy/__init__.py index e55d88ee6..acba1dd6a 100755 --- a/qubespolicy/__init__.py +++ b/qubespolicy/__init__.py @@ -66,6 +66,8 @@ def verify_target_value(system_info, value): ''' if value == '$dispvm': return True + elif value == '$adminvm': + return True elif value.startswith('$dispvm:'): dispvm_base = value.split(':', 1)[1] if dispvm_base not in system_info['domains']: @@ -93,6 +95,8 @@ def verify_special_value(value, for_target=True): return True elif value == '$anyvm': return True + elif value == '$adminvm': + return True elif value.startswith('$dispvm:') and for_target: return True elif value == '$dispvm' and for_target: @@ -184,8 +188,9 @@ def __init__(self, line, filename=None, lineno=None): 'allow action for $default rule must specify target= option') if self.override_target is not None: - if self.override_target.startswith('$') and not \ - self.override_target.startswith('$dispvm'): + if self.override_target.startswith('$') and \ + not self.override_target.startswith('$dispvm') and \ + self.override_target != '$adminvm': raise PolicySyntaxError(filename, lineno, 'target= option needs to name specific target') @@ -216,11 +221,19 @@ def is_match_single(system_info, policy_value, value): if not verify_target_value(system_info, value): return False + # handle $adminvm keyword + if policy_value == 'dom0': + # TODO: log a warning in Qubes 4.1 + policy_value = '$adminvm' + + if value == 'dom0': + value = '$adminvm' + # allow any _valid_, non-dom0 target if policy_value == '$anyvm': - return value != 'dom0' + return value != '$adminvm' - # exact match, including $dispvm* + # exact match, including $dispvm* and $adminvm if value == policy_value: return True @@ -229,6 +242,11 @@ def is_match_single(system_info, policy_value, value): if value.startswith('$dispvm'): return False + # require $adminvm to be matched explicitly (not through $tag or $type) + # - if not matched already, reject it + if value == '$adminvm': + return False + # at this point, value name a specific target domain_info = system_info['domains'][value] @@ -293,6 +311,8 @@ def expand_target(self, system_info): except KeyError: # TODO log a warning? pass + elif self.target == '$adminvm': + yield self.target elif self.target == '$dispvm': yield self.target else: @@ -378,6 +398,8 @@ def execute(self, caller_ident): assert self.action == Action.allow assert self.target is not None + if self.target == '$adminvm': + self.target = 'dom0' if self.target == 'dom0': cmd = '{multiplexer} {service} {source} {original_target}'.format( multiplexer=QUBES_RPC_MULTIPLEXER_PATH, diff --git a/qubespolicy/tests/__init__.py b/qubespolicy/tests/__init__.py index 079e1335c..fe5a42c05 100644 --- a/qubespolicy/tests/__init__.py +++ b/qubespolicy/tests/__init__.py @@ -31,7 +31,7 @@ system_info = { 'domains': { 'dom0': { - 'tags': [], + 'tags': ['dom0-tag'], 'type': 'AdminVM', 'default_dispvm': 'default-dvm', 'dispvm_allowed': False, @@ -102,6 +102,8 @@ def test_000_verify_target_value(self): qubespolicy.verify_target_value(system_info, 'test-template')) self.assertTrue( qubespolicy.verify_target_value(system_info, 'test-standalone')) + self.assertTrue( + qubespolicy.verify_target_value(system_info, '$adminvm')) self.assertFalse( qubespolicy.verify_target_value(system_info, 'no-such-vm')) self.assertFalse( @@ -127,6 +129,8 @@ def test_010_verify_special_value(self): for_target=False)) self.assertTrue(qubespolicy.verify_special_value('$type:AppVM', for_target=False)) + self.assertTrue(qubespolicy.verify_special_value('$adminvm', + for_target=False)) self.assertFalse(qubespolicy.verify_special_value('$default', for_target=False)) self.assertFalse(qubespolicy.verify_special_value('$dispvm', @@ -197,6 +201,20 @@ def test_023_line_simple(self): self.assertIsNone(line.override_user) self.assertEqual(line.default_target, 'test-vm1') + def test_024_line_simple(self): + line = qubespolicy.PolicyRule( + '$anyvm $adminvm ask,default_target=$adminvm', + 'filename', 12) + self.assertEqual(line.filename, 'filename') + self.assertEqual(line.lineno, 12) + self.assertEqual(line.action, qubespolicy.Action.ask) + self.assertEqual(line.source, '$anyvm') + self.assertEqual(line.target, '$adminvm') + self.assertEqual(line.full_action, 'ask,default_target=$adminvm') + self.assertIsNone(line.override_target) + self.assertIsNone(line.override_user) + self.assertEqual(line.default_target, '$adminvm') + def test_030_line_invalid(self): invalid_lines = [ '$dispvm $default allow', # $dispvm can't be a source @@ -236,6 +254,9 @@ def test_040_match_single(self): self.assertTrue(is_match_single(system_info, '$anyvm', '$dispvm:default-dvm')) self.assertTrue(is_match_single(system_info, '$dispvm', '$dispvm')) + self.assertTrue(is_match_single(system_info, '$adminvm', '$adminvm')) + self.assertTrue(is_match_single(system_info, '$adminvm', 'dom0')) + self.assertTrue(is_match_single(system_info, 'dom0', '$adminvm')) self.assertTrue(is_match_single(system_info, 'dom0', 'dom0')) self.assertTrue(is_match_single(system_info, '$dispvm:default-dvm', '$dispvm:default-dvm')) @@ -254,6 +275,15 @@ def test_040_match_single(self): self.assertFalse(is_match_single(system_info, '$dispvm:test-vm1', '$dispvm:test-vm1')) self.assertFalse(is_match_single(system_info, '$anyvm', 'dom0')) + self.assertFalse(is_match_single(system_info, '$anyvm', '$adminvm')) + self.assertFalse(is_match_single(system_info, + '$tag:dom0-tag', '$adminvm')) + self.assertFalse(is_match_single(system_info, + '$type:AdminVM', '$adminvm')) + self.assertFalse(is_match_single(system_info, + '$tag:dom0-tag', 'dom0')) + self.assertFalse(is_match_single(system_info, + '$type:AdminVM', 'dom0')) self.assertFalse(is_match_single(system_info, '$tag:tag1', 'dom0')) self.assertFalse(is_match_single(system_info, '$anyvm', '$tag:tag1')) self.assertFalse(is_match_single(system_info, '$anyvm', '$type:AppVM')) @@ -339,6 +369,13 @@ def test_074_expand_override_target_dom0(self): line.expand_override_target(system_info, 'test-no-dvm'), 'dom0') + def test_075_expand_override_target_dom0(self): + line = qubespolicy.PolicyRule( + '$anyvm $anyvm allow,target=$adminvm') + self.assertEqual( + line.expand_override_target(system_info, 'test-no-dvm'), + '$adminvm') + class TC_10_PolicyAction(qubes.tests.QubesTestCase): def test_000_init(self):