Merge remote-tracking branch 'origin/policy-adminvm'
This commit is contained in:
commit
51022cada5
29
Makefile
29
Makefile
@ -8,7 +8,6 @@ OS ?= Linux
|
||||
PYTHON ?= python3
|
||||
|
||||
ADMIN_API_METHODS_SIMPLE = \
|
||||
admin.vm.List \
|
||||
admin.vmclass.List \
|
||||
admin.Events \
|
||||
admin.backup.Execute \
|
||||
@ -17,6 +16,7 @@ ADMIN_API_METHODS_SIMPLE = \
|
||||
admin.label.Create \
|
||||
admin.label.Get \
|
||||
admin.label.List \
|
||||
admin.label.Index \
|
||||
admin.label.Remove \
|
||||
admin.pool.Add \
|
||||
admin.pool.Info \
|
||||
@ -83,6 +83,8 @@ ADMIN_API_METHODS_SIMPLE = \
|
||||
admin.vm.tag.List \
|
||||
admin.vm.tag.Remove \
|
||||
admin.vm.tag.Set \
|
||||
admin.vm.volume.CloneFrom \
|
||||
admin.vm.volume.CloneTo \
|
||||
admin.vm.volume.Info \
|
||||
admin.vm.volume.List \
|
||||
admin.vm.volume.ListSnapshots \
|
||||
@ -90,10 +92,6 @@ ADMIN_API_METHODS_SIMPLE = \
|
||||
admin.vm.volume.Revert \
|
||||
$(null)
|
||||
|
||||
ADMIN_API_METHODS := $(ADMIN_API_METHODS_SIMPLE) \
|
||||
admin.vm.volume.Import \
|
||||
$(null)
|
||||
|
||||
ifeq ($(OS),Linux)
|
||||
DATADIR ?= /var/lib/qubes
|
||||
STATEDIR ?= /var/run/qubes
|
||||
@ -172,15 +170,26 @@ endif
|
||||
install qubes-rpc/qubesd-query-fast $(DESTDIR)/usr/libexec/qubes/
|
||||
for method in $(ADMIN_API_METHODS_SIMPLE); do \
|
||||
ln -s ../../usr/libexec/qubes/qubesd-query-fast \
|
||||
$(DESTDIR)/etc/qubes-rpc/$$method; \
|
||||
$(DESTDIR)/etc/qubes-rpc/$$method || exit 1; \
|
||||
done
|
||||
install qubes-rpc/admin.vm.volume.Import $(DESTDIR)/etc/qubes-rpc/
|
||||
for method in $(ADMIN_API_METHODS); do \
|
||||
install -m 0644 qubes-rpc-policy/admin-default \
|
||||
$(DESTDIR)/etc/qubes-rpc/policy/$$method; \
|
||||
PYTHONPATH=.:test-packages qubes-rpc-policy/generate-admin-policy \
|
||||
--destdir=$(DESTDIR)/etc/qubes-rpc/policy \
|
||||
--exclude admin.vm.Create.AdminVM \
|
||||
admin.vm.CreateInPool.AdminVM \
|
||||
admin.vm.device.testclass.Attach \
|
||||
admin.vm.device.testclass.Detach \
|
||||
admin.vm.device.testclass.List \
|
||||
admin.vm.device.testclass.Available
|
||||
# sanity check
|
||||
for method in $(DESTDIR)/etc/qubes-rpc/policy/admin.*; do \
|
||||
ls $(DESTDIR)/etc/qubes-rpc/$$(basename $$method) >/dev/null || exit 1; \
|
||||
done
|
||||
install -d $(DESTDIR)/etc/qubes-rpc/policy/include
|
||||
install -m 0644 qubes-rpc-policy/admin-all \
|
||||
install -m 0644 qubes-rpc-policy/admin-local-ro \
|
||||
qubes-rpc-policy/admin-local-rwx \
|
||||
qubes-rpc-policy/admin-global-ro \
|
||||
qubes-rpc-policy/admin-global-rwx \
|
||||
$(DESTDIR)/etc/qubes-rpc/policy/include/
|
||||
|
||||
mkdir -p "$(DESTDIR)$(FILESDIR)"
|
||||
|
@ -1,3 +1,3 @@
|
||||
[run]
|
||||
source = qubes
|
||||
omit = qubes/tests/*
|
||||
source = qubes, qubespolicy
|
||||
omit = qubes/tests/*, qubespolicy/tests/*
|
||||
|
@ -243,6 +243,8 @@ _man_pages_author = []
|
||||
man_pages = [
|
||||
('manpages/qubesd-query', 'qubesd-query',
|
||||
u'Low-level qubesd interrogation tool', _man_pages_author, 1),
|
||||
('manpages/qrexec-policy-graph', 'qrexec-policy-graph',
|
||||
u'Graph qrexec policy', _man_pages_author, 1),
|
||||
]
|
||||
|
||||
if os.path.exists('sandbox.rst'):
|
||||
|
73
doc/manpages/qrexec-policy-graph.rst
Normal file
73
doc/manpages/qrexec-policy-graph.rst
Normal file
@ -0,0 +1,73 @@
|
||||
.. program:: qrexec-policy-graph
|
||||
|
||||
:program:`qrexec-policy-graph` -- Graph qrexec policy
|
||||
=====================================================
|
||||
|
||||
Synopsis
|
||||
--------
|
||||
|
||||
:command:`qrexec-policy-graph` [-h] [--include-ask] [--source *SOURCE* [*SOURCE* ...]] [--target *TARGET* [*TARGET* ...]] [--service *SERVICE* [*SERVICE* ...]] [--output *OUTPUT*] [--policy-dir POLICY_DIR] [--system-info SYSTEM_INFO]
|
||||
|
||||
|
||||
Options
|
||||
-------
|
||||
|
||||
.. option:: --help, -h
|
||||
|
||||
show this help message and exit
|
||||
|
||||
.. option:: --include-ask
|
||||
|
||||
Include `ask` action in graph. In most cases produce unreadable graphs
|
||||
because many services contains `$anyvm $anyvm ask` rules. It's recommended to
|
||||
limit graph using other options.
|
||||
|
||||
.. option:: --source
|
||||
|
||||
Limit graph to calls from *source*. You can specify multiple names.
|
||||
|
||||
.. option:: --target
|
||||
|
||||
Limit graph to calls to *target*. You can specify multiple names.
|
||||
|
||||
.. option:: --service
|
||||
|
||||
Limit graph to *service*. You can specify multiple names. This can be either
|
||||
bare service name, or service with argument (joined with `+`). If bare
|
||||
service name is given, output will contain also policies for specific
|
||||
arguments.
|
||||
|
||||
.. option:: --output
|
||||
|
||||
Write to *output* instead of stdout. The file will be overwritten without
|
||||
confirmation.
|
||||
|
||||
.. option:: --policy-dir
|
||||
|
||||
Look for policy in *policy-dir*. This can be useful to process policy
|
||||
extracted from other system. This option adjust only base directory, if any
|
||||
policy file contains `$include:path` with absolute path, it will try to load
|
||||
the file from that location.
|
||||
See also --system-info option.
|
||||
|
||||
.. option:: --system-info
|
||||
|
||||
Load system information from file instead of querying local qubesd instance.
|
||||
The file should be in json format, as returned by `internal.GetSystemInfo`
|
||||
qubesd method. This can be obtained by running in dom0:
|
||||
|
||||
qubesd-query -e -c /var/run/qubesd.internal.sock dom0 \
|
||||
internal.GetSystemInfo dom0 | cut -b 3-
|
||||
|
||||
.. option:: --skip-labels
|
||||
|
||||
Do not include service names on the graph. Also, include only a single
|
||||
connection between qubes if any service call is allowed there.
|
||||
|
||||
|
||||
Authors
|
||||
-------
|
||||
|
||||
| Marek Marczykowski-Górecki <marmarek at invisiblethingslab dot com>
|
||||
|
||||
.. vim: ts=3 sw=3 et tw=80
|
@ -11,13 +11,14 @@ Policy consists of a file, which is parsed line-by-line. First matching line
|
||||
is used as an action.
|
||||
|
||||
Each line consist of three values separated by white characters (space(s), tab(s)):
|
||||
|
||||
1. Source specification, which is one of:
|
||||
|
||||
- domain name
|
||||
- `$anyvm` - any domain
|
||||
- `$tag:some-tag` - VM having tag `some-tag`
|
||||
- `$type:vm-type` - VM of `vm-type` type, available types:
|
||||
AppVM, TemplateVM, StandaloneVM, DispVM
|
||||
AppVM, TemplateVM, StandaloneVM, DispVM
|
||||
|
||||
2. Target specification, one of:
|
||||
|
||||
@ -25,15 +26,20 @@ Each line consist of three values separated by white characters (space(s), tab(s
|
||||
- `$anyvm` - any domain, excluding dom0
|
||||
- `$tag:some-tag` - domain having tag `some-tag`
|
||||
- `$type:vm-type` - domain of `vm-type` type, available types:
|
||||
AppVM, TemplateVM, StandaloneVM, DispVM
|
||||
AppVM, TemplateVM, StandaloneVM, DispVM
|
||||
- `$default` - used when caller did not specified any VM
|
||||
- `$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:
|
||||
|
||||
- `allow` - allow the call, without further questions; optional parameters:
|
||||
|
||||
- `target=` - override caller provided call target -
|
||||
possible values are: domain name, `$dispvm` or `$dispvm:vm-name`
|
||||
- `user=` - call the service using this user, instead of the user
|
||||
@ -41,6 +47,7 @@ Each line consist of three values separated by white characters (space(s), tab(s
|
||||
- `deny` - deny the call, without further questions; no optional
|
||||
parameters are supported
|
||||
- `ask` - ask the user for confirmation; optional parameters:
|
||||
|
||||
- `target=` - override user provided call target
|
||||
- `user=` - call the service using this user, instead of the user
|
||||
pointed by target VM's `default_user` property
|
||||
|
@ -1,13 +0,0 @@
|
||||
## Note that policy parsing stops at the first match,
|
||||
## so adding anything below "$anyvm $anyvm action" line will have no effect
|
||||
|
||||
## Please use a single # to start your custom comments
|
||||
|
||||
## Add your entries here, make sure to append ",target=dom0" to all allow/ask actions
|
||||
|
||||
## Include a single file for all admin.* methods to ease setting up Management VM.
|
||||
## To allow only specific actions, edit specific policy file, like this one. To
|
||||
## allow all of them, edit /etc/qubes-rpc/include/admin-all.
|
||||
$include:/etc/qubes-rpc/policy/include/admin-all
|
||||
|
||||
$anyvm $anyvm deny
|
13
qubes-rpc-policy/admin-global-ro
Normal file
13
qubes-rpc-policy/admin-global-ro
Normal file
@ -0,0 +1,13 @@
|
||||
## This file is included from all global read-only admin.* policy files
|
||||
## _in default configuration_. To allow only specific action,
|
||||
## edit specific policy file.
|
||||
|
||||
## Note that policy parsing stops at the first match,
|
||||
|
||||
## Please use a single # to start your custom comments
|
||||
|
||||
## Include all already having write access
|
||||
$include:include/admin-global-rwx
|
||||
|
||||
## Add your entries here, make sure to append ",target=dom0" to all allow/ask actions
|
||||
|
11
qubes-rpc-policy/admin-global-rwx
Normal file
11
qubes-rpc-policy/admin-global-rwx
Normal file
@ -0,0 +1,11 @@
|
||||
## This file is included from all global read-write admin.* policy files
|
||||
## _in default configuration_. To allow only specific action,
|
||||
## edit specific policy file.
|
||||
|
||||
## Note that policy parsing stops at the first match,
|
||||
## so adding anything below "$anyvm $anyvm action" line will have no effect
|
||||
|
||||
## Please use a single # to start your custom comments
|
||||
|
||||
## Add your entries here, make sure to append ",target=dom0" to all allow/ask actions
|
||||
|
14
qubes-rpc-policy/admin-local-ro
Normal file
14
qubes-rpc-policy/admin-local-ro
Normal file
@ -0,0 +1,14 @@
|
||||
## This file is included from all local read-only admin.* policy files
|
||||
## _in default configuration_. To allow only specific action,
|
||||
## edit specific policy file.
|
||||
|
||||
## Note that policy parsing stops at the first match,
|
||||
## so adding anything below "$anyvm $anyvm action" line will have no effect
|
||||
|
||||
## Please use a single # to start your custom comments
|
||||
|
||||
## Include all already having write access
|
||||
$include:include/admin-local-rwx
|
||||
|
||||
## Add your entries here, make sure to append ",target=dom0" to all allow/ask actions
|
||||
|
@ -1,5 +1,6 @@
|
||||
## This file is included from all admin.* policy files _in default
|
||||
## configuration_. To allow only specific action, edit specific policy file.
|
||||
## This file is included from all local read-write admin.* policy files
|
||||
## _in default configuration_. To allow only specific action,
|
||||
## edit specific policy file.
|
||||
|
||||
## Note that policy parsing stops at the first match,
|
||||
## so adding anything below "$anyvm $anyvm action" line will have no effect
|
||||
@ -8,4 +9,3 @@
|
||||
|
||||
## Add your entries here, make sure to append ",target=dom0" to all allow/ask actions
|
||||
|
||||
$anyvm $anyvm deny
|
96
qubes-rpc-policy/generate-admin-policy
Executable file
96
qubes-rpc-policy/generate-admin-policy
Executable file
@ -0,0 +1,96 @@
|
||||
#!/usr/bin/python3
|
||||
# coding=utf-8
|
||||
# The Qubes OS Project, https://www.qubes-os.org/
|
||||
#
|
||||
# Copyright (C) 2017 Marek Marczykowski-Górecki
|
||||
# <marmarek@invisiblethingslab.com>
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program 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 General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License along
|
||||
# with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
import argparse
|
||||
import os
|
||||
|
||||
import sys
|
||||
|
||||
import qubes.api.admin
|
||||
|
||||
parser = argparse.ArgumentParser(
|
||||
description='Generate default Admin API policy')
|
||||
parser.add_argument('--include-base', action='store',
|
||||
default='include',
|
||||
help='Base path for included paths (default: %(default)s)')
|
||||
parser.add_argument('--destdir', action='store',
|
||||
default='/etc/qubes-rpc/policy',
|
||||
help='Directory where write output files to (default: %(default)s)')
|
||||
parser.add_argument('--verbose', action='store_true', default=False,
|
||||
help='Be verbose')
|
||||
parser.add_argument('--exclude', action='store', nargs='*',
|
||||
help='Exclude service')
|
||||
parser.add_argument('service', nargs='*', action='store',
|
||||
help='Generate policy for those services (default: all)')
|
||||
|
||||
default_policy_header = '''\
|
||||
## Note that policy parsing stops at the first match.
|
||||
## Anything not specifically allowed here (or in included file) will be denied.
|
||||
|
||||
## Please use a single # to start your custom comments
|
||||
|
||||
## Add your entries here, make sure to append ",target=dom0" to all allow/ask actions
|
||||
|
||||
## Include a common file for all admin.* methods to ease setting up
|
||||
## Management VM.
|
||||
## To allow only specific actions, edit specific policy file, like this one. To
|
||||
## allow all of them, edit appropriate /etc/qubes-rpc/include/admin-*.
|
||||
|
||||
'''
|
||||
|
||||
def write_default_policy(args, apiname, clasifiers):
|
||||
''' Write single default policy for given API call '''
|
||||
assert 'scope' in clasifiers, \
|
||||
'Method {} lack scope classifier'.format(apiname)
|
||||
assert any(attr in clasifiers for attr in ('read', 'write', 'execute')), \
|
||||
'Method {} lack read/write/execute classifier'.format(apiname)
|
||||
assert clasifiers['scope'] in ('local', 'global'), \
|
||||
'Method {} have invalid scope: {}'.format(apiname, clasifiers['scope'])
|
||||
|
||||
file_to_include = 'admin-{scope}-{rwx}'.format(
|
||||
scope=clasifiers['scope'],
|
||||
rwx=('rwx' if clasifiers.get('write', False) or
|
||||
clasifiers.get('execute', False)
|
||||
else 'ro'))
|
||||
|
||||
if args.verbose:
|
||||
print('Service {}: include {}'.format(apiname, file_to_include),
|
||||
file=sys.stderr)
|
||||
with open(os.path.join(args.destdir, apiname), 'w') as f:
|
||||
f.write(default_policy_header)
|
||||
f.write('$include:{}\n'.format(
|
||||
os.path.join(args.include_base, file_to_include)))
|
||||
|
||||
|
||||
def main(args=None):
|
||||
''' Main function of default-admin-policy tool'''
|
||||
args = parser.parse_args(args)
|
||||
|
||||
for func, apiname, _ in qubes.api.admin.QubesAdminAPI.list_methods():
|
||||
if args.service and apiname not in args.service:
|
||||
continue
|
||||
if args.exclude and apiname in args.exclude:
|
||||
continue
|
||||
write_default_policy(args, apiname, func.classifiers)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
@ -329,8 +329,8 @@ class property(object): # pylint: disable=redefined-builtin,invalid-name
|
||||
setter. Can raise QubesValueError if the value is invalid.
|
||||
|
||||
:param untrusted_newvalue: value to be validated
|
||||
:return sanitized value
|
||||
:raises qubes.exc.QubesValueError
|
||||
:return: sanitized value
|
||||
:raises: qubes.exc.QubesValueError
|
||||
'''
|
||||
# do not treat type='str' as sufficient validation
|
||||
if self.type is not None and self.type is not str:
|
||||
|
@ -41,7 +41,7 @@ class PermissionDenied(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def method(name, *, no_payload=False, endpoints=None):
|
||||
def method(name, *, no_payload=False, endpoints=None, **classifiers):
|
||||
'''Decorator factory for methods intended to appear in API.
|
||||
|
||||
The decorated method can be called from public API using a child of
|
||||
@ -51,6 +51,8 @@ def method(name, *, no_payload=False, endpoints=None):
|
||||
:param str name: qrexec rpc method name
|
||||
:param bool no_payload: if :py:obj:`True`, will barf on non-empty payload; \
|
||||
also will not pass payload at all to the method
|
||||
:param iterable endpoints: if specified, method serve multiple API calls
|
||||
generated by replacing `{endpoint}` with each value in this iterable
|
||||
|
||||
The expected function method should have one argument (other than usual
|
||||
*self*), ``untrusted_payload``, which will contain the payload.
|
||||
@ -75,11 +77,14 @@ def method(name, *, no_payload=False, endpoints=None):
|
||||
|
||||
# pylint: disable=protected-access
|
||||
if endpoints is None:
|
||||
func._rpcname = ((name, None),)
|
||||
func.rpcnames = ((name, None),)
|
||||
else:
|
||||
func._rpcname = tuple(
|
||||
func.rpcnames = tuple(
|
||||
(name.format(endpoint=endpoint), endpoint)
|
||||
for endpoint in endpoints)
|
||||
|
||||
func.classifiers = classifiers
|
||||
|
||||
return func
|
||||
|
||||
return decorator
|
||||
@ -133,43 +138,45 @@ class AbstractQubesAPI(object):
|
||||
#: is this operation cancellable?
|
||||
self.cancellable = False
|
||||
|
||||
untrusted_candidates = []
|
||||
for attr in dir(self):
|
||||
func = getattr(self, attr)
|
||||
candidates = list(self.list_methods(self.method))
|
||||
|
||||
if not candidates:
|
||||
raise ProtocolError('no such method: {!r}'.format(self.method))
|
||||
|
||||
assert len(candidates) == 1, \
|
||||
'multiple candidates for method {!r}'.format(self.method)
|
||||
|
||||
#: the method to execute
|
||||
self._handler = candidates[0]
|
||||
self._running_handler = None
|
||||
|
||||
@classmethod
|
||||
def list_methods(cls, select_method=None):
|
||||
for attr in dir(cls):
|
||||
func = getattr(cls, attr)
|
||||
if not callable(func):
|
||||
continue
|
||||
|
||||
try:
|
||||
# pylint: disable=protected-access
|
||||
for mname, endpoint in func._rpcname:
|
||||
if mname != self.method:
|
||||
continue
|
||||
untrusted_candidates.append((func, endpoint))
|
||||
rpcnames = func.rpcnames
|
||||
except AttributeError:
|
||||
continue
|
||||
|
||||
if not untrusted_candidates:
|
||||
raise ProtocolError('no such method: {!r}'.format(self.method))
|
||||
|
||||
assert len(untrusted_candidates) == 1, \
|
||||
'multiple candidates for method {!r}'.format(self.method)
|
||||
|
||||
#: the method to execute
|
||||
self._handler = untrusted_candidates[0]
|
||||
self._running_handler = None
|
||||
del untrusted_candidates
|
||||
for mname, endpoint in rpcnames:
|
||||
if select_method is None or mname == select_method:
|
||||
yield (func, mname, endpoint)
|
||||
|
||||
def execute(self, *, untrusted_payload):
|
||||
'''Execute management operation.
|
||||
|
||||
This method is a coroutine.
|
||||
'''
|
||||
handler, endpoint = self._handler
|
||||
handler, _, endpoint = self._handler
|
||||
kwargs = {}
|
||||
if endpoint is not None:
|
||||
kwargs['endpoint'] = endpoint
|
||||
self._running_handler = asyncio.ensure_future(handler(
|
||||
self._running_handler = asyncio.ensure_future(handler(self,
|
||||
untrusted_payload=untrusted_payload, **kwargs))
|
||||
return self._running_handler
|
||||
|
||||
|
@ -76,7 +76,8 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
|
||||
|
||||
SOCKNAME = '/var/run/qubesd.sock'
|
||||
|
||||
@qubes.api.method('admin.vmclass.List', no_payload=True)
|
||||
@qubes.api.method('admin.vmclass.List', no_payload=True,
|
||||
scope='global', read=True)
|
||||
@asyncio.coroutine
|
||||
def vmclass_list(self):
|
||||
'''List all VM classes'''
|
||||
@ -89,7 +90,8 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
|
||||
return ''.join('{}\n'.format(ep.name)
|
||||
for ep in entrypoints)
|
||||
|
||||
@qubes.api.method('admin.vm.List', no_payload=True)
|
||||
@qubes.api.method('admin.vm.List', no_payload=True,
|
||||
scope='global', read=True)
|
||||
@asyncio.coroutine
|
||||
def vm_list(self):
|
||||
'''List all the domains'''
|
||||
@ -106,13 +108,15 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
|
||||
vm.get_power_state())
|
||||
for vm in sorted(domains))
|
||||
|
||||
@qubes.api.method('admin.vm.property.List', no_payload=True)
|
||||
@qubes.api.method('admin.vm.property.List', no_payload=True,
|
||||
scope='local', read=True)
|
||||
@asyncio.coroutine
|
||||
def vm_property_list(self):
|
||||
'''List all properties on a qube'''
|
||||
return self._property_list(self.dest)
|
||||
|
||||
@qubes.api.method('admin.property.List', no_payload=True)
|
||||
@qubes.api.method('admin.property.List', no_payload=True,
|
||||
scope='global', read=True)
|
||||
@asyncio.coroutine
|
||||
def property_list(self):
|
||||
'''List all global properties'''
|
||||
@ -126,13 +130,15 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
|
||||
|
||||
return ''.join('{}\n'.format(prop.__name__) for prop in properties)
|
||||
|
||||
@qubes.api.method('admin.vm.property.Get', no_payload=True)
|
||||
@qubes.api.method('admin.vm.property.Get', no_payload=True,
|
||||
scope='local', read=True)
|
||||
@asyncio.coroutine
|
||||
def vm_property_get(self):
|
||||
'''Get a value of one property'''
|
||||
return self._property_get(self.dest)
|
||||
|
||||
@qubes.api.method('admin.property.Get', no_payload=True)
|
||||
@qubes.api.method('admin.property.Get', no_payload=True,
|
||||
scope='global', read=True)
|
||||
@asyncio.coroutine
|
||||
def property_get(self):
|
||||
'''Get a value of one global property'''
|
||||
@ -168,14 +174,16 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
|
||||
property_type,
|
||||
str(value) if value is not None else '')
|
||||
|
||||
@qubes.api.method('admin.vm.property.Set')
|
||||
@qubes.api.method('admin.vm.property.Set',
|
||||
scope='local', write=True)
|
||||
@asyncio.coroutine
|
||||
def vm_property_set(self, untrusted_payload):
|
||||
'''Set property value'''
|
||||
return self._property_set(self.dest,
|
||||
untrusted_payload=untrusted_payload)
|
||||
|
||||
@qubes.api.method('admin.property.Set')
|
||||
@qubes.api.method('admin.property.Set',
|
||||
scope='global', write=True)
|
||||
@asyncio.coroutine
|
||||
def property_set(self, untrusted_payload):
|
||||
'''Set property value'''
|
||||
@ -195,13 +203,15 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
|
||||
setattr(dest, self.arg, newvalue)
|
||||
self.app.save()
|
||||
|
||||
@qubes.api.method('admin.vm.property.Help', no_payload=True)
|
||||
@qubes.api.method('admin.vm.property.Help', no_payload=True,
|
||||
scope='local', read=True)
|
||||
@asyncio.coroutine
|
||||
def vm_property_help(self):
|
||||
'''Get help for one property'''
|
||||
return self._property_help(self.dest)
|
||||
|
||||
@qubes.api.method('admin.property.Help', no_payload=True)
|
||||
@qubes.api.method('admin.property.Help', no_payload=True,
|
||||
scope='global', read=True)
|
||||
@asyncio.coroutine
|
||||
def property_help(self):
|
||||
'''Get help for one property'''
|
||||
@ -221,13 +231,15 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
|
||||
|
||||
return qubes.utils.format_doc(doc)
|
||||
|
||||
@qubes.api.method('admin.vm.property.Reset', no_payload=True)
|
||||
@qubes.api.method('admin.vm.property.Reset', no_payload=True,
|
||||
scope='local', write=True)
|
||||
@asyncio.coroutine
|
||||
def vm_property_reset(self):
|
||||
'''Reset a property to a default value'''
|
||||
return self._property_reset(self.dest)
|
||||
|
||||
@qubes.api.method('admin.property.Reset', no_payload=True)
|
||||
@qubes.api.method('admin.property.Reset', no_payload=True,
|
||||
scope='global', write=True)
|
||||
@asyncio.coroutine
|
||||
def property_reset(self):
|
||||
'''Reset a property to a default value'''
|
||||
@ -243,7 +255,8 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
|
||||
delattr(dest, self.arg)
|
||||
self.app.save()
|
||||
|
||||
@qubes.api.method('admin.vm.volume.List', no_payload=True)
|
||||
@qubes.api.method('admin.vm.volume.List', no_payload=True,
|
||||
scope='local', read=True)
|
||||
@asyncio.coroutine
|
||||
def vm_volume_list(self):
|
||||
assert not self.arg
|
||||
@ -251,7 +264,8 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
|
||||
volume_names = self.fire_event_for_filter(self.dest.volumes.keys())
|
||||
return ''.join('{}\n'.format(name) for name in volume_names)
|
||||
|
||||
@qubes.api.method('admin.vm.volume.Info', no_payload=True)
|
||||
@qubes.api.method('admin.vm.volume.Info', no_payload=True,
|
||||
scope='local', read=True)
|
||||
@asyncio.coroutine
|
||||
def vm_volume_info(self):
|
||||
assert self.arg in self.dest.volumes.keys()
|
||||
@ -266,7 +280,8 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
|
||||
return ''.join('{}={}\n'.format(key, getattr(volume, key)) for key in
|
||||
volume_properties)
|
||||
|
||||
@qubes.api.method('admin.vm.volume.ListSnapshots', no_payload=True)
|
||||
@qubes.api.method('admin.vm.volume.ListSnapshots', no_payload=True,
|
||||
scope='local', read=True)
|
||||
@asyncio.coroutine
|
||||
def vm_volume_listsnapshots(self):
|
||||
assert self.arg in self.dest.volumes.keys()
|
||||
@ -277,7 +292,8 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
|
||||
|
||||
return ''.join('{}\n'.format(revision) for revision in revisions)
|
||||
|
||||
@qubes.api.method('admin.vm.volume.Revert')
|
||||
@qubes.api.method('admin.vm.volume.Revert',
|
||||
scope='local', write=True)
|
||||
@asyncio.coroutine
|
||||
def vm_volume_revert(self, untrusted_payload):
|
||||
assert self.arg in self.dest.volumes.keys()
|
||||
@ -294,7 +310,10 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
|
||||
self.dest.storage.get_pool(volume).revert(revision)
|
||||
self.app.save()
|
||||
|
||||
@qubes.api.method('admin.vm.volume.CloneFrom', no_payload=True)
|
||||
# write=True because this allow to clone VM - and most likely modify that
|
||||
# one - still having the same data
|
||||
@qubes.api.method('admin.vm.volume.CloneFrom', no_payload=True,
|
||||
scope='local', write=True)
|
||||
@asyncio.coroutine
|
||||
def vm_volume_clone_from(self):
|
||||
assert self.arg in self.dest.volumes.keys()
|
||||
@ -314,7 +333,8 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
|
||||
self.app.api_admin_pending_clone[token] = volume
|
||||
return token
|
||||
|
||||
@qubes.api.method('admin.vm.volume.CloneTo')
|
||||
@qubes.api.method('admin.vm.volume.CloneTo',
|
||||
scope='local', write=True)
|
||||
@asyncio.coroutine
|
||||
def vm_volume_clone_to(self, untrusted_payload):
|
||||
assert self.arg in self.dest.volumes.keys()
|
||||
@ -347,7 +367,8 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
|
||||
self.dest.volumes[self.arg] = op_retval
|
||||
self.app.save()
|
||||
|
||||
@qubes.api.method('admin.vm.volume.Resize')
|
||||
@qubes.api.method('admin.vm.volume.Resize',
|
||||
scope='local', write=True)
|
||||
@asyncio.coroutine
|
||||
def vm_volume_resize(self, untrusted_payload):
|
||||
assert self.arg in self.dest.volumes.keys()
|
||||
@ -363,7 +384,8 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
|
||||
self.dest.storage.resize(self.arg, size)
|
||||
self.app.save()
|
||||
|
||||
@qubes.api.method('admin.vm.volume.Import', no_payload=True)
|
||||
@qubes.api.method('admin.vm.volume.Import', no_payload=True,
|
||||
scope='local', write=True)
|
||||
@asyncio.coroutine
|
||||
def vm_volume_import(self):
|
||||
'''Import volume data.
|
||||
@ -392,7 +414,8 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
|
||||
|
||||
return '{} {}'.format(size, path)
|
||||
|
||||
@qubes.api.method('admin.vm.tag.List', no_payload=True)
|
||||
@qubes.api.method('admin.vm.tag.List', no_payload=True,
|
||||
scope='local', read=True)
|
||||
@asyncio.coroutine
|
||||
def vm_tag_list(self):
|
||||
assert not self.arg
|
||||
@ -403,7 +426,8 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
|
||||
|
||||
return ''.join('{}\n'.format(tag) for tag in sorted(tags))
|
||||
|
||||
@qubes.api.method('admin.vm.tag.Get', no_payload=True)
|
||||
@qubes.api.method('admin.vm.tag.Get', no_payload=True,
|
||||
scope='local', read=True)
|
||||
@asyncio.coroutine
|
||||
def vm_tag_get(self):
|
||||
qubes.vm.Tags.validate_tag(self.arg)
|
||||
@ -412,7 +436,8 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
|
||||
|
||||
return '1' if self.arg in self.dest.tags else '0'
|
||||
|
||||
@qubes.api.method('admin.vm.tag.Set', no_payload=True)
|
||||
@qubes.api.method('admin.vm.tag.Set', no_payload=True,
|
||||
scope='local', write=True)
|
||||
@asyncio.coroutine
|
||||
def vm_tag_set(self):
|
||||
qubes.vm.Tags.validate_tag(self.arg)
|
||||
@ -422,7 +447,8 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
|
||||
self.dest.tags.add(self.arg)
|
||||
self.app.save()
|
||||
|
||||
@qubes.api.method('admin.vm.tag.Remove', no_payload=True)
|
||||
@qubes.api.method('admin.vm.tag.Remove', no_payload=True,
|
||||
scope='local', write=True)
|
||||
@asyncio.coroutine
|
||||
def vm_tag_remove(self):
|
||||
qubes.vm.Tags.validate_tag(self.arg)
|
||||
@ -435,7 +461,8 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
|
||||
raise qubes.exc.QubesTagNotFoundError(self.dest, self.arg)
|
||||
self.app.save()
|
||||
|
||||
@qubes.api.method('admin.pool.List', no_payload=True)
|
||||
@qubes.api.method('admin.pool.List', no_payload=True,
|
||||
scope='global', read=True)
|
||||
@asyncio.coroutine
|
||||
def pool_list(self):
|
||||
assert not self.arg
|
||||
@ -445,7 +472,8 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
|
||||
|
||||
return ''.join('{}\n'.format(pool) for pool in pools)
|
||||
|
||||
@qubes.api.method('admin.pool.ListDrivers', no_payload=True)
|
||||
@qubes.api.method('admin.pool.ListDrivers', no_payload=True,
|
||||
scope='global', read=True)
|
||||
@asyncio.coroutine
|
||||
def pool_listdrivers(self):
|
||||
assert self.dest.name == 'dom0'
|
||||
@ -458,7 +486,8 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
|
||||
' '.join(qubes.storage.driver_parameters(driver)))
|
||||
for driver in drivers)
|
||||
|
||||
@qubes.api.method('admin.pool.Info', no_payload=True)
|
||||
@qubes.api.method('admin.pool.Info', no_payload=True,
|
||||
scope='global', read=True)
|
||||
@asyncio.coroutine
|
||||
def pool_info(self):
|
||||
assert self.dest.name == 'dom0'
|
||||
@ -471,7 +500,8 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
|
||||
return ''.join('{}={}\n'.format(prop, val)
|
||||
for prop, val in sorted(pool.config.items()))
|
||||
|
||||
@qubes.api.method('admin.pool.Add')
|
||||
@qubes.api.method('admin.pool.Add',
|
||||
scope='global', write=True)
|
||||
@asyncio.coroutine
|
||||
def pool_add(self, untrusted_payload):
|
||||
assert self.dest.name == 'dom0'
|
||||
@ -506,7 +536,8 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
|
||||
self.app.add_pool(name=pool_name, driver=self.arg, **pool_config)
|
||||
self.app.save()
|
||||
|
||||
@qubes.api.method('admin.pool.Remove', no_payload=True)
|
||||
@qubes.api.method('admin.pool.Remove', no_payload=True,
|
||||
scope='global', write=True)
|
||||
@asyncio.coroutine
|
||||
def pool_remove(self):
|
||||
assert self.dest.name == 'dom0'
|
||||
@ -517,7 +548,8 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
|
||||
self.app.remove_pool(self.arg)
|
||||
self.app.save()
|
||||
|
||||
@qubes.api.method('admin.label.List', no_payload=True)
|
||||
@qubes.api.method('admin.label.List', no_payload=True,
|
||||
scope='global', read=True)
|
||||
@asyncio.coroutine
|
||||
def label_list(self):
|
||||
assert self.dest.name == 'dom0'
|
||||
@ -527,7 +559,8 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
|
||||
|
||||
return ''.join('{}\n'.format(label.name) for label in labels)
|
||||
|
||||
@qubes.api.method('admin.label.Get', no_payload=True)
|
||||
@qubes.api.method('admin.label.Get', no_payload=True,
|
||||
scope='global', read=True)
|
||||
@asyncio.coroutine
|
||||
def label_get(self):
|
||||
assert self.dest.name == 'dom0'
|
||||
@ -541,7 +574,8 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
|
||||
|
||||
return label.color
|
||||
|
||||
@qubes.api.method('admin.label.Index', no_payload=True)
|
||||
@qubes.api.method('admin.label.Index', no_payload=True,
|
||||
scope='global', read=True)
|
||||
@asyncio.coroutine
|
||||
def label_index(self):
|
||||
assert self.dest.name == 'dom0'
|
||||
@ -555,7 +589,8 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
|
||||
|
||||
return str(label.index)
|
||||
|
||||
@qubes.api.method('admin.label.Create')
|
||||
@qubes.api.method('admin.label.Create',
|
||||
scope='global', write=True)
|
||||
@asyncio.coroutine
|
||||
def label_create(self, untrusted_payload):
|
||||
assert self.dest.name == 'dom0'
|
||||
@ -591,7 +626,8 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
|
||||
self.app.labels[new_index] = label
|
||||
self.app.save()
|
||||
|
||||
@qubes.api.method('admin.label.Remove', no_payload=True)
|
||||
@qubes.api.method('admin.label.Remove', no_payload=True,
|
||||
scope='global', write=True)
|
||||
@asyncio.coroutine
|
||||
def label_remove(self):
|
||||
assert self.dest.name == 'dom0'
|
||||
@ -613,7 +649,8 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
|
||||
del self.app.labels[label.index]
|
||||
self.app.save()
|
||||
|
||||
@qubes.api.method('admin.vm.Start', no_payload=True)
|
||||
@qubes.api.method('admin.vm.Start', no_payload=True,
|
||||
scope='local', execute=True)
|
||||
@asyncio.coroutine
|
||||
def vm_start(self):
|
||||
assert not self.arg
|
||||
@ -625,35 +662,40 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
|
||||
raise qubes.exc.QubesException('Start failed: ' + str(e))
|
||||
|
||||
|
||||
@qubes.api.method('admin.vm.Shutdown', no_payload=True)
|
||||
@qubes.api.method('admin.vm.Shutdown', no_payload=True,
|
||||
scope='local', execute=True)
|
||||
@asyncio.coroutine
|
||||
def vm_shutdown(self):
|
||||
assert not self.arg
|
||||
self.fire_event_for_permission()
|
||||
yield from self.dest.shutdown()
|
||||
|
||||
@qubes.api.method('admin.vm.Pause', no_payload=True)
|
||||
@qubes.api.method('admin.vm.Pause', no_payload=True,
|
||||
scope='local', execute=True)
|
||||
@asyncio.coroutine
|
||||
def vm_pause(self):
|
||||
assert not self.arg
|
||||
self.fire_event_for_permission()
|
||||
yield from self.dest.pause()
|
||||
|
||||
@qubes.api.method('admin.vm.Unpause', no_payload=True)
|
||||
@qubes.api.method('admin.vm.Unpause', no_payload=True,
|
||||
scope='local', execute=True)
|
||||
@asyncio.coroutine
|
||||
def vm_unpause(self):
|
||||
assert not self.arg
|
||||
self.fire_event_for_permission()
|
||||
yield from self.dest.unpause()
|
||||
|
||||
@qubes.api.method('admin.vm.Kill', no_payload=True)
|
||||
@qubes.api.method('admin.vm.Kill', no_payload=True,
|
||||
scope='local', execute=True)
|
||||
@asyncio.coroutine
|
||||
def vm_kill(self):
|
||||
assert not self.arg
|
||||
self.fire_event_for_permission()
|
||||
yield from self.dest.kill()
|
||||
|
||||
@qubes.api.method('admin.Events', no_payload=True)
|
||||
@qubes.api.method('admin.Events', no_payload=True,
|
||||
scope='global', read=True)
|
||||
@asyncio.coroutine
|
||||
def events(self):
|
||||
assert not self.arg
|
||||
@ -694,14 +736,16 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
|
||||
else:
|
||||
self.dest.remove_handler('*', dispatcher.vm_handler)
|
||||
|
||||
@qubes.api.method('admin.vm.feature.List', no_payload=True)
|
||||
@qubes.api.method('admin.vm.feature.List', no_payload=True,
|
||||
scope='local', read=True)
|
||||
@asyncio.coroutine
|
||||
def vm_feature_list(self):
|
||||
assert not self.arg
|
||||
features = self.fire_event_for_filter(self.dest.features.keys())
|
||||
return ''.join('{}\n'.format(feature) for feature in features)
|
||||
|
||||
@qubes.api.method('admin.vm.feature.Get', no_payload=True)
|
||||
@qubes.api.method('admin.vm.feature.Get', no_payload=True,
|
||||
scope='local', read=True)
|
||||
@asyncio.coroutine
|
||||
def vm_feature_get(self):
|
||||
# validation of self.arg done by qrexec-policy is enough
|
||||
@ -713,7 +757,8 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
|
||||
raise qubes.exc.QubesFeatureNotFoundError(self.dest, self.arg)
|
||||
return value
|
||||
|
||||
@qubes.api.method('admin.vm.feature.CheckWithTemplate', no_payload=True)
|
||||
@qubes.api.method('admin.vm.feature.CheckWithTemplate', no_payload=True,
|
||||
scope='local', read=True)
|
||||
@asyncio.coroutine
|
||||
def vm_feature_checkwithtemplate(self):
|
||||
# validation of self.arg done by qrexec-policy is enough
|
||||
@ -725,7 +770,8 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
|
||||
raise qubes.exc.QubesFeatureNotFoundError(self.dest, self.arg)
|
||||
return value
|
||||
|
||||
@qubes.api.method('admin.vm.feature.Remove', no_payload=True)
|
||||
@qubes.api.method('admin.vm.feature.Remove', no_payload=True,
|
||||
scope='local', write=True)
|
||||
@asyncio.coroutine
|
||||
def vm_feature_remove(self):
|
||||
# validation of self.arg done by qrexec-policy is enough
|
||||
@ -737,7 +783,8 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
|
||||
raise qubes.exc.QubesFeatureNotFoundError(self.dest, self.arg)
|
||||
self.app.save()
|
||||
|
||||
@qubes.api.method('admin.vm.feature.Set')
|
||||
@qubes.api.method('admin.vm.feature.Set',
|
||||
scope='local', write=True)
|
||||
@asyncio.coroutine
|
||||
def vm_feature_set(self, untrusted_payload):
|
||||
# validation of self.arg done by qrexec-policy is enough
|
||||
@ -749,14 +796,16 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
|
||||
self.app.save()
|
||||
|
||||
@qubes.api.method('admin.vm.Create.{endpoint}', endpoints=(ep.name
|
||||
for ep in pkg_resources.iter_entry_points(qubes.vm.VM_ENTRY_POINT)))
|
||||
for ep in pkg_resources.iter_entry_points(qubes.vm.VM_ENTRY_POINT)),
|
||||
scope='global', write=True)
|
||||
@asyncio.coroutine
|
||||
def vm_create(self, endpoint, untrusted_payload=None):
|
||||
return self._vm_create(endpoint, allow_pool=False,
|
||||
untrusted_payload=untrusted_payload)
|
||||
|
||||
@qubes.api.method('admin.vm.CreateInPool.{endpoint}', endpoints=(ep.name
|
||||
for ep in pkg_resources.iter_entry_points(qubes.vm.VM_ENTRY_POINT)))
|
||||
for ep in pkg_resources.iter_entry_points(qubes.vm.VM_ENTRY_POINT)),
|
||||
scope='global', write=True)
|
||||
@asyncio.coroutine
|
||||
def vm_create_in_pool(self, endpoint, untrusted_payload=None):
|
||||
return self._vm_create(endpoint, allow_pool=True,
|
||||
@ -846,7 +895,8 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
|
||||
raise
|
||||
self.app.save()
|
||||
|
||||
@qubes.api.method('admin.vm.Remove', no_payload=True)
|
||||
@qubes.api.method('admin.vm.Remove', no_payload=True,
|
||||
scope='global', write=True)
|
||||
@asyncio.coroutine
|
||||
def vm_remove(self):
|
||||
assert not self.arg
|
||||
@ -867,7 +917,8 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
|
||||
|
||||
@qubes.api.method('admin.vm.device.{endpoint}.Available', endpoints=(ep.name
|
||||
for ep in pkg_resources.iter_entry_points('qubes.devices')),
|
||||
no_payload=True)
|
||||
no_payload=True,
|
||||
scope='local', read=True)
|
||||
@asyncio.coroutine
|
||||
def vm_device_available(self, endpoint):
|
||||
devclass = endpoint
|
||||
@ -901,7 +952,8 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
|
||||
|
||||
@qubes.api.method('admin.vm.device.{endpoint}.List', endpoints=(ep.name
|
||||
for ep in pkg_resources.iter_entry_points('qubes.devices')),
|
||||
no_payload=True)
|
||||
no_payload=True,
|
||||
scope='local', read=True)
|
||||
@asyncio.coroutine
|
||||
def vm_device_list(self, endpoint):
|
||||
devclass = endpoint
|
||||
@ -932,8 +984,12 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
|
||||
return ''.join('{} {}\n'.format(ident, dev_info[ident])
|
||||
for ident in sorted(dev_info))
|
||||
|
||||
# Attach/Detach action can both modify persistent state (with
|
||||
# persistent=True) and volatile state of running VM (with persistent=False).
|
||||
# For this reason, write=True + execute=True
|
||||
@qubes.api.method('admin.vm.device.{endpoint}.Attach', endpoints=(ep.name
|
||||
for ep in pkg_resources.iter_entry_points('qubes.devices')))
|
||||
for ep in pkg_resources.iter_entry_points('qubes.devices')),
|
||||
scope='local', write=True, execute=True)
|
||||
@asyncio.coroutine
|
||||
def vm_device_attach(self, endpoint, untrusted_payload):
|
||||
devclass = endpoint
|
||||
@ -972,9 +1028,13 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
|
||||
yield from self.dest.devices[devclass].attach(assignment)
|
||||
self.app.save()
|
||||
|
||||
# Attach/Detach action can both modify persistent state (with
|
||||
# persistent=True) and volatile state of running VM (with persistent=False).
|
||||
# For this reason, write=True + execute=True
|
||||
@qubes.api.method('admin.vm.device.{endpoint}.Detach', endpoints=(ep.name
|
||||
for ep in pkg_resources.iter_entry_points('qubes.devices')),
|
||||
no_payload=True)
|
||||
no_payload=True,
|
||||
scope='local', write=True, execute=True)
|
||||
@asyncio.coroutine
|
||||
def vm_device_detach(self, endpoint):
|
||||
devclass = endpoint
|
||||
@ -994,7 +1054,8 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
|
||||
yield from self.dest.devices[devclass].detach(assignment)
|
||||
self.app.save()
|
||||
|
||||
@qubes.api.method('admin.vm.firewall.Get', no_payload=True)
|
||||
@qubes.api.method('admin.vm.firewall.Get', no_payload=True,
|
||||
scope='local', read=True)
|
||||
@asyncio.coroutine
|
||||
def vm_firewall_get(self):
|
||||
assert not self.arg
|
||||
@ -1004,7 +1065,8 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
|
||||
return ''.join('{}\n'.format(rule.api_rule)
|
||||
for rule in self.dest.firewall.rules)
|
||||
|
||||
@qubes.api.method('admin.vm.firewall.Set')
|
||||
@qubes.api.method('admin.vm.firewall.Set',
|
||||
scope='local', write=True)
|
||||
@asyncio.coroutine
|
||||
def vm_firewall_set(self, untrusted_payload):
|
||||
assert not self.arg
|
||||
@ -1020,7 +1082,8 @@ class QubesAdminAPI(qubes.api.AbstractQubesAPI):
|
||||
self.dest.firewall.rules = rules
|
||||
self.dest.firewall.save()
|
||||
|
||||
@qubes.api.method('admin.vm.firewall.Reload', no_payload=True)
|
||||
@qubes.api.method('admin.vm.firewall.Reload', no_payload=True,
|
||||
scope='local', execute=True)
|
||||
@asyncio.coroutine
|
||||
def vm_firewall_reload(self):
|
||||
assert not self.arg
|
||||
|
@ -119,6 +119,7 @@ class BackupHeader(object):
|
||||
|
||||
:param untrusted_header_text: header content
|
||||
:type untrusted_header_text: basestring
|
||||
|
||||
.. warning::
|
||||
This function may be exposed to not yet verified header,
|
||||
so is security critical.
|
||||
@ -864,9 +865,9 @@ def handle_streams(stream_in, streams_out, processes, size_limit=None,
|
||||
:param processes: dict of subprocess.Popen objects to monitor
|
||||
:param size_limit: int maximum data amount to process
|
||||
:param progress_callback: callable function to report progress, will be
|
||||
given copied data size (it should accumulate internally)
|
||||
given copied data size (it should accumulate internally)
|
||||
:return: failed process name, failed stream name, "size_limit" or None (
|
||||
no error)
|
||||
no error)
|
||||
'''
|
||||
buffer_size = 409600
|
||||
bytes_copied = 0
|
||||
|
@ -249,7 +249,7 @@ class OptionsCheckVisitor(docutils.nodes.SparseNodeVisitor):
|
||||
While the documentation talks about a
|
||||
'SparseNodeVisitor.depart_document()' function, this function does
|
||||
not exists. (For details see implementation of
|
||||
:py:method:`NodeVisitor.dispatch_departure()`) So we need to
|
||||
:py:meth:`NodeVisitor.dispatch_departure()`) So we need to
|
||||
manually call this.
|
||||
'''
|
||||
if ignored_options is None:
|
||||
@ -316,7 +316,7 @@ class CommandCheckVisitor(docutils.nodes.SparseNodeVisitor):
|
||||
While the documentation talks about a
|
||||
'SparseNodeVisitor.depart_document()' function, this function does
|
||||
not exists. (For details see implementation of
|
||||
:py:method:`NodeVisitor.dispatch_departure()`) So we need to
|
||||
:py:meth:`NodeVisitor.dispatch_departure()`) So we need to
|
||||
manually call this.
|
||||
'''
|
||||
if self.sub_commands:
|
||||
|
@ -177,7 +177,7 @@ def expectedFailureIfTemplate(templates):
|
||||
Decorator for marking specific test as expected to fail only for some
|
||||
templates. Template name is compared as substring, so 'whonix' will
|
||||
handle both 'whonix-ws' and 'whonix-gw'.
|
||||
templates can be either a single string, or an iterable
|
||||
templates can be either a single string, or an iterable
|
||||
"""
|
||||
def decorator(func):
|
||||
@functools.wraps(func)
|
||||
|
@ -303,11 +303,13 @@ class QubesArgumentParser(argparse.ArgumentParser):
|
||||
:py:class:`qubes.Qubes` object, just add argument for custom xml file
|
||||
:param bool want_force_root: add ``--force-root`` option
|
||||
:param mixed vmname_nargs: The number of ``VMNAME`` arguments that should be
|
||||
consumed. Values include:
|
||||
- N (an integer) consumes N arguments (and produces a list)
|
||||
- '?' consumes zero or one arguments
|
||||
- '*' consumes zero or more arguments (and produces a list)
|
||||
- '+' consumes one or more arguments (and produces a list)
|
||||
consumed. Values include:
|
||||
|
||||
- N (an integer) consumes N arguments (and produces a list)
|
||||
- '?' consumes zero or one arguments
|
||||
- '*' consumes zero or more arguments (and produces a list)
|
||||
- '+' consumes one or more arguments (and produces a list)
|
||||
|
||||
*kwargs* are passed to :py:class:`argparser.ArgumentParser`.
|
||||
|
||||
Currenty supported options:
|
||||
|
@ -594,8 +594,8 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM):
|
||||
|
||||
@property
|
||||
def block_devices(self):
|
||||
''' Return all :py:class:`qubes.storage.BlockDevice`s for current domain
|
||||
for serialization in the libvirt XML template as <disk>.
|
||||
''' Return all :py:class:`qubes.storage.BlockDevice` for current domain
|
||||
for serialization in the libvirt XML template as <disk>.
|
||||
'''
|
||||
for v in self.volumes.values():
|
||||
block_dev = v.block_device()
|
||||
@ -1050,7 +1050,7 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM):
|
||||
|
||||
:param str service: service name
|
||||
:param qubes.vm.qubesvm.QubesVM source: source domain as presented to
|
||||
this VM
|
||||
this VM
|
||||
:param str user: username to run service as
|
||||
:param bool filter_esc: filter escape sequences to protect terminal \
|
||||
emulator
|
||||
@ -1221,7 +1221,7 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM):
|
||||
This function take care to run it as appropriate user.
|
||||
|
||||
:param command: command to run (array for
|
||||
:py:meth:`subprocess.check_call`)
|
||||
:py:meth:`subprocess.check_call`)
|
||||
:param kwargs: args for :py:meth:`subprocess.check_call`
|
||||
:return: None
|
||||
''' # pylint: disable=redefined-builtin
|
||||
|
@ -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']:
|
||||
@ -82,7 +84,7 @@ def verify_special_value(value, for_target=True):
|
||||
|
||||
:param value: value to verify
|
||||
:param for_target: should classify target-only values as valid (
|
||||
'$default', '$dispvm')
|
||||
'$default', '$dispvm')
|
||||
:return: True or False
|
||||
'''
|
||||
# pylint: disable=too-many-return-statements
|
||||
@ -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:
|
||||
@ -121,11 +125,11 @@ class PolicyRule(object):
|
||||
self.filename = filename
|
||||
|
||||
try:
|
||||
self.source, self.target, self.full_action = line.split()
|
||||
self.source, self.target, self.full_action = line.split(maxsplit=2)
|
||||
except ValueError:
|
||||
raise PolicySyntaxError(filename, lineno, 'wrong number of fields')
|
||||
|
||||
(action, *params) = self.full_action.split(',')
|
||||
(action, *params) = self.full_action.replace(',', ' ').split()
|
||||
try:
|
||||
self.action = Action[action]
|
||||
except KeyError:
|
||||
@ -184,8 +188,9 @@ class PolicyRule(object):
|
||||
'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')
|
||||
|
||||
@ -197,7 +202,7 @@ class PolicyRule(object):
|
||||
|
||||
:param system_info: information about the system
|
||||
:param policy_value: value from qrexec policy (either self.source or
|
||||
self.target)
|
||||
self.target)
|
||||
:param value: value to be compared (source or target)
|
||||
:return: True or False
|
||||
'''
|
||||
@ -216,11 +221,19 @@ class PolicyRule(object):
|
||||
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 @@ class PolicyRule(object):
|
||||
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]
|
||||
|
||||
@ -247,8 +265,8 @@ class PolicyRule(object):
|
||||
Check if given (source, target) matches this policy line.
|
||||
|
||||
:param system_info: information about the system - available VMs,
|
||||
their types, labels, tags etc. as returned by
|
||||
:py:func:`app_to_system_info`
|
||||
their types, labels, tags etc. as returned by
|
||||
:py:func:`app_to_system_info`
|
||||
:param source: name of the source VM
|
||||
:param target: name of the target VM, or None if not specified
|
||||
:return: True or False
|
||||
@ -293,6 +311,8 @@ class PolicyRule(object):
|
||||
except KeyError:
|
||||
# TODO log a warning?
|
||||
pass
|
||||
elif self.target == '$adminvm':
|
||||
yield self.target
|
||||
elif self.target == '$dispvm':
|
||||
yield self.target
|
||||
else:
|
||||
@ -372,12 +392,14 @@ class PolicyAction(object):
|
||||
def execute(self, caller_ident):
|
||||
''' Execute allowed service call
|
||||
|
||||
:param caller_ident: Service caller ident (`process_ident,source_name,
|
||||
source_id`)
|
||||
:param caller_ident: Service caller ident
|
||||
(`process_ident,source_name, source_id`)
|
||||
'''
|
||||
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,
|
||||
@ -451,17 +473,20 @@ class Policy(object):
|
||||
>>> policy = Policy('some-service')
|
||||
>>> action = policy.evaluate(system_info, 'source-name', 'target-name')
|
||||
>>> if action.action == Action.ask:
|
||||
(... ask the user, see action.targets_for_ask ...)
|
||||
>>> # ... ask the user, see action.targets_for_ask ...
|
||||
>>> action.handle_user_response(response, target_chosen_by_user)
|
||||
>>> action.execute('process-ident')
|
||||
|
||||
'''
|
||||
|
||||
def __init__(self, service):
|
||||
policy_file = os.path.join(POLICY_DIR, service)
|
||||
def __init__(self, service, policy_dir=POLICY_DIR):
|
||||
policy_file = os.path.join(policy_dir, service)
|
||||
if not os.path.exists(policy_file):
|
||||
# fallback to policy without specific argument set (if any)
|
||||
policy_file = os.path.join(POLICY_DIR, service.split('+')[0])
|
||||
policy_file = os.path.join(policy_dir, service.split('+')[0])
|
||||
|
||||
#: policy storage directory
|
||||
self.policy_dir = policy_dir
|
||||
|
||||
#: service name
|
||||
self.service = service
|
||||
@ -493,7 +518,7 @@ class Policy(object):
|
||||
include_path = line.split(':', 1)[1]
|
||||
# os.path.join will leave include_path unchanged if it's
|
||||
# already absolute
|
||||
include_path = os.path.join(POLICY_DIR, include_path)
|
||||
include_path = os.path.join(self.policy_dir, include_path)
|
||||
self.load_policy_file(include_path)
|
||||
else:
|
||||
self.policy_rules.append(PolicyRule(line, path, lineno))
|
||||
@ -582,6 +607,15 @@ class Policy(object):
|
||||
'policy define \'allow\' action at {}:{} but no target is '
|
||||
'specified by caller or policy'.format(
|
||||
rule.filename, rule.lineno))
|
||||
if actual_target == '$dispvm':
|
||||
if system_info['domains'][source]['default_dispvm'] is None:
|
||||
raise AccessDenied(
|
||||
'policy define \'allow\' action to $dispvm at {}:{} '
|
||||
'but no DispVM base is set for this VM'.format(
|
||||
rule.filename, rule.lineno))
|
||||
actual_target = '$dispvm:' + \
|
||||
system_info['domains'][source]['default_dispvm']
|
||||
|
||||
return PolicyAction(self.service, source,
|
||||
actual_target, rule, target)
|
||||
else:
|
||||
@ -634,11 +668,11 @@ def get_system_info():
|
||||
data is nested dict structure with this structure:
|
||||
|
||||
- domains:
|
||||
- <domain name>:
|
||||
- tags: list of tags
|
||||
- type: domain type
|
||||
- dispvm_allowed: should DispVM based on this VM be allowed
|
||||
- default_dispvm: name of default AppVM for DispVMs started from here
|
||||
- `<domain name>`:
|
||||
- tags: list of tags
|
||||
- type: domain type
|
||||
- dispvm_allowed: should DispVM based on this VM be allowed
|
||||
- default_dispvm: name of default AppVM for DispVMs started from here
|
||||
|
||||
'''
|
||||
|
||||
|
122
qubespolicy/graph.py
Normal file
122
qubespolicy/graph.py
Normal file
@ -0,0 +1,122 @@
|
||||
# -*- encoding: utf8 -*-
|
||||
#
|
||||
# The Qubes OS Project, http://www.qubes-os.org
|
||||
#
|
||||
# Copyright (C) 2017 Marek Marczykowski-Górecki
|
||||
# <marmarek@invisiblethingslab.com>
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program 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 General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License along
|
||||
# with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
|
||||
import sys
|
||||
|
||||
import qubespolicy
|
||||
|
||||
parser = argparse.ArgumentParser(description='Graph qrexec policy')
|
||||
parser.add_argument('--include-ask', action='store_true',
|
||||
help='Include `ask` action in graph')
|
||||
parser.add_argument('--source', action='store', nargs='+',
|
||||
help='Limit graph to calls from *source*')
|
||||
parser.add_argument('--target', action='store', nargs='+',
|
||||
help='Limit graph to calls to *target*')
|
||||
parser.add_argument('--service', action='store', nargs='+',
|
||||
help='Limit graph to *service*')
|
||||
parser.add_argument('--output', action='store',
|
||||
help='Write to *output* instead of stdout')
|
||||
parser.add_argument('--policy-dir', action='store',
|
||||
default=qubespolicy.POLICY_DIR,
|
||||
help='Look for policy in *policy-dir*')
|
||||
parser.add_argument('--system-info', action='store',
|
||||
help='Load system information from file instead of querying qubesd')
|
||||
parser.add_argument('--skip-labels', action='store_true',
|
||||
help='Do not include service names on the graph, also deduplicate '
|
||||
'connections.')
|
||||
|
||||
def handle_single_action(args, action):
|
||||
'''Get single policy action and output (or not) a line to add'''
|
||||
if args.skip_labels:
|
||||
service = ''
|
||||
else:
|
||||
service = action.service
|
||||
if action.action == qubespolicy.Action.ask:
|
||||
if args.include_ask:
|
||||
# handle forced target=
|
||||
if len(action.targets_for_ask) == 1:
|
||||
return ' "{}" -> "{}" [label="{}" color=orange];\n'.format(
|
||||
action.source, action.targets_for_ask[0], service)
|
||||
return ' "{}" -> "{}" [label="{}" color=orange];\n'.format(
|
||||
action.source, action.original_target, service)
|
||||
elif action.action == qubespolicy.Action.allow:
|
||||
return ' "{}" -> "{}" [label="{}" color=red];\n'.format(
|
||||
action.source, action.target, service)
|
||||
return ''
|
||||
|
||||
def main(args=None):
|
||||
args = parser.parse_args(args)
|
||||
|
||||
output = sys.stdout
|
||||
if args.output:
|
||||
output = open(args.output, 'w')
|
||||
|
||||
if args.system_info:
|
||||
with open(args.system_info) as f_system_info:
|
||||
system_info = json.load(f_system_info)
|
||||
else:
|
||||
system_info = qubespolicy.get_system_info()
|
||||
|
||||
sources = list(system_info['domains'].keys())
|
||||
if args.source:
|
||||
sources = args.source
|
||||
|
||||
targets = list(system_info['domains'].keys())
|
||||
if args.target:
|
||||
targets = args.target
|
||||
else:
|
||||
targets.append('$dispvm')
|
||||
targets.extend('$dispvm:' + dom for dom in system_info['domains']
|
||||
if system_info['domains'][dom]['dispvm_allowed'])
|
||||
|
||||
connections = set()
|
||||
|
||||
output.write('digraph g {\n')
|
||||
for service in os.listdir(args.policy_dir):
|
||||
if os.path.isdir(os.path.join(args.policy_dir, service)):
|
||||
continue
|
||||
if args.service and service not in args.service and \
|
||||
not any(service.startswith(srv + '+') for srv in args.service):
|
||||
continue
|
||||
|
||||
policy = qubespolicy.Policy(service, args.policy_dir)
|
||||
for source in sources:
|
||||
for target in targets:
|
||||
try:
|
||||
action = policy.evaluate(system_info, source, target)
|
||||
line = handle_single_action(args, action)
|
||||
if line in connections:
|
||||
continue
|
||||
if line:
|
||||
output.write(line)
|
||||
connections.add(line)
|
||||
except qubespolicy.AccessDenied:
|
||||
continue
|
||||
|
||||
output.write('}\n')
|
||||
if args.output:
|
||||
output.close()
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
@ -31,7 +31,7 @@ tmp_policy_dir = '/tmp/policy'
|
||||
system_info = {
|
||||
'domains': {
|
||||
'dom0': {
|
||||
'tags': [],
|
||||
'tags': ['dom0-tag'],
|
||||
'type': 'AdminVM',
|
||||
'default_dispvm': 'default-dvm',
|
||||
'dispvm_allowed': False,
|
||||
@ -102,6 +102,8 @@ class TC_00_PolicyRule(qubes.tests.QubesTestCase):
|
||||
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 @@ class TC_00_PolicyRule(qubes.tests.QubesTestCase):
|
||||
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',
|
||||
@ -155,15 +159,16 @@ class TC_00_PolicyRule(qubes.tests.QubesTestCase):
|
||||
self.assertIsNone(line.default_target)
|
||||
|
||||
def test_021_line_simple(self):
|
||||
# also check spaces in action field
|
||||
line = qubespolicy.PolicyRule(
|
||||
'$tag:tag1 $type:AppVM ask,target=test-vm2,user=user',
|
||||
'$tag:tag1 $type:AppVM ask, target=test-vm2, user=user',
|
||||
'filename', 12)
|
||||
self.assertEqual(line.filename, 'filename')
|
||||
self.assertEqual(line.lineno, 12)
|
||||
self.assertEqual(line.action, qubespolicy.Action.ask)
|
||||
self.assertEqual(line.source, '$tag:tag1')
|
||||
self.assertEqual(line.target, '$type:AppVM')
|
||||
self.assertEqual(line.full_action, 'ask,target=test-vm2,user=user')
|
||||
self.assertEqual(line.full_action, 'ask, target=test-vm2, user=user')
|
||||
self.assertEqual(line.override_target, 'test-vm2')
|
||||
self.assertEqual(line.override_user, 'user')
|
||||
self.assertIsNone(line.default_target)
|
||||
@ -196,6 +201,20 @@ class TC_00_PolicyRule(qubes.tests.QubesTestCase):
|
||||
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
|
||||
@ -235,6 +254,9 @@ class TC_00_PolicyRule(qubes.tests.QubesTestCase):
|
||||
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'))
|
||||
@ -253,6 +275,15 @@ class TC_00_PolicyRule(qubes.tests.QubesTestCase):
|
||||
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'))
|
||||
@ -338,6 +369,13 @@ class TC_00_PolicyRule(qubes.tests.QubesTestCase):
|
||||
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):
|
||||
@ -485,7 +523,6 @@ class TC_10_PolicyAction(qubes.tests.QubesTestCase):
|
||||
[unittest.mock.call('test-vm2', 'internal.vm.Start')])
|
||||
self.assertEqual(mock_subprocess.mock_calls, [])
|
||||
|
||||
@unittest.mock.patch('qubespolicy.POLICY_DIR', tmp_policy_dir)
|
||||
class TC_20_Policy(qubes.tests.QubesTestCase):
|
||||
|
||||
def setUp(self):
|
||||
@ -505,7 +542,7 @@ class TC_20_Policy(qubes.tests.QubesTestCase):
|
||||
f.write('test-vm2 test-vm3 ask\n')
|
||||
f.write(' # comment \n')
|
||||
f.write('$anyvm $anyvm ask\n')
|
||||
policy = qubespolicy.Policy('test.service')
|
||||
policy = qubespolicy.Policy('test.service', tmp_policy_dir)
|
||||
self.assertEqual(policy.service, 'test.service')
|
||||
self.assertEqual(len(policy.policy_rules), 3)
|
||||
self.assertEqual(policy.policy_rules[0].source, 'test-vm1')
|
||||
@ -515,7 +552,7 @@ class TC_20_Policy(qubes.tests.QubesTestCase):
|
||||
|
||||
def test_001_not_existent(self):
|
||||
with self.assertRaises(qubespolicy.AccessDenied):
|
||||
qubespolicy.Policy('no-such.service')
|
||||
qubespolicy.Policy('no-such.service', tmp_policy_dir)
|
||||
|
||||
def test_002_include(self):
|
||||
with open(os.path.join(tmp_policy_dir, 'test.service'), 'w') as f:
|
||||
@ -524,7 +561,7 @@ class TC_20_Policy(qubes.tests.QubesTestCase):
|
||||
f.write('$anyvm $anyvm deny\n')
|
||||
with open(os.path.join(tmp_policy_dir, 'test.service2'), 'w') as f:
|
||||
f.write('test-vm3 $default allow,target=test-vm2\n')
|
||||
policy = qubespolicy.Policy('test.service')
|
||||
policy = qubespolicy.Policy('test.service', tmp_policy_dir)
|
||||
self.assertEqual(policy.service, 'test.service')
|
||||
self.assertEqual(len(policy.policy_rules), 3)
|
||||
self.assertEqual(policy.policy_rules[0].source, 'test-vm1')
|
||||
@ -557,7 +594,7 @@ class TC_20_Policy(qubes.tests.QubesTestCase):
|
||||
f.write('test-vm2 $tag:tag2 allow\n')
|
||||
f.write('$type:AppVM $default allow,target=test-vm3\n')
|
||||
f.write('$tag:tag1 $type:AppVM allow\n')
|
||||
policy = qubespolicy.Policy('test.service')
|
||||
policy = qubespolicy.Policy('test.service', tmp_policy_dir)
|
||||
self.assertEqual(policy.find_matching_rule(
|
||||
system_info, 'test-vm1', 'test-vm2'), policy.policy_rules[0])
|
||||
self.assertEqual(policy.find_matching_rule(
|
||||
@ -593,7 +630,7 @@ class TC_20_Policy(qubes.tests.QubesTestCase):
|
||||
f.write('$tag:tag1 $type:AppVM allow\n')
|
||||
f.write('test-no-dvm $dispvm allow\n')
|
||||
f.write('test-standalone $dispvm allow\n')
|
||||
policy = qubespolicy.Policy('test.service')
|
||||
policy = qubespolicy.Policy('test.service', tmp_policy_dir)
|
||||
self.assertCountEqual(policy.collect_targets_for_ask(system_info,
|
||||
'test-vm1'), ['test-vm1', 'test-vm2', 'test-vm3',
|
||||
'$dispvm:test-vm3',
|
||||
@ -614,7 +651,7 @@ class TC_20_Policy(qubes.tests.QubesTestCase):
|
||||
with open(os.path.join(tmp_policy_dir, 'test.service'), 'w') as f:
|
||||
f.write('test-vm1 test-vm2 allow\n')
|
||||
|
||||
policy = qubespolicy.Policy('test.service')
|
||||
policy = qubespolicy.Policy('test.service', tmp_policy_dir)
|
||||
action = policy.evaluate(system_info, 'test-vm1', 'test-vm2')
|
||||
self.assertEqual(action.rule, policy.policy_rules[0])
|
||||
self.assertEqual(action.action, qubespolicy.Action.allow)
|
||||
@ -633,7 +670,7 @@ class TC_20_Policy(qubes.tests.QubesTestCase):
|
||||
f.write('$tag:tag2 $anyvm allow\n')
|
||||
f.write('test-vm3 $anyvm deny\n')
|
||||
|
||||
policy = qubespolicy.Policy('test.service')
|
||||
policy = qubespolicy.Policy('test.service', tmp_policy_dir)
|
||||
action = policy.evaluate(system_info, 'test-vm1', '$default')
|
||||
self.assertEqual(action.rule, policy.policy_rules[1])
|
||||
self.assertEqual(action.action, qubespolicy.Action.allow)
|
||||
@ -655,7 +692,7 @@ class TC_20_Policy(qubes.tests.QubesTestCase):
|
||||
f.write('$tag:tag2 $anyvm allow\n')
|
||||
f.write('test-vm3 $anyvm deny\n')
|
||||
|
||||
policy = qubespolicy.Policy('test.service')
|
||||
policy = qubespolicy.Policy('test.service', tmp_policy_dir)
|
||||
action = policy.evaluate(system_info, 'test-standalone', 'test-vm2')
|
||||
self.assertEqual(action.rule, policy.policy_rules[2])
|
||||
self.assertEqual(action.action, qubespolicy.Action.ask)
|
||||
@ -676,7 +713,7 @@ class TC_20_Policy(qubes.tests.QubesTestCase):
|
||||
f.write('$tag:tag2 $anyvm allow\n')
|
||||
f.write('test-vm3 $anyvm deny\n')
|
||||
|
||||
policy = qubespolicy.Policy('test.service')
|
||||
policy = qubespolicy.Policy('test.service', tmp_policy_dir)
|
||||
action = policy.evaluate(system_info, 'test-standalone', 'test-vm3')
|
||||
self.assertEqual(action.rule, policy.policy_rules[3])
|
||||
self.assertEqual(action.action, qubespolicy.Action.ask)
|
||||
@ -688,6 +725,19 @@ class TC_20_Policy(qubes.tests.QubesTestCase):
|
||||
'default-dvm', '$dispvm:default-dvm', 'test-invalid-dvm',
|
||||
'test-no-dvm', 'test-template', 'test-standalone'])
|
||||
|
||||
def test_034_eval_resolve_dispvm(self):
|
||||
with open(os.path.join(tmp_policy_dir, 'test.service'), 'w') as f:
|
||||
f.write('test-vm3 $dispvm allow\n')
|
||||
|
||||
policy = qubespolicy.Policy('test.service', tmp_policy_dir)
|
||||
action = policy.evaluate(system_info, 'test-vm3', '$dispvm')
|
||||
self.assertEqual(action.rule, policy.policy_rules[0])
|
||||
self.assertEqual(action.action, qubespolicy.Action.allow)
|
||||
self.assertEqual(action.target, '$dispvm:default-dvm')
|
||||
self.assertEqual(action.original_target, '$dispvm')
|
||||
self.assertEqual(action.service, 'test.service')
|
||||
self.assertIsNone(action.targets_for_ask)
|
||||
|
||||
|
||||
class TC_30_Misc(qubes.tests.QubesTestCase):
|
||||
@unittest.mock.patch('socket.socket')
|
||||
|
@ -213,7 +213,9 @@ fi
|
||||
/usr/bin/qubesd*
|
||||
/usr/bin/qrexec-policy
|
||||
/usr/bin/qrexec-policy-agent
|
||||
/usr/bin/qrexec-policy-graph
|
||||
|
||||
%{_mandir}/man1/qrexec-policy-graph.1*
|
||||
%{_mandir}/man1/qubes*.1*
|
||||
|
||||
%dir %{python3_sitelib}/qubes-*.egg-info
|
||||
@ -372,6 +374,7 @@ fi
|
||||
%{python3_sitelib}/qubespolicy/gtkhelpers.py
|
||||
%{python3_sitelib}/qubespolicy/rpcconfirmation.py
|
||||
%{python3_sitelib}/qubespolicy/utils.py
|
||||
%{python3_sitelib}/qubespolicy/graph.py
|
||||
|
||||
%dir %{python3_sitelib}/qubespolicy/tests
|
||||
%dir %{python3_sitelib}/qubespolicy/tests/__pycache__
|
||||
@ -410,7 +413,10 @@ fi
|
||||
/etc/xen/scripts/block-origin
|
||||
/etc/xen/scripts/vif-route-qubes
|
||||
%attr(0664,root,qubes) %config(noreplace) /etc/qubes-rpc/policy/admin.*
|
||||
%attr(0664,root,qubes) %config(noreplace) /etc/qubes-rpc/policy/include/admin-all
|
||||
%attr(0664,root,qubes) %config(noreplace) /etc/qubes-rpc/policy/include/admin-local-ro
|
||||
%attr(0664,root,qubes) %config(noreplace) /etc/qubes-rpc/policy/include/admin-local-rwx
|
||||
%attr(0664,root,qubes) %config(noreplace) /etc/qubes-rpc/policy/include/admin-global-ro
|
||||
%attr(0664,root,qubes) %config(noreplace) /etc/qubes-rpc/policy/include/admin-global-rwx
|
||||
%attr(0664,root,qubes) %config(noreplace) /etc/qubes-rpc/policy/qubes.FeaturesRequest
|
||||
%attr(0664,root,qubes) %config(noreplace) /etc/qubes-rpc/policy/qubes.Filecopy
|
||||
%attr(0664,root,qubes) %config(noreplace) /etc/qubes-rpc/policy/qubes.GetImageRGBA
|
||||
|
1
setup.py
1
setup.py
@ -34,6 +34,7 @@ if __name__ == '__main__':
|
||||
'console_scripts': list(get_console_scripts()) + [
|
||||
'qrexec-policy = qubespolicy.cli:main',
|
||||
'qrexec-policy-agent = qubespolicy.agent:main',
|
||||
'qrexec-policy-graph = qubespolicy.graph:main',
|
||||
],
|
||||
'qubes.vm': [
|
||||
'AppVM = qubes.vm.appvm:AppVM',
|
||||
|
Loading…
Reference in New Issue
Block a user