123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153 |
- # -*- encoding: utf8 -*-
- #
- # The Qubes OS Project, http://www.qubes-os.org
- #
- # Copyright (C) 2017 Wojtek Porczyk <woju@invisiblethingslab.com>
- #
- # This library is free software; you can redistribute it and/or
- # modify it under the terms of the GNU Lesser General Public
- # License as published by the Free Software Foundation; either
- # version 2.1 of the License, or (at your option) any later version.
- #
- # This library is distributed in the hope that it will be useful,
- # but WITHOUT ANY WARRANTY; without even the implied warranty of
- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- # Lesser General Public License for more details.
- #
- # You should have received a copy of the GNU Lesser General Public
- # License along with this library; if not, see <https://www.gnu.org/licenses/>.
- import qubes.api
- import qubes.api.internal
- import qubes.ext
- import qubes.vm.adminvm
- from qrexec.policy import utils, parser
- class JustEvaluateAskResolution(parser.AskResolution):
- async def execute(self, caller_ident):
- pass
- class JustEvaluateAllowResolution(parser.AllowResolution):
- async def execute(self, caller_ident):
- pass
- class AdminExtension(qubes.ext.Extension):
- def __init__(self):
- super().__init__()
- # during tests, __init__() of the extension can be called multiple
- # times, because there are multiple Qubes() object instances
- if not hasattr(self, 'policy_cache'):
- self.policy_cache = utils.PolicyCache(lazy_load=True)
- self.policy_cache.initialize_watcher()
- # pylint: disable=too-few-public-methods
- @qubes.ext.handler(
- 'admin-permission:admin.vm.tag.Set',
- 'admin-permission:admin.vm.tag.Remove')
- def on_tag_set_or_remove(self, vm, event, arg, **kwargs):
- '''Forbid changing specific tags'''
- # pylint: disable=no-self-use,unused-argument
- if arg.startswith('created-by-') and \
- not isinstance(vm, qubes.vm.adminvm.AdminVM):
- raise qubes.api.PermissionDenied(
- 'changing this tag is prohibited by {}.{}'.format(
- __name__, type(self).__name__))
- # TODO create that tag here (need to figure out how to pass mgmtvm name)
- @qubes.ext.handler('admin-permission:admin.vm.List')
- def admin_vm_list(self, vm, event, arg, **kwargs):
- '''When called with target 'dom0' (aka "get full list"), exclude domains
- that the caller don't have permission to list
- '''
- # pylint: disable=unused-argument
- if vm.klass == 'AdminVM':
- # dom0 can always list everything
- return None
- policy = self.policy_cache.get_policy()
- system_info = qubes.api.internal.get_system_info(vm.app)
- def filter_vms(dest_vm):
- request = parser.Request(
- 'admin.vm.List',
- '+' + arg,
- vm.name,
- dest_vm.name,
- system_info=system_info,
- ask_resolution_type=JustEvaluateAskResolution,
- allow_resolution_type=JustEvaluateAllowResolution)
- try:
- resolution = policy.evaluate(request)
- # do not consider 'ask' as allow here,
- # this needs to be not interactive
- return isinstance(resolution, parser.AllowResolution)
- except parser.AccessDenied:
- return False
- return (filter_vms,)
- @qubes.ext.handler('admin-permission:admin.Events')
- def admin_events(self, vm, event, arg, **kwargs):
- '''When called with target 'dom0' (aka "get all events"),
- exclude domains that the caller don't have permission to receive
- events about
- '''
- # pylint: disable=unused-argument
- if vm.klass == 'AdminVM':
- # dom0 can always list everything
- return None
- def filter_events(event):
- subject, event, kwargs = event
- try:
- dest = subject.name
- except AttributeError:
- # domain-add and similar events fired on the Qubes() object
- if 'vm' in kwargs:
- dest = kwargs['vm'].name
- else:
- dest = '@adminvm'
- policy = self.policy_cache.get_policy()
- # TODO: cache system_info (based on last qubes.xml write time?)
- system_info = qubes.api.internal.get_system_info(vm.app)
- request = parser.Request(
- 'admin.Events',
- '+' + event.replace(':', '_'),
- vm.name,
- dest,
- system_info=system_info,
- ask_resolution_type=JustEvaluateAskResolution,
- allow_resolution_type=JustEvaluateAllowResolution)
- try:
- resolution = policy.evaluate(request)
- # do not consider 'ask' as allow here,
- # this needs to be not interactive
- return isinstance(resolution, parser.AllowResolution)
- except parser.AccessDenied:
- return False
- return (filter_events,)
- @qubes.ext.handler('qubes-close', system=True)
- def on_qubes_close(self, app, event, **kwargs):
- """Unregister policy file watches on app.close()."""
- # pylint: disable=unused-argument
- if hasattr(self, 'policy_cache'):
- self.policy_cache.cleanup()
- del self.policy_cache
- @qubes.ext.handler('domain-tag-add:created-by-*')
- def on_tag_add(self, vm, event, tag, **kwargs):
- '''Add extra tags based on creators 'tag-created-vm-with' feature'''
- # pylint: disable=unused-argument,no-self-use
- created_by = vm.app.domains[tag.partition('created-by-')[2]]
- tag_with = created_by.features.get('tag-created-vm-with', '')
- for tag_with_single in tag_with.split():
- vm.tags.add(tag_with_single)
|